From e6d16f79d8e347e9557c7960b1bb5258a5cc96e0 Mon Sep 17 00:00:00 2001 From: Kwabena Amponsah Date: Wed, 18 Mar 2026 12:27:46 +0000 Subject: [PATCH 01/28] #236 Update README --- README.md | 69 +++++++++++++++++++++++++------------------------------ 1 file changed, 31 insertions(+), 38 deletions(-) diff --git a/README.md b/README.md index b5adeb68..91e45571 100644 --- a/README.md +++ b/README.md @@ -6,37 +6,44 @@ This is a Python implementation of the Functional Curation (FC) language, intended for use with the Cardiac Electrophysiology Web Lab. -Documentation on FC can be found [here](https://chaste.cs.ox.ac.uk/trac/wiki/FunctionalCuration), while the syntax of FC protocols is described [here](https://chaste.cs.ox.ac.uk/trac/wiki/FunctionalCuration/ProtocolSyntax). +Documentation on FC can be found [here](https://chaste.github.io/docs/paper-tutorials/functionalcuration/), while the syntax of FC protocols is described [here](https://github.com/Chaste/trac_archive/wiki/Functional-Curation-_-Protocol-Syntax). An ongoing attempt to document the Web Lab and all its interconnected technologies can be viewed in the [weblab docs repository](https://github.com/ModellingWebLab/weblab_docs). -## No Windows support - -**FC is tested/developed on Linux and OS/X. There are no plans to run it work on Windows**. - -It _might_ run on Windows, if you have installed CVODE with the shared libraries, and an MSVC compiler that matches your Python installation (see [here](https://wiki.python.org/moin/WindowsCompilers)). - -## Transition! - -This package is currently in transition, migrating the functional curation code from being a Chaste extension project to a standalone Python package. -In particular, we're replacing the CellML and code-generation tool PyCml with [cellmlmanip](https://github.com/ModellingWebLab/cellmlmanip) to read and manipulate CellML code and a new module within `weblab-fc` to generate Model code. - -The code from before this transition started can be seen at the tag [`pycml-version`](https://github.com/ModellingWebLab/weblab-fc/tree/pycml-version), but crucial parts of PyCml weblab code are also temporarily stored in the [pycml](./pycml) directory. -Most of the code from [pycml_protocol.py](./pycml/pycml_protocol.py) will have to be replaced by (1) changes to `fc` so that it extracts and stores _all_ the protocol information, and (2) a new code generation module in `fc` that can use information provides by weblab protocols to generate simulation code. -(At the moment both `fc` and `pycml` read the protocol, but in the new code `fc` should gather all the information and then pass it to the code generation component of `fc`.) +> [!NOTE] +> ### No Windows support +> +> **FC is tested/developed on Linux and OS/X. There are no plans to run it work on Windows**. +> +> It _might_ run on Windows, if you have installed CVODE with the shared libraries, +> and an MSVC > compiler that matches your Python installation (see [here](https://wiki.python.org/moin/WindowsCompilers)). + +> [!WARNING] +> ### Transition! +> +> This package is currently in transition, migrating the functional curation code from +> being a Chaste extension project to a standalone Python package. +> In particular, we're replacing the CellML and code-generation tool PyCml with +> [cellmlmanip](https://github.com/ModellingWebLab/cellmlmanip) to read and manipulate +> CellML code and a new module within `weblab-fc` to generate Model code. +> +> The code from before this transition started can be seen at the tag +> [`pycml-version`](https://github.com/ModellingWebLab/weblab-fc/tree/pycml-version), +> but crucial parts of PyCml weblab code are also temporarily stored in the +> [pycml](./pycml) directory. +> Most of the code from [pycml_protocol.py](./pycml/pycml_protocol.py) will have to be +> replaced by (1) changes to `fc` so that it extracts and stores _all_ the protocol +> information, and (2) a new code generation module in `fc` that can use information +> provides by weblab protocols to generate simulation code. +> (At the moment both `fc` and `pycml` read the protocol, but in the new code `fc` +> should gather all the information and then pass it to the code generation component of `fc`.) ## Installation -In order to build the package you need Cython and numpy. These can be installed with: -```sh -pip install -r requirements/setup.txt -``` - The ontologies used are in a separate module, to install this run ```sh -git submodule init -git submodule update +git submodule update --init ``` You also need to have CVODE (from Sundials) installed. If you do this with your system package @@ -52,25 +59,11 @@ export LDFLAGS="-L$HOME/anaconda3/envs/weblab/lib" ``` Because the `weblab_fc` module has Cython components, it needs to be compiled before you can use it. -Compilation is performed using Python's [`distutils`](https://docs.python.org/3/library/distutils.html) and [`setuptools`](https://setuptools.readthedocs.io/en/latest/), and happens automatically when you install the package using `setup.py`. +Compilation is performed using Python's [`setuptools`](https://setuptools.readthedocs.io/en/latest/), and happens automatically when you install the package. For developers, this can be done using: ```sh -pip install -e .[dev,test] +pip install -e ."[dev,test]" ``` Note that you'll need to repeat this step after any changes to Cython files (e.g. `.pyx` or `.pxd` files), because these don't automatically get recompiled. - -## Full installation steps on Jonathan's Macbook (slightly out of date) - -```sh -export CONDA_ENV=weblab-fc-py36 -conda create -n $CONDA_ENV python=3.6 -conda activate $CONDA_ENV -conda install -c conda-forge sundials=4 pytables scipy numpy numexpr -pip install -r requirements/setup.txt -./requirements/weblab_cg.sh -export CFLAGS="-I/anaconda3/envs/$CONDA_ENV/include" -export LDFLAGS="-L/anaconda3/envs/$CONDA_ENV/lib" -pip install -e .[dev,test] -``` From 2b4ea0be874dff1d822dcf863ad5abe4cca6c78d Mon Sep 17 00:00:00 2001 From: Kwabena Amponsah Date: Wed, 18 Mar 2026 12:28:27 +0000 Subject: [PATCH 02/28] #236 Add pyproject.toml --- pyproject.toml | 3 +++ requirements/setup.txt | 3 --- 2 files changed, 3 insertions(+), 3 deletions(-) create mode 100644 pyproject.toml delete mode 100644 requirements/setup.txt diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 00000000..5c5771b3 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,3 @@ +[build-system] +requires = ["setuptools", "wheel", "numpy>=1,<2", "cython>=0,<3"] +build-backend = "setuptools.build_meta" diff --git a/requirements/setup.txt b/requirements/setup.txt deleted file mode 100644 index 04d2d1a1..00000000 --- a/requirements/setup.txt +++ /dev/null @@ -1,3 +0,0 @@ -# These packages are required in order to run setup.py, and hence install this project -cython -numpy From 6aa68a77bdca4fc71fc3ba977120f4cc1c18a20d Mon Sep 17 00:00:00 2001 From: Kwabena Amponsah Date: Wed, 18 Mar 2026 13:52:29 +0000 Subject: [PATCH 03/28] #236 Update CI --- .github/workflows/coverage.yml | 11 ++++----- .github/workflows/style.yml | 8 +++--- README.md | 2 +- pyproject.toml | 45 +++++++++++++++++++++++++++++++++- setup.cfg | 15 ------------ setup.py | 26 ++------------------ 6 files changed, 56 insertions(+), 51 deletions(-) diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index 9ddb9e8d..0c840f95 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -2,18 +2,18 @@ name: Unit test and coverage on: [pull_request] jobs: coverage: - runs-on: ubuntu-16.04 + runs-on: ubuntu-24.04 steps: - name: Check out repository - uses: actions/checkout@v2 + uses: actions/checkout@v6 with: submodules: recursive - name: Set up Python - uses: actions/setup-python@v2 + uses: actions/setup-python@v6 with: - python-version: 3.6 + python-version: 3.10 - name: Install Sundials run: | @@ -22,7 +22,6 @@ jobs: - name: Install Python dependencies run: | python -m pip install --upgrade pip setuptools wheel - python -m pip install -r requirements/setup.txt - name: Build and install run: | @@ -33,6 +32,6 @@ jobs: python -m pytest --cov --cov-config=.coveragerc - name: Submit report to codecov - uses: codecov/codecov-action@v1 + uses: codecov/codecov-action@v5 if: success() diff --git a/.github/workflows/style.yml b/.github/workflows/style.yml index 6e160633..91da084b 100644 --- a/.github/workflows/style.yml +++ b/.github/workflows/style.yml @@ -2,16 +2,16 @@ name: Style on: [pull_request] jobs: style: - runs-on: ubuntu-16.04 + runs-on: ubuntu-24.04 steps: - name: Check out repository - uses: actions/checkout@v2 + uses: actions/checkout@v6 - name: Set up Python - uses: actions/setup-python@v2 + uses: actions/setup-python@v6 with: - python-version: 3.6 + python-version: 3.10 - name: Install tools run: | diff --git a/README.md b/README.md index 91e45571..33f9e138 100644 --- a/README.md +++ b/README.md @@ -63,7 +63,7 @@ Compilation is performed using Python's [`setuptools`](https://setuptools.readth For developers, this can be done using: ```sh -pip install -e ."[dev,test]" +python3 -m pip install -e ."[dev,test]" ``` Note that you'll need to repeat this step after any changes to Cython files (e.g. `.pyx` or `.pxd` files), because these don't automatically get recompiled. diff --git a/pyproject.toml b/pyproject.toml index 5c5771b3..bb59d4ca 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,3 +1,46 @@ [build-system] -requires = ["setuptools", "wheel", "numpy>=1,<2", "cython>=0,<3"] +requires = ["numpy>=1,<2", "setuptools", "wheel", "cython>=0,<3"] build-backend = "setuptools.build_meta" + +[project] +name = "fc" +version = "0.1.0" +requires-python = ">= 3.6" +dependencies = [ + "cellmlmanip", + "cython", + "Jinja2>=2.10", + "matplotlib", + "numexpr", + "numpy>=1,<2", + "pyparsing!=2.4.2", + "scipy", + "tables", +] + +[project.optional-dependencies] +dev = [ + "pytest-xdist[psutil]", # "line_profiler", +] + +test = [ + "codecov", + "flake8>=3.6", + "pytest>=3.6", + "pytest-cov", + "pytest-profiling", +] + +[tool.pytest] +testpaths = ["test"] +norecursedirs = ["data"] + +[tool.isort] +force_single_line = "False" +multi_line_output = 5 +# ^ Hanging grid grouped +line_length = 120 +known_first_party = "fc" +default_section = "THIRDPARTY" +order_by_type = "False" +not_skip = "__init__.py" diff --git a/setup.cfg b/setup.cfg index a2b82ef8..31803736 100644 --- a/setup.cfg +++ b/setup.cfg @@ -10,11 +10,6 @@ ignore = W503 # break before binary operator - allow either style W504 # break after binary operator - allow either style -[tool:pytest] -testpaths = test -norecursedirs = - data - python_files = Test*.py test_*.py @@ -24,13 +19,3 @@ addopts = -ra --strict --tb=short - -[isort] -force_single_line = False -multi_line_output = 5 -# ^ Hanging grid grouped -line_length = 120 -known_first_party = fc -default_section = THIRDPARTY -order_by_type = False -not_skip = __init__.py diff --git a/setup.py b/setup.py index b780e985..ca2d6d99 100644 --- a/setup.py +++ b/setup.py @@ -6,7 +6,9 @@ If SUNDIALS is installed in a non-standard location, it requires environment variables (CFLAGS and LDFLAGS) to have been set up before running. """ +print("--------DEBUG: 00--------") import numpy +print("--------DEBUG: 10--------") from setuptools import find_packages, setup # Must come before Cython! from cython import inline @@ -75,30 +77,6 @@ ], cmdclass={'build_ext': build_ext}, ext_modules=ext_modules, - install_requires=[ - 'cellmlmanip', - 'cython', - 'Jinja2>=2.10', - 'matplotlib', - 'numexpr', - 'numpy', - 'pyparsing!=2.4.2', - 'scipy', - 'tables', - ], - extras_require={ - 'dev': [ - # 'line_profiler', - 'pytest-xdist[psutil]', - ], - 'test': [ - 'codecov', - 'flake8>=3.6', - 'pytest>=3.6', - 'pytest-cov', - 'pytest-profiling', - ], - }, entry_points={ 'console_scripts': [ 'fc_run = fc.cli:run_protocol', From 15f926fd0eddaac16f267ad61db42ac0a2310b85 Mon Sep 17 00:00:00 2001 From: Kwabena Amponsah Date: Wed, 18 Mar 2026 13:54:27 +0000 Subject: [PATCH 04/28] #236 Set Python 3.10 on CI --- .github/workflows/coverage.yml | 2 +- .github/workflows/style.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index 0c840f95..19044361 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -13,7 +13,7 @@ jobs: - name: Set up Python uses: actions/setup-python@v6 with: - python-version: 3.10 + python-version: "3.10" - name: Install Sundials run: | diff --git a/.github/workflows/style.yml b/.github/workflows/style.yml index 91da084b..8f0a2cb3 100644 --- a/.github/workflows/style.yml +++ b/.github/workflows/style.yml @@ -11,7 +11,7 @@ jobs: - name: Set up Python uses: actions/setup-python@v6 with: - python-version: 3.10 + python-version: "3.10" - name: Install tools run: | From f691aea02576e561b3b11426fb0de55afe4787e4 Mon Sep 17 00:00:00 2001 From: Kwabena Amponsah Date: Wed, 18 Mar 2026 14:00:58 +0000 Subject: [PATCH 05/28] #236 Fix sundials install on CI --- .github/workflows/coverage.yml | 9 ++++----- .github/workflows/style.yml | 11 +++++------ 2 files changed, 9 insertions(+), 11 deletions(-) diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index 19044361..0419e5c6 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -17,21 +17,20 @@ jobs: - name: Install Sundials run: | - sudo apt-get install libsundials-serial-dev + sudo apt-get install libsundials-dev - name: Install Python dependencies run: | - python -m pip install --upgrade pip setuptools wheel + python3 -m pip install --upgrade pip - name: Build and install run: | - python -m pip install -e .[test] + python3 -m pip install -e .[test] - name: Unit & coverage tests run: | - python -m pytest --cov --cov-config=.coveragerc + python3 -m pytest --cov --cov-config=.coveragerc - name: Submit report to codecov uses: codecov/codecov-action@v5 if: success() - diff --git a/.github/workflows/style.yml b/.github/workflows/style.yml index 8f0a2cb3..b9eaa8a6 100644 --- a/.github/workflows/style.yml +++ b/.github/workflows/style.yml @@ -15,12 +15,11 @@ jobs: - name: Install tools run: | - python -m pip install --upgrade pip setuptools wheel - python -m pip install flake8 + python3 -m pip install --upgrade pip + python3 -m pip install flake8 - name: Check code style run: | - python --version - python -m flake8 --version - python -m flake8 - + python3 --version + python3 -m flake8 --version + python3 -m flake8 From 14771dcd2648549cd25dde63d6f8bd9dbd8c920d Mon Sep 17 00:00:00 2001 From: Kwabena Amponsah Date: Wed, 18 Mar 2026 14:15:53 +0000 Subject: [PATCH 06/28] #236 Fix style errors --- fc/__init__.py | 5 +++-- fc/environment.py | 2 +- fc/parsing/actions.py | 8 ++++---- fc/protocol.py | 22 +++++++++++----------- setup.cfg | 13 ++++++++++--- 5 files changed, 29 insertions(+), 21 deletions(-) diff --git a/fc/__init__.py b/fc/__init__.py index f23984aa..3e93a676 100644 --- a/fc/__init__.py +++ b/fc/__init__.py @@ -15,8 +15,9 @@ finally: # Always manually delete frame # https://docs.python.org/2/library/inspect.html#the-interpreter-stack - del(frame) -del(os, inspect) + del frame +del os +del inspect from .protocol import Protocol # noqa:F401,E402 diff --git a/fc/environment.py b/fc/environment.py index f97264da..0f373502 100644 --- a/fc/environment.py +++ b/fc/environment.py @@ -20,7 +20,7 @@ class Environment(object): Variables not found within the environment are looked up in its "default delegatee". For more information, see - https://chaste.cs.ox.ac.uk/trac/wiki/FunctionalCuration/ProtocolSyntax#Identifiersandnameresolution + https://github.com/Chaste/trac_archive/wiki/Functional-Curation-_-Protocol-Syntax#Identifiersandnameresolution """ next_ident = [0] diff --git a/fc/parsing/actions.py b/fc/parsing/actions.py index bdc50008..3db7d05a 100644 --- a/fc/parsing/actions.py +++ b/fc/parsing/actions.py @@ -178,7 +178,7 @@ def __init__(self, s, loc, tokens): def __eq__(self, other): """Comparison of these parse results to another instance or a list.""" - if type(other) == type(self): + if isinstance(other, type(self)): return self.tokens == other.tokens elif isinstance(other, list): return self.tokens == other @@ -1176,8 +1176,8 @@ def merge(self, pvar): class ModelInterface(BaseGroupAction): """Parse action for the model interface section of a protocol. - See https://chaste.cs.ox.ac.uk/trac/wiki/FunctionalCuration/ProtocolSyntax#Modelinterface for more on the syntax - and semantics of the model interface. + See https://github.com/Chaste/trac_archive/wiki/Functional-Curation-_-Protocol-Syntax#Modelinterface for more on + the syntax and semantics of the model interface. Includes helper methods for merging model interfaces, e.g. when one protocol imports another. @@ -1346,7 +1346,7 @@ def modify_model(self, model, time_variable, units): self.units = units # Time variable may be replaced, so delete this reference just to be safe - del(time_variable) + del time_variable # Annotate all state variables with the magic `oxmeta:state_variable` term. This is done before unit conversion # so that annotations are transferred where needed. The original order in which state variables were defined is diff --git a/fc/protocol.py b/fc/protocol.py index 56cac9ff..8cdc3d55 100644 --- a/fc/protocol.py +++ b/fc/protocol.py @@ -128,17 +128,17 @@ def __init__(self, proto_file, indent_level=0): # # 1. The ``documentation`` section. - # https://chaste.cs.ox.ac.uk/trac/wiki/FunctionalCuration/ProtocolSyntax#Documentation + # https://github.com/Chaste/trac_archive/wiki/Functional-Curation-_-Protocol-Syntax#Documentation # This is currently not stored in this object. # 2. Namespace bindings (no section, just a list of statements) - # https://chaste.cs.ox.ac.uk/trac/wiki/FunctionalCuration/ProtocolSyntax#Namespacebindings + # https://github.com/Chaste/trac_archive/wiki/Functional-Curation-_-Protocol-Syntax#Namespacebindings # Maps namespaces (prefixes) to URIs. self.ns_map = {} # 3. Parsed results from the ``inputs`` section. - # https://chaste.cs.ox.ac.uk/trac/wiki/FunctionalCuration/ProtocolSyntax#Protocolinputdeclarations + # https://github.com/Chaste/trac_archive/wiki/Functional-Curation-_-Protocol-Syntax#Protocolinputdeclarations # This contains inputs _to the protocol_, that can be used when this # protocol is used by another protocol. @@ -147,12 +147,12 @@ def __init__(self, proto_file, indent_level=0): self.inputs = [] # 4. Any number of ``import`` statements (again, no section) - # https://chaste.cs.ox.ac.uk/trac/wiki/FunctionalCuration/ProtocolSyntax#Importsofotherprotocols + # https://github.com/Chaste/trac_archive/wiki/Functional-Curation-_-Protocol-Syntax#Importsofotherprotocols # Maps an import 'name' prefix to a :class:`Protocol` instance. self.imports = {} # 5. The ``library`` section - # https://chaste.cs.ox.ac.uk/trac/wiki/FunctionalCuration/ProtocolSyntax#Library + # https://github.com/Chaste/trac_archive/wiki/Functional-Curation-_-Protocol-Syntax#Library # Can contain assignment statements (``var = expr``), function # assignment statements (``var = lambda(...)``), or assertions # (``assert cond``). @@ -160,7 +160,7 @@ def __init__(self, proto_file, indent_level=0): self.library = [] # 6. The ``units`` section - # https://chaste.cs.ox.ac.uk/trac/wiki/FunctionalCuration/ProtocolSyntax#Physicalunitdefinitions + # https://github.com/Chaste/trac_archive/wiki/Functional-Curation-_-Protocol-Syntax#Physicalunitdefinitions # We store the definitions as well as the resolved units to allow for merging definitions from # imported protocols or nested protocols without the need to reconcile unit registries and exact # unit names. @@ -173,20 +173,20 @@ def __init__(self, proto_file, indent_level=0): # 8. The ``tasks`` section, which contains any number of simulation # tasks (possibly with nested ones). - # https://chaste.cs.ox.ac.uk/trac/wiki/FunctionalCuration/ProtocolSyntax#Simulationtasks + # https://github.com/Chaste/trac_archive/wiki/Functional-Curation-_-Protocol-Syntax#Simulationtasks # Contains instances of :class:`fc.simulations.simulations.AbstractSimulation` subclasses. self.simulations = [] # 9. The ``post-processing`` section, that contains post-processing # code - # https://chaste.cs.ox.ac.uk/trac/wiki/FunctionalCuration/ProtocolSyntax#Post-processing + # https://github.com/Chaste/trac_archive/wiki/Functional-Curation-_-Protocol-Syntax#Post-processing # Contains statement instances, as with the library. self.post_processing = [] # 10. The ``outputs`` section, listing outputs from the simulations or # from post-processing, that can be used in the ``plots`` section or by # other protocols. - # https://chaste.cs.ox.ac.uk/trac/wiki/FunctionalCuration/ProtocolSyntax#Protocoloutputs + # https://github.com/Chaste/trac_archive/wiki/Functional-Curation-_-Protocol-Syntax#Protocoloutputs # A list of dictionaries, where each dict specifies a protocol output. They can have keys: # - name: name to give the output; should be a valid simple identifier. @@ -199,7 +199,7 @@ def __init__(self, proto_file, indent_level=0): self.outputs = [] # 11. The ``plots`` section - # https://chaste.cs.ox.ac.uk/trac/wiki/FunctionalCuration/ProtocolSyntax#Graphicalplots + # https://github.com/Chaste/trac_archive/wiki/Functional-Curation-_-Protocol-Syntax#Graphicalplots # A list of dictionaries with keys: # - title: title for the plot. # - x: name of the x-variable; should be a protocol output. @@ -226,7 +226,7 @@ def _parse(self): with actions.set_reference_source(self.proto_file): details = generator.expr() assert isinstance(details, dict) - del(generator) + del generator # Store protocol inputs self.inputs = details.get('inputs', []) diff --git a/setup.cfg b/setup.cfg index 31803736..5a78a4e6 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,20 +1,27 @@ [flake8] max-line-length = 120 + exclude = */data/* pycml testoutput venv + .venv + ignore = - W391 # allow empty line at end of file - W503 # break before binary operator - allow either style - W504 # break after binary operator - allow either style + # allow empty line at end of file + W391, + # break before binary operator - allow either style + W503, + # break after binary operator - allow either style + W504 python_files = Test*.py test_*.py *_test.py tests.py + addopts = -ra --strict From 5c543bdfc0971c49c92e854cdd94466c1565dd9d Mon Sep 17 00:00:00 2001 From: Kwabena Amponsah Date: Wed, 18 Mar 2026 14:19:04 +0000 Subject: [PATCH 07/28] #236 add apt update to CI --- .github/workflows/coverage.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index 0419e5c6..d1a2f997 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -17,6 +17,7 @@ jobs: - name: Install Sundials run: | + sudo apt-get update sudo apt-get install libsundials-dev - name: Install Python dependencies From f51688f99d92d5f4819353c0749352cbe5ace70f Mon Sep 17 00:00:00 2001 From: Kwabena Amponsah Date: Wed, 18 Mar 2026 18:36:45 +0000 Subject: [PATCH 08/28] #236 Update Cython build config --- .gitignore | 1 + pyproject.toml | 37 +++++++++++++++++++++-- setup.cfg | 2 ++ setup.py | 80 +++++++++++++------------------------------------- 4 files changed, 57 insertions(+), 63 deletions(-) diff --git a/.gitignore b/.gitignore index fc530316..eb0a7e2a 100644 --- a/.gitignore +++ b/.gitignore @@ -32,6 +32,7 @@ develop-eggs lib lib64 venv*/ +.venv pyvenv*/ # Installer logs diff --git a/pyproject.toml b/pyproject.toml index bb59d4ca..d7b06145 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,20 +1,40 @@ [build-system] -requires = ["numpy>=1,<2", "setuptools", "wheel", "cython>=0,<3"] +requires = ["setuptools", "wheel", "cython", "numpy<2"] build-backend = "setuptools.build_meta" [project] +description = "Functional Curation backend for the Modelling Web Lab" name = "fc" version = "0.1.0" requires-python = ">= 3.6" +readme = "README.md" +license = "BSD-3-Clause" + +classifiers = [ + "Development Status :: 4 - Beta", + "Environment :: Console", + "Intended Audience :: Developers", + "Intended Audience :: Science/Research", + "Operating System :: POSIX", + "Operating System :: MacOS", + "Programming Language :: Python :: 3", + "Topic :: Scientific/Engineering", +] + +maintainers = [ + { name = "WebLab Team", email = "kwabena.amponsah1@nottingham.ac.uk" }, +] + dependencies = [ "cellmlmanip", "cython", "Jinja2>=2.10", "matplotlib", "numexpr", - "numpy>=1,<2", - "pyparsing!=2.4.2", + "numpy<2", + "pyparsing>=2.5, <3.4", "scipy", + "setuptools", "tables", ] @@ -31,6 +51,17 @@ test = [ "pytest-profiling", ] +[project.urls] +Repository = "https://github.com/ModellingWebLab/weblab-fc" + +[project.scripts] +fc_run = "fc.cli:run_protocol" +fc_extract_outputs = "fc.cli:extract_outputs" +fc_check_syntax = "fc.cli:check_syntax" + +[tool.setuptools] +package-dir = { "" = "fc" } + [tool.pytest] testpaths = ["test"] norecursedirs = ["data"] diff --git a/setup.cfg b/setup.cfg index 5a78a4e6..dd765481 100644 --- a/setup.cfg +++ b/setup.cfg @@ -3,6 +3,8 @@ max-line-length = 120 exclude = */data/* + *.egg-info/* + build pycml testoutput venv diff --git a/setup.py b/setup.py index ca2d6d99..5d47744d 100644 --- a/setup.py +++ b/setup.py @@ -1,26 +1,19 @@ - """ -Test distutils setup file for the Python implementation of Functional Curation. +Setup for the Python implementation of Functional Curation. -At present, this just exists to allow us to build our Cython SUNDIALS wrapper. -If SUNDIALS is installed in a non-standard location, it requires environment variables -(CFLAGS and LDFLAGS) to have been set up before running. +This builds our Cython SUNDIALS wrapper. If SUNDIALS is installed in a +non-standard location, it requires environment variables (CFLAGS and LDFLAGS) +to have been set up before running. """ -print("--------DEBUG: 00--------") +from setuptools import Extension, setup # Must come before Cython! import numpy -print("--------DEBUG: 10--------") - -from setuptools import find_packages, setup # Must come before Cython! +from Cython.Build import cythonize from cython import inline -from Cython.Distutils import build_ext -from Cython.Distutils.extension import Extension # Detect major sundials version (defaults to 2) -sundials_major = inline(''' - cdef extern from *: +fc_sundials_major = inline(''' + cdef extern from "": """ - #include - #ifndef SUNDIALS_VERSION_MAJOR #define SUNDIALS_VERSION_MAJOR 2 #endif @@ -29,59 +22,26 @@ return SUNDIALS_VERSION_MAJOR ''') -print('Building for Sundials ' + str(sundials_major) + '.x') +print("Building for Sundials " + str(fc_sundials_major) + ".x") # Define Cython modules -ext_modules = [ - Extension('fc.sundials.sundials', - sources=['fc/sundials/sundials.pxd'], - include_dirs=['.', numpy.get_include()], - libraries=['sundials_cvode', 'sundials_nvecserial'], - cython_compile_time_env={'FC_SUNDIALS_MAJOR': sundials_major}, +extensions = [ + Extension(name="fc.sundials.sundials", + sources=["fc/sundials/sundials.pxd"], + include_dirs=[".", numpy.get_include()], + libraries=["sundials_cvode", "sundials_nvecserial"], ), - Extension('fc.sundials.solver', - sources=['fc/sundials/solver.pyx'], - include_dirs=['.', numpy.get_include()], - libraries=['sundials_cvode', 'sundials_nvecserial'], - cython_compile_time_env={'FC_SUNDIALS_MAJOR': sundials_major}, + Extension(name="fc.sundials.solver", + sources=["fc/sundials/solver.pyx"], + include_dirs=[".", numpy.get_include()], + libraries=["sundials_cvode", "sundials_nvecserial"], ), ] -# Load readme for use as long description -with open('README.md') as f: - readme = f.read() - # Setup setup( - name='fc', - version='0.1.0', - description='Functional Curation backend for the Modelling Web Lab', - long_description=readme, - license='BSD', - maintainer='Web Lab team', - maintainer_email='j.p.cooper@ucl.ac.uk', - url='https://github.com/ModellingWebLab/weblab-fc', - packages=find_packages(exclude=['test', 'test.*']), + name="fc", include_package_data=True, # Include non-python files via MANIFEST.in zip_safe=False, - classifiers=[ - 'Development Status :: 4 - Beta', - 'Environment :: Console', - 'Intended Audience :: Developers', - 'Intended Audience :: Science/Research', - 'License :: OSI Approved :: BSD License', - 'Operating System :: POSIX', - 'Operating System :: MacOS', - 'Programming Language :: Python :: 3', - 'Topic :: Scientific/Engineering', - ], - cmdclass={'build_ext': build_ext}, - ext_modules=ext_modules, - entry_points={ - 'console_scripts': [ - 'fc_run = fc.cli:run_protocol', - 'fc_extract_outputs = fc.cli:extract_outputs', - 'fc_check_syntax = fc.cli:check_syntax', - ], - }, + ext_modules=cythonize(extensions, compile_time_env={'FC_SUNDIALS_MAJOR': fc_sundials_major},), ) From dae66c12eaaefbf453400efe6b6d6bdadf94b870 Mon Sep 17 00:00:00 2001 From: Kwabena Amponsah Date: Wed, 18 Mar 2026 18:45:05 +0000 Subject: [PATCH 09/28] #236 Fix print statements --- fc/environment.py | 6 +++--- fc/sundials/solver.pyx | 4 ++-- fc/templates/weblab_model.pyx | 2 +- test/output/code_generation/weblab_model.pyx | 2 +- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/fc/environment.py b/fc/environment.py index 0f373502..587c2ee3 100644 --- a/fc/environment.py +++ b/fc/environment.py @@ -78,14 +78,14 @@ def look_up(self, name): # try: # return self.bindings[name] # except KeyError: -# print 'Key error looking up', name, 'in', self +# print('Key error looking up', name, 'in', self) # import sys # tb = sys.exc_info()[2] # while tb: # local_vars = tb.tb_frame.f_locals # obj = local_vars.get('self', None) # if obj and isinstance(obj, DelegatingDict): -# print 'Looked for', local_vars['key'], 'in', obj._env +# print('Looked for', local_vars['key'], 'in', obj._env) # tb = tb.tb_next # self.debug_delegatees('root') # raise @@ -101,7 +101,7 @@ def set_delegatee_env(self, delegatee, prefix=""): "The name prefix '" + prefix + "' has already been used in this context. Check your simulations, imports, etc.") self.delegatees[prefix] = delegatee -# print 'Delegating to', delegatee, 'for', prefix, 'in', self +# print('Delegating to', delegatee, 'for', prefix, 'in', self) self.bindings.set_delegatee(delegatee.bindings, prefix) self.unwrapped_bindings.set_delegatee(delegatee.unwrapped_bindings, prefix) diff --git a/fc/sundials/solver.pyx b/fc/sundials/solver.pyx index d491c2f4..c8c06203 100644 --- a/fc/sundials/solver.pyx +++ b/fc/sundials/solver.pyx @@ -16,7 +16,7 @@ assert sizeof(np.float64_t) == sizeof(_lib.realtype) # paranoia # # Debugging! # import sys # def fprint(*args): -# print ' '.join(map(str, args)) +# print(' '.join(map(str, args))) # sys.stdout.flush() @@ -45,7 +45,7 @@ cdef int _rhs_wrapper(realtype t, N_Vector y, N_Vector ydot, void* user_data): try: model.evaluate_rhs(t, np_y, np_ydot) except Exception, e: - print e + print(e) return 1 # recoverable error return 0 diff --git a/fc/templates/weblab_model.pyx b/fc/templates/weblab_model.pyx index fa3f4045..358ae905 100644 --- a/fc/templates/weblab_model.pyx +++ b/fc/templates/weblab_model.pyx @@ -312,7 +312,7 @@ cdef class {{ class_name }}(CvodeSolver): """ # TODO Update this (and rest of fc) to Python3 # TODO Use logging here, or raise an exception - print >>sys.stderr, ' ' * self.indent_level, 'set_solver: Models implemented using Cython contain a built-in ODE solver, so ignoring setting.' + print(' ' * self.indent_level, 'set_solver: Models implemented using Cython contain a built-in ODE solver, so ignoring setting.', file=sys.stderr) {%- for table in data_tables %} cdef np.ndarray {{ table.table_name }} = np.array({{ table.data_code }}) diff --git a/test/output/code_generation/weblab_model.pyx b/test/output/code_generation/weblab_model.pyx index f98f1e4a..1bffead0 100644 --- a/test/output/code_generation/weblab_model.pyx +++ b/test/output/code_generation/weblab_model.pyx @@ -321,5 +321,5 @@ cdef class TestModel(CvodeSolver): """ # TODO Update this (and rest of fc) to Python3 # TODO Use logging here, or raise an exception - print >>sys.stderr, ' ' * self.indent_level, 'set_solver: Models implemented using Cython contain a built-in ODE solver, so ignoring setting.' + print(' ' * self.indent_level, 'set_solver: Models implemented using Cython contain a built-in ODE solver, so ignoring setting.', file=sys.stderr) From b55133fc1c962948f46872e87504c3e16be13d9e Mon Sep 17 00:00:00 2001 From: Kwabena Amponsah Date: Wed, 18 Mar 2026 20:12:58 +0000 Subject: [PATCH 10/28] #236 Update sundials wrapper --- fc/sundials/solver.pxd | 18 +-- fc/sundials/solver.pyx | 47 ++++--- fc/sundials/sundials.pxd | 265 ++++++++++++++++++++++++++------------- pyproject.toml | 2 +- setup.py | 37 +++--- 5 files changed, 230 insertions(+), 139 deletions(-) diff --git a/fc/sundials/solver.pxd b/fc/sundials/solver.pxd index 891fc328..5a99f7f1 100644 --- a/fc/sundials/solver.pxd +++ b/fc/sundials/solver.pxd @@ -4,10 +4,9 @@ cimport fc.sundials.sundials as _lib # Save typing ctypedef _lib.N_Vector N_Vector -ctypedef np.float64_t realtype -IF FC_SUNDIALS_MAJOR >= 3: - ctypedef _lib.SUNMatrix SUNMatrix - ctypedef _lib.SUNLinearSolver SUNLinearSolver +ctypedef _lib.sunrealtype realtype +ctypedef _lib.SUNMatrix SUNMatrix +ctypedef _lib.SUNLinearSolver SUNLinearSolver cdef class CvodeSolver: @@ -15,6 +14,7 @@ cdef class CvodeSolver: cdef N_Vector _state # The state vector of the model being simulated cdef int _state_size # The number of state variables / length of the state vector + cdef int _sundials_major cdef public np.ndarray state # Numpy view of the state vector cdef public object model # The model being simulated @@ -27,8 +27,10 @@ cdef class CvodeSolver: cdef re_init(self) cdef check_flag(self, int flag, char* called) - IF FC_SUNDIALS_MAJOR >= 3: - # Linear matrix solving in sundials 3+ - cdef SUNMatrix sundense_matrix - cdef SUNLinearSolver sundense_solver + # Linear matrix solving state for sundials 3+ + cdef SUNMatrix sundense_matrix + cdef SUNLinearSolver sundense_solver + + # Sundials context for v6+ + cdef _lib.SUNContext sunctx diff --git a/fc/sundials/solver.pyx b/fc/sundials/solver.pyx index c8c06203..f0f40a3d 100644 --- a/fc/sundials/solver.pyx +++ b/fc/sundials/solver.pyx @@ -11,7 +11,7 @@ from fc.error_handling import ProtocolError # Data type for numpy arrays np_dtype = np.float64 -assert sizeof(np.float64_t) == sizeof(_lib.realtype) # paranoia +assert sizeof(np.float64_t) == sizeof(_lib.sunrealtype) # paranoia # # Debugging! # import sys @@ -33,7 +33,7 @@ cdef object numpy_view(N_Vector v): ret = np.asarray(data_view, dtype=np_dtype) return ret -cdef int _rhs_wrapper(realtype t, N_Vector y, N_Vector ydot, void* user_data): +cdef int _rhs_wrapper(realtype t, N_Vector y, N_Vector ydot, void* user_data) noexcept: """Cython wrapper around a model RHS that uses numpy, for calling by CVODE.""" # Create numpy views on the N_Vectors @@ -58,10 +58,14 @@ cdef class CvodeSolver: self.cvode_mem = NULL self._state = NULL self._state_size = 0 + self._sundials_major = _lib.FC_SundialsMajor() + self.sunctx = NULL + self.sundense_matrix = NULL + self.sundense_solver = NULL - IF FC_SUNDIALS_MAJOR >= 3: - self.sundense_matrix = NULL - self.sundense_solver = NULL + flag = _lib.FC_SUNContext_Create(&self.sunctx) + if flag != 0: + raise ProtocolError('Error creating SUNDIALS context') def __dealloc__(self): """Free solver memory if allocated.""" @@ -69,11 +73,9 @@ cdef class CvodeSolver: _lib.CVodeFree(&self.cvode_mem) if self._state != NULL: _lib.N_VDestroy_Serial(self._state) - IF FC_SUNDIALS_MAJOR >= 3: - if self.sundense_solver != NULL: - _lib.SUNLinSolFree(self.sundense_solver) - if self.sundense_matrix != NULL: - _lib.SUNMatDestroy(self.sundense_matrix) + _lib.FC_SUNLinSolFree(self.sundense_solver) + _lib.FC_SUNMatDestroy(self.sundense_matrix) + _lib.FC_SUNContext_Free(&self.sunctx) def __init__(self): """Python level object initialisation.""" @@ -92,14 +94,11 @@ cdef class CvodeSolver: assert isinstance(model.state, np.ndarray) self.state = model.state self._state_size = len(model.state) - self._state = _lib.N_VMake_Serial( - self._state_size, (self.state).data) + self._state = _lib.FC_N_VMake_Serial( + self._state_size, (self.state).data, self.sunctx) # Create CVode object - IF FC_SUNDIALS_MAJOR >= 4: - self.cvode_mem = _lib.CVodeCreate(_lib.CV_BDF) - ELSE: - self.cvode_mem = _lib.CVodeCreate(_lib.CV_BDF, _lib.CV_NEWTON) + self.cvode_mem = _lib.FC_CVodeCreate(_lib.CV_BDF, 0, self.sunctx) # Initialise CVode if hasattr(self, 'set_rhs_wrapper'): @@ -121,24 +120,24 @@ cdef class CvodeSolver: # Create dense matrix for use in linear solves if self._state_size > 0: - IF FC_SUNDIALS_MAJOR >= 3: + if self._sundials_major >= 3: # Create dense matrix - self.sundense_matrix = _lib.SUNDenseMatrix( - self._state_size, self._state_size) + self.sundense_matrix = _lib.FC_SUNDenseMatrix( + self._state_size, self._state_size, self.sunctx) if self.sundense_matrix == NULL: raise ProtocolError('Error calling CVODE routine SUNDenseMatrix: Null returned') # Create linear solver - self.sundense_solver = _lib.SUNDenseLinearSolver(self._state, self.sundense_matrix) + self.sundense_solver = _lib.FC_SUNDenseLinearSolver(self._state, self.sundense_matrix, self.sunctx) if self.sundense_solver == NULL: raise ProtocolError('Error calling CVODE routine SUNDenseLinearSolver: Null returned') # Tell cvode to use this solver - flag = _lib.CVDlsSetLinearSolver(self.cvode_mem, self.sundense_solver, self.sundense_matrix) - self.check_flag(flag, 'CVDlsSetLinearSolver') - ELSE: + flag = _lib.FC_CVodeSetLinearSolver(self.cvode_mem, self.sundense_solver, self.sundense_matrix) + self.check_flag(flag, 'CVodeSetLinearSolver') + else: # Create dense matrix - flag = _lib.CVDense(self.cvode_mem, self._state_size) + flag = _lib.FC_CVDense(self.cvode_mem, self._state_size) self.check_flag(flag, 'CVDense') _lib.CVodeSetMaxNumSteps(self.cvode_mem, 20000000) diff --git a/fc/sundials/sundials.pxd b/fc/sundials/sundials.pxd index fe327c28..ae4ce5ef 100644 --- a/fc/sundials/sundials.pxd +++ b/fc/sundials/sundials.pxd @@ -1,15 +1,14 @@ - """ Minimal Cython interface to the (CVODE part of the) SUNDIALS library, for use by Functional Curation. -Handles both SUNDIALS 2.4 and 2.5, since the parts of the interface we use didn't change. - Based on http://code.google.com/p/python-sundials/source/browse/trunk/sundials/SundialsLib.pxd +Includes compatibility wrappers so Cython code can avoid deprecated compile-time IF directives. """ cdef extern from "sundials/sundials_types.h": ctypedef long int sunindextype - ctypedef double realtype + ctypedef double sunrealtype + ctypedef int sunbooleantype ctypedef bint booleantype cdef extern from "sundials/sundials_nvector.h": @@ -17,15 +16,27 @@ cdef extern from "sundials/sundials_nvector.h": void *content ctypedef _generic_N_Vector *N_Vector +cdef extern from *: + ctypedef struct _generic_SUNMatrix: + pass + ctypedef _generic_SUNMatrix* SUNMatrix + + ctypedef struct _generic_SUNLinearSolver: + pass + ctypedef _generic_SUNLinearSolver* SUNLinearSolver + + ctypedef struct SUNContext_: + pass + ctypedef SUNContext_* SUNContext + cdef extern from "nvector/nvector_serial.h": - cdef N_Vector N_VMake_Serial(long int vec_length, realtype *v_data) N_Vector N_VNew_Serial(long int vec_length) void N_VDestroy_Serial(N_Vector v) void N_VPrint_Serial(N_Vector v) cdef struct _N_VectorContent_Serial: long int length - realtype *data + sunrealtype *data ctypedef _N_VectorContent_Serial *N_VectorContent_Serial cdef extern from "cvode/cvode.h": @@ -65,94 +76,170 @@ cdef extern from "cvode/cvode.h": int CV_BAD_DKY int CV_TOO_CLOSE - ctypedef int (*CVRhsFn)(realtype t, N_Vector y, N_Vector ydot, void *user_data) - ctypedef int (*CVRootFn)(realtype t, N_Vector y, realtype *gout, void *user_data) - - # In version 4 Newton iteration became the default, and a new syntax was - # introduced to change it (which we don't need to use here) - IF FC_SUNDIALS_MAJOR >= 4: - void *CVodeCreate(int lmm) - ELSE: - void *CVodeCreate(int lmm, int iter) + ctypedef int (*CVRhsFn)(sunrealtype t, N_Vector y, N_Vector ydot, void *user_data) + ctypedef int (*CVRootFn)(sunrealtype t, N_Vector y, sunrealtype *gout, void *user_data) int CVodeSetUserData(void *cvode_mem, void *user_data) - int CVodeInit(void *cvode_mem, CVRhsFn f, realtype t0, N_Vector y0) - int CVodeReInit(void *cvode_mem, realtype t0, N_Vector y0) - int CVodeSStolerances(void *cvode_mem, realtype reltol, realtype abstol) + int CVodeInit(void *cvode_mem, CVRhsFn f, sunrealtype t0, N_Vector y0) + int CVodeReInit(void *cvode_mem, sunrealtype t0, N_Vector y0) + int CVodeSStolerances(void *cvode_mem, sunrealtype reltol, sunrealtype abstol) int CVodeRootInit(void *cvode_mem, int nrtfn, CVRootFn g) - -# int CVodeStep "CVode"(void *cvode_mem, realtype tout, N_Vector yout, realtype *tret, int itask) nogil - int CVode(void *cvode_mem, realtype tout, N_Vector yout, realtype *tret, int itask) - -# int CVodeSetMaxOrd(void *cvode_mem, int maxord) + int CVode(void *cvode_mem, sunrealtype tout, N_Vector yout, sunrealtype *tret, int itask) int CVodeSetMaxNumSteps(void *cvode_mem, long int mxsteps) -# int CVodeSetMaxHnilWarns(void *cvode_mem, int mxhnil) -# int CVodeSetStabLimDet(void *cvode_mem, booleantype stldet) -# int CVodeSetInitStep(void *cvode_mem, realtype hin) -# int CVodeSetMinStep(void *cvode_mem, realtype hmin) - int CVodeSetMaxStep(void *cvode_mem, realtype hmax) - int CVodeSetStopTime(void *cvode_mem, realtype tstop) + int CVodeSetMaxStep(void *cvode_mem, sunrealtype hmax) + int CVodeSetStopTime(void *cvode_mem, sunrealtype tstop) int CVodeSetMaxErrTestFails(void *cvode_mem, int maxnef) -# int CVodeSetMaxNonlinIters(void *cvode_mem, int maxcor) -# int CVodeSetMaxConvFails(void *cvode_mem, int maxncf) -# int CVodeSetNonlinConvCoef(void *cvode_mem, realtype nlscoef) -# int CVodeSetIterType(void *cvode_mem, int iter) -# int CVodeSetRootDirection(void *cvode_mem, int *rootdir) -# int CVodeSetNoInactiveRootWarn(void *cvode_mem) -# int CVodeGetDky(void *cvode_mem, realtype t, int k, N_Vector dky) -# int CVodeGetWorkSpace(void *cvode_mem, long int *lenrw, long int *leniw) -# int CVodeGetNumSteps(void *cvode_mem, long int *nsteps) -# int CVodeGetNumRhsEvals(void *cvode_mem, long int *nfevals) -# int CVodeGetNumLinSolvSetups(void *cvode_mem, long int *nlinsetups) -# int CVodeGetNumErrTestFails(void *cvode_mem, long int *netfails) -# int CVodeGetLastOrder(void *cvode_mem, int *qlast) -# int CVodeGetCurrentOrder(void *cvode_mem, int *qcur) -# int CVodeGetNumStabLimOrderReds(void *cvode_mem, long int *nslred) -# int CVodeGetActualInitStep(void *cvode_mem, realtype *hinused) -# int CVodeGetLastStep(void *cvode_mem, realtype *hlast) -# int CVodeGetCurrentStep(void *cvode_mem, realtype *hcur) -# int CVodeGetCurrentTime(void *cvode_mem, realtype *tcur) -# int CVodeGetTolScaleFactor(void *cvode_mem, realtype *tolsfac) -# int CVodeGetErrWeights(void *cvode_mem, N_Vector eweight) -# int CVodeGetEstLocalErrors(void *cvode_mem, N_Vector ele) -# int CVodeGetNumGEvals(void *cvode_mem, long int *ngevals) -# int CVodeGetRootInfo(void *cvode_mem, int *rootsfound) -# int CVodeGetIntegratorStats(void *cvode_mem, long int *nsteps, -# long int *nfevals, long int *nlinsetups, -# long int *netfails, int *qlast, -# int *qcur, realtype *hinused, realtype *hlast, -# realtype *hcur, realtype *tcur) -# int CVodeGetNumNonlinSolvIters(void *cvode_mem, long int *nniters) -# int CVodeGetNumNonlinSolvConvFails(void *cvode_mem, long int *nncfails) -# int CVodeGetNonlinSolvStats(void *cvode_mem, long int *nniters, long int *nncfails) -# int CVDlsGetNumJacEvals(void *cvode_mem, long int *njevals) -# int CVDlsGetNumRhsEvals(void *cvode_mem, long int *nrevalsLS) - char *CVodeGetReturnFlagName(int flag) void CVodeFree(void **cvode_mem) -IF FC_SUNDIALS_MAJOR >= 3: - cdef extern from "sundials/sundials_matrix.h": - ctypedef struct _generic_SUNMatrix: - pass - ctypedef _generic_SUNMatrix* SUNMatrix - void SUNMatDestroy(SUNMatrix A) - - cdef extern from "sunmatrix/sunmatrix_dense.h": - SUNMatrix SUNDenseMatrix(sunindextype M, sunindextype N) - - cdef extern from "sunlinsol/sunlinsol_dense.h": - ctypedef struct _generic_SUNLinearSolver: - pass - ctypedef _generic_SUNLinearSolver* SUNLinearSolver - void SUNLinSolFree(SUNLinearSolver) - - cdef extern from "sundials/sundials_linearsolver.h": - SUNLinearSolver SUNDenseLinearSolver(N_Vector y, SUNMatrix A) - - cdef extern from "cvode/cvode_direct.h": - int CVDlsSetLinearSolver(void* cvode_mem, SUNLinearSolver LS, SUNMatrix A) -ELSE: - cdef extern from "cvode/cvode_dense.h": - int CVDense(void *cvode_mem, int N) - +cdef extern from *: + """ + #include + #include + #include + #include + #include + + #if SUNDIALS_VERSION_MAJOR >= 3 + #include + #include + #include + #include + #else + #include + typedef struct _generic_SUNMatrix* SUNMatrix; + typedef struct _generic_SUNLinearSolver* SUNLinearSolver; + #endif + + #if SUNDIALS_VERSION_MAJOR >= 6 + #include + #else + typedef struct SUNContext_* SUNContext; + #define SUN_COMM_NULL 0 + #endif + + static int FC_SundialsMajor(void) + { + return SUNDIALS_VERSION_MAJOR; + } + + static int FC_SUNContext_Create(SUNContext* sunctx_out) + { + #if SUNDIALS_VERSION_MAJOR >= 6 + return SUNContext_Create(SUN_COMM_NULL, sunctx_out); + #else + *sunctx_out = NULL; + return 0; + #endif + } + + static int FC_SUNContext_Free(SUNContext* ctx) + { + #if SUNDIALS_VERSION_MAJOR >= 6 + return SUNContext_Free(ctx); + #else + *ctx = NULL; + return 0; + #endif + } + + static N_Vector FC_N_VMake_Serial(sunindextype vec_length, sunrealtype* v_data, SUNContext sunctx) + { + #if SUNDIALS_VERSION_MAJOR >= 6 + return N_VMake_Serial(vec_length, v_data, sunctx); + #else + (void)sunctx; + return N_VMake_Serial((long int)vec_length, v_data); + #endif + } + + static void* FC_CVodeCreate(int lmm, int iter, SUNContext sunctx) + { + #if SUNDIALS_VERSION_MAJOR >= 6 + (void)iter; + return CVodeCreate(lmm, sunctx); + #elif SUNDIALS_VERSION_MAJOR >= 4 + (void)iter; + (void)sunctx; + return CVodeCreate(lmm); + #else + (void)sunctx; + return CVodeCreate(lmm, iter); + #endif + } + + static SUNMatrix FC_SUNDenseMatrix(sunindextype m, sunindextype n, SUNContext sunctx) + { + #if SUNDIALS_VERSION_MAJOR >= 6 + return SUNDenseMatrix(m, n, sunctx); + #elif SUNDIALS_VERSION_MAJOR >= 3 + (void)sunctx; + return SUNDenseMatrix(m, n); + #else + (void)m; (void)n; (void)sunctx; + return NULL; + #endif + } + + static SUNLinearSolver FC_SUNDenseLinearSolver(N_Vector y, SUNMatrix a, SUNContext sunctx) + { + #if SUNDIALS_VERSION_MAJOR >= 6 + return SUNLinSol_Dense(y, a, sunctx); + #elif SUNDIALS_VERSION_MAJOR >= 3 + (void)sunctx; + return SUNDenseLinearSolver(y, a); + #else + (void)y; (void)a; (void)sunctx; + return NULL; + #endif + } + + static int FC_CVodeSetLinearSolver(void* cvode_mem, SUNLinearSolver ls, SUNMatrix a) + { + #if SUNDIALS_VERSION_MAJOR >= 3 + return CVodeSetLinearSolver(cvode_mem, ls, a); + #else + (void)cvode_mem; (void)ls; (void)a; + return CV_SUCCESS; + #endif + } + + static int FC_CVDense(void* cvode_mem, int n) + { + #if SUNDIALS_VERSION_MAJOR >= 3 + (void)cvode_mem; (void)n; + return CV_SUCCESS; + #else + return CVDense(cvode_mem, n); + #endif + } + + static void FC_SUNMatDestroy(SUNMatrix a) + { + #if SUNDIALS_VERSION_MAJOR >= 3 + if (a != NULL) SUNMatDestroy(a); + #else + (void)a; + #endif + } + + static void FC_SUNLinSolFree(SUNLinearSolver ls) + { + #if SUNDIALS_VERSION_MAJOR >= 3 + if (ls != NULL) SUNLinSolFree(ls); + #else + (void)ls; + #endif + } + """ + int FC_SundialsMajor() + int FC_SUNContext_Create(SUNContext* sunctx_out) + int FC_SUNContext_Free(SUNContext* ctx) + N_Vector FC_N_VMake_Serial(sunindextype vec_length, sunrealtype* v_data, SUNContext sunctx) + void* FC_CVodeCreate(int lmm, int iter, SUNContext sunctx) + SUNMatrix FC_SUNDenseMatrix(sunindextype m, sunindextype n, SUNContext sunctx) + SUNLinearSolver FC_SUNDenseLinearSolver(N_Vector y, SUNMatrix a, SUNContext sunctx) + int FC_CVodeSetLinearSolver(void* cvode_mem, SUNLinearSolver ls, SUNMatrix a) + int FC_CVDense(void* cvode_mem, int n) + void FC_SUNMatDestroy(SUNMatrix a) + void FC_SUNLinSolFree(SUNLinearSolver ls) \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml index d7b06145..e8b3987c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -60,7 +60,7 @@ fc_extract_outputs = "fc.cli:extract_outputs" fc_check_syntax = "fc.cli:check_syntax" [tool.setuptools] -package-dir = { "" = "fc" } +package-dir = { "" = "." } [tool.pytest] testpaths = ["test"] diff --git a/setup.py b/setup.py index 5d47744d..48cf3dd9 100644 --- a/setup.py +++ b/setup.py @@ -1,17 +1,20 @@ """ Setup for the Python implementation of Functional Curation. -This builds our Cython SUNDIALS wrapper. If SUNDIALS is installed in a -non-standard location, it requires environment variables (CFLAGS and LDFLAGS) +This builds our Cython SUNDIALS wrapper. If SUNDIALS is installed in a +non-standard location, it requires environment variables (CFLAGS and LDFLAGS) to have been set up before running. """ -from setuptools import Extension, setup # Must come before Cython! + +from setuptools import Extension, setup # Must come before Cython! + import numpy -from Cython.Build import cythonize from cython import inline +from Cython.Build import cythonize # Detect major sundials version (defaults to 2) -fc_sundials_major = inline(''' +fc_sundials_major = inline( + ''' cdef extern from "": """ #ifndef SUNDIALS_VERSION_MAJOR @@ -21,21 +24,18 @@ int SUNDIALS_VERSION_MAJOR return SUNDIALS_VERSION_MAJOR - ''') + ''' +) print("Building for Sundials " + str(fc_sundials_major) + ".x") # Define Cython modules extensions = [ - Extension(name="fc.sundials.sundials", - sources=["fc/sundials/sundials.pxd"], - include_dirs=[".", numpy.get_include()], - libraries=["sundials_cvode", "sundials_nvecserial"], - ), - Extension(name="fc.sundials.solver", - sources=["fc/sundials/solver.pyx"], - include_dirs=[".", numpy.get_include()], - libraries=["sundials_cvode", "sundials_nvecserial"], - ), + Extension( + name="fc.sundials.solver", + sources=["fc/sundials/solver.pyx"], + include_dirs=[".", numpy.get_include()], + libraries=["sundials_cvode", "sundials_nvecserial"], + ), ] # Setup @@ -43,5 +43,8 @@ name="fc", include_package_data=True, # Include non-python files via MANIFEST.in zip_safe=False, - ext_modules=cythonize(extensions, compile_time_env={'FC_SUNDIALS_MAJOR': fc_sundials_major},), + ext_modules=cythonize( + extensions, + compile_time_env={"FC_SUNDIALS_MAJOR": fc_sundials_major}, + ), ) From 9f7e8a9d5005f362d63b7e22548895e4fff26fc8 Mon Sep 17 00:00:00 2001 From: Kwabena Amponsah Date: Wed, 18 Mar 2026 20:16:19 +0000 Subject: [PATCH 11/28] #236 Fix numpy api deprecation warning --- setup.py | 1 + 1 file changed, 1 insertion(+) diff --git a/setup.py b/setup.py index 48cf3dd9..d155420f 100644 --- a/setup.py +++ b/setup.py @@ -34,6 +34,7 @@ name="fc.sundials.solver", sources=["fc/sundials/solver.pyx"], include_dirs=[".", numpy.get_include()], + define_macros=[("NPY_NO_DEPRECATED_API", "NPY_1_7_API_VERSION")], libraries=["sundials_cvode", "sundials_nvecserial"], ), ] From c4cc73a713e69f70c2d06c2cf75114b898698fc3 Mon Sep 17 00:00:00 2001 From: Kwabena Amponsah Date: Wed, 18 Mar 2026 20:32:14 +0000 Subject: [PATCH 12/28] #236 replace pkg_resources with importlib --- fc/parsing/rdf.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/fc/parsing/rdf.py b/fc/parsing/rdf.py index 1023a170..3cdece81 100644 --- a/fc/parsing/rdf.py +++ b/fc/parsing/rdf.py @@ -1,7 +1,7 @@ """ RDF handling routines, including parsing the 'oxmeta' ontology. """ -import pkg_resources +import importlib.resources import rdflib @@ -47,8 +47,8 @@ class given by ``term``, i.e. are connected to it by a path of ``rdf:type`` pred if _ONTOLOGY is None: # Load oxmeta ontology g = _ONTOLOGY = rdflib.Graph() - oxmeta_ttl = pkg_resources.resource_stream('fc', 'ontologies/oxford-metadata.ttl') - g.parse(oxmeta_ttl, format='turtle') + oxmeta_ttl = importlib.resources.files('fc').joinpath('ontologies/oxford-metadata.ttl') + g.parse(str(oxmeta_ttl), format='turtle') term = create_rdf_node(term) From d897cd427bf62b85bbca82b83d4fd1ecaf06db4a Mon Sep 17 00:00:00 2001 From: Kwabena Amponsah Date: Wed, 18 Mar 2026 20:44:00 +0000 Subject: [PATCH 13/28] #236 Fix style errors --- fc/language/expressions/abstract.py | 16 ++++++++-------- fc/parsing/CompactSyntaxParser.py | 2 +- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/fc/language/expressions/abstract.py b/fc/language/expressions/abstract.py index b3b0d2fe..1742bef4 100644 --- a/fc/language/expressions/abstract.py +++ b/fc/language/expressions/abstract.py @@ -129,13 +129,13 @@ def get_defining_environments(self, env): return self._defining_envs except AttributeError: self._root_defining_env = env # For paranoia checking that the cache is valid - d = self._defining_envs = {} - l = self._used_var_local_names = [] # noqa: E741 + defining_envs = self._defining_envs = {} + used_local_names = self._used_var_local_names = [] # noqa: E741 for name in self.used_variable_list: local_name = name[name.rfind(':') + 1:] - l.append(local_name) - d[local_name] = env.find_defining_environment(name) - return d + used_local_names.append(local_name) + defining_envs[local_name] = env.find_defining_environment(name) + return defining_envs @property def compiled_function(self): @@ -154,9 +154,9 @@ def used_variable_list(self): try: return self._used_var_list except AttributeError: - l = self._used_var_list = list(self.used_variables) # noqa: E741 - l.sort() - return l + used_var_list = self._used_var_list = list(self.used_variables) # noqa: E741 + used_var_list.sort() + return used_var_list @property def used_variables(self): diff --git a/fc/parsing/CompactSyntaxParser.py b/fc/parsing/CompactSyntaxParser.py index 969f2e3d..b05abe89 100644 --- a/fc/parsing/CompactSyntaxParser.py +++ b/fc/parsing/CompactSyntaxParser.py @@ -414,7 +414,7 @@ class CompactSyntaxParser(object): ).setName('output variable declaration').setParseAction(actions.OutputVariable) # Model variables (inputs, outputs, or just used in equations) that are allowed to be missing - locator = p.Empty().leaveWhitespace().setParseAction(lambda s, l, t: l) + locator = p.Empty().leaveWhitespace().setParseAction(lambda s, loc, tokens: loc) var_default = make_kw('default') - locator("default_start") + simple_expr("default") optional_variable = p.Group( make_kw('optional') - c_ident("name") + Optional(var_default) + locator("default_end") From 745679d594b63550d16997cb0cf7c2e37c1869b3 Mon Sep 17 00:00:00 2001 From: Kwabena Amponsah Date: Wed, 18 Mar 2026 21:07:27 +0000 Subject: [PATCH 14/28] #236 Fix test_algebraic_models --- fc/sundials/solver.pxd | 8 ++++---- fc/sundials/solver.pyx | 14 +++++++------- fc/sundials/sundials.pxd | 3 +-- fc/templates/weblab_model.pyx | 10 +++++----- test/output/code_generation/weblab_model.pyx | 10 +++++----- 5 files changed, 22 insertions(+), 23 deletions(-) diff --git a/fc/sundials/solver.pxd b/fc/sundials/solver.pxd index 5a99f7f1..56d7ee6d 100644 --- a/fc/sundials/solver.pxd +++ b/fc/sundials/solver.pxd @@ -4,7 +4,7 @@ cimport fc.sundials.sundials as _lib # Save typing ctypedef _lib.N_Vector N_Vector -ctypedef _lib.sunrealtype realtype +ctypedef _lib.sunrealtype sunrealtype ctypedef _lib.SUNMatrix SUNMatrix ctypedef _lib.SUNLinearSolver SUNLinearSolver @@ -20,9 +20,9 @@ cdef class CvodeSolver: cdef public object model # The model being simulated cpdef associate_with_model(self, model) - cpdef reset_solver(self, np.ndarray[realtype, ndim=1] reset_to) - cpdef set_free_variable(self, realtype t) - cpdef simulate(self, realtype end_point) + cpdef reset_solver(self, np.ndarray[sunrealtype, ndim=1] reset_to) + cpdef set_free_variable(self, sunrealtype t) + cpdef simulate(self, sunrealtype end_point) cdef re_init(self) cdef check_flag(self, int flag, char* called) diff --git a/fc/sundials/solver.pyx b/fc/sundials/solver.pyx index f0f40a3d..e3e25a16 100644 --- a/fc/sundials/solver.pyx +++ b/fc/sundials/solver.pyx @@ -25,7 +25,7 @@ cdef object numpy_view(N_Vector v): cdef _lib.N_VectorContent_Serial v_content = <_lib.N_VectorContent_Serial>(v.content) cdef view.array data_view = view.array( shape=(v_content.length,), - itemsize=sizeof(realtype), + itemsize=sizeof(sunrealtype), format='d', mode='c', allocate_buffer=False) @@ -33,7 +33,7 @@ cdef object numpy_view(N_Vector v): ret = np.asarray(data_view, dtype=np_dtype) return ret -cdef int _rhs_wrapper(realtype t, N_Vector y, N_Vector ydot, void* user_data) noexcept: +cdef int _rhs_wrapper(sunrealtype t, N_Vector y, N_Vector ydot, void* user_data) noexcept: """Cython wrapper around a model RHS that uses numpy, for calling by CVODE.""" # Create numpy views on the N_Vectors @@ -95,7 +95,7 @@ cdef class CvodeSolver: self.state = model.state self._state_size = len(model.state) self._state = _lib.FC_N_VMake_Serial( - self._state_size, (self.state).data, self.sunctx) + self._state_size, (self.state).data, self.sunctx) # Create CVode object self.cvode_mem = _lib.FC_CVodeCreate(_lib.CV_BDF, 0, self.sunctx) @@ -144,16 +144,16 @@ cdef class CvodeSolver: _lib.CVodeSetMaxStep(self.cvode_mem, 0.5) _lib.CVodeSetMaxErrTestFails(self.cvode_mem, 15) - cpdef reset_solver(self, np.ndarray[realtype, ndim=1] resetTo): + cpdef reset_solver(self, np.ndarray[sunrealtype, ndim=1] resetTo): self.state[:] = resetTo self.re_init() - cpdef set_free_variable(self, realtype t): + cpdef set_free_variable(self, sunrealtype t): self.model.free_variable = t self.re_init() - cpdef simulate(self, realtype end_point): - cdef realtype t = 0 + cpdef simulate(self, sunrealtype end_point): + cdef sunrealtype t = 0 if self._state_size > 0: if self.model.dirty: # A model variable has changed, so reset the solver diff --git a/fc/sundials/sundials.pxd b/fc/sundials/sundials.pxd index ae4ce5ef..8d7b1450 100644 --- a/fc/sundials/sundials.pxd +++ b/fc/sundials/sundials.pxd @@ -8,8 +8,7 @@ Includes compatibility wrappers so Cython code can avoid deprecated compile-time cdef extern from "sundials/sundials_types.h": ctypedef long int sunindextype ctypedef double sunrealtype - ctypedef int sunbooleantype - ctypedef bint booleantype + ctypedef bint sunbooleantype cdef extern from "sundials/sundials_nvector.h": cdef struct _generic_N_Vector: diff --git a/fc/templates/weblab_model.pyx b/fc/templates/weblab_model.pyx index 358ae905..381fda12 100644 --- a/fc/templates/weblab_model.pyx +++ b/fc/templates/weblab_model.pyx @@ -22,10 +22,10 @@ from fc.error_handling import ProtocolError from fc.sundials.solver cimport CvodeSolver -cdef int _evaluate_rhs(Sundials.realtype {{ free_variable }}, +cdef int _evaluate_rhs(Sundials.sunrealtype {{ free_variable }}, Sundials.N_Vector y, Sundials.N_Vector ydot, - void* user_data): + void* user_data) noexcept: """ Cython wrapper around a model RHS that uses numpy, for calling by CVODE. @@ -33,7 +33,7 @@ cdef int _evaluate_rhs(Sundials.realtype {{ free_variable }}, """ # We passed the Python model object in as CVODE user data; get it back as an object model = user_data - cdef np.ndarray[Sundials.realtype, ndim=1] parameters = model.parameters + cdef np.ndarray[Sundials.sunrealtype, ndim=1] parameters = model.parameters # Unpack state variables {%- for state in states %} @@ -188,7 +188,7 @@ cdef class {{ class_name }}(CvodeSolver): self.associate_with_model(self) #self._parameters = Sundials.N_VMake_Serial( # len(self.parameters), - # (self.parameters).data + # (self.parameters).data #) self.env = ModelWrapperEnvironment(self) @@ -221,7 +221,7 @@ cdef class {{ class_name }}(CvodeSolver): """ # Get parameters as sundials realtype numpy array - cdef np.ndarray[Sundials.realtype, ndim=1] parameters = self.parameters + cdef np.ndarray[Sundials.sunrealtype, ndim=1] parameters = self.parameters # Get current free variable cdef double {{ free_variable }} = self.free_variable diff --git a/test/output/code_generation/weblab_model.pyx b/test/output/code_generation/weblab_model.pyx index 1bffead0..fd01f83c 100644 --- a/test/output/code_generation/weblab_model.pyx +++ b/test/output/code_generation/weblab_model.pyx @@ -22,10 +22,10 @@ from fc.error_handling import ProtocolError from fc.sundials.solver cimport CvodeSolver -cdef int _evaluate_rhs(Sundials.realtype var_time, +cdef int _evaluate_rhs(Sundials.sunrealtype var_time, Sundials.N_Vector y, Sundials.N_Vector ydot, - void* user_data): + void* user_data) noexcept: """ Cython wrapper around a model RHS that uses numpy, for calling by CVODE. @@ -33,7 +33,7 @@ cdef int _evaluate_rhs(Sundials.realtype var_time, """ # We passed the Python model object in as CVODE user data; get it back as an object model = user_data - cdef np.ndarray[Sundials.realtype, ndim=1] parameters = model.parameters + cdef np.ndarray[Sundials.sunrealtype, ndim=1] parameters = model.parameters # Unpack state variables cdef double var_V = (y.content).data[0] @@ -203,7 +203,7 @@ cdef class TestModel(CvodeSolver): self.associate_with_model(self) #self._parameters = Sundials.N_VMake_Serial( # len(self.parameters), - # (self.parameters).data + # (self.parameters).data #) self.env = ModelWrapperEnvironment(self) @@ -234,7 +234,7 @@ cdef class TestModel(CvodeSolver): """ # Get parameters as sundials realtype numpy array - cdef np.ndarray[Sundials.realtype, ndim=1] parameters = self.parameters + cdef np.ndarray[Sundials.sunrealtype, ndim=1] parameters = self.parameters # Get current free variable cdef double var_time = self.free_variable From 1e26ce110b2ceb274031876015a71fda41819bf9 Mon Sep 17 00:00:00 2001 From: Kwabena Amponsah Date: Wed, 18 Mar 2026 22:22:08 +0000 Subject: [PATCH 15/28] #236 Fix pyparsing deprecation warnings --- fc/parsing/CompactSyntaxParser.py | 226 ++++++++++++++--------------- test/test_compact_syntax_parser.py | 4 +- 2 files changed, 115 insertions(+), 115 deletions(-) diff --git a/fc/parsing/CompactSyntaxParser.py b/fc/parsing/CompactSyntaxParser.py index b05abe89..f0ee0f67 100644 --- a/fc/parsing/CompactSyntaxParser.py +++ b/fc/parsing/CompactSyntaxParser.py @@ -16,7 +16,7 @@ __all__ = ['CompactSyntaxParser'] # Necessary for reasonable speed when using infixNotation -p.ParserElement.enablePackrat() +p.ParserElement.enable_packrat() ################################################################################ @@ -33,7 +33,7 @@ def make_kw(keyword, suppress=True): def adjacent(parser): """Create a copy of the given parser that doesn't permit whitespace to occur before it.""" adj = parser.copy() - adj.setWhitespaceChars('') + adj.set_whitespace_chars('') return adj @@ -61,12 +61,12 @@ def parseImpl(self, instring, loc, doActions=True): def optional_delimited_list(expr, delim): - """Like delimitedList, but the list may be empty.""" - return p.delimitedList(expr, delim) | p.Empty() + """Like DelimitedList, but the list may be empty.""" + return p.DelimitedList(expr, delim) | p.Empty() def delimited_multi_list(elements, delimiter): - """Like delimitedList, but allows for a sequence of constituent element expressions. + """Like DelimitedList, but allows for a sequence of constituent element expressions. elements should be a sequence of tuples (expr, unbounded), where expr is a ParserElement, and unbounded is True iff zero or more occurrences are allowed; otherwise the expr is @@ -128,10 +128,10 @@ class CompactSyntaxParser(object): """A parser for the compact textual syntax for protocols.""" # Newlines are significant most of the time for us - p.ParserElement.setDefaultWhitespaceChars(' \t\r') + p.ParserElement.set_default_whitespace_chars(' \t\r') # Single-line Python-style comments - comment = p.Regex(r'#.*').suppress().setName('comment') + comment = p.Regex(r'#.*').suppress().set_name('comment') # Punctuation etc. eq = p.Suppress('=') @@ -143,36 +143,36 @@ class CompactSyntaxParser(object): csquare = p.Suppress(']') dollar = p.Suppress('$') nl = p.Suppress(p.OneOrMore(Optional(comment) + p.LineEnd()) - ).setName('newline(s)') # Any line can end with a comment - obrace = (Optional(nl) + p.Suppress('{') + Optional(nl)).setName('{') - cbrace = (Optional(nl) + p.Suppress('}') + Optional(nl)).setName('}') - embedded_cbrace = (Optional(nl) + p.Suppress('}')).setName('}') + ).set_name('newline(s)') # Any line can end with a comment + obrace = (Optional(nl) + p.Suppress('{') + Optional(nl)).set_name('{') + cbrace = (Optional(nl) + p.Suppress('}') + Optional(nl)).set_name('}') + embedded_cbrace = (Optional(nl) + p.Suppress('}')).set_name('}') # Identifiers - nc_ident = p.Regex('[_a-zA-Z][_0-9a-zA-Z]*').setName('non-prefixed identifier') - c_ident = p.Regex('[_a-zA-Z][_0-9a-zA-Z]*:[_a-zA-Z][_0-9a-zA-Z]*').setName('prefixed identifier') - ident = p.Regex('[_a-zA-Z][_0-9a-zA-Z]*(:[_a-zA-Z][_0-9a-zA-Z]*)*').setName('identifier (with or without prefix)') - nc_ident_as_var = nc_ident.copy().setParseAction(actions.Variable) - ident_as_var = ident.copy().setParseAction(actions.Variable) + nc_ident = p.Regex('[_a-zA-Z][_0-9a-zA-Z]*').set_name('non-prefixed identifier') + c_ident = p.Regex('[_a-zA-Z][_0-9a-zA-Z]*:[_a-zA-Z][_0-9a-zA-Z]*').set_name('prefixed identifier') + ident = p.Regex('[_a-zA-Z][_0-9a-zA-Z]*(:[_a-zA-Z][_0-9a-zA-Z]*)*').set_name('identifier (with or without prefix)') + nc_ident_as_var = nc_ident.copy().set_parse_action(actions.Variable) + ident_as_var = ident.copy().set_parse_action(actions.Variable) # Numbers can be given in scientific notation, with an optional leading minus sign. # Within expressions they may also have units specified, e.g. in the model interface. - units_ident = p.originalTextFor(p.Literal('units_of(') - adjacent(ident) + adjacent(p.Literal(')'))) | nc_ident + units_ident = p.original_text_for(p.Literal('units_of(') - adjacent(ident) + adjacent(p.Literal(')'))) | nc_ident units_annotation = p.Suppress('::') - units_ident("units") - plain_number = p.Regex(r'-?[0-9]+((\.[0-9]+)?(e[-+]?[0-9]+)?)?').setName('number') - number = (plain_number + Optional(units_annotation)).setName('number or quantity') + plain_number = p.Regex(r'-?[0-9]+((\.[0-9]+)?(e[-+]?[0-9]+)?)?').set_name('number') + number = (plain_number + Optional(units_annotation)).set_name('number or quantity') # Used for descriptive text - quoted_string = (p.QuotedString('"', escChar="\\") | p.QuotedString("'", escChar="\\")).setName('quoted string') + quoted_string = (p.QuotedString('"', esc_char="\\") | p.QuotedString("'", esc_char="\\")).set_name('quoted string') # This may become more specific in future - quoted_uri = quoted_string.copy().setName('quoted uri') + quoted_uri = quoted_string.copy().set_name('quoted uri') # Expressions from the "post-processing" language ################################################# # Expressions and statements must be constructed recursively - expr = p.Forward().setName('expression') - stmt_list = p.Forward().setName('statement list') + expr = p.Forward().set_name('expression') + stmt_list = p.Forward().set_name('statement list') # A vector written like 1:2:5 or 1:5 or A:B:C numeric_range = p.Group(expr + colon - expr + Optional(colon - expr)) @@ -180,53 +180,53 @@ class CompactSyntaxParser(object): # Creating arrays dim_spec = Optional(expr + adjacent(dollar)) + nc_ident comprehension = p.Group( - make_kw('for') - dim_spec + make_kw('in') - numeric_range).setParseAction(actions.Comprehension) + make_kw('for') - dim_spec + make_kw('in') - numeric_range).set_parse_action(actions.Comprehension) array = p.Group(osquare - expr + (p.OneOrMore(comprehension) | p.ZeroOrMore(comma - expr)) + csquare - ).setName('array').setParseAction(actions.Array) + ).set_name('array').set_parse_action(actions.Array) # Array views opt_expr = Optional(expr, default='') view_spec = p.Group( adjacent(osquare) - Optional(('*' | expr) + adjacent(dollar))('dimspec') + opt_expr + Optional(colon - opt_expr + Optional(colon - opt_expr)) + csquare - ).setName('view specification on an array') + ).set_name('view specification on an array') # If-then-else if_expr = p.Group(make_kw('if') - expr + make_kw('then') - expr + - make_kw('else') - expr).setName('if-then-else expression').setParseAction(actions.Piecewise) + make_kw('else') - expr).set_name('if-then-else expression').set_parse_action(actions.Piecewise) # Lambda definitions param_decl = p.Group(nc_ident_as_var + Optional(eq + expr)) param_list = p.Group(optional_delimited_list(param_decl, comma)) lambda_expr = p.Group(make_kw('lambda') - param_list + ((colon - expr) | (obrace - stmt_list + embedded_cbrace)) - ).setName('lambda function').setParseAction(actions.Lambda) + ).set_name('lambda function').set_parse_action(actions.Lambda) # Function calls # TODO: Allow lambdas, not just ident? arg_list = p.Group(optional_delimited_list(expr, comma)) function_call = p.Group(ident_as_var + adjacent(oparen) - arg_list + - cparen).setName('function call').setParseAction(actions.FunctionCall) + cparen).set_name('function call').set_parse_action(actions.FunctionCall) # Tuples tuple = p.Group(oparen + expr + comma - optional_delimited_list(expr, comma) + - cparen).setName('tuple').setParseAction(actions.Tuple) + cparen).set_name('tuple').set_parse_action(actions.Tuple) # Accessors accessor = p.Combine(adjacent(p.Suppress('.')) - - p.oneOf('IS_SIMPLE_VALUE IS_ARRAY IS_STRING IS_TUPLE IS_FUNCTION IS_NULL IS_DEFAULT ' - 'NUM_DIMS NUM_ELEMENTS SHAPE')).setName('.accessor (e.g. .IS_ARRAY)') + p.one_of('IS_SIMPLE_VALUE IS_ARRAY IS_STRING IS_TUPLE IS_FUNCTION IS_NULL IS_DEFAULT ' + 'NUM_DIMS NUM_ELEMENTS SHAPE')).set_name('.accessor (e.g. .IS_ARRAY)') # Indexing - pad = (make_kw('pad') + adjacent(colon) - expr + eq + expr).setResultsName('pad') - shrink = (make_kw('shrink') + adjacent(colon) - expr).setResultsName('shrink') - index_dim = expr.setResultsName('dim') + pad = (make_kw('pad') + adjacent(colon) - expr + eq + expr).set_results_name('pad') + shrink = (make_kw('shrink') + adjacent(colon) - expr).set_results_name('shrink') + index_dim = expr.set_results_name('dim') index = p.Group(adjacent(p.Suppress('{')) - expr + - p.ZeroOrMore(comma - (pad | shrink | index_dim)) + p.Suppress('}')).setName('index expression') + p.ZeroOrMore(comma - (pad | shrink | index_dim)) + p.Suppress('}')).set_name('index expression') # Special values - null_value = p.Group(make_kw('null')).setName('null').setParseAction(actions.Symbol('null')) - default_value = p.Group(make_kw('default')).setName('default').setParseAction(actions.Symbol('defaultParameter')) - string_value = quoted_string.copy().setName('string').setParseAction(actions.Symbol('string')) + null_value = p.Group(make_kw('null')).set_name('null').set_parse_action(actions.Symbol('null')) + default_value = p.Group(make_kw('default')).set_name('default').set_parse_action(actions.Symbol('defaultParameter')) + string_value = quoted_string.copy().set_name('string').set_parse_action(actions.Symbol('string')) # Recognised MathML operators mathml_operators = set(''' @@ -243,57 +243,57 @@ class CompactSyntaxParser(object): # Wrapping MathML operators into lambdas mathml_operator = ( - p.oneOf('^ * / + - not == != <= >= < > && ||') | - p.Combine('MathML:' + p.oneOf(' '.join(mathml_operators)))) + p.one_of('^ * / + - not == != <= >= < > && ||') | + p.Combine('MathML:' + p.one_of(' '.join(mathml_operators)))) wrap = p.Group( p.Suppress('@') - adjacent(p.Word(p.nums)) + adjacent(colon) + mathml_operator - ).setName('MathML lambda "@" syntax').setParseAction(actions.Wrap) + ).set_name('MathML lambda "@" syntax').set_parse_action(actions.Wrap) # Turning on tracing for debugging protocols trace = adjacent(p.Suppress('?')) # The main expression grammar. Atoms are ordered according to rough speed of detecting mis-match. atom = ( - array | wrap | number.copy().setParseAction(actions.Number) | string_value | + array | wrap | number.copy().set_parse_action(actions.Number) | string_value | if_expr | null_value | default_value | lambda_expr | function_call | ident_as_var | tuple - ).setName('atomic expression') - expr <<= p.infixNotation(atom, [(accessor, 1, p.opAssoc.LEFT, actions.Accessor), + ).set_name('atomic expression') + expr <<= p.infix_notation(atom, [(accessor, 1, p.opAssoc.LEFT, actions.Accessor), (view_spec, 1, p.opAssoc.LEFT, actions.View), (index, 1, p.opAssoc.LEFT, actions.Index), (trace, 1, p.opAssoc.LEFT, actions.Trace), ('^', 2, p.opAssoc.LEFT, actions.Operator), ('-', 1, p.opAssoc.RIGHT, lambda *args: actions.Operator(*args, rightAssoc=True)), - (p.oneOf('* /'), 2, p.opAssoc.LEFT, actions.Operator), - (p.oneOf('+ -'), 2, p.opAssoc.LEFT, actions.Operator), + (p.one_of('* /'), 2, p.opAssoc.LEFT, actions.Operator), + (p.one_of('+ -'), 2, p.opAssoc.LEFT, actions.Operator), (p.Keyword('not'), 1, p.opAssoc.RIGHT, lambda *args: actions.Operator(*args, rightAssoc=True)), - (p.oneOf('== != <= >= < >'), 2, p.opAssoc.LEFT, actions.Operator), - (p.oneOf('&& ||'), 2, p.opAssoc.LEFT, actions.Operator) + (p.one_of('== != <= >= < >'), 2, p.opAssoc.LEFT, actions.Operator), + (p.one_of('&& ||'), 2, p.opAssoc.LEFT, actions.Operator) ]) # Simpler expressions containing no arrays, functions, etc. Used in the model interface. - simple_expr = p.Forward().setName('simple expression') + simple_expr = p.Forward().set_name('simple expression') simple_if_expr = p.Group( make_kw('if') - simple_expr + make_kw('then') - simple_expr + make_kw('else') - simple_expr - ).setName('simple if-then-else').setParseAction(actions.Piecewise) + ).set_name('simple if-then-else').set_parse_action(actions.Piecewise) simple_arg_list = p.Group(optional_delimited_list(simple_expr, comma)) simple_function_call = p.Group(ident_as_var + adjacent(oparen) - simple_arg_list + - cparen).setName('simple function call').setParseAction(actions.FunctionCall) - simple_expr <<= p.infixNotation( - number.copy().setParseAction(actions.Number) | simple_if_expr | simple_function_call | ident_as_var, + cparen).set_name('simple function call').set_parse_action(actions.FunctionCall) + simple_expr <<= p.infix_notation( + number.copy().set_parse_action(actions.Number) | simple_if_expr | simple_function_call | ident_as_var, [ ('^', 2, p.opAssoc.LEFT, actions.Operator), ('-', 1, p.opAssoc.RIGHT, lambda *args: actions.Operator(*args, rightAssoc=True)), - (p.oneOf('* /'), 2, p.opAssoc.LEFT, actions.Operator), - (p.oneOf('+ -'), 2, p.opAssoc.LEFT, actions.Operator), + (p.one_of('* /'), 2, p.opAssoc.LEFT, actions.Operator), + (p.one_of('+ -'), 2, p.opAssoc.LEFT, actions.Operator), (p.Keyword('not'), 1, p.opAssoc.RIGHT, lambda *args: actions.Operator(*args, rightAssoc=True)), - (p.oneOf('== != <= >= < >'), 2, p.opAssoc.LEFT, actions.Operator), - (p.oneOf('&& ||'), 2, p.opAssoc.LEFT, actions.Operator) + (p.one_of('== != <= >= < >'), 2, p.opAssoc.LEFT, actions.Operator), + (p.one_of('&& ||'), 2, p.opAssoc.LEFT, actions.Operator) ]) simple_param_list = p.Group(optional_delimited_list(p.Group(nc_ident_as_var), comma)) simple_lambda_expr = p.Group(make_kw('lambda') - simple_param_list + colon - - simple_expr).setName('simple lambda function').setParseAction(actions.Lambda) + simple_expr).set_name('simple lambda function').set_parse_action(actions.Lambda) # Newlines in expressions may be escaped with a backslash expr.ignore('\\' + p.LineEnd()) @@ -313,44 +313,44 @@ class CompactSyntaxParser(object): # Simple assignment (i.e. not to a tuple) simple_assign = p.Group( - nc_ident_as_var + eq - expr).setName('simple assignment').setParseAction(actions.Assignment) - simple_assign_list = p.Group(optional_delimited_list(simple_assign, nl)).setParseAction(actions.StatementList) + nc_ident_as_var + eq - expr).set_name('simple assignment').set_parse_action(actions.Assignment) + simple_assign_list = p.Group(optional_delimited_list(simple_assign, nl)).set_parse_action(actions.StatementList) # Assertions and function returns - assert_stmt = p.Group(make_kw('assert') - expr).setName('assert statement').setParseAction(actions.Assert) + assert_stmt = p.Group(make_kw('assert') - expr).set_name('assert statement').set_parse_action(actions.Assert) return_stmt = p.Group( - make_kw('return') - p.delimitedList(expr)).setName('return statement').setParseAction(actions.Return) + make_kw('return') - p.DelimitedList(expr)).set_name('return statement').set_parse_action(actions.Return) # Full assignment, to a tuple of names or single name - _idents = p.Group(p.delimitedList(nc_ident_as_var)).setParseAction(actions.MaybeTuple) + _idents = p.Group(p.DelimitedList(nc_ident_as_var)).set_parse_action(actions.MaybeTuple) assign_stmt = p.Group( ((make_kw('optional', suppress=False)("optional") + _idents) | _idents) + eq - - p.Group(p.delimitedList(expr)).setParseAction(actions.MaybeTuple) - ).setName('assignment statement').setParseAction(actions.Assignment) + p.Group(p.DelimitedList(expr)).set_parse_action(actions.MaybeTuple) + ).set_name('assignment statement').set_parse_action(actions.Assignment) # Function definition function_defn = p.Group(make_kw('def') - nc_ident_as_var + oparen + param_list + cparen - ((colon - expr) | (obrace - stmt_list + Optional(nl) + p.Suppress('}'))) - ).setName('function definition').setParseAction(actions.FunctionDef) + ).set_name('function definition').set_parse_action(actions.FunctionDef) - stmt_list << p.Group(p.delimitedList(assert_stmt | return_stmt | function_defn | assign_stmt, nl)) - stmt_list.setParseAction(actions.StatementList) + stmt_list << p.Group(p.DelimitedList(assert_stmt | return_stmt | function_defn | assign_stmt, nl)) + stmt_list.set_parse_action(actions.StatementList) # Miscellaneous constructs making up protocols ############################################## # Documentation (Markdown) - documentation = p.Group(make_kw('documentation') - obrace - p.Regex("[^}]*") + cbrace).setResultsName("dox") + documentation = p.Group(make_kw('documentation') - obrace - p.Regex("[^}]*") + cbrace).set_results_name("dox") # Namespace declarations ns_decl = p.Group( - make_kw('namespace') - nc_ident("prefix") + eq + quoted_uri("uri")).setName('namespace declaration') + make_kw('namespace') - nc_ident("prefix") + eq + quoted_uri("uri")).set_name('namespace declaration') ns_decls = optional_delimited_list(ns_decl("namespace*"), nl) # Protocol input declarations, with default values inputs = ( make_kw('inputs') - obrace - simple_assign_list + cbrace - ).setResultsName("inputs").setName('protocol inputs').setParseAction(actions.Inputs) + ).set_results_name("inputs").set_name('protocol inputs').set_parse_action(actions.Inputs) # Import statements import_stmt = p.Group( @@ -363,40 +363,40 @@ class CompactSyntaxParser(object): Optional( obrace - simple_assign_list + - embedded_cbrace)).setName('protocol import').setParseAction( + embedded_cbrace)).set_name('protocol import').set_parse_action( actions.Import) - imports = optional_delimited_list(import_stmt, nl).setResultsName('imports').setName('protocol imports') + imports = optional_delimited_list(import_stmt, nl).set_results_name('imports').set_name('protocol imports') # Library, globals defined using post-processing language. # Strictly speaking returns aren't allowed, but that gets picked up later. library = (make_kw('library') - obrace - Optional(stmt_list) + - cbrace).setResultsName("library").setName('library section').setParseAction(actions.Library) + cbrace).set_results_name("library").set_name('library section').set_parse_action(actions.Library) # Post-processing post_processing = ( make_kw('post-processing') + obrace - optional_delimited_list(assert_stmt | return_stmt | function_defn | assign_stmt, nl) + cbrace - ).setResultsName("postprocessing").setName('post-processing section').setParseAction(actions.PostProcessing) + ).set_results_name("postprocessing").set_name('post-processing section').set_parse_action(actions.PostProcessing) # Units definitions - si_prefix = p.oneOf('deka hecto kilo mega giga tera peta exa zetta yotta' + si_prefix = p.one_of('deka hecto kilo mega giga tera peta exa zetta yotta' 'deci centi milli micro nano pico femto atto zepto yocto') - _num_or_expr = p.originalTextFor(plain_number | (oparen + expr + cparen)) + _num_or_expr = p.original_text_for(plain_number | (oparen + expr + cparen)) unit_ref = p.Group(Optional(_num_or_expr)("multiplier") + Optional(si_prefix)("prefix") + nc_ident("units") + Optional(p.Suppress('^') + plain_number)("exponent") + - Optional(p.Group(p.oneOf('- +') + _num_or_expr))("offset")).setParseAction(actions.UnitRef) - units_def = p.Group(nc_ident + eq + p.delimitedList(unit_ref, '.') + Optional(quoted_string)("description") - ).setName('units definition').setParseAction(actions.UnitsDef) + Optional(p.Group(p.one_of('- +') + _num_or_expr))("offset")).set_parse_action(actions.UnitRef) + units_def = p.Group(nc_ident + eq + p.DelimitedList(unit_ref, '.') + Optional(quoted_string)("description") + ).set_name('units definition').set_parse_action(actions.UnitsDef) units = (make_kw('units') - obrace - optional_delimited_list(units_def, nl) + cbrace - ).setResultsName("units").setName('units section').setParseAction(actions.Units) + ).set_results_name("units").set_name('units section').set_parse_action(actions.Units) # Model interface section ######################### units_ref = make_kw('units') - nc_ident # Setting the units for the independent variable - set_time_units = (make_kw('independent') - make_kw('var') - units_ref("units")).setParseAction(actions.SetTimeUnits) + set_time_units = (make_kw('independent') - make_kw('var') - units_ref("units")).set_parse_action(actions.SetTimeUnits) # Input variables, with optional units and initial value input_variable = p.Group( @@ -404,21 +404,21 @@ class CompactSyntaxParser(object): c_ident('name') + Optional(units_ref)('units') + Optional(eq + plain_number)('initial_value') - ).setName('input variable declaration').setParseAction(actions.InputVariable) + ).set_name('input variable declaration').set_parse_action(actions.InputVariable) # Model outputs of interest, with optional units output_variable = p.Group( make_kw('output') - c_ident("name") + Optional(units_ref("units")) - ).setName('output variable declaration').setParseAction(actions.OutputVariable) + ).set_name('output variable declaration').set_parse_action(actions.OutputVariable) # Model variables (inputs, outputs, or just used in equations) that are allowed to be missing - locator = p.Empty().leaveWhitespace().setParseAction(lambda s, loc, tokens: loc) + locator = p.Empty().leave_whitespace().set_parse_action(lambda s, loc, tokens: loc) var_default = make_kw('default') - locator("default_start") + simple_expr("default") optional_variable = p.Group( make_kw('optional') - c_ident("name") + Optional(var_default) + locator("default_end") - ).setName('optional variable declaration').setParseAction(actions.OptionalVariable) + ).set_name('optional variable declaration').set_parse_action(actions.OptionalVariable) # New variables added to the model, with optional initial value new_variable = p.Group( @@ -428,12 +428,12 @@ class CompactSyntaxParser(object): Optional( eq + plain_number)("initial_value") - ).setName('new variable declaration').setParseAction(actions.DeclareVariable) + ).set_name('new variable declaration').set_parse_action(actions.DeclareVariable) # Adding or replacing equations in the model clamp_variable = p.Group( make_kw('clamp') - ident_as_var + Optional(make_kw('to') - simple_expr) - ).setName('clamp variable declaration').setParseAction(actions.ClampVariable) + ).set_name('clamp variable declaration').set_parse_action(actions.ClampVariable) interpolate = p.Group( make_kw('interpolate') - oparen - @@ -444,21 +444,21 @@ class CompactSyntaxParser(object): nc_ident - comma - nc_ident - - cparen).setName('interpolate').setParseAction( + cparen).set_name('interpolate').set_parse_action( actions.Interpolate) model_equation = p.Group( make_kw('define') - ( p.Group(make_kw('diff') + adjacent(oparen) - ident_as_var + p.Suppress(';') + ident_as_var + cparen) | ident_as_var ) + eq + (interpolate | simple_expr) - ).setName('model equation definition').setParseAction(actions.ModelEquation) + ).set_name('model equation definition').set_parse_action(actions.ModelEquation) # Units conversion rules units_conversion = p.Group( make_kw('convert') - nc_ident("actualDimensions") + make_kw('to') + nc_ident("desiredDimensions") + make_kw('by') - simple_lambda_expr - ).setName('units conversion rule').setParseAction(actions.UnitsConversion) + ).set_name('units conversion rule').set_parse_action(actions.UnitsConversion) model_interface = p.Group( make_kw('model') - make_kw('interface') - obrace - Optional(set_time_units - nl) + @@ -466,7 +466,7 @@ class CompactSyntaxParser(object): input_variable | output_variable | optional_variable | new_variable | clamp_variable | model_equation | units_conversion ), nl) + cbrace - ).setResultsName("model_interface").setName('model interface section').setParseAction(actions.ModelInterface) + ).set_results_name("model_interface").set_name('model interface section').set_parse_action(actions.ModelInterface) # Simulation definitions ######################## @@ -477,46 +477,46 @@ class CompactSyntaxParser(object): while_range = make_kw('while') + expr range = p.Group(make_kw('range') + nc_ident("name") + units_ref("units") + (uniform_range("uniform") | vector_range("vector") | while_range("while")) - ).setName('range').setParseAction(actions.Range) + ).set_name('range').set_parse_action(actions.Range) # Modifiers modifier_when = make_kw('at') - (make_kw('start', False) | (make_kw('each', False) - make_kw('loop')) | - make_kw('end', False)).setParseAction(actions.ModifierWhen) + make_kw('end', False)).set_parse_action(actions.ModifierWhen) set_variable = make_kw('set') - ident + eq + expr save_state = make_kw('save') - make_kw('as') - nc_ident reset_state = make_kw('reset') - Optional(make_kw('to') + nc_ident) modifier = p.Group(modifier_when + p.Group(set_variable("set") | save_state("save") | reset_state("reset")) - ).setName('modifier').setParseAction(actions.Modifier) + ).set_name('modifier').set_parse_action(actions.Modifier) modifiers = p.Group(make_kw('modifiers') + obrace - optional_delimited_list(modifier, nl) + cbrace - ).setName('modifiers').setParseAction(actions.Modifiers) + ).set_name('modifiers').set_parse_action(actions.Modifiers) # The simulations themselves - simulation = p.Forward().setName('simulation') + simulation = p.Forward().set_name('simulation') _select_output = p.Group( make_kw('select') - Optional(make_kw('optional', suppress=False)) - make_kw('output') - nc_ident - ).setName('SelectOutput') + ).set_name('SelectOutput') nested_protocol = p.Group( make_kw('protocol') - quoted_uri + obrace + simple_assign_list + Optional(nl) + optional_delimited_list(_select_output, nl) + cbrace + Optional('?') - ).setName('nested protocol').setParseAction(actions.NestedProtocol) + ).set_name('nested protocol').set_parse_action(actions.NestedProtocol) timecourse_sim = p.Group( make_kw('timecourse') - obrace - range + Optional(nl + modifiers) + cbrace - ).setName('timecourse simulation').setParseAction(actions.TimecourseSimulation) + ).set_name('timecourse simulation').set_parse_action(actions.TimecourseSimulation) nested_sim = p.Group( make_kw('nested') - obrace - range + nl + Optional(modifiers) + p.Group(make_kw('nests') + (simulation | nested_protocol | ident)) + cbrace - ).setName('nested simulation').setParseAction(actions.NestedSimulation) + ).set_name('nested simulation').set_parse_action(actions.NestedSimulation) one_step_sim = p.Group( - make_kw('oneStep') - Optional(p.originalTextFor(expr))("step") + + make_kw('oneStep') - Optional(p.original_text_for(expr))("step") + Optional(obrace - modifiers + cbrace)("modifiers") - ).setParseAction(actions.OneStepSimulation) + ).set_parse_action(actions.OneStepSimulation) simulation << p.Group(make_kw('simulation') - Optional(nc_ident + eq, default='') + (timecourse_sim | nested_sim | one_step_sim) - - Optional('?' + nl)).setParseAction(actions.Simulation) + Optional('?' + nl)).set_parse_action(actions.Simulation) tasks = p.Group(make_kw('tasks') + obrace - p.ZeroOrMore(simulation) + - cbrace).setResultsName("tasks").setName('tasks section').setParseAction(actions.Tasks) + cbrace).set_results_name("tasks").set_name('tasks section').set_parse_action(actions.Tasks) # Output specifications ####################### @@ -526,27 +526,27 @@ class CompactSyntaxParser(object): Optional(make_kw('optional', suppress=False))("optional") + nc_ident("name") + ((units_ref("units") + output_desc) | (eq + ident("ref") + Optional(units_ref)("units") + output_desc)) - ).setName('protocol output specification').setParseAction(actions.Output) + ).set_name('protocol output specification').set_parse_action(actions.Output) outputs = p.Group(make_kw('outputs') + obrace - optional_delimited_list(output_spec, nl) + - cbrace).setResultsName("outputs").setName('outputs section').setParseAction(actions.Outputs) + cbrace).set_results_name("outputs").set_name('outputs section').set_parse_action(actions.Outputs) # Plot specifications ##################### plot_curve = p.Group( - p.delimitedList(nc_ident, ',') + + p.DelimitedList(nc_ident, ',') + make_kw('against') - nc_ident + Optional(make_kw('key') - nc_ident("key")) - ).setName('Curve') + ).set_name('Curve') plot_using = (make_kw('using') - (make_kw('lines', suppress=False) | make_kw('points', suppress=False) | make_kw('linespoints', suppress=False)))("using") plot_spec = p.Group( make_kw('plot') - quoted_string + Optional(plot_using) - obrace + plot_curve + p.ZeroOrMore(nl + plot_curve) + cbrace - ).setName('plot specification').setParseAction(actions.Plot) + ).set_name('plot specification').set_parse_action(actions.Plot) plots = p.Group(make_kw('plots') + obrace - p.ZeroOrMore(plot_spec) + - cbrace).setResultsName("plots").setName('plots section').setParseAction(actions.Plots) + cbrace).set_results_name("plots").set_name('plots section').set_parse_action(actions.Plots) # Parsing a full protocol ######################### @@ -565,7 +565,7 @@ class CompactSyntaxParser(object): post_processing, outputs, plots, - ]))).setName('Protocol').setParseAction(actions.Protocol) + ]))).set_name('Protocol').set_parse_action(actions.Protocol) # Caching of parsed files # This maps source file names to a tuple (date_read, result) diff --git a/test/test_compact_syntax_parser.py b/test/test_compact_syntax_parser.py index 79b20c08..50b07b8e 100644 --- a/test/test_compact_syntax_parser.py +++ b/test/test_compact_syntax_parser.py @@ -5,7 +5,7 @@ csp = CSP.CompactSyntaxParser # An end-of-string match that doesn't allow trailing whitespace -strict_string_end = CSP.p.StringEnd().leaveWhitespace() +strict_string_end = CSP.p.StringEnd().leave_whitespace() def check_parse_results(actual, expected): @@ -758,7 +758,7 @@ def test_parsing_find_and_index(): def test_parsing_units_definitions(): # Possible syntax: (mult, offset, expt are 'numbers'; prefix is SI prefix name; base is ncIdent) # new_simple = [mult] [prefix] base [+|- offset] - # new_complex = p.delimitedList( [mult] [prefix] base [^expt], '.') + # new_complex = p.DelimitedList( [mult] [prefix] base [^expt], '.') assert_parses(csp.units_def, 'ms = milli second', [['ms', ['milli', 'second']]]) assert_parses(csp.units_def, 'C = kelvin - 273.15', [['C', ['kelvin', ['-', '273.15']]]]) assert_parses(csp.units_def, 'C=kelvin+(-273.15)', [['C', ['kelvin', ['+', '(-273.15)']]]]) From b8c5e21550c911bb7a4a11c7727521abf6937410 Mon Sep 17 00:00:00 2001 From: Kwabena Amponsah Date: Wed, 18 Mar 2026 23:09:50 +0000 Subject: [PATCH 16/28] #236 Fix pyparsing deprecations --- fc/environment.py | 4 +- test/test_compact_syntax_parser.py | 8 +- test/test_syntax_interface.py | 212 ++++++++++++++--------------- 3 files changed, 112 insertions(+), 112 deletions(-) diff --git a/fc/environment.py b/fc/environment.py index 587c2ee3..dbbc7c31 100644 --- a/fc/environment.py +++ b/fc/environment.py @@ -57,14 +57,14 @@ def define_names(self, names, values): def evaluate_expr(self, expr_str, env): from fc.parsing.CompactSyntaxParser import CompactSyntaxParser as csp - parse_action = csp.expr.parseString(expr_str, parseAll=True) + parse_action = csp.expr.parse_string(expr_str, parseAll=True) expr = parse_action[0].expr() return expr.evaluate(env) def evaluate_statement(self, stmt_str, env): from fc.parsing.CompactSyntaxParser import CompactSyntaxParser as csp - parse_action = csp.stmt_list.parseString(stmt_str, parseAll=True) + parse_action = csp.stmt_list.parse_string(stmt_str, parseAll=True) stmt_list = parse_action[0].expr() return env.execute_statements(stmt_list) diff --git a/test/test_compact_syntax_parser.py b/test/test_compact_syntax_parser.py index 50b07b8e..a52f4d05 100644 --- a/test/test_compact_syntax_parser.py +++ b/test/test_compact_syntax_parser.py @@ -32,7 +32,7 @@ def check_result(actual, expected): def assert_parses(grammar, input, results): """Utility method to test that a given grammar parses an input as expected.""" - actual_results = grammar.parseString(input, parseAll=True) + actual_results = grammar.parse_string(input, parseAll=True) check_parse_results(actual_results, results) @@ -40,7 +40,7 @@ def assert_does_not_parse(grammar, input): """Utility method to test that a given grammar fails to parse an input.""" strict_grammar = grammar + strict_string_end with pytest.raises(CSP.p.ParseBaseException): - strict_grammar.parseString(input) + strict_grammar.parse_string(input) def test_parsing_identifiers(): @@ -117,10 +117,10 @@ def test_parsing_trace(): assert_parses(csp.expr, '(1 + a)?', [[['1', '+', 'a']]]) assert_parses(csp.expr, '1 + a?', [['1', '+', ['a']]]) - action = csp.expr.parseString('var?', parseAll=True) + action = csp.expr.parse_string('var?', parseAll=True) assert action[0].expr().trace - action = csp.expr.parseString('var', parseAll=True) + action = csp.expr.parse_string('var', parseAll=True) assert not action[0].expr().trace diff --git a/test/test_syntax_interface.py b/test/test_syntax_interface.py index ea83d6b5..294c7435 100644 --- a/test/test_syntax_interface.py +++ b/test/test_syntax_interface.py @@ -18,7 +18,7 @@ def test_parsing_number(): - parse_action = CSP.expr.parseString('1.0', parseAll=True) + parse_action = CSP.expr.parse_string('1.0', parseAll=True) expr = parse_action[0].expr() assert isinstance(expr, E.Const) env = Environment() @@ -26,7 +26,7 @@ def test_parsing_number(): def test_parsing_variable(): - parse_action = CSP.expr.parseString('a', parseAll=True) + parse_action = CSP.expr.parse_string('a', parseAll=True) expr = parse_action[0].expr() assert isinstance(expr, E.NameLookUp) env = Environment() @@ -36,37 +36,37 @@ def test_parsing_variable(): def test_parsing_math_operations(): # plus - parse_action = CSP.expr.parseString('1.0 + 2.0', parseAll=True) + parse_action = CSP.expr.parse_string('1.0 + 2.0', parseAll=True) expr = parse_action[0].expr() assert isinstance(expr, E.Plus) env = Environment() assert expr.evaluate(env).value == 3 # minus - parse_action = CSP.expr.parseString('5.0 - 2.0', parseAll=True) + parse_action = CSP.expr.parse_string('5.0 - 2.0', parseAll=True) expr = parse_action[0].expr() assert isinstance(expr, E.Minus) assert expr.evaluate(env).value == 3 # times - parse_action = CSP.expr.parseString('4.0 * 2.0', parseAll=True) + parse_action = CSP.expr.parse_string('4.0 * 2.0', parseAll=True) expr = parse_action[0].expr() assert isinstance(expr, E.Times) assert expr.evaluate(env).value == 8 # division and infinity - parse_action = CSP.expr.parseString('6.0 / 2.0', parseAll=True) + parse_action = CSP.expr.parse_string('6.0 / 2.0', parseAll=True) expr = parse_action[0].expr() assert isinstance(expr, E.Divide) assert expr.evaluate(env).value == 3 - parse_action = CSP.expr.parseString('1/MathML:infinity', parseAll=True) + parse_action = CSP.expr.parse_string('1/MathML:infinity', parseAll=True) expr = parse_action[0].expr() assert isinstance(expr, E.Divide) assert expr.evaluate(env).value == 0 # power - parse_action = CSP.expr.parseString('4.0 ^ 2.0', parseAll=True) + parse_action = CSP.expr.parse_string('4.0 ^ 2.0', parseAll=True) expr = parse_action[0].expr() assert isinstance(expr, E.Power) assert expr.evaluate(env).value == 16 @@ -74,204 +74,204 @@ def test_parsing_math_operations(): def test_parsing_logical_operations(): # greater than - parse_action = CSP.expr.parseString('4.0 > 2.0', parseAll=True) + parse_action = CSP.expr.parse_string('4.0 > 2.0', parseAll=True) expr = parse_action[0].expr() assert isinstance(expr, E.Gt) env = Environment() assert expr.evaluate(env).value == 1 - parse_action = CSP.expr.parseString('2.0 > 2.0', parseAll=True) + parse_action = CSP.expr.parse_string('2.0 > 2.0', parseAll=True) expr = parse_action[0].expr() assert isinstance(expr, E.Gt) assert expr.evaluate(env).value == 0 # less than - parse_action = CSP.expr.parseString('4.0 < 2.0', parseAll=True) + parse_action = CSP.expr.parse_string('4.0 < 2.0', parseAll=True) expr = parse_action[0].expr() assert isinstance(expr, E.Lt) assert expr.evaluate(env).value == 0 - parse_action = CSP.expr.parseString('2.0 < 2.0', parseAll=True) + parse_action = CSP.expr.parse_string('2.0 < 2.0', parseAll=True) expr = parse_action[0].expr() assert isinstance(expr, E.Lt) assert expr.evaluate(env).value == 0 # less than or equal to - parse_action = CSP.expr.parseString('4.0 <= 2.0', parseAll=True) + parse_action = CSP.expr.parse_string('4.0 <= 2.0', parseAll=True) expr = parse_action[0].expr() assert isinstance(expr, E.Leq) assert expr.evaluate(env).value == 0 - parse_action = CSP.expr.parseString('2.0 <= 2.0', parseAll=True) + parse_action = CSP.expr.parse_string('2.0 <= 2.0', parseAll=True) expr = parse_action[0].expr() assert isinstance(expr, E.Leq) assert expr.evaluate(env).value == 1 - parse_action = CSP.expr.parseString('1.0 <= 2.0', parseAll=True) + parse_action = CSP.expr.parse_string('1.0 <= 2.0', parseAll=True) expr = parse_action[0].expr() assert isinstance(expr, E.Leq) assert expr.evaluate(env).value == 1 # equal to - parse_action = CSP.expr.parseString('2.0 == 2.0', parseAll=True) + parse_action = CSP.expr.parse_string('2.0 == 2.0', parseAll=True) expr = parse_action[0].expr() assert isinstance(expr, E.Eq) assert expr.evaluate(env).value == 1 # not equal to and not a number - parse_action = CSP.expr.parseString('2.0 != 2.0', parseAll=True) + parse_action = CSP.expr.parse_string('2.0 != 2.0', parseAll=True) expr = parse_action[0].expr() assert isinstance(expr, E.Neq) assert expr.evaluate(env).value == 0 - parse_action = CSP.expr.parseString('1.0 != 2.0', parseAll=True) + parse_action = CSP.expr.parse_string('1.0 != 2.0', parseAll=True) expr = parse_action[0].expr() assert isinstance(expr, E.Neq) assert expr.evaluate(env).value == 1 - parse_action = CSP.expr.parseString('MathML:notanumber != MathML:notanumber', parseAll=True) + parse_action = CSP.expr.parse_string('MathML:notanumber != MathML:notanumber', parseAll=True) expr = parse_action[0].expr() assert isinstance(expr, E.Neq) assert expr.evaluate(env).value == 1 # and - parse_action = CSP.expr.parseString('1.0 && 1.0', parseAll=True) + parse_action = CSP.expr.parse_string('1.0 && 1.0', parseAll=True) expr = parse_action[0].expr() assert isinstance(expr, E.And) assert expr.evaluate(env).value == 1 - parse_action = CSP.expr.parseString('0.0 && 1.0', parseAll=True) + parse_action = CSP.expr.parse_string('0.0 && 1.0', parseAll=True) expr = parse_action[0].expr() assert isinstance(expr, E.And) assert expr.evaluate(env).value == 0 # or and true or false - parse_action = CSP.expr.parseString('MathML:true || MathML:true', parseAll=True) + parse_action = CSP.expr.parse_string('MathML:true || MathML:true', parseAll=True) expr = parse_action[0].expr() assert isinstance(expr, E.Or) assert expr.evaluate(env).value == 1 - parse_action = CSP.expr.parseString('MathML:false || MathML:true', parseAll=True) + parse_action = CSP.expr.parse_string('MathML:false || MathML:true', parseAll=True) expr = parse_action[0].expr() assert isinstance(expr, E.Or) assert expr.evaluate(env).value == 1 - parse_action = CSP.expr.parseString('MathML:false || MathML:false', parseAll=True) + parse_action = CSP.expr.parse_string('MathML:false || MathML:false', parseAll=True) expr = parse_action[0].expr() assert isinstance(expr, E.Or) assert expr.evaluate(env).value == 0 # not - parse_action = CSP.expr.parseString('not 1', parseAll=True) + parse_action = CSP.expr.parse_string('not 1', parseAll=True) expr = parse_action[0].expr() assert isinstance(expr, E.Not) assert expr.evaluate(env).value == 0 - parse_action = CSP.expr.parseString('not 0', parseAll=True) + parse_action = CSP.expr.parse_string('not 0', parseAll=True) expr = parse_action[0].expr() assert isinstance(expr, E.Not) assert expr.evaluate(env).value == 1 def test_parsing_complicated_math(): - parse_action = CSP.expr.parseString('1.0 + (4.0 * 2.0)', parseAll=True) + parse_action = CSP.expr.parse_string('1.0 + (4.0 * 2.0)', parseAll=True) expr = parse_action[0].expr() env = Environment() assert expr.evaluate(env).value == 9 - parse_action = CSP.expr.parseString('(2.0 ^ 3.0) + (5.0 * 2.0) - 10.0', parseAll=True) + parse_action = CSP.expr.parse_string('(2.0 ^ 3.0) + (5.0 * 2.0) - 10.0', parseAll=True) expr = parse_action[0].expr() assert expr.evaluate(env).value == 8 - parse_action = CSP.expr.parseString('2.0 ^ 3.0 == 5.0 + 3.0', parseAll=True) + parse_action = CSP.expr.parse_string('2.0 ^ 3.0 == 5.0 + 3.0', parseAll=True) expr = parse_action[0].expr() assert expr.evaluate(env).value == 1 def test_parsing_MathML_funcs(): # ceiling - parse_action = CSP.expr.parseString('MathML:ceiling(1.2)', parseAll=True) + parse_action = CSP.expr.parse_string('MathML:ceiling(1.2)', parseAll=True) expr = parse_action[0].expr() assert isinstance(expr, E.Ceiling) env = Environment() assert expr.evaluate(env).value == 2 # floor - parse_action = CSP.expr.parseString('MathML:floor(1.8)', parseAll=True) + parse_action = CSP.expr.parse_string('MathML:floor(1.8)', parseAll=True) expr = parse_action[0].expr() assert isinstance(expr, E.Floor) assert expr.evaluate(env).value == 1 # ln and exponentiale value - parse_action = CSP.expr.parseString('MathML:ln(MathML:exponentiale)', parseAll=True) + parse_action = CSP.expr.parse_string('MathML:ln(MathML:exponentiale)', parseAll=True) expr = parse_action[0].expr() assert isinstance(expr, E.Ln) assert expr.evaluate(env).value == pytest.approx(1) # log - parse_action = CSP.expr.parseString('MathML:log(10)', parseAll=True) + parse_action = CSP.expr.parse_string('MathML:log(10)', parseAll=True) expr = parse_action[0].expr() assert isinstance(expr, E.Log) assert expr.evaluate(env).value == 1 # exp and infinity value - parse_action = CSP.expr.parseString('MathML:exp(MathML:ln(10))', parseAll=True) + parse_action = CSP.expr.parse_string('MathML:exp(MathML:ln(10))', parseAll=True) expr = parse_action[0].expr() assert isinstance(expr, E.Exp) assert expr.evaluate(env).value == pytest.approx(10) # abs - parse_action = CSP.expr.parseString('MathML:abs(-10)', parseAll=True) + parse_action = CSP.expr.parse_string('MathML:abs(-10)', parseAll=True) expr = parse_action[0].expr() assert isinstance(expr, E.Abs) assert expr.evaluate(env).value == pytest.approx(10) # root - parse_action = CSP.expr.parseString('MathML:root(100)', parseAll=True) + parse_action = CSP.expr.parse_string('MathML:root(100)', parseAll=True) expr = parse_action[0].expr() assert isinstance(expr, E.Root) assert expr.evaluate(env).value == pytest.approx(10) # rem - parse_action = CSP.expr.parseString('MathML:rem(100, 97)', parseAll=True) + parse_action = CSP.expr.parse_string('MathML:rem(100, 97)', parseAll=True) expr = parse_action[0].expr() assert isinstance(expr, E.Rem) assert expr.evaluate(env).value == pytest.approx(3) # max - parse_action = CSP.expr.parseString('MathML:max(100, 97, 105)', parseAll=True) + parse_action = CSP.expr.parse_string('MathML:max(100, 97, 105)', parseAll=True) expr = parse_action[0].expr() assert isinstance(expr, E.Max) assert expr.evaluate(env).value == pytest.approx(105) # min and pi value - parse_action = CSP.expr.parseString('MathML:min(100, 97, 105, MathML:pi)', parseAll=True) + parse_action = CSP.expr.parse_string('MathML:min(100, 97, 105, MathML:pi)', parseAll=True) expr = parse_action[0].expr() assert isinstance(expr, E.Min) assert expr.evaluate(env).value == pytest.approx(3.1415926535) # null - parse_action = CSP.expr.parseString('null', parseAll=True) + parse_action = CSP.expr.parse_string('null', parseAll=True) expr = parse_action[0].expr() assert isinstance(expr, E.Const) assert isinstance(expr.value, V.Null) def test_parsing_if(): - parse_action = CSP.expr.parseString('if 1 then 2 + 3 else 4-2', parseAll=True) + parse_action = CSP.expr.parse_string('if 1 then 2 + 3 else 4-2', parseAll=True) expr = parse_action[0].expr() assert isinstance(expr, E.If) env = Environment() assert expr.evaluate(env).value == pytest.approx(5) - parse_action = CSP.expr.parseString('if MathML:false then 2 + 3 else 4-2', parseAll=True) + parse_action = CSP.expr.parse_string('if MathML:false then 2 + 3 else 4-2', parseAll=True) expr = parse_action[0].expr() assert isinstance(expr, E.If) assert expr.evaluate(env).value == pytest.approx(2) def test_parsing_tuple_expression(): - parse_action = CSP.expr.parseString('(1, 2)', parseAll=True) + parse_action = CSP.expr.parse_string('(1, 2)', parseAll=True) expr = parse_action[0].expr() assert isinstance(expr, E.TupleExpression) env = Environment() @@ -280,7 +280,7 @@ def test_parsing_tuple_expression(): def test_parsing_array(): - parse_action = CSP.expr.parseString('[1, 2, 3]', parseAll=True) + parse_action = CSP.expr.parse_string('[1, 2, 3]', parseAll=True) expr = parse_action[0].expr() assert isinstance(expr, E.NewArray) env = Environment() @@ -289,50 +289,50 @@ def test_parsing_array(): def test_parsing_accessor(): # is simple true - parse_action = CSP.expr.parseString('1.IS_SIMPLE_VALUE', parseAll=True) + parse_action = CSP.expr.parse_string('1.IS_SIMPLE_VALUE', parseAll=True) expr = parse_action[0].expr() assert isinstance(expr, E.Accessor) env = Environment() assert expr.evaluate(env).value == pytest.approx(1) # is array true - parse_action = CSP.expr.parseString('[1, 2].IS_ARRAY', parseAll=True) + parse_action = CSP.expr.parse_string('[1, 2].IS_ARRAY', parseAll=True) expr = parse_action[0].expr() assert isinstance(expr, E.Accessor) assert expr.evaluate(env).value == pytest.approx(1) # is tuple true - parse_action = CSP.expr.parseString('(1, 2).IS_TUPLE', parseAll=True) + parse_action = CSP.expr.parse_string('(1, 2).IS_TUPLE', parseAll=True) expr = parse_action[0].expr() assert isinstance(expr, E.Accessor) assert expr.evaluate(env).value == pytest.approx(1) # is tuple false - parse_action = CSP.expr.parseString('[1, 2].IS_TUPLE', parseAll=True) + parse_action = CSP.expr.parse_string('[1, 2].IS_TUPLE', parseAll=True) expr = parse_action[0].expr() assert isinstance(expr, E.Accessor) assert expr.evaluate(env).value == pytest.approx(0) # multiple accessors- .shape.is_array - parse_action = CSP.expr.parseString('[1, 2, 3].SHAPE.IS_ARRAY', parseAll=True) + parse_action = CSP.expr.parse_string('[1, 2, 3].SHAPE.IS_ARRAY', parseAll=True) expr = parse_action[0].expr() assert isinstance(expr, E.Accessor) assert expr.evaluate(env).value == pytest.approx(1) # shape - parse_action = CSP.expr.parseString('[1, 2, 3].SHAPE', parseAll=True) + parse_action = CSP.expr.parse_string('[1, 2, 3].SHAPE', parseAll=True) expr = parse_action[0].expr() assert isinstance(expr, E.Accessor) np.testing.assert_array_equal(expr.evaluate(env).array, np.array([3])) # number of dimensions - parse_action = CSP.expr.parseString('[1, 2, 3].NUM_DIMS', parseAll=True) + parse_action = CSP.expr.parse_string('[1, 2, 3].NUM_DIMS', parseAll=True) expr = parse_action[0].expr() assert isinstance(expr, E.Accessor) np.testing.assert_array_equal(expr.evaluate(env).array, np.array([1])) # number of elements - parse_action = CSP.expr.parseString('[1, 2, 3].NUM_ELEMENTS', parseAll=True) + parse_action = CSP.expr.parse_string('[1, 2, 3].NUM_ELEMENTS', parseAll=True) expr = parse_action[0].expr() assert isinstance(expr, E.Accessor) np.testing.assert_array_equal(expr.evaluate(env).array, np.array([3])) @@ -340,7 +340,7 @@ def test_parsing_accessor(): def test_statements(): # test assertion - parse_action = CSP.assert_stmt.parseString("assert 1", parseAll=True) + parse_action = CSP.assert_stmt.parse_string("assert 1", parseAll=True) expr = parse_action[0].expr() assert isinstance(expr, S.Assert) env = Environment() @@ -350,14 +350,14 @@ def test_statements(): # assign one variable to an expression env = Environment() - parse_action = CSP.assign_stmt.parseString('a = 1.0 + 2.0', parseAll=True) + parse_action = CSP.assign_stmt.parse_string('a = 1.0 + 2.0', parseAll=True) expr = parse_action[0].expr() assert isinstance(expr, S.Assign) expr.evaluate(env) assert env.look_up('a').value == 3 # assign two variables at once to numbers - parse_action = CSP.assign_stmt.parseString('b, c = 1.0, 2.0', parseAll=True) + parse_action = CSP.assign_stmt.parse_string('b, c = 1.0, 2.0', parseAll=True) expr = parse_action[0].expr() assert isinstance(expr, S.Assign) expr.evaluate(env) @@ -365,7 +365,7 @@ def test_statements(): assert env.look_up('c').value == 2 # assign three variables at once to expressions - parse_action = CSP.assign_stmt.parseString('d, e, f = 1.0, 2 + 2.0, (3*4)-2', parseAll=True) + parse_action = CSP.assign_stmt.parse_string('d, e, f = 1.0, 2 + 2.0, (3*4)-2', parseAll=True) expr = parse_action[0].expr() assert isinstance(expr, S.Assign) expr.evaluate(env) @@ -376,21 +376,21 @@ def test_statements(): # test return # return one number - parse_action = CSP.return_stmt.parseString('return 1', parseAll=True) + parse_action = CSP.return_stmt.parse_string('return 1', parseAll=True) expr = parse_action[0].expr() assert isinstance(expr, S.Return) results = expr.evaluate(env) assert results.value == 1 # return one expression involving variables - parse_action = CSP.return_stmt.parseString('return d + e', parseAll=True) + parse_action = CSP.return_stmt.parse_string('return d + e', parseAll=True) expr = parse_action[0].expr() assert isinstance(expr, S.Return) results = expr.evaluate(env) assert results.value == 5 # return two numbers - parse_action = CSP.return_stmt.parseString('return 1, 3', parseAll=True) + parse_action = CSP.return_stmt.parse_string('return 1, 3', parseAll=True) expr = parse_action[0].expr() assert isinstance(expr, S.Return) result1, result2 = expr.evaluate(env).values @@ -398,7 +398,7 @@ def test_statements(): assert result2.value == 3 # test statement list - parse_action = CSP.stmt_list.parseString('z = lambda a: a+2\nassert z(2) == 4', parseAll=True) + parse_action = CSP.stmt_list.parse_string('z = lambda a: a+2\nassert z(2) == 4', parseAll=True) result = parse_action[0].expr() env.execute_statements(result) @@ -406,7 +406,7 @@ def test_statements(): def test_parsing_lambda(): # no default, one variable env = Environment() - parse_action = CSP.lambda_expr.parseString('lambda a: a + 1', parseAll=True) + parse_action = CSP.lambda_expr.parse_string('lambda a: a + 1', parseAll=True) expr = parse_action[0].expr() assert isinstance(expr, E.LambdaExpression) result = E.FunctionCall(expr, [E.N(3)]).evaluate(env) @@ -414,7 +414,7 @@ def test_parsing_lambda(): # no default, two variables env = Environment() - parse_action = CSP.lambda_expr.parseString('lambda a, b: a * b', parseAll=True) + parse_action = CSP.lambda_expr.parse_string('lambda a, b: a * b', parseAll=True) expr = parse_action[0].expr() assert isinstance(expr, E.LambdaExpression) result = E.FunctionCall(expr, [E.N(4), E.N(2)]).evaluate(env) @@ -422,7 +422,7 @@ def test_parsing_lambda(): # test lambda with defaults unused env = Environment() - parse_action = CSP.lambda_expr.parseString('lambda a=2, b=3: a + b', parseAll=True) + parse_action = CSP.lambda_expr.parse_string('lambda a=2, b=3: a + b', parseAll=True) expr = parse_action[0].expr() assert isinstance(expr, E.LambdaExpression) result = E.FunctionCall(expr, [E.N(2), E.N(6)]).evaluate(env) @@ -430,7 +430,7 @@ def test_parsing_lambda(): # test lambda with defaults used env = Environment() - parse_action = CSP.lambda_expr.parseString('lambda a=2, b=3: a + b', parseAll=True) + parse_action = CSP.lambda_expr.parse_string('lambda a=2, b=3: a + b', parseAll=True) expr = parse_action[0].expr() assert isinstance(expr, E.LambdaExpression) result = E.FunctionCall(expr, [E.Const(V.DefaultParameter())]).evaluate(env) @@ -439,21 +439,21 @@ def test_parsing_lambda(): def test_array_comprehensions(): env = Environment() - parse_action = CSP.array.parseString('[i for i in 0:10]', parseAll=True) + parse_action = CSP.array.parse_string('[i for i in 0:10]', parseAll=True) expr = parse_action[0].expr() assert isinstance(expr, E.NewArray) result = expr.evaluate(env) predicted = np.array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]) np.testing.assert_array_almost_equal(predicted, result.array) - parse_action = CSP.array.parseString('[i*2 for i in 0:2:4]', parseAll=True) + parse_action = CSP.array.parse_string('[i*2 for i in 0:2:4]', parseAll=True) expr = parse_action[0].expr() assert isinstance(expr, E.NewArray) result = expr.evaluate(env) predicted = np.array([0, 4]) np.testing.assert_array_almost_equal(predicted, result.array) - parse_action = CSP.array.parseString('[i+j*5 for i in 1:3 for j in 2:4]', parseAll=True) + parse_action = CSP.array.parse_string('[i+j*5 for i in 1:3 for j in 2:4]', parseAll=True) expr = parse_action[0].expr() assert isinstance(expr, E.NewArray) result = expr.evaluate(env) @@ -463,7 +463,7 @@ def test_array_comprehensions(): env = Environment() arr = V.Array(np.arange(10)) env.define_name('arr', arr) - parse_action = CSP.expr.parseString('arr[1:2:10]', parseAll=True) + parse_action = CSP.expr.parse_string('arr[1:2:10]', parseAll=True) expr = parse_action[0].expr() assert isinstance(expr, E.View) result = expr.evaluate(env) @@ -475,21 +475,21 @@ def test_parsing_views(): env = Environment() view_arr = V.Array(np.arange(10)) env.define_name('view_arr', view_arr) - view_parse_action = CSP.expr.parseString('view_arr[4]', parseAll=True) + view_parse_action = CSP.expr.parse_string('view_arr[4]', parseAll=True) expr = view_parse_action[0].expr() assert isinstance(expr, E.View) result = expr.evaluate(env) predicted = np.array(4) np.testing.assert_array_almost_equal(result.array, predicted) - view_parse_action = CSP.expr.parseString('view_arr[2:5]', parseAll=True) + view_parse_action = CSP.expr.parse_string('view_arr[2:5]', parseAll=True) expr = view_parse_action[0].expr() assert isinstance(expr, E.View) result = expr.evaluate(env) predicted = np.array([2, 3, 4]) np.testing.assert_array_almost_equal(result.array, predicted) - view_parse_action = CSP.expr.parseString('view_arr[1:2:10]', parseAll=True) + view_parse_action = CSP.expr.parse_string('view_arr[1:2:10]', parseAll=True) expr = view_parse_action[0].expr() assert isinstance(expr, E.View) result = expr.evaluate(env) @@ -499,28 +499,28 @@ def test_parsing_views(): env = Environment() view_arr = V.Array(np.array([[0, 1, 2, 3, 4], [7, 8, 12, 3, 9]])) env.define_name('view_arr', view_arr) - view_parse_action = CSP.expr.parseString('view_arr[1$2]', parseAll=True) + view_parse_action = CSP.expr.parse_string('view_arr[1$2]', parseAll=True) expr = view_parse_action[0].expr() assert isinstance(expr, E.View) result = expr.evaluate(env) predicted = np.array([2, 12]) np.testing.assert_array_almost_equal(result.array, predicted) - view_parse_action = CSP.expr.parseString('view_arr[1$(3):]', parseAll=True) + view_parse_action = CSP.expr.parse_string('view_arr[1$(3):]', parseAll=True) expr = view_parse_action[0].expr() assert isinstance(expr, E.View) result = expr.evaluate(env) predicted = np.array([[3, 4], [3, 9]]) np.testing.assert_array_almost_equal(result.array, predicted) - view_parse_action = CSP.expr.parseString('view_arr[*$1]', parseAll=True) + view_parse_action = CSP.expr.parse_string('view_arr[*$1]', parseAll=True) expr = view_parse_action[0].expr() assert isinstance(expr, E.View) result = expr.evaluate(env) predicted = np.array(8) np.testing.assert_array_almost_equal(result.array, predicted) - view_parse_action = CSP.expr.parseString('view_arr[*$1][0]', parseAll=True) + view_parse_action = CSP.expr.parse_string('view_arr[*$1][0]', parseAll=True) expr = view_parse_action[0].expr() assert isinstance(expr, E.View) result = expr.evaluate(env) @@ -532,7 +532,7 @@ def parsing_find_and_index_array(): env = Environment() arr = V.Array(np.arange(4)) env.define_name('arr', arr) - find_parse_action = CSP.expr.parseString('find(arr)', parseAll=True) + find_parse_action = CSP.expr.parse_string('find(arr)', parseAll=True) expr = find_parse_action[0].expr() assert isinstance(expr, E.Find) find_result = expr.evaluate(env) @@ -544,18 +544,18 @@ def parsing_find_and_index_array(): index_arr = V.Array(np.arange(1, 26).reshape(5, 5)) env.define_name('find_arr', find_arr) env.define_name('index_arr', index_arr) - find_parse_action = CSP.expr.parseString('find(find_arr)', parseAll=True) + find_parse_action = CSP.expr.parse_string('find(find_arr)', parseAll=True) expr = find_parse_action[0].expr() indices_from_find = expr.evaluate(env) env.define_name('indices_from_find', indices_from_find) - index_parse_action = CSP.expr.parseString('index_arr{indices_from_find, 1, pad:1=0}') + index_parse_action = CSP.expr.parse_string('index_arr{indices_from_find, 1, pad:1=0}') expr = index_parse_action[0].expr() index_result = expr.interpret(env) predicted = np.array([[1, 3], [7, 10], [13, 14], [19, 20], [25, 0]]) np.testing.assert_array_almost_equal(index_result.array, predicted) env.define_name('find_result', find_result) - index_parse_action = CSP.expr.parseString('arr{find_result}', parseAll=True) + index_parse_action = CSP.expr.parse_string('arr{find_result}', parseAll=True) expr = index_parse_action[0].expr() assert isinstance(expr, E.Index) result = expr.interpret(env) @@ -564,17 +564,17 @@ def parsing_find_and_index_array(): arr1 = V.Array(np.array([[1, 0, 2], [0, 3, 0], [1, 1, 1]])) env.define_name('arr1', arr1) - find_parse_action = CSP.expr.parseString('find(arr1)', parseAll=True) + find_parse_action = CSP.expr.parse_string('find(arr1)', parseAll=True) expr = find_parse_action[0].expr() indices = expr.evaluate(env) env.define_name('indices', indices) - index_parse_action = CSP.expr.parseString('arr1{indices, 1, pad:1=45}', parseAll=True) + index_parse_action = CSP.expr.parse_string('arr1{indices, 1, pad:1=45}', parseAll=True) expr = index_parse_action[0].expr() result = expr.interpret(env) predicted = np.array(np.array([[1, 2, 45], [3, 45, 45], [1, 1, 1]])) np.testing.assert_array_almost_equal(predicted, result.array) - index_parse_action = CSP.expr.parseString('arr1{indices, 1, shrink: 1}', parseAll=True) + index_parse_action = CSP.expr.parse_string('arr1{indices, 1, shrink: 1}', parseAll=True) expr = index_parse_action[0].expr() result = expr.interpret(env) predicted = np.array(np.array([[1], [3], [1]])) @@ -587,10 +587,10 @@ def test_parsing_map_and_fold(): arr = V.Array(np.arange(4)) arr2 = V.Array(np.array([4, 5, 6, 7])) env.define_names(['arr', 'arr2'], [arr, arr2]) - lambda_parse_action = CSP.lambda_expr.parseString('lambda a, b: a + b', parseAll=True) + lambda_parse_action = CSP.lambda_expr.parse_string('lambda a, b: a + b', parseAll=True) add_function = lambda_parse_action[0].expr() env.define_name('add_function', add_function.interpret(env)) - map_parse_action = CSP.expr.parseString('map(add_function, arr, arr2)', parseAll=True) + map_parse_action = CSP.expr.parse_string('map(add_function, arr, arr2)', parseAll=True) expr = map_parse_action[0].expr() assert isinstance(expr, E.Map) result = expr.evaluate(env) @@ -598,7 +598,7 @@ def test_parsing_map_and_fold(): np.testing.assert_array_almost_equal(predicted, result.array) # test fold - fold_parse_action = CSP.expr.parseString('fold(add_function, arr, 2, 0)', parseAll=True) + fold_parse_action = CSP.expr.parse_string('fold(add_function, arr, 2, 0)', parseAll=True) expr = fold_parse_action[0].expr() assert isinstance(expr, E.Fold) result = expr.evaluate(env) @@ -607,7 +607,7 @@ def test_parsing_map_and_fold(): arr3 = V.Array(np.array([[1, 2, 3], [3, 4, 5]])) env.define_name('arr3', arr3) - fold_parse_action = CSP.expr.parseString('fold(add_function, arr3, 4, 1)') + fold_parse_action = CSP.expr.parse_string('fold(add_function, arr3, 4, 1)') expr = fold_parse_action[0].expr() result = expr.evaluate(env) predicted = np.array([[10], [16]]) @@ -616,12 +616,12 @@ def test_parsing_map_and_fold(): def test_protocol_and_post_processing(): env = Environment() - parse_action = CSP.post_processing.parseString('post-processing{a=2}') + parse_action = CSP.post_processing.parse_string('post-processing{a=2}') expr = parse_action[0].expr() result = env.execute_statements(expr) assert env.look_up('a').value == 2 - parse_action = CSP.lambda_expr.parseString('lambda t: 0*t') + parse_action = CSP.lambda_expr.parse_string('lambda t: 0*t') expr = parse_action[0].expr() result = E.FunctionCall(expr, [E.NewArray(E.NewArray(E.N(1), E.N(2), E.N(3)), E.NewArray(E.N(3), E.N(4), E.N(5)))]).evaluate(env) @@ -631,36 +631,36 @@ def test_protocol_and_post_processing(): def test_get_used_vars(): env = Environment() - parse_action = CSP.expr.parseString('[i for i in 0:10]', parseAll=True) + parse_action = CSP.expr.parse_string('[i for i in 0:10]', parseAll=True) expr = parse_action[0].expr() used_vars = expr.get_used_variables() assert used_vars == set() - parse_action = CSP.array.parseString('[i*2 for j in 0:2:4]', parseAll=True) + parse_action = CSP.array.parse_string('[i*2 for j in 0:2:4]', parseAll=True) expr = parse_action[0].expr() used_vars = expr.get_used_variables() assert used_vars == set(['i']) - parse_action = CSP.array.parseString('[i+j*5 for j in 1:3 for l in 2:4]', parseAll=True) + parse_action = CSP.array.parse_string('[i+j*5 for j in 1:3 for l in 2:4]', parseAll=True) expr = parse_action[0].expr() used_vars = expr.get_used_variables() assert used_vars == set(['i']) env.define_name('a', V.Simple(1)) env.define_name('b', V.Simple(2)) - parse_action = CSP.expr.parseString('a + b', parseAll=True) + parse_action = CSP.expr.parse_string('a + b', parseAll=True) expr = parse_action[0].expr() used_vars = expr.get_used_variables() assert used_vars == set(['a', 'b']) - parse_action = CSP.expr.parseString('if a then b else 0', parseAll=True) + parse_action = CSP.expr.parse_string('if a then b else 0', parseAll=True) expr = parse_action[0].expr() used_vars = expr.get_used_variables() assert used_vars == set(['a', 'b']) def test_parsing_inputs(): - parse_action = CSP.inputs.parseString('inputs{X=1}', parseAll=True) + parse_action = CSP.inputs.parse_string('inputs{X=1}', parseAll=True) expr = parse_action[0].expr() for each in expr: assert isinstance(each, S.Assign) @@ -668,7 +668,7 @@ def test_parsing_inputs(): def test_parsing_ranges(): # test parsing uniform range - parse_action = CSP.range.parseString('range t units s uniform 0:10', parseAll=True) + parse_action = CSP.range.parse_string('range t units s uniform 0:10', parseAll=True) expr = parse_action[0].expr() expr.initialise(Environment()) assert isinstance(expr, Ranges.UniformRange) @@ -677,7 +677,7 @@ def test_parsing_ranges(): assert r[i] == pytest.approx(num) # test parsing vector range - parse_action = CSP.range.parseString('range run units dimensionless vector [1, 3, 4]', parseAll=True) + parse_action = CSP.range.parse_string('range run units dimensionless vector [1, 3, 4]', parseAll=True) expr = parse_action[0].expr() assert isinstance(expr, Ranges.VectorRange) expr.initialise(Environment()) @@ -686,7 +686,7 @@ def test_parsing_ranges(): assert r[i] == pytest.approx(num) # test parsing while range - parse_action = CSP.range.parseString('range rpt units dimensionless while rpt < 5', parseAll=True) + parse_action = CSP.range.parse_string('range rpt units dimensionless while rpt < 5', parseAll=True) expr = parse_action[0].expr() assert isinstance(expr, Ranges.While) expr.initialise(Environment()) @@ -697,7 +697,7 @@ def test_parsing_ranges(): def test_parsing_simulations(): # test parsing timecourse simulation - parse_action = CSP.simulation.parseString( + parse_action = CSP.simulation.parse_string( 'simulation sim = timecourse { range time units ms uniform 0:10 }', parseAll=True) expr = parse_action[0].expr() expr.initialise() @@ -708,7 +708,7 @@ def test_parsing_simulations(): np.testing.assert_array_almost_equal(run_sim.look_up('y').array, np.array([t * 5 for t in range(11)])) # test parsing tasks - parse_action = CSP.tasks.parseString("""tasks { + parse_action = CSP.tasks.parse_string("""tasks { simulation timecourse { range time units second uniform 1:10 } simulation timecourse { range time units second uniform 10:20 } }""", parseAll=True) @@ -718,36 +718,36 @@ def test_parsing_simulations(): def test_parsing_modifiers(): - parse_action = CSP.modifier_when.parseString('at start', parseAll=True) + parse_action = CSP.modifier_when.parse_string('at start', parseAll=True) expr = parse_action[0].expr() assert expr == Modifiers.AbstractModifier.START_ONLY - parse_action = CSP.modifier_when.parseString('at each loop', parseAll=True) + parse_action = CSP.modifier_when.parse_string('at each loop', parseAll=True) expr = parse_action[0].expr() assert expr == Modifiers.AbstractModifier.EACH_LOOP - parse_action = CSP.modifier_when.parseString('at end', parseAll=True) + parse_action = CSP.modifier_when.parse_string('at end', parseAll=True) expr = parse_action[0].expr() assert expr == Modifiers.AbstractModifier.END_ONLY - parse_action = CSP.modifier.parseString('at start set model:a = 5.0', parseAll=True) + parse_action = CSP.modifier.parse_string('at start set model:a = 5.0', parseAll=True) expr = parse_action[0].expr() assert isinstance(expr, Modifiers.SetVariable) assert expr.variable_name == 'model:a' assert expr.value_expr.value.value == 5 - parse_action = CSP.modifier.parseString('at start set model:t = 10.0', parseAll=True) + parse_action = CSP.modifier.parse_string('at start set model:t = 10.0', parseAll=True) expr = parse_action[0].expr() assert isinstance(expr, Modifiers.SetVariable) assert expr.variable_name == 'model:t' assert expr.value_expr.value.value == 10 - parse_action = CSP.modifier.parseString('at start save as state_name', parseAll=True) + parse_action = CSP.modifier.parse_string('at start save as state_name', parseAll=True) expr = parse_action[0].expr() assert isinstance(expr, Modifiers.SaveState) assert expr.state_name == 'state_name' - parse_action = CSP.modifier.parseString('at start reset to state_name', parseAll=True) + parse_action = CSP.modifier.parse_string('at start reset to state_name', parseAll=True) expr = parse_action[0].expr() assert isinstance(expr, Modifiers.ResetState) assert expr.state_name == 'state_name' From 5dd39c5a68a2f17e8e385e77b22aa2c06a4b150a Mon Sep 17 00:00:00 2001 From: Kwabena Amponsah Date: Wed, 18 Mar 2026 23:10:27 +0000 Subject: [PATCH 17/28] #236 Update protocol setup --- fc/protocol.py | 35 +++++++++++++++++------------------ pyproject.toml | 1 - setup.py | 9 ++++----- 3 files changed, 21 insertions(+), 24 deletions(-) diff --git a/fc/protocol.py b/fc/protocol.py index 8cdc3d55..60c62a4d 100644 --- a/fc/protocol.py +++ b/fc/protocol.py @@ -30,18 +30,15 @@ # Setup script SETUP_PY = ''' import numpy - -from setuptools import setup from cython import inline -from Cython.Distutils import build_ext -from Cython.Distutils.extension import Extension +from Cython.Build import cythonize +from setuptools import Extension, setup -SUNDIALS_MAJOR = inline(\''' - cdef extern from *: +FC_SUNDIALS_MAJOR = inline( + \''' + cdef extern from "": """ - #include - #ifndef SUNDIALS_VERSION_MAJOR #define SUNDIALS_VERSION_MAJOR 2 #endif @@ -49,22 +46,24 @@ int SUNDIALS_VERSION_MAJOR return SUNDIALS_VERSION_MAJOR - \''') + \''' +) -ext_modules=[ +extensions = [ Extension( - '%(module_name)s', - sources=['%(model_file)s'], - include_dirs=[numpy.get_include(), '%(fcpath)s'], - libraries=['sundials_cvode', 'sundials_nvecserial', 'm'], - cython_compile_time_env={'FC_SUNDIALS_MAJOR': SUNDIALS_MAJOR}, + name="%(module_name)s", + sources=["%(model_file)s"], + include_dirs=["%(fcpath)s", numpy.get_include()], + libraries=["sundials_cvode", "sundials_nvecserial"], ), ] setup( - name='%(module_name)s', - cmdclass={'build_ext': build_ext}, - ext_modules=ext_modules, + name="%(module_name)s", + ext_modules=cythonize( + extensions, + compile_time_env={"FC_SUNDIALS_MAJOR": FC_SUNDIALS_MAJOR}, + ), ) ''' diff --git a/pyproject.toml b/pyproject.toml index e8b3987c..8c8799aa 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -74,4 +74,3 @@ line_length = 120 known_first_party = "fc" default_section = "THIRDPARTY" order_by_type = "False" -not_skip = "__init__.py" diff --git a/setup.py b/setup.py index d155420f..51e68dbc 100644 --- a/setup.py +++ b/setup.py @@ -6,14 +6,13 @@ to have been set up before running. """ -from setuptools import Extension, setup # Must come before Cython! - import numpy from cython import inline from Cython.Build import cythonize +from setuptools import Extension, setup # Detect major sundials version (defaults to 2) -fc_sundials_major = inline( +FC_SUNDIALS_MAJOR = inline( ''' cdef extern from "": """ @@ -26,7 +25,7 @@ return SUNDIALS_VERSION_MAJOR ''' ) -print("Building for Sundials " + str(fc_sundials_major) + ".x") +print("Building for Sundials " + str(FC_SUNDIALS_MAJOR) + ".x") # Define Cython modules extensions = [ @@ -46,6 +45,6 @@ zip_safe=False, ext_modules=cythonize( extensions, - compile_time_env={"FC_SUNDIALS_MAJOR": fc_sundials_major}, + compile_time_env={"FC_SUNDIALS_MAJOR": FC_SUNDIALS_MAJOR}, ), ) From e9fd42afebe123aefbb3f1ac4ec54eb663c4aad7 Mon Sep 17 00:00:00 2001 From: Kwabena Amponsah Date: Wed, 18 Mar 2026 23:32:17 +0000 Subject: [PATCH 18/28] #236 Fix pyparsing deprecations --- fc/environment.py | 4 +- fc/parsing/CompactSyntaxParser.py | 2 +- test/test_compact_syntax_parser.py | 6 +- test/test_syntax_interface.py | 204 ++++++++++++++--------------- 4 files changed, 108 insertions(+), 108 deletions(-) diff --git a/fc/environment.py b/fc/environment.py index dbbc7c31..69b81262 100644 --- a/fc/environment.py +++ b/fc/environment.py @@ -57,14 +57,14 @@ def define_names(self, names, values): def evaluate_expr(self, expr_str, env): from fc.parsing.CompactSyntaxParser import CompactSyntaxParser as csp - parse_action = csp.expr.parse_string(expr_str, parseAll=True) + parse_action = csp.expr.parse_string(expr_str, parse_all=True) expr = parse_action[0].expr() return expr.evaluate(env) def evaluate_statement(self, stmt_str, env): from fc.parsing.CompactSyntaxParser import CompactSyntaxParser as csp - parse_action = csp.stmt_list.parse_string(stmt_str, parseAll=True) + parse_action = csp.stmt_list.parse_string(stmt_str, parse_all=True) stmt_list = parse_action[0].expr() return env.execute_statements(stmt_list) diff --git a/fc/parsing/CompactSyntaxParser.py b/fc/parsing/CompactSyntaxParser.py index f0ee0f67..4f2927cb 100644 --- a/fc/parsing/CompactSyntaxParser.py +++ b/fc/parsing/CompactSyntaxParser.py @@ -598,7 +598,7 @@ def parse_file(self, source_file): :param source_file: path to the file to parse :return: a :class:`fc.parsing.actions.Protocol` object, containing parsed information about the protocol """ - return self.try_parse(self.protocol.parseFile, source_file, parseAll=True)[0] + return self.try_parse(self.protocol.parseFile, source_file, parse_all=True)[0] def try_parse(self, callable, source_file, *args, **kwargs): """ diff --git a/test/test_compact_syntax_parser.py b/test/test_compact_syntax_parser.py index a52f4d05..99733876 100644 --- a/test/test_compact_syntax_parser.py +++ b/test/test_compact_syntax_parser.py @@ -32,7 +32,7 @@ def check_result(actual, expected): def assert_parses(grammar, input, results): """Utility method to test that a given grammar parses an input as expected.""" - actual_results = grammar.parse_string(input, parseAll=True) + actual_results = grammar.parse_string(input, parse_all=True) check_parse_results(actual_results, results) @@ -117,10 +117,10 @@ def test_parsing_trace(): assert_parses(csp.expr, '(1 + a)?', [[['1', '+', 'a']]]) assert_parses(csp.expr, '1 + a?', [['1', '+', ['a']]]) - action = csp.expr.parse_string('var?', parseAll=True) + action = csp.expr.parse_string('var?', parse_all=True) assert action[0].expr().trace - action = csp.expr.parse_string('var', parseAll=True) + action = csp.expr.parse_string('var', parse_all=True) assert not action[0].expr().trace diff --git a/test/test_syntax_interface.py b/test/test_syntax_interface.py index 294c7435..37003d85 100644 --- a/test/test_syntax_interface.py +++ b/test/test_syntax_interface.py @@ -18,7 +18,7 @@ def test_parsing_number(): - parse_action = CSP.expr.parse_string('1.0', parseAll=True) + parse_action = CSP.expr.parse_string('1.0', parse_all=True) expr = parse_action[0].expr() assert isinstance(expr, E.Const) env = Environment() @@ -26,7 +26,7 @@ def test_parsing_number(): def test_parsing_variable(): - parse_action = CSP.expr.parse_string('a', parseAll=True) + parse_action = CSP.expr.parse_string('a', parse_all=True) expr = parse_action[0].expr() assert isinstance(expr, E.NameLookUp) env = Environment() @@ -36,37 +36,37 @@ def test_parsing_variable(): def test_parsing_math_operations(): # plus - parse_action = CSP.expr.parse_string('1.0 + 2.0', parseAll=True) + parse_action = CSP.expr.parse_string('1.0 + 2.0', parse_all=True) expr = parse_action[0].expr() assert isinstance(expr, E.Plus) env = Environment() assert expr.evaluate(env).value == 3 # minus - parse_action = CSP.expr.parse_string('5.0 - 2.0', parseAll=True) + parse_action = CSP.expr.parse_string('5.0 - 2.0', parse_all=True) expr = parse_action[0].expr() assert isinstance(expr, E.Minus) assert expr.evaluate(env).value == 3 # times - parse_action = CSP.expr.parse_string('4.0 * 2.0', parseAll=True) + parse_action = CSP.expr.parse_string('4.0 * 2.0', parse_all=True) expr = parse_action[0].expr() assert isinstance(expr, E.Times) assert expr.evaluate(env).value == 8 # division and infinity - parse_action = CSP.expr.parse_string('6.0 / 2.0', parseAll=True) + parse_action = CSP.expr.parse_string('6.0 / 2.0', parse_all=True) expr = parse_action[0].expr() assert isinstance(expr, E.Divide) assert expr.evaluate(env).value == 3 - parse_action = CSP.expr.parse_string('1/MathML:infinity', parseAll=True) + parse_action = CSP.expr.parse_string('1/MathML:infinity', parse_all=True) expr = parse_action[0].expr() assert isinstance(expr, E.Divide) assert expr.evaluate(env).value == 0 # power - parse_action = CSP.expr.parse_string('4.0 ^ 2.0', parseAll=True) + parse_action = CSP.expr.parse_string('4.0 ^ 2.0', parse_all=True) expr = parse_action[0].expr() assert isinstance(expr, E.Power) assert expr.evaluate(env).value == 16 @@ -74,204 +74,204 @@ def test_parsing_math_operations(): def test_parsing_logical_operations(): # greater than - parse_action = CSP.expr.parse_string('4.0 > 2.0', parseAll=True) + parse_action = CSP.expr.parse_string('4.0 > 2.0', parse_all=True) expr = parse_action[0].expr() assert isinstance(expr, E.Gt) env = Environment() assert expr.evaluate(env).value == 1 - parse_action = CSP.expr.parse_string('2.0 > 2.0', parseAll=True) + parse_action = CSP.expr.parse_string('2.0 > 2.0', parse_all=True) expr = parse_action[0].expr() assert isinstance(expr, E.Gt) assert expr.evaluate(env).value == 0 # less than - parse_action = CSP.expr.parse_string('4.0 < 2.0', parseAll=True) + parse_action = CSP.expr.parse_string('4.0 < 2.0', parse_all=True) expr = parse_action[0].expr() assert isinstance(expr, E.Lt) assert expr.evaluate(env).value == 0 - parse_action = CSP.expr.parse_string('2.0 < 2.0', parseAll=True) + parse_action = CSP.expr.parse_string('2.0 < 2.0', parse_all=True) expr = parse_action[0].expr() assert isinstance(expr, E.Lt) assert expr.evaluate(env).value == 0 # less than or equal to - parse_action = CSP.expr.parse_string('4.0 <= 2.0', parseAll=True) + parse_action = CSP.expr.parse_string('4.0 <= 2.0', parse_all=True) expr = parse_action[0].expr() assert isinstance(expr, E.Leq) assert expr.evaluate(env).value == 0 - parse_action = CSP.expr.parse_string('2.0 <= 2.0', parseAll=True) + parse_action = CSP.expr.parse_string('2.0 <= 2.0', parse_all=True) expr = parse_action[0].expr() assert isinstance(expr, E.Leq) assert expr.evaluate(env).value == 1 - parse_action = CSP.expr.parse_string('1.0 <= 2.0', parseAll=True) + parse_action = CSP.expr.parse_string('1.0 <= 2.0', parse_all=True) expr = parse_action[0].expr() assert isinstance(expr, E.Leq) assert expr.evaluate(env).value == 1 # equal to - parse_action = CSP.expr.parse_string('2.0 == 2.0', parseAll=True) + parse_action = CSP.expr.parse_string('2.0 == 2.0', parse_all=True) expr = parse_action[0].expr() assert isinstance(expr, E.Eq) assert expr.evaluate(env).value == 1 # not equal to and not a number - parse_action = CSP.expr.parse_string('2.0 != 2.0', parseAll=True) + parse_action = CSP.expr.parse_string('2.0 != 2.0', parse_all=True) expr = parse_action[0].expr() assert isinstance(expr, E.Neq) assert expr.evaluate(env).value == 0 - parse_action = CSP.expr.parse_string('1.0 != 2.0', parseAll=True) + parse_action = CSP.expr.parse_string('1.0 != 2.0', parse_all=True) expr = parse_action[0].expr() assert isinstance(expr, E.Neq) assert expr.evaluate(env).value == 1 - parse_action = CSP.expr.parse_string('MathML:notanumber != MathML:notanumber', parseAll=True) + parse_action = CSP.expr.parse_string('MathML:notanumber != MathML:notanumber', parse_all=True) expr = parse_action[0].expr() assert isinstance(expr, E.Neq) assert expr.evaluate(env).value == 1 # and - parse_action = CSP.expr.parse_string('1.0 && 1.0', parseAll=True) + parse_action = CSP.expr.parse_string('1.0 && 1.0', parse_all=True) expr = parse_action[0].expr() assert isinstance(expr, E.And) assert expr.evaluate(env).value == 1 - parse_action = CSP.expr.parse_string('0.0 && 1.0', parseAll=True) + parse_action = CSP.expr.parse_string('0.0 && 1.0', parse_all=True) expr = parse_action[0].expr() assert isinstance(expr, E.And) assert expr.evaluate(env).value == 0 # or and true or false - parse_action = CSP.expr.parse_string('MathML:true || MathML:true', parseAll=True) + parse_action = CSP.expr.parse_string('MathML:true || MathML:true', parse_all=True) expr = parse_action[0].expr() assert isinstance(expr, E.Or) assert expr.evaluate(env).value == 1 - parse_action = CSP.expr.parse_string('MathML:false || MathML:true', parseAll=True) + parse_action = CSP.expr.parse_string('MathML:false || MathML:true', parse_all=True) expr = parse_action[0].expr() assert isinstance(expr, E.Or) assert expr.evaluate(env).value == 1 - parse_action = CSP.expr.parse_string('MathML:false || MathML:false', parseAll=True) + parse_action = CSP.expr.parse_string('MathML:false || MathML:false', parse_all=True) expr = parse_action[0].expr() assert isinstance(expr, E.Or) assert expr.evaluate(env).value == 0 # not - parse_action = CSP.expr.parse_string('not 1', parseAll=True) + parse_action = CSP.expr.parse_string('not 1', parse_all=True) expr = parse_action[0].expr() assert isinstance(expr, E.Not) assert expr.evaluate(env).value == 0 - parse_action = CSP.expr.parse_string('not 0', parseAll=True) + parse_action = CSP.expr.parse_string('not 0', parse_all=True) expr = parse_action[0].expr() assert isinstance(expr, E.Not) assert expr.evaluate(env).value == 1 def test_parsing_complicated_math(): - parse_action = CSP.expr.parse_string('1.0 + (4.0 * 2.0)', parseAll=True) + parse_action = CSP.expr.parse_string('1.0 + (4.0 * 2.0)', parse_all=True) expr = parse_action[0].expr() env = Environment() assert expr.evaluate(env).value == 9 - parse_action = CSP.expr.parse_string('(2.0 ^ 3.0) + (5.0 * 2.0) - 10.0', parseAll=True) + parse_action = CSP.expr.parse_string('(2.0 ^ 3.0) + (5.0 * 2.0) - 10.0', parse_all=True) expr = parse_action[0].expr() assert expr.evaluate(env).value == 8 - parse_action = CSP.expr.parse_string('2.0 ^ 3.0 == 5.0 + 3.0', parseAll=True) + parse_action = CSP.expr.parse_string('2.0 ^ 3.0 == 5.0 + 3.0', parse_all=True) expr = parse_action[0].expr() assert expr.evaluate(env).value == 1 def test_parsing_MathML_funcs(): # ceiling - parse_action = CSP.expr.parse_string('MathML:ceiling(1.2)', parseAll=True) + parse_action = CSP.expr.parse_string('MathML:ceiling(1.2)', parse_all=True) expr = parse_action[0].expr() assert isinstance(expr, E.Ceiling) env = Environment() assert expr.evaluate(env).value == 2 # floor - parse_action = CSP.expr.parse_string('MathML:floor(1.8)', parseAll=True) + parse_action = CSP.expr.parse_string('MathML:floor(1.8)', parse_all=True) expr = parse_action[0].expr() assert isinstance(expr, E.Floor) assert expr.evaluate(env).value == 1 # ln and exponentiale value - parse_action = CSP.expr.parse_string('MathML:ln(MathML:exponentiale)', parseAll=True) + parse_action = CSP.expr.parse_string('MathML:ln(MathML:exponentiale)', parse_all=True) expr = parse_action[0].expr() assert isinstance(expr, E.Ln) assert expr.evaluate(env).value == pytest.approx(1) # log - parse_action = CSP.expr.parse_string('MathML:log(10)', parseAll=True) + parse_action = CSP.expr.parse_string('MathML:log(10)', parse_all=True) expr = parse_action[0].expr() assert isinstance(expr, E.Log) assert expr.evaluate(env).value == 1 # exp and infinity value - parse_action = CSP.expr.parse_string('MathML:exp(MathML:ln(10))', parseAll=True) + parse_action = CSP.expr.parse_string('MathML:exp(MathML:ln(10))', parse_all=True) expr = parse_action[0].expr() assert isinstance(expr, E.Exp) assert expr.evaluate(env).value == pytest.approx(10) # abs - parse_action = CSP.expr.parse_string('MathML:abs(-10)', parseAll=True) + parse_action = CSP.expr.parse_string('MathML:abs(-10)', parse_all=True) expr = parse_action[0].expr() assert isinstance(expr, E.Abs) assert expr.evaluate(env).value == pytest.approx(10) # root - parse_action = CSP.expr.parse_string('MathML:root(100)', parseAll=True) + parse_action = CSP.expr.parse_string('MathML:root(100)', parse_all=True) expr = parse_action[0].expr() assert isinstance(expr, E.Root) assert expr.evaluate(env).value == pytest.approx(10) # rem - parse_action = CSP.expr.parse_string('MathML:rem(100, 97)', parseAll=True) + parse_action = CSP.expr.parse_string('MathML:rem(100, 97)', parse_all=True) expr = parse_action[0].expr() assert isinstance(expr, E.Rem) assert expr.evaluate(env).value == pytest.approx(3) # max - parse_action = CSP.expr.parse_string('MathML:max(100, 97, 105)', parseAll=True) + parse_action = CSP.expr.parse_string('MathML:max(100, 97, 105)', parse_all=True) expr = parse_action[0].expr() assert isinstance(expr, E.Max) assert expr.evaluate(env).value == pytest.approx(105) # min and pi value - parse_action = CSP.expr.parse_string('MathML:min(100, 97, 105, MathML:pi)', parseAll=True) + parse_action = CSP.expr.parse_string('MathML:min(100, 97, 105, MathML:pi)', parse_all=True) expr = parse_action[0].expr() assert isinstance(expr, E.Min) assert expr.evaluate(env).value == pytest.approx(3.1415926535) # null - parse_action = CSP.expr.parse_string('null', parseAll=True) + parse_action = CSP.expr.parse_string('null', parse_all=True) expr = parse_action[0].expr() assert isinstance(expr, E.Const) assert isinstance(expr.value, V.Null) def test_parsing_if(): - parse_action = CSP.expr.parse_string('if 1 then 2 + 3 else 4-2', parseAll=True) + parse_action = CSP.expr.parse_string('if 1 then 2 + 3 else 4-2', parse_all=True) expr = parse_action[0].expr() assert isinstance(expr, E.If) env = Environment() assert expr.evaluate(env).value == pytest.approx(5) - parse_action = CSP.expr.parse_string('if MathML:false then 2 + 3 else 4-2', parseAll=True) + parse_action = CSP.expr.parse_string('if MathML:false then 2 + 3 else 4-2', parse_all=True) expr = parse_action[0].expr() assert isinstance(expr, E.If) assert expr.evaluate(env).value == pytest.approx(2) def test_parsing_tuple_expression(): - parse_action = CSP.expr.parse_string('(1, 2)', parseAll=True) + parse_action = CSP.expr.parse_string('(1, 2)', parse_all=True) expr = parse_action[0].expr() assert isinstance(expr, E.TupleExpression) env = Environment() @@ -280,7 +280,7 @@ def test_parsing_tuple_expression(): def test_parsing_array(): - parse_action = CSP.expr.parse_string('[1, 2, 3]', parseAll=True) + parse_action = CSP.expr.parse_string('[1, 2, 3]', parse_all=True) expr = parse_action[0].expr() assert isinstance(expr, E.NewArray) env = Environment() @@ -289,50 +289,50 @@ def test_parsing_array(): def test_parsing_accessor(): # is simple true - parse_action = CSP.expr.parse_string('1.IS_SIMPLE_VALUE', parseAll=True) + parse_action = CSP.expr.parse_string('1.IS_SIMPLE_VALUE', parse_all=True) expr = parse_action[0].expr() assert isinstance(expr, E.Accessor) env = Environment() assert expr.evaluate(env).value == pytest.approx(1) # is array true - parse_action = CSP.expr.parse_string('[1, 2].IS_ARRAY', parseAll=True) + parse_action = CSP.expr.parse_string('[1, 2].IS_ARRAY', parse_all=True) expr = parse_action[0].expr() assert isinstance(expr, E.Accessor) assert expr.evaluate(env).value == pytest.approx(1) # is tuple true - parse_action = CSP.expr.parse_string('(1, 2).IS_TUPLE', parseAll=True) + parse_action = CSP.expr.parse_string('(1, 2).IS_TUPLE', parse_all=True) expr = parse_action[0].expr() assert isinstance(expr, E.Accessor) assert expr.evaluate(env).value == pytest.approx(1) # is tuple false - parse_action = CSP.expr.parse_string('[1, 2].IS_TUPLE', parseAll=True) + parse_action = CSP.expr.parse_string('[1, 2].IS_TUPLE', parse_all=True) expr = parse_action[0].expr() assert isinstance(expr, E.Accessor) assert expr.evaluate(env).value == pytest.approx(0) # multiple accessors- .shape.is_array - parse_action = CSP.expr.parse_string('[1, 2, 3].SHAPE.IS_ARRAY', parseAll=True) + parse_action = CSP.expr.parse_string('[1, 2, 3].SHAPE.IS_ARRAY', parse_all=True) expr = parse_action[0].expr() assert isinstance(expr, E.Accessor) assert expr.evaluate(env).value == pytest.approx(1) # shape - parse_action = CSP.expr.parse_string('[1, 2, 3].SHAPE', parseAll=True) + parse_action = CSP.expr.parse_string('[1, 2, 3].SHAPE', parse_all=True) expr = parse_action[0].expr() assert isinstance(expr, E.Accessor) np.testing.assert_array_equal(expr.evaluate(env).array, np.array([3])) # number of dimensions - parse_action = CSP.expr.parse_string('[1, 2, 3].NUM_DIMS', parseAll=True) + parse_action = CSP.expr.parse_string('[1, 2, 3].NUM_DIMS', parse_all=True) expr = parse_action[0].expr() assert isinstance(expr, E.Accessor) np.testing.assert_array_equal(expr.evaluate(env).array, np.array([1])) # number of elements - parse_action = CSP.expr.parse_string('[1, 2, 3].NUM_ELEMENTS', parseAll=True) + parse_action = CSP.expr.parse_string('[1, 2, 3].NUM_ELEMENTS', parse_all=True) expr = parse_action[0].expr() assert isinstance(expr, E.Accessor) np.testing.assert_array_equal(expr.evaluate(env).array, np.array([3])) @@ -340,7 +340,7 @@ def test_parsing_accessor(): def test_statements(): # test assertion - parse_action = CSP.assert_stmt.parse_string("assert 1", parseAll=True) + parse_action = CSP.assert_stmt.parse_string("assert 1", parse_all=True) expr = parse_action[0].expr() assert isinstance(expr, S.Assert) env = Environment() @@ -350,14 +350,14 @@ def test_statements(): # assign one variable to an expression env = Environment() - parse_action = CSP.assign_stmt.parse_string('a = 1.0 + 2.0', parseAll=True) + parse_action = CSP.assign_stmt.parse_string('a = 1.0 + 2.0', parse_all=True) expr = parse_action[0].expr() assert isinstance(expr, S.Assign) expr.evaluate(env) assert env.look_up('a').value == 3 # assign two variables at once to numbers - parse_action = CSP.assign_stmt.parse_string('b, c = 1.0, 2.0', parseAll=True) + parse_action = CSP.assign_stmt.parse_string('b, c = 1.0, 2.0', parse_all=True) expr = parse_action[0].expr() assert isinstance(expr, S.Assign) expr.evaluate(env) @@ -365,7 +365,7 @@ def test_statements(): assert env.look_up('c').value == 2 # assign three variables at once to expressions - parse_action = CSP.assign_stmt.parse_string('d, e, f = 1.0, 2 + 2.0, (3*4)-2', parseAll=True) + parse_action = CSP.assign_stmt.parse_string('d, e, f = 1.0, 2 + 2.0, (3*4)-2', parse_all=True) expr = parse_action[0].expr() assert isinstance(expr, S.Assign) expr.evaluate(env) @@ -376,21 +376,21 @@ def test_statements(): # test return # return one number - parse_action = CSP.return_stmt.parse_string('return 1', parseAll=True) + parse_action = CSP.return_stmt.parse_string('return 1', parse_all=True) expr = parse_action[0].expr() assert isinstance(expr, S.Return) results = expr.evaluate(env) assert results.value == 1 # return one expression involving variables - parse_action = CSP.return_stmt.parse_string('return d + e', parseAll=True) + parse_action = CSP.return_stmt.parse_string('return d + e', parse_all=True) expr = parse_action[0].expr() assert isinstance(expr, S.Return) results = expr.evaluate(env) assert results.value == 5 # return two numbers - parse_action = CSP.return_stmt.parse_string('return 1, 3', parseAll=True) + parse_action = CSP.return_stmt.parse_string('return 1, 3', parse_all=True) expr = parse_action[0].expr() assert isinstance(expr, S.Return) result1, result2 = expr.evaluate(env).values @@ -398,7 +398,7 @@ def test_statements(): assert result2.value == 3 # test statement list - parse_action = CSP.stmt_list.parse_string('z = lambda a: a+2\nassert z(2) == 4', parseAll=True) + parse_action = CSP.stmt_list.parse_string('z = lambda a: a+2\nassert z(2) == 4', parse_all=True) result = parse_action[0].expr() env.execute_statements(result) @@ -406,7 +406,7 @@ def test_statements(): def test_parsing_lambda(): # no default, one variable env = Environment() - parse_action = CSP.lambda_expr.parse_string('lambda a: a + 1', parseAll=True) + parse_action = CSP.lambda_expr.parse_string('lambda a: a + 1', parse_all=True) expr = parse_action[0].expr() assert isinstance(expr, E.LambdaExpression) result = E.FunctionCall(expr, [E.N(3)]).evaluate(env) @@ -414,7 +414,7 @@ def test_parsing_lambda(): # no default, two variables env = Environment() - parse_action = CSP.lambda_expr.parse_string('lambda a, b: a * b', parseAll=True) + parse_action = CSP.lambda_expr.parse_string('lambda a, b: a * b', parse_all=True) expr = parse_action[0].expr() assert isinstance(expr, E.LambdaExpression) result = E.FunctionCall(expr, [E.N(4), E.N(2)]).evaluate(env) @@ -422,7 +422,7 @@ def test_parsing_lambda(): # test lambda with defaults unused env = Environment() - parse_action = CSP.lambda_expr.parse_string('lambda a=2, b=3: a + b', parseAll=True) + parse_action = CSP.lambda_expr.parse_string('lambda a=2, b=3: a + b', parse_all=True) expr = parse_action[0].expr() assert isinstance(expr, E.LambdaExpression) result = E.FunctionCall(expr, [E.N(2), E.N(6)]).evaluate(env) @@ -430,7 +430,7 @@ def test_parsing_lambda(): # test lambda with defaults used env = Environment() - parse_action = CSP.lambda_expr.parse_string('lambda a=2, b=3: a + b', parseAll=True) + parse_action = CSP.lambda_expr.parse_string('lambda a=2, b=3: a + b', parse_all=True) expr = parse_action[0].expr() assert isinstance(expr, E.LambdaExpression) result = E.FunctionCall(expr, [E.Const(V.DefaultParameter())]).evaluate(env) @@ -439,21 +439,21 @@ def test_parsing_lambda(): def test_array_comprehensions(): env = Environment() - parse_action = CSP.array.parse_string('[i for i in 0:10]', parseAll=True) + parse_action = CSP.array.parse_string('[i for i in 0:10]', parse_all=True) expr = parse_action[0].expr() assert isinstance(expr, E.NewArray) result = expr.evaluate(env) predicted = np.array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]) np.testing.assert_array_almost_equal(predicted, result.array) - parse_action = CSP.array.parse_string('[i*2 for i in 0:2:4]', parseAll=True) + parse_action = CSP.array.parse_string('[i*2 for i in 0:2:4]', parse_all=True) expr = parse_action[0].expr() assert isinstance(expr, E.NewArray) result = expr.evaluate(env) predicted = np.array([0, 4]) np.testing.assert_array_almost_equal(predicted, result.array) - parse_action = CSP.array.parse_string('[i+j*5 for i in 1:3 for j in 2:4]', parseAll=True) + parse_action = CSP.array.parse_string('[i+j*5 for i in 1:3 for j in 2:4]', parse_all=True) expr = parse_action[0].expr() assert isinstance(expr, E.NewArray) result = expr.evaluate(env) @@ -463,7 +463,7 @@ def test_array_comprehensions(): env = Environment() arr = V.Array(np.arange(10)) env.define_name('arr', arr) - parse_action = CSP.expr.parse_string('arr[1:2:10]', parseAll=True) + parse_action = CSP.expr.parse_string('arr[1:2:10]', parse_all=True) expr = parse_action[0].expr() assert isinstance(expr, E.View) result = expr.evaluate(env) @@ -475,21 +475,21 @@ def test_parsing_views(): env = Environment() view_arr = V.Array(np.arange(10)) env.define_name('view_arr', view_arr) - view_parse_action = CSP.expr.parse_string('view_arr[4]', parseAll=True) + view_parse_action = CSP.expr.parse_string('view_arr[4]', parse_all=True) expr = view_parse_action[0].expr() assert isinstance(expr, E.View) result = expr.evaluate(env) predicted = np.array(4) np.testing.assert_array_almost_equal(result.array, predicted) - view_parse_action = CSP.expr.parse_string('view_arr[2:5]', parseAll=True) + view_parse_action = CSP.expr.parse_string('view_arr[2:5]', parse_all=True) expr = view_parse_action[0].expr() assert isinstance(expr, E.View) result = expr.evaluate(env) predicted = np.array([2, 3, 4]) np.testing.assert_array_almost_equal(result.array, predicted) - view_parse_action = CSP.expr.parse_string('view_arr[1:2:10]', parseAll=True) + view_parse_action = CSP.expr.parse_string('view_arr[1:2:10]', parse_all=True) expr = view_parse_action[0].expr() assert isinstance(expr, E.View) result = expr.evaluate(env) @@ -499,28 +499,28 @@ def test_parsing_views(): env = Environment() view_arr = V.Array(np.array([[0, 1, 2, 3, 4], [7, 8, 12, 3, 9]])) env.define_name('view_arr', view_arr) - view_parse_action = CSP.expr.parse_string('view_arr[1$2]', parseAll=True) + view_parse_action = CSP.expr.parse_string('view_arr[1$2]', parse_all=True) expr = view_parse_action[0].expr() assert isinstance(expr, E.View) result = expr.evaluate(env) predicted = np.array([2, 12]) np.testing.assert_array_almost_equal(result.array, predicted) - view_parse_action = CSP.expr.parse_string('view_arr[1$(3):]', parseAll=True) + view_parse_action = CSP.expr.parse_string('view_arr[1$(3):]', parse_all=True) expr = view_parse_action[0].expr() assert isinstance(expr, E.View) result = expr.evaluate(env) predicted = np.array([[3, 4], [3, 9]]) np.testing.assert_array_almost_equal(result.array, predicted) - view_parse_action = CSP.expr.parse_string('view_arr[*$1]', parseAll=True) + view_parse_action = CSP.expr.parse_string('view_arr[*$1]', parse_all=True) expr = view_parse_action[0].expr() assert isinstance(expr, E.View) result = expr.evaluate(env) predicted = np.array(8) np.testing.assert_array_almost_equal(result.array, predicted) - view_parse_action = CSP.expr.parse_string('view_arr[*$1][0]', parseAll=True) + view_parse_action = CSP.expr.parse_string('view_arr[*$1][0]', parse_all=True) expr = view_parse_action[0].expr() assert isinstance(expr, E.View) result = expr.evaluate(env) @@ -532,7 +532,7 @@ def parsing_find_and_index_array(): env = Environment() arr = V.Array(np.arange(4)) env.define_name('arr', arr) - find_parse_action = CSP.expr.parse_string('find(arr)', parseAll=True) + find_parse_action = CSP.expr.parse_string('find(arr)', parse_all=True) expr = find_parse_action[0].expr() assert isinstance(expr, E.Find) find_result = expr.evaluate(env) @@ -544,7 +544,7 @@ def parsing_find_and_index_array(): index_arr = V.Array(np.arange(1, 26).reshape(5, 5)) env.define_name('find_arr', find_arr) env.define_name('index_arr', index_arr) - find_parse_action = CSP.expr.parse_string('find(find_arr)', parseAll=True) + find_parse_action = CSP.expr.parse_string('find(find_arr)', parse_all=True) expr = find_parse_action[0].expr() indices_from_find = expr.evaluate(env) env.define_name('indices_from_find', indices_from_find) @@ -555,7 +555,7 @@ def parsing_find_and_index_array(): np.testing.assert_array_almost_equal(index_result.array, predicted) env.define_name('find_result', find_result) - index_parse_action = CSP.expr.parse_string('arr{find_result}', parseAll=True) + index_parse_action = CSP.expr.parse_string('arr{find_result}', parse_all=True) expr = index_parse_action[0].expr() assert isinstance(expr, E.Index) result = expr.interpret(env) @@ -564,17 +564,17 @@ def parsing_find_and_index_array(): arr1 = V.Array(np.array([[1, 0, 2], [0, 3, 0], [1, 1, 1]])) env.define_name('arr1', arr1) - find_parse_action = CSP.expr.parse_string('find(arr1)', parseAll=True) + find_parse_action = CSP.expr.parse_string('find(arr1)', parse_all=True) expr = find_parse_action[0].expr() indices = expr.evaluate(env) env.define_name('indices', indices) - index_parse_action = CSP.expr.parse_string('arr1{indices, 1, pad:1=45}', parseAll=True) + index_parse_action = CSP.expr.parse_string('arr1{indices, 1, pad:1=45}', parse_all=True) expr = index_parse_action[0].expr() result = expr.interpret(env) predicted = np.array(np.array([[1, 2, 45], [3, 45, 45], [1, 1, 1]])) np.testing.assert_array_almost_equal(predicted, result.array) - index_parse_action = CSP.expr.parse_string('arr1{indices, 1, shrink: 1}', parseAll=True) + index_parse_action = CSP.expr.parse_string('arr1{indices, 1, shrink: 1}', parse_all=True) expr = index_parse_action[0].expr() result = expr.interpret(env) predicted = np.array(np.array([[1], [3], [1]])) @@ -587,10 +587,10 @@ def test_parsing_map_and_fold(): arr = V.Array(np.arange(4)) arr2 = V.Array(np.array([4, 5, 6, 7])) env.define_names(['arr', 'arr2'], [arr, arr2]) - lambda_parse_action = CSP.lambda_expr.parse_string('lambda a, b: a + b', parseAll=True) + lambda_parse_action = CSP.lambda_expr.parse_string('lambda a, b: a + b', parse_all=True) add_function = lambda_parse_action[0].expr() env.define_name('add_function', add_function.interpret(env)) - map_parse_action = CSP.expr.parse_string('map(add_function, arr, arr2)', parseAll=True) + map_parse_action = CSP.expr.parse_string('map(add_function, arr, arr2)', parse_all=True) expr = map_parse_action[0].expr() assert isinstance(expr, E.Map) result = expr.evaluate(env) @@ -598,7 +598,7 @@ def test_parsing_map_and_fold(): np.testing.assert_array_almost_equal(predicted, result.array) # test fold - fold_parse_action = CSP.expr.parse_string('fold(add_function, arr, 2, 0)', parseAll=True) + fold_parse_action = CSP.expr.parse_string('fold(add_function, arr, 2, 0)', parse_all=True) expr = fold_parse_action[0].expr() assert isinstance(expr, E.Fold) result = expr.evaluate(env) @@ -631,36 +631,36 @@ def test_protocol_and_post_processing(): def test_get_used_vars(): env = Environment() - parse_action = CSP.expr.parse_string('[i for i in 0:10]', parseAll=True) + parse_action = CSP.expr.parse_string('[i for i in 0:10]', parse_all=True) expr = parse_action[0].expr() used_vars = expr.get_used_variables() assert used_vars == set() - parse_action = CSP.array.parse_string('[i*2 for j in 0:2:4]', parseAll=True) + parse_action = CSP.array.parse_string('[i*2 for j in 0:2:4]', parse_all=True) expr = parse_action[0].expr() used_vars = expr.get_used_variables() assert used_vars == set(['i']) - parse_action = CSP.array.parse_string('[i+j*5 for j in 1:3 for l in 2:4]', parseAll=True) + parse_action = CSP.array.parse_string('[i+j*5 for j in 1:3 for l in 2:4]', parse_all=True) expr = parse_action[0].expr() used_vars = expr.get_used_variables() assert used_vars == set(['i']) env.define_name('a', V.Simple(1)) env.define_name('b', V.Simple(2)) - parse_action = CSP.expr.parse_string('a + b', parseAll=True) + parse_action = CSP.expr.parse_string('a + b', parse_all=True) expr = parse_action[0].expr() used_vars = expr.get_used_variables() assert used_vars == set(['a', 'b']) - parse_action = CSP.expr.parse_string('if a then b else 0', parseAll=True) + parse_action = CSP.expr.parse_string('if a then b else 0', parse_all=True) expr = parse_action[0].expr() used_vars = expr.get_used_variables() assert used_vars == set(['a', 'b']) def test_parsing_inputs(): - parse_action = CSP.inputs.parse_string('inputs{X=1}', parseAll=True) + parse_action = CSP.inputs.parse_string('inputs{X=1}', parse_all=True) expr = parse_action[0].expr() for each in expr: assert isinstance(each, S.Assign) @@ -668,7 +668,7 @@ def test_parsing_inputs(): def test_parsing_ranges(): # test parsing uniform range - parse_action = CSP.range.parse_string('range t units s uniform 0:10', parseAll=True) + parse_action = CSP.range.parse_string('range t units s uniform 0:10', parse_all=True) expr = parse_action[0].expr() expr.initialise(Environment()) assert isinstance(expr, Ranges.UniformRange) @@ -677,7 +677,7 @@ def test_parsing_ranges(): assert r[i] == pytest.approx(num) # test parsing vector range - parse_action = CSP.range.parse_string('range run units dimensionless vector [1, 3, 4]', parseAll=True) + parse_action = CSP.range.parse_string('range run units dimensionless vector [1, 3, 4]', parse_all=True) expr = parse_action[0].expr() assert isinstance(expr, Ranges.VectorRange) expr.initialise(Environment()) @@ -686,7 +686,7 @@ def test_parsing_ranges(): assert r[i] == pytest.approx(num) # test parsing while range - parse_action = CSP.range.parse_string('range rpt units dimensionless while rpt < 5', parseAll=True) + parse_action = CSP.range.parse_string('range rpt units dimensionless while rpt < 5', parse_all=True) expr = parse_action[0].expr() assert isinstance(expr, Ranges.While) expr.initialise(Environment()) @@ -698,7 +698,7 @@ def test_parsing_ranges(): def test_parsing_simulations(): # test parsing timecourse simulation parse_action = CSP.simulation.parse_string( - 'simulation sim = timecourse { range time units ms uniform 0:10 }', parseAll=True) + 'simulation sim = timecourse { range time units ms uniform 0:10 }', parse_all=True) expr = parse_action[0].expr() expr.initialise() a = 5 @@ -711,43 +711,43 @@ def test_parsing_simulations(): parse_action = CSP.tasks.parse_string("""tasks { simulation timecourse { range time units second uniform 1:10 } simulation timecourse { range time units second uniform 10:20 } - }""", parseAll=True) + }""", parse_all=True) expr = parse_action[0].expr() for sim in expr: assert isinstance(sim, Simulations.AbstractSimulation) def test_parsing_modifiers(): - parse_action = CSP.modifier_when.parse_string('at start', parseAll=True) + parse_action = CSP.modifier_when.parse_string('at start', parse_all=True) expr = parse_action[0].expr() assert expr == Modifiers.AbstractModifier.START_ONLY - parse_action = CSP.modifier_when.parse_string('at each loop', parseAll=True) + parse_action = CSP.modifier_when.parse_string('at each loop', parse_all=True) expr = parse_action[0].expr() assert expr == Modifiers.AbstractModifier.EACH_LOOP - parse_action = CSP.modifier_when.parse_string('at end', parseAll=True) + parse_action = CSP.modifier_when.parse_string('at end', parse_all=True) expr = parse_action[0].expr() assert expr == Modifiers.AbstractModifier.END_ONLY - parse_action = CSP.modifier.parse_string('at start set model:a = 5.0', parseAll=True) + parse_action = CSP.modifier.parse_string('at start set model:a = 5.0', parse_all=True) expr = parse_action[0].expr() assert isinstance(expr, Modifiers.SetVariable) assert expr.variable_name == 'model:a' assert expr.value_expr.value.value == 5 - parse_action = CSP.modifier.parse_string('at start set model:t = 10.0', parseAll=True) + parse_action = CSP.modifier.parse_string('at start set model:t = 10.0', parse_all=True) expr = parse_action[0].expr() assert isinstance(expr, Modifiers.SetVariable) assert expr.variable_name == 'model:t' assert expr.value_expr.value.value == 10 - parse_action = CSP.modifier.parse_string('at start save as state_name', parseAll=True) + parse_action = CSP.modifier.parse_string('at start save as state_name', parse_all=True) expr = parse_action[0].expr() assert isinstance(expr, Modifiers.SaveState) assert expr.state_name == 'state_name' - parse_action = CSP.modifier.parse_string('at start reset to state_name', parseAll=True) + parse_action = CSP.modifier.parse_string('at start reset to state_name', parse_all=True) expr = parse_action[0].expr() assert isinstance(expr, Modifiers.ResetState) assert expr.state_name == 'state_name' From 4b2c4c6e9d7b2a30ecd5f4036f7db31f1fa7a606 Mon Sep 17 00:00:00 2001 From: Kwabena Amponsah Date: Thu, 19 Mar 2026 09:53:23 +0000 Subject: [PATCH 19/28] #236 Fix style --- fc/parsing/CompactSyntaxParser.py | 62 +++++++++++++++++-------------- 1 file changed, 35 insertions(+), 27 deletions(-) diff --git a/fc/parsing/CompactSyntaxParser.py b/fc/parsing/CompactSyntaxParser.py index 4f2927cb..7a9050d3 100644 --- a/fc/parsing/CompactSyntaxParser.py +++ b/fc/parsing/CompactSyntaxParser.py @@ -214,7 +214,7 @@ class CompactSyntaxParser(object): # Accessors accessor = p.Combine(adjacent(p.Suppress('.')) - p.one_of('IS_SIMPLE_VALUE IS_ARRAY IS_STRING IS_TUPLE IS_FUNCTION IS_NULL IS_DEFAULT ' - 'NUM_DIMS NUM_ELEMENTS SHAPE')).set_name('.accessor (e.g. .IS_ARRAY)') + 'NUM_DIMS NUM_ELEMENTS SHAPE')).set_name('.accessor (e.g. .IS_ARRAY)') # Indexing pad = (make_kw('pad') + adjacent(colon) - expr + eq + expr).set_results_name('pad') @@ -257,20 +257,22 @@ class CompactSyntaxParser(object): array | wrap | number.copy().set_parse_action(actions.Number) | string_value | if_expr | null_value | default_value | lambda_expr | function_call | ident_as_var | tuple ).set_name('atomic expression') - expr <<= p.infix_notation(atom, [(accessor, 1, p.opAssoc.LEFT, actions.Accessor), - (view_spec, 1, p.opAssoc.LEFT, actions.View), - (index, 1, p.opAssoc.LEFT, actions.Index), - (trace, 1, p.opAssoc.LEFT, actions.Trace), - ('^', 2, p.opAssoc.LEFT, actions.Operator), - ('-', 1, p.opAssoc.RIGHT, - lambda *args: actions.Operator(*args, rightAssoc=True)), - (p.one_of('* /'), 2, p.opAssoc.LEFT, actions.Operator), - (p.one_of('+ -'), 2, p.opAssoc.LEFT, actions.Operator), - (p.Keyword('not'), 1, p.opAssoc.RIGHT, - lambda *args: actions.Operator(*args, rightAssoc=True)), - (p.one_of('== != <= >= < >'), 2, p.opAssoc.LEFT, actions.Operator), - (p.one_of('&& ||'), 2, p.opAssoc.LEFT, actions.Operator) - ]) + expr <<= p.infix_notation( + atom, + [ + (accessor, 1, p.opAssoc.LEFT, actions.Accessor), + (view_spec, 1, p.opAssoc.LEFT, actions.View), + (index, 1, p.opAssoc.LEFT, actions.Index), + (trace, 1, p.opAssoc.LEFT, actions.Trace), + ("^", 2, p.opAssoc.LEFT, actions.Operator), + ("-", 1, p.opAssoc.RIGHT, lambda *args: actions.Operator(*args, rightAssoc=True)), + (p.one_of("* /"), 2, p.opAssoc.LEFT, actions.Operator), + (p.one_of("+ -"), 2, p.opAssoc.LEFT, actions.Operator), + (p.Keyword("not"), 1, p.opAssoc.RIGHT, lambda *args: actions.Operator(*args, rightAssoc=True)), + (p.one_of("== != <= >= < >"), 2, p.opAssoc.LEFT, actions.Operator), + (p.one_of("&& ||"), 2, p.opAssoc.LEFT, actions.Operator), + ], + ) # Simpler expressions containing no arrays, functions, etc. Used in the model interface. simple_expr = p.Forward().set_name('simple expression') @@ -283,14 +285,15 @@ class CompactSyntaxParser(object): simple_expr <<= p.infix_notation( number.copy().set_parse_action(actions.Number) | simple_if_expr | simple_function_call | ident_as_var, [ - ('^', 2, p.opAssoc.LEFT, actions.Operator), - ('-', 1, p.opAssoc.RIGHT, lambda *args: actions.Operator(*args, rightAssoc=True)), - (p.one_of('* /'), 2, p.opAssoc.LEFT, actions.Operator), - (p.one_of('+ -'), 2, p.opAssoc.LEFT, actions.Operator), - (p.Keyword('not'), 1, p.opAssoc.RIGHT, lambda *args: actions.Operator(*args, rightAssoc=True)), - (p.one_of('== != <= >= < >'), 2, p.opAssoc.LEFT, actions.Operator), - (p.one_of('&& ||'), 2, p.opAssoc.LEFT, actions.Operator) - ]) + ("^", 2, p.opAssoc.LEFT, actions.Operator), + ("-", 1, p.opAssoc.RIGHT, lambda *args: actions.Operator(*args, rightAssoc=True)), + (p.one_of("* /"), 2, p.opAssoc.LEFT, actions.Operator), + (p.one_of("+ -"), 2, p.opAssoc.LEFT, actions.Operator), + (p.Keyword("not"), 1, p.opAssoc.RIGHT, lambda *args: actions.Operator(*args, rightAssoc=True)), + (p.one_of("== != <= >= < >"), 2, p.opAssoc.LEFT, actions.Operator), + (p.one_of("&& ||"), 2, p.opAssoc.LEFT, actions.Operator), + ], + ) simple_param_list = p.Group(optional_delimited_list(p.Group(nc_ident_as_var), comma)) simple_lambda_expr = p.Group(make_kw('lambda') - simple_param_list + colon - simple_expr).set_name('simple lambda function').set_parse_action(actions.Lambda) @@ -381,22 +384,27 @@ class CompactSyntaxParser(object): # Units definitions si_prefix = p.one_of('deka hecto kilo mega giga tera peta exa zetta yotta' - 'deci centi milli micro nano pico femto atto zepto yocto') + 'deci centi milli micro nano pico femto atto zepto yocto') _num_or_expr = p.original_text_for(plain_number | (oparen + expr + cparen)) unit_ref = p.Group(Optional(_num_or_expr)("multiplier") + Optional(si_prefix)("prefix") + nc_ident("units") + Optional(p.Suppress('^') + plain_number)("exponent") + Optional(p.Group(p.one_of('- +') + _num_or_expr))("offset")).set_parse_action(actions.UnitRef) units_def = p.Group(nc_ident + eq + p.DelimitedList(unit_ref, '.') + Optional(quoted_string)("description") ).set_name('units definition').set_parse_action(actions.UnitsDef) - units = (make_kw('units') - obrace - optional_delimited_list(units_def, nl) + cbrace - ).set_results_name("units").set_name('units section').set_parse_action(actions.Units) + units = ( + (make_kw("units") - obrace - optional_delimited_list(units_def, nl) + cbrace) + .set_results_name("units") + .set_name("units section") + .set_parse_action(actions.Units) + ) # Model interface section ######################### units_ref = make_kw('units') - nc_ident # Setting the units for the independent variable - set_time_units = (make_kw('independent') - make_kw('var') - units_ref("units")).set_parse_action(actions.SetTimeUnits) + set_time_units = (make_kw('independent') - make_kw('var') - + units_ref("units")).set_parse_action(actions.SetTimeUnits) # Input variables, with optional units and initial value input_variable = p.Group( From 985b418b6aa27bf2028ded9cb8490a69ded38614 Mon Sep 17 00:00:00 2001 From: Kwabena Amponsah Date: Thu, 19 Mar 2026 09:54:07 +0000 Subject: [PATCH 20/28] #236 test_clamping fix pass one --- fc/protocol.py | 2 +- fc/templates/weblab_model.pyx | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/fc/protocol.py b/fc/protocol.py index 60c62a4d..1eb7268c 100644 --- a/fc/protocol.py +++ b/fc/protocol.py @@ -610,7 +610,7 @@ def set_model(self, model): # Compile the extension module result = subprocess.run( - ['python', 'setup.py', 'build_ext', '--inplace'], + [sys.executable, 'setup.py', 'build_ext', '--inplace'], cwd=temp_dir, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, ) diff --git a/fc/templates/weblab_model.pyx b/fc/templates/weblab_model.pyx index 381fda12..d72f7457 100644 --- a/fc/templates/weblab_model.pyx +++ b/fc/templates/weblab_model.pyx @@ -319,7 +319,8 @@ cdef np.ndarray {{ table.table_name }} = np.array({{ table.data_code }}) cdef double {{ table.lookup_call }}: """Look up data from {{ table }}.""" - assert {{ table.index_name }} >= {{ table.initial_index }} and {{ table.index_name }} <= {{ table.final_index }} + assert {{ table.index_name }} >= {{ table.initial_index }} + assert {{ table.index_name }} <= {{ table.final_index }} cdef double offset_over_step = ({{ table.index_name }} - {{ table.initial_index }}) * {{ table.step_inverse }} cdef unsigned index = (offset_over_step) cdef double y1 = {{ table.table_name }}[index] From 9dd32e53f36311d57cceb8f74b7ec5a900e1f27b Mon Sep 17 00:00:00 2001 From: Kwabena Amponsah Date: Thu, 19 Mar 2026 12:50:49 +0000 Subject: [PATCH 21/28] #236 Revert wrappers and test with sundials 3 --- README.md | 2 +- fc/protocol.py | 4 +- fc/sundials/solver.pxd | 25 ++-- fc/sundials/solver.pyx | 62 ++++----- fc/sundials/sundials.pxd | 265 +++++++++++++-------------------------- setup.py | 18 ++- 6 files changed, 142 insertions(+), 234 deletions(-) diff --git a/README.md b/README.md index 33f9e138..261a7caa 100644 --- a/README.md +++ b/README.md @@ -46,7 +46,7 @@ The ontologies used are in a separate module, to install this run git submodule update --init ``` -You also need to have CVODE (from Sundials) installed. If you do this with your system package +You also need to have CVODE (from Sundials v3+) installed. If you do this with your system package manager no further setup is (probably) needed. Alternatively you can install it using `conda`: ```sh conda install sundials -c conda-forge diff --git a/fc/protocol.py b/fc/protocol.py index 1eb7268c..3a120b05 100644 --- a/fc/protocol.py +++ b/fc/protocol.py @@ -35,7 +35,7 @@ from setuptools import Extension, setup -FC_SUNDIALS_MAJOR = inline( +SUNDIALS_MAJOR = inline( \''' cdef extern from "": """ @@ -62,7 +62,7 @@ name="%(module_name)s", ext_modules=cythonize( extensions, - compile_time_env={"FC_SUNDIALS_MAJOR": FC_SUNDIALS_MAJOR}, + compile_time_env={"SUNDIALS_MAJOR": SUNDIALS_MAJOR}, ), ) ''' diff --git a/fc/sundials/solver.pxd b/fc/sundials/solver.pxd index 56d7ee6d..81bbe59c 100644 --- a/fc/sundials/solver.pxd +++ b/fc/sundials/solver.pxd @@ -4,9 +4,10 @@ cimport fc.sundials.sundials as _lib # Save typing ctypedef _lib.N_Vector N_Vector -ctypedef _lib.sunrealtype sunrealtype -ctypedef _lib.SUNMatrix SUNMatrix -ctypedef _lib.SUNLinearSolver SUNLinearSolver +ctypedef np.float64_t realtype +IF SUNDIALS_MAJOR >= 3: + ctypedef _lib.SUNMatrix SUNMatrix + ctypedef _lib.SUNLinearSolver SUNLinearSolver cdef class CvodeSolver: @@ -14,23 +15,19 @@ cdef class CvodeSolver: cdef N_Vector _state # The state vector of the model being simulated cdef int _state_size # The number of state variables / length of the state vector - cdef int _sundials_major cdef public np.ndarray state # Numpy view of the state vector cdef public object model # The model being simulated cpdef associate_with_model(self, model) - cpdef reset_solver(self, np.ndarray[sunrealtype, ndim=1] reset_to) - cpdef set_free_variable(self, sunrealtype t) - cpdef simulate(self, sunrealtype end_point) + cpdef reset_solver(self, np.ndarray[realtype, ndim=1] reset_to) + cpdef set_free_variable(self, realtype t) + cpdef simulate(self, realtype end_point) cdef re_init(self) cdef check_flag(self, int flag, char* called) - # Linear matrix solving state for sundials 3+ - cdef SUNMatrix sundense_matrix - cdef SUNLinearSolver sundense_solver - - # Sundials context for v6+ - cdef _lib.SUNContext sunctx - + IF SUNDIALS_MAJOR >= 3: + # Linear matrix solving in sundials 3+ + cdef SUNMatrix sundense_matrix + cdef SUNLinearSolver sundense_solver diff --git a/fc/sundials/solver.pyx b/fc/sundials/solver.pyx index e3e25a16..00b55f90 100644 --- a/fc/sundials/solver.pyx +++ b/fc/sundials/solver.pyx @@ -11,12 +11,12 @@ from fc.error_handling import ProtocolError # Data type for numpy arrays np_dtype = np.float64 -assert sizeof(np.float64_t) == sizeof(_lib.sunrealtype) # paranoia +assert sizeof(np.float64_t) == sizeof(_lib.realtype) # paranoia # # Debugging! # import sys # def fprint(*args): -# print(' '.join(map(str, args))) +# print ' '.join(map(str, args)) # sys.stdout.flush() @@ -25,7 +25,7 @@ cdef object numpy_view(N_Vector v): cdef _lib.N_VectorContent_Serial v_content = <_lib.N_VectorContent_Serial>(v.content) cdef view.array data_view = view.array( shape=(v_content.length,), - itemsize=sizeof(sunrealtype), + itemsize=sizeof(realtype), format='d', mode='c', allocate_buffer=False) @@ -33,7 +33,7 @@ cdef object numpy_view(N_Vector v): ret = np.asarray(data_view, dtype=np_dtype) return ret -cdef int _rhs_wrapper(sunrealtype t, N_Vector y, N_Vector ydot, void* user_data) noexcept: +cdef int _rhs_wrapper(realtype t, N_Vector y, N_Vector ydot, void* user_data) noexcept: """Cython wrapper around a model RHS that uses numpy, for calling by CVODE.""" # Create numpy views on the N_Vectors @@ -44,7 +44,7 @@ cdef int _rhs_wrapper(sunrealtype t, N_Vector y, N_Vector ydot, void* user_data) model = user_data try: model.evaluate_rhs(t, np_y, np_ydot) - except Exception, e: + except Exception as e: print(e) return 1 # recoverable error return 0 @@ -58,14 +58,10 @@ cdef class CvodeSolver: self.cvode_mem = NULL self._state = NULL self._state_size = 0 - self._sundials_major = _lib.FC_SundialsMajor() - self.sunctx = NULL - self.sundense_matrix = NULL - self.sundense_solver = NULL - flag = _lib.FC_SUNContext_Create(&self.sunctx) - if flag != 0: - raise ProtocolError('Error creating SUNDIALS context') + IF SUNDIALS_MAJOR >= 3: + self.sundense_matrix = NULL + self.sundense_solver = NULL def __dealloc__(self): """Free solver memory if allocated.""" @@ -73,9 +69,11 @@ cdef class CvodeSolver: _lib.CVodeFree(&self.cvode_mem) if self._state != NULL: _lib.N_VDestroy_Serial(self._state) - _lib.FC_SUNLinSolFree(self.sundense_solver) - _lib.FC_SUNMatDestroy(self.sundense_matrix) - _lib.FC_SUNContext_Free(&self.sunctx) + IF SUNDIALS_MAJOR >= 3: + if self.sundense_solver != NULL: + _lib.SUNLinSolFree(self.sundense_solver) + if self.sundense_matrix != NULL: + _lib.SUNMatDestroy(self.sundense_matrix) def __init__(self): """Python level object initialisation.""" @@ -94,11 +92,14 @@ cdef class CvodeSolver: assert isinstance(model.state, np.ndarray) self.state = model.state self._state_size = len(model.state) - self._state = _lib.FC_N_VMake_Serial( - self._state_size, (self.state).data, self.sunctx) + self._state = _lib.N_VMake_Serial( + self._state_size, (self.state).data) # Create CVode object - self.cvode_mem = _lib.FC_CVodeCreate(_lib.CV_BDF, 0, self.sunctx) + IF SUNDIALS_MAJOR >= 4: + self.cvode_mem = _lib.CVodeCreate(_lib.CV_BDF) + ELSE: + self.cvode_mem = _lib.CVodeCreate(_lib.CV_BDF, _lib.CV_NEWTON) # Initialise CVode if hasattr(self, 'set_rhs_wrapper'): @@ -120,40 +121,40 @@ cdef class CvodeSolver: # Create dense matrix for use in linear solves if self._state_size > 0: - if self._sundials_major >= 3: + IF SUNDIALS_MAJOR >= 3: # Create dense matrix - self.sundense_matrix = _lib.FC_SUNDenseMatrix( - self._state_size, self._state_size, self.sunctx) + self.sundense_matrix = _lib.SUNDenseMatrix( + self._state_size, self._state_size) if self.sundense_matrix == NULL: raise ProtocolError('Error calling CVODE routine SUNDenseMatrix: Null returned') # Create linear solver - self.sundense_solver = _lib.FC_SUNDenseLinearSolver(self._state, self.sundense_matrix, self.sunctx) + self.sundense_solver = _lib.SUNDenseLinearSolver(self._state, self.sundense_matrix) if self.sundense_solver == NULL: raise ProtocolError('Error calling CVODE routine SUNDenseLinearSolver: Null returned') # Tell cvode to use this solver - flag = _lib.FC_CVodeSetLinearSolver(self.cvode_mem, self.sundense_solver, self.sundense_matrix) - self.check_flag(flag, 'CVodeSetLinearSolver') - else: + flag = _lib.CVDlsSetLinearSolver(self.cvode_mem, self.sundense_solver, self.sundense_matrix) + self.check_flag(flag, 'CVDlsSetLinearSolver') + ELSE: # Create dense matrix - flag = _lib.FC_CVDense(self.cvode_mem, self._state_size) + flag = _lib.CVDense(self.cvode_mem, self._state_size) self.check_flag(flag, 'CVDense') _lib.CVodeSetMaxNumSteps(self.cvode_mem, 20000000) _lib.CVodeSetMaxStep(self.cvode_mem, 0.5) _lib.CVodeSetMaxErrTestFails(self.cvode_mem, 15) - cpdef reset_solver(self, np.ndarray[sunrealtype, ndim=1] resetTo): + cpdef reset_solver(self, np.ndarray[realtype, ndim=1] resetTo): self.state[:] = resetTo self.re_init() - cpdef set_free_variable(self, sunrealtype t): + cpdef set_free_variable(self, realtype t): self.model.free_variable = t self.re_init() - cpdef simulate(self, sunrealtype end_point): - cdef sunrealtype t = 0 + cpdef simulate(self, realtype end_point): + cdef realtype t = 0 if self._state_size > 0: if self.model.dirty: # A model variable has changed, so reset the solver @@ -181,4 +182,3 @@ cdef class CvodeSolver: if flag != _lib.CV_SUCCESS: flag_name = _lib.CVodeGetReturnFlagName(flag) raise ProtocolError("Error calling CVODE routine %s: %s" % (called, flag_name)) - diff --git a/fc/sundials/sundials.pxd b/fc/sundials/sundials.pxd index 8d7b1450..3a8a4c50 100644 --- a/fc/sundials/sundials.pxd +++ b/fc/sundials/sundials.pxd @@ -1,41 +1,31 @@ + """ Minimal Cython interface to the (CVODE part of the) SUNDIALS library, for use by Functional Curation. +Handles both SUNDIALS 2.4 and 2.5, since the parts of the interface we use didn't change. + Based on http://code.google.com/p/python-sundials/source/browse/trunk/sundials/SundialsLib.pxd -Includes compatibility wrappers so Cython code can avoid deprecated compile-time IF directives. """ cdef extern from "sundials/sundials_types.h": ctypedef long int sunindextype - ctypedef double sunrealtype - ctypedef bint sunbooleantype + ctypedef double realtype + ctypedef bint booleantype cdef extern from "sundials/sundials_nvector.h": cdef struct _generic_N_Vector: void *content ctypedef _generic_N_Vector *N_Vector -cdef extern from *: - ctypedef struct _generic_SUNMatrix: - pass - ctypedef _generic_SUNMatrix* SUNMatrix - - ctypedef struct _generic_SUNLinearSolver: - pass - ctypedef _generic_SUNLinearSolver* SUNLinearSolver - - ctypedef struct SUNContext_: - pass - ctypedef SUNContext_* SUNContext - cdef extern from "nvector/nvector_serial.h": + cdef N_Vector N_VMake_Serial(long int vec_length, realtype *v_data) N_Vector N_VNew_Serial(long int vec_length) void N_VDestroy_Serial(N_Vector v) void N_VPrint_Serial(N_Vector v) cdef struct _N_VectorContent_Serial: long int length - sunrealtype *data + realtype *data ctypedef _N_VectorContent_Serial *N_VectorContent_Serial cdef extern from "cvode/cvode.h": @@ -75,170 +65,93 @@ cdef extern from "cvode/cvode.h": int CV_BAD_DKY int CV_TOO_CLOSE - ctypedef int (*CVRhsFn)(sunrealtype t, N_Vector y, N_Vector ydot, void *user_data) - ctypedef int (*CVRootFn)(sunrealtype t, N_Vector y, sunrealtype *gout, void *user_data) + ctypedef int (*CVRhsFn)(realtype t, N_Vector y, N_Vector ydot, void *user_data) + ctypedef int (*CVRootFn)(realtype t, N_Vector y, realtype *gout, void *user_data) + + # In version 4 Newton iteration became the default, and a new syntax was + # introduced to change it (which we don't need to use here) + IF SUNDIALS_MAJOR >= 4: + void *CVodeCreate(int lmm) + ELSE: + void *CVodeCreate(int lmm, int iter) int CVodeSetUserData(void *cvode_mem, void *user_data) - int CVodeInit(void *cvode_mem, CVRhsFn f, sunrealtype t0, N_Vector y0) - int CVodeReInit(void *cvode_mem, sunrealtype t0, N_Vector y0) - int CVodeSStolerances(void *cvode_mem, sunrealtype reltol, sunrealtype abstol) + int CVodeInit(void *cvode_mem, CVRhsFn f, realtype t0, N_Vector y0) + int CVodeReInit(void *cvode_mem, realtype t0, N_Vector y0) + int CVodeSStolerances(void *cvode_mem, realtype reltol, realtype abstol) int CVodeRootInit(void *cvode_mem, int nrtfn, CVRootFn g) - int CVode(void *cvode_mem, sunrealtype tout, N_Vector yout, sunrealtype *tret, int itask) + +# int CVodeStep "CVode"(void *cvode_mem, realtype tout, N_Vector yout, realtype *tret, int itask) nogil + int CVode(void *cvode_mem, realtype tout, N_Vector yout, realtype *tret, int itask) + +# int CVodeSetMaxOrd(void *cvode_mem, int maxord) int CVodeSetMaxNumSteps(void *cvode_mem, long int mxsteps) - int CVodeSetMaxStep(void *cvode_mem, sunrealtype hmax) - int CVodeSetStopTime(void *cvode_mem, sunrealtype tstop) +# int CVodeSetMaxHnilWarns(void *cvode_mem, int mxhnil) +# int CVodeSetStabLimDet(void *cvode_mem, booleantype stldet) +# int CVodeSetInitStep(void *cvode_mem, realtype hin) +# int CVodeSetMinStep(void *cvode_mem, realtype hmin) + int CVodeSetMaxStep(void *cvode_mem, realtype hmax) + int CVodeSetStopTime(void *cvode_mem, realtype tstop) int CVodeSetMaxErrTestFails(void *cvode_mem, int maxnef) +# int CVodeSetMaxNonlinIters(void *cvode_mem, int maxcor) +# int CVodeSetMaxConvFails(void *cvode_mem, int maxncf) +# int CVodeSetNonlinConvCoef(void *cvode_mem, realtype nlscoef) +# int CVodeSetIterType(void *cvode_mem, int iter) +# int CVodeSetRootDirection(void *cvode_mem, int *rootdir) +# int CVodeSetNoInactiveRootWarn(void *cvode_mem) +# int CVodeGetDky(void *cvode_mem, realtype t, int k, N_Vector dky) +# int CVodeGetWorkSpace(void *cvode_mem, long int *lenrw, long int *leniw) +# int CVodeGetNumSteps(void *cvode_mem, long int *nsteps) +# int CVodeGetNumRhsEvals(void *cvode_mem, long int *nfevals) +# int CVodeGetNumLinSolvSetups(void *cvode_mem, long int *nlinsetups) +# int CVodeGetNumErrTestFails(void *cvode_mem, long int *netfails) +# int CVodeGetLastOrder(void *cvode_mem, int *qlast) +# int CVodeGetCurrentOrder(void *cvode_mem, int *qcur) +# int CVodeGetNumStabLimOrderReds(void *cvode_mem, long int *nslred) +# int CVodeGetActualInitStep(void *cvode_mem, realtype *hinused) +# int CVodeGetLastStep(void *cvode_mem, realtype *hlast) +# int CVodeGetCurrentStep(void *cvode_mem, realtype *hcur) +# int CVodeGetCurrentTime(void *cvode_mem, realtype *tcur) +# int CVodeGetTolScaleFactor(void *cvode_mem, realtype *tolsfac) +# int CVodeGetErrWeights(void *cvode_mem, N_Vector eweight) +# int CVodeGetEstLocalErrors(void *cvode_mem, N_Vector ele) +# int CVodeGetNumGEvals(void *cvode_mem, long int *ngevals) +# int CVodeGetRootInfo(void *cvode_mem, int *rootsfound) +# int CVodeGetIntegratorStats(void *cvode_mem, long int *nsteps, +# long int *nfevals, long int *nlinsetups, +# long int *netfails, int *qlast, +# int *qcur, realtype *hinused, realtype *hlast, +# realtype *hcur, realtype *tcur) +# int CVodeGetNumNonlinSolvIters(void *cvode_mem, long int *nniters) +# int CVodeGetNumNonlinSolvConvFails(void *cvode_mem, long int *nncfails) +# int CVodeGetNonlinSolvStats(void *cvode_mem, long int *nniters, long int *nncfails) +# int CVDlsGetNumJacEvals(void *cvode_mem, long int *njevals) +# int CVDlsGetNumRhsEvals(void *cvode_mem, long int *nrevalsLS) + char *CVodeGetReturnFlagName(int flag) void CVodeFree(void **cvode_mem) -cdef extern from *: - """ - #include - #include - #include - #include - #include - - #if SUNDIALS_VERSION_MAJOR >= 3 - #include - #include - #include - #include - #else - #include - typedef struct _generic_SUNMatrix* SUNMatrix; - typedef struct _generic_SUNLinearSolver* SUNLinearSolver; - #endif - - #if SUNDIALS_VERSION_MAJOR >= 6 - #include - #else - typedef struct SUNContext_* SUNContext; - #define SUN_COMM_NULL 0 - #endif - - static int FC_SundialsMajor(void) - { - return SUNDIALS_VERSION_MAJOR; - } - - static int FC_SUNContext_Create(SUNContext* sunctx_out) - { - #if SUNDIALS_VERSION_MAJOR >= 6 - return SUNContext_Create(SUN_COMM_NULL, sunctx_out); - #else - *sunctx_out = NULL; - return 0; - #endif - } - - static int FC_SUNContext_Free(SUNContext* ctx) - { - #if SUNDIALS_VERSION_MAJOR >= 6 - return SUNContext_Free(ctx); - #else - *ctx = NULL; - return 0; - #endif - } - - static N_Vector FC_N_VMake_Serial(sunindextype vec_length, sunrealtype* v_data, SUNContext sunctx) - { - #if SUNDIALS_VERSION_MAJOR >= 6 - return N_VMake_Serial(vec_length, v_data, sunctx); - #else - (void)sunctx; - return N_VMake_Serial((long int)vec_length, v_data); - #endif - } - - static void* FC_CVodeCreate(int lmm, int iter, SUNContext sunctx) - { - #if SUNDIALS_VERSION_MAJOR >= 6 - (void)iter; - return CVodeCreate(lmm, sunctx); - #elif SUNDIALS_VERSION_MAJOR >= 4 - (void)iter; - (void)sunctx; - return CVodeCreate(lmm); - #else - (void)sunctx; - return CVodeCreate(lmm, iter); - #endif - } - - static SUNMatrix FC_SUNDenseMatrix(sunindextype m, sunindextype n, SUNContext sunctx) - { - #if SUNDIALS_VERSION_MAJOR >= 6 - return SUNDenseMatrix(m, n, sunctx); - #elif SUNDIALS_VERSION_MAJOR >= 3 - (void)sunctx; - return SUNDenseMatrix(m, n); - #else - (void)m; (void)n; (void)sunctx; - return NULL; - #endif - } - - static SUNLinearSolver FC_SUNDenseLinearSolver(N_Vector y, SUNMatrix a, SUNContext sunctx) - { - #if SUNDIALS_VERSION_MAJOR >= 6 - return SUNLinSol_Dense(y, a, sunctx); - #elif SUNDIALS_VERSION_MAJOR >= 3 - (void)sunctx; - return SUNDenseLinearSolver(y, a); - #else - (void)y; (void)a; (void)sunctx; - return NULL; - #endif - } - - static int FC_CVodeSetLinearSolver(void* cvode_mem, SUNLinearSolver ls, SUNMatrix a) - { - #if SUNDIALS_VERSION_MAJOR >= 3 - return CVodeSetLinearSolver(cvode_mem, ls, a); - #else - (void)cvode_mem; (void)ls; (void)a; - return CV_SUCCESS; - #endif - } - - static int FC_CVDense(void* cvode_mem, int n) - { - #if SUNDIALS_VERSION_MAJOR >= 3 - (void)cvode_mem; (void)n; - return CV_SUCCESS; - #else - return CVDense(cvode_mem, n); - #endif - } - - static void FC_SUNMatDestroy(SUNMatrix a) - { - #if SUNDIALS_VERSION_MAJOR >= 3 - if (a != NULL) SUNMatDestroy(a); - #else - (void)a; - #endif - } - - static void FC_SUNLinSolFree(SUNLinearSolver ls) - { - #if SUNDIALS_VERSION_MAJOR >= 3 - if (ls != NULL) SUNLinSolFree(ls); - #else - (void)ls; - #endif - } - """ - int FC_SundialsMajor() - int FC_SUNContext_Create(SUNContext* sunctx_out) - int FC_SUNContext_Free(SUNContext* ctx) - N_Vector FC_N_VMake_Serial(sunindextype vec_length, sunrealtype* v_data, SUNContext sunctx) - void* FC_CVodeCreate(int lmm, int iter, SUNContext sunctx) - SUNMatrix FC_SUNDenseMatrix(sunindextype m, sunindextype n, SUNContext sunctx) - SUNLinearSolver FC_SUNDenseLinearSolver(N_Vector y, SUNMatrix a, SUNContext sunctx) - int FC_CVodeSetLinearSolver(void* cvode_mem, SUNLinearSolver ls, SUNMatrix a) - int FC_CVDense(void* cvode_mem, int n) - void FC_SUNMatDestroy(SUNMatrix a) - void FC_SUNLinSolFree(SUNLinearSolver ls) \ No newline at end of file +IF SUNDIALS_MAJOR >= 3: + cdef extern from "sundials/sundials_matrix.h": + ctypedef struct _generic_SUNMatrix: + pass + ctypedef _generic_SUNMatrix* SUNMatrix + void SUNMatDestroy(SUNMatrix A) + + cdef extern from "sunmatrix/sunmatrix_dense.h": + SUNMatrix SUNDenseMatrix(sunindextype M, sunindextype N) + + cdef extern from "sunlinsol/sunlinsol_dense.h": + ctypedef struct _generic_SUNLinearSolver: + pass + ctypedef _generic_SUNLinearSolver* SUNLinearSolver + void SUNLinSolFree(SUNLinearSolver) + + cdef extern from "sundials/sundials_linearsolver.h": + SUNLinearSolver SUNDenseLinearSolver(N_Vector y, SUNMatrix A) + + cdef extern from "cvode/cvode_direct.h": + int CVDlsSetLinearSolver(void* cvode_mem, SUNLinearSolver LS, SUNMatrix A) +ELSE: + cdef extern from "cvode/cvode_dense.h": + int CVDense(void *cvode_mem, int N) diff --git a/setup.py b/setup.py index 51e68dbc..64f0c293 100644 --- a/setup.py +++ b/setup.py @@ -11,21 +11,19 @@ from Cython.Build import cythonize from setuptools import Extension, setup -# Detect major sundials version (defaults to 2) -FC_SUNDIALS_MAJOR = inline( +# Detect major sundials version +SUNDIALS_MAJOR = inline( ''' - cdef extern from "": - """ - #ifndef SUNDIALS_VERSION_MAJOR - #define SUNDIALS_VERSION_MAJOR 2 - #endif - """ + cdef extern from "sundials/sundials_config.h": int SUNDIALS_VERSION_MAJOR return SUNDIALS_VERSION_MAJOR ''' ) -print("Building for Sundials " + str(FC_SUNDIALS_MAJOR) + ".x") + +assert SUNDIALS_MAJOR >= 3, f"Unsupported Sundials version: {SUNDIALS_MAJOR}" + +print(f"Building for Sundials {SUNDIALS_MAJOR}") # Define Cython modules extensions = [ @@ -45,6 +43,6 @@ zip_safe=False, ext_modules=cythonize( extensions, - compile_time_env={"FC_SUNDIALS_MAJOR": FC_SUNDIALS_MAJOR}, + compile_time_env={"SUNDIALS_MAJOR": SUNDIALS_MAJOR}, ), ) From 343a5706f398e63739af1fcf628837c8deea45b7 Mon Sep 17 00:00:00 2001 From: Kwabena Amponsah Date: Thu, 19 Mar 2026 19:39:11 +0000 Subject: [PATCH 22/28] #236 Add sundials wrappers for v3 to v7 --- fc/protocol.py | 4 +- fc/sundials/solver.pxd | 15 ++--- fc/sundials/solver.pyx | 67 ++++++++----------- fc/sundials/sundials.pxd | 140 ++++++++++++++++++++++++++++++--------- setup.py | 17 +++-- 5 files changed, 150 insertions(+), 93 deletions(-) diff --git a/fc/protocol.py b/fc/protocol.py index 3a120b05..0cdce6f5 100644 --- a/fc/protocol.py +++ b/fc/protocol.py @@ -35,7 +35,7 @@ from setuptools import Extension, setup -SUNDIALS_MAJOR = inline( +SUNDIALS_VERSION_MAJOR = inline( \''' cdef extern from "": """ @@ -62,7 +62,7 @@ name="%(module_name)s", ext_modules=cythonize( extensions, - compile_time_env={"SUNDIALS_MAJOR": SUNDIALS_MAJOR}, + compile_time_env={"SUNDIALS_VERSION_MAJOR": SUNDIALS_VERSION_MAJOR}, ), ) ''' diff --git a/fc/sundials/solver.pxd b/fc/sundials/solver.pxd index 81bbe59c..dfefc35e 100644 --- a/fc/sundials/solver.pxd +++ b/fc/sundials/solver.pxd @@ -5,9 +5,6 @@ cimport fc.sundials.sundials as _lib # Save typing ctypedef _lib.N_Vector N_Vector ctypedef np.float64_t realtype -IF SUNDIALS_MAJOR >= 3: - ctypedef _lib.SUNMatrix SUNMatrix - ctypedef _lib.SUNLinearSolver SUNLinearSolver cdef class CvodeSolver: @@ -19,6 +16,13 @@ cdef class CvodeSolver: cdef public np.ndarray state # Numpy view of the state vector cdef public object model # The model being simulated + # SUNContext (v6+; NULL for older versions) + cdef void* sunctx + + # Linear matrix solving (opaque void* pointers to SUNMatrix and SUNLinearSolver) + cdef void* sundense_matrix + cdef void* sundense_solver + cpdef associate_with_model(self, model) cpdef reset_solver(self, np.ndarray[realtype, ndim=1] reset_to) cpdef set_free_variable(self, realtype t) @@ -26,8 +30,3 @@ cdef class CvodeSolver: cdef re_init(self) cdef check_flag(self, int flag, char* called) - - IF SUNDIALS_MAJOR >= 3: - # Linear matrix solving in sundials 3+ - cdef SUNMatrix sundense_matrix - cdef SUNLinearSolver sundense_solver diff --git a/fc/sundials/solver.pyx b/fc/sundials/solver.pyx index 00b55f90..dde9070d 100644 --- a/fc/sundials/solver.pyx +++ b/fc/sundials/solver.pyx @@ -13,12 +13,6 @@ from fc.error_handling import ProtocolError np_dtype = np.float64 assert sizeof(np.float64_t) == sizeof(_lib.realtype) # paranoia -# # Debugging! -# import sys -# def fprint(*args): -# print ' '.join(map(str, args)) -# sys.stdout.flush() - cdef object numpy_view(N_Vector v): """Create a Numpy array giving a view on the CVODE vector passed in.""" @@ -58,10 +52,9 @@ cdef class CvodeSolver: self.cvode_mem = NULL self._state = NULL self._state_size = 0 - - IF SUNDIALS_MAJOR >= 3: - self.sundense_matrix = NULL - self.sundense_solver = NULL + self.sunctx = _lib.fc_SUNContext_Create() + self.sundense_matrix = NULL + self.sundense_solver = NULL def __dealloc__(self): """Free solver memory if allocated.""" @@ -69,11 +62,9 @@ cdef class CvodeSolver: _lib.CVodeFree(&self.cvode_mem) if self._state != NULL: _lib.N_VDestroy_Serial(self._state) - IF SUNDIALS_MAJOR >= 3: - if self.sundense_solver != NULL: - _lib.SUNLinSolFree(self.sundense_solver) - if self.sundense_matrix != NULL: - _lib.SUNMatDestroy(self.sundense_matrix) + _lib.fc_SUNLinSolFree(self.sundense_solver) + _lib.fc_SUNMatDestroy(self.sundense_matrix) + _lib.fc_SUNContext_Free(self.sunctx) def __init__(self): """Python level object initialisation.""" @@ -92,14 +83,11 @@ cdef class CvodeSolver: assert isinstance(model.state, np.ndarray) self.state = model.state self._state_size = len(model.state) - self._state = _lib.N_VMake_Serial( - self._state_size, (self.state).data) + self._state = _lib.fc_N_VMake_Serial( + self._state_size, (self.state).data, self.sunctx) - # Create CVode object - IF SUNDIALS_MAJOR >= 4: - self.cvode_mem = _lib.CVodeCreate(_lib.CV_BDF) - ELSE: - self.cvode_mem = _lib.CVodeCreate(_lib.CV_BDF, _lib.CV_NEWTON) + # Create CVode object using wrapper that handles version differences + self.cvode_mem = _lib.fc_CVodeCreate(_lib.CV_BDF, self.sunctx) # Initialise CVode if hasattr(self, 'set_rhs_wrapper'): @@ -121,25 +109,22 @@ cdef class CvodeSolver: # Create dense matrix for use in linear solves if self._state_size > 0: - IF SUNDIALS_MAJOR >= 3: - # Create dense matrix - self.sundense_matrix = _lib.SUNDenseMatrix( - self._state_size, self._state_size) - if self.sundense_matrix == NULL: - raise ProtocolError('Error calling CVODE routine SUNDenseMatrix: Null returned') - - # Create linear solver - self.sundense_solver = _lib.SUNDenseLinearSolver(self._state, self.sundense_matrix) - if self.sundense_solver == NULL: - raise ProtocolError('Error calling CVODE routine SUNDenseLinearSolver: Null returned') - - # Tell cvode to use this solver - flag = _lib.CVDlsSetLinearSolver(self.cvode_mem, self.sundense_solver, self.sundense_matrix) - self.check_flag(flag, 'CVDlsSetLinearSolver') - ELSE: - # Create dense matrix - flag = _lib.CVDense(self.cvode_mem, self._state_size) - self.check_flag(flag, 'CVDense') + # Create dense matrix + self.sundense_matrix = _lib.fc_SUNDenseMatrix( + self._state_size, self._state_size, self.sunctx) + if self.sundense_matrix == NULL: + raise ProtocolError('Error calling CVODE routine SUNDenseMatrix: Null returned') + + # Create linear solver + self.sundense_solver = _lib.fc_SUNLinSol_Dense( + self._state, self.sundense_matrix, self.sunctx) + if self.sundense_solver == NULL: + raise ProtocolError('Error calling CVODE routine SUNLinSolDense: Null returned') + + # Tell cvode to use this solver + flag = _lib.fc_CVodeSetLinearSolver( + self.cvode_mem, self.sundense_solver, self.sundense_matrix) + self.check_flag(flag, 'CVodeSetLinearSolver') _lib.CVodeSetMaxNumSteps(self.cvode_mem, 20000000) _lib.CVodeSetMaxStep(self.cvode_mem, 0.5) diff --git a/fc/sundials/sundials.pxd b/fc/sundials/sundials.pxd index 3a8a4c50..e40c4edb 100644 --- a/fc/sundials/sundials.pxd +++ b/fc/sundials/sundials.pxd @@ -2,7 +2,7 @@ """ Minimal Cython interface to the (CVODE part of the) SUNDIALS library, for use by Functional Curation. -Handles both SUNDIALS 2.4 and 2.5, since the parts of the interface we use didn't change. +Handles SUNDIALS 3.x to 7.x Based on http://code.google.com/p/python-sundials/source/browse/trunk/sundials/SundialsLib.pxd """ @@ -18,7 +18,6 @@ cdef extern from "sundials/sundials_nvector.h": ctypedef _generic_N_Vector *N_Vector cdef extern from "nvector/nvector_serial.h": - cdef N_Vector N_VMake_Serial(long int vec_length, realtype *v_data) N_Vector N_VNew_Serial(long int vec_length) void N_VDestroy_Serial(N_Vector v) void N_VPrint_Serial(N_Vector v) @@ -68,13 +67,6 @@ cdef extern from "cvode/cvode.h": ctypedef int (*CVRhsFn)(realtype t, N_Vector y, N_Vector ydot, void *user_data) ctypedef int (*CVRootFn)(realtype t, N_Vector y, realtype *gout, void *user_data) - # In version 4 Newton iteration became the default, and a new syntax was - # introduced to change it (which we don't need to use here) - IF SUNDIALS_MAJOR >= 4: - void *CVodeCreate(int lmm) - ELSE: - void *CVodeCreate(int lmm, int iter) - int CVodeSetUserData(void *cvode_mem, void *user_data) int CVodeInit(void *cvode_mem, CVRhsFn f, realtype t0, N_Vector y0) int CVodeReInit(void *cvode_mem, realtype t0, N_Vector y0) @@ -131,27 +123,109 @@ cdef extern from "cvode/cvode.h": char *CVodeGetReturnFlagName(int flag) void CVodeFree(void **cvode_mem) -IF SUNDIALS_MAJOR >= 3: - cdef extern from "sundials/sundials_matrix.h": - ctypedef struct _generic_SUNMatrix: - pass - ctypedef _generic_SUNMatrix* SUNMatrix - void SUNMatDestroy(SUNMatrix A) - - cdef extern from "sunmatrix/sunmatrix_dense.h": - SUNMatrix SUNDenseMatrix(sunindextype M, sunindextype N) - - cdef extern from "sunlinsol/sunlinsol_dense.h": - ctypedef struct _generic_SUNLinearSolver: - pass - ctypedef _generic_SUNLinearSolver* SUNLinearSolver - void SUNLinSolFree(SUNLinearSolver) - - cdef extern from "sundials/sundials_linearsolver.h": - SUNLinearSolver SUNDenseLinearSolver(N_Vector y, SUNMatrix A) - - cdef extern from "cvode/cvode_direct.h": - int CVDlsSetLinearSolver(void* cvode_mem, SUNLinearSolver LS, SUNMatrix A) -ELSE: - cdef extern from "cvode/cvode_dense.h": - int CVDense(void *cvode_mem, int N) +# All version-dependent wrappers in one C verbatim block. +cdef extern from *: + """ + #include + #include + #include + #include + #include + #include + + #if SUNDIALS_VERSION_MAJOR >= 6 + #include + #include + #elif SUNDIALS_VERSION_MAJOR >= 4 + #include + #else + #include + #endif + + /* In Sundials 7+, realtype was renamed to sunrealtype; provide alias for generated C code */ + #if SUNDIALS_VERSION_MAJOR >= 7 + typedef sunrealtype realtype; + #endif + + /* Create / free a SUNContext (v6+) or return NULL no-op for older versions */ + static void* fc_SUNContext_Create(void) { + #if SUNDIALS_VERSION_MAJOR >= 6 + SUNContext sunctx = NULL; + SUNContext_Create(NULL, &sunctx); + return (void*)sunctx; + #else + return NULL; + #endif + } + + static void fc_SUNContext_Free(void* sunctx_ptr) { + #if SUNDIALS_VERSION_MAJOR >= 6 + SUNContext ctx = (SUNContext)sunctx_ptr; + if (ctx) SUNContext_Free(&ctx); + #endif + } + + /* CVodeCreate wrapper: adds sunctx arg for v6+, drops iter arg in v4+ */ + static void* fc_CVodeCreate(int lmm, void* sunctx) { + #if SUNDIALS_VERSION_MAJOR >= 6 + return CVodeCreate(lmm, (SUNContext)sunctx); + #elif SUNDIALS_VERSION_MAJOR >= 4 + return CVodeCreate(lmm); + #else + return CVodeCreate(lmm, CV_NEWTON); + #endif + } + + /* N_VMake_Serial wrapper: adds sunctx arg for v6+ */ + static N_Vector fc_N_VMake_Serial(int len, double* data, void* sunctx) { + #if SUNDIALS_VERSION_MAJOR >= 6 + return N_VMake_Serial((sunindextype)len, data, (SUNContext)sunctx); + #else + return N_VMake_Serial((long int)len, data); + #endif + } + + /* SUNDenseMatrix wrapper: adds sunctx arg for v6+ */ + static void* fc_SUNDenseMatrix(int M, int N, void* sunctx) { + #if SUNDIALS_VERSION_MAJOR >= 6 + return (void*)SUNDenseMatrix((sunindextype)M, (sunindextype)N, (SUNContext)sunctx); + #else + return (void*)SUNDenseMatrix(M, N); + #endif + } + + /* Dense linear solver: renamed to SUNLinSol_Dense in v6; adds sunctx */ + static void* fc_SUNLinSol_Dense(void* y, void* A, void* sunctx) { + #if SUNDIALS_VERSION_MAJOR >= 6 + return (void*)SUNLinSol_Dense((N_Vector)y, (SUNMatrix)A, (SUNContext)sunctx); + #else + return (void*)SUNDenseLinearSolver((N_Vector)y, (SUNMatrix)A); + #endif + } + + /* CVodeSetLinearSolver: renamed from CVDlsSetLinearSolver in v4+ */ + static int fc_CVodeSetLinearSolver(void* cvode_mem, void* LS, void* A) { + #if SUNDIALS_VERSION_MAJOR >= 4 + return CVodeSetLinearSolver(cvode_mem, (SUNLinearSolver)LS, (SUNMatrix)A); + #else + return CVDlsSetLinearSolver(cvode_mem, (SUNLinearSolver)LS, (SUNMatrix)A); + #endif + } + + static void fc_SUNLinSolFree(void* LS) { + if (LS) SUNLinSolFree((SUNLinearSolver)LS); + } + + static void fc_SUNMatDestroy(void* A) { + if (A) SUNMatDestroy((SUNMatrix)A); + } + """ + void* fc_SUNContext_Create() + void fc_SUNContext_Free(void* sunctx) + void* fc_CVodeCreate(int lmm, void* sunctx) + N_Vector fc_N_VMake_Serial(int len, realtype* data, void* sunctx) + void* fc_SUNDenseMatrix(int M, int N, void* sunctx) + void* fc_SUNLinSol_Dense(void* y, void* A, void* sunctx) + int fc_CVodeSetLinearSolver(void* cvode_mem, void* LS, void* A) + void fc_SUNLinSolFree(void* LS) + void fc_SUNMatDestroy(void* A) diff --git a/setup.py b/setup.py index 64f0c293..729cc182 100644 --- a/setup.py +++ b/setup.py @@ -12,18 +12,21 @@ from setuptools import Extension, setup # Detect major sundials version -SUNDIALS_MAJOR = inline( +sundials_major = inline( ''' - cdef extern from "sundials/sundials_config.h": + cdef extern from *: + """ + #include + """ int SUNDIALS_VERSION_MAJOR return SUNDIALS_VERSION_MAJOR ''' ) -assert SUNDIALS_MAJOR >= 3, f"Unsupported Sundials version: {SUNDIALS_MAJOR}" +assert sundials_major >= 3, f"Unsupported SUNDIALS version {sundials_major}" -print(f"Building for Sundials {SUNDIALS_MAJOR}") +print(f"Building for Sundials {sundials_major}.x") # Define Cython modules extensions = [ @@ -31,7 +34,6 @@ name="fc.sundials.solver", sources=["fc/sundials/solver.pyx"], include_dirs=[".", numpy.get_include()], - define_macros=[("NPY_NO_DEPRECATED_API", "NPY_1_7_API_VERSION")], libraries=["sundials_cvode", "sundials_nvecserial"], ), ] @@ -41,8 +43,5 @@ name="fc", include_package_data=True, # Include non-python files via MANIFEST.in zip_safe=False, - ext_modules=cythonize( - extensions, - compile_time_env={"SUNDIALS_MAJOR": SUNDIALS_MAJOR}, - ), + ext_modules=cythonize(extensions), ) From 70e1aba6c8f9ade22b3bdb802e758563dfe2d44f Mon Sep 17 00:00:00 2001 From: Kwabena Amponsah Date: Thu, 19 Mar 2026 20:31:32 +0000 Subject: [PATCH 23/28] #236 Fix test_algebraic_models --- fc/protocol.py | 24 +------------------- fc/templates/weblab_model.pyx | 8 +++---- test/output/code_generation/weblab_model.pyx | 8 +++---- 3 files changed, 9 insertions(+), 31 deletions(-) diff --git a/fc/protocol.py b/fc/protocol.py index 0cdce6f5..281bd136 100644 --- a/fc/protocol.py +++ b/fc/protocol.py @@ -30,25 +30,9 @@ # Setup script SETUP_PY = ''' import numpy -from cython import inline from Cython.Build import cythonize from setuptools import Extension, setup - -SUNDIALS_VERSION_MAJOR = inline( - \''' - cdef extern from "": - """ - #ifndef SUNDIALS_VERSION_MAJOR - #define SUNDIALS_VERSION_MAJOR 2 - #endif - """ - int SUNDIALS_VERSION_MAJOR - - return SUNDIALS_VERSION_MAJOR - \''' -) - extensions = [ Extension( name="%(module_name)s", @@ -58,13 +42,7 @@ ), ] -setup( - name="%(module_name)s", - ext_modules=cythonize( - extensions, - compile_time_env={"SUNDIALS_VERSION_MAJOR": SUNDIALS_VERSION_MAJOR}, - ), -) +setup(name="%(module_name)s", ext_modules=cythonize(extensions)) ''' diff --git a/fc/templates/weblab_model.pyx b/fc/templates/weblab_model.pyx index d72f7457..1add928b 100644 --- a/fc/templates/weblab_model.pyx +++ b/fc/templates/weblab_model.pyx @@ -22,7 +22,7 @@ from fc.error_handling import ProtocolError from fc.sundials.solver cimport CvodeSolver -cdef int _evaluate_rhs(Sundials.sunrealtype {{ free_variable }}, +cdef int _evaluate_rhs(Sundials.realtype {{ free_variable }}, Sundials.N_Vector y, Sundials.N_Vector ydot, void* user_data) noexcept: @@ -33,7 +33,7 @@ cdef int _evaluate_rhs(Sundials.sunrealtype {{ free_variable }}, """ # We passed the Python model object in as CVODE user data; get it back as an object model = user_data - cdef np.ndarray[Sundials.sunrealtype, ndim=1] parameters = model.parameters + cdef np.ndarray[Sundials.realtype, ndim=1] parameters = model.parameters # Unpack state variables {%- for state in states %} @@ -188,7 +188,7 @@ cdef class {{ class_name }}(CvodeSolver): self.associate_with_model(self) #self._parameters = Sundials.N_VMake_Serial( # len(self.parameters), - # (self.parameters).data + # (self.parameters).data #) self.env = ModelWrapperEnvironment(self) @@ -221,7 +221,7 @@ cdef class {{ class_name }}(CvodeSolver): """ # Get parameters as sundials realtype numpy array - cdef np.ndarray[Sundials.sunrealtype, ndim=1] parameters = self.parameters + cdef np.ndarray[Sundials.realtype, ndim=1] parameters = self.parameters # Get current free variable cdef double {{ free_variable }} = self.free_variable diff --git a/test/output/code_generation/weblab_model.pyx b/test/output/code_generation/weblab_model.pyx index fd01f83c..3b52c2cb 100644 --- a/test/output/code_generation/weblab_model.pyx +++ b/test/output/code_generation/weblab_model.pyx @@ -22,7 +22,7 @@ from fc.error_handling import ProtocolError from fc.sundials.solver cimport CvodeSolver -cdef int _evaluate_rhs(Sundials.sunrealtype var_time, +cdef int _evaluate_rhs(Sundials.realtype var_time, Sundials.N_Vector y, Sundials.N_Vector ydot, void* user_data) noexcept: @@ -33,7 +33,7 @@ cdef int _evaluate_rhs(Sundials.sunrealtype var_time, """ # We passed the Python model object in as CVODE user data; get it back as an object model = user_data - cdef np.ndarray[Sundials.sunrealtype, ndim=1] parameters = model.parameters + cdef np.ndarray[Sundials.realtype, ndim=1] parameters = model.parameters # Unpack state variables cdef double var_V = (y.content).data[0] @@ -203,7 +203,7 @@ cdef class TestModel(CvodeSolver): self.associate_with_model(self) #self._parameters = Sundials.N_VMake_Serial( # len(self.parameters), - # (self.parameters).data + # (self.parameters).data #) self.env = ModelWrapperEnvironment(self) @@ -234,7 +234,7 @@ cdef class TestModel(CvodeSolver): """ # Get parameters as sundials realtype numpy array - cdef np.ndarray[Sundials.sunrealtype, ndim=1] parameters = self.parameters + cdef np.ndarray[Sundials.realtype, ndim=1] parameters = self.parameters # Get current free variable cdef double var_time = self.free_variable From bf95101c8ea1b2af3c15614a4c57c537a248f4b6 Mon Sep 17 00:00:00 2001 From: Kwabena Amponsah Date: Fri, 20 Mar 2026 10:16:43 +0000 Subject: [PATCH 24/28] #236 Fix sundials version detection --- setup.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/setup.py b/setup.py index 729cc182..3a81beff 100644 --- a/setup.py +++ b/setup.py @@ -7,6 +7,7 @@ """ import numpy +import warnings from cython import inline from Cython.Build import cythonize from setuptools import Extension, setup @@ -21,10 +22,12 @@ int SUNDIALS_VERSION_MAJOR return SUNDIALS_VERSION_MAJOR - ''' + ''', + force=True, # Always re-compile to pick up environment changes ) -assert sundials_major >= 3, f"Unsupported SUNDIALS version {sundials_major}" +if sundials_major < 3: + warnings.warn(f"Unsupported SUNDIALS version {sundials_major}") print(f"Building for Sundials {sundials_major}.x") @@ -43,5 +46,5 @@ name="fc", include_package_data=True, # Include non-python files via MANIFEST.in zip_safe=False, - ext_modules=cythonize(extensions), + ext_modules=cythonize(extensions, force=True), ) From f9b021c7dbb635edd151da6d1565f78b90e5d2f8 Mon Sep 17 00:00:00 2001 From: Kwabena Amponsah Date: Fri, 20 Mar 2026 16:29:37 +0000 Subject: [PATCH 25/28] #236 Update sundials version handling --- setup.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/setup.py b/setup.py index 3a81beff..c9f732d9 100644 --- a/setup.py +++ b/setup.py @@ -6,11 +6,12 @@ to have been set up before running. """ -import numpy import warnings -from cython import inline -from Cython.Build import cythonize + from setuptools import Extension, setup +from Cython.Build import cythonize +from cython import inline +import numpy # Detect major sundials version sundials_major = inline( @@ -26,7 +27,7 @@ force=True, # Always re-compile to pick up environment changes ) -if sundials_major < 3: +if not (3 <= sundials_major <= 7): warnings.warn(f"Unsupported SUNDIALS version {sundials_major}") print(f"Building for Sundials {sundials_major}.x") @@ -48,3 +49,6 @@ zip_safe=False, ext_modules=cythonize(extensions, force=True), ) + +# Deprecated NumPy API warning will disappear when we upgrade to numpy 2.x, see +# https://cython.readthedocs.io/en/stable/src/userguide/numpy_tutorial.html#compilation-using-setuptools From e7bc53e1f989ead57e2e674474fe13e73245f335 Mon Sep 17 00:00:00 2001 From: Kwabena Amponsah Date: Fri, 20 Mar 2026 16:37:37 +0000 Subject: [PATCH 26/28] #236 Bump up dependency versions --- pyproject.toml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 8c8799aa..1885cffe 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,5 +1,5 @@ [build-system] -requires = ["setuptools", "wheel", "cython", "numpy<2"] +requires = ["cython", "numpy>=1.20,<2.0", "setuptools", "wheel"] build-backend = "setuptools.build_meta" [project] @@ -28,11 +28,11 @@ maintainers = [ dependencies = [ "cellmlmanip", "cython", - "Jinja2>=2.10", + "Jinja2>=3.0", "matplotlib", "numexpr", - "numpy<2", - "pyparsing>=2.5, <3.4", + "numpy>=1.20,<2.0", # limit numpy<2 for compatibility with cellmlmanip + "pyparsing>=3.0", "scipy", "setuptools", "tables", @@ -45,8 +45,8 @@ dev = [ test = [ "codecov", - "flake8>=3.6", - "pytest>=3.6", + "flake8>=6.0", + "pytest>=8.0", "pytest-cov", "pytest-profiling", ] From c1b35aed836ddb33dab7fc0fb5a01d95b234c229 Mon Sep 17 00:00:00 2001 From: Kwabena Amponsah Date: Fri, 20 Mar 2026 16:38:45 +0000 Subject: [PATCH 27/28] #236 Enable long tests --- .github/workflows/coverage.yml | 2 +- fc/protocol.py | 4 ++-- fc/templates/weblab_model.pyx | 1 - 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index d1a2f997..9a3ce6f1 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -30,7 +30,7 @@ jobs: - name: Unit & coverage tests run: | - python3 -m pytest --cov --cov-config=.coveragerc + FC_LONG_TESTS=1 python3 -m pytest --cov --cov-config=.coveragerc - name: Submit report to codecov uses: codecov/codecov-action@v5 diff --git a/fc/protocol.py b/fc/protocol.py index 281bd136..111ccad9 100644 --- a/fc/protocol.py +++ b/fc/protocol.py @@ -29,9 +29,9 @@ # Setup script SETUP_PY = ''' -import numpy -from Cython.Build import cythonize from setuptools import Extension, setup +from Cython.Build import cythonize +import numpy extensions = [ Extension( diff --git a/fc/templates/weblab_model.pyx b/fc/templates/weblab_model.pyx index 1add928b..6e14cc45 100644 --- a/fc/templates/weblab_model.pyx +++ b/fc/templates/weblab_model.pyx @@ -310,7 +310,6 @@ cdef class {{ class_name }}(CvodeSolver): See :meth:`fc.simulations.AbstractOdeModel.set_solver()`. """ - # TODO Update this (and rest of fc) to Python3 # TODO Use logging here, or raise an exception print(' ' * self.indent_level, 'set_solver: Models implemented using Cython contain a built-in ODE solver, so ignoring setting.', file=sys.stderr) From 6a0cddf4671d4a49d279ba13b6747afc0bb6e4ad Mon Sep 17 00:00:00 2001 From: Kwabena Amponsah Date: Fri, 20 Mar 2026 17:28:26 +0000 Subject: [PATCH 28/28] #236 Run multiple tests in parallel --- .github/workflows/coverage.yml | 4 ++-- README.md | 8 +++----- pyproject.toml | 6 ++---- 3 files changed, 7 insertions(+), 11 deletions(-) diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index 9a3ce6f1..dbf1818b 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -26,11 +26,11 @@ jobs: - name: Build and install run: | - python3 -m pip install -e .[test] + python3 -m pip install -e .[dev] - name: Unit & coverage tests run: | - FC_LONG_TESTS=1 python3 -m pytest --cov --cov-config=.coveragerc + FC_LONG_TESTS=1 python3 -m pytest -n auto --cov --cov-config=.coveragerc - name: Submit report to codecov uses: codecov/codecov-action@v5 diff --git a/README.md b/README.md index 261a7caa..a05ba7d3 100644 --- a/README.md +++ b/README.md @@ -13,10 +13,8 @@ An ongoing attempt to document the Web Lab and all its interconnected technologi > [!NOTE] > ### No Windows support > -> **FC is tested/developed on Linux and OS/X. There are no plans to run it work on Windows**. -> -> It _might_ run on Windows, if you have installed CVODE with the shared libraries, -> and an MSVC > compiler that matches your Python installation (see [here](https://wiki.python.org/moin/WindowsCompilers)). +> FC is tested/developed on Linux and OS/X. There are no plans to make it run on Windows. +> We recommend the [Windows Subsystem for Linux](https://github.com/microsoft/WSL). > [!WARNING] > ### Transition! @@ -63,7 +61,7 @@ Compilation is performed using Python's [`setuptools`](https://setuptools.readth For developers, this can be done using: ```sh -python3 -m pip install -e ."[dev,test]" +python3 -m pip install -e ."[dev]" ``` Note that you'll need to repeat this step after any changes to Cython files (e.g. `.pyx` or `.pxd` files), because these don't automatically get recompiled. diff --git a/pyproject.toml b/pyproject.toml index 1885cffe..6527fe7c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -40,15 +40,13 @@ dependencies = [ [project.optional-dependencies] dev = [ - "pytest-xdist[psutil]", # "line_profiler", -] - -test = [ "codecov", "flake8>=6.0", + # "line_profiler", "pytest>=8.0", "pytest-cov", "pytest-profiling", + "pytest-xdist[psutil]", ] [project.urls]