From af16cf6e22bb3abf4a413b02b195872c815d9c03 Mon Sep 17 00:00:00 2001 From: Rayen Lee Date: Fri, 12 Dec 2025 13:26:19 +1300 Subject: [PATCH 01/16] Hardcoded installation of symengine and gmp --- src/CMakeLists.txt | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 466fb2930..d971b4e33 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -178,6 +178,18 @@ add_library(cellml ${API_HEADER_FILES} ) +target_include_directories(cellml + PUBLIC + "C:/symengine/include" + "C:/gmp/include" +) + +target_link_libraries(cellml + PUBLIC + "C:/symengine/lib/symengine.lib" + "C:/gmp/lib/gmp.lib" +) + set_source_files_properties(${CMAKE_CURRENT_SOURCE_DIR}/xmldoc.cpp PROPERTIES COMPILE_DEFINITIONS XML_ERROR_CALLBACK_ARGUMENT_TYPE=${CONST_ERROR_STRUCTURED_ERROR_CALLBACK_TYPE}) From 9e2b0b3be0d71e3b7c244bedc46b71a0810bef7e Mon Sep 17 00:00:00 2001 From: Rayen Lee Date: Fri, 12 Dec 2025 13:26:37 +1300 Subject: [PATCH 02/16] Created basic rearrangement test case --- tests/analyser/analyser.cpp | 14 +++++++++ .../unarranged_algebraic_equation.cellml | 29 +++++++++++++++++++ 2 files changed, 43 insertions(+) create mode 100644 tests/resources/analyser/unarranged_algebraic_equation.cellml diff --git a/tests/analyser/analyser.cpp b/tests/analyser/analyser.cpp index ecc9db714..add9e0f43 100644 --- a/tests/analyser/analyser.cpp +++ b/tests/analyser/analyser.cpp @@ -1060,3 +1060,17 @@ TEST(Analyser, unsuitablyConstrainedNlaSystem) EXPECT_EQ(libcellml::AnalyserModel::Type::UNSUITABLY_CONSTRAINED, analyser->analyserModel()->type()); } + +TEST(Analyser, rearrangeAlgebraicEquation) +{ + auto parser = libcellml::Parser::create(); + auto model = parser->parseModel(fileContents("analyser/unarranged_algebraic_equation.cellml")); + + EXPECT_EQ(size_t(0), parser->issueCount()); + + auto analyser = libcellml::Analyser::create(); + + analyser->analyseModel(model); + + EXPECT_EQ(libcellml::AnalyserModel::Type::ALGEBRAIC, analyser->analyserModel()->type()); +} diff --git a/tests/resources/analyser/unarranged_algebraic_equation.cellml b/tests/resources/analyser/unarranged_algebraic_equation.cellml new file mode 100644 index 000000000..5840ba82b --- /dev/null +++ b/tests/resources/analyser/unarranged_algebraic_equation.cellml @@ -0,0 +1,29 @@ + + + + + + + + + + x + 1 + + + + z + 3 + + + + x + + + y + z + + + + + From dafb56e8cb531de59d27d9ca378f750dec319c8c Mon Sep 17 00:00:00 2001 From: Rayen Lee Date: Fri, 12 Dec 2025 13:26:58 +1300 Subject: [PATCH 03/16] Basic implementation of equation rearrangement --- src/analyser.cpp | 130 +++++++++++++++++++++++++++++++++++++++++++++++ src/analyser_p.h | 12 +++++ 2 files changed, 142 insertions(+) diff --git a/src/analyser.cpp b/src/analyser.cpp index e79550d27..f7bf7f00c 100644 --- a/src/analyser.cpp +++ b/src/analyser.cpp @@ -22,6 +22,7 @@ limitations under the License. #include #include +#include #include "libcellml/analyserequation.h" #include "libcellml/analyserexternalvariable.h" @@ -198,6 +199,127 @@ bool AnalyserInternalEquation::variableOnLhsOrRhs(const AnalyserInternalVariable || variableOnRhs(variable); } +SymEngine::RCP AnalyserInternalEquation::symEngineRepresentation(AnalyserEquationAstPtr ast, const std::map> &symbolMap) +{ + if (ast == nullptr) { + return SymEngine::null; + } + + AnalyserEquationAstPtr leftAst = ast->leftChild(); + AnalyserEquationAstPtr rightAst = ast->rightChild(); + + // Recursively call getConvertedAst on left and right children. + SymEngine::RCP left = symEngineRepresentation(leftAst, symbolMap); + SymEngine::RCP right = symEngineRepresentation(rightAst, symbolMap); + + // Analyse mAst current type and value. + switch (ast->type()) { + case AnalyserEquationAst::Type::EQUALITY: + return Eq(left, right); + case AnalyserEquationAst::Type::PLUS: + return add(left, right); + case AnalyserEquationAst::Type::CI: + // Seems like the voi doesn't exist in mAllVariables, so we don't have an easy means of access. + // For now we'll just throw an error if the symbol is not found. + if (symbolMap.find(ast->variable()->name()) == symbolMap.end()) { + throw std::runtime_error("Unsupported variable in symEngineRepresentation"); + } + return symbolMap.at(ast->variable()->name()); + default: + // Our parser is unable to handle this type, so we need to let the caller know by throwing an error. + throw std::runtime_error("Unsupported AST type in symEngineRepresentation"); + } +} + +AnalyserEquationAstPtr AnalyserInternalEquation::parseSymEngineExpression(SymEngine::RCP &seExpression, + std::map, AnalyserInternalVariablePtr, SymEngine::RCPBasicKeyLess> &astMap) +{ + auto children = seExpression->get_args(); + + AnalyserEquationAstPtr ast = AnalyserEquationAst::create(); + + switch (seExpression->get_type_code()) { + case SymEngine::SYMENGINE_EQUALITY: { + ast->setType(AnalyserEquationAst::Type::EQUALITY); + break; + } + case SymEngine::SYMENGINE_ADD: { + ast->setType(AnalyserEquationAst::Type::PLUS); + break; + } + case SymEngine::SYMENGINE_MUL: { + ast->setType(AnalyserEquationAst::Type::TIMES); + break; + } + case SymEngine::SYMENGINE_SYMBOL: { + SymEngine::RCP symbolExpr = SymEngine::rcp_dynamic_cast(seExpression); + ast->setType(AnalyserEquationAst::Type::CI); + ast->setVariable(astMap.at(symbolExpr)->mVariable); + break; + } + default: + break; + } + + // Assume two children max. + // This is likely wrong since SYMENGINE_ADD could have x + y + z (and thus 3 children), + // but it's sufficient for this very early implementation. + if (children.size() > 0) { + ast->setLeftChild(parseSymEngineExpression(children[0], astMap)); + if (children.size() > 1) { + ast->setRightChild(parseSymEngineExpression(children[1], astMap)); + } + } + + return ast; +} + +AnalyserEquationAstPtr AnalyserInternalEquation::rearrangeFor(const AnalyserInternalVariablePtr &variable) +{ + std::map> symbolMap; + std::map, AnalyserInternalVariablePtr, SymEngine::RCPBasicKeyLess> astMap; + + for (const auto &variable : mAllVariables) { + SymEngine::RCP symbol = SymEngine::symbol(variable->mVariable->name()); + symbolMap[variable->mVariable->name()] = symbol; + astMap[symbol] = variable; + } + + SymEngine::RCP equation; + try { + equation = symEngineRepresentation(mAst, symbolMap); + } catch (const std::runtime_error &e) { + // Our parser was unable to convert the AST to a SymEngine expression. + return nullptr; + } + + SymEngine::RCP solutionSet = solve(equation, symbolMap[variable->mVariable->name()]); + SymEngine::vec_basic solutions = solutionSet->get_args(); + + // Our system needs to be able to isolate a single solution. + if (solutions.size() != 1) { + return nullptr; + } + SymEngine::RCP answer = solutions.front(); + + // Rebuild the AST from the rearranged expression. + AnalyserEquationAstPtr ast = AnalyserEquationAst::create(); + AnalyserEquationAstPtr isolatedVariableAst = AnalyserEquationAst::create(); + AnalyserEquationAstPtr rearrangedEquationAst = parseSymEngineExpression(answer, astMap); + + ast->setType(AnalyserEquationAst::Type::EQUALITY); + ast->setLeftChild(isolatedVariableAst); + ast->setRightChild(rearrangedEquationAst); + + isolatedVariableAst->setType(AnalyserEquationAst::Type::CI); + isolatedVariableAst->setVariable(variable->mVariable); + isolatedVariableAst->setParent(ast); + + rearrangedEquationAst->setParent(ast); + + return ast; +} + bool AnalyserInternalEquation::check(const AnalyserModelPtr &analyserModel, bool checkNlaSystems) { // Nothing to check if the equation has a known type. @@ -278,6 +400,14 @@ bool AnalyserInternalEquation::check(const AnalyserModelPtr &analyserModel, bool mVariables.front() : nullptr; + // If we have one variable left, but it's not isolated, try to rearrange it. + if ((unknownVariableLeft != nullptr) && !variableOnLhsOrRhs(unknownVariableLeft)) { + auto newAst = rearrangeFor(unknownVariableLeft); + if (newAst != nullptr) { + mAst = newAst; + } + } + if (((unknownVariableLeft != nullptr) && (checkNlaSystems || variableOnLhsOrRhs(unknownVariableLeft))) || !initialisedVariables.empty()) { diff --git a/src/analyser_p.h b/src/analyser_p.h index f888861de..b27818673 100644 --- a/src/analyser_p.h +++ b/src/analyser_p.h @@ -14,6 +14,8 @@ See the License for the specific language governing permissions and limitations under the License. */ +#include + #include "libcellml/generatorprofile.h" #include "libcellml/issue.h" @@ -22,6 +24,12 @@ limitations under the License. #include "logger_p.h" #include "utilities.h" +namespace SymEngine { +template class RCP; +class Basic; +class Symbol; +} // namespace SymEngine + namespace libcellml { struct AnalyserInternalEquation; @@ -128,6 +136,10 @@ struct AnalyserInternalEquation bool variableOnRhs(const AnalyserInternalVariablePtr &variable); bool variableOnLhsOrRhs(const AnalyserInternalVariablePtr &variable); + SymEngine::RCP symEngineRepresentation(AnalyserEquationAstPtr ast, const std::map> &symbolMap); + AnalyserEquationAstPtr parseSymEngineExpression(SymEngine::RCP &seExpression, std::map, AnalyserInternalVariablePtr, SymEngine::RCPBasicKeyLess> &astMap); + AnalyserEquationAstPtr rearrangeFor(const AnalyserInternalVariablePtr &variable); + bool check(const AnalyserModelPtr &analyserModel, bool checkNlaSystems); }; From 4331798338bb36492f6d3fc09d62304ce6af36c5 Mon Sep 17 00:00:00 2001 From: Rayen Lee Date: Fri, 12 Dec 2025 08:57:53 +1300 Subject: [PATCH 04/16] Change symEngineRepresentation to symEngineEquation --- src/analyser.cpp | 8 ++++---- src/analyser_p.h | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/analyser.cpp b/src/analyser.cpp index f7bf7f00c..6d840e75c 100644 --- a/src/analyser.cpp +++ b/src/analyser.cpp @@ -199,7 +199,7 @@ bool AnalyserInternalEquation::variableOnLhsOrRhs(const AnalyserInternalVariable || variableOnRhs(variable); } -SymEngine::RCP AnalyserInternalEquation::symEngineRepresentation(AnalyserEquationAstPtr ast, const std::map> &symbolMap) +SymEngine::RCP AnalyserInternalEquation::symEngineEquation(AnalyserEquationAstPtr ast, const std::map> &symbolMap) { if (ast == nullptr) { return SymEngine::null; @@ -209,8 +209,8 @@ SymEngine::RCP AnalyserInternalEquation::symEngineRepres AnalyserEquationAstPtr rightAst = ast->rightChild(); // Recursively call getConvertedAst on left and right children. - SymEngine::RCP left = symEngineRepresentation(leftAst, symbolMap); - SymEngine::RCP right = symEngineRepresentation(rightAst, symbolMap); + SymEngine::RCP left = symEngineEquation(leftAst, symbolMap); + SymEngine::RCP right = symEngineEquation(rightAst, symbolMap); // Analyse mAst current type and value. switch (ast->type()) { @@ -287,7 +287,7 @@ AnalyserEquationAstPtr AnalyserInternalEquation::rearrangeFor(const AnalyserInte SymEngine::RCP equation; try { - equation = symEngineRepresentation(mAst, symbolMap); + equation = symEngineEquation(mAst, symbolMap); } catch (const std::runtime_error &e) { // Our parser was unable to convert the AST to a SymEngine expression. return nullptr; diff --git a/src/analyser_p.h b/src/analyser_p.h index b27818673..554ea132c 100644 --- a/src/analyser_p.h +++ b/src/analyser_p.h @@ -136,7 +136,7 @@ struct AnalyserInternalEquation bool variableOnRhs(const AnalyserInternalVariablePtr &variable); bool variableOnLhsOrRhs(const AnalyserInternalVariablePtr &variable); - SymEngine::RCP symEngineRepresentation(AnalyserEquationAstPtr ast, const std::map> &symbolMap); + SymEngine::RCP symEngineEquation(AnalyserEquationAstPtr ast, const std::map> &symbolMap); AnalyserEquationAstPtr parseSymEngineExpression(SymEngine::RCP &seExpression, std::map, AnalyserInternalVariablePtr, SymEngine::RCPBasicKeyLess> &astMap); AnalyserEquationAstPtr rearrangeFor(const AnalyserInternalVariablePtr &variable); From 3953282f5498fb507d76eb3e66b08494ab5f5b5c Mon Sep 17 00:00:00 2001 From: Rayen Lee Date: Fri, 12 Dec 2025 09:10:53 +1300 Subject: [PATCH 05/16] Added TODO comments --- src/analyser.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/analyser.cpp b/src/analyser.cpp index 6d840e75c..cf5af0d91 100644 --- a/src/analyser.cpp +++ b/src/analyser.cpp @@ -261,9 +261,7 @@ AnalyserEquationAstPtr AnalyserInternalEquation::parseSymEngineExpression(SymEng break; } - // Assume two children max. - // This is likely wrong since SYMENGINE_ADD could have x + y + z (and thus 3 children), - // but it's sufficient for this very early implementation. + // TODO Update to account for symengine expressions with 3 or more children. if (children.size() > 0) { ast->setLeftChild(parseSymEngineExpression(children[0], astMap)); if (children.size() > 1) { @@ -404,6 +402,7 @@ bool AnalyserInternalEquation::check(const AnalyserModelPtr &analyserModel, bool if ((unknownVariableLeft != nullptr) && !variableOnLhsOrRhs(unknownVariableLeft)) { auto newAst = rearrangeFor(unknownVariableLeft); if (newAst != nullptr) { + // TODO Update variables and/or equation type when necessary. mAst = newAst; } } From 2de909875418fdb886a749c0436371b43d86e99d Mon Sep 17 00:00:00 2001 From: Rayen Lee Date: Fri, 12 Dec 2025 09:24:01 +1300 Subject: [PATCH 06/16] Renamed equation to seEquation --- src/analyser.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/analyser.cpp b/src/analyser.cpp index cf5af0d91..6ed947527 100644 --- a/src/analyser.cpp +++ b/src/analyser.cpp @@ -283,15 +283,15 @@ AnalyserEquationAstPtr AnalyserInternalEquation::rearrangeFor(const AnalyserInte astMap[symbol] = variable; } - SymEngine::RCP equation; + SymEngine::RCP seEquation; try { - equation = symEngineEquation(mAst, symbolMap); + seEquation = symEngineEquation(mAst, symbolMap); } catch (const std::runtime_error &e) { // Our parser was unable to convert the AST to a SymEngine expression. return nullptr; } - SymEngine::RCP solutionSet = solve(equation, symbolMap[variable->mVariable->name()]); + SymEngine::RCP solutionSet = solve(seEquation, symbolMap[variable->mVariable->name()]); SymEngine::vec_basic solutions = solutionSet->get_args(); // Our system needs to be able to isolate a single solution. From 123867384846e64fcae8903258855bedf0166c6c Mon Sep 17 00:00:00 2001 From: Rayen Lee Date: Fri, 12 Dec 2025 09:48:54 +1300 Subject: [PATCH 07/16] Defined type aliases for symengine maps Also changed the name of astMap to variableMap to better reflect what it's actually mapping to --- src/analyser.cpp | 18 +++++++++--------- src/analyser_p.h | 7 +++++-- 2 files changed, 14 insertions(+), 11 deletions(-) diff --git a/src/analyser.cpp b/src/analyser.cpp index 6ed947527..c2a30387e 100644 --- a/src/analyser.cpp +++ b/src/analyser.cpp @@ -199,7 +199,7 @@ bool AnalyserInternalEquation::variableOnLhsOrRhs(const AnalyserInternalVariable || variableOnRhs(variable); } -SymEngine::RCP AnalyserInternalEquation::symEngineEquation(AnalyserEquationAstPtr ast, const std::map> &symbolMap) +SymEngine::RCP AnalyserInternalEquation::symEngineEquation(AnalyserEquationAstPtr ast, const SymEngineSymbolMap &symbolMap) { if (ast == nullptr) { return SymEngine::null; @@ -232,7 +232,7 @@ SymEngine::RCP AnalyserInternalEquation::symEngineEquati } AnalyserEquationAstPtr AnalyserInternalEquation::parseSymEngineExpression(SymEngine::RCP &seExpression, - std::map, AnalyserInternalVariablePtr, SymEngine::RCPBasicKeyLess> &astMap) + SymEngineVariableMap &variableMap) { auto children = seExpression->get_args(); @@ -254,7 +254,7 @@ AnalyserEquationAstPtr AnalyserInternalEquation::parseSymEngineExpression(SymEng case SymEngine::SYMENGINE_SYMBOL: { SymEngine::RCP symbolExpr = SymEngine::rcp_dynamic_cast(seExpression); ast->setType(AnalyserEquationAst::Type::CI); - ast->setVariable(astMap.at(symbolExpr)->mVariable); + ast->setVariable(variableMap.at(symbolExpr)->mVariable); break; } default: @@ -263,9 +263,9 @@ AnalyserEquationAstPtr AnalyserInternalEquation::parseSymEngineExpression(SymEng // TODO Update to account for symengine expressions with 3 or more children. if (children.size() > 0) { - ast->setLeftChild(parseSymEngineExpression(children[0], astMap)); + ast->setLeftChild(parseSymEngineExpression(children[0], variableMap)); if (children.size() > 1) { - ast->setRightChild(parseSymEngineExpression(children[1], astMap)); + ast->setRightChild(parseSymEngineExpression(children[1], variableMap)); } } @@ -274,13 +274,13 @@ AnalyserEquationAstPtr AnalyserInternalEquation::parseSymEngineExpression(SymEng AnalyserEquationAstPtr AnalyserInternalEquation::rearrangeFor(const AnalyserInternalVariablePtr &variable) { - std::map> symbolMap; - std::map, AnalyserInternalVariablePtr, SymEngine::RCPBasicKeyLess> astMap; + SymEngineSymbolMap symbolMap; + SymEngineVariableMap variableMap; for (const auto &variable : mAllVariables) { SymEngine::RCP symbol = SymEngine::symbol(variable->mVariable->name()); symbolMap[variable->mVariable->name()] = symbol; - astMap[symbol] = variable; + variableMap[symbol] = variable; } SymEngine::RCP seEquation; @@ -303,7 +303,7 @@ AnalyserEquationAstPtr AnalyserInternalEquation::rearrangeFor(const AnalyserInte // Rebuild the AST from the rearranged expression. AnalyserEquationAstPtr ast = AnalyserEquationAst::create(); AnalyserEquationAstPtr isolatedVariableAst = AnalyserEquationAst::create(); - AnalyserEquationAstPtr rearrangedEquationAst = parseSymEngineExpression(answer, astMap); + AnalyserEquationAstPtr rearrangedEquationAst = parseSymEngineExpression(answer, variableMap); ast->setType(AnalyserEquationAst::Type::EQUALITY); ast->setLeftChild(isolatedVariableAst); diff --git a/src/analyser_p.h b/src/analyser_p.h index 554ea132c..537a8923a 100644 --- a/src/analyser_p.h +++ b/src/analyser_p.h @@ -47,6 +47,9 @@ using AnalyserEquationPtrs = std::vector; using AnalyserVariablePtrs = std::vector; using AnalyserExternalVariablePtrs = std::vector; +using SymEngineVariableMap = std::map, AnalyserInternalVariablePtr, SymEngine::RCPBasicKeyLess>; +using SymEngineSymbolMap = std::map>; + struct AnalyserInternalVariable { enum struct Type @@ -136,8 +139,8 @@ struct AnalyserInternalEquation bool variableOnRhs(const AnalyserInternalVariablePtr &variable); bool variableOnLhsOrRhs(const AnalyserInternalVariablePtr &variable); - SymEngine::RCP symEngineEquation(AnalyserEquationAstPtr ast, const std::map> &symbolMap); - AnalyserEquationAstPtr parseSymEngineExpression(SymEngine::RCP &seExpression, std::map, AnalyserInternalVariablePtr, SymEngine::RCPBasicKeyLess> &astMap); + SymEngine::RCP symEngineEquation(AnalyserEquationAstPtr ast, const SymEngineSymbolMap &symbolMap); + AnalyserEquationAstPtr parseSymEngineExpression(SymEngine::RCP &seExpression, SymEngineVariableMap &variableMap); AnalyserEquationAstPtr rearrangeFor(const AnalyserInternalVariablePtr &variable); bool check(const AnalyserModelPtr &analyserModel, bool checkNlaSystems); From 539dbf76e1decdd7bce1588e05c3b6667482a33e Mon Sep 17 00:00:00 2001 From: Rayen Lee Date: Fri, 12 Dec 2025 09:52:09 +1300 Subject: [PATCH 08/16] Updated function parameters to use const and pass by reference --- src/analyser.cpp | 6 +++--- src/analyser_p.h | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/analyser.cpp b/src/analyser.cpp index c2a30387e..2bf2de902 100644 --- a/src/analyser.cpp +++ b/src/analyser.cpp @@ -199,7 +199,7 @@ bool AnalyserInternalEquation::variableOnLhsOrRhs(const AnalyserInternalVariable || variableOnRhs(variable); } -SymEngine::RCP AnalyserInternalEquation::symEngineEquation(AnalyserEquationAstPtr ast, const SymEngineSymbolMap &symbolMap) +SymEngine::RCP AnalyserInternalEquation::symEngineEquation(const AnalyserEquationAstPtr &ast, const SymEngineSymbolMap &symbolMap) { if (ast == nullptr) { return SymEngine::null; @@ -231,8 +231,8 @@ SymEngine::RCP AnalyserInternalEquation::symEngineEquati } } -AnalyserEquationAstPtr AnalyserInternalEquation::parseSymEngineExpression(SymEngine::RCP &seExpression, - SymEngineVariableMap &variableMap) +AnalyserEquationAstPtr AnalyserInternalEquation::parseSymEngineExpression(const SymEngine::RCP &seExpression, + const SymEngineVariableMap &variableMap) { auto children = seExpression->get_args(); diff --git a/src/analyser_p.h b/src/analyser_p.h index 537a8923a..2bb7f5eb9 100644 --- a/src/analyser_p.h +++ b/src/analyser_p.h @@ -139,8 +139,8 @@ struct AnalyserInternalEquation bool variableOnRhs(const AnalyserInternalVariablePtr &variable); bool variableOnLhsOrRhs(const AnalyserInternalVariablePtr &variable); - SymEngine::RCP symEngineEquation(AnalyserEquationAstPtr ast, const SymEngineSymbolMap &symbolMap); - AnalyserEquationAstPtr parseSymEngineExpression(SymEngine::RCP &seExpression, SymEngineVariableMap &variableMap); + SymEngine::RCP symEngineEquation(const AnalyserEquationAstPtr &ast, const SymEngineSymbolMap &symbolMap); + AnalyserEquationAstPtr parseSymEngineExpression(const SymEngine::RCP &seExpression, const SymEngineVariableMap &variableMap); AnalyserEquationAstPtr rearrangeFor(const AnalyserInternalVariablePtr &variable); bool check(const AnalyserModelPtr &analyserModel, bool checkNlaSystems); From f2cab14ebd467fb137c5ed4d891f446f5b3312d5 Mon Sep 17 00:00:00 2001 From: Rayen Lee Date: Fri, 12 Dec 2025 11:50:18 +1300 Subject: [PATCH 09/16] Added support for parsing SymEngine integers to AST --- src/analyser.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/analyser.cpp b/src/analyser.cpp index 2bf2de902..8267f994c 100644 --- a/src/analyser.cpp +++ b/src/analyser.cpp @@ -257,6 +257,11 @@ AnalyserEquationAstPtr AnalyserInternalEquation::parseSymEngineExpression(const ast->setVariable(variableMap.at(symbolExpr)->mVariable); break; } + case SymEngine::SYMENGINE_INTEGER: { + ast->setType(AnalyserEquationAst::Type::CN); + ast->setValue(seExpression->__str__()); + break; + } default: break; } From bdb3bd112fdefb156efe8db7764a76b9e216e59c Mon Sep 17 00:00:00 2001 From: Rayen Lee Date: Fri, 12 Dec 2025 12:06:29 +1300 Subject: [PATCH 10/16] Assign parenthood to recreated ast tree --- src/analyser.cpp | 9 ++++++--- src/analyser_p.h | 2 +- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/src/analyser.cpp b/src/analyser.cpp index 8267f994c..33788a527 100644 --- a/src/analyser.cpp +++ b/src/analyser.cpp @@ -232,12 +232,15 @@ SymEngine::RCP AnalyserInternalEquation::symEngineEquati } AnalyserEquationAstPtr AnalyserInternalEquation::parseSymEngineExpression(const SymEngine::RCP &seExpression, + const AnalyserEquationAstPtr &parentAst, const SymEngineVariableMap &variableMap) { auto children = seExpression->get_args(); AnalyserEquationAstPtr ast = AnalyserEquationAst::create(); + ast->setParent(parentAst); + switch (seExpression->get_type_code()) { case SymEngine::SYMENGINE_EQUALITY: { ast->setType(AnalyserEquationAst::Type::EQUALITY); @@ -268,9 +271,9 @@ AnalyserEquationAstPtr AnalyserInternalEquation::parseSymEngineExpression(const // TODO Update to account for symengine expressions with 3 or more children. if (children.size() > 0) { - ast->setLeftChild(parseSymEngineExpression(children[0], variableMap)); + ast->setLeftChild(parseSymEngineExpression(children[0], ast, variableMap)); if (children.size() > 1) { - ast->setRightChild(parseSymEngineExpression(children[1], variableMap)); + ast->setRightChild(parseSymEngineExpression(children[1], ast, variableMap)); } } @@ -308,7 +311,7 @@ AnalyserEquationAstPtr AnalyserInternalEquation::rearrangeFor(const AnalyserInte // Rebuild the AST from the rearranged expression. AnalyserEquationAstPtr ast = AnalyserEquationAst::create(); AnalyserEquationAstPtr isolatedVariableAst = AnalyserEquationAst::create(); - AnalyserEquationAstPtr rearrangedEquationAst = parseSymEngineExpression(answer, variableMap); + AnalyserEquationAstPtr rearrangedEquationAst = parseSymEngineExpression(answer, nullptr, variableMap); ast->setType(AnalyserEquationAst::Type::EQUALITY); ast->setLeftChild(isolatedVariableAst); diff --git a/src/analyser_p.h b/src/analyser_p.h index 2bb7f5eb9..f5aec6745 100644 --- a/src/analyser_p.h +++ b/src/analyser_p.h @@ -140,7 +140,7 @@ struct AnalyserInternalEquation bool variableOnLhsOrRhs(const AnalyserInternalVariablePtr &variable); SymEngine::RCP symEngineEquation(const AnalyserEquationAstPtr &ast, const SymEngineSymbolMap &symbolMap); - AnalyserEquationAstPtr parseSymEngineExpression(const SymEngine::RCP &seExpression, const SymEngineVariableMap &variableMap); + AnalyserEquationAstPtr parseSymEngineExpression(const SymEngine::RCP &seExpression, const AnalyserEquationAstPtr &parentAst, const SymEngineVariableMap &variableMap); AnalyserEquationAstPtr rearrangeFor(const AnalyserInternalVariablePtr &variable); bool check(const AnalyserModelPtr &analyserModel, bool checkNlaSystems); From 3b2c5cc112d0a1d654c4103ac1b76b45ee472e19 Mon Sep 17 00:00:00 2001 From: Rayen Lee Date: Fri, 12 Dec 2025 12:31:06 +1300 Subject: [PATCH 11/16] Removed DAE test case and associated files --- tests/generator/generator.cpp | 24 --- .../generator/dae_cellml_1_1_model/model.c | 190 ------------------ .../dae_cellml_1_1_model/model.cellml | 127 ------------ .../generator/dae_cellml_1_1_model/model.h | 37 ---- .../generator/dae_cellml_1_1_model/model.py | 138 ------------- 5 files changed, 516 deletions(-) delete mode 100644 tests/resources/generator/dae_cellml_1_1_model/model.c delete mode 100644 tests/resources/generator/dae_cellml_1_1_model/model.cellml delete mode 100644 tests/resources/generator/dae_cellml_1_1_model/model.h delete mode 100644 tests/resources/generator/dae_cellml_1_1_model/model.py diff --git a/tests/generator/generator.cpp b/tests/generator/generator.cpp index 411080c52..586a1bed7 100644 --- a/tests/generator/generator.cpp +++ b/tests/generator/generator.cpp @@ -1602,30 +1602,6 @@ TEST(Generator, analyserModelScopeTest) EXPECT_EQ_FILE_CONTENTS("generator/hodgkin_huxley_squid_axon_model_1952/model.c", generator->implementationCode(analyserModel)); } -TEST(Generator, daeModel) -{ - auto parser = libcellml::Parser::create(false); - auto model = parser->parseModel(fileContents("generator/dae_cellml_1_1_model/model.cellml")); - - EXPECT_EQ(size_t(0), parser->errorCount()); - - auto analyser = libcellml::Analyser::create(); - - analyser->analyseModel(model); - - EXPECT_EQ(size_t(0), analyser->errorCount()); - - auto analyserModel = analyser->analyserModel(); - auto generator = libcellml::Generator::create(); - - EXPECT_EQ_FILE_CONTENTS("generator/dae_cellml_1_1_model/model.h", generator->interfaceCode(analyserModel)); - EXPECT_EQ_FILE_CONTENTS("generator/dae_cellml_1_1_model/model.c", generator->implementationCode(analyserModel)); - - auto profile = libcellml::GeneratorProfile::create(libcellml::GeneratorProfile::Profile::PYTHON); - - EXPECT_EQ_FILE_CONTENTS("generator/dae_cellml_1_1_model/model.py", generator->implementationCode(analyserModel, profile)); -} - TEST(Generator, variableInitialisedUsingAnotherVariable) { // Note: this should be in sync with the corresponding Analyser test. diff --git a/tests/resources/generator/dae_cellml_1_1_model/model.c b/tests/resources/generator/dae_cellml_1_1_model/model.c deleted file mode 100644 index a5ebbb5eb..000000000 --- a/tests/resources/generator/dae_cellml_1_1_model/model.c +++ /dev/null @@ -1,190 +0,0 @@ -/* The content of this file was generated using the C profile of libCellML 0.6.3. */ - -#include "model.h" - -#include -#include - -const char VERSION[] = "0.7.0"; -const char LIBCELLML_VERSION[] = "0.6.3"; - -const size_t STATE_COUNT = 2; -const size_t CONSTANT_COUNT = 5; -const size_t COMPUTED_CONSTANT_COUNT = 0; -const size_t ALGEBRAIC_VARIABLE_COUNT = 5; - -const VariableInfo VOI_INFO = {"t", "second", "main"}; - -const VariableInfo STATE_INFO[] = { - {"q_1", "coulomb", "main"}, - {"v_3", "C_per_s", "main"} -}; - -const VariableInfo CONSTANT_INFO[] = { - {"v_in", "C_per_s", "main"}, - {"v_out", "C_per_s", "main"}, - {"C", "C2_per_J", "main"}, - {"R", "Js_per_C2", "main"}, - {"L", "Js2_per_C2", "main"} -}; - -const VariableInfo COMPUTED_CONSTANT_INFO[] = { -}; - -const VariableInfo ALGEBRAIC_INFO[] = { - {"v_1", "C_per_s", "main"}, - {"v_2", "C_per_s", "main"}, - {"u_3", "J_per_C", "main"}, - {"u_2", "J_per_C", "main"}, - {"u_1", "J_per_C", "main"} -}; - -double * createStatesArray() -{ - double *res = (double *) malloc(STATE_COUNT*sizeof(double)); - - for (size_t i = 0; i < STATE_COUNT; ++i) { - res[i] = NAN; - } - - return res; -} - -double * createConstantsArray() -{ - double *res = (double *) malloc(CONSTANT_COUNT*sizeof(double)); - - for (size_t i = 0; i < CONSTANT_COUNT; ++i) { - res[i] = NAN; - } - - return res; -} - -double * createComputedConstantsArray() -{ - double *res = (double *) malloc(COMPUTED_CONSTANT_COUNT*sizeof(double)); - - for (size_t i = 0; i < COMPUTED_CONSTANT_COUNT; ++i) { - res[i] = NAN; - } - - return res; -} - -double * createAlgebraicVariablesArray() -{ - double *res = (double *) malloc(ALGEBRAIC_VARIABLE_COUNT*sizeof(double)); - - for (size_t i = 0; i < ALGEBRAIC_VARIABLE_COUNT; ++i) { - res[i] = NAN; - } - - return res; -} - -void deleteArray(double *array) -{ - free(array); -} - -typedef struct { - double voi; - double *states; - double *rates; - double *constants; - double *computedConstants; - double *algebraicVariables; -} RootFindingInfo; - -extern void nlaSolve(void (*objectiveFunction)(double *, double *, void *), - double *u, size_t n, void *data); - -void objectiveFunction0(double *u, double *f, void *data) -{ - double voi = ((RootFindingInfo *) data)->voi; - double *states = ((RootFindingInfo *) data)->states; - double *rates = ((RootFindingInfo *) data)->rates; - double *constants = ((RootFindingInfo *) data)->constants; - double *computedConstants = ((RootFindingInfo *) data)->computedConstants; - double *algebraicVariables = ((RootFindingInfo *) data)->algebraicVariables; - - algebraicVariables[0] = u[0]; - - f[0] = constants[0]-(algebraicVariables[0]+algebraicVariables[1]); -} - -void findRoot0(double voi, double *states, double *rates, double *constants, double *computedConstants, double *algebraicVariables) -{ - RootFindingInfo rfi = { voi, states, rates, constants, computedConstants, algebraicVariables }; - double u[1]; - - u[0] = algebraicVariables[0]; - - nlaSolve(objectiveFunction0, u, 1, &rfi); - - algebraicVariables[0] = u[0]; -} - -void objectiveFunction1(double *u, double *f, void *data) -{ - double voi = ((RootFindingInfo *) data)->voi; - double *states = ((RootFindingInfo *) data)->states; - double *rates = ((RootFindingInfo *) data)->rates; - double *constants = ((RootFindingInfo *) data)->constants; - double *computedConstants = ((RootFindingInfo *) data)->computedConstants; - double *algebraicVariables = ((RootFindingInfo *) data)->algebraicVariables; - - algebraicVariables[2] = u[0]; - - f[0] = algebraicVariables[4]-(algebraicVariables[3]+algebraicVariables[2]); -} - -void findRoot1(double voi, double *states, double *rates, double *constants, double *computedConstants, double *algebraicVariables) -{ - RootFindingInfo rfi = { voi, states, rates, constants, computedConstants, algebraicVariables }; - double u[1]; - - u[0] = algebraicVariables[2]; - - nlaSolve(objectiveFunction1, u, 1, &rfi); - - algebraicVariables[2] = u[0]; -} - -void initialiseArrays(double *states, double *rates, double *constants, double *computedConstants, double *algebraicVariables) -{ - states[0] = 1.0; - states[1] = 0.0; - constants[0] = 1.0; - constants[1] = 1.0; - constants[2] = 20.0; - constants[3] = 2.0; - constants[4] = 10.0; - algebraicVariables[0] = 0.0; - algebraicVariables[2] = 0.0; -} - -void computeComputedConstants(double *states, double *rates, double *constants, double *computedConstants, double *algebraic) -{ -} - -void computeRates(double voi, double *states, double *rates, double *constants, double *computedConstants, double *algebraicVariables) -{ - algebraicVariables[1] = states[1]+constants[1]; - findRoot0(voi, states, rates, constants, computedConstants, algebraicVariables); - rates[0] = algebraicVariables[0]; - algebraicVariables[3] = constants[3]*algebraicVariables[1]; - algebraicVariables[4] = states[0]/constants[2]; - findRoot1(voi, states, rates, constants, computedConstants, algebraicVariables); - rates[1] = algebraicVariables[2]/constants[4]; -} - -void computeVariables(double voi, double *states, double *rates, double *constants, double *computedConstants, double *algebraicVariables) -{ - algebraicVariables[1] = states[1]+constants[1]; - findRoot0(voi, states, rates, constants, computedConstants, algebraicVariables); - algebraicVariables[3] = constants[3]*algebraicVariables[1]; - algebraicVariables[4] = states[0]/constants[2]; - findRoot1(voi, states, rates, constants, computedConstants, algebraicVariables); -} diff --git a/tests/resources/generator/dae_cellml_1_1_model/model.cellml b/tests/resources/generator/dae_cellml_1_1_model/model.cellml deleted file mode 100644 index 3cf399427..000000000 --- a/tests/resources/generator/dae_cellml_1_1_model/model.cellml +++ /dev/null @@ -1,127 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - t - - q_1 - - v_1 - - - - v_in - - - v_1 - v_2 - - - - - v_2 - - - v_3 - v_out - - - - - u_1 - - - u_2 - u_3 - - - - - - u_1 - - - q_1 - C - - - - - u_2 - - - R - v_2 - - - - - - - - t - - v_3 - - - - u_3 - L - - - - - diff --git a/tests/resources/generator/dae_cellml_1_1_model/model.h b/tests/resources/generator/dae_cellml_1_1_model/model.h deleted file mode 100644 index 3350366ae..000000000 --- a/tests/resources/generator/dae_cellml_1_1_model/model.h +++ /dev/null @@ -1,37 +0,0 @@ -/* The content of this file was generated using the C profile of libCellML 0.6.3. */ - -#pragma once - -#include - -extern const char VERSION[]; -extern const char LIBCELLML_VERSION[]; - -extern const size_t STATE_COUNT; -extern const size_t CONSTANT_COUNT; -extern const size_t COMPUTED_CONSTANT_COUNT; -extern const size_t ALGEBRAIC_VARIABLE_COUNT; - -typedef struct { - char name[6]; - char units[11]; - char component[5]; -} VariableInfo; - -extern const VariableInfo VOI_INFO; -extern const VariableInfo STATE_INFO[]; -extern const VariableInfo CONSTANT_INFO[]; -extern const VariableInfo COMPUTED_CONSTANT_INFO[]; -extern const VariableInfo ALGEBRAIC_INFO[]; - -double * createStatesArray(); -double * createConstantsArray(); -double * createComputedConstantsArray(); -double * createAlgebraicVariablesArray(); - -void deleteArray(double *array); - -void initialiseArrays(double *states, double *rates, double *constants, double *computedConstants, double *algebraicVariables); -void computeComputedConstants(double *states, double *rates, double *constants, double *computedConstants, double *algebraic); -void computeRates(double voi, double *states, double *rates, double *constants, double *computedConstants, double *algebraicVariables); -void computeVariables(double voi, double *states, double *rates, double *constants, double *computedConstants, double *algebraicVariables); diff --git a/tests/resources/generator/dae_cellml_1_1_model/model.py b/tests/resources/generator/dae_cellml_1_1_model/model.py deleted file mode 100644 index e84732552..000000000 --- a/tests/resources/generator/dae_cellml_1_1_model/model.py +++ /dev/null @@ -1,138 +0,0 @@ -# The content of this file was generated using the Python profile of libCellML 0.6.3. - -from enum import Enum -from math import * - - -__version__ = "0.6.0" -LIBCELLML_VERSION = "0.6.3" - -STATE_COUNT = 2 -CONSTANT_COUNT = 5 -COMPUTED_CONSTANT_COUNT = 0 -ALGEBRAIC_VARIABLE_COUNT = 5 - -VOI_INFO = {"name": "t", "units": "second", "component": "main"} - -STATE_INFO = [ - {"name": "q_1", "units": "coulomb", "component": "main"}, - {"name": "v_3", "units": "C_per_s", "component": "main"} -] - -CONSTANT_INFO = [ - {"name": "v_in", "units": "C_per_s", "component": "main"}, - {"name": "v_out", "units": "C_per_s", "component": "main"}, - {"name": "C", "units": "C2_per_J", "component": "main"}, - {"name": "R", "units": "Js_per_C2", "component": "main"}, - {"name": "L", "units": "Js2_per_C2", "component": "main"} -] - -COMPUTED_CONSTANT_INFO = [ -] - -ALGEBRAIC_INFO = [ - {"name": "v_1", "units": "C_per_s", "component": "main"}, - {"name": "v_2", "units": "C_per_s", "component": "main"}, - {"name": "u_3", "units": "J_per_C", "component": "main"}, - {"name": "u_2", "units": "J_per_C", "component": "main"}, - {"name": "u_1", "units": "J_per_C", "component": "main"} -] - - -def create_states_array(): - return [nan]*STATE_COUNT - - -def create_constants_array(): - return [nan]*CONSTANT_COUNT - - -def create_computed_constants_array(): - return [nan]*COMPUTED_CONSTANT_COUNT - - -def create_algebraic_variables_array(): - return [nan]*ALGEBRAIC_VARIABLE_COUNT - - -from nlasolver import nla_solve - - -def objective_function_0(u, f, data): - voi = data[0] - states = data[1] - rates = data[2] - constants = data[3] - computed_constants = data[4] - algebraic_variables = data[5] - - algebraicVariables[0] = u[0] - - f[0] = constants[0]-(algebraicVariables[0]+algebraicVariables[1]) - - -def find_root_0(voi, states, rates, constants, computed_constants, algebraic_variables): - u = [nan]*1 - - u[0] = algebraicVariables[0] - - u = nla_solve(objective_function_0, u, 1, [voi, states, rates, constants, computed_constants, algebraic_variables]) - - algebraicVariables[0] = u[0] - - -def objective_function_1(u, f, data): - voi = data[0] - states = data[1] - rates = data[2] - constants = data[3] - computed_constants = data[4] - algebraic_variables = data[5] - - algebraicVariables[2] = u[0] - - f[0] = algebraicVariables[4]-(algebraicVariables[3]+algebraicVariables[2]) - - -def find_root_1(voi, states, rates, constants, computed_constants, algebraic_variables): - u = [nan]*1 - - u[0] = algebraicVariables[2] - - u = nla_solve(objective_function_1, u, 1, [voi, states, rates, constants, computed_constants, algebraic_variables]) - - algebraicVariables[2] = u[0] - - -def initialise_arrays(states, rates, constants, computed_constants, algebraic_variables): - states[0] = 1.0 - states[1] = 0.0 - constants[0] = 1.0 - constants[1] = 1.0 - constants[2] = 20.0 - constants[3] = 2.0 - constants[4] = 10.0 - algebraicVariables[0] = 0.0 - algebraicVariables[2] = 0.0 - - -def compute_computed_constants(states, rates, constants, computed_constants, algebraic): - pass - - -def compute_rates(voi, states, rates, constants, computed_constants, algebraic_variables): - algebraicVariables[1] = states[1]+constants[1] - find_root_0(voi, states, rates, constants, computed_constants, algebraic_variables) - rates[0] = algebraicVariables[0] - algebraicVariables[3] = constants[3]*algebraicVariables[1] - algebraicVariables[4] = states[0]/constants[2] - find_root_1(voi, states, rates, constants, computed_constants, algebraic_variables) - rates[1] = algebraicVariables[2]/constants[4] - - -def compute_variables(voi, states, rates, constants, computed_constants, algebraic_variables): - algebraicVariables[1] = states[1]+constants[1] - find_root_0(voi, states, rates, constants, computed_constants, algebraic_variables) - algebraicVariables[3] = constants[3]*algebraicVariables[1] - algebraicVariables[4] = states[0]/constants[2] - find_root_1(voi, states, rates, constants, computed_constants, algebraic_variables) From 060b8153fd508c1255ac21c0e76afa999a0d9048 Mon Sep 17 00:00:00 2001 From: Rayen Lee Date: Fri, 12 Dec 2025 13:14:04 +1300 Subject: [PATCH 12/16] Replaced exception use in symEngineEquation() Substituted with std::pair> use instead --- src/analyser.cpp | 32 ++++++++++++++++---------------- src/analyser_p.h | 3 ++- 2 files changed, 18 insertions(+), 17 deletions(-) diff --git a/src/analyser.cpp b/src/analyser.cpp index 33788a527..4df5a570e 100644 --- a/src/analyser.cpp +++ b/src/analyser.cpp @@ -199,35 +199,38 @@ bool AnalyserInternalEquation::variableOnLhsOrRhs(const AnalyserInternalVariable || variableOnRhs(variable); } -SymEngine::RCP AnalyserInternalEquation::symEngineEquation(const AnalyserEquationAstPtr &ast, const SymEngineSymbolMap &symbolMap) +SymEngineEquationResult AnalyserInternalEquation::symEngineEquation(const AnalyserEquationAstPtr &ast, const SymEngineSymbolMap &symbolMap) { if (ast == nullptr) { - return SymEngine::null; + return {true, SymEngine::null}; } AnalyserEquationAstPtr leftAst = ast->leftChild(); AnalyserEquationAstPtr rightAst = ast->rightChild(); // Recursively call getConvertedAst on left and right children. - SymEngine::RCP left = symEngineEquation(leftAst, symbolMap); - SymEngine::RCP right = symEngineEquation(rightAst, symbolMap); + auto [leftSuccess, left] = symEngineEquation(leftAst, symbolMap); + auto [rightSuccess, right] = symEngineEquation(rightAst, symbolMap); + + if (!leftSuccess || !rightSuccess) { + return {false, SymEngine::null}; + } // Analyse mAst current type and value. switch (ast->type()) { case AnalyserEquationAst::Type::EQUALITY: - return Eq(left, right); + return {true, Eq(left, right)}; case AnalyserEquationAst::Type::PLUS: - return add(left, right); + return {true, add(left, right)}; case AnalyserEquationAst::Type::CI: // Seems like the voi doesn't exist in mAllVariables, so we don't have an easy means of access. - // For now we'll just throw an error if the symbol is not found. if (symbolMap.find(ast->variable()->name()) == symbolMap.end()) { - throw std::runtime_error("Unsupported variable in symEngineRepresentation"); + return {false, SymEngine::null}; } - return symbolMap.at(ast->variable()->name()); + return {true, symbolMap.at(ast->variable()->name())}; default: - // Our parser is unable to handle this type, so we need to let the caller know by throwing an error. - throw std::runtime_error("Unsupported AST type in symEngineRepresentation"); + // Rearrangement is not possible with this type. + return {false, SymEngine::null}; } } @@ -291,11 +294,8 @@ AnalyserEquationAstPtr AnalyserInternalEquation::rearrangeFor(const AnalyserInte variableMap[symbol] = variable; } - SymEngine::RCP seEquation; - try { - seEquation = symEngineEquation(mAst, symbolMap); - } catch (const std::runtime_error &e) { - // Our parser was unable to convert the AST to a SymEngine expression. + auto [success, seEquation] = symEngineEquation(mAst, symbolMap); + if (!success) { return nullptr; } diff --git a/src/analyser_p.h b/src/analyser_p.h index f5aec6745..3c1d78544 100644 --- a/src/analyser_p.h +++ b/src/analyser_p.h @@ -49,6 +49,7 @@ using AnalyserExternalVariablePtrs = std::vector; using SymEngineVariableMap = std::map, AnalyserInternalVariablePtr, SymEngine::RCPBasicKeyLess>; using SymEngineSymbolMap = std::map>; +using SymEngineEquationResult = std::pair>; struct AnalyserInternalVariable { @@ -139,7 +140,7 @@ struct AnalyserInternalEquation bool variableOnRhs(const AnalyserInternalVariablePtr &variable); bool variableOnLhsOrRhs(const AnalyserInternalVariablePtr &variable); - SymEngine::RCP symEngineEquation(const AnalyserEquationAstPtr &ast, const SymEngineSymbolMap &symbolMap); + SymEngineEquationResult symEngineEquation(const AnalyserEquationAstPtr &ast, const SymEngineSymbolMap &symbolMap); AnalyserEquationAstPtr parseSymEngineExpression(const SymEngine::RCP &seExpression, const AnalyserEquationAstPtr &parentAst, const SymEngineVariableMap &variableMap); AnalyserEquationAstPtr rearrangeFor(const AnalyserInternalVariablePtr &variable); From 685e1a1619ebbc53f5ad3f41fc1fb05835d13b44 Mon Sep 17 00:00:00 2001 From: Rayen Lee Date: Fri, 12 Dec 2025 13:16:51 +1300 Subject: [PATCH 13/16] Removed redundant map include --- src/analyser_p.h | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/analyser_p.h b/src/analyser_p.h index 3c1d78544..fd1eff93e 100644 --- a/src/analyser_p.h +++ b/src/analyser_p.h @@ -14,8 +14,6 @@ See the License for the specific language governing permissions and limitations under the License. */ -#include - #include "libcellml/generatorprofile.h" #include "libcellml/issue.h" From 81f57664b8c532f2ec8644cac5f39d253c0d7ca7 Mon Sep 17 00:00:00 2001 From: Rayen Lee Date: Fri, 12 Dec 2025 14:03:46 +1300 Subject: [PATCH 14/16] Change SymEngineEquationResult to use tuple instead of pair --- src/analyser_p.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/analyser_p.h b/src/analyser_p.h index fd1eff93e..733a5c121 100644 --- a/src/analyser_p.h +++ b/src/analyser_p.h @@ -47,7 +47,7 @@ using AnalyserExternalVariablePtrs = std::vector; using SymEngineVariableMap = std::map, AnalyserInternalVariablePtr, SymEngine::RCPBasicKeyLess>; using SymEngineSymbolMap = std::map>; -using SymEngineEquationResult = std::pair>; +using SymEngineEquationResult = std::tuple>; struct AnalyserInternalVariable { From cb8b4eeb9eb9efd0d3691dd5a2e3fa494523e4fd Mon Sep 17 00:00:00 2001 From: Alan Garny Date: Mon, 15 Dec 2025 19:44:07 +1300 Subject: [PATCH 15/16] CMake: find and use SymEngine. --- cmake/environmentchecks.cmake | 7 +++++++ src/3rdparty/symengine/symenginebegin.h | 21 +++++++++++++++++++++ src/3rdparty/symengine/symengineend.h | 19 +++++++++++++++++++ src/CMakeLists.txt | 19 +++++++------------ src/analyser.cpp | 3 +++ 5 files changed, 57 insertions(+), 12 deletions(-) create mode 100644 src/3rdparty/symengine/symenginebegin.h create mode 100644 src/3rdparty/symengine/symengineend.h diff --git a/cmake/environmentchecks.cmake b/cmake/environmentchecks.cmake index 260b5c0ae..6409daaa0 100644 --- a/cmake/environmentchecks.cmake +++ b/cmake/environmentchecks.cmake @@ -183,6 +183,13 @@ if(NOT DEFINED _ZLIB_FIND_REPORTED) message(STATUS "Found ZLIB: ${ZLIB_LIBRARIES} (found version \"${ZLIB_VERSION_STRING}\").") endif() +# Set the minimum policy version to work around SymEngine's outdated CMake requirements. +set(CMAKE_POLICY_VERSION_MINIMUM 3.10) + +# Find SymEngine. +find_package(SymEngine REQUIRED CONFIG) +message(STATUS "Found SymEngine: ${SYMENGINE_LIBRARIES} (found version \"${SymEngine_VERSION}\").") + if(BUILDCACHE_EXE OR CLCACHE_EXE OR CCACHE_EXE) set(COMPILER_CACHE_AVAILABLE TRUE CACHE INTERNAL "Executable required to cache compilations.") endif() diff --git a/src/3rdparty/symengine/symenginebegin.h b/src/3rdparty/symengine/symenginebegin.h new file mode 100644 index 000000000..7251c1d32 --- /dev/null +++ b/src/3rdparty/symengine/symenginebegin.h @@ -0,0 +1,21 @@ +/* +Copyright libOpenCOR contributors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#ifdef __clang__ +# pragma clang diagnostic push +# pragma clang diagnostic ignored "-Wdeprecated-literal-operator" +# pragma clang diagnostic ignored "-Wunused-parameter" +#endif diff --git a/src/3rdparty/symengine/symengineend.h b/src/3rdparty/symengine/symengineend.h new file mode 100644 index 000000000..3d9678e22 --- /dev/null +++ b/src/3rdparty/symengine/symengineend.h @@ -0,0 +1,19 @@ +/* +Copyright libOpenCOR contributors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#ifdef __clang__ +# pragma clang diagnostic pop +#endif diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index d971b4e33..afe5aa1b5 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -178,18 +178,6 @@ add_library(cellml ${API_HEADER_FILES} ) -target_include_directories(cellml - PUBLIC - "C:/symengine/include" - "C:/gmp/include" -) - -target_link_libraries(cellml - PUBLIC - "C:/symengine/lib/symengine.lib" - "C:/gmp/lib/gmp.lib" -) - set_source_files_properties(${CMAKE_CURRENT_SOURCE_DIR}/xmldoc.cpp PROPERTIES COMPILE_DEFINITIONS XML_ERROR_CALLBACK_ARGUMENT_TYPE=${CONST_ERROR_STRUCTURED_ERROR_CALLBACK_TYPE}) @@ -215,6 +203,13 @@ endif() apply_libxml2_settings(cellml) +target_link_libraries(cellml PRIVATE ${SYMENGINE_LIBRARIES}) +target_include_directories(cellml + PRIVATE + $ + ${SYMENGINE_INCLUDE_DIRS} +) + # Use target compile features to propagate features to consuming projects. target_compile_features(cellml PUBLIC cxx_std_17) diff --git a/src/analyser.cpp b/src/analyser.cpp index 4df5a570e..ad04541a5 100644 --- a/src/analyser.cpp +++ b/src/analyser.cpp @@ -22,7 +22,10 @@ limitations under the License. #include #include + +#include "symenginebegin.h" #include +#include "symengineend.h" #include "libcellml/analyserequation.h" #include "libcellml/analyserexternalvariable.h" From 9020f40a6b76f89b63651f894878c25440b83a30 Mon Sep 17 00:00:00 2001 From: Alan Garny Date: Wed, 17 Dec 2025 16:57:36 +1300 Subject: [PATCH 16/16] CI: use SymEngine as a dependency. --- .github/workflows/ci.yml | 29 ++++++++++++++++++++++++++--- 1 file changed, 26 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b17a4a1ca..f4234708e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -22,17 +22,19 @@ jobs: - name: 'Windows static - C++' os: windows-latest python_bindings: OFF - additional_cmake_options: -DLibXml2_DIR="C:\libxml2\libxml2-2.9.10\CMake" -DZLIB_DIR="C:\zlib\lib\cmake\ZLIB-1.2.12" + additional_cmake_options: -DLibXml2_DIR="C:\libxml2\libxml2-2.9.10\CMake" -DSYMENGINE_DIR="C:\symengine\CMake" -DZLIB_DIR="C:\zlib\lib\cmake\ZLIB-1.2.12" - name: 'Windows shared - C++/Python' os: windows-latest python_bindings: ON - additional_cmake_options: -DLibXml2_DIR="C:\libxml2\libxml2-2.9.10\CMake" -DZLIB_DIR="C:\zlib\lib\cmake\ZLIB-1.2.12" + additional_cmake_options: -DLibXml2_DIR="C:\libxml2\libxml2-2.9.10\CMake" -DSYMENGINE_DIR="C:\symengine\CMake" -DZLIB_DIR="C:\zlib\lib\cmake\ZLIB-1.2.12" - name: 'Linux static - C++' os: ubuntu-latest python_bindings: OFF + additional_cmake_options: -DSYMENGINE_DIR="$HOME/symengine/build/cmake" - name: 'Linux shared - C++/Python' os: ubuntu-latest python_bindings: ON + additional_cmake_options: -DSYMENGINE_DIR="$HOME/symengine/build/cmake" - name: 'macOS static - C++ (Intel)' os: macos-15-intel python_bindings: OFF @@ -68,6 +70,23 @@ jobs: cd C:\ curl -L https://github.com/cellml/gha/releases/download/gha/libxml2-Windows.tar.gz -o libxml2.tar.gz -s tar -xzf libxml2.tar.gz + - name: Install SymEngine (Windows only) + if: ${{ runner.os == 'Windows' }} + run: | + cd C:\ + curl -L https://github.com/cellml/gha/releases/download/gha/symengine-Windows.tar.gz -o symengine.tar.gz -s + tar -xzf symengine.tar.gz + - name: Install SymEngine (Linux only) + if: ${{ runner.os == 'Linux' }} + run: | + cd $HOME + curl -L https://github.com/cellml/gha/releases/download/gha/symengine-Linux.tar.gz -o symengine.tar.gz -s + tar -xzf symengine.tar.gz + - name: Install SymEngine (macOS only) + if: ${{ runner.os == 'macOS' }} + run: | + brew update + brew install symengine - name: Install zlib (Windows only) if: ${{ runner.os == 'Windows' }} run: | @@ -101,13 +120,17 @@ jobs: run: | cd $HOME wget https://github.com/cellml/gha/releases/download/gha/libxml2-WASM.tar.gz -O - | tar -xz + - name: Install SymEngine + run: | + cd $HOME + wget https://github.com/cellml/gha/releases/download/gha/symengine-WASM.tar.gz -O - | tar -xz - name: Install zlib run: | cd $HOME wget https://github.com/cellml/gha/releases/download/gha/zlib-WASM.tar.gz -O - | tar -xz - name: Configure libCellML run: | - emcmake cmake -G Ninja -S . -B build-wasm -DBUILD_TYPE=Release -DLIBXML2_INCLUDE_DIR=$HOME/libxml2/include/libxml2 -DLIBXML2_LIBRARY=$HOME/libxml2/lib/libxml2.a -DZLIB_INCLUDE_DIR=$HOME/zlib/include -DZLIB_LIBRARY=$HOME/zlib/lib/libz.a + emcmake cmake -G Ninja -S . -B build-wasm -DBUILD_TYPE=Release -DLIBXML2_INCLUDE_DIR=$HOME/libxml2/include/libxml2 -DLIBXML2_LIBRARY=$HOME/libxml2/lib/libxml2.a -DSYMENGINE_INCLUDE_DIR=$HOME/symengine/include -DSYMENGINE_LIBRARY=$HOME/symengine/lib/libsymengine.a -DZLIB_INCLUDE_DIR=$HOME/zlib/include -DZLIB_LIBRARY=$HOME/zlib/lib/libz.a - name: Build libCellML run: cmake --build build-wasm - name: Unit testing