mxlpy 0.16.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.
@@ -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
 
@@ -24,6 +24,8 @@ __all__ = [
24
24
 
25
25
  @dataclass
26
26
  class Context:
27
+ """Context for converting a function to sympy expression."""
28
+
27
29
  symbols: dict[str, sympy.Symbol | sympy.Expr]
28
30
  caller: Callable
29
31
  parent_module: ModuleType | None
@@ -34,6 +36,7 @@ class Context:
34
36
  caller: Callable | None = None,
35
37
  parent_module: ModuleType | None = None,
36
38
  ) -> "Context":
39
+ """Update the context with new values."""
37
40
  return Context(
38
41
  symbols=self.symbols if symbols is None else symbols,
39
42
  caller=self.caller if caller is None else caller,
@@ -44,7 +47,26 @@ class Context:
44
47
 
45
48
 
46
49
  def get_fn_source(fn: Callable) -> str:
47
- """Get the string representation of a function."""
50
+ """Get the string representation of a function.
51
+
52
+ Parameters
53
+ ----------
54
+ fn
55
+ The function to extract source from
56
+
57
+ Returns
58
+ -------
59
+ str
60
+ String representation of the function's source code
61
+
62
+ Examples
63
+ --------
64
+ >>> def example_fn(x): return x * 2
65
+ >>> source = get_fn_source(example_fn)
66
+ >>> print(source)
67
+ def example_fn(x): return x * 2
68
+
69
+ """
48
70
  try:
49
71
  return inspect.getsource(fn)
50
72
  except OSError: # could not get source code
@@ -52,7 +74,31 @@ def get_fn_source(fn: Callable) -> str:
52
74
 
53
75
 
54
76
  def get_fn_ast(fn: Callable) -> ast.FunctionDef:
55
- """Get the source code of a function as an AST."""
77
+ """Get the source code of a function as an AST.
78
+
79
+ Parameters
80
+ ----------
81
+ fn
82
+ The function to convert to AST
83
+
84
+ Returns
85
+ -------
86
+ ast.FunctionDef
87
+ Abstract syntax tree representation of the function
88
+
89
+ Raises
90
+ ------
91
+ TypeError
92
+ If the input is not a function
93
+
94
+ Examples
95
+ --------
96
+ >>> def example_fn(x): return x * 2
97
+ >>> ast_tree = get_fn_ast(example_fn)
98
+ >>> isinstance(ast_tree, ast.FunctionDef)
99
+ True
100
+
101
+ """
56
102
  tree = ast.parse(textwrap.dedent(get_fn_source(fn)))
57
103
  if not isinstance(fn_def := tree.body[0], ast.FunctionDef):
58
104
  msg = "Not a function"
@@ -61,6 +107,27 @@ def get_fn_ast(fn: Callable) -> ast.FunctionDef:
61
107
 
62
108
 
63
109
  def sympy_to_inline(expr: sympy.Expr) -> str:
110
+ """Convert a sympy expression to inline Python code.
111
+
112
+ Parameters
113
+ ----------
114
+ expr
115
+ The sympy expression to convert
116
+
117
+ Returns
118
+ -------
119
+ str
120
+ Python code string for the expression
121
+
122
+ Examples
123
+ --------
124
+ >>> import sympy
125
+ >>> x = sympy.Symbol('x')
126
+ >>> expr = x**2 + 2*x + 1
127
+ >>> sympy_to_inline(expr)
128
+ 'x**2 + 2*x + 1'
129
+
130
+ """
64
131
  return cast(str, pycode(expr, fully_qualified_modules=True))
65
132
 
66
133
 
@@ -70,7 +137,32 @@ def sympy_to_fn(
70
137
  args: list[str],
71
138
  expr: sympy.Expr,
72
139
  ) -> str:
73
- """Convert a sympy expression to a python function."""
140
+ """Convert a sympy expression to a python function.
141
+
142
+ Parameters
143
+ ----------
144
+ fn_name
145
+ Name of the function to generate
146
+ args
147
+ List of argument names for the function
148
+ expr
149
+ Sympy expression to convert to a function body
150
+
151
+ Returns
152
+ -------
153
+ str
154
+ String representation of the generated function
155
+
156
+ Examples
157
+ --------
158
+ >>> import sympy
159
+ >>> x, y = sympy.symbols('x y')
160
+ >>> expr = x**2 + y
161
+ >>> print(sympy_to_fn(fn_name="square_plus_y", args=["x", "y"], expr=expr))
162
+ def square_plus_y(x: float, y: float) -> float:
163
+ return x**2 + y
164
+
165
+ """
74
166
  fn_args = ", ".join(f"{i}: float" for i in args)
75
167
 
76
168
  return f"""def {fn_name}({fn_args}) -> float:
@@ -82,7 +174,33 @@ def fn_to_sympy(
82
174
  fn: Callable,
83
175
  model_args: list[sympy.Symbol | sympy.Expr] | None = None,
84
176
  ) -> sympy.Expr:
85
- """Convert a python function to a sympy expression."""
177
+ """Convert a python function to a sympy expression.
178
+
179
+ Parameters
180
+ ----------
181
+ fn
182
+ The function to convert
183
+ model_args
184
+ Optional list of sympy symbols to substitute for function arguments
185
+
186
+ Returns
187
+ -------
188
+ sympy.Expr
189
+ Sympy expression equivalent to the function
190
+
191
+ Examples
192
+ --------
193
+ >>> def square_fn(x):
194
+ ... return x**2
195
+ >>> import sympy
196
+ >>> fn_to_sympy(square_fn)
197
+ x**2
198
+ >>> # With model_args
199
+ >>> y = sympy.Symbol('y')
200
+ >>> fn_to_sympy(square_fn, [y])
201
+ y**2
202
+
203
+ """
86
204
  fn_def = get_fn_ast(fn)
87
205
  fn_args = [str(arg.arg) for arg in fn_def.args.args]
88
206
  sympy_expr = _handle_fn_body(