mxlpy 0.20.0__py3-none-any.whl → 0.22.0__py3-none-any.whl

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.
mxlpy/label_map.py CHANGED
@@ -551,13 +551,13 @@ class LabelMapper:
551
551
 
552
552
  m = Model()
553
553
 
554
- m.add_parameters(self.model.parameters)
554
+ m.add_parameters(self.model.get_parameter_values())
555
555
 
556
- for name, dp in self.model.derived_parameters.items():
556
+ for name, dp in self.model.get_derived_parameters().items():
557
557
  m.add_derived(name, fn=dp.fn, args=dp.args)
558
558
 
559
559
  variables: dict[str, float | Derived] = {}
560
- for k, v in self.model.variables.items():
560
+ for k, v in self.model.get_initial_conditions().items():
561
561
  if (isos := isotopomers.get(k)) is None:
562
562
  variables[k] = v
563
563
  else:
@@ -585,14 +585,14 @@ class LabelMapper:
585
585
  args=label_names,
586
586
  )
587
587
 
588
- for name, dv in self.model.derived_variables.items():
588
+ for name, dv in self.model.get_derived_variables().items():
589
589
  m.add_derived(
590
590
  name,
591
591
  fn=dv.fn,
592
592
  args=[f"{i}__total" if i in isotopomers else i for i in dv.args],
593
593
  )
594
594
 
595
- for rxn_name, rxn in self.model.reactions.items():
595
+ for rxn_name, rxn in self.model.get_raw_reactions().items():
596
596
  if (label_map := self.label_maps.get(rxn_name)) is None:
597
597
  m.add_reaction(
598
598
  rxn_name,
mxlpy/linear_label_map.py CHANGED
@@ -272,8 +272,10 @@ class LinearLabelMapper:
272
272
  m = Model()
273
273
  m.add_variables(variables)
274
274
  m.add_parameters(concs.to_dict() | fluxes.to_dict() | {"EXT": external_label})
275
+
276
+ rxns = self.model.get_raw_reactions()
275
277
  for rxn_name, label_map in self.label_maps.items():
276
- rxn = self.model.reactions[rxn_name]
278
+ rxn = rxns[rxn_name]
277
279
  subs, prods = _unpack_stoichiometries(rxn.stoichiometry)
278
280
 
279
281
  subs = _stoichiometry_to_duplicate_list(subs)
mxlpy/mc.py CHANGED
@@ -22,7 +22,6 @@ from typing import TYPE_CHECKING, Protocol, cast
22
22
  import pandas as pd
23
23
 
24
24
  from mxlpy import mca, scan
25
- from mxlpy.integrators import DefaultIntegrator
26
25
  from mxlpy.parallel import Cache, parallelise
27
26
  from mxlpy.scan import (
28
27
  ProtocolWorker,
@@ -67,6 +66,7 @@ class ParameterScanWorker(Protocol):
67
66
  model: Model,
68
67
  *,
69
68
  parameters: pd.DataFrame,
69
+ y0: dict[str, float] | None,
70
70
  rel_norm: bool,
71
71
  integrator: IntegratorType,
72
72
  ) -> SteadyStates:
@@ -78,6 +78,7 @@ def _parameter_scan_worker(
78
78
  model: Model,
79
79
  *,
80
80
  parameters: pd.DataFrame,
81
+ y0: dict[str, float] | None,
81
82
  rel_norm: bool,
82
83
  integrator: IntegratorType,
83
84
  ) -> SteadyStates:
@@ -110,6 +111,7 @@ def _parameter_scan_worker(
110
111
  parallel=False,
111
112
  rel_norm=rel_norm,
112
113
  integrator=integrator,
114
+ y0=y0,
113
115
  )
114
116
 
115
117
 
@@ -122,7 +124,7 @@ def steady_state(
122
124
  cache: Cache | None = None,
123
125
  rel_norm: bool = False,
124
126
  worker: SteadyStateWorker = _steady_state_worker,
125
- integrator: IntegratorType = DefaultIntegrator,
127
+ integrator: IntegratorType | None = None,
126
128
  ) -> SteadyStates:
127
129
  """Monte-carlo scan of steady states.
128
130
 
@@ -153,6 +155,7 @@ def steady_state(
153
155
  worker,
154
156
  rel_norm=rel_norm,
155
157
  integrator=integrator,
158
+ y0=None,
156
159
  ),
157
160
  model=model,
158
161
  ),
@@ -161,8 +164,8 @@ def steady_state(
161
164
  cache=cache,
162
165
  )
163
166
  return SteadyStates(
164
- variables=pd.concat({k: v.variables for k, v in res.items()}, axis=1).T,
165
- fluxes=pd.concat({k: v.fluxes for k, v in res.items()}, axis=1).T,
167
+ variables=pd.concat({k: v.variables for k, v in res}, axis=1).T,
168
+ fluxes=pd.concat({k: v.fluxes for k, v in res}, axis=1).T,
166
169
  parameters=mc_to_scan,
167
170
  )
168
171
 
@@ -176,7 +179,7 @@ def time_course(
176
179
  max_workers: int | None = None,
177
180
  cache: Cache | None = None,
178
181
  worker: TimeCourseWorker = _time_course_worker,
179
- integrator: IntegratorType = DefaultIntegrator,
182
+ integrator: IntegratorType | None = None,
180
183
  ) -> TimeCourseByPars:
181
184
  """MC time course.
182
185
 
@@ -207,6 +210,7 @@ def time_course(
207
210
  worker,
208
211
  time_points=time_points,
209
212
  integrator=integrator,
213
+ y0=None,
210
214
  ),
211
215
  model=model,
212
216
  ),
@@ -217,8 +221,8 @@ def time_course(
217
221
 
218
222
  return TimeCourseByPars(
219
223
  parameters=mc_to_scan,
220
- variables=pd.concat({k: v.variables.T for k, v in res.items()}, axis=1).T,
221
- fluxes=pd.concat({k: v.fluxes.T for k, v in res.items()}, axis=1).T,
224
+ variables=pd.concat({k: v.variables.T for k, v in res}, axis=1).T,
225
+ fluxes=pd.concat({k: v.fluxes.T for k, v in res}, axis=1).T,
222
226
  )
223
227
 
224
228
 
@@ -232,7 +236,7 @@ def time_course_over_protocol(
232
236
  max_workers: int | None = None,
233
237
  cache: Cache | None = None,
234
238
  worker: ProtocolWorker = _protocol_worker,
235
- integrator: IntegratorType = DefaultIntegrator,
239
+ integrator: IntegratorType | None = None,
236
240
  ) -> ProtocolByPars:
237
241
  """MC time course.
238
242
 
@@ -264,6 +268,7 @@ def time_course_over_protocol(
264
268
  worker,
265
269
  protocol=protocol,
266
270
  integrator=integrator,
271
+ y0=None,
267
272
  time_points_per_step=time_points_per_step,
268
273
  ),
269
274
  model=model,
@@ -272,8 +277,8 @@ def time_course_over_protocol(
272
277
  max_workers=max_workers,
273
278
  cache=cache,
274
279
  )
275
- concs = {k: v.variables.T for k, v in res.items()}
276
- fluxes = {k: v.fluxes.T for k, v in res.items()}
280
+ concs = {k: v.variables.T for k, v in res}
281
+ fluxes = {k: v.fluxes.T for k, v in res}
277
282
  return ProtocolByPars(
278
283
  variables=pd.concat(concs, axis=1).T,
279
284
  fluxes=pd.concat(fluxes, axis=1).T,
@@ -292,7 +297,7 @@ def scan_steady_state(
292
297
  cache: Cache | None = None,
293
298
  rel_norm: bool = False,
294
299
  worker: ParameterScanWorker = _parameter_scan_worker,
295
- integrator: IntegratorType = DefaultIntegrator,
300
+ integrator: IntegratorType | None = None,
296
301
  ) -> McSteadyStates:
297
302
  """Parameter scan of mc distributed steady states.
298
303
 
@@ -339,6 +344,7 @@ def scan_steady_state(
339
344
  parameters=to_scan,
340
345
  rel_norm=rel_norm,
341
346
  integrator=integrator,
347
+ y0=None,
342
348
  ),
343
349
  model=model,
344
350
  ),
@@ -346,8 +352,8 @@ def scan_steady_state(
346
352
  cache=cache,
347
353
  max_workers=max_workers,
348
354
  )
349
- concs = {k: v.variables.T for k, v in res.items()}
350
- fluxes = {k: v.fluxes.T for k, v in res.items()}
355
+ concs = {k: v.variables.T for k, v in res}
356
+ fluxes = {k: v.fluxes.T for k, v in res}
351
357
  return McSteadyStates(
352
358
  variables=pd.concat(concs, axis=1).T,
353
359
  fluxes=pd.concat(fluxes, axis=1).T,
@@ -422,7 +428,7 @@ def variable_elasticities(
422
428
  cache=cache,
423
429
  max_workers=max_workers,
424
430
  )
425
- return cast(pd.DataFrame, pd.concat(res))
431
+ return cast(pd.DataFrame, pd.concat(dict(res)))
426
432
 
427
433
 
428
434
  def parameter_elasticities(
@@ -486,7 +492,7 @@ def parameter_elasticities(
486
492
  cache=cache,
487
493
  max_workers=max_workers,
488
494
  )
489
- return cast(pd.DataFrame, pd.concat(res))
495
+ return cast(pd.DataFrame, pd.concat(dict(res)))
490
496
 
491
497
 
492
498
  def response_coefficients(
@@ -501,7 +507,7 @@ def response_coefficients(
501
507
  disable_tqdm: bool = False,
502
508
  max_workers: int | None = None,
503
509
  rel_norm: bool = False,
504
- integrator: IntegratorType = DefaultIntegrator,
510
+ integrator: IntegratorType | None = None,
505
511
  ) -> ResponseCoefficientsByPars:
506
512
  """Calculate response coefficients using Monte Carlo analysis.
507
513
 
@@ -558,9 +564,7 @@ def response_coefficients(
558
564
  )
559
565
 
560
566
  return ResponseCoefficientsByPars(
561
- variables=cast(
562
- pd.DataFrame, pd.concat({k: v.variables for k, v in res.items()})
563
- ),
564
- fluxes=cast(pd.DataFrame, pd.concat({k: v.fluxes for k, v in res.items()})),
567
+ variables=cast(pd.DataFrame, pd.concat({k: v.variables for k, v in res})),
568
+ fluxes=cast(pd.DataFrame, pd.concat({k: v.fluxes for k, v in res})),
565
569
  parameters=mc_to_scan,
566
570
  )
mxlpy/mca.py CHANGED
@@ -22,7 +22,6 @@ from typing import TYPE_CHECKING
22
22
 
23
23
  import pandas as pd
24
24
 
25
- from mxlpy.integrators import DefaultIntegrator
26
25
  from mxlpy.parallel import parallelise
27
26
  from mxlpy.scan import _steady_state_worker
28
27
  from mxlpy.types import ResponseCoefficients
@@ -46,7 +45,7 @@ def _response_coefficient_worker(
46
45
  normalized: bool,
47
46
  rel_norm: bool,
48
47
  displacement: float = 1e-4,
49
- integrator: IntegratorType,
48
+ integrator: IntegratorType | None,
50
49
  ) -> tuple[pd.Series, pd.Series]:
51
50
  """Calculate response coefficients for a single parameter.
52
51
 
@@ -72,7 +71,7 @@ def _response_coefficient_worker(
72
71
  - Series of flux response coefficients
73
72
 
74
73
  """
75
- old = model.parameters[parameter]
74
+ old = model.get_parameter_values()[parameter]
76
75
  if y0 is not None:
77
76
  model.update_variables(y0)
78
77
 
@@ -81,6 +80,7 @@ def _response_coefficient_worker(
81
80
  model,
82
81
  rel_norm=rel_norm,
83
82
  integrator=integrator,
83
+ y0=None,
84
84
  )
85
85
 
86
86
  model.update_parameters({parameter: old * (1 - displacement)})
@@ -88,6 +88,7 @@ def _response_coefficient_worker(
88
88
  model,
89
89
  rel_norm=rel_norm,
90
90
  integrator=integrator,
91
+ y0=None,
91
92
  )
92
93
 
93
94
  conc_resp = (upper.variables - lower.variables) / (2 * displacement * old)
@@ -99,6 +100,7 @@ def _response_coefficient_worker(
99
100
  model,
100
101
  rel_norm=rel_norm,
101
102
  integrator=integrator,
103
+ y0=None,
102
104
  )
103
105
  conc_resp *= old / norm.variables
104
106
  flux_resp *= old / norm.fluxes
@@ -203,7 +205,7 @@ def parameter_elasticities(
203
205
 
204
206
  variables = model.get_initial_conditions() if variables is None else variables
205
207
  for par in to_scan:
206
- old = model.parameters[par]
208
+ old = model.get_parameter_values()[par]
207
209
 
208
210
  model.update_parameters({par: old * (1 + displacement)})
209
211
  upper = model.get_fluxes(variables=variables, time=time)
@@ -237,7 +239,7 @@ def response_coefficients(
237
239
  parallel: bool = True,
238
240
  max_workers: int | None = None,
239
241
  rel_norm: bool = False,
240
- integrator: IntegratorType = DefaultIntegrator,
242
+ integrator: IntegratorType | None = None,
241
243
  ) -> ResponseCoefficients:
242
244
  """Calculate response coefficients.
243
245
 
@@ -284,6 +286,6 @@ def response_coefficients(
284
286
  max_workers=max_workers,
285
287
  )
286
288
  return ResponseCoefficients(
287
- variables=pd.DataFrame({k: v[0] for k, v in res.items()}),
288
- fluxes=pd.DataFrame({k: v[1] for k, v in res.items()}),
289
+ variables=pd.DataFrame({k: v[0] for k, v in res}),
290
+ fluxes=pd.DataFrame({k: v[1] for k, v in res}),
289
291
  )
mxlpy/meta/__init__.py CHANGED
@@ -2,12 +2,14 @@
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
- from .codegen_latex import generate_latex_code
6
- from .codegen_modebase import generate_mxlpy_code
7
- from .codegen_py import generate_model_code_py
5
+ from .codegen_latex import generate_latex_code, to_tex_export
6
+ from .codegen_model import generate_model_code_py, generate_model_code_rs
7
+ from .codegen_mxlpy import generate_mxlpy_code
8
8
 
9
9
  __all__ = [
10
10
  "generate_latex_code",
11
11
  "generate_model_code_py",
12
+ "generate_model_code_rs",
12
13
  "generate_mxlpy_code",
14
+ "to_tex_export",
13
15
  ]
@@ -7,7 +7,7 @@ from typing import TYPE_CHECKING
7
7
 
8
8
  import sympy
9
9
 
10
- from mxlpy.meta.source_tools import fn_to_sympy
10
+ from mxlpy.meta.sympy_tools import fn_to_sympy, list_of_symbols
11
11
  from mxlpy.types import Derived, RateFn
12
12
 
13
13
  if TYPE_CHECKING:
@@ -21,6 +21,7 @@ __all__ = [
21
21
  "default_init",
22
22
  "generate_latex_code",
23
23
  "get_model_tex_diff",
24
+ "to_tex_export",
24
25
  ]
25
26
 
26
27
  cdot = r"\cdot"
@@ -31,10 +32,6 @@ newline = r"\\" + "\n"
31
32
  floatbarrier = r"\FloatBarrier"
32
33
 
33
34
 
34
- def _list_of_symbols(args: list[str]) -> list[sympy.Symbol | sympy.Expr]:
35
- return [sympy.Symbol(arg) for arg in args]
36
-
37
-
38
35
  def default_init[T1, T2](d: dict[T1, T2] | None) -> dict[T1, T2]:
39
36
  """Return empty dict if d is None.
40
37
 
@@ -63,10 +60,6 @@ def _gls(s: str) -> str:
63
60
  return rf"\gls{{{s}}}"
64
61
 
65
62
 
66
- def _abbrev_and_full(s: str) -> str:
67
- return rf"\acrfull{{{s}}}"
68
-
69
-
70
63
  def _gls_short(s: str) -> str:
71
64
  return rf"\acrshort{{{s}}}"
72
65
 
@@ -75,6 +68,10 @@ def _gls_full(s: str) -> str:
75
68
  return rf"\acrlong{{{s}}}"
76
69
 
77
70
 
71
+ def _gls_short_and_full(s: str) -> str:
72
+ return rf"\acrfull{{{s}}}"
73
+
74
+
78
75
  def _rename_latex(s: str) -> str:
79
76
  if s[0].isdigit():
80
77
  s = s[1:]
@@ -109,6 +106,8 @@ def _sympy_to_latex(expr: sympy.Expr) -> str:
109
106
 
110
107
  def _fn_to_latex(
111
108
  fn: Callable,
109
+ *,
110
+ origin: str,
112
111
  arg_names: list[str],
113
112
  long_name_cutoff: int,
114
113
  ) -> tuple[str, dict[str, str]]:
@@ -121,10 +120,13 @@ def _fn_to_latex(
121
120
  replacements = {k: _name_to_latex(f"_x{i}") for i, k in enumerate(long_names)}
122
121
 
123
122
  expr = fn_to_sympy(
124
- fn, _list_of_symbols([replacements.get(k, k) for k in tex_names])
123
+ fn,
124
+ origin=origin,
125
+ model_args=list_of_symbols([replacements.get(k, k) for k in tex_names]),
125
126
  )
126
- fn_str = _sympy_to_latex(expr)
127
- return fn_str, replacements
127
+ if expr is None:
128
+ return rf"\textcolor{{red}}{{{origin}}}", replacements
129
+ return _sympy_to_latex(expr), replacements
128
130
 
129
131
 
130
132
  def _table(
@@ -323,7 +325,8 @@ def _stoichs_to_latex(
323
325
  )
324
326
  sympy_fn = fn_to_sympy(
325
327
  rxn_stoich.fn,
326
- _list_of_symbols([replacements.get(k, k) for k in arg_names]),
328
+ origin=rxn_name,
329
+ model_args=list_of_symbols([replacements.get(k, k) for k in arg_names]),
327
330
  )
328
331
  expr = expr + sympy_fn * sympy.Symbol(rxn_name) # type: ignore
329
332
  else:
@@ -480,7 +483,7 @@ class TexExport:
480
483
 
481
484
  def _add_gls_if_found(k: str) -> str:
482
485
  if (new := gls.get(k)) is not None:
483
- return _abbrev_and_full(new)
486
+ return _gls_short_and_full(new)
484
487
  return k
485
488
 
486
489
  return TexExport(
@@ -564,7 +567,7 @@ class TexExport:
564
567
 
565
568
  def export_derived(
566
569
  self,
567
- long_name_cutoff: int,
570
+ long_name_cutoff: int = 10,
568
571
  ) -> str:
569
572
  """Export derived quantities as LaTeX equations.
570
573
 
@@ -587,16 +590,20 @@ class TexExport:
587
590
  for k, v in sorted(self.derived.items()):
588
591
  fn_str, repls = _fn_to_latex(
589
592
  v.fn,
593
+ origin=k,
590
594
  arg_names=v.args,
591
595
  long_name_cutoff=long_name_cutoff,
592
596
  )
593
- rows.append(f"{_mathrm(_name_to_latex(k))} &= {fn_str} \\\\")
597
+ rows.append(f" {_mathrm(_name_to_latex(k))} &= {fn_str} \\\\")
594
598
  if repls:
595
599
  rows.append(_replacements_in_align(repls))
596
600
 
597
601
  return _latex_align(rows)
598
602
 
599
- def export_reactions(self, long_name_cutoff: int) -> str:
603
+ def export_reactions(
604
+ self,
605
+ long_name_cutoff: int = 10,
606
+ ) -> str:
600
607
  """Export reactions as LaTeX equations.
601
608
 
602
609
  Returns
@@ -618,17 +625,18 @@ class TexExport:
618
625
  for k, v in sorted(self.reactions.items()):
619
626
  fn_str, repls = _fn_to_latex(
620
627
  v.fn,
628
+ origin=k,
621
629
  arg_names=v.args,
622
630
  long_name_cutoff=long_name_cutoff,
623
631
  )
624
- rows.append(f"{_mathrm(_name_to_latex(k))} &= {fn_str} \\\\")
632
+ rows.append(f" {_mathrm(_name_to_latex(k))} &= {fn_str} \\\\")
625
633
  if repls:
626
634
  rows.append(_replacements_in_align(repls))
627
635
  return _latex_align(rows)
628
636
 
629
637
  def export_diff_eqs(
630
638
  self,
631
- long_name_cutoff: int,
639
+ long_name_cutoff: int = 10,
632
640
  ) -> str:
633
641
  """Export stoichiometries as LaTeX table.
634
642
 
@@ -654,12 +662,15 @@ class TexExport:
654
662
  long_name_cutoff=long_name_cutoff,
655
663
  )
656
664
 
657
- rows.append(f"{dxdt} &= {stoich_str} \\\\")
665
+ rows.append(f" {dxdt} &= {stoich_str} \\\\")
658
666
  if repls:
659
667
  rows.append(_replacements_in_align(repls))
660
668
  return _latex_align(rows)
661
669
 
662
- def export_all(self, long_name_cutoff: int = 10) -> str:
670
+ def export_all(
671
+ self,
672
+ long_name_cutoff: int = 10,
673
+ ) -> str:
663
674
  """Export all model parts as a complete LaTeX document section.
664
675
 
665
676
  Returns
@@ -754,7 +765,7 @@ class TexExport:
754
765
  \usepackage[a4paper,top=2cm,bottom=2cm,left=2cm,right=2cm,marginparwidth=1.75cm]{{geometry}}
755
766
  \usepackage{{amsmath, amssymb, array, booktabs,
756
767
  breqn, caption, longtable, mathtools, placeins,
757
- ragged2e, tabularx, titlesec, titling}}
768
+ ragged2e, tabularx, titlesec, titling, xcolor}}
758
769
  \newcommand{{\sectionbreak}}{{\clearpage}}
759
770
  \setlength{{\parindent}}{{0pt}}
760
771
  \allowdisplaybreaks
@@ -769,17 +780,20 @@ class TexExport:
769
780
  """
770
781
 
771
782
 
772
- def _to_tex_export(self: Model) -> TexExport:
783
+ def to_tex_export(model: Model) -> TexExport:
784
+ """Create TexExport object from a model."""
773
785
  diff_eqs = {}
774
- for rxn_name, rxn in self.reactions.items():
786
+ for rxn_name, rxn in model.get_raw_reactions().items():
775
787
  for var_name, factor in rxn.stoichiometry.items():
776
788
  diff_eqs.setdefault(var_name, {})[rxn_name] = factor
777
789
 
778
790
  return TexExport(
779
- parameters=self.parameters,
780
- variables=self.get_initial_conditions(), # FIXME: think about this later
781
- derived=self.derived,
782
- reactions={k: TexReaction(v.fn, v.args) for k, v in self.reactions.items()},
791
+ parameters=model.get_parameter_values(),
792
+ variables=model.get_initial_conditions(), # FIXME: think about this later
793
+ derived=model.get_raw_derived(),
794
+ reactions={
795
+ k: TexReaction(v.fn, v.args) for k, v in model.get_raw_reactions().items()
796
+ },
783
797
  diff_eqs=diff_eqs,
784
798
  )
785
799
 
@@ -820,7 +834,7 @@ def generate_latex_code(
820
834
  """
821
835
  gls = default_init(gls)
822
836
  return (
823
- _to_tex_export(model)
837
+ to_tex_export(model)
824
838
  .rename_with_glossary(gls)
825
839
  .export_document(long_name_cutoff=long_name_cutoff)
826
840
  )
@@ -866,7 +880,7 @@ def get_model_tex_diff(
866
880
  return f"""{" start autogenerated ":%^60}
867
881
  {_clearpage()}
868
882
  {_subsubsection("Model changes")}{_label(section_label)}
869
- {((_to_tex_export(m1) - _to_tex_export(m2)).rename_with_glossary(gls).export_all(long_name_cutoff=long_name_cutoff))}
883
+ {((to_tex_export(m1) - to_tex_export(m2)).rename_with_glossary(gls).export_all(long_name_cutoff=long_name_cutoff))}
870
884
  {_clearpage()}
871
885
  {" end autogenerated ":%^60}
872
886
  """