diff --git a/CMakeLists.txt b/CMakeLists.txt index fc7ac25..e9f9ffc 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -13,7 +13,7 @@ if(APPLE AND NOT DEFINED ENV{MACOSX_DEPLOYMENT_TARGET}) endif() project(LLU - VERSION 3.0.1 + VERSION 3.1.0 DESCRIPTION "Modern C++ wrapper over LibraryLink and WSTP" LANGUAGES CXX) @@ -69,7 +69,8 @@ if(NOT TARGET LLU) ${LLU_SOURCE_DIR}/FileUtilities.cpp ${LLU_SOURCE_DIR}/TypedMArgument.cpp ${LLU_SOURCE_DIR}/Containers/DataStore.cpp - ${LLU_SOURCE_DIR}/Containers/NumericArray.cpp) + ${LLU_SOURCE_DIR}/Containers/NumericArray.cpp + ${LLU_SOURCE_DIR}/Containers/SparseArray.cpp) #add the main library add_library(LLU ${LLU_SOURCE_FILES}) diff --git a/README.md b/README.md index 6fc2370..6f1a523 100644 --- a/README.md +++ b/README.md @@ -172,7 +172,6 @@ Limitations with respect to LibraryLink There are a few LibraryLink features currently not covered by LLU, most notably: -- Sparse Arrays - Tensor subsetting: `MTensor_getTensor` - Callbacks - Wolfram IO Library (asynchronous tasks) diff --git a/docs/conf.py.in b/docs/conf.py.in index 81f3fa7..b0ad603 100644 --- a/docs/conf.py.in +++ b/docs/conf.py.in @@ -82,7 +82,7 @@ language = None # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. -exclude_patterns = ['_build', 'DoxygenMain.md'] +exclude_patterns = ['_build', 'DoxygenMain.md', 'tutorial'] # The reST default role (used for this markup: `text`) to use for all # documents. diff --git a/docs/index.rst b/docs/index.rst index 3d24e49..a37e4e7 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -205,7 +205,6 @@ Limitations with respect to LibraryLink There are a few LibraryLink features currently not covered by LLU, most notably: -- Sparse Arrays - Tensor subsetting: `MTensor_getTensor` - Callbacks - Wolfram IO Library (asynchronous tasks) diff --git a/docs/modules/containers.rst b/docs/modules/containers.rst index 9ef76eb..6e2666e 100644 --- a/docs/modules/containers.rst +++ b/docs/modules/containers.rst @@ -25,7 +25,7 @@ generic, datatype-agnostic wrappers and full-fledged wrappers templated with the +----------------------------------------------+----------------------------------------------+----------------------------------------+ | :ref:`DataStore ` | :ref:`GenericDataList ` | :ref:`DataList\ ` | +----------------------------------------------+----------------------------------------------+----------------------------------------+ -| :ref:`MSparseArray ` | ⊝ | ⊝ | +| :ref:`MSparseArray ` | :ref:`GenericSparseArray ` | :ref:`SparseArray\ ` | +----------------------------------------------+----------------------------------------------+----------------------------------------+ Memory management @@ -275,6 +275,23 @@ perform operations on the underlying data. .. doxygenclass:: LLU::MContainer< MArgumentType::Tensor > :members: + +.. _genericsa-label: + +:cpp:type:`LLU::GenericSparseArray` +------------------------------------ + +GenericSparseArray is a light-weight wrapper over :ref:`msparsearray-label`. It offers the same API that LibraryLink has for MSparseArray, except for access +to the underlying array data because GenericSparseArray is not aware of the array data type. Typically one would use GenericSparseArray to take an MSparseArray +of an unknown type from LibraryLink, investigate its properties and data type, then upgrade the GenericSparseArray to the strongly-typed one in order to +perform operations on the underlying data. + +.. doxygentypedef:: LLU::GenericSparseArray + +.. doxygenclass:: LLU::MContainer< MArgumentType::SparseArray > + :members: + + Typed Wrappers ============================ @@ -302,7 +319,7 @@ DataList is a strongly-typed wrapper derived from GenericDataList in which all n +-------------------------+--------------------------+------------------------+ | NodeType::Tensor | LLU::GenericTensor | MTensor | +-------------------------+--------------------------+------------------------+ -| NodeType::SparseArray | MSparseArray | MSparseArray | +| NodeType::SparseArray | LLU::GenericSparseArray | MSparseArray | +-------------------------+--------------------------+------------------------+ | NodeType::NumericArray | LLU::GenericNumericArray | MNumericArray | +-------------------------+--------------------------+------------------------+ @@ -557,6 +574,68 @@ On the Wolfram Language side, we can load and use this function as follows: :members: +.. _spararr-label: + +:cpp:class:`LLU::SparseArray\ LLU::SparseArray>` +------------------------------------------------------------------------------- + +:cpp:expr:`LLU::SparseArray` is a wrapper over an MSparseArray which holds elements of type ``T``. MSparseArray supports only 3 types of data, +meaning that :cpp:class:`template\ LLU::SparseArray` class template can be instantiated with only 3 types ``T``: + + - ``mint`` + - ``double`` + - ``std::complex`` + + +Here is an example of the SparseArray class in action: + +.. code-block:: cpp + :linenos: + + template + void sparseModifyValues(LLU::SparseArray& sa, LLU::TensorTypedView newValues) { + + // extract a Tensor with explicit values of the SparseArray + // this does not make a copy of the values so modifying the Tensor will modify the values in the SparseArray + auto values = sa.explicitValues(); + if (values.size() < newValues.size()) { + throw std::runtime_error {"Too many values provided."}; + } + + // copy new values in place of the old ones + std::copy(std::cbegin(newValues), std::cend(newValues), std::begin(values)); + + // Recompute explicit positions (necessary since one of the new values might be equal to the implicit value of the SparseArray) + sa.resparsify(); + } + + LLU_LIBRARY_FUNCTION(ModifyValues) { + auto sp = mngr.getGenericSparseArray(0); + auto values = mngr.getGenericTensor(1); + + // Operate on the GenericSparseArray as if its type was known + LLU::asTypedSparseArray(sp, [&values](auto&& sparseArray) { + using T = typename std::remove_reference_t::value_type; + sparseModifyValues(sparseArray, LLU::TensorTypedView {values}); + }); + } + +On the Wolfram Language side, we can load and use this function as follows: + +.. code-block:: wolfram-language + + (* Our function takes a shared SparseArray to modify it in-place. The SparseArray can be of any type. *) + `LLU`PacletFunctionSet[$ModifyValues, {{LibraryDataType[SparseArray, _, _], "Shared"}, {_, _, "Constant"}}, "Void"]; + + sparse = SparseArray[{{3.5, 0., 0., 0.}, {.5, -7., 0., 0.}, {4., 0., 3., 0.}, {0., 0., 0., 1.}}]; + $ModifyValues[sparse, {3.5, .5, -7.}]; + Normal[sparse] + + (* Out[] = {{3.5, 0., 0., 0.}, {.5, -7., 0., 0.}, {4., 0., 3., 0.}, {0., 0., 0., 1.}} *) + +.. doxygenclass:: LLU::SparseArray + :members: + Iterators ======================== diff --git a/docs/tutorial/IntegratingCppWithWL.md b/docs/tutorial/IntegratingCppWithWL.md index 00893c3..7cac59b 100644 --- a/docs/tutorial/IntegratingCppWithWL.md +++ b/docs/tutorial/IntegratingCppWithWL.md @@ -659,8 +659,8 @@ using Complex = std::complex; /// Tensor stands for a GenericTensor - type agnostic wrapper over MTensor using Tensor = MContainer; -/// SparseArray type corresponds to the "raw" MSparseArray as LLU does not have its own wrapper for this structure yet -using SparseArray = MSparseArray; +/// SparseArray stands for a GenericSparseArray - type agnostic wrapper over MSparseArray +using SparseArray = MContainer; /// NumericArray stands for a GenericNumericArray - type agnostic wrapper over MNumericArray using NumericArray = MContainer; diff --git a/include/LLU/Containers/Generic/Base.hpp b/include/LLU/Containers/Generic/Base.hpp index f20039f..1426a42 100644 --- a/include/LLU/Containers/Generic/Base.hpp +++ b/include/LLU/Containers/Generic/Base.hpp @@ -74,8 +74,10 @@ namespace LLU { * @return reference to this object */ MContainerBase& operator=(MContainerBase&& mc) noexcept { - reset(mc.container, mc.owner); - mc.container = nullptr; + if (this != &mc) { + reset(mc.container, mc.owner); + mc.container = nullptr; + } return *this; } @@ -198,17 +200,19 @@ namespace LLU { * @param newOwnerMode - owner of the new container */ void reset(Container newCont, Ownership newOwnerMode = Ownership::Library) noexcept { - switch (owner) { - case Ownership::Shared: - disown(); - break; - case Ownership::Library: - free(); - break; - case Ownership::LibraryLink: break; + if (newCont != container) { + switch (owner) { + case Ownership::Shared: + disown(); + break; + case Ownership::Library: + free(); + break; + case Ownership::LibraryLink: break; + } + container = newCont; } owner = newOwnerMode; - container = newCont; } private: @@ -231,7 +235,7 @@ namespace LLU { /** * @class MContainer - * @brief MContainer is an abstract class template for generic containers. Only specializations shall be used. + * @brief MContainer is an abstract class template for generic containers. Only specializations shall be used. * @tparam Type - container type (see MArgumentType definition) */ template, int> = 0> diff --git a/include/LLU/Containers/Generic/SparseArray.hpp b/include/LLU/Containers/Generic/SparseArray.hpp new file mode 100644 index 0000000..e860aa4 --- /dev/null +++ b/include/LLU/Containers/Generic/SparseArray.hpp @@ -0,0 +1,174 @@ +/** + * @file SparseArray.hpp + * @brief GenericSparseArray definition and implementation + */ +#ifndef LLU_CONTAINERS_GENERIC_SPARSEARRAY_HPP +#define LLU_CONTAINERS_GENERIC_SPARSEARRAY_HPP + +#include "LLU/Containers/Generic/Base.hpp" +#include "LLU/Containers/Generic/Tensor.hpp" + +namespace LLU { + + template<> + class MContainer; + + /// MContainer specialization for MSparseArray is called GenericSparseArray + using GenericSparseArray = MContainer; + + /** + * @brief MContainer specialization for MSparseArray + */ + template<> + class MContainer : public MContainerBase { + public: + /// Inherit constructors from MContainerBase + using MContainerBase::MContainerBase; + + /** + * @brief Default constructor, the MContainer does not manage any instance of MSparseArray. + */ + MContainer() = default; + + /** + * @brief Create a new SparseArray from positions, values, dimensions and an implicit value + * @param positions - positions of all the explicit values in the array + * @param values - explicit values to be stored in the array + * @param dimensions - dimensions of the new SparseArray + * @param implicitValue - implicit value (the one that is not stored) of the new SparseArray + * @see + */ + MContainer(const GenericTensor& positions, const GenericTensor& values, const GenericTensor& dimensions, const GenericTensor& implicitValue); + + /** + * @brief Create a new SparseArray from data array and an implicit value + * @param data - a tensor whose contents will be copied and sparsified + * @param implicitValue - implicit value (the one that is not stored) of the new SparseArray + * @see + */ + MContainer(const GenericTensor& data, const GenericTensor& implicitValue); + + /** + * @brief Create a copy of given SparseArray with different implicit value + * @param s - other SparseArray + * @param implicitValue - implicit value (the one that is not stored) of the new SparseArray + * @see + */ + MContainer(const GenericSparseArray& s, const GenericTensor& implicitValue); + + /** + * @brief Clone this MContainer, performs a deep copy of the underlying MSparseArray. + * @note The cloned MContainer always belongs to the library (Ownership::Library) because LibraryLink has no idea of its existence. + * @return new MContainer, by value + */ + MContainer clone() const { + return MContainer {cloneContainer(), Ownership::Library}; + } + + /** + * @brief Get the implicit value of this sparse array. + * @return Rank 0 tensor of the same type as the value type of this sparse array. + * @see + */ + GenericTensor getImplicitValueAsTensor() const; + + /** + * @brief Change the implicit value of this array. + * @param implicitValue - new implicit value + * @note The underlying MSparseArray object may be replaced in the process. + * @see + */ + void setImplicitValueFromTensor(const GenericTensor& implicitValue); + + /** + * @brief Get the rank (number of dimensions) of this sparse array. + * @return the rank of this array + * @see + */ + mint getRank() const { + return LibraryData::SparseArrayAPI()->MSparseArray_getRank(this->getContainer()); + } + + /** + * @brief Get dimensions of this sparse array. + * @return a read-only raw array of container dimensions + * @see + */ + mint const* getDimensions() const { + return LibraryData::SparseArrayAPI()->MSparseArray_getDimensions(this->getContainer()); + } + + /** + * @brief Get a tensor with the values corresponding to the explicitly stored positions in the sparse array. + * @return GenericTensor of rank 1 with length equal to the number of explicit positions in the array or an empty wrapper for "pattern sparse arrays" + * @see + */ + GenericTensor getExplicitValues() const; + + /** + * @brief Get a row pointer array for this sparse array. + * @details The values returned are the cumulative number of explicitly represented elements for each row, so the values will be non-decreasing. + * @return GenericTensor of rank 1 and integer type or an empty GenericTensor + * @see + */ + GenericTensor getRowPointers() const; + + /** + * @brief Get the column indices for the explicitly stored positions in this sparse array. + * @details The first dimension of the resulting tensor is the number of explicit positions, and the second dimension is equal to getRank() - 1. + * @return GenericTensor of rank 2 or an empty GenericTensor + * @see + */ + GenericTensor getColumnIndices() const; + + /** + * @brief Get the explicitly specified positions in this sparse array. + * @details The first dimension of the resulting tensor is the number of explicit positions, and the second dimension is equal to getRank(). + * @return GenericTensor of rank 2 or an empty GenericTensor + * @see + */ + [[nodiscard]] GenericTensor getExplicitPositions() const; + + /** + * @brief Expand this sparse array to a regular tensor + * @return GenericTensor of the same data type as this array + * @see + */ + [[nodiscard]] GenericTensor toGenericTensor() const; + + /** + * @brief Use current implicit value to recalculate the sparse array after the data has been modified. + */ + void resparsify(); + + /** + * @brief Get the data type of this MSparseArray + * @return type of elements (MType_Integer, MType_Real or MType_Complex) + */ + mint type() const; + + private: + + /** + * @brief Make a deep copy of the raw container + * @see + **/ + Container cloneImpl() const override; + + /** + * @copydoc MContainer::shareCount() + * @see + */ + mint shareCountImpl() const noexcept override { + return LibraryData::SparseArrayAPI()->MSparseArray_shareCount(this->getContainer()); + } + + ///@copydoc MContainer::pass + void passImpl(MArgument& res) const noexcept override { + MArgument_setMSparseArray(res, this->getContainer()); + } + }; + +} // namespace LLU + +#endif // LLU_CONTAINERS_GENERIC_SPARSEARRAY_HPP diff --git a/include/LLU/Containers/MArrayDimensions.h b/include/LLU/Containers/MArrayDimensions.h index a3642cd..10b67e6 100644 --- a/include/LLU/Containers/MArrayDimensions.h +++ b/include/LLU/Containers/MArrayDimensions.h @@ -74,6 +74,9 @@ namespace LLU { * @brief Get raw pointer to container dimensions **/ const mint* data() const noexcept { + if (dims.empty()) { + return nullptr; + } return dims.data(); } @@ -126,7 +129,7 @@ namespace LLU { private: /// Total number of elements in the container - mint flattenedLength = 0; + mint flattenedLength = 1; /// Container dimensions std::vector dims; @@ -148,6 +151,8 @@ namespace LLU { /// Calculate total array length based on current value of dims [[nodiscard]] mint totalLengthFromDims() const noexcept; + + static constexpr std::size_t MAX_DIM = static_cast((std::numeric_limits::max)()); }; template @@ -158,13 +163,14 @@ namespace LLU { template MArrayDimensions::MArrayDimensions(InputIter dimsBegin, InputIter dimsEnd) { - mint depth = checkContainerSize(std::distance(dimsBegin, dimsEnd)); - auto dimsOk = std::all_of(dimsBegin, dimsEnd - 1, [](auto d) { return (d > 0) && (d <= (std::numeric_limits::max)()); }) && - (dimsBegin[depth - 1] >= 0) && (dimsBegin[depth - 1] <= (std::numeric_limits::max)()); - if (!dimsOk) { - ErrorManager::throwExceptionWithDebugInfo(ErrorName::DimensionsError, "Invalid input vector with array dimensions"); + if (mint depth = checkContainerSize(std::distance(dimsBegin, dimsEnd)); depth >= 1) { + auto dimsOk = std::all_of(dimsBegin, dimsEnd - 1, [](auto d) { return (d > 0) && (static_cast(d) <= MAX_DIM); }) && + (dimsBegin[depth - 1] >= 0) && (static_cast(dimsBegin[depth - 1]) <= MAX_DIM); + if (!dimsOk) { + ErrorManager::throwExceptionWithDebugInfo(ErrorName::DimensionsError, "Invalid input vector with array dimensions"); + } + dims.reserve(depth); } - dims.reserve(depth); std::copy(dimsBegin, dimsEnd, std::back_inserter(dims)); flattenedLength = totalLengthFromDims(); fillOffsets(); @@ -172,7 +178,7 @@ namespace LLU { template mint MArrayDimensions::checkContainerSize(T s) const { - if (s <= 0 || static_cast(s) > static_cast((std::numeric_limits::max)())) { + if (s < 0 || static_cast(s) > MAX_DIM) { ErrorManager::throwException(ErrorName::DimensionsError); } return static_cast(s); diff --git a/include/LLU/Containers/SparseArray.h b/include/LLU/Containers/SparseArray.h new file mode 100644 index 0000000..d00f70a --- /dev/null +++ b/include/LLU/Containers/SparseArray.h @@ -0,0 +1,215 @@ +/** + * @file SparseArray.h + * @author Rafal Chojna + * @date November 25, 2020 + * @brief Templated C++ wrapper for MSparseArray + */ +#ifndef LLU_CONTAINERS_SPARSEARRAY_H +#define LLU_CONTAINERS_SPARSEARRAY_H + +#include "LLU/Containers/Generic/SparseArray.hpp" +#include "LLU/Containers/Tensor.h" + +namespace LLU { + + /** + * @brief Strongly typed wrapper for MSparseArray. + * @tparam T - any type supported by MSparseArray (mint, double or std::complex) + */ + template + class SparseArray : public GenericSparseArray { + public: + using value_type = T; + + /** + * @brief Default constructor, creates a SparseArray that does not wrap over any raw MSparseArray + */ + SparseArray() = default; + + /** + * @brief Constructs SparseArray based on MSparseArray + * @param[in] t - LibraryLink structure to be wrapped + * @param[in] owner - who manages the memory the raw MSparseArray + * @throws ErrorName::SparseArrayTypeError - if the SparseArray template type \b T does not match the actual data type of the MSparseArray + **/ + SparseArray(MSparseArray t, Ownership owner); + + /** + * @brief Create new SparseArray from a GenericSparseArray + * @param[in] t - generic SparseArray to be wrapped into SparseArray class + * @throws ErrorName::SparseArrayTypeError - if the SparseArray template type \b T does not match the actual data type of the generic SparseArray + **/ + explicit SparseArray(GenericSparseArray t); + + /** + * @brief Create a new SparseArray from positions, values, dimensions and an implicit value + * @param positions - positions of all the explicit values in the array + * @param values - explicit values to be stored in the array + * @param dimensions - dimensions of the new SparseArray + * @param implicitValue - implicit value (the one that is not stored) of the new SparseArray + * @see + */ + SparseArray(const Tensor& positions, const Tensor& values, const Tensor& dimensions, T implicitValue); + + /** + * @brief Create a new SparseArray from data array and an implicit value + * @param data - a tensor whose contents will be copied and sparsified + * @param implicitValue - implicit value (the one that is not stored) of the new SparseArray + * @see + */ + SparseArray(const Tensor& data, T implicitValue); + + /** + * @brief Create a copy of given SparseArray with different implicit value + * @param s - other SparseArray of type T + * @param implicitValue - implicit value (the one that is not stored) of the new SparseArray + * @see + */ + SparseArray(const SparseArray& s, T implicitValue); + + /** + * @brief Get the rank (number of dimensions) of this sparse array. + * @return the rank of this array + * @see + */ + mint rank() const { + return getRank(); + } + + /** + * @brief Get the implicit value of this sparse array. + * @return implicit value (the one that is not stored) + * @see + */ + T implicitValue() const; + + /** + * @brief Change the implicit value of this array. + * @param newImplicitValue - new implicit value + * @note The underlying MSparseArray object may be replaced in the process. + * @see + */ + void setImplicitValue(T newImplicitValue); + + /** + * @brief Get a tensor with the values corresponding to the explicitly stored positions in the sparse array. + * @return Tensor of rank 1 with length equal to the number of explicit positions in the array + * @see + */ + Tensor explicitValues() const; + + /** + * @brief Get a row pointer array for this sparse array. + * @details The values returned are the cumulative number of explicitly represented elements for each row, so the values will be non-decreasing. + * @return Tensor of integers of rank 1 + * @see + */ + Tensor rowPointers() const; + + /** + * @brief Get the column indices for the explicitly stored positions in this sparse array. + * @details The first dimension of the resulting tensor is the number of explicit positions, and the second dimension is equal to rank() - 1. + * @return Tensor of rank 2 + * @see + */ + Tensor columnIndices() const; + + /** + * @brief Get the explicitly specified positions in this sparse array. + * @details The first dimension of the resulting tensor is the number of explicit positions, and the second dimension is equal to rank(). + * @return Tensor of rank 2 + * @see + */ + [[nodiscard]] Tensor explicitPositions() const; + + /** + * @brief Expand this sparse array to a regular tensor + * @return Tensor of the same data type as this array + * @see + */ + [[nodiscard]] Tensor toTensor() const; + }; + + /** + * Take a SparseArray-like object \p t and a function \p callable and call the function with a SparseArrayTypedView created from \p t + * @tparam SparseArrayT - a SparseArray-like type (GenericSparseArray, SparseArrayView or MNumericArray) + * @tparam F - any callable object + * @param t - SparseArray-like object on which an operation will be performed + * @param callable - a callable object that can be called with a SparseArrayTypedView of any type + * @return result of calling \p callable on a SparseArrayTypedView over \p t + */ + template + auto asTypedSparseArray(const GenericSparseArray& sa, F&& callable) { + switch (sa.type()) { + case MType_Integer: return std::forward(callable)(SparseArray {sa.getContainer(), Ownership::LibraryLink}); + case MType_Real: return std::forward(callable)(SparseArray {sa.getContainer(), Ownership::LibraryLink}); + case MType_Complex: return std::forward(callable)(SparseArray> {sa.getContainer(), Ownership::LibraryLink}); + default: ErrorManager::throwException(ErrorName::SparseArrayTypeError); + } + } + + /// @cond + // Overload of asTypedSparseArray for MSparseArray + template + auto asTypedSparseArray(MSparseArray t, F&& callable) { + return asTypedSparseArray(GenericSparseArray {t, Ownership::LibraryLink}, std::forward(callable)); + } + /// @endcond + + template + SparseArray::SparseArray(GenericSparseArray t) : GenericSparseArray(std::move(t)) { + if (TensorType != GenericSparseArray::type()) { + ErrorManager::throwException(ErrorName::SparseArrayTypeError); + } + } + + template + SparseArray::SparseArray(MSparseArray t, Ownership owner) : SparseArray(GenericSparseArray {t, owner}) {} + + template + SparseArray::SparseArray(const Tensor& positions, const Tensor& values, const Tensor& dimensions, T implicitValue) : + GenericSparseArray(positions, values, dimensions, Tensor(implicitValue, {})){} + + template + SparseArray::SparseArray(const Tensor& data, T implicitValue) : GenericSparseArray(data, Tensor(implicitValue, {})) {} + + + template + SparseArray::SparseArray(const SparseArray& s, T implicitValue) : GenericSparseArray(s, Tensor(implicitValue, {})) {} + + template + T SparseArray::implicitValue() const { + return Tensor {GenericSparseArray::getImplicitValueAsTensor()}.front(); + } + + template + void SparseArray::setImplicitValue(T newImplicitValue) { + GenericSparseArray::setImplicitValueFromTensor(Tensor(newImplicitValue, {})); + } + + template + Tensor SparseArray::explicitValues() const { + return Tensor {GenericSparseArray::getExplicitValues()}; + } + + template + Tensor SparseArray::rowPointers() const { + return Tensor {MContainer::getRowPointers()}; + } + template + Tensor SparseArray::columnIndices() const { + return Tensor {MContainer::getColumnIndices()}; + } + template + Tensor SparseArray::explicitPositions() const { + return Tensor {MContainer::getExplicitPositions()}; + } + template + Tensor SparseArray::toTensor() const { + return Tensor {MContainer::toGenericTensor()}; + } + + +} // namespace LLU + +#endif // LLU_CONTAINERS_SPARSEARRAY_H diff --git a/include/LLU/ErrorLog/Errors.h b/include/LLU/ErrorLog/Errors.h index 9c9f52f..6e50cd1 100644 --- a/include/LLU/ErrorLog/Errors.h +++ b/include/LLU/ErrorLog/Errors.h @@ -71,6 +71,19 @@ namespace LLU { extern const std::string TensorSizeError; ///< wrong assumption about Tensor size extern const std::string TensorIndexError; ///< trying to access non-existing element + // SparseArray errors: + extern const std::string SparseArrayCloneError; ///< could not clone MSparseArray + extern const std::string SparseArrayTypeError; ///< SparseArray type mismatch + extern const std::string SparseArrayFromPositionsError; ///< could not create MSparseArray from explicit positions + extern const std::string SparseArrayFromTensorError; ///< could not create MSparseArray from MTensor + extern const std::string SparseArrayImplicitValueResetError; ///< could not reset the implicit value of MSparseArray + extern const std::string SparseArrayImplicitValueError; ///< could not read implicit value from MSparseArray + extern const std::string SparseArrayExplicitValuesError; ///< could not read explicit values from MSparseArray + extern const std::string SparseArrayRowPointersError; ///< could not read row pointers from MSparseArray + extern const std::string SparseArrayColumnIndicesError; ///< could not read column indices from MSparseArray + extern const std::string SparseArrayExplicitPositionsError; ///< could not read explicit positions from MSparseArray + extern const std::string SparseArrayToTensorError; ///< could not dump MSparseArray to MTensor + // MImage errors: extern const std::string ImageNewError; ///< creating new MImage failed extern const std::string ImageCloneError; ///< MImage cloning failed diff --git a/include/LLU/LLU.h b/include/LLU/LLU.h index 1adc8fd..1f69e35 100644 --- a/include/LLU/LLU.h +++ b/include/LLU/LLU.h @@ -13,6 +13,7 @@ #include "LLU/Containers/DataList.h" #include "LLU/Containers/Image.h" #include "LLU/Containers/NumericArray.h" +#include "LLU/Containers/SparseArray.h" #include "LLU/Containers/Tensor.h" #include "LLU/Containers/Views/Image.hpp" #include "LLU/Containers/Views/NumericArray.hpp" diff --git a/include/LLU/MArgumentManager.h b/include/LLU/MArgumentManager.h index bc9ce81..b91885f 100644 --- a/include/LLU/MArgumentManager.h +++ b/include/LLU/MArgumentManager.h @@ -21,6 +21,7 @@ #include "LLU/Containers/DataList.h" #include "LLU/Containers/Image.h" #include "LLU/Containers/NumericArray.h" +#include "LLU/Containers/SparseArray.h" #include "LLU/Containers/Tensor.h" #include "LLU/ErrorLog/ErrorManager.h" #include "LLU/LibraryData.h" @@ -183,6 +184,34 @@ namespace LLU { **/ MTensor getMTensor(size_type index) const; + /** + * @brief Get MArgument of type MSparseArray at position \p index and wrap it into SparseArray + * @tparam T - type of data stored in SparseArray + * @param[in] index - position of desired MArgument in \c Args + * @returns SparseArray wrapper of MArgument at position \c index + * @throws ErrorName::MArgumentIndexError - if \c index is out-of-bounds + **/ + template + SparseArray getSparseArray(size_type index) const; + + /** + * @brief Get MArgument of type MSparseArray at position \p index and wrap it into generic MContainer wrapper + * @tparam Mode - passing mode to be used + * @param index - position of desired MArgument in \c Args + * @return MContainer wrapper of MSparseArray with given passing mode + */ + template + GenericSparseArray getGenericSparseArray(size_type index) const; + + /** + * @brief Get MArgument of type MSparseArray at position \c index + * @warning Use of this function is discouraged. Use getSparseArray instead, if possible. + * @param[in] index - position of desired MArgument in \c Args + * @returns MArgument at position \c index interpreted as MSparseArray + * @throws ErrorName::MArgumentIndexError - if \c index is out-of-bounds + **/ + MSparseArray getMSparseArray(size_type index) const; + /** * @brief Get MArgument of type MImage at position \p index and wrap it into Image object * @tparam T - type of data stored in Image @@ -430,11 +459,19 @@ namespace LLU { **/ void setDataStore(DataStore ds); + /** + * @brief Set MSparseArray wrapped by \c sa as output MArgument + * @tparam T - SparseArray data type + * @param[in] sa - reference to SparseArray which should pass its internal MSparseArray to LibraryLink + **/ + template + void setSparseArray(const SparseArray& sa); + /** * @brief Set MSparseArray as output MArgument * @param[in] sa - MSparseArray to be passed to LibraryLink **/ - void setSparseArray(MSparseArray sa); + void setMSparseArray(MSparseArray sa); /************************************ generic setters ************************************/ @@ -487,6 +524,20 @@ namespace LLU { na.pass(res); } + /// @copydoc setSparseArray + template + void set(const SparseArray& ten) { + setSparseArray(ten); + } + + /** + * Set MSparseArray wrapped by \c t as output MArgument + * @param[in] t - reference to generic SparseArray which should pass its internal MSparseArray to LibraryLink + */ + void set(const GenericSparseArray& t) { + t.pass(res); + } + /// @copydoc setTensor template void set(const Tensor& ten) { @@ -878,6 +929,7 @@ namespace LLU { LLU_MARGUMENTMANAGER_GENERATE_GET_SPECIALIZATION_FOR_CONTAINER(Tensor) LLU_MARGUMENTMANAGER_GENERATE_GET_SPECIALIZATION_FOR_CONTAINER(Image) LLU_MARGUMENTMANAGER_GENERATE_GET_SPECIALIZATION_FOR_CONTAINER(DataList) + LLU_MARGUMENTMANAGER_GENERATE_GET_SPECIALIZATION_FOR_CONTAINER(SparseArray) #undef LLU_MARGUMENTMANAGER_GENERATE_GET_SPECIALIZATION_FOR_CONTAINER @@ -1053,6 +1105,21 @@ namespace LLU { return {getDataStore(index), getOwner(Mode)}; } + template + SparseArray MArgumentManager::getSparseArray(size_type index) const { + return SparseArray { getGenericSparseArray(index) }; + } + + template + void MArgumentManager::setSparseArray(const SparseArray& sa) { + sa.pass(res); + } + + template + GenericSparseArray MArgumentManager::getGenericSparseArray(size_type index) const { + return GenericSparseArray(getMSparseArray(index), getOwner(Mode)); + } + template DynamicType& MArgumentManager::getManagedExpression(size_type index, ManagedExpressionStore& store) const { auto ptr = getManagedExpressionPtr(index, store); diff --git a/include/LLU/TypedMArgument.h b/include/LLU/TypedMArgument.h index ebc22f6..a49f666 100644 --- a/include/LLU/TypedMArgument.h +++ b/include/LLU/TypedMArgument.h @@ -13,6 +13,7 @@ #include "LLU/Containers/Generic/Image.hpp" #include "LLU/Containers/Generic/NumericArray.hpp" +#include "LLU/Containers/Generic/SparseArray.hpp" #include "LLU/Containers/Generic/Tensor.hpp" #include "LLU/MArgument.h" #include "LLU/Utilities.hpp" @@ -45,8 +46,8 @@ namespace LLU::Argument { /// Tensor stands for a GenericTensor - type agnostic wrapper over MTensor using Tensor = MContainer; - /// SparseArray type corresponds to the "raw" MSparseArray as LLU does not have its own wrapper for this structure yet - using SparseArray = MSparseArray; + /// SparseArray stands for a GenericSparseArray - type agnostic wrapper over MSparseArray + using SparseArray = MContainer; /// NumericArray stands for a GenericNumericArray - type agnostic wrapper over MNumericArray using NumericArray = MContainer; @@ -121,7 +122,7 @@ namespace LLU::Argument { return {value->ri[0], value->ri[1]}; } else if constexpr (T == MArgumentType::UTF8String) { return {value}; - } else if constexpr (ContainerTypeQ && T != MArgumentType::SparseArray) { + } else if constexpr (ContainerTypeQ) { return {value, Ownership::LibraryLink}; } else { return value; @@ -141,7 +142,7 @@ namespace LLU::Argument { } else if constexpr (T == MArgumentType::UTF8String) { // NOLINTNEXTLINE(cppcoreguidelines-pro-type-const-cast): LibraryLink will not modify the string, so const_cast is safe here return const_cast(value.data()); - } else if constexpr (ContainerTypeQ && T != MArgumentType::SparseArray) { + } else if constexpr (ContainerTypeQ) { return value.abandonContainer(); } else { return value; diff --git a/src/Containers/DataStore.cpp b/src/Containers/DataStore.cpp index dd0ad8a..6f9b4ba 100644 --- a/src/Containers/DataStore.cpp +++ b/src/Containers/DataStore.cpp @@ -58,7 +58,7 @@ namespace LLU { PrimitiveWrapper::addDataStoreNode(getContainer(), name, std::get_if(&node)->abandonContainer()); break; case MArgumentType::SparseArray: - PrimitiveWrapper::addDataStoreNode(getContainer(), name, *std::get_if(&node)); + PrimitiveWrapper::addDataStoreNode(getContainer(), name, std::get_if(&node)->abandonContainer()); break; case MArgumentType::NumericArray: PrimitiveWrapper::addDataStoreNode(getContainer(), name, @@ -95,7 +95,7 @@ namespace LLU { PrimitiveWrapper::addDataStoreNode(getContainer(), std::get_if(&node)->abandonContainer()); break; case MArgumentType::SparseArray: - PrimitiveWrapper::addDataStoreNode(getContainer(), *std::get_if(&node)); + PrimitiveWrapper::addDataStoreNode(getContainer(), std::get_if(&node)->abandonContainer()); break; case MArgumentType::NumericArray: PrimitiveWrapper::addDataStoreNode(getContainer(), std::get_if(&node)->abandonContainer()); diff --git a/src/Containers/MArrayDimensions.cpp b/src/Containers/MArrayDimensions.cpp index 9073919..e9c67a1 100644 --- a/src/Containers/MArrayDimensions.cpp +++ b/src/Containers/MArrayDimensions.cpp @@ -16,7 +16,7 @@ namespace { * @throws ErrorName::DimensionsError - if \c v is too big **/ void checkInitializerListSize(std::initializer_list v) { - if (v.size() <= 0 || v.size() > static_cast((std::numeric_limits::max)())) { + if (v.size() > static_cast((std::numeric_limits::max)())) { LLU::ErrorManager::throwException(LLU::ErrorName::DimensionsError); } } @@ -29,8 +29,7 @@ namespace { namespace LLU { - MArrayDimensions::MArrayDimensions(std::initializer_list dimensions) { - dims = dimensions; + MArrayDimensions::MArrayDimensions(std::initializer_list dimensions) : dims {dimensions} { checkInitializerListSize(dimensions); flattenedLength = totalLengthFromDims(); fillOffsets(); diff --git a/src/Containers/SparseArray.cpp b/src/Containers/SparseArray.cpp new file mode 100644 index 0000000..28c21e7 --- /dev/null +++ b/src/Containers/SparseArray.cpp @@ -0,0 +1,106 @@ +/** + * @file SparseArray.cpp + * @author Rafal Chojna + * @date November 25, 2020 + * @brief Implementation of GenericSparseArray and SparseArray + */ + +#include "LLU/Containers/SparseArray.h" + +namespace LLU { + + GenericSparseArray::MContainer(const GenericTensor& positions, const GenericTensor& values, const GenericTensor& dimensions, + const GenericTensor& implicitValue) { + Container tmp {}; + if (0 != LibraryData::SparseArrayAPI()->MSparseArray_fromExplicitPositions(positions.getContainer(), values.getContainer(), dimensions.getContainer(), + implicitValue.getContainer(), &tmp)) { + ErrorManager::throwException(ErrorName::SparseArrayFromPositionsError); + } + this->reset(tmp); + } + + GenericSparseArray::MContainer(const GenericTensor& data, const GenericTensor& implicitValue) { + Container tmp {}; + if (0 != LibraryData::SparseArrayAPI()->MSparseArray_fromMTensor(data.getContainer(), implicitValue.getContainer(), &tmp)) { + ErrorManager::throwException(ErrorName::SparseArrayFromTensorError); + } + this->reset(tmp); + } + + GenericSparseArray::MContainer(const GenericSparseArray& s, const GenericTensor& implicitValue) { + Container tmp {}; + if (0 != LibraryData::SparseArrayAPI()->MSparseArray_resetImplicitValue(s.getContainer(), implicitValue.getContainer(), &tmp)) { + ErrorManager::throwException(ErrorName::SparseArrayImplicitValueResetError); + } + this->reset(tmp); + } + + auto GenericSparseArray::cloneImpl() const -> Container { + Container tmp {}; + if (0 != LibraryData::SparseArrayAPI()->MSparseArray_clone(this->getContainer(), &tmp)) { + ErrorManager::throwException(ErrorName::SparseArrayCloneError); + } + return tmp; + } + + GenericTensor GenericSparseArray::getImplicitValueAsTensor() const { + if (auto* implValue = LibraryData::SparseArrayAPI()->MSparseArray_getImplicitValue(this->getContainer()); implValue) { + return {*implValue, Ownership::LibraryLink}; + } + ErrorManager::throwException(ErrorName::SparseArrayImplicitValueError); + } + + void MContainer::setImplicitValueFromTensor(const GenericTensor& implicitValue) { + Container rawSparse = getContainer(); + if (0 != LibraryData::SparseArrayAPI()->MSparseArray_resetImplicitValue(rawSparse, implicitValue.getContainer(), &rawSparse)) { + ErrorManager::throwException(ErrorName::SparseArrayImplicitValueResetError); + } + this->reset(rawSparse); + } + + GenericTensor MContainer::getExplicitValues() const { + if (auto* explicitValues = LibraryData::SparseArrayAPI()->MSparseArray_getExplicitValues(this->getContainer()); explicitValues) { + return {*explicitValues, Ownership::LibraryLink}; + } + ErrorManager::throwException(ErrorName::SparseArrayExplicitValuesError); + } + + GenericTensor MContainer::getRowPointers() const { + if (auto* rowPointers = LibraryData::SparseArrayAPI()->MSparseArray_getRowPointers(this->getContainer()); rowPointers) { + return {*rowPointers, Ownership::LibraryLink}; + } + ErrorManager::throwException(ErrorName::SparseArrayRowPointersError); + } + + GenericTensor MContainer::getColumnIndices() const { + if (auto* colIndices = LibraryData::SparseArrayAPI()->MSparseArray_getColumnIndices(this->getContainer()); colIndices) { + return {*colIndices, Ownership::LibraryLink}; + } + ErrorManager::throwException(ErrorName::SparseArrayColumnIndicesError); + } + + GenericTensor MContainer::getExplicitPositions() const { + MTensor mt {}; + if (0 != LibraryData::SparseArrayAPI()->MSparseArray_getExplicitPositions(getContainer(), &mt)) { + ErrorManager::throwException(ErrorName::SparseArrayExplicitPositionsError); + } + return {mt, Ownership::Library}; + } + + GenericTensor MContainer::toGenericTensor() const { + MTensor mt {}; + if (0 != LibraryData::SparseArrayAPI()->MSparseArray_toMTensor(getContainer(), &mt)) { + ErrorManager::throwException(ErrorName::SparseArrayToTensorError); + } + return {mt, Ownership::Library}; + } + + void GenericSparseArray::resparsify() { + setImplicitValueFromTensor(GenericTensor{}); + } + + mint GenericSparseArray::type() const { + return getImplicitValueAsTensor().type(); + } + +} // namespace LLU \ No newline at end of file diff --git a/src/ErrorLog/ErrorManager.cpp b/src/ErrorLog/ErrorManager.cpp index f795033..1e24f71 100644 --- a/src/ErrorLog/ErrorManager.cpp +++ b/src/ErrorLog/ErrorManager.cpp @@ -53,6 +53,19 @@ namespace LLU { {ErrorName::TensorSizeError, "An error was caused by an incorrect Tensor size."}, {ErrorName::TensorIndexError, "An error was caused by attempting to access a nonexistent Tensor element."}, + // MSparseArray errors: + {ErrorName::SparseArrayCloneError, "Failed to clone MSparseArray."}, + {ErrorName::SparseArrayTypeError, "An error was caused by an MSparseArray type mismatch."}, + {ErrorName::SparseArrayFromPositionsError, "Failed to create MSparseArray from explicit positions."}, + {ErrorName::SparseArrayFromTensorError, "Failed to create MSparseArray from MTensor."}, + {ErrorName::SparseArrayImplicitValueResetError, "Failed to reset the implicit value of MSparseArray."}, + {ErrorName::SparseArrayImplicitValueError, "Could not read implicit value from MSparseArray."}, + {ErrorName::SparseArrayExplicitValuesError, "Could not read explicit values from MSparseArray."}, + {ErrorName::SparseArrayRowPointersError, "Could not read row pointers from MSparseArray."}, + {ErrorName::SparseArrayColumnIndicesError, "Could not read column indices from MSparseArray."}, + {ErrorName::SparseArrayExplicitPositionsError, "Could not read explicit positions from MSparseArray."}, + {ErrorName::SparseArrayToTensorError, "MSparseArray to MTensor conversion failed."}, + // MImage errors: {ErrorName::ImageNewError, "Failed to create a new MImage."}, {ErrorName::ImageCloneError, "Failed to clone MImage."}, diff --git a/src/ErrorLog/Errors.cpp b/src/ErrorLog/Errors.cpp index 7fb7dd1..875a7be 100644 --- a/src/ErrorLog/Errors.cpp +++ b/src/ErrorLog/Errors.cpp @@ -44,6 +44,18 @@ namespace LLU::ErrorName { LLU_DEFINE_ERROR_NAME(TensorSizeError); LLU_DEFINE_ERROR_NAME(TensorIndexError); + LLU_DEFINE_ERROR_NAME(SparseArrayCloneError); + LLU_DEFINE_ERROR_NAME(SparseArrayTypeError); + LLU_DEFINE_ERROR_NAME(SparseArrayFromPositionsError); + LLU_DEFINE_ERROR_NAME(SparseArrayFromTensorError); + LLU_DEFINE_ERROR_NAME(SparseArrayImplicitValueResetError); + LLU_DEFINE_ERROR_NAME(SparseArrayImplicitValueError); + LLU_DEFINE_ERROR_NAME(SparseArrayExplicitValuesError); + LLU_DEFINE_ERROR_NAME(SparseArrayRowPointersError); + LLU_DEFINE_ERROR_NAME(SparseArrayColumnIndicesError); + LLU_DEFINE_ERROR_NAME(SparseArrayExplicitPositionsError); + LLU_DEFINE_ERROR_NAME(SparseArrayToTensorError); + LLU_DEFINE_ERROR_NAME(ImageNewError); LLU_DEFINE_ERROR_NAME(ImageCloneError); LLU_DEFINE_ERROR_NAME(ImageTypeError); diff --git a/src/MArgumentManager.cpp b/src/MArgumentManager.cpp index f0dfac6..1988dd5 100644 --- a/src/MArgumentManager.cpp +++ b/src/MArgumentManager.cpp @@ -119,6 +119,10 @@ namespace LLU { return MArgument_getDataStore(getArgs(index)); } + MSparseArray MArgumentManager::getMSparseArray(size_type index) const { + return MArgument_getMSparseArray(getArgs(index)); + } + void MArgumentManager::setMNumericArray(MNumericArray na) { MArgument_setMNumericArray(res, na); } @@ -136,7 +140,7 @@ namespace LLU { MArgument_setDataStore(res, ds); } - void MArgumentManager::setSparseArray(MSparseArray sa) { + void MArgumentManager::setMSparseArray(MSparseArray sa) { MArgument_setMSparseArray(res, sa); } diff --git a/src/TypedMArgument.cpp b/src/TypedMArgument.cpp index d84432d..56dad91 100644 --- a/src/TypedMArgument.cpp +++ b/src/TypedMArgument.cpp @@ -25,7 +25,7 @@ namespace LLU::Argument { return std::complex {mc->ri[0], mc->ri[1]}; } case MArgumentType::Tensor: return GenericTensor {MArgument_getMTensor(m), Ownership::LibraryLink}; - case MArgumentType::SparseArray: return MArgument_getMSparseArray(m); + case MArgumentType::SparseArray: return GenericSparseArray {MArgument_getMSparseArray(m), Ownership::LibraryLink}; case MArgumentType::NumericArray: return GenericNumericArray {MArgument_getMNumericArray(m), Ownership::LibraryLink}; case MArgumentType::Image: return GenericImage {MArgument_getMImage(m), Ownership::LibraryLink}; case MArgumentType::UTF8String: return std::string_view {MArgument_getUTF8String(m)}; @@ -48,7 +48,7 @@ namespace LLU::Argument { break; } case MArgumentType::Tensor: MArgument_setMTensor(res, std::get_if(&tma)->abandonContainer()); break; - case MArgumentType::SparseArray: MArgument_setMSparseArray(res, *std::get_if(&tma)); break; + case MArgumentType::SparseArray: MArgument_setMSparseArray(res, std::get_if(&tma)->abandonContainer()); break; case MArgumentType::NumericArray: MArgument_setMNumericArray(res, std::get_if(&tma)->abandonContainer()); break; case MArgumentType::Image: MArgument_setMImage(res, std::get_if(&tma)->abandonContainer()); break; // NOLINTNEXTLINE(cppcoreguidelines-pro-type-const-cast): LibraryLink will not modify the string, so const_cast is safe here diff --git a/tests/UnitTests/CMakeLists.txt b/tests/UnitTests/CMakeLists.txt index 3a1583b..f4f7b05 100644 --- a/tests/UnitTests/CMakeLists.txt +++ b/tests/UnitTests/CMakeLists.txt @@ -21,16 +21,17 @@ set(TEST_MODULES "Async" "DataList" "ErrorReporting" + "GenericContainers" "Image" "ManagedExpressions" "MArgumentManager" - "WSTP" "NumericArray" - "GenericContainers" "Scalar" + "SparseArray" "String" "Tensor" "Utilities" + "WSTP" ) @@ -46,7 +47,7 @@ if(WolframLanguage_wolframscript_EXE) report = Catch[ TestReport["${UnitTest}TestSuite.mt", MemoryConstraint -> Quantity[500, "MB"], TimeConstraint -> 30], _String, - Function[{value, tag}, Print["ERROR: " <> value]; Exit[1]] + Function[{value, tag}, Print["ERROR: " <> ToString[value]]; Exit[1]] ]; report = report /. resObj_TestResultObject :> RuleCondition[TextString[resObj[{ "TestID", diff --git a/tests/UnitTests/DataList/DataListTestSuite.mt b/tests/UnitTests/DataList/DataListTestSuite.mt index 7cf6ff4..babdb95 100644 --- a/tests/UnitTests/DataList/DataListTestSuite.mt +++ b/tests/UnitTests/DataList/DataListTestSuite.mt @@ -469,7 +469,7 @@ Test[ real, "mreal" -> real, complex, complex, "complex" -> complex, "mcomplex" -> complex, tensor, "Tensor" -> tensor, - sparse, "MSparseArray" -> sparse, + sparse, "SparseArray" -> sparse, numeric, "NumericArray" -> numeric, image, "Image" -> image, string, string, "String" -> string, "RawString" -> string, @@ -501,7 +501,12 @@ Test[ , Developer`DataStore[ bool, int, real, complex, - tensor, "Tensor" -> True, sparse, numeric, "NumericArray" -> True, image, "Image" -> True, string, "String" -> True, ds0, "DataList" -> True + tensor, + sparse, + numeric, + image, + string, + ds0 ] , TestID -> "DataListTestSuite-20200505-L9U9K6" @@ -561,7 +566,7 @@ VerificationTest[ {timeWSTP, r1} = RepeatedTiming[ReverseListOfStringsWSTP[los]]; Print["Reverse strings - WSTP: " <> ToString[timeWSTP] <> "s. " <> getSlowdown[timeWSTP]]; - r1 == List @@ r2 == List @@ r3 == List @@ r4 == List @@ r5 == List @@ r6 + r1 === List @@ r2 === List @@ r3 === List @@ r4 === List @@ r5 === List @@ r6 , TestID -> "DataListTestSuite-20180906-W5N4V0" ]; diff --git a/tests/UnitTests/DataList/TestSources/DataListTest.cpp b/tests/UnitTests/DataList/TestSources/DataListTest.cpp index 1b86fbf..4485aba 100644 --- a/tests/UnitTests/DataList/TestSources/DataListTest.cpp +++ b/tests/UnitTests/DataList/TestSources/DataListTest.cpp @@ -346,9 +346,9 @@ LLU_LIBRARY_FUNCTION(PullAndPush) { //dsOut.push_back("MTensor", rawT); // this container has already been pushed to the dsOut one line above // get and push SparseArray - auto& sa = *std::get_if(&values[5]); - dsOut.push_back(sa); - dsOut.push_back("MSparseArray", sa); + auto& sa = *std::get_if(&values[5]); + dsOut.push_back(sa.clone()); + dsOut.push_back("SparseArray", std::move(sa)); // get and push NumericArray auto& na = *std::get_if(&values[6]); @@ -386,11 +386,6 @@ LLU_LIBRARY_FUNCTION(PullAndPush) { mngr.set(dsOut); } -template -T getValueAndAdvance(GenericDataList::iterator& node) { - return (*node++).as(); -} - LLU_LIBRARY_FUNCTION(PullAndPush2) { using LLU::MArgumentType; using LLU::Argument::toPrimitiveType; @@ -418,41 +413,29 @@ LLU_LIBRARY_FUNCTION(PullAndPush2) { // get and push Tensor auto t = (node++)->as(); - auto *rawT = toPrimitiveType(t); dsOut.push_back(t.clone()); - // NOLINTNEXTLINE: deliberate example of a shady code - dsOut.push_back("Tensor", rawT); // rawT is a pointer type, it gets converted to bool and send as Boolean node + // auto *rawT = toPrimitiveType(t); + // dsOut.push_back("Tensor", rawT); // rawT is a pointer type, it gets converted to bool and send as Boolean node! // get and push SparseArray - auto *sa = (node++)->as(); - dsOut.push_back(sa); + auto sa = (node++)->as(); + dsOut.push_back(sa.clone()); // get and push NumericArray auto na = (node++)->as(); - auto *rawNA = toPrimitiveType(na); dsOut.push_back(na.clone()); - // NOLINTNEXTLINE: deliberate example of a shady code - surprising implicit pointer -> bool conversion - dsOut.push_back("NumericArray", rawNA); // get and push Image auto im = (node++)->as(); - auto *rawIm = toPrimitiveType(im); dsOut.push_back(im.clone()); - // NOLINTNEXTLINE: deliberate example of a shady code - surprising implicit pointer -> bool conversion - dsOut.push_back("Image", rawIm); // get and push String auto str = (node++)->as(); - auto *rawStr = toPrimitiveType(str); dsOut.push_back(str); - dsOut.push_back("String", rawStr); // get and push DataStore auto ds = (node++)->as(); - auto *rawDS = toPrimitiveType(ds); dsOut.push_back(ds.clone()); - // NOLINTNEXTLINE: deliberate example of a shady code - surprising implicit pointer -> bool conversion - dsOut.push_back("DataList", rawDS); mngr.set(dsOut); } diff --git a/tests/UnitTests/NumericArray/NumericArrayTestSuite.mt b/tests/UnitTests/NumericArray/NumericArrayTestSuite.mt index 6a8fb4d..c4c314a 100644 --- a/tests/UnitTests/NumericArray/NumericArrayTestSuite.mt +++ b/tests/UnitTests/NumericArray/NumericArrayTestSuite.mt @@ -51,13 +51,13 @@ ConditionalTest[ExactTest, TestID -> "NumericArrayTestSuite-20190910-C1R3B0" ]; -Test[ +TestMatch[ testDimensions[{}] , - Failure["DimensionsError", <| - "MessageTemplate" -> "An error caused by inconsistent dimensions or by exceeding array bounds.", + Failure["NumericArrayNewError", <| + "MessageTemplate" -> "Failed to create a new NumericArray.", "MessageParameters" -> <||>, - "ErrorCode" -> 3, + "ErrorCode" -> _?CppErrorCodeQ, "Parameters" -> {}|> ] , diff --git a/tests/UnitTests/SparseArray/SparseArrayTestSuite.mt b/tests/UnitTests/SparseArray/SparseArrayTestSuite.mt new file mode 100644 index 0000000..2cacc9b --- /dev/null +++ b/tests/UnitTests/SparseArray/SparseArrayTestSuite.mt @@ -0,0 +1,243 @@ +(* Wolfram Language Test file *) +TestRequirement[$VersionNumber >= 12]; +(***************************************************************************************************************************************) +(* + Set of test cases for LLU functionality related to handling and exchanging sparse arrays +*) +(***************************************************************************************************************************************) +TestExecute[ + Needs["CCompilerDriver`"]; + currentDirectory = DirectoryName[$TestFileName]; + + (* Get configuration (path to LLU sources, compilation options, etc.) *) + Get[FileNameJoin[{ParentDirectory[currentDirectory], "TestConfig.wl"}]]; + + (* Compile the test library *) + lib = CCompilerDriver`CreateLibrary[ + FileNameJoin[{currentDirectory, "TestSources", #}]& /@ {"SparseArrayOperations.cpp"}, + "SparseArrayTest", + options (* defined in TestConfig.wl *) + ]; + + Get[FileNameJoin[{$LLUSharedDir, "LibraryLinkUtilities.wl"}]]; + `LLU`InitializePacletLibrary[lib]; + + `LLU`PacletFunctionSet[$GetImplicitValue, {{LibraryDataType[SparseArray, _, _], "Constant"}}, {_, _}]; + `LLU`PacletFunctionSet[$GetExplicitValues, {{LibraryDataType[SparseArray, _, _], "Constant"}}, {_, _}]; + `LLU`PacletFunctionSet[$GetRowPointers, {{LibraryDataType[SparseArray, _, _], "Constant"}}, {_, _}]; + `LLU`PacletFunctionSet[$GetColumnIndices, {{LibraryDataType[SparseArray, _, _], "Constant"}}, {_, _}]; + `LLU`PacletFunctionSet[$GetExplicitPositions, {{LibraryDataType[SparseArray, _, _], "Constant"}}, {_, _}]; + `LLU`PacletFunctionSet[$ToTensor, {{LibraryDataType[SparseArray, _, _], "Constant"}}, {_, _}]; + `LLU`PacletFunctionSet[$SetImplicitValue, {LibraryDataType[SparseArray, _, _], {_, _, "Constant"}}, LibraryDataType[SparseArray]]; + `LLU`PacletFunctionSet[$ModifyValues, {{LibraryDataType[SparseArray, _, _], "Shared"}, {_, _, "Constant"}}, "Void"]; + `LLU`PacletFunctionSet[$GetImplicitValueTyped, {{LibraryDataType[SparseArray, Real], "Constant"}}, Real]; + `LLU`PacletFunctionSet[$GetExplicitValuesTyped, {{LibraryDataType[SparseArray, Real], "Constant"}}, {Real, _}]; + `LLU`PacletFunctionSet[$GetRowPointersTyped, {{LibraryDataType[SparseArray, Real], "Constant"}}, {Integer, _}]; + `LLU`PacletFunctionSet[$GetColumnIndicesTyped, {{LibraryDataType[SparseArray, Real], "Constant"}}, {Integer, _}]; + `LLU`PacletFunctionSet[$GetExplicitPositionsTyped, {{LibraryDataType[SparseArray, Real], "Constant"}}, {Integer, 2}]; + `LLU`PacletFunctionSet[$ToTensorTyped, {{LibraryDataType[SparseArray, Real], "Constant"}}, {Real, _}]; + `LLU`PacletFunctionSet[$SetImplicitValueTyped, {LibraryDataType[SparseArray, Real], Real}, LibraryDataType[SparseArray]]; + + sparse = SparseArray[{{1., 0., 0., 0.}, {2., 1., 0., 0.}, {4., 0., 3., 0.}, {0., 0., 0., 1.}}]; + zero = SparseArray[{0}]; + +]; + +Test[ + $GetImplicitValue[sparse] + , + 0. + , + TestID->"SparseArrayTestSuite-20201130-Q0V0F5" +]; + +Test[ + $GetImplicitValue[zero] + , + 0 + , + TestID->"SparseArrayTestSuite-20201130-Y0L3W6" +]; + +Test[ + $GetExplicitValues[sparse] + , + {1., 2., 1., 4., 3., 1.} + , + TestID->"SparseArrayTestSuite-20201130-Q8Q5J9" +]; + +Test[ + $GetExplicitValues[zero] + , + {} + , + TestID->"SparseArrayTestSuite-20201130-C8B7X3" +]; + +Test[ + $GetRowPointers[sparse] + , + {0, 1, 3, 5, 6} + , + TestID->"SparseArrayTestSuite-20201130-R1M2S0" +]; + +Test[ + $GetRowPointers[zero] + , + {0, 0} + , + TestID->"SparseArrayTestSuite-20201130-Z1Z3Q9" +]; + +Test[ + $GetColumnIndices[sparse] + , + {{1}, {1}, {2}, {1}, {3}, {4}} + , + TestID->"SparseArrayTestSuite-20201130-O1T1O2" +]; + +Test[ + $GetColumnIndices[zero] + , + {} + , + TestID->"SparseArrayTestSuite-20201130-K0M1W1" +]; + +Test[ + $GetExplicitPositions[sparse] + , + {{1, 1}, {2, 1}, {2, 2}, {3, 1}, {3, 3}, {4, 4}} + , + TestID->"SparseArrayTestSuite-20201130-F9L3K7" +]; + +Test[ + $GetExplicitPositions[zero] + , + {} + , + TestID->"SparseArrayTestSuite-20201130-E5E9L4" +]; + +Test[ + $ToTensor[sparse] + , + {{1., 0., 0., 0.}, {2., 1., 0., 0.}, {4., 0., 3., 0.}, {0., 0., 0., 1.}} + , + TestID->"SparseArrayTestSuite-20201130-X2E9G1" +]; + +Test[ + $ToTensor[zero] + , + {0} + , + TestID->"SparseArrayTestSuite-20201130-U0R1P8" +]; + +Test[ + $SetImplicitValue[sparse, 1.] + , + SparseArray[ + Automatic, + {4, 4}, + 1., + { + 1, + {{0, 3, 6, 10, 13}, {{2}, {3}, {4}, {1}, {3}, {4}, {1}, {2}, {3}, {4}, {1}, {2}, {3}}}, + {0., 0., 0., 2., 0., 0., 4., 0., 3., 0., 0., 0., 0.} + } + ] + , + TestID->"SparseArrayTestSuite-20201130-Y4S0B5" +]; + +Test[ + $SetImplicitValue[zero, 1] + , + SparseArray[Automatic, {1}, 1, {1, {{0, 1}, {{1}}}, {0}}] + , + TestID->"SparseArrayTestSuite-20201130-A3S2K4" +]; + +Test[ + $ModifyValues[sparse, {3.5, .5, -7.}]; + $GetExplicitValues[sparse] + , + {3.5, .5, -7., 4., 3., 1.} + , + TestID->"SparseArrayTestSuite-20210115-J6F3M0" +]; + +TestExecute[ + sparse = SparseArray[{{1., 0., 0., 0.}, {2., 1., 0., 0.}, {4., 0., 3., 0.}, {0., 0., 0., 1.}}]; +]; + +Test[ + $GetImplicitValueTyped[sparse] + , + 0. + , + TestID->"SparseArrayTestSuite-20210202-Z6Z6J6" +]; + +Test[ + $GetExplicitValuesTyped[sparse] + , + {1., 2., 1., 4., 3., 1.} + , + TestID->"SparseArrayTestSuite-20210202-M2I0M4" +]; + +Test[ + $GetRowPointersTyped[sparse] + , + {0, 1, 3, 5, 6} + , + TestID->"SparseArrayTestSuite-20210202-P4J9J2" +]; + +Test[ + $GetColumnIndicesTyped[sparse] + , + {{1}, {1}, {2}, {1}, {3}, {4}} + , + TestID->"SparseArrayTestSuite-20210202-T3R7B9" +]; + +Test[ + $GetExplicitPositionsTyped[sparse] + , + {{1, 1}, {2, 1}, {2, 2}, {3, 1}, {3, 3}, {4, 4}} + , + TestID->"SparseArrayTestSuite-20210202-O6A5L7" +]; + +Test[ + $ToTensorTyped[sparse] + , + {{1., 0., 0., 0.}, {2., 1., 0., 0.}, {4., 0., 3., 0.}, {0., 0., 0., 1.}} + , + TestID->"SparseArrayTestSuite-20210202-Z9L6I6" +]; + +Test[ + $SetImplicitValueTyped[sparse, 1.] + , + SparseArray[ + Automatic, + {4, 4}, + 1., + { + 1, + {{0, 3, 6, 10, 13}, {{2}, {3}, {4}, {1}, {3}, {4}, {1}, {2}, {3}, {4}, {1}, {2}, {3}}}, + {0., 0., 0., 2., 0., 0., 4., 0., 3., 0., 0., 0., 0.} + } + ] + , + TestID->"SparseArrayTestSuite-20210202-M7T4Z9" +]; \ No newline at end of file diff --git a/tests/UnitTests/SparseArray/TestSources/SparseArrayOperations.cpp b/tests/UnitTests/SparseArray/TestSources/SparseArrayOperations.cpp new file mode 100644 index 0000000..2e422d8 --- /dev/null +++ b/tests/UnitTests/SparseArray/TestSources/SparseArrayOperations.cpp @@ -0,0 +1,126 @@ +/** + * @file SparseArrayOperations.cpp + * @author Rafal Chojna + * @date November 25, 2020 + * @brief + */ + +#include +#include +#include + +EXTERN_C DLLEXPORT int WolframLibrary_initialize(WolframLibraryData libData) { + LLU::LibraryData::setLibraryData(libData); + return 0; +} + +LLU_LIBRARY_FUNCTION(GetImplicitValue) { + const auto sp = mngr.getGenericSparseArray(0); + auto implVal = sp.getImplicitValueAsTensor(); + mngr.set(implVal.clone()); +} + +LLU_LIBRARY_FUNCTION(GetExplicitValues) { + const auto sp = mngr.getGenericSparseArray(0); + auto explValues = sp.getExplicitValues(); + mngr.set(explValues.clone()); +} + +LLU_LIBRARY_FUNCTION(GetRowPointers) { + const auto sp = mngr.getGenericSparseArray(0); + auto rowPointers = sp.getRowPointers(); + mngr.set(rowPointers.clone()); +} + +LLU_LIBRARY_FUNCTION(GetColumnIndices) { + const auto sp = mngr.getGenericSparseArray(0); + auto colIndices = sp.getColumnIndices(); + mngr.set(colIndices.clone()); +} + +LLU_LIBRARY_FUNCTION(GetExplicitPositions) { + const auto sp = mngr.getGenericSparseArray(0); + auto explPositions = sp.getExplicitPositions(); + mngr.set(explPositions); +} + +LLU_LIBRARY_FUNCTION(ToTensor) { + const auto sp = mngr.getGenericSparseArray(0); + auto tensor = sp.toGenericTensor(); + mngr.set(tensor); +} + +LLU_LIBRARY_FUNCTION(SetImplicitValue) { + auto sp = mngr.getGenericSparseArray(0); + auto implValue = mngr.getGenericTensor(1); + sp.setImplicitValueFromTensor(implValue); + mngr.set(sp); +} + +template +void sparseModifyValues(LLU::SparseArray& sa, LLU::TensorTypedView newValues) { + auto values = sa.explicitValues(); + if (values.size() < newValues.size()) { + throw std::runtime_error {"Too many values provided."}; + } + std::copy(std::cbegin(newValues), std::cend(newValues), std::begin(values)); + + /* Recompute explicit positions */ + sa.resparsify(); +} + +LLU_LIBRARY_FUNCTION(ModifyValues) { + auto sp = mngr.getGenericSparseArray(0); + auto values = mngr.getGenericTensor(1); + auto dataType = sp.type(); + if (dataType != values.type()) { + throw std::runtime_error {"Inconsistent types."}; + } + LLU::asTypedSparseArray(sp, [&values](auto&& sparseArray) { + using T = typename std::remove_reference_t::value_type; + sparseModifyValues(sparseArray, LLU::TensorTypedView {values}); + }); +} + +LLU_LIBRARY_FUNCTION(GetImplicitValueTyped) { + const auto sp = mngr.getSparseArray(0); + auto implVal = sp.implicitValue(); + mngr.set(implVal); +} + +LLU_LIBRARY_FUNCTION(GetExplicitValuesTyped) { + const auto sp = mngr.getSparseArray(0); + auto explValues = sp.explicitValues(); + mngr.set(explValues.clone()); +} + +LLU_LIBRARY_FUNCTION(GetRowPointersTyped) { + const auto sp = mngr.getSparseArray(0); + auto rowPointers = sp.rowPointers(); + mngr.set(rowPointers.clone()); +} + +LLU_LIBRARY_FUNCTION(GetColumnIndicesTyped) { + const auto sp = mngr.getSparseArray(0); + auto colIndices = sp.columnIndices(); + mngr.set(colIndices.clone()); +} + +LLU_LIBRARY_FUNCTION(GetExplicitPositionsTyped) { + const auto sp = mngr.getSparseArray(0); + auto explPositions = sp.explicitPositions(); + mngr.set(explPositions); +} + +LLU_LIBRARY_FUNCTION(ToTensorTyped) { + const auto sp = mngr.getSparseArray(0); + auto tensor = sp.toTensor(); + mngr.set(tensor); +} + +LLU_LIBRARY_FUNCTION(SetImplicitValueTyped) { + auto sp = mngr.getSparseArray(0); + auto implValue = mngr.getReal(1); + sp.setImplicitValue(implValue); + mngr.set(sp); +} \ No newline at end of file diff --git a/tests/UnitTests/Tensor/TensorTestSuite.mt b/tests/UnitTests/Tensor/TensorTestSuite.mt index 3147ea2..dc3431d 100644 --- a/tests/UnitTests/Tensor/TensorTestSuite.mt +++ b/tests/UnitTests/Tensor/TensorTestSuite.mt @@ -35,6 +35,8 @@ TestExecute[ CreateMatrix = LibraryFunctionLoad[lib, "CreateMatrix", {Integer, Integer}, {Integer, 2}]; EmptyVector = LibraryFunctionLoad[lib, "CreateEmptyVector", {}, {Integer, 1}]; + (* Rank 0 tensor can only be returned with return type declared as {Integer, _}. If you try Integer or {Integer, 0} it will return garbage. *) + RankZero = LibraryFunctionLoad[lib, "CreateRankZero", {}, {Integer, _}]; EmptyMatrix = LibraryFunctionLoad[lib, "CreateEmptyMatrix", {}, {Integer, _}]; CloneTensor = LibraryFunctionLoad[lib, "CloneTensor", {{_, _, "Constant"}}, {_, _}]; TestDimensions = LibraryFunctionLoad[lib, "TestDimensions", {{Integer, 1, "Manual"}}, {Real, _}]; @@ -164,14 +166,38 @@ Test[ TestID -> "TensorTestSuite-20190726-N5W9J1" ]; +Test[ + RankZero[] + , + 42 + , + TestID -> "TensorTestSuite-20190726-M4C8B7" +]; + Test[ TestDimensions[{}] , + 0. + , + TestID -> "TensorTestSuite-20190729-X1X5Q8" +]; + +Test[ + TestDimensions[{0}] + , + {} + , + TestID -> "TensorTestSuite-20210222-C8G2A6" +]; + +Test[ + TestDimensions[{0, 0}] + , LibraryFunctionError["LIBRARY_DIMENSION_ERROR", 3] , LibraryFunction::dimerr , - TestID -> "TensorTestSuite-20190729-X1X5Q8" + TestID -> "TensorTestSuite-20210222-L8F7O5" ]; Test[ diff --git a/tests/UnitTests/Tensor/TestSources/Basic.cpp b/tests/UnitTests/Tensor/TestSources/Basic.cpp index 11ff34a..3c47c93 100644 --- a/tests/UnitTests/Tensor/TestSources/Basic.cpp +++ b/tests/UnitTests/Tensor/TestSources/Basic.cpp @@ -31,6 +31,11 @@ LLU_LIBRARY_FUNCTION(CreateEmptyVector) { mngr.setTensor(out); } +LLU_LIBRARY_FUNCTION(CreateRankZero) { + Tensor out(42, {}); + mngr.setTensor(out); +} + LLU_LIBRARY_FUNCTION(CreateEmptyMatrix) { Tensor out(0, {3, 5, 0}); mngr.setTensor(out);