diff --git a/Sofa/framework/Core/CMakeLists.txt b/Sofa/framework/Core/CMakeLists.txt index 54e7838a0c1..e328e392c67 100644 --- a/Sofa/framework/Core/CMakeLists.txt +++ b/Sofa/framework/Core/CMakeLists.txt @@ -145,6 +145,7 @@ set(HEADER_FILES ${SRC_ROOT}/collision/IntersectorFactory.h ${SRC_ROOT}/collision/NarrowPhaseDetection.h ${SRC_ROOT}/collision/Pipeline.h + ${SRC_ROOT}/data/Data[Eigen].h ${SRC_ROOT}/loader/BaseLoader.h ${SRC_ROOT}/loader/ImageLoader.h ${SRC_ROOT}/loader/MeshLoader.h @@ -343,6 +344,7 @@ set(SOURCE_FILES ${SRC_ROOT}/objectmodel/ScriptEvent.cpp ${SRC_ROOT}/objectmodel/Tag.cpp ${SRC_ROOT}/objectmodel/TagSet.cpp + ${SRC_ROOT}/data/Data[Eigen].cpp ${SRC_ROOT}/topology/BaseMeshTopology.cpp ${SRC_ROOT}/topology/BaseTopology.cpp ${SRC_ROOT}/topology/BaseTopologyObject.cpp diff --git a/Sofa/framework/Core/src/sofa/core/data/Data[Eigen].cpp b/Sofa/framework/Core/src/sofa/core/data/Data[Eigen].cpp new file mode 100644 index 00000000000..e07e5253e0c --- /dev/null +++ b/Sofa/framework/Core/src/sofa/core/data/Data[Eigen].cpp @@ -0,0 +1,243 @@ +/****************************************************************************** + * SOFA, Simulation Open-Framework Architecture * + * (c) 2006 INRIA, USTL, UJF, CNRS, MGH * + * * + * This program is free software; you can redistribute it and/or modify it * + * under the terms of the GNU Lesser General Public License as published by * + * the Free Software Foundation; either version 2.1 of the License, or (at * + * your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, but WITHOUT * + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License * + * for more details. * + * * + * You should have received a copy of the GNU Lesser General Public License * + * along with this program. If not, see . * + ******************************************************************************* + * Authors: The SOFA Team and external contributors (see Authors.txt) * + * * + * Contact information: contact@sofa-framework.org * + ******************************************************************************/ +#include + +#include + +namespace sofa::core::objectmodel +{ + +/// Shared IO implementation for Eigen matrices, partially specialized on the +/// whole Eigen::Matrix family (dynamic or fixed size, +/// any scalar type) so that a single implementation serves every matrix type. +template struct EigenMatrixDataIO; // only the Eigen::Matrix family is specialized + +template +struct EigenMatrixDataIO> +{ + using Matrix = Eigen::Matrix; + + /// Reads the dimensions, resizes dynamic matrices accordingly, then reads + /// the coefficients in row-major order. Sets failbit and returns false on + /// invalid dimensions (negative, or conflicting with a compile-time size) + /// or on parsing failure. + static bool read(std::istream& in, Matrix& matrix) + { + Eigen::Index rows = 0; + Eigen::Index cols = 0; + in >> rows >> cols; + + const bool dimsValid = !in.fail() + && rows >= 0 && cols >= 0 + && (Rows == Eigen::Dynamic || rows == Rows) + && (Cols == Eigen::Dynamic || cols == Cols); + if (!dimsValid) + { + in.setstate(std::ios::failbit); + return false; + } + + if constexpr (Rows == Eigen::Dynamic || Cols == Eigen::Dynamic) + { + matrix.resize(rows, cols); + } + + for (Eigen::Index row = 0; row < rows; ++row) + { + for (Eigen::Index col = 0; col < cols; ++col) + { + in >> matrix(row, col); + } + } + return !in.fail(); + } + + /// Prints the dimensions, then the coefficients in row-major order. + static void print(std::ostream& out, const Matrix& matrix) + { + out << matrix.rows() << ' ' << matrix.cols(); + for (Eigen::Index row = 0; row < matrix.rows(); ++row) + { + for (Eigen::Index col = 0; col < matrix.cols(); ++col) + { + out << ' ' << matrix(row, col); + } + } + } +}; + +/// The read() and getValueString() specializations are thin forwarders to the +/// shared EigenMatrixDataIO helper (see Data[Eigen].h). +template<> +bool SOFA_CORE_API Data::read( const std::string& str ) +{ + std::istringstream in( str ); + const bool ok = EigenMatrixDataIO::read( in, *beginEdit() ); + endEdit(); + return ok; +} + +template<> +std::string SOFA_CORE_API Data::getValueString() const +{ + std::ostringstream out; + EigenMatrixDataIO::print( out, getValue() ); + return out.str(); +} + +template<> +bool SOFA_CORE_API Data::read( const std::string& str ) +{ + std::istringstream in( str ); + const bool ok = EigenMatrixDataIO::read( in, *beginEdit() ); + endEdit(); + return ok; +} + +template<> +std::string SOFA_CORE_API Data::getValueString() const +{ + std::ostringstream out; + EigenMatrixDataIO::print( out, getValue() ); + return out.str(); +} + +template<> +bool SOFA_CORE_API Data::read( const std::string& str ) +{ + std::istringstream in( str ); + const bool ok = EigenMatrixDataIO::read( in, *beginEdit() ); + endEdit(); + return ok; +} + +template<> +std::string SOFA_CORE_API Data::getValueString() const +{ + std::ostringstream out; + EigenMatrixDataIO::print( out, getValue() ); + return out.str(); +} + +template<> +bool SOFA_CORE_API Data::read( const std::string& str ) +{ + std::istringstream in( str ); + const bool ok = EigenMatrixDataIO::read( in, *beginEdit() ); + endEdit(); + return ok; +} + +template<> +std::string SOFA_CORE_API Data::getValueString() const +{ + std::ostringstream out; + EigenMatrixDataIO::print( out, getValue() ); + return out.str(); +} + +template<> +bool SOFA_CORE_API Data::read( const std::string& str ) +{ + std::istringstream in( str ); + const bool ok = EigenMatrixDataIO::read( in, *beginEdit() ); + endEdit(); + return ok; +} + +template<> +std::string SOFA_CORE_API Data::getValueString() const +{ + std::ostringstream out; + EigenMatrixDataIO::print( out, getValue() ); + return out.str(); +} + +template<> +bool SOFA_CORE_API Data::read( const std::string& str ) +{ + std::istringstream in( str ); + const bool ok = EigenMatrixDataIO::read( in, *beginEdit() ); + endEdit(); + return ok; +} + +template<> +std::string SOFA_CORE_API Data::getValueString() const +{ + std::ostringstream out; + EigenMatrixDataIO::print( out, getValue() ); + return out.str(); +} + +template<> +bool SOFA_CORE_API Data::read( const std::string& str ) +{ + std::istringstream in( str ); + const bool ok = EigenMatrixDataIO::read( in, *beginEdit() ); + endEdit(); + return ok; +} + +template<> +std::string SOFA_CORE_API Data::getValueString() const +{ + std::ostringstream out; + EigenMatrixDataIO::print( out, getValue() ); + return out.str(); +} + +template<> +bool SOFA_CORE_API Data::read( const std::string& str ) +{ + std::istringstream in( str ); + const bool ok = EigenMatrixDataIO::read( in, *beginEdit() ); + endEdit(); + return ok; +} + +template<> +std::string SOFA_CORE_API Data::getValueString() const +{ + std::ostringstream out; + EigenMatrixDataIO::print( out, getValue() ); + return out.str(); +} + +template<> +bool SOFA_CORE_API Data::read( const std::string& str ) +{ + std::istringstream in( str ); + const bool ok = EigenMatrixDataIO::read( in, *beginEdit() ); + endEdit(); + return ok; +} + +template<> +std::string SOFA_CORE_API Data::getValueString() const +{ + std::ostringstream out; + EigenMatrixDataIO::print( out, getValue() ); + return out.str(); +} + +} // namespace sofa::core::objectmodel diff --git a/Sofa/framework/Core/src/sofa/core/data/Data[Eigen].h b/Sofa/framework/Core/src/sofa/core/data/Data[Eigen].h new file mode 100644 index 00000000000..8adf7c6dc48 --- /dev/null +++ b/Sofa/framework/Core/src/sofa/core/data/Data[Eigen].h @@ -0,0 +1,70 @@ +/****************************************************************************** +* SOFA, Simulation Open-Framework Architecture * +* (c) 2006 INRIA, USTL, UJF, CNRS, MGH * +* * +* This program is free software; you can redistribute it and/or modify it * +* under the terms of the GNU Lesser General Public License as published by * +* the Free Software Foundation; either version 2.1 of the License, or (at * +* your option) any later version. * +* * +* This program is distributed in the hope that it will be useful, but WITHOUT * +* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * +* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License * +* for more details. * +* * +* You should have received a copy of the GNU Lesser General Public License * +* along with this program. If not, see . * +******************************************************************************* +* Authors: The SOFA Team and external contributors (see Authors.txt) * +* * +* Contact information: contact@sofa-framework.org * +******************************************************************************/ +#pragma once + +#include +#include + +/// Support of Eigen matrices in sofa::core::objectmodel::Data. +/// +/// The string format is "rows cols c00 c01 ... c(rows-1)(cols-1)": the +/// dimensions, then the coefficients in row-major order, all separated by +/// whitespaces. Reading resizes dynamic matrices to the given dimensions, so +/// no pre-sized default value is needed; for fixed-size matrices the +/// dimensions must match the compile-time sizes, otherwise the read fails. +/// +/// Both directions are explicit specializations of Data::read() and +/// Data::getValueString(), declared below and defined in Data[Eigen].cpp. +/// No operator<< or operator>> is added to namespace +/// +/// Note: a matrix type that is not listed below cannot be stored in a Data: +/// the generic Data::read() would be instantiated and fail to compile, as +/// Eigen matrices have no stream operators. +namespace sofa::core::objectmodel +{ +template<> bool SOFA_CORE_API Data::read( const std::string& str ); +template<> std::string SOFA_CORE_API Data::getValueString() const; + +template<> bool SOFA_CORE_API Data::read( const std::string& str ); +template<> std::string SOFA_CORE_API Data::getValueString() const; + +template<> bool SOFA_CORE_API Data::read( const std::string& str ); +template<> std::string SOFA_CORE_API Data::getValueString() const; + +template<> bool SOFA_CORE_API Data::read( const std::string& str ); +template<> std::string SOFA_CORE_API Data::getValueString() const; + +template<> bool SOFA_CORE_API Data::read( const std::string& str ); +template<> std::string SOFA_CORE_API Data::getValueString() const; + +template<> bool SOFA_CORE_API Data::read( const std::string& str ); +template<> std::string SOFA_CORE_API Data::getValueString() const; + +template<> bool SOFA_CORE_API Data::read( const std::string& str ); +template<> std::string SOFA_CORE_API Data::getValueString() const; + +template<> bool SOFA_CORE_API Data::read( const std::string& str ); +template<> std::string SOFA_CORE_API Data::getValueString() const; + +template<> bool SOFA_CORE_API Data::read( const std::string& str ); +template<> std::string SOFA_CORE_API Data::getValueString() const; +} // namespace sofa::core::objectmodel diff --git a/Sofa/framework/Core/test/CMakeLists.txt b/Sofa/framework/Core/test/CMakeLists.txt index 45bc7d7f088..46c068d93e6 100644 --- a/Sofa/framework/Core/test/CMakeLists.txt +++ b/Sofa/framework/Core/test/CMakeLists.txt @@ -13,6 +13,7 @@ set(SOURCE_FILES objectmodel/BaseLink_test.cpp objectmodel/BaseObjectDescription_test.cpp objectmodel/Data_test.cpp + objectmodel/DataEigen_test.cpp objectmodel/DataLink_test.cpp objectmodel/DataFileName_test.cpp objectmodel/DataFileNameVector_test.cpp diff --git a/Sofa/framework/Core/test/objectmodel/DataEigen_test.cpp b/Sofa/framework/Core/test/objectmodel/DataEigen_test.cpp new file mode 100644 index 00000000000..3ba432a4164 --- /dev/null +++ b/Sofa/framework/Core/test/objectmodel/DataEigen_test.cpp @@ -0,0 +1,153 @@ +/****************************************************************************** +* SOFA, Simulation Open-Framework Architecture * +* (c) 2006 INRIA, USTL, UJF, CNRS, MGH * +* * +* This program is free software; you can redistribute it and/or modify it * +* under the terms of the GNU Lesser General Public License as published by * +* the Free Software Foundation; either version 2.1 of the License, or (at * +* your option) any later version. * +* * +* This program is distributed in the hope that it will be useful, but WITHOUT * +* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * +* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License * +* for more details. * +* * +* You should have received a copy of the GNU Lesser General Public License * +* along with this program. If not, see . * +******************************************************************************* +* Authors: The SOFA Team and external contributors (see Authors.txt) * +* * +* Contact information: contact@sofa-framework.org * +******************************************************************************/ +#include + +#include +#include +#include +#include +using sofa::testing::BaseTest ; + +namespace sofa { + +using namespace core::objectmodel; + +class DataEigen_test : public BaseTest +{ +public: + // No default value needed: the dimensions are read from the string and + // dynamic matrices are resized accordingly. + Data dataMatrixXf; + Data dataMatrixXd; + Data dataMatrixXi; + Data dataMatrix3d; +}; + +TEST_F(DataEigen_test, read_dynamicFloatMatrix) +{ + EXPECT_TRUE(dataMatrixXf.read("2 2 1 2 3 4")); + const Eigen::MatrixXf& m = dataMatrixXf.getValue(); + ASSERT_EQ(m.rows(), 2); + ASSERT_EQ(m.cols(), 2); + EXPECT_FLOAT_EQ(m(0, 0), 1.f); + EXPECT_FLOAT_EQ(m(0, 1), 2.f); + EXPECT_FLOAT_EQ(m(1, 0), 3.f); + EXPECT_FLOAT_EQ(m(1, 1), 4.f); +} + +TEST_F(DataEigen_test, read_dynamicDoubleMatrix) +{ + EXPECT_TRUE(dataMatrixXd.read("2 3 1.5 2.5 3.5 4.5 5.5 6.5")); + const Eigen::MatrixXd& m = dataMatrixXd.getValue(); + ASSERT_EQ(m.rows(), 2); + ASSERT_EQ(m.cols(), 3); + EXPECT_DOUBLE_EQ(m(0, 0), 1.5); + EXPECT_DOUBLE_EQ(m(0, 2), 3.5); + EXPECT_DOUBLE_EQ(m(1, 0), 4.5); + EXPECT_DOUBLE_EQ(m(1, 2), 6.5); +} + +TEST_F(DataEigen_test, read_integerMatrix) +{ + EXPECT_TRUE(dataMatrixXi.read("2 2 1 2 3 4")); + const Eigen::MatrixXi& m = dataMatrixXi.getValue(); + ASSERT_EQ(m.rows(), 2); + ASSERT_EQ(m.cols(), 2); + EXPECT_EQ(m(0, 0), 1); + EXPECT_EQ(m(0, 1), 2); + EXPECT_EQ(m(1, 0), 3); + EXPECT_EQ(m(1, 1), 4); +} + +TEST_F(DataEigen_test, read_fixedSizeMatrix) +{ + EXPECT_TRUE(dataMatrix3d.read("3 3 1 2 3 4 5 6 7 8 9")); + const Eigen::Matrix3d& m = dataMatrix3d.getValue(); + EXPECT_DOUBLE_EQ(m(0, 0), 1.0); + EXPECT_DOUBLE_EQ(m(1, 1), 5.0); + EXPECT_DOUBLE_EQ(m(2, 2), 9.0); +} + +TEST_F(DataEigen_test, read_resizesPreviousValue) +{ + Data data{Eigen::MatrixXf::Zero(3, 3)}; + EXPECT_TRUE(data.read("2 2 1 2 3 4")); + const Eigen::MatrixXf& m = data.getValue(); + ASSERT_EQ(m.rows(), 2); + ASSERT_EQ(m.cols(), 2); + EXPECT_FLOAT_EQ(m(1, 1), 4.f); +} + +TEST_F(DataEigen_test, read_fixedSizeDimensionMismatch_returnsFalse) +{ + EXPECT_FALSE(dataMatrix3d.read("2 2 1 2 3 4")); +} + +TEST_F(DataEigen_test, read_invalidString_returnsFalse) +{ + EXPECT_FALSE(dataMatrixXf.read("2 2 1 2 abc 4")); +} + +TEST_F(DataEigen_test, read_negativeDimensions_returnsFalse) +{ + EXPECT_FALSE(dataMatrixXf.read("-1 2 1 2")); +} + +TEST_F(DataEigen_test, getValueString_format) +{ + ASSERT_TRUE(dataMatrixXd.read("2 3 1.5 2.5 3.5 4.5 5.5 6.5")); + EXPECT_EQ(dataMatrixXd.getValueString(), "2 3 1.5 2.5 3.5 4.5 5.5 6.5"); +} + +TEST_F(DataEigen_test, getValueString_roundTrip) +{ + ASSERT_TRUE(dataMatrixXd.read("2 3 1.5 2.5 3.5 4.5 5.5 6.5")); + const std::string str = dataMatrixXd.getValueString(); + EXPECT_FALSE(str.empty()); + + Data copy; + ASSERT_TRUE(copy.read(str)); + ASSERT_EQ(copy.getValue().rows(), 2); + ASSERT_EQ(copy.getValue().cols(), 3); + EXPECT_TRUE(copy.getValue().isApprox(dataMatrixXd.getValue())); +} + +TEST_F(DataEigen_test, getValueString_fixedSizeMatrix) +{ + ASSERT_TRUE(dataMatrix3d.read("3 3 1 2 3 4 5 6 7 8 9")); + + Data copy; + ASSERT_TRUE(copy.read(dataMatrix3d.getValueString())); + EXPECT_TRUE(copy.getValue().isApprox(dataMatrix3d.getValue())); +} + +TEST_F(DataEigen_test, eigenStreamFormat_isUnchanged) +{ + // Data[Eigen].h must not add any operator<< to namespace Eigen: + // streaming a matrix directly keeps Eigen's own format. + ASSERT_TRUE(dataMatrix3d.read("3 3 1 2 3 4 5 6 7 8 9")); + std::ostringstream out; + out << dataMatrix3d.getValue(); + EXPECT_EQ(out.str(), "1 2 3\n4 5 6\n7 8 9"); +} + +}