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/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,21 +40,24 @@ __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
49
  "Parameter",
40
- "ProtocolByPars",
50
+ "ProtocolScan",
41
51
  "RateFn",
42
52
  "Reaction",
43
53
  "Readout",
44
54
  "ResponseCoefficients",
45
55
  "ResponseCoefficientsByPars",
56
+ "Result",
46
57
  "RetType",
47
- "SteadyStates",
48
- "TimeCourseByPars",
58
+ "Rhs",
59
+ "SteadyStateScan",
60
+ "TimeCourseScan",
49
61
  "Variable",
50
62
  "unwrap",
51
63
  "unwrap2",
@@ -54,7 +66,13 @@ __all__ = [
54
66
  type RateFn = Callable[..., float]
55
67
  type Array = NDArray[np.floating[Any]]
56
68
  type ArrayLike = NDArray[np.floating[Any]] | pd.Index | list[float]
57
-
69
+ type Rhs = Callable[
70
+ [
71
+ float, # t
72
+ Iterable[float], # y
73
+ ],
74
+ tuple[float, ...],
75
+ ]
58
76
 
59
77
  Param = ParamSpec("Param")
60
78
  RetType = TypeVar("RetType")
@@ -110,8 +128,8 @@ class IntegratorProtocol(Protocol):
110
128
 
111
129
  def __init__(
112
130
  self,
113
- rhs: Callable,
114
- y0: ArrayLike,
131
+ rhs: Rhs,
132
+ y0: tuple[float, ...],
115
133
  jacobian: Callable | None = None,
116
134
  ) -> None:
117
135
  """Initialise the integrator."""
@@ -147,24 +165,29 @@ class IntegratorProtocol(Protocol):
147
165
 
148
166
 
149
167
  type IntegratorType = Callable[
150
- [Callable, ArrayLike, Callable | None], IntegratorProtocol
168
+ [
169
+ Rhs, # model
170
+ tuple[float, ...], # y0
171
+ Callable | None, # jacobian
172
+ ],
173
+ IntegratorProtocol,
151
174
  ]
152
175
 
153
176
 
154
177
  @dataclass
155
- class Parameter:
156
- """Container for parameter meta information."""
178
+ class Variable:
179
+ """Container for variable meta information."""
157
180
 
158
- value: float
181
+ initial_value: float | InitialAssignment
159
182
  unit: sympy.Expr | None = None
160
183
  source: str | None = None
161
184
 
162
185
 
163
186
  @dataclass
164
- class Variable:
165
- """Container for variable meta information."""
187
+ class Parameter:
188
+ """Container for parameter meta information."""
166
189
 
167
- initial_value: float | Derived
190
+ value: float | InitialAssignment
168
191
  unit: sympy.Expr | None = None
169
192
  source: str | None = None
170
193
 
@@ -200,6 +223,37 @@ class Derived:
200
223
  args[name] = cast(float, self.fn(*(args[arg] for arg in self.args)))
201
224
 
202
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
233
+
234
+ def calculate(self, args: dict[str, Any]) -> float:
235
+ """Calculate the derived value.
236
+
237
+ Args:
238
+ args: Dictionary of args variables.
239
+
240
+ Returns:
241
+ The calculated derived value.
242
+
243
+ """
244
+ return cast(float, self.fn(*(args[arg] for arg in self.args)))
245
+
246
+ def calculate_inpl(self, name: str, args: dict[str, Any]) -> None:
247
+ """Calculate the derived value in place.
248
+
249
+ Args:
250
+ name: Name of the derived variable.
251
+ args: Dictionary of args variables.
252
+
253
+ """
254
+ args[name] = cast(float, self.fn(*(args[arg] for arg in self.args)))
255
+
256
+
203
257
  @dataclass(kw_only=True, slots=True)
204
258
  class Readout:
205
259
  """Container for a readout."""
@@ -322,22 +376,417 @@ class MockSurrogate(AbstractSurrogate):
322
376
  ) # type: ignore
323
377
 
324
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
+
325
424
  @dataclass(kw_only=True, slots=True)
326
- class ResponseCoefficients:
327
- """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
+ )
328
450
 
329
- variables: pd.DataFrame
330
- 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
+ )
331
768
 
332
769
  def __iter__(self) -> Iterator[pd.DataFrame]:
333
770
  """Iterate over the concentration and flux response coefficients."""
334
771
  return iter((self.variables, self.fluxes))
335
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
+
336
781
  @property
337
- def results(self) -> pd.DataFrame:
782
+ def combined(self) -> pd.DataFrame:
338
783
  """Return the response coefficients as a DataFrame."""
339
784
  return pd.concat((self.variables, self.fluxes), axis=1)
340
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
+
341
790
 
342
791
  @dataclass(kw_only=True, slots=True)
343
792
  class ResponseCoefficientsByPars:
@@ -347,124 +796,247 @@ class ResponseCoefficientsByPars:
347
796
  fluxes: pd.DataFrame
348
797
  parameters: pd.DataFrame
349
798
 
350
- def __iter__(self) -> Iterator[pd.DataFrame]:
351
- """Iterate over the concentration and flux response coefficients."""
352
- return iter((self.variables, self.fluxes))
353
-
354
799
  @property
355
- def results(self) -> pd.DataFrame:
800
+ def combined(self) -> pd.DataFrame:
356
801
  """Return the response coefficients as a DataFrame."""
357
802
  return pd.concat((self.variables, self.fluxes), axis=1)
358
803
 
804
+ def __iter__(self) -> Iterator[pd.DataFrame]:
805
+ """Iterate over the concentration and flux response coefficients."""
806
+ return iter((self.variables, self.fluxes))
807
+
359
808
 
360
809
  @dataclass(kw_only=True, slots=True)
361
- class SteadyStates:
362
- """Container for steady states."""
363
-
364
- variables: pd.DataFrame
365
- fluxes: pd.DataFrame
366
- parameters: pd.DataFrame
810
+ class SteadyStateScan:
811
+ """Container for steady states by scanned values."""
367
812
 
368
- def __iter__(self) -> Iterator[pd.DataFrame]:
369
- """Iterate over the concentration and flux steady states."""
370
- return iter((self.variables, self.fluxes))
813
+ to_scan: pd.DataFrame
814
+ raw_index: pd.Index | pd.MultiIndex
815
+ raw_results: list[Result]
371
816
 
372
817
  @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)
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
+ )
376
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
+ )
377
830
 
378
- @dataclass(kw_only=True, slots=True)
379
- class McSteadyStates:
380
- """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()
381
835
 
382
- variables: pd.DataFrame
383
- fluxes: pd.DataFrame
384
- parameters: pd.DataFrame
385
- 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
+ )
386
867
 
387
868
  def __iter__(self) -> Iterator[pd.DataFrame]:
388
869
  """Iterate over the concentration and flux steady states."""
389
870
  return iter((self.variables, self.fluxes))
390
871
 
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
872
 
397
873
  @dataclass(kw_only=True, slots=True)
398
- class TimeCourseByPars:
399
- """Container for time courses by parameter."""
874
+ class TimeCourseScan:
875
+ """Container for time courses by scanned values."""
400
876
 
401
- variables: pd.DataFrame
402
- fluxes: pd.DataFrame
403
- parameters: pd.DataFrame
877
+ to_scan: pd.DataFrame
878
+ raw_results: dict[Hashable, Result]
404
879
 
405
- def __iter__(self) -> Iterator[pd.DataFrame]:
406
- """Iterate over the concentration and flux time courses."""
407
- 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
+ )
886
+
887
+ @property
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
+ )
408
893
 
409
894
  @property
410
- def results(self) -> pd.DataFrame:
895
+ def combined(self) -> pd.DataFrame:
411
896
  """Return the time courses as a DataFrame."""
412
- 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
+ )
413
928
 
414
929
  def get_by_name(self, name: str) -> pd.DataFrame:
415
930
  """Get time courses by name."""
416
- return self.results[name].unstack().T
931
+ return self.combined[name].unstack().T
417
932
 
418
933
  def get_agg_per_time(self, agg: str | Callable) -> pd.DataFrame:
419
934
  """Get aggregated time courses."""
420
- 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))
421
936
  return cast(pd.DataFrame, mean.unstack().T)
422
937
 
423
938
  def get_agg_per_run(self, agg: str | Callable) -> pd.DataFrame:
424
939
  """Get aggregated time courses."""
425
- 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))
426
941
  return cast(pd.DataFrame, mean.unstack().T)
427
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
+
428
947
 
429
948
  @dataclass(kw_only=True, slots=True)
430
- class ProtocolByPars:
431
- """Container for protocols by parameter."""
949
+ class ProtocolScan:
950
+ """Container for protocols by scanned values."""
432
951
 
433
- variables: pd.DataFrame
434
- fluxes: pd.DataFrame
435
- parameters: pd.DataFrame
952
+ to_scan: pd.DataFrame
436
953
  protocol: pd.DataFrame
954
+ raw_results: dict[Hashable, Result]
437
955
 
438
- def __iter__(self) -> Iterator[pd.DataFrame]:
439
- """Iterate over the concentration and flux protocols."""
440
- 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
+ )
441
963
 
442
964
  @property
443
- def results(self) -> pd.DataFrame:
444
- """Return the protocols as a DataFrame."""
445
- 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
+ )
446
1006
 
447
1007
  def get_by_name(self, name: str) -> pd.DataFrame:
448
1008
  """Get concentration or flux by name."""
449
- return self.results[name].unstack().T
1009
+ return self.combined[name].unstack().T
450
1010
 
451
1011
  def get_agg_per_time(self, agg: str | Callable) -> pd.DataFrame:
452
1012
  """Get aggregated concentration or flux."""
453
- 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))
454
1014
  return cast(pd.DataFrame, mean.unstack().T)
455
1015
 
456
1016
  def get_agg_per_run(self, agg: str | Callable) -> pd.DataFrame:
457
1017
  """Get aggregated concentration or flux."""
458
- 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))
459
1019
  return cast(pd.DataFrame, mean.unstack().T)
460
1020
 
1021
+ def __iter__(self) -> Iterator[pd.DataFrame]:
1022
+ """Iterate over the concentration and flux protocols."""
1023
+ return iter((self.variables, self.fluxes))
461
1024
 
462
- @dataclass(kw_only=True)
463
- class AbstractEstimator:
464
- """Abstract class for parameter estimation using neural networks."""
465
1025
 
466
- parameter_names: list[str]
1026
+ @dataclass(kw_only=True, slots=True)
1027
+ class McSteadyStates:
1028
+ """Container for Monte Carlo steady states."""
467
1029
 
468
- @abstractmethod
469
- def predict(self, features: pd.Series | pd.DataFrame) -> pd.DataFrame:
470
- """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))