mxlpy 0.11.0__py3-none-any.whl → 0.13.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/fit.py CHANGED
@@ -22,7 +22,7 @@ from mxlpy.types import (
22
22
  Array,
23
23
  ArrayLike,
24
24
  Callable,
25
- IntegratorProtocol,
25
+ IntegratorType,
26
26
  cast,
27
27
  )
28
28
 
@@ -57,7 +57,7 @@ class SteadyStateResidualFn(Protocol):
57
57
  data: pd.Series,
58
58
  model: Model,
59
59
  y0: dict[str, float],
60
- integrator: Callable[[Callable, ArrayLike], IntegratorProtocol],
60
+ integrator: IntegratorType,
61
61
  ) -> float:
62
62
  """Calculate residual error between model steady state and experimental data."""
63
63
  ...
@@ -74,7 +74,7 @@ class TimeSeriesResidualFn(Protocol):
74
74
  data: pd.DataFrame,
75
75
  model: Model,
76
76
  y0: dict[str, float],
77
- integrator: Callable[[Callable, ArrayLike], IntegratorProtocol],
77
+ integrator: IntegratorType,
78
78
  ) -> float:
79
79
  """Calculate residual error between model time course and experimental data."""
80
80
  ...
@@ -108,7 +108,7 @@ def _steady_state_residual(
108
108
  data: pd.Series,
109
109
  model: Model,
110
110
  y0: dict[str, float] | None,
111
- integrator: Callable[[Callable, ArrayLike], IntegratorProtocol],
111
+ integrator: IntegratorType,
112
112
  ) -> float:
113
113
  """Calculate residual error between model steady state and experimental data.
114
114
 
@@ -155,7 +155,7 @@ def _time_course_residual(
155
155
  data: pd.DataFrame,
156
156
  model: Model,
157
157
  y0: dict[str, float] | None,
158
- integrator: Callable[[Callable, ArrayLike], IntegratorProtocol],
158
+ integrator: IntegratorType,
159
159
  ) -> float:
160
160
  """Calculate residual error between model time course and experimental data.
161
161
 
@@ -194,7 +194,7 @@ def steady_state(
194
194
  y0: dict[str, float] | None = None,
195
195
  minimize_fn: MinimizeFn = _default_minimize_fn,
196
196
  residual_fn: SteadyStateResidualFn = _steady_state_residual,
197
- integrator: Callable[[Callable, ArrayLike], IntegratorProtocol] = DefaultIntegrator,
197
+ integrator: IntegratorType = DefaultIntegrator,
198
198
  ) -> dict[str, float]:
199
199
  """Fit model parameters to steady-state experimental data.
200
200
 
@@ -248,7 +248,7 @@ def time_course(
248
248
  y0: dict[str, float] | None = None,
249
249
  minimize_fn: MinimizeFn = _default_minimize_fn,
250
250
  residual_fn: TimeSeriesResidualFn = _time_course_residual,
251
- integrator: Callable[[Callable, ArrayLike], IntegratorProtocol] = DefaultIntegrator,
251
+ integrator: IntegratorType = DefaultIntegrator,
252
252
  ) -> dict[str, float]:
253
253
  """Fit model parameters to time course of experimental data.
254
254
 
@@ -45,6 +45,7 @@ class Assimulo:
45
45
 
46
46
  rhs: Callable
47
47
  y0: ArrayLike
48
+ jacobian: Callable | None = None
48
49
  atol: float = 1e-8
49
50
  rtol: float = 1e-8
50
51
  maxnef: int = 4 # max error failures
@@ -61,7 +62,11 @@ class Assimulo:
61
62
  number of convergence failures (`self.maxncf`), and verbosity level (`self.verbosity`).
62
63
 
63
64
  """
64
- self.integrator = CVode(Explicit_Problem(self.rhs, self.y0))
65
+ problem = Explicit_Problem(self.rhs, self.y0)
66
+ if self.jacobian is not None:
67
+ problem.jac = self.jacobian
68
+
69
+ self.integrator = CVode(problem)
65
70
  self.integrator.atol = self.atol
66
71
  self.integrator.rtol = self.rtol
67
72
  self.integrator.maxnef = self.maxnef
@@ -42,6 +42,7 @@ class Scipy:
42
42
 
43
43
  rhs: Callable
44
44
  y0: ArrayLike
45
+ jacobian: Callable | None = None
45
46
  atol: float = 1e-8
46
47
  rtol: float = 1e-8
47
48
  t0: float = 0.0
@@ -82,7 +83,7 @@ class Scipy:
82
83
  steps = 100 if steps is None else steps + 1
83
84
 
84
85
  return self.integrate_time_course(
85
- time_points=np.linspace(self.t0, t_end, steps)
86
+ time_points=np.linspace(self.t0, t_end, steps, dtype=float)
86
87
  )
87
88
 
88
89
  def integrate_time_course(
@@ -97,17 +98,21 @@ class Scipy:
97
98
  tuple[ArrayLike, ArrayLike]: Tuple containing the time points and the integrated values.
98
99
 
99
100
  """
100
- y = spi.odeint(
101
- func=self.rhs,
101
+ res = spi.solve_ivp(
102
+ fun=self.rhs,
102
103
  y0=self.y0,
103
- t=time_points,
104
- tfirst=True,
104
+ t_span=(time_points[0], time_points[-1]),
105
+ t_eval=time_points,
106
+ jac=self.jacobian,
105
107
  atol=self.atol,
106
108
  rtol=self.rtol,
109
+ method="LSODA",
107
110
  )
108
- self.t0 = time_points[-1]
109
- self.y0 = y[-1, :]
110
- return np.array(time_points, dtype=float), y
111
+ if res.success:
112
+ self.t0 = time_points[-1]
113
+ self.y0 = res.y[:, -1]
114
+ return np.array(time_points, dtype=float), res.y.T
115
+ return None, None
111
116
 
112
117
  def integrate_to_steady_state(
113
118
  self,
@@ -131,7 +136,7 @@ class Scipy:
131
136
 
132
137
  """
133
138
  self.reset()
134
- integ = spi.ode(self.rhs)
139
+ integ = spi.ode(self.rhs, jac=self.jacobian)
135
140
  integ.set_integrator(name="lsoda")
136
141
  integ.set_initial_value(self.y0)
137
142
  t = self.t0 + step_size
mxlpy/mc.py CHANGED
@@ -34,8 +34,7 @@ from mxlpy.scan import (
34
34
  _update_parameters_and,
35
35
  )
36
36
  from mxlpy.types import (
37
- ArrayLike,
38
- IntegratorProtocol,
37
+ IntegratorType,
39
38
  McSteadyStates,
40
39
  ProtocolByPars,
41
40
  ResponseCoefficientsByPars,
@@ -55,8 +54,6 @@ __all__ = [
55
54
  ]
56
55
 
57
56
  if TYPE_CHECKING:
58
- from collections.abc import Callable
59
-
60
57
  from mxlpy.model import Model
61
58
  from mxlpy.types import Array
62
59
 
@@ -81,7 +78,7 @@ class ParameterScanWorker(Protocol):
81
78
  *,
82
79
  parameters: pd.DataFrame,
83
80
  rel_norm: bool,
84
- integrator: Callable[[Callable, ArrayLike], IntegratorProtocol],
81
+ integrator: IntegratorType,
85
82
  ) -> SteadyStates:
86
83
  """Call the worker function."""
87
84
  ...
@@ -93,7 +90,7 @@ def _parameter_scan_worker(
93
90
  *,
94
91
  parameters: pd.DataFrame,
95
92
  rel_norm: bool,
96
- integrator: Callable[[Callable, ArrayLike], IntegratorProtocol],
93
+ integrator: IntegratorType,
97
94
  ) -> SteadyStates:
98
95
  """Worker function for parallel steady state scanning across parameter sets.
99
96
 
@@ -137,7 +134,7 @@ def steady_state(
137
134
  cache: Cache | None = None,
138
135
  rel_norm: bool = False,
139
136
  worker: SteadyStateWorker = _steady_state_worker,
140
- integrator: Callable[[Callable, ArrayLike], IntegratorProtocol] = DefaultIntegrator,
137
+ integrator: IntegratorType = DefaultIntegrator,
141
138
  ) -> SteadyStates:
142
139
  """Monte-carlo scan of steady states.
143
140
 
@@ -188,7 +185,7 @@ def time_course(
188
185
  max_workers: int | None = None,
189
186
  cache: Cache | None = None,
190
187
  worker: TimeCourseWorker = _time_course_worker,
191
- integrator: Callable[[Callable, ArrayLike], IntegratorProtocol] = DefaultIntegrator,
188
+ integrator: IntegratorType = DefaultIntegrator,
192
189
  ) -> TimeCourseByPars:
193
190
  """MC time course.
194
191
 
@@ -241,7 +238,7 @@ def time_course_over_protocol(
241
238
  max_workers: int | None = None,
242
239
  cache: Cache | None = None,
243
240
  worker: ProtocolWorker = _protocol_worker,
244
- integrator: Callable[[Callable, ArrayLike], IntegratorProtocol] = DefaultIntegrator,
241
+ integrator: IntegratorType = DefaultIntegrator,
245
242
  ) -> ProtocolByPars:
246
243
  """MC time course.
247
244
 
@@ -299,7 +296,7 @@ def scan_steady_state(
299
296
  cache: Cache | None = None,
300
297
  rel_norm: bool = False,
301
298
  worker: ParameterScanWorker = _parameter_scan_worker,
302
- integrator: Callable[[Callable, ArrayLike], IntegratorProtocol] = DefaultIntegrator,
299
+ integrator: IntegratorType = DefaultIntegrator,
303
300
  ) -> McSteadyStates:
304
301
  """Parameter scan of mc distributed steady states.
305
302
 
@@ -501,7 +498,7 @@ def response_coefficients(
501
498
  disable_tqdm: bool = False,
502
499
  max_workers: int | None = None,
503
500
  rel_norm: bool = False,
504
- integrator: Callable[[Callable, ArrayLike], IntegratorProtocol] = DefaultIntegrator,
501
+ integrator: IntegratorType = DefaultIntegrator,
505
502
  ) -> ResponseCoefficientsByPars:
506
503
  """Calculate response coefficients using Monte Carlo analysis.
507
504
 
mxlpy/mca.py CHANGED
@@ -25,7 +25,7 @@ import pandas as pd
25
25
  from mxlpy.integrators import DefaultIntegrator
26
26
  from mxlpy.parallel import parallelise
27
27
  from mxlpy.scan import _steady_state_worker
28
- from mxlpy.types import ArrayLike, ResponseCoefficients
28
+ from mxlpy.types import ResponseCoefficients
29
29
 
30
30
  __all__ = [
31
31
  "parameter_elasticities",
@@ -34,10 +34,8 @@ __all__ = [
34
34
  ]
35
35
 
36
36
  if TYPE_CHECKING:
37
- from collections.abc import Callable
38
-
39
- from mxlpy import IntegratorProtocol
40
37
  from mxlpy.model import Model
38
+ from mxlpy.types import IntegratorType
41
39
 
42
40
 
43
41
  ###############################################################################
@@ -169,7 +167,7 @@ def _response_coefficient_worker(
169
167
  normalized: bool,
170
168
  rel_norm: bool,
171
169
  displacement: float = 1e-4,
172
- integrator: Callable[[Callable, ArrayLike], IntegratorProtocol],
170
+ integrator: IntegratorType,
173
171
  ) -> tuple[pd.Series, pd.Series]:
174
172
  """Calculate response coefficients for a single parameter.
175
173
 
@@ -240,7 +238,7 @@ def response_coefficients(
240
238
  parallel: bool = True,
241
239
  max_workers: int | None = None,
242
240
  rel_norm: bool = False,
243
- integrator: Callable[[Callable, ArrayLike], IntegratorProtocol] = DefaultIntegrator,
241
+ integrator: IntegratorType = DefaultIntegrator,
244
242
  ) -> ResponseCoefficients:
245
243
  """Calculate response coefficients.
246
244
 
mxlpy/plot.py CHANGED
@@ -52,7 +52,7 @@ __all__ = [
52
52
 
53
53
  import itertools as it
54
54
  import math
55
- from typing import TYPE_CHECKING, Literal, cast
55
+ from typing import TYPE_CHECKING, Any, Literal, cast
56
56
 
57
57
  import numpy as np
58
58
  import pandas as pd
@@ -292,15 +292,19 @@ def reset_prop_cycle(ax: Axes) -> None:
292
292
  def context(
293
293
  colors: list[str] | None = None,
294
294
  line_width: float | None = None,
295
+ line_style: Linestyle | None = None,
296
+ rc: dict[str, Any] | None = None,
295
297
  ) -> Generator[None, None, None]:
296
298
  """Context manager to set the defaults for plots.
297
299
 
298
300
  Args:
299
301
  colors: colors to use for the plot.
300
302
  line_width: line width to use for the plot.
303
+ line_style: line style to use for the plot.
304
+ rc: additional keyword arguments to pass to the rc context.
301
305
 
302
306
  """
303
- rc = {}
307
+ rc = {} if rc is None else rc
304
308
 
305
309
  if colors is not None:
306
310
  rc["axes.prop_cycle"] = cycler(color=colors)
@@ -308,6 +312,9 @@ def context(
308
312
  if line_width is not None:
309
313
  rc["lines.linewidth"] = line_width
310
314
 
315
+ if line_style is not None:
316
+ rc["lines.linestyle"] = line_style
317
+
311
318
  with plt.rc_context(rc):
312
319
  yield
313
320
 
@@ -476,19 +483,17 @@ def lines(
476
483
  ) -> FigAx:
477
484
  """Plot multiple lines on the same axis."""
478
485
  fig, ax = _default_fig_ax(ax=ax, grid=grid)
479
- ax.plot(
486
+ _lines = ax.plot(
480
487
  x.index,
481
488
  x,
482
- # linestyle=linestyle,
483
- # linewidth=linewidth,
484
489
  alpha=alpha,
485
490
  )
486
491
  _default_labels(ax, xlabel=x.index.name, ylabel=None)
487
492
  if legend:
488
- if isinstance(x, pd.Series):
489
- ax.legend([str(x.name)])
490
- else:
491
- ax.legend(x.columns)
493
+ names = x.columns if isinstance(x, pd.DataFrame) else [str(x.name)]
494
+ for line, name in zip(_lines, names, strict=True):
495
+ line.set_label(name)
496
+ ax.legend()
492
497
  return fig, ax
493
498
 
494
499
 
mxlpy/report.py CHANGED
@@ -31,6 +31,14 @@ def _new_removed_changed[T](
31
31
  return new, removed, changed
32
32
 
33
33
 
34
+ def _table_row(items: list[str]) -> str:
35
+ return f"| {' | '.join(items)} |"
36
+
37
+
38
+ def _table_header(items: list[str]) -> str:
39
+ return f"{_table_row(items)}\n{_table_row(['---'] * len(items))}"
40
+
41
+
34
42
  def markdown(
35
43
  m1: Model,
36
44
  m2: Model,
@@ -167,7 +175,7 @@ def markdown(
167
175
  for k, v in rel_diff.loc[rel_diff.abs() >= rel_change].items():
168
176
  k = cast(str, k)
169
177
  dependent.append(
170
- f"| <span style='color:orange'>{k}</span> | {d1[k]:.2f} | {d2[k]:.2f} | {v:.1%} "
178
+ f"| <span style='color:orange'>{k}</span> | {d1[k]:.2f} | {d2[k]:.2f} | {v:.1%} |"
171
179
  )
172
180
  if len(dependent) >= 1:
173
181
  content.extend(
@@ -186,7 +194,7 @@ def markdown(
186
194
  for k, v in rel_diff.loc[rel_diff.abs() >= rel_change].items():
187
195
  k = cast(str, k)
188
196
  rhs.append(
189
- f"| <span style='color:orange'>{k}</span> | {r1[k]:.2f} | {r2[k]:.2f} | {v:.1%} "
197
+ f"| <span style='color:orange'>{k}</span> | {r1[k]:.2f} | {r2[k]:.2f} | {v:.1%} |"
190
198
  )
191
199
  if len(rhs) >= 1:
192
200
  content.extend(
mxlpy/scan.py CHANGED
@@ -38,8 +38,7 @@ import pandas as pd
38
38
  from mxlpy.parallel import Cache, parallelise
39
39
  from mxlpy.simulator import Result, Simulator
40
40
  from mxlpy.types import (
41
- ArrayLike,
42
- IntegratorProtocol,
41
+ IntegratorType,
43
42
  ProtocolByPars,
44
43
  SteadyStates,
45
44
  TimeCourseByPars,
@@ -286,7 +285,7 @@ class SteadyStateWorker(Protocol):
286
285
  y0: dict[str, float] | None,
287
286
  *,
288
287
  rel_norm: bool,
289
- integrator: Callable[[Callable, ArrayLike], IntegratorProtocol],
288
+ integrator: IntegratorType,
290
289
  ) -> TimePoint:
291
290
  """Call the worker function."""
292
291
  ...
@@ -301,7 +300,7 @@ class TimeCourseWorker(Protocol):
301
300
  y0: dict[str, float] | None,
302
301
  time_points: Array,
303
302
  *,
304
- integrator: Callable[[Callable, ArrayLike], IntegratorProtocol],
303
+ integrator: IntegratorType,
305
304
  ) -> TimeCourse:
306
305
  """Call the worker function."""
307
306
  ...
@@ -316,7 +315,7 @@ class ProtocolWorker(Protocol):
316
315
  y0: dict[str, float] | None,
317
316
  protocol: pd.DataFrame,
318
317
  *,
319
- integrator: Callable[[Callable, ArrayLike], IntegratorProtocol],
318
+ integrator: IntegratorType,
320
319
  time_points_per_step: int = 10,
321
320
  ) -> TimeCourse:
322
321
  """Call the worker function."""
@@ -328,7 +327,7 @@ def _steady_state_worker(
328
327
  y0: dict[str, float] | None,
329
328
  *,
330
329
  rel_norm: bool,
331
- integrator: Callable[[Callable, ArrayLike], IntegratorProtocol],
330
+ integrator: IntegratorType,
332
331
  ) -> TimePoint:
333
332
  """Simulate the model to steady state and return concentrations and fluxes.
334
333
 
@@ -357,7 +356,7 @@ def _time_course_worker(
357
356
  model: Model,
358
357
  y0: dict[str, float] | None,
359
358
  time_points: Array,
360
- integrator: Callable[[Callable, ArrayLike], IntegratorProtocol],
359
+ integrator: IntegratorType,
361
360
  ) -> TimeCourse:
362
361
  """Simulate the model to steady state and return concentrations and fluxes.
363
362
 
@@ -391,7 +390,7 @@ def _protocol_worker(
391
390
  y0: dict[str, float] | None,
392
391
  protocol: pd.DataFrame,
393
392
  *,
394
- integrator: Callable[[Callable, ArrayLike], IntegratorProtocol] = DefaultIntegrator,
393
+ integrator: IntegratorType = DefaultIntegrator,
395
394
  time_points_per_step: int = 10,
396
395
  ) -> TimeCourse:
397
396
  """Simulate the model over a protocol and return concentrations and fluxes.
@@ -440,7 +439,7 @@ def steady_state(
440
439
  rel_norm: bool = False,
441
440
  cache: Cache | None = None,
442
441
  worker: SteadyStateWorker = _steady_state_worker,
443
- integrator: Callable[[Callable, ArrayLike], IntegratorProtocol] = DefaultIntegrator,
442
+ integrator: IntegratorType = DefaultIntegrator,
444
443
  ) -> SteadyStates:
445
444
  """Get steady-state results over supplied parameters.
446
445
 
@@ -515,7 +514,7 @@ def time_course(
515
514
  parallel: bool = True,
516
515
  cache: Cache | None = None,
517
516
  worker: TimeCourseWorker = _time_course_worker,
518
- integrator: Callable[[Callable, ArrayLike], IntegratorProtocol] = DefaultIntegrator,
517
+ integrator: IntegratorType = DefaultIntegrator,
519
518
  ) -> TimeCourseByPars:
520
519
  """Get time course for each supplied parameter.
521
520
 
@@ -601,7 +600,7 @@ def time_course_over_protocol(
601
600
  parallel: bool = True,
602
601
  cache: Cache | None = None,
603
602
  worker: ProtocolWorker = _protocol_worker,
604
- integrator: Callable[[Callable, ArrayLike], IntegratorProtocol] = DefaultIntegrator,
603
+ integrator: IntegratorType = DefaultIntegrator,
605
604
  ) -> ProtocolByPars:
606
605
  """Get protocol series for each supplied parameter.
607
606
 
mxlpy/simulator.py CHANGED
@@ -21,10 +21,10 @@ from mxlpy.integrators import DefaultIntegrator
21
21
  __all__ = ["Result", "Simulator"]
22
22
 
23
23
  if TYPE_CHECKING:
24
- from collections.abc import Callable, Iterator
24
+ from collections.abc import Iterator
25
25
 
26
26
  from mxlpy.model import Model
27
- from mxlpy.types import Array, ArrayLike, IntegratorProtocol
27
+ from mxlpy.types import Array, ArrayLike, IntegratorProtocol, IntegratorType
28
28
 
29
29
 
30
30
  def _normalise_split_results(
@@ -321,16 +321,14 @@ class Simulator:
321
321
  simulation_parameters: list[dict[str, float]] | None
322
322
 
323
323
  # For resets (e.g. update variable)
324
- _integrator_type: Callable[[Callable, ArrayLike], IntegratorProtocol]
324
+ _integrator_type: IntegratorType
325
325
  _time_shift: float | None
326
326
 
327
327
  def __init__(
328
328
  self,
329
329
  model: Model,
330
330
  y0: dict[str, float] | None = None,
331
- integrator: Callable[
332
- [Callable, ArrayLike], IntegratorProtocol
333
- ] = DefaultIntegrator,
331
+ integrator: IntegratorType = DefaultIntegrator,
334
332
  *,
335
333
  test_run: bool = True,
336
334
  ) -> None:
@@ -350,6 +348,7 @@ class Simulator:
350
348
  self._integrator_type = integrator
351
349
  self._time_shift = None
352
350
  self.variables = None
351
+ self.dependent = None
353
352
  self.simulation_parameters = None
354
353
 
355
354
  if test_run:
@@ -358,10 +357,26 @@ class Simulator:
358
357
  self._initialise_integrator()
359
358
 
360
359
  def _initialise_integrator(self) -> None:
360
+ from sympy import lambdify
361
+
362
+ from mxlpy.symbolic import to_symbolic_model
363
+
364
+ try:
365
+ jac = to_symbolic_model(self.model).jacobian()
366
+
367
+ _jacobian = lambda t, y: lambdify( # noqa: E731
368
+ ("time", self.model.get_variable_names()),
369
+ jac.subs(self.model._parameters), # noqa: SLF001
370
+ )(t, y)
371
+
372
+ except: # noqa: E722
373
+ _jacobian = None # type: ignore
374
+
361
375
  y0 = self.y0
362
376
  self.integrator = self._integrator_type(
363
377
  self.model,
364
378
  [y0[k] for k in self.model.get_variable_names()],
379
+ _jacobian,
365
380
  )
366
381
 
367
382
  def clear_results(self) -> None:
mxlpy/types.py CHANGED
@@ -28,6 +28,7 @@ __all__ = [
28
28
  "Derived",
29
29
  "Float",
30
30
  "IntegratorProtocol",
31
+ "IntegratorType",
31
32
  "McSteadyStates",
32
33
  "MockSurrogate",
33
34
  "Param",
@@ -114,6 +115,7 @@ class IntegratorProtocol(Protocol):
114
115
  self,
115
116
  rhs: Callable,
116
117
  y0: ArrayLike,
118
+ jacobian: Callable | None = None,
117
119
  ) -> None:
118
120
  """Initialise the integrator."""
119
121
  ...
@@ -147,6 +149,11 @@ class IntegratorProtocol(Protocol):
147
149
  ...
148
150
 
149
151
 
152
+ type IntegratorType = Callable[
153
+ [Callable, ArrayLike, Callable | None], IntegratorProtocol
154
+ ]
155
+
156
+
150
157
  @dataclass(kw_only=True, slots=True)
151
158
  class Derived:
152
159
  """Container for a derived value."""
@@ -185,7 +192,11 @@ class Derived:
185
192
  dependent: Dictionary of dependent variables.
186
193
 
187
194
  """
188
- dependent[self.name] = self.fn(*dependent.loc[:, self.args].to_numpy().T)
195
+ try:
196
+ dependent[self.name] = self.fn(*dependent.loc[:, self.args].to_numpy().T)
197
+ except ValueError: # e.g. numpy.where
198
+ sub = dependent.loc[:, self.args].to_numpy()
199
+ dependent[self.name] = [self.fn(*row) for row in sub]
189
200
 
190
201
 
191
202
  @dataclass(kw_only=True, slots=True)
@@ -226,7 +237,11 @@ class Readout:
226
237
  dependent: Dictionary of dependent variables.
227
238
 
228
239
  """
229
- dependent[self.name] = self.fn(*dependent.loc[:, self.args].to_numpy().T)
240
+ try:
241
+ dependent[self.name] = self.fn(*dependent.loc[:, self.args].to_numpy().T)
242
+ except ValueError: # e.g. numpy.where
243
+ sub = dependent.loc[:, self.args].to_numpy()
244
+ dependent[self.name] = [self.fn(*row) for row in sub]
230
245
 
231
246
 
232
247
  @dataclass(kw_only=True, slots=True)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mxlpy
3
- Version: 0.11.0
3
+ Version: 0.13.0
4
4
  Summary: A package to build metabolic models
5
5
  Author-email: Marvin van Aalst <marvin.vanaalst@gmail.com>
6
6
  Maintainer-email: Marvin van Aalst <marvin.vanaalst@gmail.com>
@@ -32,6 +32,7 @@ Requires-Dist: pandas>=2.2.3
32
32
  Requires-Dist: parameteriser>=0.1.0
33
33
  Requires-Dist: pebble>=5.0.7
34
34
  Requires-Dist: python-libsbml>=5.20.4
35
+ Requires-Dist: salib>=1.5.1
35
36
  Requires-Dist: scipy>=1.14.1
36
37
  Requires-Dist: seaborn>=0.13.2
37
38
  Requires-Dist: symbtools>=0.4.0
@@ -1,28 +1,28 @@
1
1
  mxlpy/__init__.py,sha256=XZYNFyDC5rWcKi6139mq04cROI7LwJvxB2_3ApKwcvY,4194
2
2
  mxlpy/distributions.py,sha256=ce6RTqn19YzMMec-u09fSIUA8A92M6rehCuHuXWcX7A,8734
3
- mxlpy/fit.py,sha256=vJ0AWCvERxPkxgwuOmL9rsH4vXnlBSco4vG-5X98RK8,8085
3
+ mxlpy/fit.py,sha256=LwSoLfNVrqSlTtuUApwH36LjzGU0HLs4C_2qqTTjXFE,7859
4
4
  mxlpy/fns.py,sha256=ct_RFj9koW8vXHyr27GnbZUHUS_zfs4rDysybuFiOaU,4599
5
5
  mxlpy/identify.py,sha256=af52SCG4nlY9sSw22goaIheuvXR09QYK4ksCT24QHWI,1946
6
6
  mxlpy/label_map.py,sha256=urv-QTb0MUEKjwWvKtJSB8H2kvhLn1EKfRIH7awQQ8Y,17769
7
7
  mxlpy/linear_label_map.py,sha256=2lgERcUVDLXruRI08HBYJo_wK654y46voLUeBTzBy3k,10312
8
- mxlpy/mc.py,sha256=GIuJJ-9QRqGsd2xl1LmjmMc-bOdihVShbFmXvu4o5p4,17305
9
- mxlpy/mca.py,sha256=MjhH0CcHmXGTR4PCHTTeCbZWGBUa5TyXWtWzcg7M4Vs,9453
8
+ mxlpy/mc.py,sha256=HWuJq4fV_wfTDERbLJRSF3fjCCYMxzLdqAyO53Z_uF8,16985
9
+ mxlpy/mca.py,sha256=H0dfV45Kz5nMIW8s2V61op7x6LmI21wWgRf94i6iIY4,9328
10
10
  mxlpy/model.py,sha256=-BmS4bGCq_fMUnRLZG5uwl86ip_SiaWxsbgLPeV0nHQ,57656
11
11
  mxlpy/npe.py,sha256=oiRLA43-qf-AcS2KpQfJIOt7-Ev9Aj5sF6TMq9bJn84,8747
12
12
  mxlpy/parallel.py,sha256=kX4Td5YoovDwZp6kX_3cfO6QtHSS9ieJ0bMZiKs3Xv8,5002
13
13
  mxlpy/parameterise.py,sha256=2jMhhO-bHTFP_0kXercJekeATAZYBg5FrK1MQ_mWGpk,654
14
14
  mxlpy/paths.py,sha256=TK2wO4N9lG-UV1JGfeB64q48JVDbwqIUj63rl55MKuQ,1022
15
- mxlpy/plot.py,sha256=z1JW7Si1JQyNMj_MMLkgbLkOkSjVcfAZJGjm_WqCgT4,24355
15
+ mxlpy/plot.py,sha256=4uu-6d8LH-GWX-sG_TlSpkSsnikv1DLTtnjJzA7nuRA,24670
16
16
  mxlpy/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
17
- mxlpy/report.py,sha256=TYG-cKo9u6izlnjB4vgu6I2hYrtS9e8wYc_A4MrHq-g,6982
18
- mxlpy/scan.py,sha256=3k4084d4eg-3Ok24cL8KWPF7P1L6fbEtiaOM9VC5Eko,19210
19
- mxlpy/simulator.py,sha256=T9t2jZ6U5NyK1ICF1UkST8M8v4EPV_H98kzZ4TvQK-w,20115
20
- mxlpy/types.py,sha256=ksdn76Sdw0XhQEpQepcETvuGqcJolfrmbIRBT0R_2Bg,13612
17
+ mxlpy/report.py,sha256=h7dhcBzPFydLPxdsEXokzDf7Ce4PirXMsvLqlDZLSWM,7181
18
+ mxlpy/scan.py,sha256=-1SLyXJOX3U3CxeP1dEC4ytAoBMCH0Ql89wGvsG3LbI,18858
19
+ mxlpy/simulator.py,sha256=iTP--7QvQl0VDgSe4qg9yrnJUUo3c7keAUHjK8keZWQ,20524
20
+ mxlpy/types.py,sha256=RRhi1s6T9Yy8uZdLkWi7pvQTLmyPOlCgGTF8URYQGVA,14149
21
21
  mxlpy/experimental/__init__.py,sha256=kZTE-92OErpHzNRqmgSQYH4CGXrogGJ5EL35XGZQ81M,206
22
22
  mxlpy/experimental/diff.py,sha256=4bztagJzFMsQJM7dlun_kv-WrWssM8CIw7gcL63hFf8,8952
23
23
  mxlpy/integrators/__init__.py,sha256=kqmV6a0TRyLGR_XqbyAI652AfptYnXAUpqbSFg0CpP8,450
24
- mxlpy/integrators/int_assimulo.py,sha256=Y9jvpJWXkmueUyjDnu_6SqDntyMdG_DU370EtuRyMqA,4605
25
- mxlpy/integrators/int_scipy.py,sha256=Z9LSwqBKgYfwmrNiMxJ3-vRad5n_yQo1Ime4WDAAZEI,4343
24
+ mxlpy/integrators/int_assimulo.py,sha256=d-4HHOj4vmGpg8ig2IXMO5CPiIrq89_quEKvCxIKrhw,4747
25
+ mxlpy/integrators/int_scipy.py,sha256=dFHlYTeb2zX97f3VuNdMJdI7WEYshF4JAIgprKKk2z4,4581
26
26
  mxlpy/meta/__init__.py,sha256=Jyy4063fZy6iT4LSwjPyEAVr4N_3xxcLc8wDBoDPyKc,278
27
27
  mxlpy/meta/codegen_latex.py,sha256=1z0waYPmohY9GTJ_5DBuwXOXGTCHCH8E14tg6MYsf2A,13460
28
28
  mxlpy/meta/codegen_modebase.py,sha256=qQ8p6_KqMYCw7SRmf2HHRGvl25sYPKVI_zWKhzFhFLw,3138
@@ -44,7 +44,7 @@ mxlpy/surrogates/_torch.py,sha256=E_1eDUlPSVFwROkdMDCqYwwHE-61pjNMJWotnhjzge0,58
44
44
  mxlpy/symbolic/__init__.py,sha256=3hQjCMw8-6iOxeUdfnCg8449fF_BRF2u6lCM1GPpkRY,222
45
45
  mxlpy/symbolic/strikepy.py,sha256=r6nRtckV1nxKq3i1bYYWZOkzwZ5XeKQuZM5ck44vUo0,20010
46
46
  mxlpy/symbolic/symbolic_model.py,sha256=YL9noEeP3_0DoKXwMPELtfmPuP6mgNcLIJgDRCkyB7A,2434
47
- mxlpy-0.11.0.dist-info/METADATA,sha256=N1-dMC1k0QUlSF2ZN_URFUyg119f9QUz7xcBS2Ia_PQ,4536
48
- mxlpy-0.11.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
49
- mxlpy-0.11.0.dist-info/licenses/LICENSE,sha256=bEzjyjy1stQhfRDVaVHa3xV1x-V8emwdlbMvYO8Zo84,35073
50
- mxlpy-0.11.0.dist-info/RECORD,,
47
+ mxlpy-0.13.0.dist-info/METADATA,sha256=hq-UMQRyVx4D63yda8JHYWF38sdOBlXKbbjm43Mer2w,4564
48
+ mxlpy-0.13.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
49
+ mxlpy-0.13.0.dist-info/licenses/LICENSE,sha256=bEzjyjy1stQhfRDVaVHa3xV1x-V8emwdlbMvYO8Zo84,35073
50
+ mxlpy-0.13.0.dist-info/RECORD,,
File without changes