diff --git a/src/Plugins/SimplnxCore/CMakeLists.txt b/src/Plugins/SimplnxCore/CMakeLists.txt index 9f644cc27d..92f4ca1dda 100644 --- a/src/Plugins/SimplnxCore/CMakeLists.txt +++ b/src/Plugins/SimplnxCore/CMakeLists.txt @@ -44,6 +44,7 @@ set(FilterList ComputeVectorColorsFilter ComputeVertexToTriangleDistancesFilter ComputeVolumeFractionsFilter + ConcatenateDataArraysFilter ConditionalSetValueFilter ConvertColorToGrayScaleFilter ConvertDataFilter @@ -167,6 +168,7 @@ set(AlgorithmList ComputeTriangleGeomCentroids ComputeVectorColors ComputeVertexToTriangleDistances + ConcatenateDataArrays ConvertColorToGrayScale ConvertData CreatePythonSkeleton diff --git a/src/Plugins/SimplnxCore/docs/ConcatenateDataArraysFilter.md b/src/Plugins/SimplnxCore/docs/ConcatenateDataArraysFilter.md new file mode 100644 index 0000000000..afc9ffd500 --- /dev/null +++ b/src/Plugins/SimplnxCore/docs/ConcatenateDataArraysFilter.md @@ -0,0 +1,23 @@ +# Concatenate Data Arrays + +## Group (Subgroup) + +Core (Generation) + +## Description + +This **Filter** concatenates multiple input arrays by taking a list of input arrays and appending their data sequentially into a single output array. The concatenation process involves combining the arrays such that the order of the input arrays directly affects the structure of the output. For example, if the first input array contains 5 tuples and the second contains 7 tuples, the resulting output array will have 12 tuples, with the tuples from the second array appended directly after those from the first array. + +This filter is designed to handle arrays of matching array types and component dimensions. If the arrays have different array types or component dimensions, the filter will raise an error. + +% Auto generated parameter table will be inserted here + +## Example Pipelines + +## License & Copyright + +Please see the description file distributed with this **Plugin** + +## DREAM3D-NX Help + +If you need help, need to file a bug report or want to request a new feature, please head over to the [DREAM3DNX-Issues](https://github.com/BlueQuartzSoftware/DREAM3DNX-Issues/discussions) GitHub site where the community of DREAM3D-NX users can help answer your questions. diff --git a/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/ConcatenateDataArrays.cpp b/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/ConcatenateDataArrays.cpp new file mode 100644 index 0000000000..ca1b582ee1 --- /dev/null +++ b/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/ConcatenateDataArrays.cpp @@ -0,0 +1,44 @@ +#include "ConcatenateDataArrays.hpp" + +#include "simplnx/DataStructure/IArray.hpp" + +using namespace nx::core; + +// ----------------------------------------------------------------------------- +ConcatenateDataArrays::ConcatenateDataArrays(DataStructure& dataStructure, const IFilter::MessageHandler& msgHandler, const std::atomic_bool& shouldCancel, + ConcatenateDataArraysInputValues* inputValues) +: m_DataStructure(dataStructure) +, m_InputValues(inputValues) +, m_ShouldCancel(shouldCancel) +, m_MessageHandler(msgHandler) +{ +} + +// ----------------------------------------------------------------------------- +ConcatenateDataArrays::~ConcatenateDataArrays() noexcept = default; + +// ----------------------------------------------------------------------------- +const std::atomic_bool& ConcatenateDataArrays::getCancel() +{ + return m_ShouldCancel; +} + +// ----------------------------------------------------------------------------- +Result<> ConcatenateDataArrays::operator()() +{ + const auto& outputDataArray = m_DataStructure.getDataRefAs(m_InputValues->OutputArrayPath); + switch(outputDataArray.getArrayType()) + { + case IArray::ArrayType::DataArray: { + return ConcatenateArrays(m_DataStructure, m_InputValues->InputArrayPaths, m_InputValues->OutputArrayPath, m_MessageHandler, m_ShouldCancel); + } + case IArray::ArrayType::StringArray: { + return ConcatenateArraysImpl(m_DataStructure, m_InputValues->InputArrayPaths, m_InputValues->OutputArrayPath, m_MessageHandler, m_ShouldCancel); + } + case IArray::ArrayType::NeighborListArray: { + return ConcatenateNeighborLists(m_DataStructure, m_InputValues->InputArrayPaths, m_InputValues->OutputArrayPath, m_MessageHandler, m_ShouldCancel); + } + case IArray::ArrayType::Any: + return MakeErrorResult(-3001, "The input arrays list has array type 'Any'. Only array types 'DataArray', 'StringArray', and NeighborList' are allowed."); + } +} diff --git a/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/ConcatenateDataArrays.hpp b/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/ConcatenateDataArrays.hpp new file mode 100644 index 0000000000..d1821a470d --- /dev/null +++ b/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/ConcatenateDataArrays.hpp @@ -0,0 +1,151 @@ +#pragma once + +#include "SimplnxCore/SimplnxCore_export.hpp" + +#include "simplnx/DataStructure/DataArray.hpp" +#include "simplnx/DataStructure/DataStructure.hpp" +#include "simplnx/DataStructure/NeighborList.hpp" +#include "simplnx/DataStructure/StringArray.hpp" +#include "simplnx/Filter/IFilter.hpp" +#include "simplnx/Utilities/DataArrayUtilities.hpp" +#include "simplnx/Utilities/FilterUtilities.hpp" + +namespace nx::core +{ +template +struct is_allowed_array_type : std::false_type +{ +}; + +template +struct is_allowed_array_type> : std::true_type +{ +}; + +template <> +struct is_allowed_array_type : std::true_type +{ +}; + +template +typename std::enable_if::value, Result<>>::type ConcatenateArraysImpl(DataStructure& dataStructure, const std::vector& inputArrayPaths, + const DataPath& outputArrayPath, const IFilter::MessageHandler& messageHandler, + const std::atomic_bool& shouldCancel) +{ + auto& outputDataArray = dataStructure.getDataRefAs(outputArrayPath); + usize destTupleOffset = 0; + for(const auto& inputArrayPath : inputArrayPaths) + { + if(shouldCancel) + { + return {}; + } + + messageHandler({IFilter::Message::Type::Info, fmt::format("Concatenating array '{}'...", inputArrayPath.toString())}); + const auto& inputDataArray = dataStructure.getDataRefAs(inputArrayPath); + auto result = CopyFromArray::CopyData(inputDataArray, outputDataArray, destTupleOffset, 0, inputDataArray.getNumberOfTuples()); + if(result.invalid()) + { + return result; + } + destTupleOffset += inputDataArray.getNumberOfTuples(); + } + + return {}; +} + +template +Result<> ConcatenateNeighborListsImpl(DataStructure& dataStructure, const std::vector& inputArrayPaths, const DataPath& outputArrayPath, const IFilter::MessageHandler& messageHandler, + const std::atomic_bool& shouldCancel) +{ + auto& outputNeighborList = dataStructure.getDataRefAs>(outputArrayPath); + int32 currentOutputTuple = 0; + for(const auto& inputNeighborListPath : inputArrayPaths) + { + if(shouldCancel) + { + return {}; + } + + messageHandler({IFilter::Message::Type::Info, fmt::format("Concatenating neighbor list '{}'...", inputNeighborListPath.toString())}); + const auto& inputNeighborList = dataStructure.getDataRefAs>(inputNeighborListPath); + for(int32 listIdx = 0; listIdx < inputNeighborList.getNumberOfLists(); ++listIdx) + { + outputNeighborList.setList(currentOutputTuple, inputNeighborList.getList(listIdx)); + currentOutputTuple++; + } + } + + return {}; +} + +struct SIMPLNXCORE_EXPORT ConcatenateDataArraysInputValues +{ + std::vector InputArrayPaths; + DataPath OutputArrayPath; +}; + +/** + * @class + */ +class SIMPLNXCORE_EXPORT ConcatenateDataArrays +{ +public: + ConcatenateDataArrays(DataStructure& dataStructure, const IFilter::MessageHandler& msgHandler, const std::atomic_bool& shouldCancel, ConcatenateDataArraysInputValues* inputValues); + ~ConcatenateDataArrays() noexcept; + + ConcatenateDataArrays(const ConcatenateDataArrays&) = delete; + ConcatenateDataArrays(ConcatenateDataArrays&&) noexcept = delete; + ConcatenateDataArrays& operator=(const ConcatenateDataArrays&) = delete; + ConcatenateDataArrays& operator=(ConcatenateDataArrays&&) noexcept = delete; + + Result<> operator()(); + + const std::atomic_bool& getCancel(); + +private: + DataStructure& m_DataStructure; + const ConcatenateDataArraysInputValues* m_InputValues = nullptr; + const std::atomic_bool& m_ShouldCancel; + const IFilter::MessageHandler& m_MessageHandler; + + struct ConcatenateDataArraysTemplateImpl + { + template + void operator()(DataStructure& dataStructure, const std::vector& inputArrayPaths, const DataPath& outputArrayPath, const IFilter::MessageHandler& messageHandler, + const std::atomic_bool& shouldCancel, Result<>& result) + { + result = ConcatenateArraysImpl>(dataStructure, inputArrayPaths, outputArrayPath, messageHandler, shouldCancel); + } + }; + + struct ConcatenateNeighborListsTemplateImpl + { + template + void operator()(DataStructure& dataStructure, const std::vector& inputArrayPaths, const DataPath& outputArrayPath, const IFilter::MessageHandler& messageHandler, + const std::atomic_bool& shouldCancel, Result<>& result) + { + result = ConcatenateNeighborListsImpl(dataStructure, inputArrayPaths, outputArrayPath, messageHandler, shouldCancel); + } + }; + + static Result<> ConcatenateArrays(DataStructure& dataStructure, const std::vector& inputArrayPaths, const DataPath& outputArrayPath, const IFilter::MessageHandler& messageHandler, + const std::atomic_bool& shouldCancel) + { + const auto& outputDataArray = dataStructure.getDataRefAs(outputArrayPath); + Result<> result; + ExecuteDataFunction(ConcatenateDataArraysTemplateImpl{}, outputDataArray.getDataType(), dataStructure, inputArrayPaths, outputArrayPath, messageHandler, shouldCancel, result); + return result; + } + + static Result<> ConcatenateNeighborLists(DataStructure& dataStructure, const std::vector& inputArrayPaths, const DataPath& outputArrayPath, const IFilter::MessageHandler& messageHandler, + const std::atomic_bool& shouldCancel) + { + const auto& outputNeighborList = dataStructure.getDataRefAs(outputArrayPath); + Result<> result; + ExecuteNeighborFunction(ConcatenateNeighborListsTemplateImpl{}, outputNeighborList.getDataType(), dataStructure, inputArrayPaths, outputArrayPath, messageHandler, shouldCancel, result); + return result; + } +}; + +} // namespace nx::core diff --git a/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/ConcatenateDataArraysFilter.cpp b/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/ConcatenateDataArraysFilter.cpp new file mode 100644 index 0000000000..bd74b39864 --- /dev/null +++ b/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/ConcatenateDataArraysFilter.cpp @@ -0,0 +1,234 @@ +#include "ConcatenateDataArraysFilter.hpp" + +#include "simplnx/DataStructure/DataPath.hpp" +#include "simplnx/DataStructure/IDataArray.hpp" +#include "simplnx/Filter/Actions/CreateArrayAction.hpp" +#include "simplnx/Filter/Actions/CreateNeighborListAction.hpp" +#include "simplnx/Filter/Actions/CreateStringArrayAction.hpp" +#include "simplnx/Parameters/ArrayCreationParameter.hpp" +#include "simplnx/Parameters/DynamicTableParameter.hpp" +#include "simplnx/Parameters/MultiArraySelectionParameter.hpp" +#include "simplnx/Utilities/DataArrayUtilities.hpp" + +#include "SimplnxCore/Filters/Algorithms/ConcatenateDataArrays.hpp" + +using namespace nx::core; + +namespace +{ +std::string createVectorString(const std::vector& vec, const std::string& separator = "x") +{ + std::ostringstream oss; + + // Use a range-based for loop and append the separator manually + for(size_t i = 0; i < vec.size(); ++i) + { + if(i != 0) + { + oss << separator; + } + oss << vec[i]; + } + + return oss.str(); +} +} // namespace + +namespace nx::core +{ +//------------------------------------------------------------------------------ +std::string ConcatenateDataArraysFilter::name() const +{ + return FilterTraits::name; +} + +//------------------------------------------------------------------------------ +std::string ConcatenateDataArraysFilter::className() const +{ + return FilterTraits::className; +} + +//------------------------------------------------------------------------------ +Uuid ConcatenateDataArraysFilter::uuid() const +{ + return FilterTraits::uuid; +} + +//------------------------------------------------------------------------------ +std::string ConcatenateDataArraysFilter::humanName() const +{ + return "Concatenate Data Arrays"; +} + +//------------------------------------------------------------------------------ +std::vector ConcatenateDataArraysFilter::defaultTags() const +{ + return {className(), "Concatenate", "Data Array", "Join", "Make", "Append", "Push", "Pushback"}; +} + +//------------------------------------------------------------------------------ +Parameters ConcatenateDataArraysFilter::parameters() const +{ + Parameters params; + + params.insertSeparator(Parameters::Separator{"Input Parameters"}); + params.insert(std::make_unique(k_InputArrays_Key, "Arrays To Concatenate", + "Select the arrays that will be concatenated together. The arrays will be concatenated in the order they are listed here.", + std::vector{}, MultiArraySelectionParameter::AllowedTypes{IArray::ArrayType::Any}, GetAllDataTypes())); + + params.insertSeparator(Parameters::Separator{"Output Parameters"}); + params.insert(std::make_unique(k_OutputArray_Key, "Output Array", "The output array that contains the concatenated arrays.", DataPath({"Concatenated Array"}))); + DynamicTableInfo tableInfo; + tableInfo.setRowsInfo(DynamicTableInfo::StaticVectorInfo(1)); + tableInfo.setColsInfo(DynamicTableInfo::DynamicVectorInfo(1, "DIM {}")); + + params.insert(std::make_unique(k_OutputTupleDims_Key, "Output Array Tuple Dimensions (Slowest to Fastest)", + "The tuple dimensions for the output data array, from slowest to fastest.", tableInfo)); + + return params; +} + +//------------------------------------------------------------------------------ +IFilter::UniquePointer ConcatenateDataArraysFilter::clone() const +{ + return std::make_unique(); +} + +//------------------------------------------------------------------------------ +IFilter::PreflightResult ConcatenateDataArraysFilter::preflightImpl(const DataStructure& dataStructure, const Arguments& filterArgs, const MessageHandler& messageHandler, + const std::atomic_bool& shouldCancel) const +{ + auto inputArrayPaths = filterArgs.value(k_InputArrays_Key); + auto outputArrayPath = filterArgs.value(k_OutputArray_Key); + auto outputTupleDimsTableData = filterArgs.value(k_OutputTupleDims_Key); + + if(inputArrayPaths.empty()) + { + return MakePreflightErrorResult(to_underlying(ErrorCodes::EmptyInputArrays), "No input arrays have been selected. Please select at least 2 input arrays."); + } + + if(inputArrayPaths.size() == 1) + { + return MakePreflightErrorResult(to_underlying(ErrorCodes::OneInputArray), "Only one input array has been selected. Please select at least 2 input arrays."); + } + + std::vector tDims = {}; + const auto& rowData = outputTupleDimsTableData.at(0); + tDims.reserve(rowData.size()); + for(usize col = 0; col < rowData.size(); ++col) + { + auto floatValue = rowData[col]; + if(floatValue <= 0) + { + return MakePreflightErrorResult(to_underlying(ErrorCodes::NonPositiveTupleDimValue), + fmt::format("Tuple dimension value '{}' (column {}) must be a positive integer.", static_cast(floatValue), col)); + } + + tDims.push_back(static_cast(floatValue)); + } + usize totalTuples = std::accumulate(tDims.begin(), tDims.end(), static_cast(1), std::multiplies<>()); + + // Check for unequal array types, data types, and component dimensions + std::vector cDims; + IArray::ArrayType arrayType; + std::string arrayTypeName; + usize tupleCount = 0; + for(usize i = 0; i < inputArrayPaths.size(); ++i) + { + const auto& inputDataArray = dataStructure.getDataRefAs(inputArrayPaths[i]); + for(usize j = i + 1; j < inputArrayPaths.size(); ++j) + { + const auto& inputDataArray2 = dataStructure.getDataRefAs(inputArrayPaths[j]); + + if(inputDataArray.getTypeName() != inputDataArray2.getTypeName()) + { + return MakePreflightErrorResult(to_underlying(ErrorCodes::TypeNameMismatch), + fmt::format("Input array '{}' has array type '{}', but input array '{}' has array type '{}'. The array types must match.", inputArrayPaths[i].toString(), + inputDataArray.getTypeName(), inputArrayPaths[j].toString(), inputDataArray2.getTypeName())); + } + + if(inputDataArray.getComponentShape() != inputDataArray2.getComponentShape()) + { + std::string firstArrayComponentsStr = createVectorString(inputDataArray.getComponentShape()); + std::string secondArrayComponentsStr = createVectorString(inputDataArray2.getComponentShape()); + return MakePreflightErrorResult(to_underlying(ErrorCodes::ComponentShapeMismatch), + fmt::format("Input array '{}' has component shape '{}', but input array '{}' has component shape '{}'. The component shapes must match.", + inputArrayPaths[i].toString(), firstArrayComponentsStr, inputArrayPaths[j].toString(), secondArrayComponentsStr)); + } + + cDims = inputDataArray.getComponentShape(); + arrayType = inputDataArray.getArrayType(); + arrayTypeName = inputDataArray.getTypeName(); + } + + auto tupleShape = inputDataArray.getTupleShape(); + tupleCount += std::accumulate(tupleShape.begin(), tupleShape.end(), static_cast(1), std::multiplies<>()); + } + + if(tupleCount != totalTuples) + { + std::string tDimsStr = createVectorString(tDims); + return MakePreflightErrorResult( + to_underlying(ErrorCodes::TotalTuplesMismatch), + fmt::format("The chosen input arrays have a combined total tuple count of {}, but the chosen tuple dimensions ([{}]) have a combined total tuple count of {}. These tuple counts must match.", + tupleCount, tDimsStr, totalTuples)); + } + + // Create the output array + nx::core::Result resultOutputActions; + + if((arrayType == IArray::ArrayType::NeighborListArray || arrayType == IArray::ArrayType::StringArray) && tDims.size() > 1) + { + std::string tDimsStr = createVectorString(tDims, ", "); + resultOutputActions.warnings().push_back( + Warning{to_underlying(WarningCodes::MultipleTupleDimsNotSupported), + fmt::format("The input arrays have array type {}, but the chosen tuple dimensions have more than one dimension. Since " + "array type {} does not support having multiple tuple dimensions, the output array will have tuple dimensions [{}] instead of tuple dimensions [{}].", + arrayTypeName, arrayTypeName, tupleCount, tDimsStr)}); + } + + switch(arrayType) + { + case IArray::ArrayType::DataArray: { + const auto& inputDataArray = dataStructure.getDataRefAs(inputArrayPaths[0]); + auto action = std::make_unique(inputDataArray.getDataType(), tDims, cDims, outputArrayPath); + resultOutputActions.value().appendAction(std::move(action)); + break; + } + case IArray::ArrayType::StringArray: { + auto action = std::make_unique(tDims, outputArrayPath); + resultOutputActions.value().appendAction(std::move(action)); + break; + } + case IArray::ArrayType::NeighborListArray: { + const auto& inputNeighborList = dataStructure.getDataRefAs(inputArrayPaths[0]); + auto action = std::make_unique(inputNeighborList.getDataType(), totalTuples, outputArrayPath); + resultOutputActions.value().appendAction(std::move(action)); + break; + } + case IArray::ArrayType::Any: { + return MakePreflightErrorResult(to_underlying(ErrorCodes::InputArraysEqualAny), + "Every array in the input arrays list has array type 'Any'. This SHOULD NOT be possible, so please contact the developers."); + } + default: + return MakePreflightErrorResult( + to_underlying(ErrorCodes::InputArraysUnsupported), + fmt::format("Every array in the input arrays list has array type '{}'. This is an array type that is currently not supported by this filter, so please contact the developers.", + arrayTypeName)); + } + + return {std::move(resultOutputActions)}; +} + +//------------------------------------------------------------------------------ +Result<> ConcatenateDataArraysFilter::executeImpl(DataStructure& dataStructure, const Arguments& args, const PipelineFilter* pipelineNode, const MessageHandler& messageHandler, + const std::atomic_bool& shouldCancel) const +{ + ConcatenateDataArraysInputValues inputValues; + + inputValues.InputArrayPaths = args.value(k_InputArrays_Key); + inputValues.OutputArrayPath = args.value(k_OutputArray_Key); + + return ConcatenateDataArrays(dataStructure, messageHandler, shouldCancel, &inputValues)(); +} +} // namespace nx::core diff --git a/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/ConcatenateDataArraysFilter.hpp b/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/ConcatenateDataArraysFilter.hpp new file mode 100644 index 0000000000..729ce20725 --- /dev/null +++ b/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/ConcatenateDataArraysFilter.hpp @@ -0,0 +1,118 @@ +#pragma once + +#include "SimplnxCore/SimplnxCore_export.hpp" + +#include "simplnx/Filter/FilterTraits.hpp" +#include "simplnx/Filter/IFilter.hpp" + +namespace nx::core +{ +class SIMPLNXCORE_EXPORT ConcatenateDataArraysFilter : public IFilter +{ +public: + ConcatenateDataArraysFilter() = default; + ~ConcatenateDataArraysFilter() noexcept override = default; + + ConcatenateDataArraysFilter(const ConcatenateDataArraysFilter&) = delete; + ConcatenateDataArraysFilter(ConcatenateDataArraysFilter&&) noexcept = delete; + + ConcatenateDataArraysFilter& operator=(const ConcatenateDataArraysFilter&) = delete; + ConcatenateDataArraysFilter& operator=(ConcatenateDataArraysFilter&&) noexcept = delete; + + // Parameter Keys + static inline constexpr StringLiteral k_InputArrays_Key = "input_arrays"; + static inline constexpr StringLiteral k_OutputArray_Key = "output_array"; + static inline constexpr StringLiteral k_OutputTupleDims_Key = "output_tuple_dims"; + + // Error Codes + enum class ErrorCodes : int32 + { + EmptyInputArrays = -2300, + OneInputArray = -2301, + NonPositiveTupleDimValue = -2302, + TypeNameMismatch = -2303, + ComponentShapeMismatch = -2304, + TotalTuplesMismatch = -2305, + InputArraysEqualAny = -2306, + InputArraysUnsupported = -2307 + }; + + // Warning Codes + enum class WarningCodes : int32 + { + MultipleTupleDimsNotSupported = -100 + }; + + /** + * @brief Reads SIMPL json and converts it simplnx Arguments. + * @param json + * @return Result + */ + static Result FromSIMPLJson(const nlohmann::json& json); + + /** + * @brief Returns the filter's name. + * @return std::string + */ + std::string name() const override; + + /** + * @brief Returns the C++ classname of this filter. + * @return std::string + */ + std::string className() const override; + + /** + * @brief Returns the filter's UUID. + * @return Uuid + */ + Uuid uuid() const override; + + /** + * @brief Returns the filter name as a human-readable string. + * @return std::string + */ + std::string humanName() const override; + + /** + * @brief Returns the default tags for this filter. + * @return + */ + std::vector defaultTags() const override; + + /** + * @brief Returns a collection of parameters required to execute the filter. + * @return Parameters + */ + Parameters parameters() const override; + + /** + * @brief Creates and returns a copy of the filter. + * @return UniquePointer + */ + UniquePointer clone() const override; + +protected: + /** + * @brief + * @param data + * @param filterArgs + * @param messageHandler + * @return Result + */ + PreflightResult preflightImpl(const DataStructure& dataStructure, const Arguments& filterArgs, const MessageHandler& messageHandler, const std::atomic_bool& shouldCancel) const override; + + /** + * @brief + * @param dataStructure + * @param args + * @param pipelineNode + * @param messageHandler + * @return Result<> + */ + Result<> executeImpl(DataStructure& dataStructure, const Arguments& args, const PipelineFilter* pipelineNode, const MessageHandler& messageHandler, + const std::atomic_bool& shouldCancel) const override; +}; +} // namespace nx::core + +SIMPLNX_DEF_FILTER_TRAITS(nx::core, ConcatenateDataArraysFilter, "e547e382-4928-4ec6-a311-d26c16e9f675"); diff --git a/src/Plugins/SimplnxCore/test/CMakeLists.txt b/src/Plugins/SimplnxCore/test/CMakeLists.txt index 93e0bab441..14d45a1368 100644 --- a/src/Plugins/SimplnxCore/test/CMakeLists.txt +++ b/src/Plugins/SimplnxCore/test/CMakeLists.txt @@ -44,6 +44,7 @@ set(${PLUGIN_NAME}UnitTest_SRCS ComputeVectorColorsTest.cpp ComputeVertexToTriangleDistancesTest.cpp ComputeVolumeFractionsTest.cpp + ConcatenateDataArraysTest.cpp ConditionalSetValueTest.cpp ConvertColorToGrayScaleTest.cpp ConvertDataTest.cpp diff --git a/src/Plugins/SimplnxCore/test/ConcatenateDataArraysTest.cpp b/src/Plugins/SimplnxCore/test/ConcatenateDataArraysTest.cpp new file mode 100644 index 0000000000..6c9d97d0d7 --- /dev/null +++ b/src/Plugins/SimplnxCore/test/ConcatenateDataArraysTest.cpp @@ -0,0 +1,492 @@ +#include "SimplnxCore/Filters/ConcatenateDataArraysFilter.hpp" +#include "SimplnxCore/SimplnxCore_test_dirs.hpp" + +#include "simplnx/Parameters/DynamicTableParameter.hpp" +#include "simplnx/UnitTest/UnitTestCommon.hpp" + +#include + +using namespace nx::core; + +namespace +{ +const std::string k_TestArray1Name = "TestArray1"; +const std::string k_TestArray2Name = "TestArray2"; +const std::string k_TestResultArrayName = "TestResultArray"; +const std::vector k_InputArrayPaths = std::vector{DataPath({k_TestArray1Name}), DataPath({k_TestArray2Name})}; +const DataPath k_OutputArrayPath = DataPath({k_TestResultArrayName}); +} // namespace + +TEMPLATE_TEST_CASE("SimplnxCore::ConcatenateDataArraysFilter: DataArrays Valid - 1 Tuple", "[SimplnxCore][ConcatenateDataArraysFilter]", bool, int8, int16, int32, int64, uint8, uint16, uint32, uint64, + float32, float64) +{ + using T = TestType; + + ConcatenateDataArraysFilter filter; + DataStructure dataStructure; + Arguments args; + + auto array1 = DataArray::template CreateWithStore>(dataStructure, k_TestArray1Name, std::vector{1}, std::vector{3}); + array1->setComponent(0, 0, 0); + array1->setComponent(0, 1, 1); + array1->setComponent(0, 2, 0); + auto array2 = DataArray::template CreateWithStore>(dataStructure, k_TestArray2Name, std::vector{1}, std::vector{3}); + array2->setComponent(0, 0, 1); + array2->setComponent(0, 1, 1); + array2->setComponent(0, 2, 1); + + args.insert(ConcatenateDataArraysFilter::k_InputArrays_Key, std::make_any>(k_InputArrayPaths)); + args.insert(ConcatenateDataArraysFilter::k_OutputArray_Key, std::make_any(k_OutputArrayPath)); + args.insert(ConcatenateDataArraysFilter::k_OutputTupleDims_Key, std::make_any(DynamicTableParameter::ValueType{{2}})); + + auto result = filter.execute(dataStructure, args); + SIMPLNX_RESULT_REQUIRE_VALID(result.result); + + REQUIRE_NOTHROW(dataStructure.getDataRefAs>(k_OutputArrayPath)); + auto outputArray = dataStructure.getDataRefAs>(k_OutputArrayPath); + + REQUIRE(outputArray.getTupleShape() == std::vector{2}); + REQUIRE(outputArray.getNumberOfComponents() == 3); + REQUIRE(outputArray[0] == 0); + REQUIRE(outputArray[1] == 1); + REQUIRE(outputArray[2] == 0); + REQUIRE(outputArray[3] == 1); + REQUIRE(outputArray[4] == 1); + REQUIRE(outputArray[5] == 1); +} + +TEMPLATE_TEST_CASE("SimplnxCore::ConcatenateDataArraysFilter: DataArrays Valid - 2 Tuples", "[SimplnxCore][ConcatenateDataArraysFilter]", bool, int8, int16, int32, int64, uint8, uint16, uint32, + uint64, float32, float64) +{ + using T = TestType; + + ConcatenateDataArraysFilter filter; + DataStructure dataStructure; + Arguments args; + + auto array1 = DataArray::template CreateWithStore>(dataStructure, k_TestArray1Name, std::vector{2}, std::vector{2}); + array1->setComponent(0, 0, 0); + array1->setComponent(0, 1, 1); + array1->setComponent(1, 0, 0); + array1->setComponent(1, 1, 0); + auto array2 = DataArray::template CreateWithStore>(dataStructure, k_TestArray2Name, std::vector{2}, std::vector{2}); + array2->setComponent(0, 0, 1); + array2->setComponent(0, 1, 1); + array2->setComponent(1, 0, 1); + array2->setComponent(1, 1, 0); + + args.insert(ConcatenateDataArraysFilter::k_InputArrays_Key, std::make_any>(k_InputArrayPaths)); + args.insert(ConcatenateDataArraysFilter::k_OutputArray_Key, std::make_any(k_OutputArrayPath)); + args.insert(ConcatenateDataArraysFilter::k_OutputTupleDims_Key, std::make_any(DynamicTableParameter::ValueType{{2, 2}})); + + auto result = filter.execute(dataStructure, args); + SIMPLNX_RESULT_REQUIRE_VALID(result.result); + + REQUIRE_NOTHROW(dataStructure.getDataRefAs>(k_OutputArrayPath)); + auto outputArray = dataStructure.getDataRefAs>(k_OutputArrayPath); + + REQUIRE(outputArray.getTupleShape() == std::vector{2, 2}); + REQUIRE(outputArray.getNumberOfComponents() == 2); + REQUIRE(outputArray[0] == 0); + REQUIRE(outputArray[1] == 1); + REQUIRE(outputArray[2] == 0); + REQUIRE(outputArray[3] == 0); + REQUIRE(outputArray[4] == 1); + REQUIRE(outputArray[5] == 1); + REQUIRE(outputArray[6] == 1); + REQUIRE(outputArray[7] == 0); +} + +TEMPLATE_TEST_CASE("SimplnxCore::ConcatenateDataArraysFilter: DataArrays Valid - 3 Tuples", "[SimplnxCore][ConcatenateDataArraysFilter]", bool, int8, int16, int32, int64, uint8, uint16, uint32, + uint64, float32, float64) +{ + using T = TestType; + + ConcatenateDataArraysFilter filter; + DataStructure dataStructure; + Arguments args; + + auto array1 = DataArray::template CreateWithStore>(dataStructure, k_TestArray1Name, std::vector{3}, std::vector{1}); + array1->setValue(0, 0); + array1->setValue(1, 1); + array1->setValue(2, 0); + auto array2 = DataArray::template CreateWithStore>(dataStructure, k_TestArray2Name, std::vector{3}, std::vector{1}); + array2->setValue(0, 1); + array2->setValue(1, 1); + array2->setValue(2, 1); + + args.insert(ConcatenateDataArraysFilter::k_InputArrays_Key, std::make_any>(k_InputArrayPaths)); + args.insert(ConcatenateDataArraysFilter::k_OutputArray_Key, std::make_any(k_OutputArrayPath)); + args.insert(ConcatenateDataArraysFilter::k_OutputTupleDims_Key, std::make_any(DynamicTableParameter::ValueType{{3, 2}})); + + auto result = filter.execute(dataStructure, args); + SIMPLNX_RESULT_REQUIRE_VALID(result.result); + + REQUIRE_NOTHROW(dataStructure.getDataRefAs>(k_OutputArrayPath)); + auto outputArray = dataStructure.getDataRefAs>(k_OutputArrayPath); + + REQUIRE(outputArray.getTupleShape() == std::vector{3, 2}); + REQUIRE(outputArray.getNumberOfComponents() == 1); + REQUIRE(outputArray[0] == 0); + REQUIRE(outputArray[1] == 1); + REQUIRE(outputArray[2] == 0); + REQUIRE(outputArray[3] == 1); + REQUIRE(outputArray[4] == 1); + REQUIRE(outputArray[5] == 1); +} + +TEMPLATE_TEST_CASE("SimplnxCore::ConcatenateDataArraysFilter: NeighborLists Valid - 1 List", "[SimplnxCore][ConcatenateDataArraysFilter]", int8, int16, int32, int64, uint8, uint16, uint32, uint64, + float32, float64) +{ + using T = TestType; + + ConcatenateDataArraysFilter filter; + DataStructure dataStructure; + Arguments args; + + auto inputNeighborList1 = NeighborList::Create(dataStructure, k_TestArray1Name, 1); + typename NeighborList::SharedVectorType inputList1(new std::vector({0, 1, 0})); + inputNeighborList1->setList(0, inputList1); + auto inputNeighborList2 = NeighborList::Create(dataStructure, k_TestArray2Name, 1); + typename NeighborList::SharedVectorType inputList2(new std::vector({1, 1, 1})); + inputNeighborList2->setList(0, inputList2); + + args.insert(ConcatenateDataArraysFilter::k_InputArrays_Key, std::make_any>(k_InputArrayPaths)); + args.insert(ConcatenateDataArraysFilter::k_OutputArray_Key, std::make_any(k_OutputArrayPath)); + args.insert(ConcatenateDataArraysFilter::k_OutputTupleDims_Key, std::make_any(DynamicTableParameter::ValueType{{2}})); + + auto result = filter.execute(dataStructure, args); + SIMPLNX_RESULT_REQUIRE_VALID(result.result); + + REQUIRE_NOTHROW(dataStructure.getDataRefAs>(k_OutputArrayPath)); + auto outputNeighborList = dataStructure.getDataRefAs>(k_OutputArrayPath); + + REQUIRE(outputNeighborList.getTupleShape() == std::vector{2}); + auto outputList1 = outputNeighborList.getList(0); + auto outputList2 = outputNeighborList.getList(1); + + REQUIRE(outputList1 == inputList1); + REQUIRE(outputList2 == inputList2); +} + +TEMPLATE_TEST_CASE("SimplnxCore::ConcatenateDataArraysFilter: NeighborLists Valid - 2 Lists", "[SimplnxCore][ConcatenateDataArraysFilter]", int8, int16, int32, int64, uint8, uint16, uint32, uint64, + float32, float64) +{ + using T = TestType; + + ConcatenateDataArraysFilter filter; + DataStructure dataStructure; + Arguments args; + + auto inputNeighborList1 = NeighborList::Create(dataStructure, k_TestArray1Name, 2); + typename NeighborList::SharedVectorType inputList1(new std::vector({0, 1, 0})); + inputNeighborList1->setList(0, inputList1); + typename NeighborList::SharedVectorType inputList2(new std::vector({1, 0, 0})); + inputNeighborList1->setList(1, inputList2); + auto inputNeighborList2 = NeighborList::Create(dataStructure, k_TestArray2Name, 2); + typename NeighborList::SharedVectorType inputList3(new std::vector({1, 1, 1})); + inputNeighborList2->setList(0, inputList3); + typename NeighborList::SharedVectorType inputList4(new std::vector({0, 0, 1})); + inputNeighborList2->setList(1, inputList4); + + args.insert(ConcatenateDataArraysFilter::k_InputArrays_Key, std::make_any>(k_InputArrayPaths)); + args.insert(ConcatenateDataArraysFilter::k_OutputArray_Key, std::make_any(k_OutputArrayPath)); + args.insert(ConcatenateDataArraysFilter::k_OutputTupleDims_Key, std::make_any(DynamicTableParameter::ValueType{{4}})); + + auto result = filter.execute(dataStructure, args); + SIMPLNX_RESULT_REQUIRE_VALID(result.result); + + REQUIRE_NOTHROW(dataStructure.getDataRefAs>(k_OutputArrayPath)); + auto outputNeighborList = dataStructure.getDataRefAs>(k_OutputArrayPath); + + REQUIRE(outputNeighborList.getTupleShape() == std::vector{4}); + auto outputList1 = outputNeighborList.getList(0); + auto outputList2 = outputNeighborList.getList(1); + auto outputList3 = outputNeighborList.getList(2); + auto outputList4 = outputNeighborList.getList(3); + + REQUIRE(outputList1 == inputList1); + REQUIRE(outputList2 == inputList2); + REQUIRE(outputList3 == inputList3); + REQUIRE(outputList4 == inputList4); +} + +TEMPLATE_TEST_CASE("SimplnxCore::ConcatenateDataArraysFilter: NeighborLists Valid - 3 Lists", "[SimplnxCore][ConcatenateDataArraysFilter]", int8, int16, int32, int64, uint8, uint16, uint32, uint64, + float32, float64) +{ + using T = TestType; + + ConcatenateDataArraysFilter filter; + DataStructure dataStructure; + Arguments args; + + auto inputNeighborList1 = NeighborList::Create(dataStructure, k_TestArray1Name, 3); + typename NeighborList::SharedVectorType inputList1(new std::vector({0, 1, 0})); + inputNeighborList1->setList(0, inputList1); + typename NeighborList::SharedVectorType inputList2(new std::vector({1, 0, 0})); + inputNeighborList1->setList(1, inputList2); + typename NeighborList::SharedVectorType inputList3(new std::vector({2, 2, 1})); + inputNeighborList1->setList(2, inputList3); + auto inputNeighborList2 = NeighborList::Create(dataStructure, k_TestArray2Name, 3); + typename NeighborList::SharedVectorType inputList4(new std::vector({1, 1, 1})); + inputNeighborList2->setList(0, inputList4); + typename NeighborList::SharedVectorType inputList5(new std::vector({0, 0, 1})); + inputNeighborList2->setList(1, inputList5); + typename NeighborList::SharedVectorType inputList6(new std::vector({4, 5, 6})); + inputNeighborList2->setList(2, inputList6); + + args.insert(ConcatenateDataArraysFilter::k_InputArrays_Key, std::make_any>(k_InputArrayPaths)); + args.insert(ConcatenateDataArraysFilter::k_OutputArray_Key, std::make_any(k_OutputArrayPath)); + args.insert(ConcatenateDataArraysFilter::k_OutputTupleDims_Key, std::make_any(DynamicTableParameter::ValueType{{6}})); + + auto result = filter.execute(dataStructure, args); + SIMPLNX_RESULT_REQUIRE_VALID(result.result); + + REQUIRE_NOTHROW(dataStructure.getDataRefAs>(k_OutputArrayPath)); + auto outputNeighborList = dataStructure.getDataRefAs>(k_OutputArrayPath); + + REQUIRE(outputNeighborList.getTupleShape() == std::vector{6}); + auto outputList1 = outputNeighborList.getList(0); + auto outputList2 = outputNeighborList.getList(1); + auto outputList3 = outputNeighborList.getList(2); + auto outputList4 = outputNeighborList.getList(3); + auto outputList5 = outputNeighborList.getList(4); + auto outputList6 = outputNeighborList.getList(5); + + REQUIRE(outputList1 == inputList1); + REQUIRE(outputList2 == inputList2); + REQUIRE(outputList3 == inputList3); + REQUIRE(outputList4 == inputList4); + REQUIRE(outputList5 == inputList5); + REQUIRE(outputList6 == inputList6); +} + +TEST_CASE("SimplnxCore::ConcatenateDataArraysFilter: StringArray Valid - 1 Tuple", "[SimplnxCore][ConcatenateDataArraysFilter]") +{ + ConcatenateDataArraysFilter filter; + DataStructure dataStructure; + Arguments args; + + std::string value1 = "Foo"; + std::string value2 = "Bar"; + + StringArray::CreateWithValues(dataStructure, k_TestArray1Name, {value1}); + StringArray::CreateWithValues(dataStructure, k_TestArray2Name, {value2}); + + args.insert(ConcatenateDataArraysFilter::k_InputArrays_Key, std::make_any>(k_InputArrayPaths)); + args.insert(ConcatenateDataArraysFilter::k_OutputArray_Key, std::make_any(k_OutputArrayPath)); + args.insert(ConcatenateDataArraysFilter::k_OutputTupleDims_Key, std::make_any(DynamicTableParameter::ValueType{{2}})); + + auto result = filter.execute(dataStructure, args); + SIMPLNX_RESULT_REQUIRE_VALID(result.result); + + REQUIRE_NOTHROW(dataStructure.getDataRefAs(k_OutputArrayPath)); + auto outputArray = dataStructure.getDataRefAs(k_OutputArrayPath); + + REQUIRE(outputArray.getTupleShape() == std::vector{2}); + REQUIRE(outputArray[0] == value1); + REQUIRE(outputArray[1] == value2); +} + +TEST_CASE("SimplnxCore::ConcatenateDataArraysFilter: StringArray Valid - 2 Tuples", "[SimplnxCore][ConcatenateDataArraysFilter]") +{ + ConcatenateDataArraysFilter filter; + DataStructure dataStructure; + Arguments args; + + std::string value1 = "Foo"; + std::string value2 = "Bar"; + std::string value3 = "Baz"; + std::string value4 = "Fizzle"; + + StringArray::CreateWithValues(dataStructure, k_TestArray1Name, {value1, value2}); + StringArray::CreateWithValues(dataStructure, k_TestArray2Name, {value3, value4}); + + args.insert(ConcatenateDataArraysFilter::k_InputArrays_Key, std::make_any>(k_InputArrayPaths)); + args.insert(ConcatenateDataArraysFilter::k_OutputArray_Key, std::make_any(k_OutputArrayPath)); + args.insert(ConcatenateDataArraysFilter::k_OutputTupleDims_Key, std::make_any(DynamicTableParameter::ValueType{{4}})); + + auto result = filter.execute(dataStructure, args); + SIMPLNX_RESULT_REQUIRE_VALID(result.result); + + REQUIRE_NOTHROW(dataStructure.getDataRefAs(k_OutputArrayPath)); + auto outputArray = dataStructure.getDataRefAs(k_OutputArrayPath); + + REQUIRE(outputArray.getTupleShape() == std::vector{4}); + REQUIRE(outputArray[0] == value1); + REQUIRE(outputArray[1] == value2); + REQUIRE(outputArray[2] == value3); + REQUIRE(outputArray[3] == value4); +} + +TEST_CASE("SimplnxCore::ConcatenateDataArraysFilter: StringArray Valid - 3 Tuples", "[SimplnxCore][ConcatenateDataArraysFilter]") +{ + ConcatenateDataArraysFilter filter; + DataStructure dataStructure; + Arguments args; + + std::string value1 = "Foo"; + std::string value2 = "Bar"; + std::string value3 = "Baz"; + std::string value4 = "Fizzle"; + std::string value5 = "Sizzle"; + std::string value6 = "Twizzler"; + + StringArray::CreateWithValues(dataStructure, k_TestArray1Name, {value1, value2, value3}); + StringArray::CreateWithValues(dataStructure, k_TestArray2Name, {value4, value5, value6}); + + args.insert(ConcatenateDataArraysFilter::k_InputArrays_Key, std::make_any>(k_InputArrayPaths)); + args.insert(ConcatenateDataArraysFilter::k_OutputArray_Key, std::make_any(k_OutputArrayPath)); + args.insert(ConcatenateDataArraysFilter::k_OutputTupleDims_Key, std::make_any(DynamicTableParameter::ValueType{{6}})); + + auto result = filter.execute(dataStructure, args); + SIMPLNX_RESULT_REQUIRE_VALID(result.result); + + REQUIRE_NOTHROW(dataStructure.getDataRefAs(k_OutputArrayPath)); + auto outputArray = dataStructure.getDataRefAs(k_OutputArrayPath); + + REQUIRE(outputArray.getTupleShape() == std::vector{6}); + REQUIRE(outputArray[0] == value1); + REQUIRE(outputArray[1] == value2); + REQUIRE(outputArray[2] == value3); + REQUIRE(outputArray[3] == value4); + REQUIRE(outputArray[4] == value5); + REQUIRE(outputArray[5] == value6); +} + +TEST_CASE("SimplnxCore::ConcatenateDataArraysFilter: Invalid Parameters", "[SimplnxCore][ConcatenateDataArraysFilter]") +{ + ConcatenateDataArraysFilter filter; + DataStructure dataStructure; + Arguments args; + + SECTION("Empty Input Arrays") + { + Int8Array::CreateWithStore(dataStructure, k_TestArray1Name, std::vector{1}, std::vector{3}); + Int8Array::CreateWithStore(dataStructure, k_TestArray2Name, std::vector{1}, std::vector{3}); + + args.insert(ConcatenateDataArraysFilter::k_InputArrays_Key, std::make_any>(std::vector{})); + args.insert(ConcatenateDataArraysFilter::k_OutputArray_Key, std::make_any(k_OutputArrayPath)); + args.insert(ConcatenateDataArraysFilter::k_OutputTupleDims_Key, std::make_any(DynamicTableParameter::ValueType{{2}})); + + auto result = filter.execute(dataStructure, args); + SIMPLNX_RESULT_REQUIRE_INVALID(result.result); + REQUIRE(result.result.errors().size() == 1); + REQUIRE(result.result.errors()[0].code == to_underlying(ConcatenateDataArraysFilter::ErrorCodes::EmptyInputArrays)); + } + SECTION("One Input Array") + { + Int8Array::CreateWithStore(dataStructure, k_TestArray1Name, std::vector{1}, std::vector{3}); + Int8Array::CreateWithStore(dataStructure, k_TestArray2Name, std::vector{1}, std::vector{3}); + + args.insert(ConcatenateDataArraysFilter::k_InputArrays_Key, std::make_any>(std::vector{DataPath({k_TestArray1Name})})); + args.insert(ConcatenateDataArraysFilter::k_OutputArray_Key, std::make_any(k_OutputArrayPath)); + args.insert(ConcatenateDataArraysFilter::k_OutputTupleDims_Key, std::make_any(DynamicTableParameter::ValueType{{2}})); + + auto result = filter.execute(dataStructure, args); + SIMPLNX_RESULT_REQUIRE_INVALID(result.result); + REQUIRE(result.result.errors().size() == 1); + REQUIRE(result.result.errors()[0].code == to_underlying(ConcatenateDataArraysFilter::ErrorCodes::OneInputArray)); + } + SECTION("Mismatching Type Names 1") + { + Int8Array::CreateWithStore(dataStructure, k_TestArray1Name, std::vector{1}, std::vector{3}); + Int32Array::CreateWithStore(dataStructure, k_TestArray2Name, std::vector{1}, std::vector{3}); + + args.insert(ConcatenateDataArraysFilter::k_InputArrays_Key, std::make_any>(std::vector{k_InputArrayPaths})); + args.insert(ConcatenateDataArraysFilter::k_OutputArray_Key, std::make_any(k_OutputArrayPath)); + args.insert(ConcatenateDataArraysFilter::k_OutputTupleDims_Key, std::make_any(DynamicTableParameter::ValueType{{2}})); + + auto result = filter.execute(dataStructure, args); + SIMPLNX_RESULT_REQUIRE_INVALID(result.result); + REQUIRE(result.result.errors().size() == 1); + REQUIRE(result.result.errors()[0].code == to_underlying(ConcatenateDataArraysFilter::ErrorCodes::TypeNameMismatch)); + } + + SECTION("Mismatching Type Names 2") + { + Int8Array::CreateWithStore(dataStructure, k_TestArray1Name, std::vector{1}, std::vector{3}); + StringArray::CreateWithValues(dataStructure, k_TestArray2Name, {"Foo"}); + + args.insert(ConcatenateDataArraysFilter::k_InputArrays_Key, std::make_any>(std::vector{k_InputArrayPaths})); + args.insert(ConcatenateDataArraysFilter::k_OutputArray_Key, std::make_any(k_OutputArrayPath)); + args.insert(ConcatenateDataArraysFilter::k_OutputTupleDims_Key, std::make_any(DynamicTableParameter::ValueType{{2}})); + + auto result = filter.execute(dataStructure, args); + SIMPLNX_RESULT_REQUIRE_INVALID(result.result); + REQUIRE(result.result.errors().size() == 1); + REQUIRE(result.result.errors()[0].code == to_underlying(ConcatenateDataArraysFilter::ErrorCodes::TypeNameMismatch)); + } + SECTION("Mismatching Component Dimensions") + { + Int8Array::CreateWithStore(dataStructure, k_TestArray1Name, std::vector{1}, std::vector{3, 4}); + Int8Array::CreateWithStore(dataStructure, k_TestArray2Name, std::vector{1}, std::vector{4, 3}); + + args.insert(ConcatenateDataArraysFilter::k_InputArrays_Key, std::make_any>(std::vector{k_InputArrayPaths})); + args.insert(ConcatenateDataArraysFilter::k_OutputArray_Key, std::make_any(k_OutputArrayPath)); + args.insert(ConcatenateDataArraysFilter::k_OutputTupleDims_Key, std::make_any(DynamicTableParameter::ValueType{{2}})); + + auto result = filter.execute(dataStructure, args); + SIMPLNX_RESULT_REQUIRE_INVALID(result.result); + REQUIRE(result.result.errors().size() == 1); + REQUIRE(result.result.errors()[0].code == to_underlying(ConcatenateDataArraysFilter::ErrorCodes::ComponentShapeMismatch)); + } + SECTION("Mismatching Tuple Counts") + { + Int8Array::CreateWithStore(dataStructure, k_TestArray1Name, std::vector{2, 3}, std::vector{3}); + Int8Array::CreateWithStore(dataStructure, k_TestArray2Name, std::vector{4, 5}, std::vector{3}); + + args.insert(ConcatenateDataArraysFilter::k_InputArrays_Key, std::make_any>(std::vector{k_InputArrayPaths})); + args.insert(ConcatenateDataArraysFilter::k_OutputArray_Key, std::make_any(k_OutputArrayPath)); + args.insert(ConcatenateDataArraysFilter::k_OutputTupleDims_Key, std::make_any(DynamicTableParameter::ValueType{{2}})); + + auto result = filter.execute(dataStructure, args); + SIMPLNX_RESULT_REQUIRE_INVALID(result.result); + REQUIRE(result.result.errors().size() == 1); + REQUIRE(result.result.errors()[0].code == to_underlying(ConcatenateDataArraysFilter::ErrorCodes::TotalTuplesMismatch)); + } + SECTION("NeighborList Multiple Tuple Dims") + { + auto inputNeighborList1 = NeighborList::Create(dataStructure, k_TestArray1Name, 2); + typename NeighborList::SharedVectorType inputList1(new std::vector({0, 1, 0})); + inputNeighborList1->setList(0, inputList1); + typename NeighborList::SharedVectorType inputList2(new std::vector({0, 1, 0})); + inputNeighborList1->setList(1, inputList2); + auto inputNeighborList2 = NeighborList::Create(dataStructure, k_TestArray2Name, 2); + typename NeighborList::SharedVectorType inputList3(new std::vector({1, 1, 1})); + inputNeighborList2->setList(0, inputList3); + typename NeighborList::SharedVectorType inputList4(new std::vector({1, 1, 1})); + inputNeighborList2->setList(1, inputList4); + + args.insert(ConcatenateDataArraysFilter::k_InputArrays_Key, std::make_any>(std::vector{k_InputArrayPaths})); + args.insert(ConcatenateDataArraysFilter::k_OutputArray_Key, std::make_any(k_OutputArrayPath)); + args.insert(ConcatenateDataArraysFilter::k_OutputTupleDims_Key, std::make_any(DynamicTableParameter::ValueType{{2, 2}})); + + auto result = filter.execute(dataStructure, args); + SIMPLNX_RESULT_REQUIRE_VALID(result.result); + REQUIRE(result.result.warnings().size() == 1); + REQUIRE(result.result.warnings()[0].code == to_underlying(ConcatenateDataArraysFilter::WarningCodes::MultipleTupleDimsNotSupported)); + + REQUIRE_NOTHROW(dataStructure.getDataRefAs>(k_OutputArrayPath)); + auto outputNeighborList = dataStructure.getDataRefAs>(k_OutputArrayPath); + REQUIRE(outputNeighborList.getTupleShape() == std::vector{4}); + } + SECTION("StringArray Multiple Tuple Dims") + { + StringArray::CreateWithValues(dataStructure, k_TestArray1Name, {"Foo", "Bar"}); + StringArray::CreateWithValues(dataStructure, k_TestArray2Name, {"Baz", "Fizzle"}); + + args.insert(ConcatenateDataArraysFilter::k_InputArrays_Key, std::make_any>(k_InputArrayPaths)); + args.insert(ConcatenateDataArraysFilter::k_OutputArray_Key, std::make_any(k_OutputArrayPath)); + args.insert(ConcatenateDataArraysFilter::k_OutputTupleDims_Key, std::make_any(DynamicTableParameter::ValueType{{2, 2}})); + + auto result = filter.execute(dataStructure, args); + SIMPLNX_RESULT_REQUIRE_VALID(result.result); + REQUIRE(result.result.warnings().size() == 1); + REQUIRE(result.result.warnings()[0].code == to_underlying(ConcatenateDataArraysFilter::WarningCodes::MultipleTupleDimsNotSupported)); + + REQUIRE_NOTHROW(dataStructure.getDataRefAs(k_OutputArrayPath)); + auto outputDataArray = dataStructure.getDataRefAs(k_OutputArrayPath); + REQUIRE(outputDataArray.getTupleShape() == std::vector{4}); + } +}