Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 26 additions & 3 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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: |
Expand Down Expand Up @@ -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
Copy link

Copilot AI Dec 17, 2025

Choose a reason for hiding this comment

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

The WASM build configuration uses SYMENGINE_INCLUDE_DIR and SYMENGINE_LIBRARY directly, but the find_package(SymEngine REQUIRED CONFIG) in cmake/environmentchecks.cmake expects SymEngine to be found via CMake config mode. This inconsistency means the WASM build bypasses the standard SymEngine detection mechanism, which could lead to issues if the variable names or detection logic changes.

Suggested change
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
emcmake cmake -G Ninja -S . -B build-wasm -DBUILD_TYPE=Release -DCMAKE_PREFIX_PATH=$HOME/symengine -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

Copilot uses AI. Check for mistakes.
- name: Build libCellML
run: cmake --build build-wasm
- name: Unit testing
Expand Down
7 changes: 7 additions & 0 deletions cmake/environmentchecks.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -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}\").")
Comment on lines +187 to +191
Copy link

Copilot AI Dec 17, 2025

Choose a reason for hiding this comment

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

Setting CMAKE_POLICY_VERSION_MINIMUM doesn't actually change CMake's behavior. To work around outdated CMake requirements in a dependency, you should use cmake_policy(VERSION) or set specific policies with cmake_policy(SET). The current statement has no effect.

Suggested change
set(CMAKE_POLICY_VERSION_MINIMUM 3.10)
# Find SymEngine.
find_package(SymEngine REQUIRED CONFIG)
message(STATUS "Found SymEngine: ${SYMENGINE_LIBRARIES} (found version \"${SymEngine_VERSION}\").")
cmake_policy(PUSH)
cmake_policy(VERSION 3.10)
# Find SymEngine.
find_package(SymEngine REQUIRED CONFIG)
message(STATUS "Found SymEngine: ${SYMENGINE_LIBRARIES} (found version \"${SymEngine_VERSION}\").")
cmake_policy(POP)

Copilot uses AI. Check for mistakes.

if(BUILDCACHE_EXE OR CLCACHE_EXE OR CCACHE_EXE)
set(COMPILER_CACHE_AVAILABLE TRUE CACHE INTERNAL "Executable required to cache compilations.")
endif()
Expand Down
21 changes: 21 additions & 0 deletions src/3rdparty/symengine/symenginebegin.h
Original file line number Diff line number Diff line change
@@ -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
19 changes: 19 additions & 0 deletions src/3rdparty/symengine/symengineend.h
Original file line number Diff line number Diff line change
@@ -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
7 changes: 7 additions & 0 deletions src/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,13 @@ endif()

apply_libxml2_settings(cellml)

target_link_libraries(cellml PRIVATE ${SYMENGINE_LIBRARIES})
target_include_directories(cellml
PRIVATE
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/3rdparty/symengine>
${SYMENGINE_INCLUDE_DIRS}
)

# Use target compile features to propagate features to consuming projects.
target_compile_features(cellml PUBLIC cxx_std_17)

Expand Down
140 changes: 140 additions & 0 deletions src/analyser.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,10 @@ limitations under the License.
#include <cmath>
#include <iterator>

#include "symenginebegin.h"
#include <symengine/solve.h>
#include "symengineend.h"

#include "libcellml/analyserequation.h"
#include "libcellml/analyserexternalvariable.h"
#include "libcellml/generatorprofile.h"
Expand Down Expand Up @@ -198,6 +202,133 @@ bool AnalyserInternalEquation::variableOnLhsOrRhs(const AnalyserInternalVariable
|| variableOnRhs(variable);
}

SymEngineEquationResult AnalyserInternalEquation::symEngineEquation(const AnalyserEquationAstPtr &ast, const SymEngineSymbolMap &symbolMap)
{
if (ast == nullptr) {
return {true, SymEngine::null};
}

AnalyserEquationAstPtr leftAst = ast->leftChild();
AnalyserEquationAstPtr rightAst = ast->rightChild();

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

Copilot AI Dec 17, 2025

Choose a reason for hiding this comment

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

The comment refers to "getConvertedAst" but the function being called is "symEngineEquation". This appears to be a copy-paste error or outdated comment from refactoring.

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

Copilot uses AI. Check for mistakes.
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.
Copy link

Copilot AI Dec 17, 2025

Choose a reason for hiding this comment

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

The comment refers to "mAst" but should refer to "ast" which is the parameter. The current variable being analyzed is the parameter, not the member variable.

Suggested change
// Analyse mAst current type and value.
// Analyse ast current type and value.

Copilot uses AI. Check for mistakes.
switch (ast->type()) {
case AnalyserEquationAst::Type::EQUALITY:
return {true, Eq(left, right)};
case AnalyserEquationAst::Type::PLUS:
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.
if (symbolMap.find(ast->variable()->name()) == symbolMap.end()) {
return {false, SymEngine::null};
}
return {true, symbolMap.at(ast->variable()->name())};
default:
// Rearrangement is not possible with this type.
return {false, SymEngine::null};
}
Comment on lines +223 to +237
Copy link

Copilot AI Dec 17, 2025

Choose a reason for hiding this comment

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

The switch statement only handles EQUALITY, PLUS, and CI types, returning false for all other types. However, basic mathematical operations like subtraction, multiplication, division, and exponentiation are likely needed for equation rearrangement. The current implementation will fail to rearrange equations containing these operations.

Copilot uses AI. Check for mistakes.
Comment on lines +223 to +237
Copy link

Copilot AI Dec 17, 2025

Choose a reason for hiding this comment

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

The parseSymEngineExpression function handles MUL (multiplication) type but the symEngineEquation function does not convert TIMES to multiplication when building the SymEngine expression. This asymmetry means the conversion to SymEngine will fail for equations containing multiplication, even though the reverse conversion is implemented.

Copilot uses AI. Check for mistakes.
}

AnalyserEquationAstPtr AnalyserInternalEquation::parseSymEngineExpression(const SymEngine::RCP<const SymEngine::Basic> &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);
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<const SymEngine::Symbol> symbolExpr = SymEngine::rcp_dynamic_cast<const SymEngine::Symbol>(seExpression);
ast->setType(AnalyserEquationAst::Type::CI);
ast->setVariable(variableMap.at(symbolExpr)->mVariable);
break;
}
case SymEngine::SYMENGINE_INTEGER: {
ast->setType(AnalyserEquationAst::Type::CN);
ast->setValue(seExpression->__str__());
break;
}
default:
break;
}
Comment on lines +274 to +276
Copy link

Copilot AI Dec 17, 2025

Choose a reason for hiding this comment

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

The default case in the switch statement silently breaks without setting any type for the AST node. This leaves the AST in an incomplete state when encountering unsupported SymEngine expression types, which could lead to undefined behavior when the AST is later used.

Copilot uses AI. Check for mistakes.

// TODO Update to account for symengine expressions with 3 or more children.
if (children.size() > 0) {
Copy link

Copilot AI Dec 17, 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' is less idiomatic than 'children.empty()' or '!children.empty()'. Using the empty() method is clearer and is the standard C++ way to check if a container is non-empty.

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

Copilot uses AI. Check for mistakes.
ast->setLeftChild(parseSymEngineExpression(children[0], ast, variableMap));
if (children.size() > 1) {
ast->setRightChild(parseSymEngineExpression(children[1], ast, variableMap));
}
Comment on lines +278 to +283
Copy link

Copilot AI Dec 17, 2025

Choose a reason for hiding this comment

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

The TODO indicates that expressions with 3 or more children are not properly handled. However, operations like addition and multiplication are commonly n-ary in mathematical expressions (e.g., a + b + c). The current implementation only processes the first two children and silently ignores the rest, which will produce incorrect results for such expressions.

Copilot uses AI. Check for mistakes.
}

return ast;
}

AnalyserEquationAstPtr AnalyserInternalEquation::rearrangeFor(const AnalyserInternalVariablePtr &variable)
{
SymEngineSymbolMap symbolMap;
SymEngineVariableMap variableMap;

for (const auto &variable : mAllVariables) {
Copy link

Copilot AI Dec 17, 2025

Choose a reason for hiding this comment

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

The parameter name 'variable' shadows the loop variable 'variable' used in the for-loop. This makes the code confusing and could lead to errors. The parameter should be renamed to something like 'targetVariable' or 'variableToIsolate'.

Copilot uses AI. Check for mistakes.
SymEngine::RCP<const SymEngine::Symbol> symbol = SymEngine::symbol(variable->mVariable->name());
symbolMap[variable->mVariable->name()] = symbol;
variableMap[symbol] = variable;
}

auto [success, seEquation] = symEngineEquation(mAst, symbolMap);
if (!success) {
return nullptr;
}

SymEngine::RCP<const SymEngine::Set> 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.
if (solutions.size() != 1) {
return nullptr;
}
SymEngine::RCP<const SymEngine::Basic> answer = solutions.front();

// Rebuild the AST from the rearranged expression.
AnalyserEquationAstPtr ast = AnalyserEquationAst::create();
AnalyserEquationAstPtr isolatedVariableAst = AnalyserEquationAst::create();
AnalyserEquationAstPtr rearrangedEquationAst = parseSymEngineExpression(answer, nullptr, variableMap);

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);
Comment on lines +317 to +327
Copy link

Copilot AI Dec 17, 2025

Choose a reason for hiding this comment

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

The parseSymEngineExpression is called with 'nullptr' as the parent for the rearrangedEquationAst, but then the parent is set to 'ast' on line 327. This is inconsistent - the parent should either be passed during creation or set immediately after. The current approach creates a temporary inconsistent state.

Copilot uses AI. Check for mistakes.

return ast;
}

bool AnalyserInternalEquation::check(const AnalyserModelPtr &analyserModel, bool checkNlaSystems)
{
// Nothing to check if the equation has a known type.
Expand Down Expand Up @@ -278,6 +409,15 @@ 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) {
// TODO Update variables and/or equation type when necessary.
mAst = newAst;
Copy link

Copilot AI Dec 17, 2025

Choose a reason for hiding this comment

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

When rearrangement fails (newAst is nullptr), the code silently continues with the original AST. However, the subsequent check on line 421-422 still requires the variable to be on LHS or RHS, which will fail. This means equations that can't be rearranged will incorrectly fail analysis, potentially marking variables as overconstrained or underconstrained when they're simply not rearrangeable with the current implementation.

Suggested change
mAst = newAst;
mAst = newAst;
} else {
// Rearrangement failed, so we no longer treat this variable as isolated.
unknownVariableLeft = nullptr;

Copilot uses AI. Check for mistakes.
}
}

if (((unknownVariableLeft != nullptr)
&& (checkNlaSystems || variableOnLhsOrRhs(unknownVariableLeft)))
|| !initialisedVariables.empty()) {
Expand Down
14 changes: 14 additions & 0 deletions src/analyser_p.h
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,12 @@ limitations under the License.
#include "logger_p.h"
#include "utilities.h"

namespace SymEngine {
template<class T> class RCP;
class Basic;
class Symbol;
} // namespace SymEngine

namespace libcellml {

struct AnalyserInternalEquation;
Expand All @@ -39,6 +45,10 @@ using AnalyserEquationPtrs = std::vector<AnalyserEquationPtr>;
using AnalyserVariablePtrs = std::vector<AnalyserVariablePtr>;
using AnalyserExternalVariablePtrs = std::vector<AnalyserExternalVariablePtr>;

using SymEngineVariableMap = std::map<SymEngine::RCP<const SymEngine::Symbol>, AnalyserInternalVariablePtr, SymEngine::RCPBasicKeyLess>;
using SymEngineSymbolMap = std::map<std::string, SymEngine::RCP<const SymEngine::Symbol>>;
using SymEngineEquationResult = std::tuple<bool, SymEngine::RCP<const SymEngine::Basic>>;

struct AnalyserInternalVariable
{
enum struct Type
Expand Down Expand Up @@ -128,6 +138,10 @@ struct AnalyserInternalEquation
bool variableOnRhs(const AnalyserInternalVariablePtr &variable);
bool variableOnLhsOrRhs(const AnalyserInternalVariablePtr &variable);

SymEngineEquationResult symEngineEquation(const AnalyserEquationAstPtr &ast, const SymEngineSymbolMap &symbolMap);
AnalyserEquationAstPtr parseSymEngineExpression(const SymEngine::RCP<const SymEngine::Basic> &seExpression, const AnalyserEquationAstPtr &parentAst, const SymEngineVariableMap &variableMap);
AnalyserEquationAstPtr rearrangeFor(const AnalyserInternalVariablePtr &variable);

bool check(const AnalyserModelPtr &analyserModel, bool checkNlaSystems);
};

Expand Down
14 changes: 14 additions & 0 deletions tests/analyser/analyser.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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());
}
24 changes: 0 additions & 24 deletions tests/generator/generator.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
Loading
Loading