mxlpy 0.21.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/types.py CHANGED
@@ -17,9 +17,18 @@ Classes:
17
17
  from __future__ import annotations
18
18
 
19
19
  from abc import abstractmethod
20
- from collections.abc import Callable, Iterable, Iterator, Mapping
20
+ from collections.abc import Callable, Hashable, Iterable, Iterator, Mapping
21
21
  from dataclasses import dataclass, field
22
- from typing import TYPE_CHECKING, Any, ParamSpec, Protocol, TypeVar, cast
22
+ from typing import (
23
+ TYPE_CHECKING,
24
+ Any,
25
+ Literal,
26
+ ParamSpec,
27
+ Protocol,
28
+ TypeVar,
29
+ cast,
30
+ overload,
31
+ )
23
32
 
24
33
  import numpy as np
25
34
  import pandas as pd
@@ -31,34 +40,47 @@ __all__ = [
31
40
  "Array",
32
41
  "ArrayLike",
33
42
  "Derived",
43
+ "InitialAssignment",
34
44
  "IntegratorProtocol",
35
45
  "IntegratorType",
36
46
  "McSteadyStates",
37
47
  "MockSurrogate",
38
48
  "Param",
39
- "ProtocolByPars",
49
+ "Parameter",
50
+ "ProtocolScan",
40
51
  "RateFn",
41
52
  "Reaction",
42
53
  "Readout",
43
54
  "ResponseCoefficients",
44
55
  "ResponseCoefficientsByPars",
56
+ "Result",
45
57
  "RetType",
46
- "SteadyStates",
47
- "TimeCourseByPars",
58
+ "Rhs",
59
+ "SteadyStateScan",
60
+ "TimeCourseScan",
61
+ "Variable",
48
62
  "unwrap",
49
63
  "unwrap2",
50
64
  ]
51
65
 
52
66
  type RateFn = Callable[..., float]
53
67
  type Array = NDArray[np.floating[Any]]
54
- type ArrayLike = NDArray[np.floating[Any]] | list[float]
55
-
68
+ type ArrayLike = NDArray[np.floating[Any]] | pd.Index | list[float]
69
+ type Rhs = Callable[
70
+ [
71
+ float, # t
72
+ Iterable[float], # y
73
+ ],
74
+ tuple[float, ...],
75
+ ]
56
76
 
57
77
  Param = ParamSpec("Param")
58
78
  RetType = TypeVar("RetType")
59
79
 
60
80
 
61
81
  if TYPE_CHECKING:
82
+ import sympy
83
+
62
84
  from mxlpy.model import Model
63
85
 
64
86
 
@@ -106,8 +128,8 @@ class IntegratorProtocol(Protocol):
106
128
 
107
129
  def __init__(
108
130
  self,
109
- rhs: Callable,
110
- y0: ArrayLike,
131
+ rhs: Rhs,
132
+ y0: tuple[float, ...],
111
133
  jacobian: Callable | None = None,
112
134
  ) -> None:
113
135
  """Initialise the integrator."""
@@ -143,38 +165,93 @@ class IntegratorProtocol(Protocol):
143
165
 
144
166
 
145
167
  type IntegratorType = Callable[
146
- [Callable, ArrayLike, Callable | None], IntegratorProtocol
168
+ [
169
+ Rhs, # model
170
+ tuple[float, ...], # y0
171
+ Callable | None, # jacobian
172
+ ],
173
+ IntegratorProtocol,
147
174
  ]
148
175
 
149
176
 
177
+ @dataclass
178
+ class Variable:
179
+ """Container for variable meta information."""
180
+
181
+ initial_value: float | InitialAssignment
182
+ unit: sympy.Expr | None = None
183
+ source: str | None = None
184
+
185
+
186
+ @dataclass
187
+ class Parameter:
188
+ """Container for parameter meta information."""
189
+
190
+ value: float | InitialAssignment
191
+ unit: sympy.Expr | None = None
192
+ source: str | None = None
193
+
194
+
150
195
  @dataclass(kw_only=True, slots=True)
151
196
  class Derived:
152
197
  """Container for a derived value."""
153
198
 
154
199
  fn: RateFn
155
200
  args: list[str]
201
+ unit: sympy.Expr | None = None
202
+
203
+ def calculate(self, args: dict[str, Any]) -> float:
204
+ """Calculate the derived value.
205
+
206
+ Args:
207
+ args: Dictionary of args variables.
208
+
209
+ Returns:
210
+ The calculated derived value.
211
+
212
+ """
213
+ return cast(float, self.fn(*(args[arg] for arg in self.args)))
214
+
215
+ def calculate_inpl(self, name: str, args: dict[str, Any]) -> None:
216
+ """Calculate the derived value in place.
217
+
218
+ Args:
219
+ name: Name of the derived variable.
220
+ args: Dictionary of args variables.
221
+
222
+ """
223
+ args[name] = cast(float, self.fn(*(args[arg] for arg in self.args)))
224
+
225
+
226
+ @dataclass(kw_only=True, slots=True)
227
+ class InitialAssignment:
228
+ """Container for a derived value."""
229
+
230
+ fn: RateFn
231
+ args: list[str]
232
+ unit: sympy.Expr | None = None
156
233
 
157
- def calculate(self, dependent: dict[str, Any]) -> float:
234
+ def calculate(self, args: dict[str, Any]) -> float:
158
235
  """Calculate the derived value.
159
236
 
160
237
  Args:
161
- dependent: Dictionary of dependent variables.
238
+ args: Dictionary of args variables.
162
239
 
163
240
  Returns:
164
241
  The calculated derived value.
165
242
 
166
243
  """
167
- return cast(float, self.fn(*(dependent[arg] for arg in self.args)))
244
+ return cast(float, self.fn(*(args[arg] for arg in self.args)))
168
245
 
169
- def calculate_inpl(self, name: str, dependent: dict[str, Any]) -> None:
246
+ def calculate_inpl(self, name: str, args: dict[str, Any]) -> None:
170
247
  """Calculate the derived value in place.
171
248
 
172
249
  Args:
173
250
  name: Name of the derived variable.
174
- dependent: Dictionary of dependent variables.
251
+ args: Dictionary of args variables.
175
252
 
176
253
  """
177
- dependent[name] = cast(float, self.fn(*(dependent[arg] for arg in self.args)))
254
+ args[name] = cast(float, self.fn(*(args[arg] for arg in self.args)))
178
255
 
179
256
 
180
257
  @dataclass(kw_only=True, slots=True)
@@ -183,28 +260,29 @@ class Readout:
183
260
 
184
261
  fn: RateFn
185
262
  args: list[str]
263
+ unit: sympy.Expr | None = None
186
264
 
187
- def calculate(self, dependent: dict[str, Any]) -> float:
265
+ def calculate(self, args: dict[str, Any]) -> float:
188
266
  """Calculate the derived value.
189
267
 
190
268
  Args:
191
- dependent: Dictionary of dependent variables.
269
+ args: Dictionary of args variables.
192
270
 
193
271
  Returns:
194
272
  The calculated derived value.
195
273
 
196
274
  """
197
- return cast(float, self.fn(*(dependent[arg] for arg in self.args)))
275
+ return cast(float, self.fn(*(args[arg] for arg in self.args)))
198
276
 
199
- def calculate_inpl(self, name: str, dependent: dict[str, Any]) -> None:
277
+ def calculate_inpl(self, name: str, args: dict[str, Any]) -> None:
200
278
  """Calculate the reaction in place.
201
279
 
202
280
  Args:
203
281
  name: Name of the derived variable.
204
- dependent: Dictionary of dependent variables.
282
+ args: Dictionary of args variables.
205
283
 
206
284
  """
207
- dependent[name] = cast(float, self.fn(*(dependent[arg] for arg in self.args)))
285
+ args[name] = cast(float, self.fn(*(args[arg] for arg in self.args)))
208
286
 
209
287
 
210
288
  @dataclass(kw_only=True, slots=True)
@@ -214,35 +292,36 @@ class Reaction:
214
292
  fn: RateFn
215
293
  stoichiometry: Mapping[str, float | Derived]
216
294
  args: list[str]
295
+ unit: sympy.Expr | None = None
217
296
 
218
297
  def get_modifiers(self, model: Model) -> list[str]:
219
298
  """Get the modifiers of the reaction."""
220
- include = set(model.variables)
299
+ include = set(model.get_variable_names())
221
300
  exclude = set(self.stoichiometry)
222
301
 
223
302
  return [k for k in self.args if k in include and k not in exclude]
224
303
 
225
- def calculate(self, dependent: dict[str, Any]) -> float:
304
+ def calculate(self, args: dict[str, Any]) -> float:
226
305
  """Calculate the derived value.
227
306
 
228
307
  Args:
229
- dependent: Dictionary of dependent variables.
308
+ args: Dictionary of args variables.
230
309
 
231
310
  Returns:
232
311
  The calculated derived value.
233
312
 
234
313
  """
235
- return cast(float, self.fn(*(dependent[arg] for arg in self.args)))
314
+ return cast(float, self.fn(*(args[arg] for arg in self.args)))
236
315
 
237
- def calculate_inpl(self, name: str, dependent: dict[str, Any]) -> None:
316
+ def calculate_inpl(self, name: str, args: dict[str, Any]) -> None:
238
317
  """Calculate the reaction in place.
239
318
 
240
319
  Args:
241
320
  name: Name of the derived variable.
242
- dependent: Dictionary of dependent variables.
321
+ args: Dictionary of args variables.
243
322
 
244
323
  """
245
- dependent[name] = cast(float, self.fn(*(dependent[arg] for arg in self.args)))
324
+ args[name] = cast(float, self.fn(*(args[arg] for arg in self.args)))
246
325
 
247
326
 
248
327
  @dataclass(kw_only=True)
@@ -297,22 +376,417 @@ class MockSurrogate(AbstractSurrogate):
297
376
  ) # type: ignore
298
377
 
299
378
 
379
+ @dataclass(kw_only=True)
380
+ class AbstractEstimator:
381
+ """Abstract class for parameter estimation using neural networks."""
382
+
383
+ parameter_names: list[str]
384
+
385
+ @abstractmethod
386
+ def predict(self, features: pd.Series | pd.DataFrame) -> pd.DataFrame:
387
+ """Predict the target values for the given features."""
388
+
389
+
390
+ ###############################################################################
391
+ # Simulation results
392
+ ###############################################################################
393
+
394
+
395
+ def _normalise_split_results(
396
+ results: list[pd.DataFrame],
397
+ normalise: float | ArrayLike,
398
+ ) -> list[pd.DataFrame]:
399
+ """Normalize split results by a given factor or array.
400
+
401
+ Args:
402
+ results: List of DataFrames containing the results to normalize.
403
+ normalise: Normalization factor or array.
404
+
405
+ Returns:
406
+ list[pd.DataFrame]: List of normalized DataFrames.
407
+
408
+ """
409
+ if isinstance(normalise, int | float):
410
+ return [i / normalise for i in results]
411
+ if len(normalise) == len(results):
412
+ return [(i.T / j).T for i, j in zip(results, normalise, strict=True)]
413
+
414
+ results = []
415
+ start = 0
416
+ end = 0
417
+ for i in results:
418
+ end += len(i)
419
+ results.append(i / np.reshape(normalise[start:end], (len(i), 1))) # type: ignore
420
+ start += end
421
+ return results
422
+
423
+
300
424
  @dataclass(kw_only=True, slots=True)
301
- class ResponseCoefficients:
302
- """Container for response coefficients."""
425
+ class Result:
426
+ """Simulation results."""
427
+
428
+ model: Model
429
+ raw_variables: list[pd.DataFrame]
430
+ raw_parameters: list[dict[str, float]]
431
+ raw_args: list[pd.DataFrame] = field(default_factory=list)
432
+
433
+ @classmethod
434
+ def default(cls, model: Model, time_points: Array) -> Result:
435
+ """Get result filled with NaNs."""
436
+ return Result(
437
+ model=model,
438
+ raw_variables=[
439
+ pd.DataFrame(
440
+ data=np.full(
441
+ shape=(len(time_points), len(model.get_variable_names())),
442
+ fill_value=np.nan,
443
+ ),
444
+ index=time_points,
445
+ columns=model.get_variable_names(),
446
+ )
447
+ ],
448
+ raw_parameters=[model.get_parameter_values()],
449
+ )
303
450
 
304
- variables: pd.DataFrame
305
- fluxes: pd.DataFrame
451
+ @property
452
+ def variables(self) -> pd.DataFrame:
453
+ """Simulation variables."""
454
+ return self.get_variables(
455
+ include_derived_variables=True,
456
+ include_readouts=True,
457
+ concatenated=True,
458
+ normalise=None,
459
+ )
460
+
461
+ @property
462
+ def fluxes(self) -> pd.DataFrame:
463
+ """Simulation fluxes."""
464
+ return self.get_fluxes()
465
+
466
+ def _compute_args(self) -> list[pd.DataFrame]:
467
+ # Already computed
468
+ if len(self.raw_args) > 0:
469
+ return self.raw_args
470
+
471
+ # Compute new otherwise
472
+ for res, p in zip(self.raw_variables, self.raw_parameters, strict=True):
473
+ self.model.update_parameters(p)
474
+ self.raw_args.append(
475
+ self.model.get_args_time_course(
476
+ variables=res,
477
+ include_variables=True,
478
+ include_parameters=True,
479
+ include_derived_parameters=True,
480
+ include_derived_variables=True,
481
+ include_reactions=True,
482
+ include_surrogate_variables=True,
483
+ include_surrogate_fluxes=True,
484
+ include_readouts=True,
485
+ )
486
+ )
487
+ return self.raw_args
488
+
489
+ def _select_data(
490
+ self,
491
+ dependent: list[pd.DataFrame],
492
+ *,
493
+ include_variables: bool = False,
494
+ include_parameters: bool = False,
495
+ include_derived_parameters: bool = False,
496
+ include_derived_variables: bool = False,
497
+ include_reactions: bool = False,
498
+ include_surrogate_variables: bool = False,
499
+ include_surrogate_fluxes: bool = False,
500
+ include_readouts: bool = False,
501
+ ) -> list[pd.DataFrame]:
502
+ names = self.model.get_arg_names(
503
+ include_time=False,
504
+ include_variables=include_variables,
505
+ include_parameters=include_parameters,
506
+ include_derived_parameters=include_derived_parameters,
507
+ include_derived_variables=include_derived_variables,
508
+ include_reactions=include_reactions,
509
+ include_surrogate_variables=include_surrogate_variables,
510
+ include_surrogate_fluxes=include_surrogate_fluxes,
511
+ include_readouts=include_readouts,
512
+ )
513
+ return [i.loc[:, names] for i in dependent]
514
+
515
+ def _adjust_data(
516
+ self,
517
+ data: list[pd.DataFrame],
518
+ normalise: float | ArrayLike | None = None,
519
+ *,
520
+ concatenated: bool = True,
521
+ ) -> pd.DataFrame | list[pd.DataFrame]:
522
+ if normalise is not None:
523
+ data = _normalise_split_results(data, normalise=normalise)
524
+ if concatenated:
525
+ return pd.concat(data, axis=0)
526
+ return data
527
+
528
+ @overload
529
+ def get_args( # type: ignore
530
+ self,
531
+ *,
532
+ include_variables: bool = True,
533
+ include_parameters: bool = False,
534
+ include_derived_parameters: bool = False,
535
+ include_derived_variables: bool = True,
536
+ include_reactions: bool = True,
537
+ include_surrogate_variables: bool = False,
538
+ include_surrogate_fluxes: bool = False,
539
+ include_readouts: bool = False,
540
+ concatenated: Literal[False],
541
+ normalise: float | ArrayLike | None = None,
542
+ ) -> list[pd.DataFrame]: ...
543
+
544
+ @overload
545
+ def get_args(
546
+ self,
547
+ *,
548
+ include_variables: bool = True,
549
+ include_parameters: bool = False,
550
+ include_derived_parameters: bool = False,
551
+ include_derived_variables: bool = True,
552
+ include_reactions: bool = True,
553
+ include_surrogate_variables: bool = False,
554
+ include_surrogate_fluxes: bool = False,
555
+ include_readouts: bool = False,
556
+ concatenated: Literal[True],
557
+ normalise: float | ArrayLike | None = None,
558
+ ) -> pd.DataFrame: ...
559
+
560
+ @overload
561
+ def get_args(
562
+ self,
563
+ *,
564
+ include_variables: bool = True,
565
+ include_parameters: bool = False,
566
+ include_derived_parameters: bool = False,
567
+ include_derived_variables: bool = True,
568
+ include_reactions: bool = True,
569
+ include_surrogate_variables: bool = False,
570
+ include_surrogate_fluxes: bool = False,
571
+ include_readouts: bool = False,
572
+ concatenated: bool = True,
573
+ normalise: float | ArrayLike | None = None,
574
+ ) -> pd.DataFrame: ...
575
+
576
+ def get_args(
577
+ self,
578
+ *,
579
+ include_variables: bool = True,
580
+ include_parameters: bool = False,
581
+ include_derived_parameters: bool = False,
582
+ include_derived_variables: bool = True,
583
+ include_reactions: bool = True,
584
+ include_surrogate_variables: bool = False,
585
+ include_surrogate_fluxes: bool = False,
586
+ include_readouts: bool = False,
587
+ concatenated: bool = True,
588
+ normalise: float | ArrayLike | None = None,
589
+ ) -> pd.DataFrame | list[pd.DataFrame]:
590
+ """Get the variables over time.
591
+
592
+ Examples:
593
+ >>> Result().get_variables()
594
+ Time ATP NADPH
595
+ 0.000000 1.000000 1.000000
596
+ 0.000100 0.999900 0.999900
597
+ 0.000200 0.999800 0.999800
598
+
599
+ """
600
+ variables = self._select_data(
601
+ self._compute_args(),
602
+ include_variables=include_variables,
603
+ include_parameters=include_parameters,
604
+ include_derived_parameters=include_derived_parameters,
605
+ include_derived_variables=include_derived_variables,
606
+ include_reactions=include_reactions,
607
+ include_surrogate_variables=include_surrogate_variables,
608
+ include_surrogate_fluxes=include_surrogate_fluxes,
609
+ include_readouts=include_readouts,
610
+ )
611
+ return self._adjust_data(
612
+ variables, normalise=normalise, concatenated=concatenated
613
+ )
614
+
615
+ @overload
616
+ def get_variables( # type: ignore
617
+ self,
618
+ *,
619
+ include_derived_variables: bool = True,
620
+ include_readouts: bool = True,
621
+ concatenated: Literal[False],
622
+ normalise: float | ArrayLike | None = None,
623
+ ) -> list[pd.DataFrame]: ...
624
+
625
+ @overload
626
+ def get_variables(
627
+ self,
628
+ *,
629
+ include_derived_variables: bool = True,
630
+ include_readouts: bool = True,
631
+ concatenated: Literal[True],
632
+ normalise: float | ArrayLike | None = None,
633
+ ) -> pd.DataFrame: ...
634
+
635
+ @overload
636
+ def get_variables(
637
+ self,
638
+ *,
639
+ include_derived_variables: bool = True,
640
+ include_readouts: bool = True,
641
+ concatenated: bool = True,
642
+ normalise: float | ArrayLike | None = None,
643
+ ) -> pd.DataFrame: ...
644
+
645
+ def get_variables(
646
+ self,
647
+ *,
648
+ include_derived_variables: bool = True,
649
+ include_readouts: bool = True,
650
+ concatenated: bool = True,
651
+ normalise: float | ArrayLike | None = None,
652
+ ) -> pd.DataFrame | list[pd.DataFrame]:
653
+ """Get the variables over time.
654
+
655
+ Examples:
656
+ >>> Result().get_variables()
657
+ Time ATP NADPH
658
+ 0.000000 1.000000 1.000000
659
+ 0.000100 0.999900 0.999900
660
+ 0.000200 0.999800 0.999800
661
+
662
+ """
663
+ if not include_derived_variables and not include_readouts:
664
+ return self._adjust_data(
665
+ self.raw_variables,
666
+ normalise=normalise,
667
+ concatenated=concatenated,
668
+ )
669
+
670
+ variables = self._select_data(
671
+ self._compute_args(),
672
+ include_variables=True,
673
+ include_derived_variables=include_derived_variables,
674
+ include_readouts=include_readouts,
675
+ )
676
+ return self._adjust_data(
677
+ variables, normalise=normalise, concatenated=concatenated
678
+ )
679
+
680
+ @overload
681
+ def get_fluxes( # type: ignore
682
+ self,
683
+ *,
684
+ include_surrogates: bool = True,
685
+ normalise: float | ArrayLike | None = None,
686
+ concatenated: Literal[False],
687
+ ) -> list[pd.DataFrame]: ...
688
+
689
+ @overload
690
+ def get_fluxes(
691
+ self,
692
+ *,
693
+ include_surrogates: bool = True,
694
+ normalise: float | ArrayLike | None = None,
695
+ concatenated: Literal[True],
696
+ ) -> pd.DataFrame: ...
697
+
698
+ @overload
699
+ def get_fluxes(
700
+ self,
701
+ *,
702
+ include_surrogates: bool = True,
703
+ normalise: float | ArrayLike | None = None,
704
+ concatenated: bool = True,
705
+ ) -> pd.DataFrame: ...
706
+
707
+ def get_fluxes(
708
+ self,
709
+ *,
710
+ include_surrogates: bool = True,
711
+ normalise: float | ArrayLike | None = None,
712
+ concatenated: bool = True,
713
+ ) -> pd.DataFrame | list[pd.DataFrame]:
714
+ """Get the flux results.
715
+
716
+ Examples:
717
+ >>> Result.get_fluxes()
718
+ Time v1 v2
719
+ 0.000000 1.000000 10.00000
720
+ 0.000100 0.999900 9.999000
721
+ 0.000200 0.999800 9.998000
722
+
723
+ Returns:
724
+ pd.DataFrame: DataFrame of fluxes.
725
+
726
+ """
727
+ fluxes = self._select_data(
728
+ self._compute_args(),
729
+ include_reactions=True,
730
+ include_surrogate_fluxes=include_surrogates,
731
+ )
732
+ return self._adjust_data(
733
+ fluxes,
734
+ normalise=normalise,
735
+ concatenated=concatenated,
736
+ )
737
+
738
+ def get_combined(self) -> pd.DataFrame:
739
+ """Get the variables and fluxes as a single pandas.DataFrame.
740
+
741
+ Examples:
742
+ >>> Result.get_combined()
743
+ Time ATP NADPH v1 v2
744
+ 0.000000 1.000000 1.000000 1.000000 10.00000
745
+ 0.000100 0.999900 0.999900 0.999900 9.999000
746
+ 0.000200 0.999800 0.999800 0.999800 9.998000
747
+
748
+ Returns:
749
+ pd.DataFrame: DataFrame of fluxes.
750
+
751
+ """
752
+ return pd.concat((self.variables, self.fluxes), axis=1)
753
+
754
+ def get_new_y0(self) -> dict[str, float]:
755
+ """Get the new initial conditions after the simulation.
756
+
757
+ Examples:
758
+ >>> Simulator(model).simulate_to_steady_state().get_new_y0()
759
+ {"ATP": 1.0, "NADPH": 1.0}
760
+
761
+ """
762
+ return dict(
763
+ self.get_variables(
764
+ include_derived_variables=False,
765
+ include_readouts=False,
766
+ ).iloc[-1]
767
+ )
306
768
 
307
769
  def __iter__(self) -> Iterator[pd.DataFrame]:
308
770
  """Iterate over the concentration and flux response coefficients."""
309
771
  return iter((self.variables, self.fluxes))
310
772
 
773
+
774
+ @dataclass(kw_only=True, slots=True)
775
+ class ResponseCoefficients:
776
+ """Container for response coefficients."""
777
+
778
+ variables: pd.DataFrame
779
+ fluxes: pd.DataFrame
780
+
311
781
  @property
312
- def results(self) -> pd.DataFrame:
782
+ def combined(self) -> pd.DataFrame:
313
783
  """Return the response coefficients as a DataFrame."""
314
784
  return pd.concat((self.variables, self.fluxes), axis=1)
315
785
 
786
+ def __iter__(self) -> Iterator[pd.DataFrame]:
787
+ """Iterate over the concentration and flux response coefficients."""
788
+ return iter((self.variables, self.fluxes))
789
+
316
790
 
317
791
  @dataclass(kw_only=True, slots=True)
318
792
  class ResponseCoefficientsByPars:
@@ -322,124 +796,247 @@ class ResponseCoefficientsByPars:
322
796
  fluxes: pd.DataFrame
323
797
  parameters: pd.DataFrame
324
798
 
325
- def __iter__(self) -> Iterator[pd.DataFrame]:
326
- """Iterate over the concentration and flux response coefficients."""
327
- return iter((self.variables, self.fluxes))
328
-
329
799
  @property
330
- def results(self) -> pd.DataFrame:
800
+ def combined(self) -> pd.DataFrame:
331
801
  """Return the response coefficients as a DataFrame."""
332
802
  return pd.concat((self.variables, self.fluxes), axis=1)
333
803
 
804
+ def __iter__(self) -> Iterator[pd.DataFrame]:
805
+ """Iterate over the concentration and flux response coefficients."""
806
+ return iter((self.variables, self.fluxes))
334
807
 
335
- @dataclass(kw_only=True, slots=True)
336
- class SteadyStates:
337
- """Container for steady states."""
338
808
 
339
- variables: pd.DataFrame
340
- fluxes: pd.DataFrame
341
- parameters: pd.DataFrame
809
+ @dataclass(kw_only=True, slots=True)
810
+ class SteadyStateScan:
811
+ """Container for steady states by scanned values."""
342
812
 
343
- def __iter__(self) -> Iterator[pd.DataFrame]:
344
- """Iterate over the concentration and flux steady states."""
345
- return iter((self.variables, self.fluxes))
813
+ to_scan: pd.DataFrame
814
+ raw_index: pd.Index | pd.MultiIndex
815
+ raw_results: list[Result]
346
816
 
347
817
  @property
348
- def results(self) -> pd.DataFrame:
349
- """Return the steady states as a DataFrame."""
350
- return pd.concat((self.variables, self.fluxes), axis=1)
818
+ def variables(self) -> pd.DataFrame:
819
+ """Return steady-state variables by scan."""
820
+ return pd.DataFrame(
821
+ [i.variables.iloc[-1].T for i in self.raw_results], index=self.raw_index
822
+ )
351
823
 
824
+ @property
825
+ def fluxes(self) -> pd.DataFrame:
826
+ """Return steady-state fluxes by scan."""
827
+ return pd.DataFrame(
828
+ [i.fluxes.iloc[-1].T for i in self.raw_results], index=self.raw_index
829
+ )
352
830
 
353
- @dataclass(kw_only=True, slots=True)
354
- class McSteadyStates:
355
- """Container for Monte Carlo steady states."""
831
+ @property
832
+ def combined(self) -> pd.DataFrame:
833
+ """Return steady-state args by scan."""
834
+ return self.get_args()
356
835
 
357
- variables: pd.DataFrame
358
- fluxes: pd.DataFrame
359
- parameters: pd.DataFrame
360
- mc_to_scan: pd.DataFrame
836
+ def get_args(
837
+ self,
838
+ *,
839
+ include_variables: bool = True,
840
+ include_parameters: bool = False,
841
+ include_derived_parameters: bool = False,
842
+ include_derived_variables: bool = True,
843
+ include_reactions: bool = True,
844
+ include_surrogate_variables: bool = False,
845
+ include_surrogate_fluxes: bool = False,
846
+ include_readouts: bool = False,
847
+ ) -> pd.DataFrame:
848
+ """Return steady-state args by scan."""
849
+ return pd.DataFrame(
850
+ [
851
+ i.get_args(
852
+ include_variables=include_variables,
853
+ include_parameters=include_parameters,
854
+ include_derived_parameters=include_derived_parameters,
855
+ include_derived_variables=include_derived_variables,
856
+ include_reactions=include_reactions,
857
+ include_surrogate_variables=include_surrogate_variables,
858
+ include_surrogate_fluxes=include_surrogate_fluxes,
859
+ include_readouts=include_readouts,
860
+ )
861
+ .iloc[-1]
862
+ .T
863
+ for i in self.raw_results
864
+ ],
865
+ index=self.raw_index,
866
+ )
361
867
 
362
868
  def __iter__(self) -> Iterator[pd.DataFrame]:
363
869
  """Iterate over the concentration and flux steady states."""
364
870
  return iter((self.variables, self.fluxes))
365
871
 
366
- @property
367
- def results(self) -> pd.DataFrame:
368
- """Return the steady states as a DataFrame."""
369
- return pd.concat((self.variables, self.fluxes), axis=1)
370
-
371
872
 
372
873
  @dataclass(kw_only=True, slots=True)
373
- class TimeCourseByPars:
374
- """Container for time courses by parameter."""
874
+ class TimeCourseScan:
875
+ """Container for time courses by scanned values."""
375
876
 
376
- variables: pd.DataFrame
377
- fluxes: pd.DataFrame
378
- parameters: pd.DataFrame
877
+ to_scan: pd.DataFrame
878
+ raw_results: dict[Hashable, Result]
379
879
 
380
- def __iter__(self) -> Iterator[pd.DataFrame]:
381
- """Iterate over the concentration and flux time courses."""
382
- return iter((self.variables, self.fluxes))
880
+ @property
881
+ def variables(self) -> pd.DataFrame:
882
+ """Return all args of the time courses."""
883
+ return pd.concat(
884
+ {k: i.variables for k, i in self.raw_results.items()}, names=["n", "time"]
885
+ )
383
886
 
384
887
  @property
385
- def results(self) -> pd.DataFrame:
888
+ def fluxes(self) -> pd.DataFrame:
889
+ """Return all args of the time courses."""
890
+ return pd.concat(
891
+ {k: i.fluxes for k, i in self.raw_results.items()}, names=["n", "time"]
892
+ )
893
+
894
+ @property
895
+ def combined(self) -> pd.DataFrame:
386
896
  """Return the time courses as a DataFrame."""
387
- return pd.concat((self.variables, self.fluxes), axis=1)
897
+ return self.get_args()
898
+
899
+ def get_args(
900
+ self,
901
+ *,
902
+ include_variables: bool = True,
903
+ include_parameters: bool = False,
904
+ include_derived_parameters: bool = False,
905
+ include_derived_variables: bool = True,
906
+ include_reactions: bool = True,
907
+ include_surrogate_variables: bool = False,
908
+ include_surrogate_fluxes: bool = False,
909
+ include_readouts: bool = False,
910
+ ) -> pd.DataFrame:
911
+ """Return all args of the time courses."""
912
+ return pd.concat(
913
+ {
914
+ k: i.get_args(
915
+ include_variables=include_variables,
916
+ include_parameters=include_parameters,
917
+ include_derived_parameters=include_derived_parameters,
918
+ include_derived_variables=include_derived_variables,
919
+ include_reactions=include_reactions,
920
+ include_surrogate_variables=include_surrogate_variables,
921
+ include_surrogate_fluxes=include_surrogate_fluxes,
922
+ include_readouts=include_readouts,
923
+ )
924
+ for k, i in self.raw_results.items()
925
+ },
926
+ names=["n", "time"],
927
+ )
388
928
 
389
929
  def get_by_name(self, name: str) -> pd.DataFrame:
390
930
  """Get time courses by name."""
391
- return self.results[name].unstack().T
931
+ return self.combined[name].unstack().T
392
932
 
393
933
  def get_agg_per_time(self, agg: str | Callable) -> pd.DataFrame:
394
934
  """Get aggregated time courses."""
395
- mean = cast(pd.DataFrame, self.results.unstack(level=1).agg(agg, axis=0))
935
+ mean = cast(pd.DataFrame, self.combined.unstack(level=1).agg(agg, axis=0))
396
936
  return cast(pd.DataFrame, mean.unstack().T)
397
937
 
398
938
  def get_agg_per_run(self, agg: str | Callable) -> pd.DataFrame:
399
939
  """Get aggregated time courses."""
400
- mean = cast(pd.DataFrame, self.results.unstack(level=0).agg(agg, axis=0))
940
+ mean = cast(pd.DataFrame, self.combined.unstack(level=0).agg(agg, axis=0))
401
941
  return cast(pd.DataFrame, mean.unstack().T)
402
942
 
943
+ def __iter__(self) -> Iterator[pd.DataFrame]:
944
+ """Iterate over the concentration and flux time courses."""
945
+ return iter((self.variables, self.fluxes))
946
+
403
947
 
404
948
  @dataclass(kw_only=True, slots=True)
405
- class ProtocolByPars:
406
- """Container for protocols by parameter."""
949
+ class ProtocolScan:
950
+ """Container for protocols by scanned values."""
407
951
 
408
- variables: pd.DataFrame
409
- fluxes: pd.DataFrame
410
- parameters: pd.DataFrame
952
+ to_scan: pd.DataFrame
411
953
  protocol: pd.DataFrame
954
+ raw_results: dict[Hashable, Result]
412
955
 
413
- def __iter__(self) -> Iterator[pd.DataFrame]:
414
- """Iterate over the concentration and flux protocols."""
415
- return iter((self.variables, self.fluxes))
956
+ @property
957
+ def variables(self) -> pd.DataFrame:
958
+ """Return all args of the time courses."""
959
+ return pd.concat(
960
+ {k: i.variables for k, i in self.raw_results.items()},
961
+ names=["n", "time"],
962
+ )
416
963
 
417
964
  @property
418
- def results(self) -> pd.DataFrame:
419
- """Return the protocols as a DataFrame."""
420
- return pd.concat((self.variables, self.fluxes), axis=1)
965
+ def fluxes(self) -> pd.DataFrame:
966
+ """Return all args of the time courses."""
967
+ return pd.concat(
968
+ {k: i.fluxes for k, i in self.raw_results.items()},
969
+ names=["n", "time"],
970
+ )
971
+
972
+ @property
973
+ def combined(self) -> pd.DataFrame:
974
+ """Return the time courses as a DataFrame."""
975
+ return self.get_args()
976
+
977
+ def get_args(
978
+ self,
979
+ *,
980
+ include_variables: bool = True,
981
+ include_parameters: bool = False,
982
+ include_derived_parameters: bool = False,
983
+ include_derived_variables: bool = True,
984
+ include_reactions: bool = True,
985
+ include_surrogate_variables: bool = False,
986
+ include_surrogate_fluxes: bool = False,
987
+ include_readouts: bool = False,
988
+ ) -> pd.DataFrame:
989
+ """Return all args of the time courses."""
990
+ return pd.concat(
991
+ {
992
+ k: i.get_args(
993
+ include_variables=include_variables,
994
+ include_parameters=include_parameters,
995
+ include_derived_parameters=include_derived_parameters,
996
+ include_derived_variables=include_derived_variables,
997
+ include_reactions=include_reactions,
998
+ include_surrogate_variables=include_surrogate_variables,
999
+ include_surrogate_fluxes=include_surrogate_fluxes,
1000
+ include_readouts=include_readouts,
1001
+ )
1002
+ for k, i in self.raw_results.items()
1003
+ },
1004
+ names=["n", "time"],
1005
+ )
421
1006
 
422
1007
  def get_by_name(self, name: str) -> pd.DataFrame:
423
1008
  """Get concentration or flux by name."""
424
- return self.results[name].unstack().T
1009
+ return self.combined[name].unstack().T
425
1010
 
426
1011
  def get_agg_per_time(self, agg: str | Callable) -> pd.DataFrame:
427
1012
  """Get aggregated concentration or flux."""
428
- mean = cast(pd.DataFrame, self.results.unstack(level=1).agg(agg, axis=0))
1013
+ mean = cast(pd.DataFrame, self.combined.unstack(level=1).agg(agg, axis=0))
429
1014
  return cast(pd.DataFrame, mean.unstack().T)
430
1015
 
431
1016
  def get_agg_per_run(self, agg: str | Callable) -> pd.DataFrame:
432
1017
  """Get aggregated concentration or flux."""
433
- mean = cast(pd.DataFrame, self.results.unstack(level=0).agg(agg, axis=0))
1018
+ mean = cast(pd.DataFrame, self.combined.unstack(level=0).agg(agg, axis=0))
434
1019
  return cast(pd.DataFrame, mean.unstack().T)
435
1020
 
1021
+ def __iter__(self) -> Iterator[pd.DataFrame]:
1022
+ """Iterate over the concentration and flux protocols."""
1023
+ return iter((self.variables, self.fluxes))
436
1024
 
437
- @dataclass(kw_only=True)
438
- class AbstractEstimator:
439
- """Abstract class for parameter estimation using neural networks."""
440
1025
 
441
- parameter_names: list[str]
1026
+ @dataclass(kw_only=True, slots=True)
1027
+ class McSteadyStates:
1028
+ """Container for Monte Carlo steady states."""
442
1029
 
443
- @abstractmethod
444
- def predict(self, features: pd.Series | pd.DataFrame) -> pd.DataFrame:
445
- """Predict the target values for the given features."""
1030
+ variables: pd.DataFrame
1031
+ fluxes: pd.DataFrame
1032
+ parameters: pd.DataFrame
1033
+ mc_to_scan: pd.DataFrame
1034
+
1035
+ @property
1036
+ def combined(self) -> pd.DataFrame:
1037
+ """Return the steady states as a DataFrame."""
1038
+ return pd.concat((self.variables, self.fluxes), axis=1)
1039
+
1040
+ def __iter__(self) -> Iterator[pd.DataFrame]:
1041
+ """Iterate over the concentration and flux steady states."""
1042
+ return iter((self.variables, self.fluxes))