mxlpy 0.22.0__py3-none-any.whl → 0.23.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/scan.py CHANGED
@@ -15,16 +15,21 @@ Functions:
15
15
 
16
16
  from __future__ import annotations
17
17
 
18
- from dataclasses import dataclass
19
18
  from functools import partial
20
- from typing import TYPE_CHECKING, Protocol, Self, cast
19
+ from typing import TYPE_CHECKING, Protocol
21
20
 
22
21
  import numpy as np
23
22
  import pandas as pd
24
23
 
25
24
  from mxlpy.parallel import Cache, parallelise
26
- from mxlpy.simulator import Result, Simulator
27
- from mxlpy.types import IntegratorType, ProtocolByPars, SteadyStates, TimeCourseByPars
25
+ from mxlpy.simulator import Simulator
26
+ from mxlpy.types import (
27
+ IntegratorType,
28
+ ProtocolScan,
29
+ Result,
30
+ SteadyStateScan,
31
+ TimeCourseScan,
32
+ )
28
33
 
29
34
  if TYPE_CHECKING:
30
35
  from collections.abc import Callable
@@ -36,9 +41,7 @@ if TYPE_CHECKING:
36
41
  __all__ = [
37
42
  "ProtocolWorker",
38
43
  "SteadyStateWorker",
39
- "TimeCourse",
40
44
  "TimeCourseWorker",
41
- "TimePoint",
42
45
  "steady_state",
43
46
  "time_course",
44
47
  "time_course_over_protocol",
@@ -67,206 +70,11 @@ def _update_parameters_and_initial_conditions[T](
67
70
  return fn(model)
68
71
 
69
72
 
70
- def _empty_conc_series(model: Model) -> pd.Series:
71
- """Create an empty concentration series for the model.
72
-
73
- Args:
74
- model: Model instance to generate the series for.
75
-
76
- Returns:
77
- pd.Series: Series with NaN values for each model variable.
78
-
79
- """
80
- return pd.Series(
81
- data=np.full(shape=len(model.get_variable_names()), fill_value=np.nan),
82
- index=model.get_variable_names(),
83
- )
84
-
85
-
86
- def _empty_flux_series(model: Model) -> pd.Series:
87
- """Create an empty flux series for the model.
88
-
89
- Args:
90
- model: Model instance to generate the series for.
91
-
92
- Returns:
93
- pd.Series: Series with NaN values for each model reaction.
94
-
95
- """
96
- return pd.Series(
97
- data=np.full(shape=len(model.get_reaction_names()), fill_value=np.nan),
98
- index=model.get_reaction_names(),
99
- )
100
-
101
-
102
- def _empty_conc_df(model: Model, time_points: Array) -> pd.DataFrame:
103
- """Create an empty concentration DataFrame for the model over given time points.
104
-
105
- Args:
106
- model: Model instance to generate the DataFrame for.
107
- time_points: Array of time points.
108
-
109
- Returns:
110
- pd.DataFrame: DataFrame with NaN values for each model variable at each time point.
111
-
112
- """
113
- return pd.DataFrame(
114
- data=np.full(
115
- shape=(len(time_points), len(model.get_variable_names())),
116
- fill_value=np.nan,
117
- ),
118
- index=time_points,
119
- columns=model.get_variable_names(),
120
- )
121
-
122
-
123
- def _empty_flux_df(model: Model, time_points: Array) -> pd.DataFrame:
124
- """Create an empty concentration DataFrame for the model over given time points.
125
-
126
- Args:
127
- model: Model instance to generate the DataFrame for.
128
- time_points: Array of time points.
129
-
130
- Returns:
131
- pd.DataFrame: DataFrame with NaN values for each model variable at each time point.
132
-
133
- """
134
- return pd.DataFrame(
135
- data=np.full(
136
- shape=(len(time_points), len(model.get_reaction_names())),
137
- fill_value=np.nan,
138
- ),
139
- index=time_points,
140
- columns=model.get_reaction_names(),
141
- )
142
-
143
-
144
73
  ###############################################################################
145
74
  # Single returns
146
75
  ###############################################################################
147
76
 
148
77
 
149
- @dataclass(slots=True)
150
- class TimePoint:
151
- """Represents a single time point in a simulation.
152
-
153
- Attributes:
154
- concs: Series of concentrations at the time point.
155
- fluxes: Series of fluxes at the time point.
156
-
157
- Args:
158
- model: Model instance to generate the time point for.
159
- concs: DataFrame of concentrations (default: None).
160
- fluxes: DataFrame of fluxes (default: None).
161
- idx: Index of the time point in the DataFrame (default: -1).
162
-
163
- """
164
-
165
- variables: pd.Series
166
- fluxes: pd.Series
167
-
168
- @classmethod
169
- def from_result(
170
- cls,
171
- *,
172
- model: Model,
173
- result: Result | None,
174
- idx: int = -1,
175
- ) -> Self:
176
- """Initialize the Scan object.
177
-
178
- Args:
179
- model: The model object.
180
- result: Result of the simulation
181
- idx: Index to select specific row from concs and fluxes DataFrames.
182
-
183
- """
184
- if result is None:
185
- return cls(
186
- variables=_empty_conc_series(model),
187
- fluxes=_empty_flux_series(model),
188
- )
189
-
190
- return cls(
191
- variables=result.variables.iloc[idx],
192
- fluxes=result.fluxes.iloc[idx],
193
- )
194
-
195
- @property
196
- def results(self) -> pd.Series:
197
- """Get the combined results of concentrations and fluxes.
198
-
199
- Example:
200
- >>> time_point.results
201
- x1 1.0
202
- x2 0.5
203
- v1 0.1
204
- v2 0.2
205
-
206
- Returns:
207
- pd.Series: Combined series of concentrations and fluxes.
208
-
209
- """
210
- return pd.concat((self.variables, self.fluxes), axis=0)
211
-
212
-
213
- @dataclass(slots=True)
214
- class TimeCourse:
215
- """Represents a time course in a simulation.
216
-
217
- Attributes:
218
- variables: DataFrame of concentrations over time.
219
- fluxes: DataFrame of fluxes over time.
220
-
221
- """
222
-
223
- variables: pd.DataFrame
224
- fluxes: pd.DataFrame
225
-
226
- @classmethod
227
- def from_scan(
228
- cls,
229
- *,
230
- model: Model,
231
- time_points: Array,
232
- result: Result | None,
233
- ) -> Self:
234
- """Initialize the Scan object.
235
-
236
- Args:
237
- model (Model): The model object.
238
- time_points (Array): Array of time points.
239
- result: Result of the simulation
240
-
241
- """
242
- if result is None:
243
- return cls(
244
- _empty_conc_df(model, time_points),
245
- _empty_flux_df(model, time_points),
246
- )
247
- return cls(
248
- result.variables,
249
- result.fluxes,
250
- )
251
-
252
- @property
253
- def results(self) -> pd.DataFrame:
254
- """Get the combined results of concentrations and fluxes over time.
255
-
256
- Examples:
257
- >>> time_course.results
258
- Time x1 x2 v1 v2
259
- 0.0 1.0 1.00 1.00 1.00
260
- 0.1 0.9 0.99 0.99 0.99
261
- 0.2 0.8 0.99 0.99 0.99
262
-
263
- Returns:
264
- pd.DataFrame: Combined DataFrame of concentrations and fluxes.
265
-
266
- """
267
- return pd.concat((self.variables, self.fluxes), axis=1)
268
-
269
-
270
78
  ###############################################################################
271
79
  # Workers
272
80
  ###############################################################################
@@ -282,7 +90,7 @@ class SteadyStateWorker(Protocol):
282
90
  rel_norm: bool,
283
91
  integrator: IntegratorType | None,
284
92
  y0: dict[str, float] | None,
285
- ) -> TimePoint:
93
+ ) -> Result:
286
94
  """Call the worker function."""
287
95
  ...
288
96
 
@@ -297,7 +105,7 @@ class TimeCourseWorker(Protocol):
297
105
  *,
298
106
  integrator: IntegratorType | None,
299
107
  y0: dict[str, float] | None,
300
- ) -> TimeCourse:
108
+ ) -> Result:
301
109
  """Call the worker function."""
302
110
  ...
303
111
 
@@ -313,7 +121,7 @@ class ProtocolWorker(Protocol):
313
121
  integrator: IntegratorType | None,
314
122
  y0: dict[str, float] | None,
315
123
  time_points_per_step: int = 10,
316
- ) -> TimeCourse:
124
+ ) -> Result:
317
125
  """Call the worker function."""
318
126
  ...
319
127
 
@@ -324,7 +132,7 @@ def _steady_state_worker(
324
132
  rel_norm: bool,
325
133
  integrator: IntegratorType | None,
326
134
  y0: dict[str, float] | None,
327
- ) -> TimePoint:
135
+ ) -> Result:
328
136
  """Simulate the model to steady state and return concentrations and fluxes.
329
137
 
330
138
  Args:
@@ -345,7 +153,9 @@ def _steady_state_worker(
345
153
  )
346
154
  except ZeroDivisionError:
347
155
  res = None
348
- return TimePoint.from_result(model=model, result=res)
156
+ return (
157
+ Result.default(model=model, time_points=np.array([0.0])) if res is None else res
158
+ )
349
159
 
350
160
 
351
161
  def _time_course_worker(
@@ -353,7 +163,7 @@ def _time_course_worker(
353
163
  time_points: Array,
354
164
  y0: dict[str, float] | None,
355
165
  integrator: IntegratorType | None,
356
- ) -> TimeCourse:
166
+ ) -> Result:
357
167
  """Simulate the model to steady state and return concentrations and fluxes.
358
168
 
359
169
  Args:
@@ -374,11 +184,7 @@ def _time_course_worker(
374
184
  )
375
185
  except ZeroDivisionError:
376
186
  res = None
377
- return TimeCourse.from_scan(
378
- model=model,
379
- time_points=time_points,
380
- result=res,
381
- )
187
+ return Result.default(model=model, time_points=time_points) if res is None else res
382
188
 
383
189
 
384
190
  def _protocol_worker(
@@ -388,7 +194,7 @@ def _protocol_worker(
388
194
  integrator: IntegratorType | None,
389
195
  y0: dict[str, float] | None,
390
196
  time_points_per_step: int = 10,
391
- ) -> TimeCourse:
197
+ ) -> Result:
392
198
  """Simulate the model over a protocol and return concentrations and fluxes.
393
199
 
394
200
  Args:
@@ -419,11 +225,7 @@ def _protocol_worker(
419
225
  protocol.index[-1].total_seconds(),
420
226
  len(protocol) * time_points_per_step,
421
227
  )
422
- return TimeCourse.from_scan(
423
- model=model,
424
- time_points=time_points,
425
- result=res,
426
- )
228
+ return Result.default(model=model, time_points=time_points) if res is None else res
427
229
 
428
230
 
429
231
  def steady_state(
@@ -436,7 +238,7 @@ def steady_state(
436
238
  cache: Cache | None = None,
437
239
  worker: SteadyStateWorker = _steady_state_worker,
438
240
  integrator: IntegratorType | None = None,
439
- ) -> SteadyStates:
241
+ ) -> SteadyStateScan:
440
242
  """Get steady-state results over supplied values.
441
243
 
442
244
  Args:
@@ -492,16 +294,16 @@ def steady_state(
492
294
  cache=cache,
493
295
  parallel=parallel,
494
296
  )
495
- concs = pd.DataFrame({k: v.variables.T for k, v in res}).T
496
- fluxes = pd.DataFrame({k: v.fluxes.T for k, v in res}).T
497
- idx = (
498
- pd.Index(to_scan.iloc[:, 0])
499
- if to_scan.shape[1] == 1
500
- else pd.MultiIndex.from_frame(to_scan)
297
+
298
+ return SteadyStateScan(
299
+ raw_index=(
300
+ pd.Index(to_scan.iloc[:, 0])
301
+ if to_scan.shape[1] == 1
302
+ else pd.MultiIndex.from_frame(to_scan)
303
+ ),
304
+ raw_results=[i[1] for i in res],
305
+ to_scan=to_scan,
501
306
  )
502
- concs.index = idx
503
- fluxes.index = idx
504
- return SteadyStates(variables=concs, fluxes=fluxes, parameters=to_scan)
505
307
 
506
308
 
507
309
  def time_course(
@@ -514,7 +316,7 @@ def time_course(
514
316
  cache: Cache | None = None,
515
317
  integrator: IntegratorType | None = None,
516
318
  worker: TimeCourseWorker = _time_course_worker,
517
- ) -> TimeCourseByPars:
319
+ ) -> TimeCourseScan:
518
320
  """Get time course for each supplied parameter.
519
321
 
520
322
  Examples:
@@ -585,12 +387,9 @@ def time_course(
585
387
  cache=cache,
586
388
  parallel=parallel,
587
389
  )
588
- concs = cast(dict, {k: v.variables for k, v in res})
589
- fluxes = cast(dict, {k: v.fluxes for k, v in res})
590
- return TimeCourseByPars(
591
- parameters=to_scan,
592
- variables=pd.concat(concs, names=["n", "time"]),
593
- fluxes=pd.concat(fluxes, names=["n", "time"]),
390
+ return TimeCourseScan(
391
+ to_scan=to_scan,
392
+ raw_results=dict(res),
594
393
  )
595
394
 
596
395
 
@@ -605,7 +404,7 @@ def time_course_over_protocol(
605
404
  cache: Cache | None = None,
606
405
  worker: ProtocolWorker = _protocol_worker,
607
406
  integrator: IntegratorType | None = None,
608
- ) -> ProtocolByPars:
407
+ ) -> ProtocolScan:
609
408
  """Get protocol series for each supplied parameter.
610
409
 
611
410
  Examples:
@@ -656,11 +455,8 @@ def time_course_over_protocol(
656
455
  cache=cache,
657
456
  parallel=parallel,
658
457
  )
659
- concs = cast(dict, {k: v.variables for k, v in res})
660
- fluxes = cast(dict, {k: v.fluxes for k, v in res})
661
- return ProtocolByPars(
662
- parameters=to_scan,
458
+ return ProtocolScan(
459
+ to_scan=to_scan,
663
460
  protocol=protocol,
664
- variables=pd.concat(concs, names=["n", "time"]),
665
- fluxes=pd.concat(fluxes, names=["n", "time"]),
461
+ raw_results=dict(res),
666
462
  )