PyAntiGen 2.1.3__tar.gz → 2.1.4__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.
Files changed (112) hide show
  1. {pyantigen-2.1.3 → pyantigen-2.1.4}/PKG-INFO +1 -1
  2. {pyantigen-2.1.3 → pyantigen-2.1.4}/PyAntiGen.egg-info/PKG-INFO +1 -1
  3. {pyantigen-2.1.3 → pyantigen-2.1.4}/PyAntiGen.egg-info/scm_file_list.json +1 -0
  4. pyantigen-2.1.4/PyAntiGen.egg-info/scm_version.json +8 -0
  5. {pyantigen-2.1.3 → pyantigen-2.1.4}/docs/ANALYTIC_GRADIENT.md +3 -0
  6. {pyantigen-2.1.3 → pyantigen-2.1.4}/pyantigen/_version.py +3 -3
  7. {pyantigen-2.1.3 → pyantigen-2.1.4}/pyantigen/engine/Analytic_gradient.py +14 -1
  8. {pyantigen-2.1.3 → pyantigen-2.1.4}/pyantigen/generate/scaled_time.py +47 -1
  9. pyantigen-2.1.3/PyAntiGen.egg-info/scm_version.json +0 -8
  10. {pyantigen-2.1.3 → pyantigen-2.1.4}/.github/workflows/publish.yml +0 -0
  11. {pyantigen-2.1.3 → pyantigen-2.1.4}/.github/workflows/tests.yml +0 -0
  12. {pyantigen-2.1.3 → pyantigen-2.1.4}/.gitignore +0 -0
  13. {pyantigen-2.1.3 → pyantigen-2.1.4}/.vscode/launch.json +0 -0
  14. {pyantigen-2.1.3 → pyantigen-2.1.4}/LICENSE +0 -0
  15. {pyantigen-2.1.3 → pyantigen-2.1.4}/MANIFEST.in +0 -0
  16. {pyantigen-2.1.3 → pyantigen-2.1.4}/PyAntiGen.egg-info/SOURCES.txt +0 -0
  17. {pyantigen-2.1.3 → pyantigen-2.1.4}/PyAntiGen.egg-info/dependency_links.txt +0 -0
  18. {pyantigen-2.1.3 → pyantigen-2.1.4}/PyAntiGen.egg-info/entry_points.txt +0 -0
  19. {pyantigen-2.1.3 → pyantigen-2.1.4}/PyAntiGen.egg-info/requires.txt +0 -0
  20. {pyantigen-2.1.3 → pyantigen-2.1.4}/PyAntiGen.egg-info/top_level.txt +0 -0
  21. {pyantigen-2.1.3 → pyantigen-2.1.4}/README.md +0 -0
  22. {pyantigen-2.1.3 → pyantigen-2.1.4}/docs/MULTISTART.md +0 -0
  23. {pyantigen-2.1.3 → pyantigen-2.1.4}/docs/PROTOCOL_LAYER.md +0 -0
  24. {pyantigen-2.1.3 → pyantigen-2.1.4}/docs/V2_DESIGN.md +0 -0
  25. {pyantigen-2.1.3 → pyantigen-2.1.4}/pyantigen/.agents/skills/module_generation/SKILL.md +0 -0
  26. {pyantigen-2.1.3 → pyantigen-2.1.4}/pyantigen/.agents/skills/ode_conversion_antimony/SKILL.md +0 -0
  27. {pyantigen-2.1.3 → pyantigen-2.1.4}/pyantigen/.agents/skills/ode_extraction_pipeline/SKILL.md +0 -0
  28. {pyantigen-2.1.3 → pyantigen-2.1.4}/pyantigen/.gitignore +0 -0
  29. {pyantigen-2.1.3 → pyantigen-2.1.4}/pyantigen/__init__.py +0 -0
  30. {pyantigen-2.1.3 → pyantigen-2.1.4}/pyantigen/cli.py +0 -0
  31. {pyantigen-2.1.3 → pyantigen-2.1.4}/pyantigen/engine/Anchor_cache.py +0 -0
  32. {pyantigen-2.1.3 → pyantigen-2.1.4}/pyantigen/engine/Deadline.py +0 -0
  33. {pyantigen-2.1.3 → pyantigen-2.1.4}/pyantigen/engine/Differential_evolution.py +0 -0
  34. {pyantigen-2.1.3 → pyantigen-2.1.4}/pyantigen/engine/Evaluator.py +0 -0
  35. {pyantigen-2.1.3 → pyantigen-2.1.4}/pyantigen/engine/Event_times.py +0 -0
  36. {pyantigen-2.1.3 → pyantigen-2.1.4}/pyantigen/engine/Fast_profile.py +0 -0
  37. {pyantigen-2.1.3 → pyantigen-2.1.4}/pyantigen/engine/Fit_cache.py +0 -0
  38. {pyantigen-2.1.3 → pyantigen-2.1.4}/pyantigen/engine/Identifiability.py +0 -0
  39. {pyantigen-2.1.3 → pyantigen-2.1.4}/pyantigen/engine/Model_cache.py +0 -0
  40. {pyantigen-2.1.3 → pyantigen-2.1.4}/pyantigen/engine/Model_optimize.py +0 -0
  41. {pyantigen-2.1.3 → pyantigen-2.1.4}/pyantigen/engine/Model_simulate.py +0 -0
  42. {pyantigen-2.1.3 → pyantigen-2.1.4}/pyantigen/engine/Multistart.py +0 -0
  43. {pyantigen-2.1.3 → pyantigen-2.1.4}/pyantigen/engine/Nelder_mead.py +0 -0
  44. {pyantigen-2.1.3 → pyantigen-2.1.4}/pyantigen/engine/Noise_floor.py +0 -0
  45. {pyantigen-2.1.3 → pyantigen-2.1.4}/pyantigen/engine/Optimize.py +0 -0
  46. {pyantigen-2.1.3 → pyantigen-2.1.4}/pyantigen/engine/Petab_export.py +0 -0
  47. {pyantigen-2.1.3 → pyantigen-2.1.4}/pyantigen/engine/Preequil_cache.py +0 -0
  48. {pyantigen-2.1.3 → pyantigen-2.1.4}/pyantigen/engine/Profile_checkpoint.py +0 -0
  49. {pyantigen-2.1.3 → pyantigen-2.1.4}/pyantigen/engine/Profile_plan.py +0 -0
  50. {pyantigen-2.1.3 → pyantigen-2.1.4}/pyantigen/engine/README.md +0 -0
  51. {pyantigen-2.1.3 → pyantigen-2.1.4}/pyantigen/engine/Results.py +0 -0
  52. {pyantigen-2.1.3 → pyantigen-2.1.4}/pyantigen/engine/Scaled_amici.py +0 -0
  53. {pyantigen-2.1.3 → pyantigen-2.1.4}/pyantigen/engine/Segmented.py +0 -0
  54. {pyantigen-2.1.3 → pyantigen-2.1.4}/pyantigen/engine/Sensitivity_analysis.py +0 -0
  55. {pyantigen-2.1.3 → pyantigen-2.1.4}/pyantigen/engine/Simulate.py +0 -0
  56. {pyantigen-2.1.3 → pyantigen-2.1.4}/pyantigen/engine/Stop_rules.py +0 -0
  57. {pyantigen-2.1.3 → pyantigen-2.1.4}/pyantigen/engine/__init__.py +0 -0
  58. {pyantigen-2.1.3 → pyantigen-2.1.4}/pyantigen/generate/AntimonyGen.py +0 -0
  59. {pyantigen-2.1.3 → pyantigen-2.1.4}/pyantigen/generate/RxnDict_to_antimony.py +0 -0
  60. {pyantigen-2.1.3 → pyantigen-2.1.4}/pyantigen/generate/TelluriumGen.py +0 -0
  61. {pyantigen-2.1.3 → pyantigen-2.1.4}/pyantigen/generate/__init__.py +0 -0
  62. {pyantigen-2.1.3 → pyantigen-2.1.4}/pyantigen/generate/antimony_utils.py +0 -0
  63. {pyantigen-2.1.3 → pyantigen-2.1.4}/pyantigen/generate/data_interpolation.py +0 -0
  64. {pyantigen-2.1.3 → pyantigen-2.1.4}/pyantigen/generate/input_schedule.py +0 -0
  65. {pyantigen-2.1.3 → pyantigen-2.1.4}/pyantigen/generate/isotopomer_tools.py +0 -0
  66. {pyantigen-2.1.3 → pyantigen-2.1.4}/pyantigen/generate/model_generation.py +0 -0
  67. {pyantigen-2.1.3 → pyantigen-2.1.4}/pyantigen/generate/models.py +0 -0
  68. {pyantigen-2.1.3 → pyantigen-2.1.4}/pyantigen/generate/module_base.py +0 -0
  69. {pyantigen-2.1.3 → pyantigen-2.1.4}/pyantigen/generate/pyantigen.py +0 -0
  70. {pyantigen-2.1.3 → pyantigen-2.1.4}/pyantigen/generate/rate_laws.py +0 -0
  71. {pyantigen-2.1.3 → pyantigen-2.1.4}/pyantigen/generate/reaction_creation.py +0 -0
  72. {pyantigen-2.1.3 → pyantigen-2.1.4}/pyantigen/study/__init__.py +0 -0
  73. {pyantigen-2.1.3 → pyantigen-2.1.4}/pyantigen/study/assay.py +0 -0
  74. {pyantigen-2.1.3 → pyantigen-2.1.4}/pyantigen/study/describe.py +0 -0
  75. {pyantigen-2.1.3 → pyantigen-2.1.4}/pyantigen/study/design.py +0 -0
  76. {pyantigen-2.1.3 → pyantigen-2.1.4}/pyantigen/study/lower_v1.py +0 -0
  77. {pyantigen-2.1.3 → pyantigen-2.1.4}/pyantigen/study/optimization.py +0 -0
  78. {pyantigen-2.1.3 → pyantigen-2.1.4}/pyantigen/study/params.py +0 -0
  79. {pyantigen-2.1.3 → pyantigen-2.1.4}/pyantigen/study/quantity.py +0 -0
  80. {pyantigen-2.1.3 → pyantigen-2.1.4}/pyantigen/study/reagent.py +0 -0
  81. {pyantigen-2.1.3 → pyantigen-2.1.4}/pyantigen/study/refs.py +0 -0
  82. {pyantigen-2.1.3 → pyantigen-2.1.4}/pyantigen/study/remarks.py +0 -0
  83. {pyantigen-2.1.3 → pyantigen-2.1.4}/pyantigen/study/serialize.py +0 -0
  84. {pyantigen-2.1.3 → pyantigen-2.1.4}/pyantigen/study/validate.py +0 -0
  85. {pyantigen-2.1.3 → pyantigen-2.1.4}/pyantigen/template/Example/AntiGen_paths.py +0 -0
  86. {pyantigen-2.1.3 → pyantigen-2.1.4}/pyantigen/template/Example/Flipflop_reference.py +0 -0
  87. {pyantigen-2.1.3 → pyantigen-2.1.4}/pyantigen/template/Example/Model_generate.py +0 -0
  88. {pyantigen-2.1.3 → pyantigen-2.1.4}/pyantigen/template/Example/Model_run.py +0 -0
  89. {pyantigen-2.1.3 → pyantigen-2.1.4}/pyantigen/template/Example/Modules/Data.py +0 -0
  90. {pyantigen-2.1.3 → pyantigen-2.1.4}/pyantigen/template/Example/Modules/Events.py +0 -0
  91. {pyantigen-2.1.3 → pyantigen-2.1.4}/pyantigen/template/Example/Modules/Experiment.py +0 -0
  92. {pyantigen-2.1.3 → pyantigen-2.1.4}/pyantigen/template/Example/Modules/Loss_config.py +0 -0
  93. {pyantigen-2.1.3 → pyantigen-2.1.4}/pyantigen/template/Example/Modules/Observed_species.py +0 -0
  94. {pyantigen-2.1.3 → pyantigen-2.1.4}/pyantigen/template/Example/Modules/Optimizer_settings.py +0 -0
  95. {pyantigen-2.1.3 → pyantigen-2.1.4}/pyantigen/template/Example/Modules/Plots.py +0 -0
  96. {pyantigen-2.1.3 → pyantigen-2.1.4}/pyantigen/template/Example/Modules/Solver_settings.py +0 -0
  97. {pyantigen-2.1.3 → pyantigen-2.1.4}/pyantigen/template/Example/Modules/Update_opt_parameters.py +0 -0
  98. {pyantigen-2.1.3 → pyantigen-2.1.4}/pyantigen/template/Example/Modules/Update_parameters.py +0 -0
  99. {pyantigen-2.1.3 → pyantigen-2.1.4}/pyantigen/template/Example/optimizations/Example1_ADneg.json +0 -0
  100. {pyantigen-2.1.3 → pyantigen-2.1.4}/pyantigen/template/Example/optimizations/Example1_ADpos.json +0 -0
  101. {pyantigen-2.1.3 → pyantigen-2.1.4}/pyantigen/template/Example/optimizations/Example3_joint.json +0 -0
  102. {pyantigen-2.1.3 → pyantigen-2.1.4}/pyantigen/template/Example/optimizations/Example4_flipflop.json +0 -0
  103. {pyantigen-2.1.3 → pyantigen-2.1.4}/pyantigen/template/Example/optimizations/example.py +0 -0
  104. {pyantigen-2.1.3 → pyantigen-2.1.4}/pyantigen/template/Example/studies/example.json +0 -0
  105. {pyantigen-2.1.3 → pyantigen-2.1.4}/pyantigen/template/Example/studies/example.py +0 -0
  106. {pyantigen-2.1.3 → pyantigen-2.1.4}/pyantigen/template/Example/studies/flipflop.json +0 -0
  107. {pyantigen-2.1.3 → pyantigen-2.1.4}/pyantigen/template/data/ADneg.csv +0 -0
  108. {pyantigen-2.1.3 → pyantigen-2.1.4}/pyantigen/template/data/ADpos.csv +0 -0
  109. {pyantigen-2.1.3 → pyantigen-2.1.4}/pyantigen/template/data/Flipflop.csv +0 -0
  110. {pyantigen-2.1.3 → pyantigen-2.1.4}/pyantigen/template/data/make_flipflop_data.py +0 -0
  111. {pyantigen-2.1.3 → pyantigen-2.1.4}/pyproject.toml +0 -0
  112. {pyantigen-2.1.3 → pyantigen-2.1.4}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: PyAntiGen
3
- Version: 2.1.3
3
+ Version: 2.1.4
4
4
  Summary: Declarative generation, simulation and identifiability analysis of compartmental Antimony/SBML models
5
5
  Author-email: Don Elbert <elbert5770@gmail.com>
6
6
  License-Expression: MIT
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: PyAntiGen
3
- Version: 2.1.3
3
+ Version: 2.1.4
4
4
  Summary: Declarative generation, simulation and identifiability analysis of compartmental Antimony/SBML models
5
5
  Author-email: Don Elbert <elbert5770@gmail.com>
6
6
  License-Expression: MIT
@@ -103,6 +103,7 @@
103
103
  "tests/README.md",
104
104
  "tests/__init__.py",
105
105
  "tests/conftest.py",
106
+ "tests/engine/test_amici_flux_outputs.py",
106
107
  "tests/engine/test_amici_jumps.py",
107
108
  "tests/engine/test_amici_sensitivity.py",
108
109
  "tests/engine/test_analytic_gradient.py",
@@ -0,0 +1,8 @@
1
+ {
2
+ "tag": "2.1.4",
3
+ "distance": 0,
4
+ "node": "g2e65abb870fe7cb1136e01bcd8c30b6d0f712dd6",
5
+ "dirty": false,
6
+ "branch": "HEAD",
7
+ "node_date": "2026-10-14"
8
+ }
@@ -123,6 +123,9 @@ dynamics read them.
123
123
  * Columns that are not species, parameters or compartments (reaction rates) are not available to the
124
124
  loss; the gradient is then zero for anything that depends only on them.
125
125
  * The profile-likelihood stage keeps its own optimizers.
126
+ The gradient can differentiate what a profile point minimizes: `provider.freeze_sigmas(block_sigmas)` pins the floored blocks'
127
+ sigmas as `evaluate_nll_fixed(frozen_sigmas=...)` does (without it the gradient is off by 10 to 30% away from the fit);
128
+ `freeze_sigmas(None)` releases them. Nothing calls it yet.
126
129
  * Cost: the gradient costs one AMICI forward-sensitivity run, which grows with the number of
127
130
  parameters it differentiates. On SILK, 8 parameters: about 1.2 s a gradient against 9 objective
128
131
  evaluations (about 1.4 s) for forward differences; with all 48 parameters AMICI needs about 40 s a
@@ -18,7 +18,7 @@ version_tuple: tuple[int | str, ...]
18
18
  commit_id: str | None
19
19
  __commit_id__: str | None
20
20
 
21
- __version__ = version = '2.1.3'
22
- __version_tuple__ = version_tuple = (2, 1, 3)
21
+ __version__ = version = '2.1.4'
22
+ __version_tuple__ = version_tuple = (2, 1, 4)
23
23
 
24
- __commit_id__ = commit_id = 'g45ef1dd29'
24
+ __commit_id__ = commit_id = 'g2e65abb87'
@@ -91,6 +91,8 @@ class LossGradient:
91
91
  self.param_names = list(param_names)
92
92
  self.rel_step = float(rel_step)
93
93
  self.explicit = self._explicit_parameters()
94
+ # {(block, observable): sigma}: the floored blocks' sigmas a profile point holds still (see freeze_sigmas)
95
+ self.frozen_sigmas = None
94
96
 
95
97
  # -- which parameters the loss reads by name ------------------------------------
96
98
  def _explicit_parameters(self):
@@ -123,7 +125,8 @@ class LossGradient:
123
125
  from pyantigen.engine.Optimize import collect_loss_blocks, concentrated_nll
124
126
  p_dict = dict(zip(self.param_names, np.asarray(p_lin, dtype=float).tolist()))
125
127
  blocks = collect_loss_blocks(items, self.groups, self.replicates, self.param_names,
126
- np.asarray(p_lin, dtype=float), p_dict)
128
+ np.asarray(p_lin, dtype=float), p_dict,
129
+ frozen_sigmas=self.frozen_sigmas)
127
130
  if not blocks:
128
131
  raise AnalyticGradientError("the loss has no data to score")
129
132
  v = concentrated_nll(blocks)
@@ -581,6 +584,16 @@ class AmiciGradient:
581
584
  points += [p for p in (lo, hi) if np.all(np.isfinite(p)) and not np.array_equal(p, x0)]
582
585
  return points
583
586
 
587
+ def freeze_sigmas(self, sigmas):
588
+ """Differentiate the loss a profile point minimizes: floored blocks keep the sigma they had at the fit.
589
+
590
+ *sigmas* is what ``Optimize.block_sigmas`` returns for the fit (``{(block, observable): sigma_used}``),
591
+ the same dictionary the profile passes to ``evaluate_nll_fixed(frozen_sigmas=...)``. A frozen block is
592
+ scored with that sigma instead of ``min(sigma_hat, floor)`` at the current point, which changes its
593
+ derivative (the sigma no longer moves with the parameters). ``None`` releases them.
594
+ """
595
+ self.loss.frozen_sigmas = dict(sigmas) if sigmas else None
596
+
584
597
  def linear_gradient(self, p_lin):
585
598
  """``(nll, d nll / d p_lin)`` evaluated on AMICI's own simulation."""
586
599
  items, dirs = {}, {}
@@ -23,7 +23,8 @@ What this module does to the model
23
23
  from per-segment constants ``sched_a0/a1`` and ``sched_c0_<m>/c1_<m>`` (the
24
24
  anchors and coefficients of the segment's two end points). The solver sees
25
25
  ``D`` as a function of the parameters.
26
- * Every reaction rate and every rate rule is multiplied by ``sched_Delta``.
26
+ * Every reaction rate and every rate rule is multiplied by ``sched_Delta``; a formula that reads a reaction id
27
+ (a rule that sums fluxes) reads ``id/sched_Delta``, the flux of the unscaled model.
27
28
  * A measured curve ``c`` (an assignment rule in the model) becomes
28
29
  ``sched_ci_c + sched_cs_c*sched_clock``: on one segment it is a straight line.
29
30
  * Each parameter the schedule assigns (``Q_Leak``, ...) becomes a state with rate
@@ -133,6 +134,32 @@ def _with_clock(node):
133
134
  return _parse(_TIME_TOKEN.sub(CLOCK, libsbml.formulaToL3String(node)))
134
135
 
135
136
 
137
+ _TOKEN = re.compile(r"(?<![A-Za-z0-9_.])[A-Za-z_][A-Za-z0-9_]*")
138
+
139
+
140
+ def _reads_any(node, names):
141
+ if node.isName() and node.getName() in names:
142
+ return True
143
+ return any(_reads_any(node.getChild(i), names) for i in range(node.getNumChildren()))
144
+
145
+
146
+ def _with_true_fluxes(node, reaction_ids):
147
+ """*node*'s mathematics with each reaction id read as the flux of the original time, as a new tree; None if it reads none.
148
+
149
+ A reaction id in a formula means the reaction's rate. Step 3 of :func:`scale_model` multiplies every rate law by
150
+ ``sched_Delta``, so a rule that sums fluxes (``BBB_transport := BBBTransport_AB42 + ...``) would read
151
+ ``Delta`` times the flux, and an output or a rate rule built on it would be wrong by the length of the segment.
152
+ Reading ``(R/sched_Delta)`` instead gives the rule the flux it has in the unscaled model, and the solver
153
+ differentiates the quotient, ``dDelta/dp`` included. Done on the formula text, for the reason given in
154
+ :func:`_with_clock`.
155
+ """
156
+ if not _reads_any(node, reaction_ids):
157
+ return None
158
+ text = _TOKEN.sub(lambda m: f"({m.group(0)}/{DELTA})" if m.group(0) in reaction_ids else m.group(0),
159
+ libsbml.formulaToL3String(node))
160
+ return _parse(text)
161
+
162
+
136
163
  def _expand_initial(model, text, depth=0):
137
164
  """*text* with every parameter that has an initial assignment replaced by that assignment.
138
165
 
@@ -221,6 +248,25 @@ def scale_model(sbml, schedule):
221
248
  _add_parameter(model, f"{PREFIX}cs_{c}")
222
249
  rule.setMath(_parse(f"{PREFIX}ci_{c} + {PREFIX}cs_{c}*{CLOCK}"))
223
250
 
251
+ # 3a. a formula that reads a reaction id reads that reaction's rate: keep it the unscaled one
252
+ reaction_ids = {model.getReaction(i).getId() for i in range(model.getNumReactions())}
253
+ if reaction_ids:
254
+ for i in range(model.getNumRules()):
255
+ rule = model.getRule(i)
256
+ new = _with_true_fluxes(rule.getMath(), reaction_ids)
257
+ if new is not None:
258
+ rule.setMath(new)
259
+ for i in range(model.getNumReactions()):
260
+ kl = model.getReaction(i).getKineticLaw()
261
+ new = _with_true_fluxes(kl.getMath(), reaction_ids) if kl is not None and kl.isSetMath() else None
262
+ if new is not None:
263
+ kl.setMath(new)
264
+ for i in range(model.getNumInitialAssignments()):
265
+ assignment = model.getInitialAssignment(i)
266
+ new = _with_true_fluxes(assignment.getMath(), reaction_ids)
267
+ if new is not None:
268
+ assignment.setMath(new)
269
+
224
270
  # 3. rates times Delta (before the new rules, which must not be scaled)
225
271
  delta = lambda: _name_node(DELTA) # noqa: E731
226
272
  for i in range(model.getNumReactions()):
@@ -1,8 +0,0 @@
1
- {
2
- "tag": "2.1.3",
3
- "distance": 0,
4
- "node": "g45ef1dd293ac66f01a48a35895aa0b83ec9a6b96",
5
- "dirty": false,
6
- "branch": "HEAD",
7
- "node_date": "2026-10-13"
8
- }
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes