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