Skip to content

Conversation

@agarny
Copy link
Contributor

@agarny agarny commented Dec 18, 2025

No description provided.

RLee64 and others added 30 commits December 12, 2025 13:26
Also changed the name of astMap to variableMap to better reflect what it's actually mapping to
Substituted with std::pair<bool, SymEngine::RCP<const SymEngine::Basic>> use instead
Mostly GPT generated test cases
Test case has been superseded by the newly added test suite to more robustly test for equation rearrangement
Previous system assumed that symengine expressions contained only 0-2 children, but in reality it could be any whole number. This adjusts the existing conversion logic to account for this.
Made it so that we also now consider mathml unary +/- operators
Can now handle unary plus, minus, unary minus, and CN types from AST (which means code now passes Analyser.rearrangeAdditiveEquations)
Had an error at equation 4

Also added brackets to equation comments to make things more clear
Now passes Analyser.rearrangeMultiplicativeEquations
Now covers a greater variety of cases
Symengine is not capable of rearranging trig functions into inverse trig functions (e.g. tan(x) = y cannot become x = atan(y))
Now has better Eq 1 test case and more consistent variable naming
Now supports
- Decimal point values (i.e. doubles)
- Constants (E, pi, and inf)
We need to ensure that there can only be one real solution for each
Some specific things had to be added to handle this
- CN representing integers must be appropriately converted since symengine requires exponentials to be whole numbers
- We need to filter out real from non-real solutions since we assume users will want to ignore the complex domain in most cases
Now assumes left child will always be the non-null component
Copilot AI review requested due to automatic review settings December 18, 2025 21:15
@agarny
Copy link
Contributor Author

agarny commented Dec 18, 2025

Argh, wrong repo!

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This pull request adds support for the SymEngine symbolic mathematics library to enable automatic equation rearrangement in the libCellML analyser. The integration allows the analyser to solve for unknowns in algebraic equations that are not already in the form variable = expression, eliminating the need for numerical equation solvers in some cases.

Key changes:

  • Integration of SymEngine library for symbolic equation manipulation and solving
  • New equation rearrangement capabilities in the analyser
  • Addition of comprehensive test cases for various equation types (trigonometric, polynomial, multiplicative, etc.)

Reviewed changes

Copilot reviewed 19 out of 19 changed files in this pull request and generated 14 comments.

Show a summary per file
File Description
cmake/environmentchecks.cmake Adds SymEngine package detection and configuration
src/CMakeLists.txt Links SymEngine libraries and includes directories for the cellml target
src/analyser_p.h Adds SymEngine-related type definitions and forward declarations for new equation rearrangement methods
src/analyser.cpp Implements equation conversion to/from SymEngine format, symbolic solving, and AST reconstruction
src/3rdparty/symengine/symenginebegin.h Pragma directives to suppress SymEngine compiler warnings on Clang
src/3rdparty/symengine/symengineend.h Pragma directives to restore compiler warning settings after SymEngine includes
tests/analyser/tests.cmake Adds new SymEngine test file to the build configuration
tests/analyser/analysersymengine.cpp Comprehensive test suite covering addition, multiplication, trigonometric functions, constants, and polynomial equations
tests/resources/analyser/symengine/*.cellml Test model files for validating equation rearrangement functionality
tests/generator/generator.cpp Removes DAE model test (likely now handled by SymEngine rearrangement)
tests/resources/generator/dae_cellml_1_1_model/* Removes DAE model test resources
.github/workflows/ci.yml Adds GMP and SymEngine installation steps for all platforms, updates to staging branch trigger, modernizes CMake invocations

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

auto currentAst = ast;

// All children (except the last) are guaranteed to be left children in the AST tree.
for (int i = 0; i + 1 < children.size(); ++i) {
Copy link

Copilot AI Dec 18, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The loop variable i is declared as int but is being compared with children.size() which returns size_t (an unsigned type). This causes a signed/unsigned comparison warning. Use size_t for the loop variable instead to avoid potential issues with large collections and eliminate compiler warnings.

Copilot uses AI. Check for mistakes.
if (SymEngine::eq(*constant, *SymEngine::E)) {
ast->setType(AnalyserEquationAst::Type::E);
} else if (SymEngine::eq(*constant, *SymEngine::pi)) {
ast->setType(AnalyserEquationAst::Type::PI);
Copy link

Copilot AI Dec 18, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When SymEngine::SYMENGINE_CONSTANT is encountered but the constant is neither E nor pi, the AST type is never set, leaving it in an undefined state. This will lead to incorrect behavior. Consider setting a default type, returning an error, or handling all possible SymEngine constants explicitly.

Suggested change
ast->setType(AnalyserEquationAst::Type::PI);
ast->setType(AnalyserEquationAst::Type::PI);
} else {
ast->setType(AnalyserEquationAst::Type::CN);
ast->setValue(seExpression->__str__());

Copilot uses AI. Check for mistakes.
Comment on lines +368 to +370
default:
break;
}
Copy link

Copilot AI Dec 18, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The default case at line 368 doesn't set any AST type, which will leave the AST in an undefined state when an unhandled SymEngine expression type is encountered. This could lead to silent failures or incorrect behavior. Consider logging an error, returning nullptr, or throwing an exception to indicate that the expression type is not supported.

Copilot uses AI. Check for mistakes.
Comment on lines +63 to +65
<apply>
<apply><power/><ci>d</ci><cn cellml:units="dimensionless">3</cn></apply>
</apply>
Copy link

Copilot AI Dec 18, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The XML structure is malformed. The power element at line 64 is missing its second child (the exponent). The apply element should contain both the base and the exponent as children. This will likely cause parsing errors. The structure should be: &lt;apply&gt;&lt;power/&gt;&lt;ci&gt;d&lt;/ci&gt;&lt;cn cellml:units="dimensionless"&gt;3&lt;/cn&gt;&lt;/apply&gt;.

Suggested change
<apply>
<apply><power/><ci>d</ci><cn cellml:units="dimensionless">3</cn></apply>
</apply>
<apply><power/><ci>d</ci><cn cellml:units="dimensionless">3</cn></apply>

Copilot uses AI. Check for mistakes.
AnalyserEquationAstPtr leftAst = ast->leftChild();
AnalyserEquationAstPtr rightAst = ast->rightChild();

// Recursively call getConvertedAst on left and right children.
Copy link

Copilot AI Dec 18, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The comment at line 216 mentions "getConvertedAst" but the actual function being called is "symEngineEquation". Update the comment to reference the correct function name to avoid confusion.

Suggested change
// Recursively call getConvertedAst on left and right children.
// Recursively call symEngineEquation on left and right children.

Copilot uses AI. Check for mistakes.
Comment on lines +419 to +422
for (const auto &variable : mAllVariables) {
SymEngine::RCP<const SymEngine::Symbol> symbol = SymEngine::symbol(variable->mVariable->name());
symbolMap[variable->mVariable->name()] = symbol;
variableMap[symbol] = variable;
Copy link

Copilot AI Dec 18, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The variable name variable shadows the function parameter with the same name. This makes the code confusing and error-prone. Consider renaming the loop variable to something like var or currentVariable to avoid the shadowing.

Suggested change
for (const auto &variable : mAllVariables) {
SymEngine::RCP<const SymEngine::Symbol> symbol = SymEngine::symbol(variable->mVariable->name());
symbolMap[variable->mVariable->name()] = symbol;
variableMap[symbol] = variable;
for (const auto &currentVariable : mAllVariables) {
SymEngine::RCP<const SymEngine::Symbol> symbol = SymEngine::symbol(currentVariable->mVariable->name());
symbolMap[currentVariable->mVariable->name()] = symbol;
variableMap[symbol] = currentVariable;

Copilot uses AI. Check for mistakes.
Comment on lines +63 to +65
<apply>
<apply><power/><ci>d</ci><cn cellml:units="dimensionless">3</cn></apply>
</apply>
Copy link

Copilot AI Dec 18, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's an extra nested apply tag that doesn't have an operator. Line 63 shows &lt;apply&gt; followed immediately by another &lt;apply&gt;&lt;power/&gt;... on line 64, but line 63's apply has no operator specified. This creates malformed MathML. Remove the extra apply tag on line 63.

Suggested change
<apply>
<apply><power/><ci>d</ci><cn cellml:units="dimensionless">3</cn></apply>
</apply>
<apply><power/><ci>d</ci><cn cellml:units="dimensionless">3</cn></apply>

Copilot uses AI. Check for mistakes.
}

// The final child is created and placed where appropriate.
if (children.size() != 0) {
Copy link

Copilot AI Dec 18, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The comparison children.size() != 0 should use the more idiomatic !children.empty() for checking if a container is empty. This is the preferred C++ style and may have better performance characteristics for some containers.

Suggested change
if (children.size() != 0) {
if (!children.empty()) {

Copilot uses AI. Check for mistakes.
@@ -0,0 +1,22 @@
/*
Copyright libOpenCOR contributors.
Copy link

Copilot AI Dec 18, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The copyright notice attributes the code to "libOpenCOR contributors" but this is a libCellML repository. The copyright should be "libCellML Contributors" to be consistent with the rest of the codebase, as seen in other files like tests/analyser/analysersymengine.cpp.

Suggested change
Copyright libOpenCOR contributors.
Copyright libCellML Contributors.

Copilot uses AI. Check for mistakes.
@@ -0,0 +1,19 @@
/*
Copyright libOpenCOR contributors.
Copy link

Copilot AI Dec 18, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The copyright notice attributes the code to "libOpenCOR contributors" but this is a libCellML repository. The copyright should be "libCellML Contributors" to be consistent with the rest of the codebase.

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants