viva-copasi 0.1.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- viva_copasi-0.1.2/LICENSE +21 -0
- viva_copasi-0.1.2/PKG-INFO +146 -0
- viva_copasi-0.1.2/README.md +126 -0
- viva_copasi-0.1.2/pbg_copasi/__init__.py +80 -0
- viva_copasi-0.1.2/pyproject.toml +32 -0
- viva_copasi-0.1.2/setup.cfg +4 -0
- viva_copasi-0.1.2/tests/test_composites.py +164 -0
- viva_copasi-0.1.2/tests/test_model_loading.py +29 -0
- viva_copasi-0.1.2/tests/test_parameter_estimation.py +198 -0
- viva_copasi-0.1.2/tests/test_processes.py +187 -0
- viva_copasi-0.1.2/viva_copasi/__init__.py +29 -0
- viva_copasi-0.1.2/viva_copasi/composites/__init__.py +327 -0
- viva_copasi-0.1.2/viva_copasi/composites/repressilator.xml +1052 -0
- viva_copasi-0.1.2/viva_copasi/composites/steady-state.composite.yaml +35 -0
- viva_copasi-0.1.2/viva_copasi/composites/utc-process.composite.yaml +52 -0
- viva_copasi-0.1.2/viva_copasi/composites/utc-step.composite.yaml +45 -0
- viva_copasi-0.1.2/viva_copasi/core.py +31 -0
- viva_copasi-0.1.2/viva_copasi/parameter_estimation.py +313 -0
- viva_copasi-0.1.2/viva_copasi/processes.py +512 -0
- viva_copasi-0.1.2/viva_copasi/types.py +11 -0
- viva_copasi-0.1.2/viva_copasi/visualizations.py +83 -0
- viva_copasi-0.1.2/viva_copasi.egg-info/PKG-INFO +146 -0
- viva_copasi-0.1.2/viva_copasi.egg-info/SOURCES.txt +24 -0
- viva_copasi-0.1.2/viva_copasi.egg-info/dependency_links.txt +1 -0
- viva_copasi-0.1.2/viva_copasi.egg-info/requires.txt +11 -0
- viva_copasi-0.1.2/viva_copasi.egg-info/top_level.txt +2 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Vivarium Collective
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: viva-copasi
|
|
3
|
+
Version: 0.1.2
|
|
4
|
+
Summary: process-bigraph-compatible COPASI Steps and Processes (UTC + SteadyState)
|
|
5
|
+
License: MIT
|
|
6
|
+
Requires-Python: >=3.11
|
|
7
|
+
Description-Content-Type: text/markdown
|
|
8
|
+
License-File: LICENSE
|
|
9
|
+
Requires-Dist: process-bigraph>=1.8.5
|
|
10
|
+
Requires-Dist: bigraph-schema
|
|
11
|
+
Requires-Dist: copasi-basico
|
|
12
|
+
Requires-Dist: python-copasi
|
|
13
|
+
Requires-Dist: viva-superpowers
|
|
14
|
+
Requires-Dist: pyyaml
|
|
15
|
+
Provides-Extra: dev
|
|
16
|
+
Requires-Dist: pytest; extra == "dev"
|
|
17
|
+
Requires-Dist: bigraph-viz; extra == "dev"
|
|
18
|
+
Requires-Dist: matplotlib; extra == "dev"
|
|
19
|
+
Dynamic: license-file
|
|
20
|
+
|
|
21
|
+
# viva-copasi
|
|
22
|
+
|
|
23
|
+
process-bigraph-compatible COPASI Steps and Processes for SBML model simulation.
|
|
24
|
+
|
|
25
|
+
Exposes any SBML kinetic model as a `process-bigraph` Step or Process so it can
|
|
26
|
+
be composed with other simulators in a bigraph document, using
|
|
27
|
+
[COPASI](https://copasi.org/) / [basico](https://basico.readthedocs.io/) as the
|
|
28
|
+
simulation back-end.
|
|
29
|
+
|
|
30
|
+
## Installation
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
uv venv .venv && source .venv/bin/activate
|
|
34
|
+
uv pip install -e .
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## Quick Start
|
|
38
|
+
|
|
39
|
+
```python
|
|
40
|
+
from importlib.resources import files
|
|
41
|
+
from process_bigraph import Composite, gather_emitter_results
|
|
42
|
+
from viva_copasi.composites import build_composite, register_copasi
|
|
43
|
+
|
|
44
|
+
# Resolve the bundled Repressilator model
|
|
45
|
+
model_path = str(files('viva_copasi.composites').joinpath('repressilator.xml'))
|
|
46
|
+
|
|
47
|
+
# Build and run the incremental UTC process composite
|
|
48
|
+
sim = build_composite('utc-process')
|
|
49
|
+
for _ in range(10):
|
|
50
|
+
sim.update({}, 1.0) # advance 1 time unit per step
|
|
51
|
+
|
|
52
|
+
results = gather_emitter_results(sim)
|
|
53
|
+
print(results[('emitter',)][-1])
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
## Classes
|
|
57
|
+
|
|
58
|
+
- `CopasiUTCStep` — one-shot Uniform Time Course (UTC) trajectory (Step)
|
|
59
|
+
- `CopasiUTCProcess` — incremental UTC simulation (Process)
|
|
60
|
+
- `CopasiSteadyStateStep` — steady-state solve (Step)
|
|
61
|
+
- `BaseCopasi` — shared model-load mixin (SBML parsing, species/reaction indexing)
|
|
62
|
+
|
|
63
|
+
All classes accept a `model_source` config key pointing to an SBML or COPASI
|
|
64
|
+
model file (path or URL).
|
|
65
|
+
|
|
66
|
+
## Composites
|
|
67
|
+
|
|
68
|
+
Three discoverable composites in `viva_copasi/composites/`:
|
|
69
|
+
|
|
70
|
+
| Name | Type | Description |
|
|
71
|
+
|---|---|---|
|
|
72
|
+
| `utc-step` | Step | One-shot UTC trajectory; returns full time series in a single `update()` call |
|
|
73
|
+
| `utc-process` | Process | Incremental UTC; advances one interval per `update()`, emitting concentrations + fluxes |
|
|
74
|
+
| `steady-state` | Step | Steady-state solve; returns equilibrium concentrations and reaction fluxes |
|
|
75
|
+
|
|
76
|
+
Each ships with the bundled SBML model
|
|
77
|
+
[BIOMD0000000012](https://www.ebi.ac.uk/biomodels/BIOMD0000000012) — the Elowitz
|
|
78
|
+
& Leibler 2000 Repressilator.
|
|
79
|
+
|
|
80
|
+
Load a composite by name:
|
|
81
|
+
|
|
82
|
+
```python
|
|
83
|
+
from viva_copasi.composites import build_composite, list_composite_specs
|
|
84
|
+
|
|
85
|
+
print(list_composite_specs()) # ['steady-state', 'utc-process', 'utc-step']
|
|
86
|
+
sim = build_composite('utc-process')
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
Override the model or parameters:
|
|
90
|
+
|
|
91
|
+
```python
|
|
92
|
+
sim = build_composite('utc-step', overrides={
|
|
93
|
+
'model_source': '/path/to/my_model.xml',
|
|
94
|
+
'time': 50.0,
|
|
95
|
+
'n_points': 51,
|
|
96
|
+
})
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
## API
|
|
100
|
+
|
|
101
|
+
### `CopasiUTCStep` (Step)
|
|
102
|
+
|
|
103
|
+
One-shot UTC trajectory.
|
|
104
|
+
|
|
105
|
+
Config: `model_source` (str), `time` (float, default 1.0), `n_points` (int, default 2).
|
|
106
|
+
|
|
107
|
+
Outputs: `result` — dict with keys `time` (list), `columns` (list), `values` (list of rows).
|
|
108
|
+
|
|
109
|
+
### `CopasiUTCProcess` (Process)
|
|
110
|
+
|
|
111
|
+
Incremental UTC simulation.
|
|
112
|
+
|
|
113
|
+
Config: `model_source` (str), `time` (float, default 1.0), `intervals` (int, default 10).
|
|
114
|
+
|
|
115
|
+
Outputs: `species_concentrations` (map[float]), `fluxes` (map[float]), `time` (list[float]).
|
|
116
|
+
|
|
117
|
+
### `CopasiSteadyStateStep` (Step)
|
|
118
|
+
|
|
119
|
+
Steady-state solve.
|
|
120
|
+
|
|
121
|
+
Config: `model_source` (str).
|
|
122
|
+
|
|
123
|
+
Outputs: `results` — dict with keys `time`, `species_concentrations`, `fluxes`.
|
|
124
|
+
|
|
125
|
+
### `register_copasi(core=None)`
|
|
126
|
+
|
|
127
|
+
Register all COPASI process classes, the RAM emitter, and the
|
|
128
|
+
`SpeciesConcentrationsPlot` visualization into a bigraph core.
|
|
129
|
+
|
|
130
|
+
### `make_copasi_utc_document(...)` / `make_copasi_utc_process_document(...)` / `make_copasi_steady_state_document(...)`
|
|
131
|
+
|
|
132
|
+
Programmatic composite-document factories for callers that want full control
|
|
133
|
+
over wiring without a YAML file.
|
|
134
|
+
|
|
135
|
+
## Tests
|
|
136
|
+
|
|
137
|
+
```bash
|
|
138
|
+
python -m pytest tests/ -q
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
## Demos
|
|
142
|
+
|
|
143
|
+
```bash
|
|
144
|
+
python demo/demo_report.py # runs all 3 classes; saves demo/report.html
|
|
145
|
+
python demo/composite_report.py # runs utc-process composite; saves demo/utc-process-report.html
|
|
146
|
+
```
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
# viva-copasi
|
|
2
|
+
|
|
3
|
+
process-bigraph-compatible COPASI Steps and Processes for SBML model simulation.
|
|
4
|
+
|
|
5
|
+
Exposes any SBML kinetic model as a `process-bigraph` Step or Process so it can
|
|
6
|
+
be composed with other simulators in a bigraph document, using
|
|
7
|
+
[COPASI](https://copasi.org/) / [basico](https://basico.readthedocs.io/) as the
|
|
8
|
+
simulation back-end.
|
|
9
|
+
|
|
10
|
+
## Installation
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
uv venv .venv && source .venv/bin/activate
|
|
14
|
+
uv pip install -e .
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## Quick Start
|
|
18
|
+
|
|
19
|
+
```python
|
|
20
|
+
from importlib.resources import files
|
|
21
|
+
from process_bigraph import Composite, gather_emitter_results
|
|
22
|
+
from viva_copasi.composites import build_composite, register_copasi
|
|
23
|
+
|
|
24
|
+
# Resolve the bundled Repressilator model
|
|
25
|
+
model_path = str(files('viva_copasi.composites').joinpath('repressilator.xml'))
|
|
26
|
+
|
|
27
|
+
# Build and run the incremental UTC process composite
|
|
28
|
+
sim = build_composite('utc-process')
|
|
29
|
+
for _ in range(10):
|
|
30
|
+
sim.update({}, 1.0) # advance 1 time unit per step
|
|
31
|
+
|
|
32
|
+
results = gather_emitter_results(sim)
|
|
33
|
+
print(results[('emitter',)][-1])
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## Classes
|
|
37
|
+
|
|
38
|
+
- `CopasiUTCStep` — one-shot Uniform Time Course (UTC) trajectory (Step)
|
|
39
|
+
- `CopasiUTCProcess` — incremental UTC simulation (Process)
|
|
40
|
+
- `CopasiSteadyStateStep` — steady-state solve (Step)
|
|
41
|
+
- `BaseCopasi` — shared model-load mixin (SBML parsing, species/reaction indexing)
|
|
42
|
+
|
|
43
|
+
All classes accept a `model_source` config key pointing to an SBML or COPASI
|
|
44
|
+
model file (path or URL).
|
|
45
|
+
|
|
46
|
+
## Composites
|
|
47
|
+
|
|
48
|
+
Three discoverable composites in `viva_copasi/composites/`:
|
|
49
|
+
|
|
50
|
+
| Name | Type | Description |
|
|
51
|
+
|---|---|---|
|
|
52
|
+
| `utc-step` | Step | One-shot UTC trajectory; returns full time series in a single `update()` call |
|
|
53
|
+
| `utc-process` | Process | Incremental UTC; advances one interval per `update()`, emitting concentrations + fluxes |
|
|
54
|
+
| `steady-state` | Step | Steady-state solve; returns equilibrium concentrations and reaction fluxes |
|
|
55
|
+
|
|
56
|
+
Each ships with the bundled SBML model
|
|
57
|
+
[BIOMD0000000012](https://www.ebi.ac.uk/biomodels/BIOMD0000000012) — the Elowitz
|
|
58
|
+
& Leibler 2000 Repressilator.
|
|
59
|
+
|
|
60
|
+
Load a composite by name:
|
|
61
|
+
|
|
62
|
+
```python
|
|
63
|
+
from viva_copasi.composites import build_composite, list_composite_specs
|
|
64
|
+
|
|
65
|
+
print(list_composite_specs()) # ['steady-state', 'utc-process', 'utc-step']
|
|
66
|
+
sim = build_composite('utc-process')
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
Override the model or parameters:
|
|
70
|
+
|
|
71
|
+
```python
|
|
72
|
+
sim = build_composite('utc-step', overrides={
|
|
73
|
+
'model_source': '/path/to/my_model.xml',
|
|
74
|
+
'time': 50.0,
|
|
75
|
+
'n_points': 51,
|
|
76
|
+
})
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
## API
|
|
80
|
+
|
|
81
|
+
### `CopasiUTCStep` (Step)
|
|
82
|
+
|
|
83
|
+
One-shot UTC trajectory.
|
|
84
|
+
|
|
85
|
+
Config: `model_source` (str), `time` (float, default 1.0), `n_points` (int, default 2).
|
|
86
|
+
|
|
87
|
+
Outputs: `result` — dict with keys `time` (list), `columns` (list), `values` (list of rows).
|
|
88
|
+
|
|
89
|
+
### `CopasiUTCProcess` (Process)
|
|
90
|
+
|
|
91
|
+
Incremental UTC simulation.
|
|
92
|
+
|
|
93
|
+
Config: `model_source` (str), `time` (float, default 1.0), `intervals` (int, default 10).
|
|
94
|
+
|
|
95
|
+
Outputs: `species_concentrations` (map[float]), `fluxes` (map[float]), `time` (list[float]).
|
|
96
|
+
|
|
97
|
+
### `CopasiSteadyStateStep` (Step)
|
|
98
|
+
|
|
99
|
+
Steady-state solve.
|
|
100
|
+
|
|
101
|
+
Config: `model_source` (str).
|
|
102
|
+
|
|
103
|
+
Outputs: `results` — dict with keys `time`, `species_concentrations`, `fluxes`.
|
|
104
|
+
|
|
105
|
+
### `register_copasi(core=None)`
|
|
106
|
+
|
|
107
|
+
Register all COPASI process classes, the RAM emitter, and the
|
|
108
|
+
`SpeciesConcentrationsPlot` visualization into a bigraph core.
|
|
109
|
+
|
|
110
|
+
### `make_copasi_utc_document(...)` / `make_copasi_utc_process_document(...)` / `make_copasi_steady_state_document(...)`
|
|
111
|
+
|
|
112
|
+
Programmatic composite-document factories for callers that want full control
|
|
113
|
+
over wiring without a YAML file.
|
|
114
|
+
|
|
115
|
+
## Tests
|
|
116
|
+
|
|
117
|
+
```bash
|
|
118
|
+
python -m pytest tests/ -q
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
## Demos
|
|
122
|
+
|
|
123
|
+
```bash
|
|
124
|
+
python demo/demo_report.py # runs all 3 classes; saves demo/report.html
|
|
125
|
+
python demo/composite_report.py # runs utc-process composite; saves demo/utc-process-report.html
|
|
126
|
+
```
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
"""Back-compat shim: ``pbg_copasi`` was renamed to ``viva_copasi``.
|
|
2
|
+
|
|
3
|
+
Part of the pbg -> viva rebrand (the GitHub repo is already
|
|
4
|
+
``vivarium-collective/viva-copasi``). This shim keeps every existing consumer
|
|
5
|
+
working during the deprecation window:
|
|
6
|
+
|
|
7
|
+
* ``import pbg_copasi`` / ``from pbg_copasi import X`` works (re-exports
|
|
8
|
+
the new package's top-level ``__all__``);
|
|
9
|
+
* ``import pbg_copasi.<sub>`` transparently resolves to
|
|
10
|
+
``viva_copasi.<sub>`` via a meta-path finder (so
|
|
11
|
+
``from pbg_copasi.simulators import ...`` still resolves); and
|
|
12
|
+
* ``python -m pbg_copasi.<sub>`` still executes.
|
|
13
|
+
|
|
14
|
+
Importing anything under this package emits a one-time
|
|
15
|
+
:class:`DeprecationWarning`. Update imports to ``viva_copasi``; this shim is
|
|
16
|
+
removed in a future major release.
|
|
17
|
+
"""
|
|
18
|
+
from __future__ import annotations
|
|
19
|
+
|
|
20
|
+
import importlib
|
|
21
|
+
import importlib.abc
|
|
22
|
+
import importlib.util
|
|
23
|
+
import sys
|
|
24
|
+
import warnings
|
|
25
|
+
|
|
26
|
+
warnings.warn(
|
|
27
|
+
"pbg_copasi is renamed to viva_copasi; update your imports "
|
|
28
|
+
"(the pbg_copasi alias is removed in a future major release).",
|
|
29
|
+
DeprecationWarning,
|
|
30
|
+
stacklevel=2,
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
_OLD = "pbg_copasi."
|
|
34
|
+
_NEW = "viva_copasi."
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class _Redirect(importlib.abc.MetaPathFinder, importlib.abc.Loader):
|
|
38
|
+
"""Forward ``pbg_copasi.<sub>`` imports to ``viva_copasi.<sub>``."""
|
|
39
|
+
|
|
40
|
+
def _target(self, name: str) -> str:
|
|
41
|
+
return _NEW + name[len(_OLD):]
|
|
42
|
+
|
|
43
|
+
def find_spec(self, name, path=None, target=None):
|
|
44
|
+
if not name.startswith(_OLD):
|
|
45
|
+
return None
|
|
46
|
+
real = importlib.util.find_spec(self._target(name))
|
|
47
|
+
if real is None:
|
|
48
|
+
return None
|
|
49
|
+
spec = importlib.util.spec_from_loader(
|
|
50
|
+
name,
|
|
51
|
+
self,
|
|
52
|
+
origin=real.origin,
|
|
53
|
+
is_package=real.submodule_search_locations is not None,
|
|
54
|
+
)
|
|
55
|
+
if real.submodule_search_locations is not None:
|
|
56
|
+
spec.submodule_search_locations = list(real.submodule_search_locations)
|
|
57
|
+
return spec
|
|
58
|
+
|
|
59
|
+
def create_module(self, spec):
|
|
60
|
+
# Alias the fully-initialized new-package module under BOTH names so
|
|
61
|
+
# `import a.b` and identity checks against either name agree.
|
|
62
|
+
mod = importlib.import_module(self._target(spec.name))
|
|
63
|
+
sys.modules[spec.name] = mod
|
|
64
|
+
return mod
|
|
65
|
+
|
|
66
|
+
def exec_module(self, module): # already executed by import_module
|
|
67
|
+
pass
|
|
68
|
+
|
|
69
|
+
def get_code(self, name):
|
|
70
|
+
# Support `python -m pbg_copasi.<sub>`: runpy needs a code object.
|
|
71
|
+
target = self._target(name)
|
|
72
|
+
return importlib.util.find_spec(target).loader.get_code(target)
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
sys.meta_path.insert(0, _Redirect())
|
|
76
|
+
|
|
77
|
+
_new = importlib.import_module("viva_copasi")
|
|
78
|
+
__version__ = getattr(_new, "__version__", "0.0.0")
|
|
79
|
+
# Re-export the new package's public surface (if any is declared).
|
|
80
|
+
globals().update({k: getattr(_new, k) for k in getattr(_new, "__all__", [])})
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=68.0", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "viva-copasi"
|
|
7
|
+
version = "0.1.2"
|
|
8
|
+
description = "process-bigraph-compatible COPASI Steps and Processes (UTC + SteadyState)"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
license = { text = "MIT" }
|
|
11
|
+
requires-python = ">=3.11"
|
|
12
|
+
dependencies = [
|
|
13
|
+
"process-bigraph>=1.8.5",
|
|
14
|
+
"bigraph-schema",
|
|
15
|
+
"copasi-basico",
|
|
16
|
+
"python-copasi",
|
|
17
|
+
"viva-superpowers",
|
|
18
|
+
"pyyaml",
|
|
19
|
+
]
|
|
20
|
+
|
|
21
|
+
[project.optional-dependencies]
|
|
22
|
+
dev = ["pytest", "bigraph-viz", "matplotlib"]
|
|
23
|
+
|
|
24
|
+
[tool.uv.sources]
|
|
25
|
+
# viva-superpowers (renamed from pbg-superpowers) is git-only, not on PyPI.
|
|
26
|
+
viva-superpowers = { git = "https://github.com/vivarium-collective/viva-superpowers.git", branch = "main" }
|
|
27
|
+
|
|
28
|
+
[tool.setuptools.packages.find]
|
|
29
|
+
include = ["viva_copasi*", "pbg_copasi"] # pbg_copasi = deprecation shim
|
|
30
|
+
|
|
31
|
+
[tool.setuptools.package-data]
|
|
32
|
+
"viva_copasi.composites" = ["*.xml", "*.yaml"]
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
"""Integration tests for viva-copasi composite specs.
|
|
2
|
+
|
|
3
|
+
Tests cover:
|
|
4
|
+
- Discovery: list_composite_specs() returns the three expected names.
|
|
5
|
+
- Structural validity: each *.composite.yaml has the required top-level keys.
|
|
6
|
+
- Instantiation: build_composite() produces a runnable process_bigraph.Composite.
|
|
7
|
+
- Short run: the UTC process composite advances and emits data.
|
|
8
|
+
|
|
9
|
+
Test model: Elowitz 2000 Repressilator (BIOMD0000000012), bundled at
|
|
10
|
+
viva_copasi/composites/repressilator.xml.
|
|
11
|
+
"""
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
|
|
14
|
+
import pytest
|
|
15
|
+
from process_bigraph import Composite, allocate_core, gather_emitter_results
|
|
16
|
+
from process_bigraph.emitter import RAMEmitter
|
|
17
|
+
|
|
18
|
+
from viva_copasi.composites import (
|
|
19
|
+
list_composite_specs,
|
|
20
|
+
load_composite_spec,
|
|
21
|
+
build_composite,
|
|
22
|
+
register_copasi,
|
|
23
|
+
make_copasi_utc_process_document,
|
|
24
|
+
make_copasi_steady_state_document,
|
|
25
|
+
_COMPOSITES_DIR,
|
|
26
|
+
)
|
|
27
|
+
from viva_copasi.processes import (
|
|
28
|
+
CopasiUTCStep,
|
|
29
|
+
CopasiSteadyStateStep,
|
|
30
|
+
CopasiUTCProcess,
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
# Repressilator model — same file bundled in the composites package dir.
|
|
34
|
+
BUNDLED_MODEL = str(_COMPOSITES_DIR / "repressilator.xml")
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
@pytest.fixture
|
|
38
|
+
def core():
|
|
39
|
+
"""Minimal core with all COPASI classes + RAM emitter registered."""
|
|
40
|
+
return register_copasi()
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
# ---------------------------------------------------------------------------
|
|
44
|
+
# Discovery
|
|
45
|
+
# ---------------------------------------------------------------------------
|
|
46
|
+
|
|
47
|
+
def test_list_composite_specs_returns_three():
|
|
48
|
+
"""Discovery returns the three expected composite names."""
|
|
49
|
+
specs = list_composite_specs()
|
|
50
|
+
assert 'utc-step' in specs
|
|
51
|
+
assert 'utc-process' in specs
|
|
52
|
+
assert 'steady-state' in specs
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
# ---------------------------------------------------------------------------
|
|
56
|
+
# Structural validity
|
|
57
|
+
# ---------------------------------------------------------------------------
|
|
58
|
+
|
|
59
|
+
def test_utc_step_spec_well_formed():
|
|
60
|
+
"""utc-step.composite.yaml has required top-level keys."""
|
|
61
|
+
spec = load_composite_spec('utc-step')
|
|
62
|
+
assert 'name' in spec
|
|
63
|
+
assert 'state' in spec
|
|
64
|
+
assert spec['name'] == 'utc-step'
|
|
65
|
+
assert 'copasi_utc' in spec['state']
|
|
66
|
+
assert spec['state']['copasi_utc']['address'] == 'local:CopasiUTCStep'
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def test_utc_process_spec_well_formed():
|
|
70
|
+
"""utc-process.composite.yaml has required top-level keys."""
|
|
71
|
+
spec = load_composite_spec('utc-process')
|
|
72
|
+
assert 'name' in spec
|
|
73
|
+
assert 'state' in spec
|
|
74
|
+
assert spec['name'] == 'utc-process'
|
|
75
|
+
assert 'copasi_utc_process' in spec['state']
|
|
76
|
+
assert spec['state']['copasi_utc_process']['address'] == 'local:CopasiUTCProcess'
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def test_steady_state_spec_well_formed():
|
|
80
|
+
"""steady-state.composite.yaml has required top-level keys."""
|
|
81
|
+
spec = load_composite_spec('steady-state')
|
|
82
|
+
assert 'name' in spec
|
|
83
|
+
assert 'state' in spec
|
|
84
|
+
assert spec['name'] == 'steady-state'
|
|
85
|
+
assert 'copasi_ss' in spec['state']
|
|
86
|
+
assert spec['state']['copasi_ss']['address'] == 'local:CopasiSteadyStateStep'
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
# ---------------------------------------------------------------------------
|
|
90
|
+
# build_composite — instantiation
|
|
91
|
+
# ---------------------------------------------------------------------------
|
|
92
|
+
|
|
93
|
+
def test_build_composite_utc_step(core):
|
|
94
|
+
"""build_composite('utc-step') instantiates a Composite without error."""
|
|
95
|
+
sim = build_composite('utc-step', core=core)
|
|
96
|
+
assert sim is not None
|
|
97
|
+
assert isinstance(sim, Composite)
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def test_build_composite_utc_process(core):
|
|
101
|
+
"""build_composite('utc-process') instantiates a Composite without error."""
|
|
102
|
+
sim = build_composite('utc-process', core=core)
|
|
103
|
+
assert sim is not None
|
|
104
|
+
assert isinstance(sim, Composite)
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def test_build_composite_steady_state(core):
|
|
108
|
+
"""build_composite('steady-state') instantiates a Composite without error."""
|
|
109
|
+
sim = build_composite('steady-state', core=core)
|
|
110
|
+
assert sim is not None
|
|
111
|
+
assert isinstance(sim, Composite)
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
# ---------------------------------------------------------------------------
|
|
115
|
+
# End-to-end: UTC process composite — short run + emitter check
|
|
116
|
+
# ---------------------------------------------------------------------------
|
|
117
|
+
|
|
118
|
+
def test_utc_process_composite_short_run(core):
|
|
119
|
+
"""CopasiUTCProcess composite runs 2 steps and emits species_concentrations."""
|
|
120
|
+
doc = make_copasi_utc_process_document(
|
|
121
|
+
model_source=BUNDLED_MODEL,
|
|
122
|
+
time=1.0,
|
|
123
|
+
intervals=5,
|
|
124
|
+
)
|
|
125
|
+
sim = Composite({'state': doc}, core=core)
|
|
126
|
+
sim.run(2.0)
|
|
127
|
+
|
|
128
|
+
stores = sim.state['stores']
|
|
129
|
+
assert 'species_concentrations' in stores
|
|
130
|
+
assert isinstance(stores['species_concentrations'], dict)
|
|
131
|
+
# Repressilator has 6 species
|
|
132
|
+
assert len(stores['species_concentrations']) >= 3
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def test_utc_process_composite_emitter(core):
|
|
136
|
+
"""Emitter collects at least one record after a short run."""
|
|
137
|
+
doc = make_copasi_utc_process_document(
|
|
138
|
+
model_source=BUNDLED_MODEL,
|
|
139
|
+
time=1.0,
|
|
140
|
+
intervals=5,
|
|
141
|
+
)
|
|
142
|
+
sim = Composite({'state': doc}, core=core)
|
|
143
|
+
sim.run(2.0)
|
|
144
|
+
|
|
145
|
+
raw = gather_emitter_results(sim)
|
|
146
|
+
rows = raw[('emitter',)]
|
|
147
|
+
assert len(rows) >= 1
|
|
148
|
+
# Each row should have species_concentrations
|
|
149
|
+
row = rows[0]
|
|
150
|
+
assert 'species_concentrations' in row or 'fluxes' in row
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
# ---------------------------------------------------------------------------
|
|
154
|
+
# End-to-end: steady-state composite — single step
|
|
155
|
+
# ---------------------------------------------------------------------------
|
|
156
|
+
|
|
157
|
+
def test_steady_state_composite_runs(core):
|
|
158
|
+
"""CopasiSteadyStateStep composite runs and populates results store."""
|
|
159
|
+
doc = make_copasi_steady_state_document(model_source=BUNDLED_MODEL)
|
|
160
|
+
sim = Composite({'state': doc}, core=core)
|
|
161
|
+
sim.run(1.0)
|
|
162
|
+
|
|
163
|
+
ss_stores = sim.state['ss_stores']
|
|
164
|
+
assert 'results' in ss_stores
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import pytest
|
|
2
|
+
|
|
3
|
+
from viva_copasi.processes import _model_path_resolution
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def test_missing_relative_path_raises_with_context(tmp_path, monkeypatch):
|
|
7
|
+
monkeypatch.chdir(tmp_path)
|
|
8
|
+
with pytest.raises(FileNotFoundError) as e:
|
|
9
|
+
_model_path_resolution("does_not_exist.cps")
|
|
10
|
+
msg = str(e.value)
|
|
11
|
+
assert "does_not_exist.cps" in msg
|
|
12
|
+
assert str(tmp_path / "does_not_exist.cps") in msg
|
|
13
|
+
assert str(tmp_path) in msg
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def test_missing_absolute_path_raises(tmp_path):
|
|
17
|
+
with pytest.raises(FileNotFoundError):
|
|
18
|
+
_model_path_resolution(str(tmp_path / "nope.xml"))
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def test_url_passes_through():
|
|
22
|
+
url = "https://example.org/model.xml"
|
|
23
|
+
assert _model_path_resolution(url) == url
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def test_existing_absolute_path_unchanged(tmp_path):
|
|
27
|
+
f = tmp_path / "m.xml"
|
|
28
|
+
f.write_text("<sbml/>")
|
|
29
|
+
assert _model_path_resolution(str(f)) == str(f)
|