mxlpy 0.22.0__py3-none-any.whl → 0.24.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/mc.py CHANGED
@@ -24,9 +24,11 @@ import pandas as pd
24
24
  from mxlpy import mca, scan
25
25
  from mxlpy.parallel import Cache, parallelise
26
26
  from mxlpy.scan import (
27
+ ProtocolTimeCourseWorker,
27
28
  ProtocolWorker,
28
29
  SteadyStateWorker,
29
30
  TimeCourseWorker,
31
+ _protocol_time_course_worker,
30
32
  _protocol_worker,
31
33
  _steady_state_worker,
32
34
  _time_course_worker,
@@ -35,10 +37,10 @@ from mxlpy.scan import (
35
37
  from mxlpy.types import (
36
38
  IntegratorType,
37
39
  McSteadyStates,
38
- ProtocolByPars,
40
+ ProtocolScan,
39
41
  ResponseCoefficientsByPars,
40
- SteadyStates,
41
- TimeCourseByPars,
42
+ SteadyStateScan,
43
+ TimeCourseScan,
42
44
  )
43
45
 
44
46
  if TYPE_CHECKING:
@@ -49,11 +51,12 @@ if TYPE_CHECKING:
49
51
  __all__ = [
50
52
  "ParameterScanWorker",
51
53
  "parameter_elasticities",
54
+ "protocol",
55
+ "protocol_time_course",
52
56
  "response_coefficients",
53
57
  "scan_steady_state",
54
58
  "steady_state",
55
59
  "time_course",
56
- "time_course_over_protocol",
57
60
  "variable_elasticities",
58
61
  ]
59
62
 
@@ -69,7 +72,7 @@ class ParameterScanWorker(Protocol):
69
72
  y0: dict[str, float] | None,
70
73
  rel_norm: bool,
71
74
  integrator: IntegratorType,
72
- ) -> SteadyStates:
75
+ ) -> SteadyStateScan:
73
76
  """Call the worker function."""
74
77
  ...
75
78
 
@@ -81,7 +84,7 @@ def _parameter_scan_worker(
81
84
  y0: dict[str, float] | None,
82
85
  rel_norm: bool,
83
86
  integrator: IntegratorType,
84
- ) -> SteadyStates:
87
+ ) -> SteadyStateScan:
85
88
  """Worker function for parallel steady state scanning across parameter sets.
86
89
 
87
90
  This function executes a parameter scan for steady state solutions for a
@@ -125,7 +128,7 @@ def steady_state(
125
128
  rel_norm: bool = False,
126
129
  worker: SteadyStateWorker = _steady_state_worker,
127
130
  integrator: IntegratorType | None = None,
128
- ) -> SteadyStates:
131
+ ) -> SteadyStateScan:
129
132
  """Monte-carlo scan of steady states.
130
133
 
131
134
  Examples:
@@ -163,10 +166,14 @@ def steady_state(
163
166
  max_workers=max_workers,
164
167
  cache=cache,
165
168
  )
166
- return SteadyStates(
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,
169
- parameters=mc_to_scan,
169
+ return SteadyStateScan(
170
+ raw_index=(
171
+ pd.Index(mc_to_scan.iloc[:, 0])
172
+ if mc_to_scan.shape[1] == 1
173
+ else pd.MultiIndex.from_frame(mc_to_scan)
174
+ ),
175
+ raw_results=[i[1] for i in res],
176
+ to_scan=mc_to_scan,
170
177
  )
171
178
 
172
179
 
@@ -180,7 +187,7 @@ def time_course(
180
187
  cache: Cache | None = None,
181
188
  worker: TimeCourseWorker = _time_course_worker,
182
189
  integrator: IntegratorType | None = None,
183
- ) -> TimeCourseByPars:
190
+ ) -> TimeCourseScan:
184
191
  """MC time course.
185
192
 
186
193
  Examples:
@@ -219,14 +226,13 @@ def time_course(
219
226
  cache=cache,
220
227
  )
221
228
 
222
- return TimeCourseByPars(
223
- parameters=mc_to_scan,
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,
229
+ return TimeCourseScan(
230
+ to_scan=mc_to_scan,
231
+ raw_results=dict(res),
226
232
  )
227
233
 
228
234
 
229
- def time_course_over_protocol(
235
+ def protocol(
230
236
  model: Model,
231
237
  *,
232
238
  protocol: pd.DataFrame,
@@ -237,7 +243,7 @@ def time_course_over_protocol(
237
243
  cache: Cache | None = None,
238
244
  worker: ProtocolWorker = _protocol_worker,
239
245
  integrator: IntegratorType | None = None,
240
- ) -> ProtocolByPars:
246
+ ) -> ProtocolScan:
241
247
  """MC time course.
242
248
 
243
249
  Examples:
@@ -277,13 +283,68 @@ def time_course_over_protocol(
277
283
  max_workers=max_workers,
278
284
  cache=cache,
279
285
  )
280
- concs = {k: v.variables.T for k, v in res}
281
- fluxes = {k: v.fluxes.T for k, v in res}
282
- return ProtocolByPars(
283
- variables=pd.concat(concs, axis=1).T,
284
- fluxes=pd.concat(fluxes, axis=1).T,
285
- parameters=mc_to_scan,
286
+ return ProtocolScan(
287
+ to_scan=mc_to_scan,
288
+ protocol=protocol,
289
+ raw_results=dict(res),
290
+ )
291
+
292
+
293
+ def protocol_time_course(
294
+ model: Model,
295
+ *,
296
+ protocol: pd.DataFrame,
297
+ time_points: Array,
298
+ mc_to_scan: pd.DataFrame,
299
+ y0: dict[str, float] | None = None,
300
+ max_workers: int | None = None,
301
+ cache: Cache | None = None,
302
+ worker: ProtocolTimeCourseWorker = _protocol_time_course_worker,
303
+ integrator: IntegratorType | None = None,
304
+ ) -> ProtocolScan:
305
+ """MC time course.
306
+
307
+ Examples:
308
+ >>> protocol_time_course(model, protocol, time_points, mc_to_scan)
309
+ p t x y
310
+ 0 0.0 0.1 0.00
311
+ 1.0 0.2 0.01
312
+ 2.0 0.3 0.02
313
+ 3.0 0.4 0.03
314
+ ... ... ...
315
+ 1 0.0 0.1 0.00
316
+ 1.0 0.2 0.01
317
+ 2.0 0.3 0.02
318
+ 3.0 0.4 0.03
319
+
320
+ Returns:
321
+ tuple[concentrations, fluxes] using pandas multiindex
322
+ Both dataframes are of shape (#time_points * #mc_to_scan, #variables)
323
+
324
+ """
325
+ if y0 is not None:
326
+ model.update_variables(y0)
327
+
328
+ res = parallelise(
329
+ partial(
330
+ _update_parameters_and_initial_conditions,
331
+ fn=partial(
332
+ worker,
333
+ protocol=protocol,
334
+ time_points=time_points,
335
+ integrator=integrator,
336
+ y0=None,
337
+ ),
338
+ model=model,
339
+ ),
340
+ inputs=list(mc_to_scan.iterrows()),
341
+ max_workers=max_workers,
342
+ cache=cache,
343
+ )
344
+ return ProtocolScan(
345
+ to_scan=mc_to_scan,
286
346
  protocol=protocol,
347
+ raw_results=dict(res),
287
348
  )
288
349
 
289
350
 
mxlpy/mca.py CHANGED
@@ -91,8 +91,12 @@ def _response_coefficient_worker(
91
91
  y0=None,
92
92
  )
93
93
 
94
- conc_resp = (upper.variables - lower.variables) / (2 * displacement * old)
95
- flux_resp = (upper.fluxes - lower.fluxes) / (2 * displacement * old)
94
+ conc_resp = (upper.variables.iloc[-1] - lower.variables.iloc[-1]) / (
95
+ 2 * displacement * old
96
+ )
97
+ flux_resp = (upper.fluxes.iloc[-1] - lower.fluxes.iloc[-1]) / (
98
+ 2 * displacement * old
99
+ )
96
100
  # Reset
97
101
  model.update_parameters({parameter: old})
98
102
  if normalized:
@@ -102,8 +106,8 @@ def _response_coefficient_worker(
102
106
  integrator=integrator,
103
107
  y0=None,
104
108
  )
105
- conc_resp *= old / norm.variables
106
- flux_resp *= old / norm.fluxes
109
+ conc_resp *= old / norm.variables.iloc[-1]
110
+ flux_resp *= old / norm.fluxes.iloc[-1]
107
111
  return conc_resp, flux_resp
108
112
 
109
113
 
mxlpy/meta/__init__.py CHANGED
@@ -3,13 +3,18 @@
3
3
  from __future__ import annotations
4
4
 
5
5
  from .codegen_latex import generate_latex_code, to_tex_export
6
- from .codegen_model import generate_model_code_py, generate_model_code_rs
6
+ from .codegen_model import (
7
+ generate_model_code_py,
8
+ generate_model_code_rs,
9
+ generate_model_code_ts,
10
+ )
7
11
  from .codegen_mxlpy import generate_mxlpy_code
8
12
 
9
13
  __all__ = [
10
14
  "generate_latex_code",
11
15
  "generate_model_code_py",
12
16
  "generate_model_code_rs",
17
+ "generate_model_code_ts",
13
18
  "generate_mxlpy_code",
14
19
  "to_tex_export",
15
20
  ]
@@ -6,6 +6,7 @@ from dataclasses import dataclass
6
6
  from typing import TYPE_CHECKING
7
7
 
8
8
  import sympy
9
+ from wadler_lindig import pformat
9
10
 
10
11
  from mxlpy.meta.sympy_tools import fn_to_sympy, list_of_symbols
11
12
  from mxlpy.types import Derived, RateFn
@@ -358,6 +359,10 @@ class TexReaction:
358
359
  fn: RateFn
359
360
  args: list[str]
360
361
 
362
+ def __repr__(self) -> str:
363
+ """Return default representation."""
364
+ return pformat(self)
365
+
361
366
 
362
367
  @dataclass
363
368
  class TexExport:
@@ -397,6 +402,10 @@ class TexExport:
397
402
  reactions: dict[str, TexReaction]
398
403
  diff_eqs: dict[str, Mapping[str, float | Derived]]
399
404
 
405
+ def __repr__(self) -> str:
406
+ """Return default representation."""
407
+ return pformat(self)
408
+
400
409
  @staticmethod
401
410
  def _diff_parameters(
402
411
  p1: dict[str, float],
@@ -9,6 +9,7 @@ from mxlpy.meta.sympy_tools import (
9
9
  fn_to_sympy,
10
10
  list_of_symbols,
11
11
  stoichiometries_to_sympy,
12
+ sympy_to_inline_js,
12
13
  sympy_to_inline_py,
13
14
  sympy_to_inline_rust,
14
15
  )
@@ -23,6 +24,7 @@ if TYPE_CHECKING:
23
24
  __all__ = [
24
25
  "generate_model_code_py",
25
26
  "generate_model_code_rs",
27
+ "generate_model_code_ts",
26
28
  ]
27
29
 
28
30
  _LOGGER = logging.getLogger(__name__)
@@ -37,6 +39,7 @@ def _generate_model_code(
37
39
  assignment_template: str,
38
40
  sympy_inline_fn: Callable[[sympy.Expr], str],
39
41
  return_template: str,
42
+ custom_fns: dict[str, sympy.Expr],
40
43
  imports: list[str] | None = None,
41
44
  end: str | None = None,
42
45
  free_parameters: list[str] | None = None,
@@ -70,11 +73,13 @@ def _generate_model_code(
70
73
 
71
74
  # Derived
72
75
  for name, derived in model.get_raw_derived().items():
73
- expr = fn_to_sympy(
74
- derived.fn,
75
- origin=name,
76
- model_args=list_of_symbols(derived.args),
77
- )
76
+ expr = custom_fns.get(name)
77
+ if expr is None:
78
+ expr = fn_to_sympy(
79
+ derived.fn,
80
+ origin=name,
81
+ model_args=list_of_symbols(derived.args),
82
+ )
78
83
  if expr is None:
79
84
  msg = f"Unable to parse fn for derived value '{name}'"
80
85
  raise ValueError(msg)
@@ -82,11 +87,16 @@ def _generate_model_code(
82
87
 
83
88
  # Reactions
84
89
  for name, rxn in model.get_raw_reactions().items():
85
- expr = fn_to_sympy(
86
- rxn.fn,
87
- origin=name,
88
- model_args=list_of_symbols(rxn.args),
89
- )
90
+ expr = custom_fns.get(name)
91
+ if expr is None:
92
+ try:
93
+ expr = fn_to_sympy(
94
+ rxn.fn,
95
+ origin=name,
96
+ model_args=list_of_symbols(rxn.args),
97
+ )
98
+ except KeyError:
99
+ _LOGGER.warning("Failed to parse %s", name)
90
100
  if expr is None:
91
101
  msg = f"Unable to parse fn for reaction value '{name}'"
92
102
  raise ValueError(msg)
@@ -110,7 +120,8 @@ def _generate_model_code(
110
120
  _LOGGER.warning(msg)
111
121
 
112
122
  # Return
113
- ret = ", ".join(f"d{i}dt" for i in diff_eqs) if len(diff_eqs) > 0 else "()"
123
+ ret_order = [i for i in variables if i in diff_eqs]
124
+ ret = ", ".join(f"d{i}dt" for i in ret_order) if len(diff_eqs) > 0 else "()"
114
125
  source.append(return_template.format(ret))
115
126
 
116
127
  if end is not None:
@@ -122,6 +133,7 @@ def _generate_model_code(
122
133
 
123
134
  def generate_model_code_py(
124
135
  model: Model,
136
+ custom_fns: dict[str, sympy.Expr] | None = None,
125
137
  free_parameters: list[str] | None = None,
126
138
  ) -> str:
127
139
  """Transform the model into a python function, inlining the function calls."""
@@ -136,21 +148,51 @@ def generate_model_code_py(
136
148
  return _generate_model_code(
137
149
  model,
138
150
  imports=[
151
+ "import math\n",
139
152
  "from collections.abc import Iterable\n",
140
153
  ],
141
154
  sized=False,
142
155
  model_fn=model_fn,
143
156
  variables_template=" {} = variables",
144
- assignment_template=" {k} = {v}",
157
+ assignment_template=" {k}: float = {v}",
145
158
  sympy_inline_fn=sympy_to_inline_py,
146
159
  return_template=" return {}",
147
160
  end=None,
148
161
  free_parameters=free_parameters,
162
+ custom_fns={} if custom_fns is None else custom_fns,
163
+ )
164
+
165
+
166
+ def generate_model_code_ts(
167
+ model: Model,
168
+ custom_fns: dict[str, sympy.Expr] | None = None,
169
+ free_parameters: list[str] | None = None,
170
+ ) -> str:
171
+ """Transform the model into a typescript function, inlining the function calls."""
172
+ if free_parameters is None:
173
+ model_fn = "function model(time: number, variables: number[]) {"
174
+ else:
175
+ args = ", ".join(f"{k}: number" for k in free_parameters)
176
+ model_fn = f"function model(time: number, variables: number[], {args}) {{"
177
+
178
+ return _generate_model_code(
179
+ model,
180
+ imports=[],
181
+ sized=False,
182
+ model_fn=model_fn,
183
+ variables_template=" let [{}] = variables;",
184
+ assignment_template=" let {k}: number = {v};",
185
+ sympy_inline_fn=sympy_to_inline_js,
186
+ return_template=" return [{}];",
187
+ end="};",
188
+ free_parameters=free_parameters,
189
+ custom_fns={} if custom_fns is None else custom_fns,
149
190
  )
150
191
 
151
192
 
152
193
  def generate_model_code_rs(
153
194
  model: Model,
195
+ custom_fns: dict[str, sympy.Expr] | None = None,
154
196
  free_parameters: list[str] | None = None,
155
197
  ) -> str:
156
198
  """Transform the model into a rust function, inlining the function calls."""
@@ -171,4 +213,5 @@ def generate_model_code_rs(
171
213
  return_template=" return [{}]",
172
214
  end="}",
173
215
  free_parameters=free_parameters,
216
+ custom_fns={} if custom_fns is None else custom_fns,
174
217
  )