mxlpy 0.15.0__py3-none-any.whl → 0.17.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/mca.py CHANGED
@@ -38,6 +38,73 @@ if TYPE_CHECKING:
38
38
  from mxlpy.types import IntegratorType
39
39
 
40
40
 
41
+ def _response_coefficient_worker(
42
+ parameter: str,
43
+ *,
44
+ model: Model,
45
+ y0: dict[str, float] | None,
46
+ normalized: bool,
47
+ rel_norm: bool,
48
+ displacement: float = 1e-4,
49
+ integrator: IntegratorType,
50
+ ) -> tuple[pd.Series, pd.Series]:
51
+ """Calculate response coefficients for a single parameter.
52
+
53
+ Internal helper function that computes concentration and flux response
54
+ coefficients using finite differences. The function:
55
+ 1. Perturbs the parameter up and down by a small displacement
56
+ 2. Calculates steady states for each perturbation
57
+ 3. Computes response coefficients from the differences
58
+ 4. Optionally normalizes the results
59
+
60
+ Args:
61
+ parameter: Name of the parameter to analyze
62
+ model: Metabolic model instance
63
+ y0: Initial conditions as a dictionary {species: value}
64
+ normalized: Whether to normalize the coefficients
65
+ rel_norm: Whether to use relative normalization
66
+ displacement: Relative perturbation size (default: 1e-4)
67
+ integrator: Integrator function to use for steady state calculation
68
+
69
+ Returns:
70
+ tuple[pd.Series, pd.Series]: Tuple containing:
71
+ - Series of concentration response coefficients
72
+ - Series of flux response coefficients
73
+
74
+ """
75
+ old = model.parameters[parameter]
76
+ if y0 is not None:
77
+ model.update_variables(y0)
78
+
79
+ model.update_parameters({parameter: old * (1 + displacement)})
80
+ upper = _steady_state_worker(
81
+ model,
82
+ rel_norm=rel_norm,
83
+ integrator=integrator,
84
+ )
85
+
86
+ model.update_parameters({parameter: old * (1 - displacement)})
87
+ lower = _steady_state_worker(
88
+ model,
89
+ rel_norm=rel_norm,
90
+ integrator=integrator,
91
+ )
92
+
93
+ conc_resp = (upper.variables - lower.variables) / (2 * displacement * old)
94
+ flux_resp = (upper.fluxes - lower.fluxes) / (2 * displacement * old)
95
+ # Reset
96
+ model.update_parameters({parameter: old})
97
+ if normalized:
98
+ norm = _steady_state_worker(
99
+ model,
100
+ rel_norm=rel_norm,
101
+ integrator=integrator,
102
+ )
103
+ conc_resp *= old / norm.variables
104
+ flux_resp *= old / norm.fluxes
105
+ return conc_resp, flux_resp
106
+
107
+
41
108
  ###############################################################################
42
109
  # Non-steady state
43
110
  ###############################################################################
@@ -46,8 +113,8 @@ if TYPE_CHECKING:
46
113
  def variable_elasticities(
47
114
  model: Model,
48
115
  *,
49
- concs: dict[str, float] | None = None,
50
- variables: list[str] | None = None,
116
+ to_scan: list[str] | None = None,
117
+ variables: dict[str, float] | None = None,
51
118
  time: float = 0,
52
119
  normalized: bool = True,
53
120
  displacement: float = 1e-4,
@@ -67,8 +134,8 @@ def variable_elasticities(
67
134
 
68
135
  Args:
69
136
  model: Metabolic model instance
70
- concs: Initial concentrations {metabolite: value}. Uses model defaults if None
71
- variables: List of variables to analyze. Uses all if None
137
+ to_scan: List of variables to analyze. Uses all if None
138
+ variables: Custom variable values. Defaults to initial conditions.
72
139
  time: Time point for evaluation
73
140
  normalized: Whether to normalize coefficients
74
141
  displacement: Relative perturbation size
@@ -77,23 +144,23 @@ def variable_elasticities(
77
144
  DataFrame with elasticity coefficients (reactions x metabolites)
78
145
 
79
146
  """
80
- concs = model.get_initial_conditions() if concs is None else concs
81
- variables = model.get_variable_names() if variables is None else variables
147
+ variables = model.get_initial_conditions() if variables is None else variables
148
+ to_scan = model.get_variable_names() if to_scan is None else to_scan
82
149
  elasticities = {}
83
150
 
84
- for var in variables:
85
- old = concs[var]
151
+ for var in to_scan:
152
+ old = variables[var]
86
153
 
87
154
  upper = model.get_fluxes(
88
- variables=concs | {var: old * (1 + displacement)}, time=time
155
+ variables=variables | {var: old * (1 + displacement)}, time=time
89
156
  )
90
157
  lower = model.get_fluxes(
91
- variables=concs | {var: old * (1 - displacement)}, time=time
158
+ variables=variables | {var: old * (1 - displacement)}, time=time
92
159
  )
93
160
 
94
161
  elasticity_coef = (upper - lower) / (2 * displacement * old)
95
162
  if normalized:
96
- elasticity_coef *= old / model.get_fluxes(variables=concs, time=time)
163
+ elasticity_coef *= old / model.get_fluxes(variables=variables, time=time)
97
164
  elasticities[var] = elasticity_coef
98
165
 
99
166
  return pd.DataFrame(data=elasticities)
@@ -101,10 +168,10 @@ def variable_elasticities(
101
168
 
102
169
  def parameter_elasticities(
103
170
  model: Model,
104
- parameters: list[str] | None = None,
105
- concs: dict[str, float] | None = None,
106
- time: float = 0,
107
171
  *,
172
+ to_scan: list[str] | None = None,
173
+ variables: dict[str, float] | None = None,
174
+ time: float = 0,
108
175
  normalized: bool = True,
109
176
  displacement: float = 1e-4,
110
177
  ) -> pd.DataFrame:
@@ -119,8 +186,8 @@ def parameter_elasticities(
119
186
 
120
187
  Args:
121
188
  model: Metabolic model instance
122
- parameters: List of parameters to analyze. Uses all if None
123
- concs: Initial concentrations {metabolite: value}. Uses model defaults if None
189
+ to_scan: List of parameters to analyze. Uses all if None
190
+ variables: Custom variable values. Defaults to initial conditions.
124
191
  time: Time point for evaluation
125
192
  normalized: Whether to normalize coefficients
126
193
  displacement: Relative perturbation size
@@ -129,26 +196,26 @@ def parameter_elasticities(
129
196
  DataFrame with parameter elasticities (reactions x parameters)
130
197
 
131
198
  """
132
- concs = model.get_initial_conditions() if concs is None else concs
133
- parameters = model.get_parameter_names() if parameters is None else parameters
199
+ variables = model.get_initial_conditions() if variables is None else variables
200
+ to_scan = model.get_parameter_names() if to_scan is None else to_scan
134
201
 
135
202
  elasticities = {}
136
203
 
137
- concs = model.get_initial_conditions() if concs is None else concs
138
- for par in parameters:
204
+ variables = model.get_initial_conditions() if variables is None else variables
205
+ for par in to_scan:
139
206
  old = model.parameters[par]
140
207
 
141
208
  model.update_parameters({par: old * (1 + displacement)})
142
- upper = model.get_fluxes(variables=concs, time=time)
209
+ upper = model.get_fluxes(variables=variables, time=time)
143
210
 
144
211
  model.update_parameters({par: old * (1 - displacement)})
145
- lower = model.get_fluxes(variables=concs, time=time)
212
+ lower = model.get_fluxes(variables=variables, time=time)
146
213
 
147
214
  # Reset
148
215
  model.update_parameters({par: old})
149
216
  elasticity_coef = (upper - lower) / (2 * displacement * old)
150
217
  if normalized:
151
- elasticity_coef *= old / model.get_fluxes(variables=concs, time=time)
218
+ elasticity_coef *= old / model.get_fluxes(variables=variables, time=time)
152
219
  elasticities[par] = elasticity_coef
153
220
 
154
221
  return pd.DataFrame(data=elasticities)
@@ -159,79 +226,11 @@ def parameter_elasticities(
159
226
  # ###############################################################################
160
227
 
161
228
 
162
- def _response_coefficient_worker(
163
- parameter: str,
164
- *,
165
- model: Model,
166
- y0: dict[str, float] | None,
167
- normalized: bool,
168
- rel_norm: bool,
169
- displacement: float = 1e-4,
170
- integrator: IntegratorType,
171
- ) -> tuple[pd.Series, pd.Series]:
172
- """Calculate response coefficients for a single parameter.
173
-
174
- Internal helper function that computes concentration and flux response
175
- coefficients using finite differences. The function:
176
- 1. Perturbs the parameter up and down by a small displacement
177
- 2. Calculates steady states for each perturbation
178
- 3. Computes response coefficients from the differences
179
- 4. Optionally normalizes the results
180
-
181
- Args:
182
- parameter: Name of the parameter to analyze
183
- model: Metabolic model instance
184
- y0: Initial conditions as a dictionary {species: value}
185
- normalized: Whether to normalize the coefficients
186
- rel_norm: Whether to use relative normalization
187
- displacement: Relative perturbation size (default: 1e-4)
188
- integrator: Integrator function to use for steady state calculation
189
-
190
- Returns:
191
- tuple[pd.Series, pd.Series]: Tuple containing:
192
- - Series of concentration response coefficients
193
- - Series of flux response coefficients
194
-
195
- """
196
- old = model.parameters[parameter]
197
-
198
- model.update_parameters({parameter: old * (1 + displacement)})
199
- upper = _steady_state_worker(
200
- model,
201
- y0=y0,
202
- rel_norm=rel_norm,
203
- integrator=integrator,
204
- )
205
-
206
- model.update_parameters({parameter: old * (1 - displacement)})
207
- lower = _steady_state_worker(
208
- model,
209
- y0=y0,
210
- rel_norm=rel_norm,
211
- integrator=integrator,
212
- )
213
-
214
- conc_resp = (upper.variables - lower.variables) / (2 * displacement * old)
215
- flux_resp = (upper.fluxes - lower.fluxes) / (2 * displacement * old)
216
- # Reset
217
- model.update_parameters({parameter: old})
218
- if normalized:
219
- norm = _steady_state_worker(
220
- model,
221
- y0=y0,
222
- rel_norm=rel_norm,
223
- integrator=integrator,
224
- )
225
- conc_resp *= old / norm.variables
226
- flux_resp *= old / norm.fluxes
227
- return conc_resp, flux_resp
228
-
229
-
230
229
  def response_coefficients(
231
230
  model: Model,
232
- parameters: list[str] | None = None,
233
231
  *,
234
- y0: dict[str, float] | None = None,
232
+ to_scan: list[str] | None = None,
233
+ variables: dict[str, float] | None = None,
235
234
  normalized: bool = True,
236
235
  displacement: float = 1e-4,
237
236
  disable_tqdm: bool = False,
@@ -250,8 +249,8 @@ def response_coefficients(
250
249
 
251
250
  Args:
252
251
  model: Metabolic model instance
253
- parameters: Parameters to analyze. Uses all if None
254
- y0: Initial conditions as a dictionary {species: value}
252
+ to_scan: Parameters to analyze. Uses all if None
253
+ variables: Custom variable values. Defaults to initial conditions.
255
254
  normalized: Whether to normalize coefficients
256
255
  displacement: Relative perturbation size
257
256
  disable_tqdm: Disable progress bar
@@ -266,19 +265,19 @@ def response_coefficients(
266
265
  - Concentration response coefficients
267
266
 
268
267
  """
269
- parameters = model.get_parameter_names() if parameters is None else parameters
268
+ to_scan = model.get_parameter_names() if to_scan is None else to_scan
270
269
 
271
270
  res = parallelise(
272
271
  partial(
273
272
  _response_coefficient_worker,
274
273
  model=model,
275
- y0=y0,
274
+ y0=variables,
276
275
  normalized=normalized,
277
276
  displacement=displacement,
278
277
  rel_norm=rel_norm,
279
278
  integrator=integrator,
280
279
  ),
281
- inputs=list(zip(parameters, parameters, strict=True)),
280
+ inputs=list(zip(to_scan, to_scan, strict=True)),
282
281
  cache=None,
283
282
  disable_tqdm=disable_tqdm,
284
283
  parallel=parallel,
@@ -37,7 +37,26 @@ def _list_of_symbols(args: list[str]) -> list[sympy.Symbol | sympy.Expr]:
37
37
 
38
38
 
39
39
  def default_init[T1, T2](d: dict[T1, T2] | None) -> dict[T1, T2]:
40
- """Return empty dict if d is None."""
40
+ """Return empty dict if d is None.
41
+
42
+ Parameters
43
+ ----------
44
+ d
45
+ Dictionary to check
46
+
47
+ Returns
48
+ -------
49
+ dict
50
+ Original dictionary if not None, otherwise an empty dictionary
51
+
52
+ Examples
53
+ --------
54
+ >>> default_init(None)
55
+ {}
56
+ >>> default_init({"key": "value"})
57
+ {'key': 'value'}
58
+
59
+ """
41
60
  return {} if d is None else d
42
61
 
43
62
 
@@ -256,7 +275,23 @@ def _stoichiometries_to_latex(stoich: Mapping[str, float | Derived]) -> str:
256
275
 
257
276
  @dataclass
258
277
  class TexReaction:
259
- """Collection for reaction."""
278
+ """Collection for reaction.
279
+
280
+ Parameters
281
+ ----------
282
+ fn
283
+ Rate function for the reaction
284
+ args
285
+ List of argument names for the rate function
286
+
287
+ Examples
288
+ --------
289
+ >>> def rate_fn(k, s): return k * s
290
+ >>> reaction = TexReaction(fn=rate_fn, args=["k1", "S"])
291
+ >>> reaction.fn(0.1, 1.0)
292
+ 0.1
293
+
294
+ """
260
295
 
261
296
  fn: RateFn
262
297
  args: list[str]
@@ -264,7 +299,35 @@ class TexReaction:
264
299
 
265
300
  @dataclass
266
301
  class TexExport:
267
- """Container for LaTeX export."""
302
+ """Container for LaTeX export.
303
+
304
+ This class handles the conversion of model components to LaTeX format
305
+ for exporting models as LaTeX documents.
306
+
307
+ Parameters
308
+ ----------
309
+ parameters
310
+ Dictionary of parameter names and their values
311
+ variables
312
+ Dictionary of variable names and their initial values
313
+ derived
314
+ Dictionary of derived variables and their definitions
315
+ reactions
316
+ Dictionary of reaction names and their rate functions
317
+ stoichiometries
318
+ Dictionary mapping reaction names to stoichiometry dictionaries
319
+
320
+ Examples
321
+ --------
322
+ >>> parameters = {"k1": 0.1, "k2": 0.2}
323
+ >>> variables = {"S": 1.0, "P": 0.0}
324
+ >>> derived = {"total": Derived(fn=lambda s, p: s + p, args=["S", "P"])}
325
+ >>> reactions = {"v1": TexReaction(fn=lambda k, s: k*s, args=["k1", "S"])}
326
+ >>> stoich = {"v1": {"S": -1, "P": 1}}
327
+ >>> tex = TexExport(parameters, variables, derived, reactions, stoich)
328
+ >>> latex_doc = tex.export_document(author="User", title="My Model")
329
+
330
+ """
268
331
 
269
332
  parameters: dict[str, float]
270
333
  variables: dict[str, float]
@@ -298,7 +361,27 @@ class TexExport:
298
361
  return {k: v for k, v in p2.items() if k not in p1 or p1[k] != v}
299
362
 
300
363
  def __sub__(self, other: object) -> TexExport:
301
- """Return difference of two tex exports."""
364
+ """Return difference of two tex exports.
365
+
366
+ Parameters
367
+ ----------
368
+ other
369
+ Another TexExport instance to compare with
370
+
371
+ Returns
372
+ -------
373
+ TexExport
374
+ A new TexExport containing only the elements that differ
375
+
376
+ Examples
377
+ --------
378
+ >>> tex1 = TexExport({"k": 1.0}, {}, {}, {}, {})
379
+ >>> tex2 = TexExport({"k": 2.0}, {}, {}, {}, {})
380
+ >>> diff = tex1 - tex2
381
+ >>> diff.parameters
382
+ {'k': 2.0}
383
+
384
+ """
302
385
  if not isinstance(other, TexExport):
303
386
  raise TypeError
304
387
 
@@ -315,7 +398,28 @@ class TexExport:
315
398
  )
316
399
 
317
400
  def rename_with_glossary(self, gls: dict[str, str]) -> TexExport:
318
- """Rename all elements according to glossary."""
401
+ """Rename all elements according to glossary.
402
+
403
+ Parameters
404
+ ----------
405
+ gls
406
+ Dictionary mapping original names to glossary names
407
+
408
+ Returns
409
+ -------
410
+ TexExport
411
+ A new TexExport with renamed elements
412
+
413
+ Examples
414
+ --------
415
+ >>> tex = TexExport({"k": 1.0}, {"S": 1.0}, {}, {}, {})
416
+ >>> renamed = tex.rename_with_glossary({"k": "rate", "S": "substrate"})
417
+ >>> renamed.parameters
418
+ {'rate': 1.0}
419
+ >>> renamed.variables
420
+ {'substrate': 1.0}
421
+
422
+ """
319
423
 
320
424
  def _add_gls_if_found(k: str) -> str:
321
425
  if (new := gls.get(k)) is not None:
@@ -343,7 +447,21 @@ class TexExport:
343
447
  )
344
448
 
345
449
  def export_variables(self) -> str:
346
- """Export variables."""
450
+ """Export variables as LaTeX table.
451
+
452
+ Returns
453
+ -------
454
+ str
455
+ LaTeX code for variables table
456
+
457
+ Examples
458
+ --------
459
+ >>> tex = TexExport({}, {"S": 1.0, "P": 0.5}, {}, {}, {})
460
+ >>> latex = tex.export_variables()
461
+ >>> "Model variables" in latex
462
+ True
463
+
464
+ """
347
465
  return _table(
348
466
  headers=["Model name", "Initial concentration"],
349
467
  rows=[
@@ -360,7 +478,21 @@ class TexExport:
360
478
  )
361
479
 
362
480
  def export_parameters(self) -> str:
363
- """Export parameters."""
481
+ """Export parameters as LaTeX table.
482
+
483
+ Returns
484
+ -------
485
+ str
486
+ LaTeX code for parameters table
487
+
488
+ Examples
489
+ --------
490
+ >>> tex = TexExport({"k1": 0.1, "k2": 0.2}, {}, {}, {}, {})
491
+ >>> latex = tex.export_parameters()
492
+ >>> "Model parameters" in latex
493
+ True
494
+
495
+ """
364
496
  return _table(
365
497
  headers=["Parameter name", "Parameter value"],
366
498
  rows=[
@@ -374,7 +506,23 @@ class TexExport:
374
506
  )
375
507
 
376
508
  def export_derived(self) -> str:
377
- """Export derived quantities."""
509
+ """Export derived quantities as LaTeX equations.
510
+
511
+ Returns
512
+ -------
513
+ str
514
+ LaTeX code with derived quantity equations
515
+
516
+ Examples
517
+ --------
518
+ >>> def sum_fn(x, y): return x + y
519
+ >>> derived = {"total": Derived(fn=sum_fn, args=["S", "P"])}
520
+ >>> tex = TexExport({}, {}, derived, {}, {})
521
+ >>> latex = tex.export_derived()
522
+ >>> "total" in latex
523
+ True
524
+
525
+ """
378
526
  return _latex_list(
379
527
  rows=[
380
528
  _dmath(
@@ -385,7 +533,23 @@ class TexExport:
385
533
  )
386
534
 
387
535
  def export_reactions(self) -> str:
388
- """Export reactions."""
536
+ """Export reactions as LaTeX equations.
537
+
538
+ Returns
539
+ -------
540
+ str
541
+ LaTeX code with reaction rate equations
542
+
543
+ Examples
544
+ --------
545
+ >>> def rate_fn(k, s): return k * s
546
+ >>> reactions = {"v1": TexReaction(fn=rate_fn, args=["k1", "S"])}
547
+ >>> tex = TexExport({}, {}, {}, reactions, {})
548
+ >>> latex = tex.export_reactions()
549
+ >>> "v1" in latex
550
+ True
551
+
552
+ """
389
553
  return _latex_list(
390
554
  rows=[
391
555
  _dmath(
@@ -396,7 +560,22 @@ class TexExport:
396
560
  )
397
561
 
398
562
  def export_stoichiometries(self) -> str:
399
- """Export stoichiometries."""
563
+ """Export stoichiometries as LaTeX table.
564
+
565
+ Returns
566
+ -------
567
+ str
568
+ LaTeX code for stoichiometries table
569
+
570
+ Examples
571
+ --------
572
+ >>> stoich = {"v1": {"S": -1, "P": 1}}
573
+ >>> tex = TexExport({}, {}, {}, {}, stoich)
574
+ >>> latex = tex.export_stoichiometries()
575
+ >>> "Model stoichiometries" in latex
576
+ True
577
+
578
+ """
400
579
  return _table(
401
580
  headers=["Rate name", "Stoichiometry"],
402
581
  rows=[
@@ -413,7 +592,21 @@ class TexExport:
413
592
  )
414
593
 
415
594
  def export_all(self) -> str:
416
- """Export all model parts."""
595
+ """Export all model parts as a complete LaTeX document section.
596
+
597
+ Returns
598
+ -------
599
+ str
600
+ LaTeX code containing all model components
601
+
602
+ Examples
603
+ --------
604
+ >>> tex = TexExport({"k": 1.0}, {"S": 1.0}, {}, {}, {})
605
+ >>> latex = tex.export_all()
606
+ >>> "Parameters" in latex and "Variables" in latex
607
+ True
608
+
609
+ """
417
610
  sections = []
418
611
  if len(self.variables) > 0:
419
612
  sections.append(
@@ -456,7 +649,28 @@ class TexExport:
456
649
  author: str = "mxlpy",
457
650
  title: str = "Model construction",
458
651
  ) -> str:
459
- """Export latex document."""
652
+ r"""Export complete LaTeX document with all model components.
653
+
654
+ Parameters
655
+ ----------
656
+ author
657
+ Name of the author for the document
658
+ title
659
+ Title for the document
660
+
661
+ Returns
662
+ -------
663
+ str
664
+ Complete LaTeX document as a string
665
+
666
+ Examples
667
+ --------
668
+ >>> tex = TexExport({"k": 1.0}, {"S": 1.0}, {}, {}, {})
669
+ >>> doc = tex.export_document(author="Jane Doe", title="My Model")
670
+ >>> "\\title{My Model}" in doc and "\\author{Jane Doe}" in doc
671
+ True
672
+
673
+ """
460
674
  content = self.export_all()
461
675
  return rf"""\documentclass{{article}}
462
676
  \usepackage[english]{{babel}}
@@ -491,7 +705,33 @@ def generate_latex_code(
491
705
  model: Model,
492
706
  gls: dict[str, str] | None = None,
493
707
  ) -> str:
494
- """Export as LaTeX."""
708
+ """Export model as LaTeX document.
709
+
710
+ Parameters
711
+ ----------
712
+ model
713
+ The model to export
714
+ gls
715
+ Optional glossary mapping for renaming model components
716
+
717
+ Returns
718
+ -------
719
+ str
720
+ Complete LaTeX document as string
721
+
722
+ Examples
723
+ --------
724
+ >>> from mxlpy import Model
725
+ >>> model = Model()
726
+ >>> model.add_parameter("k1", 0.1)
727
+ >>> model.add_variable("S", 1.0)
728
+ >>> latex = generate_latex_code(model)
729
+ >>> "Model parameters" in latex and "Model variables" in latex
730
+ True
731
+ >>> # With glossary
732
+ >>> latex = generate_latex_code(model, {"k1": "rate", "S": "substrate"})
733
+
734
+ """
495
735
  gls = default_init(gls)
496
736
  return _to_tex_export(model).rename_with_glossary(gls).export_document()
497
737
 
@@ -501,7 +741,32 @@ def get_model_tex_diff(
501
741
  m2: Model,
502
742
  gls: dict[str, str] | None = None,
503
743
  ) -> str:
504
- """Create LaTeX diff of two models."""
744
+ """Create LaTeX diff showing changes between two models.
745
+
746
+ Parameters
747
+ ----------
748
+ m1
749
+ First model (considered as base model)
750
+ m2
751
+ Second model (compared against the base)
752
+ gls
753
+ Optional glossary mapping for renaming model components
754
+
755
+ Returns
756
+ -------
757
+ str
758
+ LaTeX document section showing differences between models
759
+
760
+ Examples
761
+ --------
762
+ >>> from mxlpy import Model
763
+ >>> m1 = Model().add_parameter("k1", 0.1)
764
+ >>> m2 = Model().add_parameter("k1", 0.2)
765
+ >>> diff = get_model_tex_diff(m1, m2)
766
+ >>> "Model changes" in diff and "Parameters" in diff
767
+ True
768
+
769
+ """
505
770
  gls = default_init(gls)
506
771
  section_label = "sec:model-diff"
507
772