diff --git a/CHANGELOG.md b/CHANGELOG.md index ba0f1dde6..ec0df53b4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -67,6 +67,7 @@ - Added `ConstantSignalSource` component as first use case for `BusToSignalAdapter`. - Added IDA option to suppress algebraic variables in local error tests. - Removed `COO_Matrix` class. +- Added portable `Vector` class and policy-based memory utilities. ## v0.1 diff --git a/GridKit/AutomaticDifferentiation/DependencyTracking/Variable.hpp b/GridKit/AutomaticDifferentiation/DependencyTracking/Variable.hpp index b6285abc0..6e2f216d0 100644 --- a/GridKit/AutomaticDifferentiation/DependencyTracking/Variable.hpp +++ b/GridKit/AutomaticDifferentiation/DependencyTracking/Variable.hpp @@ -12,6 +12,7 @@ #include #include #include +#include #include #include @@ -38,8 +39,7 @@ namespace GridKit Variable() : value_(0.0), variable_number_(INVALID_VAR_NUMBER), - is_fixed_(false), - dependencies_(new DependencyMap) + is_fixed_(false) { } @@ -49,8 +49,7 @@ namespace GridKit explicit Variable(double value) : value_(value), variable_number_(INVALID_VAR_NUMBER), - is_fixed_(false), - dependencies_(new DependencyMap) + is_fixed_(false) { } @@ -62,10 +61,9 @@ namespace GridKit Variable(double value, size_t variable_number) : value_(value), variable_number_(variable_number), - is_fixed_(false), - dependencies_(new DependencyMap) + is_fixed_(false) { - (*dependencies_)[variable_number_] = 1.0; + dependencies_[variable_number_] = 1.0; } /** @@ -75,18 +73,33 @@ namespace GridKit : value_(v.value_), variable_number_(INVALID_VAR_NUMBER), is_fixed_(false), - dependencies_(new DependencyMap(*v.dependencies_)) + dependencies_(v.dependencies_) { } /** - @brief Destructor deletes the dependency map. + @brief Move constructor. + + Unlike the copy constructor, this relocates @a v rather than + creating a new, detached temporary from it, so (unlike copy) + it carries over `variable_number_`/`is_fixed_` as-is instead + of resetting them. The dependency map is relocated via + `std::map`'s own move constructor (O(1)) instead of being + deep-copied. */ - ~Variable() + Variable(Variable&& v) noexcept + : value_(v.value_), + variable_number_(v.variable_number_), + is_fixed_(v.is_fixed_), + dependencies_(std::move(v.dependencies_)) { - delete dependencies_; } + /** + @brief Destructor. + */ + ~Variable() = default; + /** @brief Assignment operator. Assigning double value to Variable removes its dependencies. Use only if you know @@ -96,7 +109,7 @@ namespace GridKit { value_ = rhs; - dependencies_->clear(); + dependencies_.clear(); return *this; } @@ -120,8 +133,34 @@ namespace GridKit setFixed(rhs.is_fixed()); // set dependencies from rhs - dependencies_->clear(); // clear map just in case - addDependencies(rhs); // use only dependencies from the rhs + dependencies_.clear(); // clear map just in case + addDependencies(rhs); // use only dependencies from the rhs + + return *this; + } + + /** + @brief Move assignment operator. + + Same contract as the copy assignment operator (value and + dependencies come from rhs, variable ID of *this is left + unchanged), but relocates rhs's dependency map via + `std::map`'s own move assignment instead of deep-copying its + entries. + */ + Variable& operator=(Variable&& rhs) noexcept + { + if (this == &rhs) // self-assignment + return *this; + + // set value from rhs + value_ = rhs.value_; + + // if rhs is a constant so is *this + setFixed(rhs.is_fixed()); + + // relocate dependencies from rhs instead of copying them + dependencies_ = std::move(rhs.dependencies_); return *this; } @@ -170,7 +209,7 @@ namespace GridKit */ double der(size_t i) const { - return (*dependencies_)[i]; + return dependencies_[i]; } /** @@ -189,9 +228,9 @@ namespace GridKit */ void setVariableNumber(size_t variable_number) { - dependencies_->clear(); - variable_number_ = variable_number; - (*dependencies_)[variable_number_] = 1.0; + dependencies_.clear(); + variable_number_ = variable_number; + dependencies_[variable_number_] = 1.0; } /** @@ -266,8 +305,8 @@ namespace GridKit size_t variable_number_; ///< Independent variable ID bool is_fixed_; ///< Constant parameter flag. - mutable DependencyMap* dependencies_; - static const size_t INVALID_VAR_NUMBER = INVALID_INDEX; + mutable DependencyMap dependencies_; + static const size_t INVALID_VAR_NUMBER = INVALID_INDEX; }; //------------------------------------ @@ -275,27 +314,27 @@ namespace GridKit //------------------------------------ // unary - - inline const Variable operator-(const Variable& v); + inline Variable operator-(const Variable& v); // + - inline const Variable operator+(const Variable& lhs, const Variable& rhs); - inline const Variable operator+(const Variable& lhs, const double& rhs); - inline const Variable operator+(const double& lhs, const Variable& rhs); + inline Variable operator+(const Variable& lhs, const Variable& rhs); + inline Variable operator+(const Variable& lhs, const double& rhs); + inline Variable operator+(const double& lhs, const Variable& rhs); // - - inline const Variable operator-(const Variable& lhs, const Variable& rhs); - inline const Variable operator-(const Variable& lhs, const double& rhs); - inline const Variable operator-(const double& lhs, const Variable& rhs); + inline Variable operator-(const Variable& lhs, const Variable& rhs); + inline Variable operator-(const Variable& lhs, const double& rhs); + inline Variable operator-(const double& lhs, const Variable& rhs); // * - inline const Variable operator*(const Variable& lhs, const Variable& rhs); - inline const Variable operator*(const Variable& lhs, const double& rhs); - inline const Variable operator*(const double& lhs, const Variable& rhs); + inline Variable operator*(const Variable& lhs, const Variable& rhs); + inline Variable operator*(const Variable& lhs, const double& rhs); + inline Variable operator*(const double& lhs, const Variable& rhs); // / - inline const Variable operator/(const Variable& lhs, const Variable& rhs); - inline const Variable operator/(const Variable& lhs, const double& rhs); - inline const Variable operator/(const double& lhs, const Variable& rhs); + inline Variable operator/(const Variable& lhs, const Variable& rhs); + inline Variable operator/(const Variable& lhs, const double& rhs); + inline Variable operator/(const double& lhs, const Variable& rhs); // == inline bool operator==(const Variable& lhs, const Variable& rhs); diff --git a/GridKit/AutomaticDifferentiation/DependencyTracking/VariableImplementation.hpp b/GridKit/AutomaticDifferentiation/DependencyTracking/VariableImplementation.hpp index c685848ee..ae55dc3e4 100644 --- a/GridKit/AutomaticDifferentiation/DependencyTracking/VariableImplementation.hpp +++ b/GridKit/AutomaticDifferentiation/DependencyTracking/VariableImplementation.hpp @@ -13,8 +13,7 @@ namespace GridKit */ const Variable::DependencyMap& Variable::getDependencies() const { - assert(dependencies_ != 0); - return *dependencies_; + return dependencies_; } /** @@ -35,8 +34,8 @@ namespace GridKit */ void Variable::addDependencies(const Variable& v) { - for (auto& p : *(v.dependencies_)) - (*dependencies_)[p.first] = p.second; + for (auto& p : v.dependencies_) + dependencies_[p.first] = p.second; } /** @@ -44,8 +43,8 @@ namespace GridKit */ void Variable::scaleDependencies(double c) { - for (auto& p : *dependencies_) - (*dependencies_)[p.first] *= c; + for (auto& p : dependencies_) + p.second *= c; } /** @@ -67,10 +66,10 @@ namespace GridKit return; } - if (dependencies_ != NULL && !dependencies_->empty()) + if (!dependencies_.empty()) { os << " dependencies: [ "; - for (auto& p : *dependencies_) + for (auto& p : dependencies_) os << "(" << p.first << ", " << p.second << ") "; os << "]"; } @@ -97,8 +96,8 @@ namespace GridKit Variable& Variable::operator+=(const Variable& rhs) { // compute partial derivatives of *this - for (auto& p : *(rhs.dependencies_)) - (*dependencies_)[p.first] += (p.second); + for (auto& p : rhs.dependencies_) + dependencies_[p.first] += (p.second); // compute value of *this value_ += rhs.value_; @@ -124,8 +123,8 @@ namespace GridKit Variable& Variable::operator-=(const Variable& rhs) { // compute partial derivatives of *this - for (auto& p : *(rhs.dependencies_)) - (*dependencies_)[p.first] -= (p.second); + for (auto& p : rhs.dependencies_) + dependencies_[p.first] -= (p.second); // compute value of *this value_ -= rhs.value_; @@ -159,8 +158,8 @@ namespace GridKit scaleDependencies(rhs.value_); // compute partial derivatives of rhs and add them to *this - for (auto& p : *(rhs.dependencies_)) - (*dependencies_)[p.first] += (p.second * value_); + for (auto& p : rhs.dependencies_) + dependencies_[p.first] += (p.second * value_); // compute value of this value_ *= rhs.value_; @@ -195,8 +194,8 @@ namespace GridKit // derivation by parts of @a *this scaleDependencies(inverseRhs); - for (auto& p : *(rhs.dependencies_)) - (*dependencies_)[p.first] -= (p.second * value_ * inverseRhsSq); + for (auto& p : rhs.dependencies_) + dependencies_[p.first] -= (p.second * value_ * inverseRhsSq); // compute value of this value_ *= inverseRhs; diff --git a/GridKit/AutomaticDifferentiation/DependencyTracking/VariableOperators.hpp b/GridKit/AutomaticDifferentiation/DependencyTracking/VariableOperators.hpp index de7a29008..ec786da3d 100644 --- a/GridKit/AutomaticDifferentiation/DependencyTracking/VariableOperators.hpp +++ b/GridKit/AutomaticDifferentiation/DependencyTracking/VariableOperators.hpp @@ -13,73 +13,97 @@ namespace GridKit //------------------------------------ // unary - - const Variable operator-(const Variable& v) + Variable operator-(const Variable& v) { return -1.0 * v; } // + - const Variable operator+(const Variable& lhs, const Variable& rhs) + Variable operator+(const Variable& lhs, const Variable& rhs) { - return Variable(lhs) += rhs; + Variable result(lhs); + result += rhs; + return result; } - const Variable operator+(const Variable& lhs, const double& rhs) + Variable operator+(const Variable& lhs, const double& rhs) { - return Variable(lhs) += rhs; + Variable result(lhs); + result += rhs; + return result; } - const Variable operator+(const double& lhs, const Variable& rhs) + Variable operator+(const double& lhs, const Variable& rhs) { - return Variable(rhs) += lhs; + Variable result(rhs); + result += lhs; + return result; } // - - const Variable operator-(const Variable& lhs, const Variable& rhs) + Variable operator-(const Variable& lhs, const Variable& rhs) { - return Variable(lhs) -= rhs; + Variable result(lhs); + result -= rhs; + return result; } - const Variable operator-(const Variable& lhs, const double& rhs) + Variable operator-(const Variable& lhs, const double& rhs) { - return Variable(lhs) -= rhs; + Variable result(lhs); + result -= rhs; + return result; } - const Variable operator-(const double& lhs, const Variable& rhs) + Variable operator-(const double& lhs, const Variable& rhs) { - return Variable(lhs) -= rhs; + Variable result(lhs); + result -= rhs; + return result; } // * - const Variable operator*(const Variable& lhs, const Variable& rhs) + Variable operator*(const Variable& lhs, const Variable& rhs) { - return Variable(lhs) *= rhs; + Variable result(lhs); + result *= rhs; + return result; } - const Variable operator*(const Variable& lhs, const double& rhs) + Variable operator*(const Variable& lhs, const double& rhs) { - return Variable(lhs) *= rhs; + Variable result(lhs); + result *= rhs; + return result; } - const Variable operator*(const double& lhs, const Variable& rhs) + Variable operator*(const double& lhs, const Variable& rhs) { - return Variable(lhs) *= rhs; + Variable result(lhs); + result *= rhs; + return result; } // / - const Variable operator/(const Variable& lhs, const Variable& rhs) + Variable operator/(const Variable& lhs, const Variable& rhs) { - return Variable(lhs) /= rhs; + Variable result(lhs); + result /= rhs; + return result; } - const Variable operator/(const Variable& lhs, const double& rhs) + Variable operator/(const Variable& lhs, const double& rhs) { - return Variable(lhs) /= rhs; + Variable result(lhs); + result /= rhs; + return result; } - const Variable operator/(const double& lhs, const Variable& rhs) + Variable operator/(const double& lhs, const Variable& rhs) { - return Variable(lhs) /= rhs; + Variable result(lhs); + result /= rhs; + return result; } // == @@ -354,22 +378,22 @@ namespace std return res; \ } -#define IMPL_FUN_2(FUN, DER1, DER2) \ - inline GridKit::DependencyTracking::Variable FUN(const GridKit::DependencyTracking::Variable& x, \ - const GridKit::DependencyTracking::Variable& y) \ - { \ - double val = FUN(x(), y()); \ - double der1 = DER1(x(), y()); \ - double der2 = DER2(x(), y()); \ - GridKit::DependencyTracking::Variable res1(x); /* copy derivatives of x*/ \ - GridKit::DependencyTracking::Variable res2(y); /* copy derivatives of y*/ \ - res1.setValue(val); /* set function value f(x, y) */ \ - res2.setValue(val); /* set function value f(x, y) */ \ - res1.scaleDependencies(der1); /* compute derivatives of f(x, y) with respect to x */ \ - res2.scaleDependencies(der2); /* compute derivatives of f(x, y) with respect to y */ \ - GridKit::DependencyTracking::Variable res(res1); /* add derivatives with respect to x to return */ \ - res.addDependencies(res2); /* add derivatives with respect to y to return */ \ - return res; \ +#define IMPL_FUN_2(FUN, DER1, DER2) \ + inline GridKit::DependencyTracking::Variable FUN(const GridKit::DependencyTracking::Variable& x, \ + const GridKit::DependencyTracking::Variable& y) \ + { \ + double val = FUN(x(), y()); \ + double der1 = DER1(x(), y()); \ + double der2 = DER2(x(), y()); \ + GridKit::DependencyTracking::Variable res1(x); /* copy derivatives of x*/ \ + GridKit::DependencyTracking::Variable res2(y); /* copy derivatives of y*/ \ + res1.setValue(val); /* set function value f(x, y) */ \ + res2.setValue(val); /* set function value f(x, y) */ \ + res1.scaleDependencies(der1); /* compute derivatives of f(x, y) with respect to x */ \ + res2.scaleDependencies(der2); /* compute derivatives of f(x, y) with respect to y */ \ + GridKit::DependencyTracking::Variable res(std::move(res1)); /* relocate derivatives w.r.t. x into return */ \ + res.addDependencies(res2); /* add derivatives with respect to y to return */ \ + return res; \ } IMPL_FUN_1(sin, GridKit::DependencyTracking::sin_derivative) diff --git a/GridKit/CMakeLists.txt b/GridKit/CMakeLists.txt index f1ce7b5a4..4c3f7ffef 100644 --- a/GridKit/CMakeLists.txt +++ b/GridKit/CMakeLists.txt @@ -11,6 +11,9 @@ install(TARGETS definitions EXPORT gridkit-targets) # General Utilities and File IO add_subdirectory(Utilities) +# Memory management utilities +add_subdirectory(MemoryUtilities) + # Create component models add_subdirectory(Model) diff --git a/GridKit/LinearAlgebra/CMakeLists.txt b/GridKit/LinearAlgebra/CMakeLists.txt index 98d64e05a..e645e23ed 100644 --- a/GridKit/LinearAlgebra/CMakeLists.txt +++ b/GridKit/LinearAlgebra/CMakeLists.txt @@ -1,5 +1,3 @@ add_subdirectory(SparseMatrix) add_subdirectory(DenseMatrix) add_subdirectory(Vector) - -install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/MemoryUtils.hpp DESTINATION include/GridKit/LinearAlgebra) diff --git a/GridKit/LinearAlgebra/MemoryUtils.hpp b/GridKit/LinearAlgebra/MemoryUtils.hpp deleted file mode 100644 index 9738b890a..000000000 --- a/GridKit/LinearAlgebra/MemoryUtils.hpp +++ /dev/null @@ -1,318 +0,0 @@ -#pragma once - -#include - -namespace GridKit -{ - namespace LinearAlgebra - { - - namespace memory - { - enum MemorySpace - { - HOST = 0, - DEVICE - }; - - /** - * @brief Class containing dummy functions when there is no GPU support. - * - * @author Slaven Peles - */ - struct Cpu - { - /** - * @brief Dummy function to stand in when GPU support is not enabled. - */ - static void deviceSynchronize() - { - // Nothing to synchronize - } - - /** - * @brief Dummy function to stand in when GPU support is not enabled. - * - * @return Always return success! - */ - static int getLastDeviceError() - { - // not on device, nothing to get - return 0; - } - - /** - * @brief Dummy function to notify us something is wrong. - * - * This will be called only if GPU device support is not built, so - * trying to access a device should indicate a bug in the code. - * - * @return Always return failure! - */ - static int deleteOnDevice(void* /* v */) - { - std::cerr << "Trying to delete on a GPU device, but GPU support not available.\n"; - return -1; - } - - /** - * @brief Dummy function to notify us something is wrong. - * - * This will be called only if GPU device support is not built, so - * trying to access a device should indicate a bug in the code. - * - * @return Always return failure! - */ - template - static int allocateArrayOnDevice(T** /* v */, I /* n */) - { - std::cerr << "Trying to allocate on a GPU device, but GPU support not available.\n"; - return -1; - } - - /** - * @brief Dummy function to notify us something is wrong. - * - * This will be called only if GPU device support is not built, so - * trying to access a device should indicate a bug in the code. - * - * @return Always return failure! - */ - template - static int allocateBufferOnDevice(T** /* v */, I /* n */) - { - std::cerr << "Trying to allocate on a GPU device, but GPU support not available.\n"; - return -1; - } - - /** - * @brief Dummy function to notify us something is wrong. - * - * This will be called only if GPU device support is not built, so - * trying to access a device should indicate a bug in the code. - * - * @return Always return failure! - */ - template - static int setZeroArrayOnDevice(T* /* v */, I /* n */) - { - std::cerr << "Trying to initialize array on a GPU device, but GPU support not available.\n"; - return -1; - } - - /** - * @brief Dummy function to notify us something is wrong. - * - * This will be called only if GPU device support is not built, so - * trying to access a device should indicate a bug in the code. - * - * @return Always return failure! - */ - template - static int setArrayToConstOnDevice(T* /* v */, T /* c */, I /* n */) - { - std::cerr << "Trying to initialize array on a GPU device, but GPU support not available.\n"; - return -1; - } - - /** - * @brief Dummy function to notify us something is wrong. - * - * This will be called only if GPU device support is not built, so - * trying to access a device should indicate a bug in the code. - * - * @return Always return failure! - */ - template - static int copyArrayDeviceToHost(T* /* dst */, const T* /* src */, I /* n */) - { - std::cerr << "Trying to copy from a GPU device, but GPU support not available.\n"; - return -1; - } - - /** - * @brief Dummy function to notify us something is wrong. - * - * This will be called only if GPU device support is not built, so - * trying to access a device should indicate a bug in the code. - * - * @return Always return failure! - */ - template - static int copyArrayDeviceToDevice(T* /* dst */, const T* /* src */, I /* n */) - { - std::cerr << "Trying to copy to a GPU device, but GPU support not available.\n"; - return -1; - } - - template - static int copyArrayHostToDevice(T* /* dst */, const T* /* src */, I /* n */) - { - std::cerr << "Trying to copy to a GPU device, but GPU support not available.\n"; - return -1; - } - - }; // struct Cpu - }; // namespace memory - - /** - * @class MemoryUtils - * - * @brief Provides basic memory allocation, free and copy functions. - * - * This class provides abstractions for memory management functions for - * different GPU programming models. - * - * @tparam Policy - Memory management policy (vendor specific) - * - * @author Slaven Peles - */ - template - class MemoryUtils - { - public: - MemoryUtils() = default; - ~MemoryUtils() = default; - - void deviceSynchronize(); - int getLastDeviceError(); - int deleteOnDevice(void* v); - - template - int allocateArrayOnDevice(T** v, I n); - - template - int allocateBufferOnDevice(T** v, I n); - - template - int setZeroArrayOnDevice(T* v, I n); - - template - int setArrayToConstOnDevice(T* v, T c, I n); - - template - int copyArrayDeviceToHost(T* dst, const T* src, I n); - - template - int copyArrayDeviceToDevice(T* dst, const T* src, I n); - - template - int copyArrayHostToDevice(T* dst, const T* src, I n); - - /// - /// Methods implemented here are always needed - /// - - template - int allocateArrayOnHost(T** v, I n) - { - std::size_t arraysize = static_cast(n) * sizeof(T); - *v = new T[arraysize]; - return *v == nullptr ? 1 : 0; - } - - template - int deleteOnHost(T* v) - { - delete[] v; - v = nullptr; - return 0; - } - - template - int copyArrayHostToHost(T* dst, const T* src, I n) - { - std::size_t arraysize = static_cast(n) * sizeof(T); - memcpy(dst, src, arraysize); - return 0; - } - - template - int setZeroArrayOnHost(T* v, I n) - { - std::size_t arraysize = static_cast(n) * sizeof(T); - memset(v, 0, arraysize); - return 0; - } - - template - int setArrayToConstOnHost(T* v, T c, I n) - { - for (I i = 0; i < n; ++i) - { - v[i] = c; - } - return 0; - } - }; - - template - void MemoryUtils::deviceSynchronize() - { - Policy::deviceSynchronize(); - } - - template - int MemoryUtils::getLastDeviceError() - { - return Policy::getLastDeviceError(); - } - - template - int MemoryUtils::deleteOnDevice(void* v) - { - return Policy::deleteOnDevice(v); - } - - template - template - int MemoryUtils::allocateArrayOnDevice(T** v, I n) - { - return Policy::template allocateArrayOnDevice(v, n); - } - - template - template - int MemoryUtils::allocateBufferOnDevice(T** v, I n) - { - return Policy::template allocateBufferOnDevice(v, n); - } - - template - template - int MemoryUtils::setZeroArrayOnDevice(T* v, I n) - { - return Policy::template setZeroArrayOnDevice(v, n); - } - - template - template - int MemoryUtils::setArrayToConstOnDevice(T* v, T c, I n) - { - return Policy::template setArrayToConstOnDevice(v, c, n); - } - - template - template - int MemoryUtils::copyArrayDeviceToHost(T* dst, const T* src, I n) - { - return Policy::template copyArrayDeviceToHost(dst, src, n); - } - - template - template - int MemoryUtils::copyArrayDeviceToDevice(T* dst, const T* src, I n) - { - return Policy::template copyArrayDeviceToDevice(dst, src, n); - } - - template - template - int MemoryUtils::copyArrayHostToDevice(T* dst, const T* src, I n) - { - return Policy::template copyArrayHostToDevice(dst, src, n); - } - - using MemoryHandler = MemoryUtils; - } // namespace LinearAlgebra -} // namespace GridKit diff --git a/GridKit/LinearAlgebra/SparseMatrix/CMakeLists.txt b/GridKit/LinearAlgebra/SparseMatrix/CMakeLists.txt index c7f5fb4f5..f112aef37 100644 --- a/GridKit/LinearAlgebra/SparseMatrix/CMakeLists.txt +++ b/GridKit/LinearAlgebra/SparseMatrix/CMakeLists.txt @@ -1,4 +1,5 @@ gridkit_add_library( sparse_matrix SOURCES CooMatrix.cpp CsrMatrix.cpp - HEADERS CooMatrix.hpp CsrMatrix.hpp) + HEADERS CooMatrix.hpp CsrMatrix.hpp + LINK_LIBRARIES GridKit::utilities_logger GridKit::cpu_backend) diff --git a/GridKit/LinearAlgebra/SparseMatrix/CooMatrix.hpp b/GridKit/LinearAlgebra/SparseMatrix/CooMatrix.hpp index b167d2760..54313e863 100644 --- a/GridKit/LinearAlgebra/SparseMatrix/CooMatrix.hpp +++ b/GridKit/LinearAlgebra/SparseMatrix/CooMatrix.hpp @@ -4,7 +4,7 @@ #include #include -#include +#include namespace GridKit { diff --git a/GridKit/LinearAlgebra/SparseMatrix/CsrMatrix.hpp b/GridKit/LinearAlgebra/SparseMatrix/CsrMatrix.hpp index ce1a89c50..d661a07cd 100644 --- a/GridKit/LinearAlgebra/SparseMatrix/CsrMatrix.hpp +++ b/GridKit/LinearAlgebra/SparseMatrix/CsrMatrix.hpp @@ -4,7 +4,7 @@ #include #include -#include +#include namespace GridKit { diff --git a/GridKit/LinearAlgebra/Vector/CMakeLists.txt b/GridKit/LinearAlgebra/Vector/CMakeLists.txt index a32b0cade..314eed26e 100644 --- a/GridKit/LinearAlgebra/Vector/CMakeLists.txt +++ b/GridKit/LinearAlgebra/Vector/CMakeLists.txt @@ -2,4 +2,9 @@ gridkit_add_library( dense_vector SOURCES Vector.cpp HEADERS Vector.hpp - LINK_LIBRARIES GridKit::utilities_logger) + LINK_LIBRARIES GridKit::utilities_logger GridKit::cpu_backend) + +# # If no GPU is enabled link to dummy device backend +# if(NOT GridKit_ENABLE_GPU) +# target_link_libraries(dense_vector PUBLIC GridKit::cpu_backend) +# endif(NOT GridKit_ENABLE_GPU) diff --git a/GridKit/LinearAlgebra/Vector/Vector.cpp b/GridKit/LinearAlgebra/Vector/Vector.cpp index abb77228e..bbbf82c98 100644 --- a/GridKit/LinearAlgebra/Vector/Vector.cpp +++ b/GridKit/LinearAlgebra/Vector/Vector.cpp @@ -107,8 +107,9 @@ namespace GridKit * @param[in] data - Pointer to data * @param[in] memspace - Memory space (HOST or DEVICE) * - * @pre Vector data pointers must be null. If the vector data already - * exists this function returns error message. + * @pre The vector's data pointer for the given `memspace` must be null. + * If data for that memspace already exists this function returns an + * error message; the other memspace's pointer is unaffected. * * @warning This function DOES NOT ALLOCATE any data, it only assigns the * pointer. @@ -120,21 +121,26 @@ namespace GridKit int Vector::setData(ScalarT* data, memory::MemorySpace memspace) { using namespace memory; - if (d_data_ || h_data_) - { - out::error() << "Vector::setData - data already exists, ignoring call\n"; - return 1; - } switch (memspace) { case HOST: + if (h_data_) + { + out::error() << "Vector::setData - host data already exists, ignoring call\n"; + return 1; + } h_data_ = data; setHostUpdated(true); setDeviceUpdated(false); owns_cpu_data_ = false; break; case DEVICE: + if (d_data_) + { + out::error() << "Vector::setData - device data already exists, ignoring call\n"; + return 1; + } d_data_ = data; setHostUpdated(false); setDeviceUpdated(true); @@ -194,6 +200,15 @@ namespace GridKit assert(cpu_updated_ && gpu_updated_ && "Update flags not allocated"); using namespace memory; + + if (k_ <= j) + { + out::error() << "Vector::setDataUpdated - vector index " << j + << " out of range, multivector has only " << k_ + << " vectors\n"; + return 1; + } + switch (memspace) { case HOST: @@ -241,6 +256,12 @@ namespace GridKit memory::MemorySpace memspaceSrc, memory::MemorySpace memspaceDst) { + if (source == nullptr) + { + out::error() << "Vector::copyFromExternal - source data is null or stale\n"; + return 1; + } + switch (memspaceDst) { case memory::HOST: @@ -572,6 +593,14 @@ namespace GridKit { using namespace memory; + if (k_ <= j) + { + out::error() << "Vector::syncData - vector index " << j + << " out of range, multivector has only " << k_ + << " vectors\n"; + return 1; + } + switch (memspaceDst) { case DEVICE: // cpu->gpu @@ -631,6 +660,7 @@ namespace GridKit switch (memspace) { case HOST: + { if (!owns_cpu_data_) { out::error() << "Vector::allocate - cannot reallocate host data," @@ -638,11 +668,18 @@ namespace GridKit return 1; } mem_.deleteOnHost(h_data_); - mem_.allocateArrayOnHost(&h_data_, n_capacity_ * k_); + int rc = mem_.allocateArrayOnHost(&h_data_, n_capacity_ * k_); + if (rc != 0) + { + out::error() << "Vector::allocate - failed to allocate host data\n"; + return 1; + } owns_cpu_data_ = true; setHostUpdated(false); break; + } case DEVICE: + { if (!owns_gpu_data_) { out::error() << "Vector::allocate - cannot reallocate device data," @@ -650,11 +687,17 @@ namespace GridKit return 1; } mem_.deleteOnDevice(d_data_); - mem_.allocateArrayOnDevice(&d_data_, n_capacity_ * k_); + int rc = mem_.allocateArrayOnDevice(&d_data_, n_capacity_ * k_); + if (rc != 0) + { + out::error() << "Vector::allocate - failed to allocate device data\n"; + return 1; + } owns_gpu_data_ = true; setDeviceUpdated(false); break; } + } return 0; } @@ -708,6 +751,15 @@ namespace GridKit int Vector::setToZero(IdxT j, memory::MemorySpace memspace) { using namespace memory; + + if (k_ <= j) + { + out::error() << "Vector::setToZero - vector index " << j + << " out of range, multivector has only " << k_ + << " vectors\n"; + return 1; + } + switch (memspace) { case HOST: @@ -787,6 +839,15 @@ namespace GridKit int Vector::setToConst(IdxT j, ScalarT C, memory::MemorySpace memspace) { using namespace memory; + + if (k_ <= j) + { + out::error() << "Vector::setToConst - vector index " << j + << " out of range, multivector has only " << k_ + << " vectors\n"; + return 1; + } + switch (memspace) { case HOST: @@ -817,8 +878,14 @@ namespace GridKit * @brief Resize vector to `new_n_size`. * * Use for vectors and multivectors that change size throughout computation. + * If `new_n_size` is within the current capacity, this simply adjusts + * `n_size_`. If `new_n_size` exceeds the current capacity, the vector + * reallocates - in whichever memory spaces (HOST and/or DEVICE) are + * currently allocated - to a buffer sized for `new_n_size`, copies over + * the existing per-column data, and adopts `new_n_size` as the new + * capacity. Data beyond the previous size is left uninitialized, same + * as freshly allocated data. * - * @note Vector needs to have capacity set to maximum expected size. * @warning This method is not to be used in vectors who do not own their * data. * @@ -826,7 +893,7 @@ namespace GridKit * * @return 0 if successful, 1 otherwise. * - * @pre `new_n_size` <= `n_capacity_` + * @todo Decide if we need to preserve data when resizing. */ template int Vector::resize(IdxT new_n_size) @@ -834,17 +901,40 @@ namespace GridKit assert(owns_cpu_data_ && owns_gpu_data_ && "Cannot resize if vector is not owning the data."); - if (new_n_size > n_capacity_) - { - out::error() << "Vector::resize - requested size " << new_n_size - << " exceeds capacity " << n_capacity_ << "\n"; - return 1; - } - else + if (new_n_size <= n_capacity_) { n_size_ = new_n_size; return 0; } + + // Grow beyond current capacity: reallocate and copy over existing + // per-column data. Columns are stored on n_size_-element strides, so + // the old stride must be captured before n_size_/n_capacity_ change. + const IdxT old_n_size = n_size_; + + if (h_data_ != nullptr) + { + ScalarT* new_data = nullptr; + mem_.allocateArrayOnHost(&new_data, new_n_size * k_); + for (IdxT j = 0; j < k_; ++j) + mem_.copyArrayHostToHost(&new_data[j * new_n_size], &h_data_[j * old_n_size], old_n_size); + mem_.deleteOnHost(h_data_); + h_data_ = new_data; + } + + if (d_data_ != nullptr) + { + ScalarT* new_data = nullptr; + mem_.allocateArrayOnDevice(&new_data, new_n_size * k_); + for (IdxT j = 0; j < k_; ++j) + mem_.copyArrayDeviceToDevice(&new_data[j * new_n_size], &d_data_[j * old_n_size], old_n_size); + mem_.deleteOnDevice(d_data_); + d_data_ = new_data; + } + + n_capacity_ = new_n_size; + n_size_ = new_n_size; + return 0; } /** diff --git a/GridKit/LinearAlgebra/Vector/Vector.hpp b/GridKit/LinearAlgebra/Vector/Vector.hpp index 2690fb0d9..f337d71e2 100644 --- a/GridKit/LinearAlgebra/Vector/Vector.hpp +++ b/GridKit/LinearAlgebra/Vector/Vector.hpp @@ -1,7 +1,7 @@ #pragma once #include -#include +#include namespace GridKit { @@ -31,7 +31,11 @@ namespace GridKit class Vector { public: - Vector() = default; + Vector() + : Vector(0) + { + } + Vector(IdxT n); Vector(IdxT n, IdxT k); ~Vector(); diff --git a/GridKit/MemoryUtilities/CMakeLists.txt b/GridKit/MemoryUtilities/CMakeLists.txt new file mode 100644 index 000000000..3cf65f94a --- /dev/null +++ b/GridKit/MemoryUtilities/CMakeLists.txt @@ -0,0 +1,15 @@ +#[[ + +@brief Build GridKit memory management utilities + +@author Slaven Peles + +]] + +add_subdirectory(cpu) +# add_subdirectory(cuda) +# add_subdirectory(hip) + +install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/MemoryUtils.hpp + ${CMAKE_CURRENT_SOURCE_DIR}/MemoryUtils.tpp + DESTINATION include/GridKit/MemoryUtilities) diff --git a/GridKit/MemoryUtilities/MemoryUtils.hpp b/GridKit/MemoryUtilities/MemoryUtils.hpp new file mode 100644 index 000000000..ca9419932 --- /dev/null +++ b/GridKit/MemoryUtilities/MemoryUtils.hpp @@ -0,0 +1,172 @@ +#pragma once + +#include +#include +#include + +namespace GridKit +{ + namespace memory + { + enum MemorySpace + { + HOST = 0, + DEVICE + }; + } // namespace memory +} // namespace GridKit + +namespace GridKit +{ + /** + * @class MemoryUtils + * + * @brief Provides basic memory allocation, free and copy functions. + * + * This class provides abstractions for memory management functions for + * different GPU programming models. + * + * @tparam Policy - Memory management policy (vendor specific) + * + * @author Slaven Peles + */ + template + class MemoryUtils + { + public: + MemoryUtils() = default; + ~MemoryUtils() = default; + + void deviceSynchronize(); + int getLastDeviceError(); + int deleteOnDevice(void* v); + + template + int allocateArrayOnDevice(T** v, I n); + + template + int allocateBufferOnDevice(T** v, I n); + + template + int setZeroArrayOnDevice(T* v, I n); + + template + int setArrayToConstOnDevice(T* v, T c, I n); + + template + int copyArrayDeviceToHost(T* dst, const T* src, I n); + + template + int copyArrayDeviceToDevice(T* dst, const T* src, I n); + + template + int copyArrayHostToDevice(T* dst, const T* src, I n); + + /// + /// Methods implemented here are always needed + /// + + template + int allocateArrayOnHost(T** v, I n) + { + *v = new T[static_cast(n)]; + return 0; + } + + template + int deleteOnHost(T*& v) + { + delete[] v; + v = nullptr; + return 0; + } + + /** + * @brief Copy an array from HOST to HOST. + * + * Trivially copyable types (plain scalars, etc.) are copied with a + * byte-wise memcpy. Types that are not trivially copyable (e.g. a type + * owning a heap-allocated data structure) would have that structure + * corrupted by a byte-wise copy, so those are copied element-by-element + * using the type's own copy assignment instead. + */ + template + int copyArrayHostToHost(T* dst, const T* src, I n) + { + if constexpr (std::is_trivially_copyable_v) + { + std::size_t arraysize = static_cast(n) * sizeof(T); + std::memcpy(dst, src, arraysize); + } + else + { + for (I i = 0; i < n; ++i) + dst[i] = src[i]; + } + return 0; + } + + /** + * @brief Set an array on HOST to zero. + * + * Trivially copyable types are zeroed with a byte-wise memset. Types + * that are not trivially copyable would have their internal state + * corrupted by a byte-wise zeroing, so those are reset to a + * value-initialized (default-constructed) state element-by-element + * instead. + */ + template + int setZeroArrayOnHost(T* v, I n) + { + if constexpr (std::is_trivially_copyable_v) + { + std::size_t arraysize = static_cast(n) * sizeof(T); + std::memset(v, 0, arraysize); + } + else + { + for (I i = 0; i < n; ++i) + v[i] = T{}; + } + return 0; + } + + template + int setArrayToConstOnHost(T* v, T c, I n) + { + for (I i = 0; i < n; ++i) + { + v[i] = c; + } + return 0; + } + }; // class MemoryUtils + +} // namespace GridKit + +// Device-side member templates are defined here so they are visible (and +// implicitly instantiated on demand for whatever a caller needs) in +// every translation unit that uses MemoryUtils, instead of relying on a +// hand-maintained list of explicit instantiations for one backend. +#include + +#ifdef GridKit_ENABLE_GPU + +// Check if GPU support is enabled in GridKit and set appropriate device memory manager. +#if defined GridKit_ENABLE_CUDA +#include +using MemoryHandler = GridKit::MemoryUtils; +#elif defined GridKit_ENABLE_HIP +#include +using MemoryHandler = GridKit::MemoryUtils; +#else +#error Unrecognized device, probably bug in CMake configuration +#endif + +#else + +// If no GPU support is present, set device memory manager to a dummy object. +#include +using MemoryHandler = GridKit::MemoryUtils; + +#endif diff --git a/GridKit/MemoryUtilities/MemoryUtils.tpp b/GridKit/MemoryUtilities/MemoryUtils.tpp new file mode 100644 index 000000000..fd28ddda2 --- /dev/null +++ b/GridKit/MemoryUtilities/MemoryUtils.tpp @@ -0,0 +1,81 @@ +/** + * @file MemoryUtils.tpp + * + * Contains implementation of memory utility functions wrappers. + * All it does it calls vendor specific functions frm an abstract interface. + * + * @author Slaven Peles + */ + +#pragma once + +namespace GridKit +{ + template + void MemoryUtils::deviceSynchronize() + { + Policy::deviceSynchronize(); + } + + template + int MemoryUtils::getLastDeviceError() + { + return Policy::getLastDeviceError(); + } + + template + int MemoryUtils::deleteOnDevice(void* v) + { + return Policy::deleteOnDevice(v); + } + + template + template + int MemoryUtils::allocateArrayOnDevice(T** v, I n) + { + return Policy::template allocateArrayOnDevice(v, n); + } + + template + template + int MemoryUtils::allocateBufferOnDevice(T** v, I n) + { + return Policy::template allocateBufferOnDevice(v, n); + } + + template + template + int MemoryUtils::setZeroArrayOnDevice(T* v, I n) + { + return Policy::template setZeroArrayOnDevice(v, n); + } + + template + template + int MemoryUtils::setArrayToConstOnDevice(T* v, T c, I n) + { + return Policy::template setArrayToConstOnDevice(v, c, n); + } + + template + template + int MemoryUtils::copyArrayDeviceToHost(T* dst, const T* src, I n) + { + return Policy::template copyArrayDeviceToHost(dst, src, n); + } + + template + template + int MemoryUtils::copyArrayDeviceToDevice(T* dst, const T* src, I n) + { + return Policy::template copyArrayDeviceToDevice(dst, src, n); + } + + template + template + int MemoryUtils::copyArrayHostToDevice(T* dst, const T* src, I n) + { + return Policy::template copyArrayHostToDevice(dst, src, n); + } + +} // namespace GridKit diff --git a/GridKit/MemoryUtilities/cpu/CMakeLists.txt b/GridKit/MemoryUtilities/cpu/CMakeLists.txt new file mode 100644 index 000000000..8a0f4017a --- /dev/null +++ b/GridKit/MemoryUtilities/cpu/CMakeLists.txt @@ -0,0 +1,17 @@ +#[[ + +@brief Build GridKit backend when there is no GPU support + +@author Slaven Peles + +]] + +set(GridKit_CPU_SRC MemoryUtils.cpp) + +set(GridKit_CPU_HEADER_INSTALL CpuMemory.hpp) + +# First create dummy backend +gridkit_add_library( + cpu_backend + SOURCES ${GridKit_CPU_SRC} + HEADERS ${GridKit_CPU_HEADER_INSTALL}) diff --git a/GridKit/MemoryUtilities/cpu/CpuMemory.hpp b/GridKit/MemoryUtilities/cpu/CpuMemory.hpp new file mode 100644 index 000000000..2b4ec595a --- /dev/null +++ b/GridKit/MemoryUtilities/cpu/CpuMemory.hpp @@ -0,0 +1,148 @@ +#pragma once + +#include + +namespace GridKit +{ + namespace memory + { + /** + * @brief Class containing dummy functions when there is no GPU support. + * + * @author Slaven Peles + */ + struct Cpu + { + /** + * @brief Dummy function to stand in when GPU support is not enabled. + */ + static void deviceSynchronize() + { + // Nothing to synchronize + } + + /** + * @brief Dummy function to stand in when GPU support is not enabled. + * + * @return Allways return success! + */ + static int getLastDeviceError() + { + // not on device, nothing to get + return 0; + } + + /** + * @brief Dummy function to notify us something is wrong. + * + * This will be called only if GPU device support is not built, so + * trying to access a device should indicate a bug in the code. + * + * @return Always return failure! + */ + static int deleteOnDevice(void* /* v */) + { + std::cerr << "Trying to delete on a GPU device, but GPU support not available.\n"; + return -1; + } + + /** + * @brief Dummy function to notify us something is wrong. + * + * This will be called only if GPU device support is not built, so + * trying to access a device should indicate a bug in the code. + * + * @return Always return failure! + */ + template + static int allocateArrayOnDevice(T** /* v */, I /* n */) + { + std::cerr << "Trying to allocate on a GPU device, but GPU support not available.\n"; + return -1; + } + + /** + * @brief Dummy function to notify us something is wrong. + * + * This will be called only if GPU device support is not built, so + * trying to access a device should indicate a bug in the code. + * + * @return Always return failure! + */ + template + static int allocateBufferOnDevice(T** /* v */, I /* n */) + { + std::cerr << "Trying to allocate on a GPU device, but GPU support not available.\n"; + return -1; + } + + /** + * @brief Dummy function to notify us something is wrong. + * + * This will be called only if GPU device support is not built, so + * trying to access a device should indicate a bug in the code. + * + * @return Always return failure! + */ + template + static int setZeroArrayOnDevice(T* /* v */, I /* n */) + { + std::cerr << "Trying to initialize array on a GPU device, but GPU support not available.\n"; + return -1; + } + + /** + * @brief Dummy function to notify us something is wrong. + * + * This will be called only if GPU device support is not built, so + * trying to access a device should indicate a bug in the code. + * + * @return Always return failure! + */ + template + static int setArrayToConstOnDevice(T* /* v */, T /* c */, I /* n */) + { + std::cerr << "Trying to initialize array on a GPU device, but GPU support not available.\n"; + return -1; + } + + /** + * @brief Dummy function to notify us something is wrong. + * + * This will be called only if GPU device support is not built, so + * trying to access a device should indicate a bug in the code. + * + * @return Always return failure! + */ + template + static int copyArrayDeviceToHost(T* /* dst */, const T* /* src */, I /* n */) + { + std::cerr << "Trying to copy from a GPU device, but GPU support not available.\n"; + return -1; + } + + /** + * @brief Dummy function to notify us something is wrong. + * + * This will be called only if GPU device support is not built, so + * trying to access a device should indicate a bug in the code. + * + * @return Always return failure! + */ + template + static int copyArrayDeviceToDevice(T* /* dst */, const T* /* src */, I /* n */) + { + std::cerr << "Trying to copy to a GPU device, but GPU support not available.\n"; + return -1; + } + + template + static int copyArrayHostToDevice(T* /* dst */, const T* /* src */, I /* n */) + { + std::cerr << "Trying to copy to a GPU device, but GPU support not available.\n"; + return -1; + } + }; // struct Cpu + + } // namespace memory +} // namespace GridKit diff --git a/GridKit/MemoryUtilities/cpu/MemoryUtils.cpp b/GridKit/MemoryUtilities/cpu/MemoryUtils.cpp new file mode 100644 index 000000000..501fabb20 --- /dev/null +++ b/GridKit/MemoryUtilities/cpu/MemoryUtils.cpp @@ -0,0 +1,24 @@ +/** + * @file MemoryUtils.cpp + * + * Explicitly instantiates the non-template members of + * MemoryUtils. The device-side member templates + * (allocateArrayOnDevice, copyArrayDeviceToHost, etc.) are defined in + * MemoryUtils.tpp, which is included directly by MemoryUtils.hpp, so they + * are implicitly instantiated on demand for whatever combination + * each caller (CooMatrix, CsrMatrix, Vector, ...) actually needs. + * + * @author Slaven Peles + */ + +#include + +#include +#include + +namespace GridKit +{ + template void MemoryUtils::deviceSynchronize(); + template int MemoryUtils::getLastDeviceError(); + template int MemoryUtils::deleteOnDevice(void*); +} // namespace GridKit diff --git a/GridKit/MemoryUtilities/cuda/.gitkeep b/GridKit/MemoryUtilities/cuda/.gitkeep new file mode 100644 index 000000000..e69de29bb diff --git a/GridKit/MemoryUtilities/hip/.gitkeep b/GridKit/MemoryUtilities/hip/.gitkeep new file mode 100644 index 000000000..e69de29bb diff --git a/GridKit/Model/PhasorDynamics/Bus/Bus.hpp b/GridKit/Model/PhasorDynamics/Bus/Bus.hpp index c7c78f6ee..229325710 100644 --- a/GridKit/Model/PhasorDynamics/Bus/Bus.hpp +++ b/GridKit/Model/PhasorDynamics/Bus/Bus.hpp @@ -117,7 +117,7 @@ namespace GridKit } } coo_jac_ = new CooMatrixT(num_rows, num_cols, nnz_); - coo_jac_->setDataPointers(J_rows_buffer_, J_cols_buffer_, J_vals_buffer_, LinearAlgebra::memory::HOST); + coo_jac_->setDataPointers(J_rows_buffer_, J_cols_buffer_, J_vals_buffer_, memory::HOST); } return 0; diff --git a/GridKit/Model/PhasorDynamics/Component.hpp b/GridKit/Model/PhasorDynamics/Component.hpp index 20dbd0403..8cfdd0c78 100644 --- a/GridKit/Model/PhasorDynamics/Component.hpp +++ b/GridKit/Model/PhasorDynamics/Component.hpp @@ -239,7 +239,7 @@ namespace GridKit } } coo_jac_ = new CooMatrixT(num_rows, num_cols, nnz_); - coo_jac_->setDataPointers(J_rows_buffer_, J_cols_buffer_, J_vals_buffer_, LinearAlgebra::memory::HOST); + coo_jac_->setDataPointers(J_rows_buffer_, J_cols_buffer_, J_vals_buffer_, memory::HOST); } return 0; diff --git a/examples/Enzyme/Standalone/EnzymeSparse.cpp b/examples/Enzyme/Standalone/EnzymeSparse.cpp index 4f1818c4e..8649d1ddf 100644 --- a/examples/Enzyme/Standalone/EnzymeSparse.cpp +++ b/examples/Enzyme/Standalone/EnzymeSparse.cpp @@ -6,8 +6,8 @@ #include #include -#include #include +#include #include /** @@ -123,14 +123,14 @@ __attribute__((noinline)) SparseMatrix* jac_f(size_t N, ScalarT* input) void check(SparseMatrix* matrix_1, SparseMatrix* matrix_2, int& fail) { size_t nnz_1 = matrix_1->getNnz(); - size_t* rows_1 = matrix_1->getRowData(GridKit::LinearAlgebra::memory::HOST); - size_t* cols_1 = matrix_1->getColData(GridKit::LinearAlgebra::memory::HOST); - double* vals_1 = matrix_1->getValues(GridKit::LinearAlgebra::memory::HOST); + size_t* rows_1 = matrix_1->getRowData(GridKit::memory::HOST); + size_t* cols_1 = matrix_1->getColData(GridKit::memory::HOST); + double* vals_1 = matrix_1->getValues(GridKit::memory::HOST); size_t nnz_2 = matrix_2->getNnz(); - size_t* rows_2 = matrix_2->getRowData(GridKit::LinearAlgebra::memory::HOST); - size_t* cols_2 = matrix_2->getColData(GridKit::LinearAlgebra::memory::HOST); - double* vals_2 = matrix_2->getValues(GridKit::LinearAlgebra::memory::HOST); + size_t* rows_2 = matrix_2->getRowData(GridKit::memory::HOST); + size_t* cols_2 = matrix_2->getColData(GridKit::memory::HOST); + double* vals_2 = matrix_2->getValues(GridKit::memory::HOST); if (nnz_1 != nnz_2) fail++; diff --git a/tests/UnitTests/LinearAlgebra/Vector/VectorTests.hpp b/tests/UnitTests/LinearAlgebra/Vector/VectorTests.hpp index 8cb36bcd7..afc4a7f30 100644 --- a/tests/UnitTests/LinearAlgebra/Vector/VectorTests.hpp +++ b/tests/UnitTests/LinearAlgebra/Vector/VectorTests.hpp @@ -6,8 +6,8 @@ #include #include -#include #include +#include #include namespace GridKit