mxlpy 0.20.0__py3-none-any.whl → 0.22.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
mxlpy/fit.py CHANGED
@@ -10,39 +10,89 @@ Functions:
10
10
 
11
11
  from __future__ import annotations
12
12
 
13
+ import logging
14
+ from copy import deepcopy
15
+ from dataclasses import dataclass
13
16
  from functools import partial
14
17
  from typing import TYPE_CHECKING, Protocol
15
18
 
16
19
  import numpy as np
17
20
  from scipy.optimize import minimize
18
21
 
19
- from mxlpy.integrators import DefaultIntegrator
22
+ from mxlpy import parallel
20
23
  from mxlpy.simulator import Simulator
21
24
  from mxlpy.types import Array, ArrayLike, Callable, IntegratorType, cast
22
25
 
23
26
  if TYPE_CHECKING:
24
27
  import pandas as pd
25
28
 
29
+ from mxlpy.carousel import Carousel
26
30
  from mxlpy.model import Model
27
31
 
32
+ LOGGER = logging.getLogger(__name__)
33
+
28
34
  __all__ = [
35
+ "Bounds",
36
+ "CarouselFit",
37
+ "FitResult",
29
38
  "InitialGuess",
39
+ "LOGGER",
30
40
  "LossFn",
41
+ "MinResult",
31
42
  "MinimizeFn",
32
43
  "ProtocolResidualFn",
33
44
  "ResidualFn",
34
45
  "SteadyStateResidualFn",
35
46
  "TimeSeriesResidualFn",
47
+ "carousel_protocol_time_course",
48
+ "carousel_steady_state",
49
+ "carousel_time_course",
50
+ "protocol_time_course",
36
51
  "rmse",
37
52
  "steady_state",
38
53
  "time_course",
39
- "time_course_over_protocol",
40
54
  ]
41
55
 
42
56
 
57
+ @dataclass
58
+ class MinResult:
59
+ """Result of a minimization operation."""
60
+
61
+ parameters: dict[str, float]
62
+ residual: float
63
+
64
+
65
+ @dataclass
66
+ class FitResult:
67
+ """Result of a fit operation."""
68
+
69
+ model: Model
70
+ best_pars: dict[str, float]
71
+ loss: float
72
+
73
+
74
+ @dataclass
75
+ class CarouselFit:
76
+ """Result of a carousel fit operation."""
77
+
78
+ fits: list[FitResult]
79
+
80
+ def get_best_fit(self) -> FitResult:
81
+ """Get the best fit from the carousel."""
82
+ return min(self.fits, key=lambda x: x.loss)
83
+
84
+
43
85
  type InitialGuess = dict[str, float]
44
86
  type ResidualFn = Callable[[Array], float]
45
- type MinimizeFn = Callable[[ResidualFn, InitialGuess], dict[str, float]]
87
+ type Bounds = dict[str, tuple[float | None, float | None]]
88
+ type MinimizeFn = Callable[
89
+ [
90
+ ResidualFn,
91
+ InitialGuess,
92
+ Bounds,
93
+ ],
94
+ MinResult | None,
95
+ ]
46
96
  type LossFn = Callable[
47
97
  [
48
98
  pd.DataFrame | pd.Series,
@@ -70,7 +120,7 @@ class SteadyStateResidualFn(Protocol):
70
120
  par_names: list[str],
71
121
  data: pd.Series,
72
122
  model: Model,
73
- y0: dict[str, float],
123
+ y0: dict[str, float] | None,
74
124
  integrator: IntegratorType,
75
125
  loss_fn: LossFn,
76
126
  ) -> float:
@@ -88,7 +138,7 @@ class TimeSeriesResidualFn(Protocol):
88
138
  par_names: list[str],
89
139
  data: pd.DataFrame,
90
140
  model: Model,
91
- y0: dict[str, float],
141
+ y0: dict[str, float] | None,
92
142
  integrator: IntegratorType,
93
143
  loss_fn: LossFn,
94
144
  ) -> float:
@@ -106,11 +156,10 @@ class ProtocolResidualFn(Protocol):
106
156
  par_names: list[str],
107
157
  data: pd.DataFrame,
108
158
  model: Model,
109
- y0: dict[str, float],
159
+ y0: dict[str, float] | None,
110
160
  integrator: IntegratorType,
111
161
  loss_fn: LossFn,
112
162
  protocol: pd.DataFrame,
113
- time_points_per_step: int = 10,
114
163
  ) -> float:
115
164
  """Calculate residual error between model time course and experimental data."""
116
165
  ...
@@ -119,22 +168,28 @@ class ProtocolResidualFn(Protocol):
119
168
  def _default_minimize_fn(
120
169
  residual_fn: ResidualFn,
121
170
  p0: dict[str, float],
122
- ) -> dict[str, float]:
171
+ bounds: Bounds,
172
+ ) -> MinResult | None:
123
173
  res = minimize(
124
174
  residual_fn,
125
175
  x0=list(p0.values()),
126
- bounds=[(1e-12, 1e6) for _ in range(len(p0))],
176
+ bounds=[bounds.get(name, (1e-6, 1e6)) for name in p0],
127
177
  method="L-BFGS-B",
128
178
  )
129
179
  if res.success:
130
- return dict(
131
- zip(
132
- p0,
133
- res.x,
134
- strict=True,
135
- )
180
+ return MinResult(
181
+ parameters=dict(
182
+ zip(
183
+ p0,
184
+ res.x,
185
+ strict=True,
186
+ ),
187
+ ),
188
+ residual=res.fun,
136
189
  )
137
- return dict(zip(p0, np.full(len(p0), np.nan, dtype=float), strict=True))
190
+
191
+ LOGGER.warning("Minimisation failed.")
192
+ return None
138
193
 
139
194
 
140
195
  def _steady_state_residual(
@@ -232,7 +287,7 @@ def _time_course_residual(
232
287
  )
233
288
 
234
289
 
235
- def _protocol_residual(
290
+ def _protocol_time_course_residual(
236
291
  par_values: ArrayLike,
237
292
  # This will be filled out by partial
238
293
  par_names: list[str],
@@ -242,7 +297,6 @@ def _protocol_residual(
242
297
  integrator: IntegratorType,
243
298
  loss_fn: LossFn,
244
299
  protocol: pd.DataFrame,
245
- time_points_per_step: int = 10,
246
300
  ) -> float:
247
301
  """Calculate residual error between model time course and experimental data.
248
302
 
@@ -267,9 +321,9 @@ def _protocol_residual(
267
321
  y0=y0,
268
322
  integrator=integrator,
269
323
  )
270
- .simulate_over_protocol(
324
+ .simulate_protocol_time_course(
271
325
  protocol=protocol,
272
- time_points_per_step=time_points_per_step,
326
+ time_points=data.index,
273
327
  )
274
328
  .get_result()
275
329
  )
@@ -283,16 +337,96 @@ def _protocol_residual(
283
337
  )
284
338
 
285
339
 
340
+ def _carousel_steady_state_worker(
341
+ model: Model,
342
+ p0: dict[str, float],
343
+ data: pd.Series,
344
+ y0: dict[str, float] | None,
345
+ integrator: IntegratorType | None,
346
+ loss_fn: LossFn,
347
+ minimize_fn: MinimizeFn,
348
+ residual_fn: SteadyStateResidualFn,
349
+ bounds: Bounds | None,
350
+ ) -> FitResult | None:
351
+ model_pars = model.get_parameter_values()
352
+
353
+ return steady_state(
354
+ model,
355
+ p0={k: v for k, v in p0.items() if k in model_pars},
356
+ y0=y0,
357
+ data=data,
358
+ minimize_fn=minimize_fn,
359
+ residual_fn=residual_fn,
360
+ integrator=integrator,
361
+ loss_fn=loss_fn,
362
+ bounds=bounds,
363
+ )
364
+
365
+
366
+ def _carousel_time_course_worker(
367
+ model: Model,
368
+ p0: dict[str, float],
369
+ data: pd.DataFrame,
370
+ y0: dict[str, float] | None,
371
+ integrator: IntegratorType | None,
372
+ loss_fn: LossFn,
373
+ minimize_fn: MinimizeFn,
374
+ residual_fn: TimeSeriesResidualFn,
375
+ bounds: Bounds | None,
376
+ ) -> FitResult | None:
377
+ model_pars = model.get_parameter_values()
378
+ return time_course(
379
+ model,
380
+ p0={k: v for k, v in p0.items() if k in model_pars},
381
+ y0=y0,
382
+ data=data,
383
+ minimize_fn=minimize_fn,
384
+ residual_fn=residual_fn,
385
+ integrator=integrator,
386
+ loss_fn=loss_fn,
387
+ bounds=bounds,
388
+ )
389
+
390
+
391
+ def _carousel_protocol_worker(
392
+ model: Model,
393
+ p0: dict[str, float],
394
+ data: pd.DataFrame,
395
+ protocol: pd.DataFrame,
396
+ y0: dict[str, float] | None,
397
+ integrator: IntegratorType | None,
398
+ loss_fn: LossFn,
399
+ minimize_fn: MinimizeFn,
400
+ residual_fn: ProtocolResidualFn,
401
+ bounds: Bounds | None,
402
+ ) -> FitResult | None:
403
+ model_pars = model.get_parameter_values()
404
+ return protocol_time_course(
405
+ model,
406
+ p0={k: v for k, v in p0.items() if k in model_pars},
407
+ y0=y0,
408
+ protocol=protocol,
409
+ data=data,
410
+ minimize_fn=minimize_fn,
411
+ residual_fn=residual_fn,
412
+ integrator=integrator,
413
+ loss_fn=loss_fn,
414
+ bounds=bounds,
415
+ )
416
+
417
+
286
418
  def steady_state(
287
419
  model: Model,
420
+ *,
288
421
  p0: dict[str, float],
289
422
  data: pd.Series,
290
423
  y0: dict[str, float] | None = None,
291
424
  minimize_fn: MinimizeFn = _default_minimize_fn,
292
425
  residual_fn: SteadyStateResidualFn = _steady_state_residual,
293
- integrator: IntegratorType = DefaultIntegrator,
426
+ integrator: IntegratorType | None = None,
294
427
  loss_fn: LossFn = rmse,
295
- ) -> dict[str, float]:
428
+ bounds: Bounds | None = None,
429
+ ) -> FitResult | None:
296
430
  """Fit model parameters to steady-state experimental data.
297
431
 
298
432
  Examples:
@@ -308,18 +442,19 @@ def steady_state(
308
442
  residual_fn: Function to calculate fitting error
309
443
  integrator: ODE integrator class
310
444
  loss_fn: Loss function to use for residual calculation
445
+ bounds: Mapping of bounds per parameter
311
446
 
312
447
  Returns:
313
448
  dict[str, float]: Fitted parameters as {parameter_name: fitted_value}
314
449
 
315
450
  Note:
316
- Uses L-BFGS-B optimization with bounds [1e-12, 1e6] for all parameters
451
+ Uses L-BFGS-B optimization with bounds [1e-6, 1e6] for all parameters
317
452
 
318
453
  """
319
454
  par_names = list(p0.keys())
320
455
 
321
456
  # Copy to restore
322
- p_orig = model.parameters
457
+ p_orig = model.get_parameter_values()
323
458
 
324
459
  fn = cast(
325
460
  ResidualFn,
@@ -333,23 +468,31 @@ def steady_state(
333
468
  loss_fn=loss_fn,
334
469
  ),
335
470
  )
336
- res = minimize_fn(fn, p0)
337
-
338
- # Restore
471
+ min_result = minimize_fn(fn, p0, {} if bounds is None else bounds)
472
+ # Restore original model
339
473
  model.update_parameters(p_orig)
340
- return res
474
+ if min_result is None:
475
+ return min_result
476
+
477
+ return FitResult(
478
+ model=deepcopy(model).update_parameters(min_result.parameters),
479
+ best_pars=min_result.parameters,
480
+ loss=min_result.residual,
481
+ )
341
482
 
342
483
 
343
484
  def time_course(
344
485
  model: Model,
486
+ *,
345
487
  p0: dict[str, float],
346
488
  data: pd.DataFrame,
347
489
  y0: dict[str, float] | None = None,
348
490
  minimize_fn: MinimizeFn = _default_minimize_fn,
349
491
  residual_fn: TimeSeriesResidualFn = _time_course_residual,
350
- integrator: IntegratorType = DefaultIntegrator,
492
+ integrator: IntegratorType | None = None,
351
493
  loss_fn: LossFn = rmse,
352
- ) -> dict[str, float]:
494
+ bounds: Bounds | None = None,
495
+ ) -> FitResult | None:
353
496
  """Fit model parameters to time course of experimental data.
354
497
 
355
498
  Examples:
@@ -365,16 +508,17 @@ def time_course(
365
508
  residual_fn: Function to calculate fitting error
366
509
  integrator: ODE integrator class
367
510
  loss_fn: Loss function to use for residual calculation
511
+ bounds: Mapping of bounds per parameter
368
512
 
369
513
  Returns:
370
514
  dict[str, float]: Fitted parameters as {parameter_name: fitted_value}
371
515
 
372
516
  Note:
373
- Uses L-BFGS-B optimization with bounds [1e-12, 1e6] for all parameters
517
+ Uses L-BFGS-B optimization with bounds [1e-6, 1e6] for all parameters
374
518
 
375
519
  """
376
520
  par_names = list(p0.keys())
377
- p_orig = model.parameters
521
+ p_orig = model.get_parameter_values()
378
522
 
379
523
  fn = cast(
380
524
  ResidualFn,
@@ -388,23 +532,33 @@ def time_course(
388
532
  loss_fn=loss_fn,
389
533
  ),
390
534
  )
391
- res = minimize_fn(fn, p0)
535
+
536
+ min_result = minimize_fn(fn, p0, {} if bounds is None else bounds)
537
+ # Restore original model
392
538
  model.update_parameters(p_orig)
393
- return res
539
+ if min_result is None:
540
+ return min_result
394
541
 
542
+ return FitResult(
543
+ model=deepcopy(model).update_parameters(min_result.parameters),
544
+ best_pars=min_result.parameters,
545
+ loss=min_result.residual,
546
+ )
395
547
 
396
- def time_course_over_protocol(
548
+
549
+ def protocol_time_course(
397
550
  model: Model,
551
+ *,
398
552
  p0: dict[str, float],
399
553
  data: pd.DataFrame,
400
554
  protocol: pd.DataFrame,
401
555
  y0: dict[str, float] | None = None,
402
556
  minimize_fn: MinimizeFn = _default_minimize_fn,
403
- residual_fn: ProtocolResidualFn = _protocol_residual,
404
- integrator: IntegratorType = DefaultIntegrator,
557
+ residual_fn: ProtocolResidualFn = _protocol_time_course_residual,
558
+ integrator: IntegratorType | None = None,
405
559
  loss_fn: LossFn = rmse,
406
- time_points_per_step: int = 10,
407
- ) -> dict[str, float]:
560
+ bounds: Bounds | None = None,
561
+ ) -> FitResult | None:
408
562
  """Fit model parameters to time course of experimental data.
409
563
 
410
564
  Examples:
@@ -422,16 +576,17 @@ def time_course_over_protocol(
422
576
  integrator: ODE integrator class
423
577
  loss_fn: Loss function to use for residual calculation
424
578
  time_points_per_step: Number of time points per step in the protocol
579
+ bounds: Mapping of bounds per parameter
425
580
 
426
581
  Returns:
427
582
  dict[str, float]: Fitted parameters as {parameter_name: fitted_value}
428
583
 
429
584
  Note:
430
- Uses L-BFGS-B optimization with bounds [1e-12, 1e6] for all parameters
585
+ Uses L-BFGS-B optimization with bounds [1e-6, 1e6] for all parameters
431
586
 
432
587
  """
433
588
  par_names = list(p0.keys())
434
- p_orig = model.parameters
589
+ p_orig = model.get_parameter_values()
435
590
 
436
591
  fn = cast(
437
592
  ResidualFn,
@@ -444,9 +599,196 @@ def time_course_over_protocol(
444
599
  integrator=integrator,
445
600
  loss_fn=loss_fn,
446
601
  protocol=protocol,
447
- time_points_per_step=time_points_per_step,
448
602
  ),
449
603
  )
450
- res = minimize_fn(fn, p0)
604
+
605
+ min_result = minimize_fn(fn, p0, {} if bounds is None else bounds)
606
+ # Restore original model
451
607
  model.update_parameters(p_orig)
452
- return res
608
+ if min_result is None:
609
+ return min_result
610
+
611
+ return FitResult(
612
+ model=deepcopy(model).update_parameters(min_result.parameters),
613
+ best_pars=min_result.parameters,
614
+ loss=min_result.residual,
615
+ )
616
+
617
+
618
+ def carousel_steady_state(
619
+ carousel: Carousel,
620
+ *,
621
+ p0: dict[str, float],
622
+ data: pd.Series,
623
+ y0: dict[str, float] | None = None,
624
+ minimize_fn: MinimizeFn = _default_minimize_fn,
625
+ residual_fn: SteadyStateResidualFn = _steady_state_residual,
626
+ integrator: IntegratorType | None = None,
627
+ loss_fn: LossFn = rmse,
628
+ bounds: Bounds | None = None,
629
+ ) -> CarouselFit:
630
+ """Fit model parameters to steady-state experimental data over a carousel.
631
+
632
+ Examples:
633
+ >>> carousel_steady_state(carousel, p0=p0, data=data)
634
+
635
+ Args:
636
+ carousel: Model carousel to fit
637
+ p0: Initial parameter guesses as {parameter_name: value}
638
+ data: Experimental time course data
639
+ protocol: Experimental protocol
640
+ y0: Initial conditions as {species_name: value}
641
+ minimize_fn: Function to minimize fitting error
642
+ residual_fn: Function to calculate fitting error
643
+ integrator: ODE integrator class
644
+ loss_fn: Loss function to use for residual calculation
645
+ time_points_per_step: Number of time points per step in the protocol
646
+ bounds: Mapping of bounds per parameter
647
+
648
+ Returns:
649
+ dict[str, float]: Fitted parameters as {parameter_name: fitted_value}
650
+
651
+ Note:
652
+ Uses L-BFGS-B optimization with bounds [1e-6, 1e6] for all parameters
653
+
654
+ """
655
+ return CarouselFit(
656
+ [
657
+ fit
658
+ for i in parallel.parallelise(
659
+ partial(
660
+ _carousel_steady_state_worker,
661
+ p0=p0,
662
+ data=data,
663
+ y0=y0,
664
+ integrator=integrator,
665
+ loss_fn=loss_fn,
666
+ minimize_fn=minimize_fn,
667
+ residual_fn=residual_fn,
668
+ bounds=bounds,
669
+ ),
670
+ inputs=list(enumerate(carousel.variants)),
671
+ )
672
+ if (fit := i[1]) is not None
673
+ ]
674
+ )
675
+
676
+
677
+ def carousel_time_course(
678
+ carousel: Carousel,
679
+ *,
680
+ p0: dict[str, float],
681
+ data: pd.DataFrame,
682
+ y0: dict[str, float] | None = None,
683
+ minimize_fn: MinimizeFn = _default_minimize_fn,
684
+ residual_fn: TimeSeriesResidualFn = _time_course_residual,
685
+ integrator: IntegratorType | None = None,
686
+ loss_fn: LossFn = rmse,
687
+ bounds: Bounds | None = None,
688
+ ) -> CarouselFit:
689
+ """Fit model parameters to time course of experimental data over a carousel.
690
+
691
+ Examples:
692
+ >>> carousel_steady_state(carousel, p0=p0, data=data)
693
+
694
+ Args:
695
+ carousel: Model carousel to fit
696
+ p0: Initial parameter guesses as {parameter_name: value}
697
+ data: Experimental time course data
698
+ protocol: Experimental protocol
699
+ y0: Initial conditions as {species_name: value}
700
+ minimize_fn: Function to minimize fitting error
701
+ residual_fn: Function to calculate fitting error
702
+ integrator: ODE integrator class
703
+ loss_fn: Loss function to use for residual calculation
704
+ time_points_per_step: Number of time points per step in the protocol
705
+ bounds: Mapping of bounds per parameter
706
+
707
+ Returns:
708
+ dict[str, float]: Fitted parameters as {parameter_name: fitted_value}
709
+
710
+ Note:
711
+ Uses L-BFGS-B optimization with bounds [1e-6, 1e6] for all parameters
712
+
713
+ """
714
+ return CarouselFit(
715
+ [
716
+ fit
717
+ for i in parallel.parallelise(
718
+ partial(
719
+ _carousel_time_course_worker,
720
+ p0=p0,
721
+ data=data,
722
+ y0=y0,
723
+ integrator=integrator,
724
+ loss_fn=loss_fn,
725
+ minimize_fn=minimize_fn,
726
+ residual_fn=residual_fn,
727
+ bounds=bounds,
728
+ ),
729
+ inputs=list(enumerate(carousel.variants)),
730
+ )
731
+ if (fit := i[1]) is not None
732
+ ]
733
+ )
734
+
735
+
736
+ def carousel_protocol_time_course(
737
+ carousel: Carousel,
738
+ *,
739
+ p0: dict[str, float],
740
+ data: pd.DataFrame,
741
+ protocol: pd.DataFrame,
742
+ y0: dict[str, float] | None = None,
743
+ minimize_fn: MinimizeFn = _default_minimize_fn,
744
+ residual_fn: ProtocolResidualFn = _protocol_time_course_residual,
745
+ integrator: IntegratorType | None = None,
746
+ loss_fn: LossFn = rmse,
747
+ bounds: Bounds | None = None,
748
+ ) -> CarouselFit:
749
+ """Fit model parameters to time course of experimental data over a protocol.
750
+
751
+ Examples:
752
+ >>> carousel_steady_state(carousel, p0=p0, data=data)
753
+
754
+ Args:
755
+ carousel: Model carousel to fit
756
+ p0: Initial parameter guesses as {parameter_name: value}
757
+ data: Experimental time course data
758
+ protocol: Experimental protocol
759
+ y0: Initial conditions as {species_name: value}
760
+ minimize_fn: Function to minimize fitting error
761
+ residual_fn: Function to calculate fitting error
762
+ integrator: ODE integrator class
763
+ loss_fn: Loss function to use for residual calculation
764
+ time_points_per_step: Number of time points per step in the protocol
765
+ bounds: Mapping of bounds per parameter
766
+
767
+ Returns:
768
+ dict[str, float]: Fitted parameters as {parameter_name: fitted_value}
769
+
770
+ Note:
771
+ Uses L-BFGS-B optimization with bounds [1e-6, 1e6] for all parameters
772
+
773
+ """
774
+ return CarouselFit(
775
+ [
776
+ fit
777
+ for i in parallel.parallelise(
778
+ partial(
779
+ _carousel_protocol_worker,
780
+ p0=p0,
781
+ data=data,
782
+ protocol=protocol,
783
+ y0=y0,
784
+ integrator=integrator,
785
+ loss_fn=loss_fn,
786
+ minimize_fn=minimize_fn,
787
+ residual_fn=residual_fn,
788
+ bounds=bounds,
789
+ ),
790
+ inputs=list(enumerate(carousel.variants)),
791
+ )
792
+ if (fit := i[1]) is not None
793
+ ]
794
+ )
mxlpy/identify.py CHANGED
@@ -28,16 +28,15 @@ def _mc_fit_time_course_worker(
28
28
  data: pd.DataFrame,
29
29
  loss_fn: fit.LossFn,
30
30
  ) -> float:
31
- p_fit = fit.time_course(model=model, p0=p0.to_dict(), data=data)
32
- return fit._time_course_residual( # noqa: SLF001
33
- par_values=list(p_fit.values()),
34
- par_names=list(p_fit.keys()),
35
- data=data,
31
+ fit_result = fit.time_course(
36
32
  model=model,
37
- y0=None,
38
- integrator=fit.DefaultIntegrator,
33
+ p0=p0.to_dict(),
34
+ data=data,
39
35
  loss_fn=loss_fn,
40
36
  )
37
+ if fit_result is None:
38
+ return np.inf
39
+ return fit_result.loss
41
40
 
42
41
 
43
42
  def profile_likelihood(
@@ -60,21 +59,26 @@ def profile_likelihood(
60
59
 
61
60
  """
62
61
  parameter_distributions = sample(
63
- {k: LogNormal(np.log(v), sigma=1) for k, v in model.parameters.items()},
62
+ {
63
+ k: LogNormal(np.log(v), sigma=1)
64
+ for k, v in model.get_parameter_values().items()
65
+ },
64
66
  n=n_random,
65
67
  )
66
68
 
67
69
  res = {}
68
70
  for value in tqdm(parameter_values, desc=parameter_name):
69
71
  model.update_parameter(parameter_name, value)
70
- res[value] = parallelise(
71
- partial(
72
- _mc_fit_time_course_worker, model=model, data=data, loss_fn=loss_fn
73
- ),
74
- inputs=list(
75
- parameter_distributions.drop(columns=parameter_name).iterrows()
76
- ),
77
- disable_tqdm=True,
72
+ res[value] = dict(
73
+ parallelise(
74
+ partial(
75
+ _mc_fit_time_course_worker, model=model, data=data, loss_fn=loss_fn
76
+ ),
77
+ inputs=list(
78
+ parameter_distributions.drop(columns=parameter_name).iterrows()
79
+ ),
80
+ disable_tqdm=True,
81
+ )
78
82
  )
79
83
  errors = pd.DataFrame(res, dtype=float).T.abs().mean(axis=1)
80
84
  errors.index.name = "fitting error"
@@ -98,6 +98,9 @@ class Scipy:
98
98
  tuple[ArrayLike, ArrayLike]: Tuple containing the time points and the integrated values.
99
99
 
100
100
  """
101
+ if time_points[0] != self.t0:
102
+ time_points = np.insert(time_points, 0, self.t0)
103
+
101
104
  res = spi.solve_ivp(
102
105
  fun=self.rhs,
103
106
  y0=self.y0,