mxlpy 0.21.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/__init__.py +5 -1
- mxlpy/compare.py +2 -6
- mxlpy/experimental/diff.py +1 -1
- mxlpy/fit.py +195 -99
- mxlpy/identify.py +14 -9
- mxlpy/integrators/int_scipy.py +3 -0
- mxlpy/label_map.py +5 -5
- mxlpy/linear_label_map.py +3 -1
- mxlpy/mc.py +3 -0
- mxlpy/mca.py +2 -2
- mxlpy/meta/__init__.py +5 -3
- mxlpy/meta/codegen_latex.py +44 -30
- mxlpy/meta/codegen_model.py +174 -0
- mxlpy/meta/{codegen_modebase.py → codegen_mxlpy.py} +35 -29
- mxlpy/meta/source_tools.py +408 -167
- mxlpy/meta/sympy_tools.py +117 -0
- mxlpy/model.py +528 -224
- mxlpy/report.py +153 -90
- mxlpy/sbml/_export.py +11 -8
- mxlpy/sbml/_import.py +7 -7
- mxlpy/scan.py +1 -1
- mxlpy/simulator.py +238 -57
- mxlpy/symbolic/symbolic_model.py +29 -17
- mxlpy/types.py +45 -20
- mxlpy/units.py +128 -0
- {mxlpy-0.21.0.dist-info → mxlpy-0.22.0.dist-info}/METADATA +1 -1
- {mxlpy-0.21.0.dist-info → mxlpy-0.22.0.dist-info}/RECORD +29 -27
- mxlpy/meta/codegen_py.py +0 -115
- {mxlpy-0.21.0.dist-info → mxlpy-0.22.0.dist-info}/WHEEL +0 -0
- {mxlpy-0.21.0.dist-info → mxlpy-0.22.0.dist-info}/licenses/LICENSE +0 -0
mxlpy/__init__.py
CHANGED
@@ -52,6 +52,7 @@ from . import (
|
|
52
52
|
plot,
|
53
53
|
report,
|
54
54
|
sbml,
|
55
|
+
units,
|
55
56
|
)
|
56
57
|
from .integrators import DefaultIntegrator, Scipy
|
57
58
|
from .label_map import LabelMapper
|
@@ -61,7 +62,7 @@ from .model import Model
|
|
61
62
|
from .scan import steady_state, time_course, time_course_over_protocol
|
62
63
|
from .simulator import Simulator
|
63
64
|
from .symbolic import SymbolicModel, to_symbolic_model
|
64
|
-
from .types import Derived, IntegratorProtocol, unwrap
|
65
|
+
from .types import Derived, IntegratorProtocol, Parameter, Variable, unwrap
|
65
66
|
|
66
67
|
with contextlib.suppress(ImportError):
|
67
68
|
from .integrators import Assimulo
|
@@ -87,9 +88,11 @@ __all__ = [
|
|
87
88
|
"LabelMapper",
|
88
89
|
"LinearLabelMapper",
|
89
90
|
"Model",
|
91
|
+
"Parameter",
|
90
92
|
"Scipy",
|
91
93
|
"Simulator",
|
92
94
|
"SymbolicModel",
|
95
|
+
"Variable",
|
93
96
|
"cartesian_product",
|
94
97
|
"compare",
|
95
98
|
"distributions",
|
@@ -110,6 +113,7 @@ __all__ = [
|
|
110
113
|
"time_course",
|
111
114
|
"time_course_over_protocol",
|
112
115
|
"to_symbolic_model",
|
116
|
+
"units",
|
113
117
|
"unwrap",
|
114
118
|
]
|
115
119
|
|
mxlpy/compare.py
CHANGED
@@ -230,11 +230,7 @@ def protocol_time_courses(
|
|
230
230
|
) -> ProtocolComparison:
|
231
231
|
"""Compare the time courses of two models."""
|
232
232
|
return ProtocolComparison(
|
233
|
-
res1=unwrap(
|
234
|
-
|
235
|
-
),
|
236
|
-
res2=unwrap(
|
237
|
-
Simulator(m2).simulate_over_protocol(protocol=protocol).get_result()
|
238
|
-
),
|
233
|
+
res1=unwrap(Simulator(m1).simulate_protocol(protocol=protocol).get_result()),
|
234
|
+
res2=unwrap(Simulator(m2).simulate_protocol(protocol=protocol).get_result()),
|
239
235
|
protocol=protocol,
|
240
236
|
)
|
mxlpy/experimental/diff.py
CHANGED
@@ -187,7 +187,7 @@ def model_diff(m1: Model, m2: Model) -> ModelDiff:
|
|
187
187
|
if (v2 := m2._parameters.get(k)) is None: # noqa: SLF001
|
188
188
|
diff.missing_parameters.add(k)
|
189
189
|
elif v1 != v2:
|
190
|
-
diff.different_parameters[k] = (v1, v2)
|
190
|
+
diff.different_parameters[k] = (v1, v2) # type: ignore
|
191
191
|
|
192
192
|
for k, v1 in m1._variables.items(): # noqa: SLF001
|
193
193
|
if (v2 := m2._variables.get(k)) is None: # noqa: SLF001
|
mxlpy/fit.py
CHANGED
@@ -32,6 +32,7 @@ if TYPE_CHECKING:
|
|
32
32
|
LOGGER = logging.getLogger(__name__)
|
33
33
|
|
34
34
|
__all__ = [
|
35
|
+
"Bounds",
|
35
36
|
"CarouselFit",
|
36
37
|
"FitResult",
|
37
38
|
"InitialGuess",
|
@@ -43,13 +44,13 @@ __all__ = [
|
|
43
44
|
"ResidualFn",
|
44
45
|
"SteadyStateResidualFn",
|
45
46
|
"TimeSeriesResidualFn",
|
47
|
+
"carousel_protocol_time_course",
|
46
48
|
"carousel_steady_state",
|
47
49
|
"carousel_time_course",
|
48
|
-
"
|
50
|
+
"protocol_time_course",
|
49
51
|
"rmse",
|
50
52
|
"steady_state",
|
51
53
|
"time_course",
|
52
|
-
"time_course_over_protocol",
|
53
54
|
]
|
54
55
|
|
55
56
|
|
@@ -83,7 +84,15 @@ class CarouselFit:
|
|
83
84
|
|
84
85
|
type InitialGuess = dict[str, float]
|
85
86
|
type ResidualFn = Callable[[Array], float]
|
86
|
-
type
|
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
|
+
]
|
87
96
|
type LossFn = Callable[
|
88
97
|
[
|
89
98
|
pd.DataFrame | pd.Series,
|
@@ -151,7 +160,6 @@ class ProtocolResidualFn(Protocol):
|
|
151
160
|
integrator: IntegratorType,
|
152
161
|
loss_fn: LossFn,
|
153
162
|
protocol: pd.DataFrame,
|
154
|
-
time_points_per_step: int = 10,
|
155
163
|
) -> float:
|
156
164
|
"""Calculate residual error between model time course and experimental data."""
|
157
165
|
...
|
@@ -160,11 +168,12 @@ class ProtocolResidualFn(Protocol):
|
|
160
168
|
def _default_minimize_fn(
|
161
169
|
residual_fn: ResidualFn,
|
162
170
|
p0: dict[str, float],
|
171
|
+
bounds: Bounds,
|
163
172
|
) -> MinResult | None:
|
164
173
|
res = minimize(
|
165
174
|
residual_fn,
|
166
175
|
x0=list(p0.values()),
|
167
|
-
bounds=[(
|
176
|
+
bounds=[bounds.get(name, (1e-6, 1e6)) for name in p0],
|
168
177
|
method="L-BFGS-B",
|
169
178
|
)
|
170
179
|
if res.success:
|
@@ -278,7 +287,7 @@ def _time_course_residual(
|
|
278
287
|
)
|
279
288
|
|
280
289
|
|
281
|
-
def
|
290
|
+
def _protocol_time_course_residual(
|
282
291
|
par_values: ArrayLike,
|
283
292
|
# This will be filled out by partial
|
284
293
|
par_names: list[str],
|
@@ -288,7 +297,6 @@ def _protocol_residual(
|
|
288
297
|
integrator: IntegratorType,
|
289
298
|
loss_fn: LossFn,
|
290
299
|
protocol: pd.DataFrame,
|
291
|
-
time_points_per_step: int = 10,
|
292
300
|
) -> float:
|
293
301
|
"""Calculate residual error between model time course and experimental data.
|
294
302
|
|
@@ -313,9 +321,9 @@ def _protocol_residual(
|
|
313
321
|
y0=y0,
|
314
322
|
integrator=integrator,
|
315
323
|
)
|
316
|
-
.
|
324
|
+
.simulate_protocol_time_course(
|
317
325
|
protocol=protocol,
|
318
|
-
|
326
|
+
time_points=data.index,
|
319
327
|
)
|
320
328
|
.get_result()
|
321
329
|
)
|
@@ -329,6 +337,84 @@ def _protocol_residual(
|
|
329
337
|
)
|
330
338
|
|
331
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
|
+
|
332
418
|
def steady_state(
|
333
419
|
model: Model,
|
334
420
|
*,
|
@@ -339,6 +425,7 @@ def steady_state(
|
|
339
425
|
residual_fn: SteadyStateResidualFn = _steady_state_residual,
|
340
426
|
integrator: IntegratorType | None = None,
|
341
427
|
loss_fn: LossFn = rmse,
|
428
|
+
bounds: Bounds | None = None,
|
342
429
|
) -> FitResult | None:
|
343
430
|
"""Fit model parameters to steady-state experimental data.
|
344
431
|
|
@@ -355,18 +442,19 @@ def steady_state(
|
|
355
442
|
residual_fn: Function to calculate fitting error
|
356
443
|
integrator: ODE integrator class
|
357
444
|
loss_fn: Loss function to use for residual calculation
|
445
|
+
bounds: Mapping of bounds per parameter
|
358
446
|
|
359
447
|
Returns:
|
360
448
|
dict[str, float]: Fitted parameters as {parameter_name: fitted_value}
|
361
449
|
|
362
450
|
Note:
|
363
|
-
Uses L-BFGS-B optimization with bounds [1e-
|
451
|
+
Uses L-BFGS-B optimization with bounds [1e-6, 1e6] for all parameters
|
364
452
|
|
365
453
|
"""
|
366
454
|
par_names = list(p0.keys())
|
367
455
|
|
368
456
|
# Copy to restore
|
369
|
-
p_orig = model.
|
457
|
+
p_orig = model.get_parameter_values()
|
370
458
|
|
371
459
|
fn = cast(
|
372
460
|
ResidualFn,
|
@@ -380,7 +468,7 @@ def steady_state(
|
|
380
468
|
loss_fn=loss_fn,
|
381
469
|
),
|
382
470
|
)
|
383
|
-
min_result = minimize_fn(fn, p0)
|
471
|
+
min_result = minimize_fn(fn, p0, {} if bounds is None else bounds)
|
384
472
|
# Restore original model
|
385
473
|
model.update_parameters(p_orig)
|
386
474
|
if min_result is None:
|
@@ -403,6 +491,7 @@ def time_course(
|
|
403
491
|
residual_fn: TimeSeriesResidualFn = _time_course_residual,
|
404
492
|
integrator: IntegratorType | None = None,
|
405
493
|
loss_fn: LossFn = rmse,
|
494
|
+
bounds: Bounds | None = None,
|
406
495
|
) -> FitResult | None:
|
407
496
|
"""Fit model parameters to time course of experimental data.
|
408
497
|
|
@@ -419,16 +508,17 @@ def time_course(
|
|
419
508
|
residual_fn: Function to calculate fitting error
|
420
509
|
integrator: ODE integrator class
|
421
510
|
loss_fn: Loss function to use for residual calculation
|
511
|
+
bounds: Mapping of bounds per parameter
|
422
512
|
|
423
513
|
Returns:
|
424
514
|
dict[str, float]: Fitted parameters as {parameter_name: fitted_value}
|
425
515
|
|
426
516
|
Note:
|
427
|
-
Uses L-BFGS-B optimization with bounds [1e-
|
517
|
+
Uses L-BFGS-B optimization with bounds [1e-6, 1e6] for all parameters
|
428
518
|
|
429
519
|
"""
|
430
520
|
par_names = list(p0.keys())
|
431
|
-
p_orig = model.
|
521
|
+
p_orig = model.get_parameter_values()
|
432
522
|
|
433
523
|
fn = cast(
|
434
524
|
ResidualFn,
|
@@ -443,7 +533,7 @@ def time_course(
|
|
443
533
|
),
|
444
534
|
)
|
445
535
|
|
446
|
-
min_result = minimize_fn(fn, p0)
|
536
|
+
min_result = minimize_fn(fn, p0, {} if bounds is None else bounds)
|
447
537
|
# Restore original model
|
448
538
|
model.update_parameters(p_orig)
|
449
539
|
if min_result is None:
|
@@ -456,7 +546,7 @@ def time_course(
|
|
456
546
|
)
|
457
547
|
|
458
548
|
|
459
|
-
def
|
549
|
+
def protocol_time_course(
|
460
550
|
model: Model,
|
461
551
|
*,
|
462
552
|
p0: dict[str, float],
|
@@ -464,10 +554,10 @@ def time_course_over_protocol(
|
|
464
554
|
protocol: pd.DataFrame,
|
465
555
|
y0: dict[str, float] | None = None,
|
466
556
|
minimize_fn: MinimizeFn = _default_minimize_fn,
|
467
|
-
residual_fn: ProtocolResidualFn =
|
557
|
+
residual_fn: ProtocolResidualFn = _protocol_time_course_residual,
|
468
558
|
integrator: IntegratorType | None = None,
|
469
559
|
loss_fn: LossFn = rmse,
|
470
|
-
|
560
|
+
bounds: Bounds | None = None,
|
471
561
|
) -> FitResult | None:
|
472
562
|
"""Fit model parameters to time course of experimental data.
|
473
563
|
|
@@ -486,16 +576,17 @@ def time_course_over_protocol(
|
|
486
576
|
integrator: ODE integrator class
|
487
577
|
loss_fn: Loss function to use for residual calculation
|
488
578
|
time_points_per_step: Number of time points per step in the protocol
|
579
|
+
bounds: Mapping of bounds per parameter
|
489
580
|
|
490
581
|
Returns:
|
491
582
|
dict[str, float]: Fitted parameters as {parameter_name: fitted_value}
|
492
583
|
|
493
584
|
Note:
|
494
|
-
Uses L-BFGS-B optimization with bounds [1e-
|
585
|
+
Uses L-BFGS-B optimization with bounds [1e-6, 1e6] for all parameters
|
495
586
|
|
496
587
|
"""
|
497
588
|
par_names = list(p0.keys())
|
498
|
-
p_orig = model.
|
589
|
+
p_orig = model.get_parameter_values()
|
499
590
|
|
500
591
|
fn = cast(
|
501
592
|
ResidualFn,
|
@@ -508,11 +599,10 @@ def time_course_over_protocol(
|
|
508
599
|
integrator=integrator,
|
509
600
|
loss_fn=loss_fn,
|
510
601
|
protocol=protocol,
|
511
|
-
time_points_per_step=time_points_per_step,
|
512
602
|
),
|
513
603
|
)
|
514
604
|
|
515
|
-
min_result = minimize_fn(fn, p0)
|
605
|
+
min_result = minimize_fn(fn, p0, {} if bounds is None else bounds)
|
516
606
|
# Restore original model
|
517
607
|
model.update_parameters(p_orig)
|
518
608
|
if min_result is None:
|
@@ -525,78 +615,6 @@ def time_course_over_protocol(
|
|
525
615
|
)
|
526
616
|
|
527
617
|
|
528
|
-
def _carousel_steady_state_worker(
|
529
|
-
model: Model,
|
530
|
-
p0: dict[str, float],
|
531
|
-
data: pd.Series,
|
532
|
-
y0: dict[str, float] | None,
|
533
|
-
integrator: IntegratorType | None,
|
534
|
-
loss_fn: LossFn,
|
535
|
-
minimize_fn: MinimizeFn,
|
536
|
-
residual_fn: SteadyStateResidualFn,
|
537
|
-
) -> FitResult | None:
|
538
|
-
model_pars = model.parameters
|
539
|
-
|
540
|
-
return steady_state(
|
541
|
-
model,
|
542
|
-
p0={k: v for k, v in p0.items() if k in model_pars},
|
543
|
-
y0=y0,
|
544
|
-
data=data,
|
545
|
-
minimize_fn=minimize_fn,
|
546
|
-
residual_fn=residual_fn,
|
547
|
-
integrator=integrator,
|
548
|
-
loss_fn=loss_fn,
|
549
|
-
)
|
550
|
-
|
551
|
-
|
552
|
-
def _carousel_time_course_worker(
|
553
|
-
model: Model,
|
554
|
-
p0: dict[str, float],
|
555
|
-
data: pd.DataFrame,
|
556
|
-
y0: dict[str, float] | None,
|
557
|
-
integrator: IntegratorType | None,
|
558
|
-
loss_fn: LossFn,
|
559
|
-
minimize_fn: MinimizeFn,
|
560
|
-
residual_fn: TimeSeriesResidualFn,
|
561
|
-
) -> FitResult | None:
|
562
|
-
model_pars = model.parameters
|
563
|
-
return time_course(
|
564
|
-
model,
|
565
|
-
p0={k: v for k, v in p0.items() if k in model_pars},
|
566
|
-
y0=y0,
|
567
|
-
data=data,
|
568
|
-
minimize_fn=minimize_fn,
|
569
|
-
residual_fn=residual_fn,
|
570
|
-
integrator=integrator,
|
571
|
-
loss_fn=loss_fn,
|
572
|
-
)
|
573
|
-
|
574
|
-
|
575
|
-
def _carousel_protocol_worker(
|
576
|
-
model: Model,
|
577
|
-
p0: dict[str, float],
|
578
|
-
data: pd.DataFrame,
|
579
|
-
protocol: pd.DataFrame,
|
580
|
-
y0: dict[str, float] | None,
|
581
|
-
integrator: IntegratorType | None,
|
582
|
-
loss_fn: LossFn,
|
583
|
-
minimize_fn: MinimizeFn,
|
584
|
-
residual_fn: ProtocolResidualFn,
|
585
|
-
) -> FitResult | None:
|
586
|
-
model_pars = model.parameters
|
587
|
-
return time_course_over_protocol(
|
588
|
-
model,
|
589
|
-
p0={k: v for k, v in p0.items() if k in model_pars},
|
590
|
-
y0=y0,
|
591
|
-
protocol=protocol,
|
592
|
-
data=data,
|
593
|
-
minimize_fn=minimize_fn,
|
594
|
-
residual_fn=residual_fn,
|
595
|
-
integrator=integrator,
|
596
|
-
loss_fn=loss_fn,
|
597
|
-
)
|
598
|
-
|
599
|
-
|
600
618
|
def carousel_steady_state(
|
601
619
|
carousel: Carousel,
|
602
620
|
*,
|
@@ -607,8 +625,33 @@ def carousel_steady_state(
|
|
607
625
|
residual_fn: SteadyStateResidualFn = _steady_state_residual,
|
608
626
|
integrator: IntegratorType | None = None,
|
609
627
|
loss_fn: LossFn = rmse,
|
628
|
+
bounds: Bounds | None = None,
|
610
629
|
) -> CarouselFit:
|
611
|
-
"""Fit model parameters to steady-state experimental data over a carousel.
|
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
|
+
"""
|
612
655
|
return CarouselFit(
|
613
656
|
[
|
614
657
|
fit
|
@@ -622,6 +665,7 @@ def carousel_steady_state(
|
|
622
665
|
loss_fn=loss_fn,
|
623
666
|
minimize_fn=minimize_fn,
|
624
667
|
residual_fn=residual_fn,
|
668
|
+
bounds=bounds,
|
625
669
|
),
|
626
670
|
inputs=list(enumerate(carousel.variants)),
|
627
671
|
)
|
@@ -640,8 +684,33 @@ def carousel_time_course(
|
|
640
684
|
residual_fn: TimeSeriesResidualFn = _time_course_residual,
|
641
685
|
integrator: IntegratorType | None = None,
|
642
686
|
loss_fn: LossFn = rmse,
|
687
|
+
bounds: Bounds | None = None,
|
643
688
|
) -> CarouselFit:
|
644
|
-
"""Fit model parameters to time course of experimental data over a carousel.
|
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
|
+
"""
|
645
714
|
return CarouselFit(
|
646
715
|
[
|
647
716
|
fit
|
@@ -655,6 +724,7 @@ def carousel_time_course(
|
|
655
724
|
loss_fn=loss_fn,
|
656
725
|
minimize_fn=minimize_fn,
|
657
726
|
residual_fn=residual_fn,
|
727
|
+
bounds=bounds,
|
658
728
|
),
|
659
729
|
inputs=list(enumerate(carousel.variants)),
|
660
730
|
)
|
@@ -663,7 +733,7 @@ def carousel_time_course(
|
|
663
733
|
)
|
664
734
|
|
665
735
|
|
666
|
-
def
|
736
|
+
def carousel_protocol_time_course(
|
667
737
|
carousel: Carousel,
|
668
738
|
*,
|
669
739
|
p0: dict[str, float],
|
@@ -671,11 +741,36 @@ def carousel_time_course_over_protocol(
|
|
671
741
|
protocol: pd.DataFrame,
|
672
742
|
y0: dict[str, float] | None = None,
|
673
743
|
minimize_fn: MinimizeFn = _default_minimize_fn,
|
674
|
-
residual_fn: ProtocolResidualFn =
|
744
|
+
residual_fn: ProtocolResidualFn = _protocol_time_course_residual,
|
675
745
|
integrator: IntegratorType | None = None,
|
676
746
|
loss_fn: LossFn = rmse,
|
747
|
+
bounds: Bounds | None = None,
|
677
748
|
) -> CarouselFit:
|
678
|
-
"""Fit model parameters to time course of experimental data over a protocol.
|
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
|
+
"""
|
679
774
|
return CarouselFit(
|
680
775
|
[
|
681
776
|
fit
|
@@ -690,6 +785,7 @@ def carousel_time_course_over_protocol(
|
|
690
785
|
loss_fn=loss_fn,
|
691
786
|
minimize_fn=minimize_fn,
|
692
787
|
residual_fn=residual_fn,
|
788
|
+
bounds=bounds,
|
693
789
|
),
|
694
790
|
inputs=list(enumerate(carousel.variants)),
|
695
791
|
)
|
mxlpy/identify.py
CHANGED
@@ -59,21 +59,26 @@ def profile_likelihood(
|
|
59
59
|
|
60
60
|
"""
|
61
61
|
parameter_distributions = sample(
|
62
|
-
{
|
62
|
+
{
|
63
|
+
k: LogNormal(np.log(v), sigma=1)
|
64
|
+
for k, v in model.get_parameter_values().items()
|
65
|
+
},
|
63
66
|
n=n_random,
|
64
67
|
)
|
65
68
|
|
66
69
|
res = {}
|
67
70
|
for value in tqdm(parameter_values, desc=parameter_name):
|
68
71
|
model.update_parameter(parameter_name, value)
|
69
|
-
res[value] =
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
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
|
+
)
|
77
82
|
)
|
78
83
|
errors = pd.DataFrame(res, dtype=float).T.abs().mean(axis=1)
|
79
84
|
errors.index.name = "fitting error"
|
mxlpy/integrators/int_scipy.py
CHANGED
@@ -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,
|
mxlpy/label_map.py
CHANGED
@@ -551,13 +551,13 @@ class LabelMapper:
|
|
551
551
|
|
552
552
|
m = Model()
|
553
553
|
|
554
|
-
m.add_parameters(self.model.
|
554
|
+
m.add_parameters(self.model.get_parameter_values())
|
555
555
|
|
556
|
-
for name, dp in self.model.
|
556
|
+
for name, dp in self.model.get_derived_parameters().items():
|
557
557
|
m.add_derived(name, fn=dp.fn, args=dp.args)
|
558
558
|
|
559
559
|
variables: dict[str, float | Derived] = {}
|
560
|
-
for k, v in self.model.
|
560
|
+
for k, v in self.model.get_initial_conditions().items():
|
561
561
|
if (isos := isotopomers.get(k)) is None:
|
562
562
|
variables[k] = v
|
563
563
|
else:
|
@@ -585,14 +585,14 @@ class LabelMapper:
|
|
585
585
|
args=label_names,
|
586
586
|
)
|
587
587
|
|
588
|
-
for name, dv in self.model.
|
588
|
+
for name, dv in self.model.get_derived_variables().items():
|
589
589
|
m.add_derived(
|
590
590
|
name,
|
591
591
|
fn=dv.fn,
|
592
592
|
args=[f"{i}__total" if i in isotopomers else i for i in dv.args],
|
593
593
|
)
|
594
594
|
|
595
|
-
for rxn_name, rxn in self.model.
|
595
|
+
for rxn_name, rxn in self.model.get_raw_reactions().items():
|
596
596
|
if (label_map := self.label_maps.get(rxn_name)) is None:
|
597
597
|
m.add_reaction(
|
598
598
|
rxn_name,
|
mxlpy/linear_label_map.py
CHANGED
@@ -272,8 +272,10 @@ class LinearLabelMapper:
|
|
272
272
|
m = Model()
|
273
273
|
m.add_variables(variables)
|
274
274
|
m.add_parameters(concs.to_dict() | fluxes.to_dict() | {"EXT": external_label})
|
275
|
+
|
276
|
+
rxns = self.model.get_raw_reactions()
|
275
277
|
for rxn_name, label_map in self.label_maps.items():
|
276
|
-
rxn =
|
278
|
+
rxn = rxns[rxn_name]
|
277
279
|
subs, prods = _unpack_stoichiometries(rxn.stoichiometry)
|
278
280
|
|
279
281
|
subs = _stoichiometry_to_duplicate_list(subs)
|
mxlpy/mc.py
CHANGED
@@ -66,6 +66,7 @@ class ParameterScanWorker(Protocol):
|
|
66
66
|
model: Model,
|
67
67
|
*,
|
68
68
|
parameters: pd.DataFrame,
|
69
|
+
y0: dict[str, float] | None,
|
69
70
|
rel_norm: bool,
|
70
71
|
integrator: IntegratorType,
|
71
72
|
) -> SteadyStates:
|
@@ -77,6 +78,7 @@ def _parameter_scan_worker(
|
|
77
78
|
model: Model,
|
78
79
|
*,
|
79
80
|
parameters: pd.DataFrame,
|
81
|
+
y0: dict[str, float] | None,
|
80
82
|
rel_norm: bool,
|
81
83
|
integrator: IntegratorType,
|
82
84
|
) -> SteadyStates:
|
@@ -109,6 +111,7 @@ def _parameter_scan_worker(
|
|
109
111
|
parallel=False,
|
110
112
|
rel_norm=rel_norm,
|
111
113
|
integrator=integrator,
|
114
|
+
y0=y0,
|
112
115
|
)
|
113
116
|
|
114
117
|
|