modelbase2 0.1.79__py3-none-any.whl → 0.3.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.
- modelbase2/__init__.py +148 -25
- modelbase2/distributions.py +336 -0
- modelbase2/experimental/__init__.py +17 -0
- modelbase2/experimental/codegen.py +239 -0
- modelbase2/experimental/diff.py +227 -0
- modelbase2/experimental/notes.md +4 -0
- modelbase2/experimental/tex.py +521 -0
- modelbase2/fit.py +284 -0
- modelbase2/fns.py +185 -0
- modelbase2/integrators/__init__.py +19 -0
- modelbase2/integrators/int_assimulo.py +146 -0
- modelbase2/integrators/int_scipy.py +147 -0
- modelbase2/label_map.py +610 -0
- modelbase2/linear_label_map.py +301 -0
- modelbase2/mc.py +548 -0
- modelbase2/mca.py +280 -0
- modelbase2/model.py +1621 -0
- modelbase2/nnarchitectures.py +128 -0
- modelbase2/npe.py +271 -0
- modelbase2/parallel.py +171 -0
- modelbase2/parameterise.py +28 -0
- modelbase2/paths.py +36 -0
- modelbase2/plot.py +832 -0
- modelbase2/sbml/__init__.py +14 -0
- modelbase2/sbml/_data.py +77 -0
- modelbase2/sbml/_export.py +656 -0
- modelbase2/sbml/_import.py +585 -0
- modelbase2/sbml/_mathml.py +691 -0
- modelbase2/sbml/_name_conversion.py +52 -0
- modelbase2/sbml/_unit_conversion.py +74 -0
- modelbase2/scan.py +616 -0
- modelbase2/scope.py +96 -0
- modelbase2/simulator.py +635 -0
- modelbase2/surrogates/__init__.py +31 -0
- modelbase2/surrogates/_poly.py +91 -0
- modelbase2/surrogates/_torch.py +191 -0
- modelbase2/surrogates.py +316 -0
- modelbase2/types.py +352 -11
- modelbase2-0.3.0.dist-info/METADATA +93 -0
- modelbase2-0.3.0.dist-info/RECORD +43 -0
- {modelbase2-0.1.79.dist-info → modelbase2-0.3.0.dist-info}/WHEEL +1 -1
- modelbase2/core/__init__.py +0 -29
- modelbase2/core/algebraic_module_container.py +0 -130
- modelbase2/core/constant_container.py +0 -113
- modelbase2/core/data.py +0 -109
- modelbase2/core/name_container.py +0 -29
- modelbase2/core/reaction_container.py +0 -115
- modelbase2/core/utils.py +0 -28
- modelbase2/core/variable_container.py +0 -24
- modelbase2/ode/__init__.py +0 -13
- modelbase2/ode/integrator.py +0 -80
- modelbase2/ode/mca.py +0 -270
- modelbase2/ode/model.py +0 -470
- modelbase2/ode/simulator.py +0 -153
- modelbase2/utils/__init__.py +0 -0
- modelbase2/utils/plotting.py +0 -372
- modelbase2-0.1.79.dist-info/METADATA +0 -44
- modelbase2-0.1.79.dist-info/RECORD +0 -22
- {modelbase2-0.1.79.dist-info → modelbase2-0.3.0.dist-info/licenses}/LICENSE +0 -0
@@ -0,0 +1,521 @@
|
|
1
|
+
"""Export model to latex."""
|
2
|
+
|
3
|
+
from __future__ import annotations
|
4
|
+
|
5
|
+
import ast
|
6
|
+
import inspect
|
7
|
+
from dataclasses import dataclass
|
8
|
+
from typing import TYPE_CHECKING, cast
|
9
|
+
|
10
|
+
import latexify
|
11
|
+
|
12
|
+
from modelbase2.types import Derived, RateFn
|
13
|
+
|
14
|
+
__all__ = ["TexExport", "TexReaction", "default_init", "get_model_tex_diff", "to_tex"]
|
15
|
+
|
16
|
+
if TYPE_CHECKING:
|
17
|
+
from collections.abc import Callable, Mapping
|
18
|
+
|
19
|
+
from modelbase2 import Model
|
20
|
+
|
21
|
+
|
22
|
+
cdot = r"\cdot"
|
23
|
+
empty_set = r"\emptyset"
|
24
|
+
left_right_arrows = r"\xleftrightharpoons{}"
|
25
|
+
right_arrow = r"\xrightarrow{}"
|
26
|
+
newline = r"\\" + "\n"
|
27
|
+
floatbarrier = r"\FloatBarrier"
|
28
|
+
|
29
|
+
|
30
|
+
def default_init[T1, T2](d: dict[T1, T2] | None) -> dict[T1, T2]:
|
31
|
+
"""Return empty dict if d is None."""
|
32
|
+
return {} if d is None else d
|
33
|
+
|
34
|
+
|
35
|
+
def _gls(s: str) -> str:
|
36
|
+
return rf"\gls{{{s}}}"
|
37
|
+
|
38
|
+
|
39
|
+
def _abbrev_and_full(s: str) -> str:
|
40
|
+
return rf"\acrfull{{{s}}}"
|
41
|
+
|
42
|
+
|
43
|
+
def _gls_short(s: str) -> str:
|
44
|
+
return rf"\acrshort{{{s}}}"
|
45
|
+
|
46
|
+
|
47
|
+
def _gls_full(s: str) -> str:
|
48
|
+
return rf"\acrlong{{{s}}}"
|
49
|
+
|
50
|
+
|
51
|
+
def _rename_latex(s: str) -> str:
|
52
|
+
if s[0].isdigit():
|
53
|
+
s = s[1:]
|
54
|
+
if s[0] == "-":
|
55
|
+
s = s[1:]
|
56
|
+
return (
|
57
|
+
s.replace(" ", "_")
|
58
|
+
.replace("(", "")
|
59
|
+
.replace(")", "")
|
60
|
+
.replace("-", "_")
|
61
|
+
.replace("*", "")
|
62
|
+
)
|
63
|
+
|
64
|
+
|
65
|
+
def _escape_non_math(s: str) -> str:
|
66
|
+
return s.replace("_", r"\_")
|
67
|
+
|
68
|
+
|
69
|
+
def _fn_to_latex(fn: Callable, arg_names: list[str]) -> str:
|
70
|
+
code = inspect.getsource(fn)
|
71
|
+
src = cast(ast.Module, ast.parse(code))
|
72
|
+
fn_def = cast(ast.FunctionDef, src.body[0])
|
73
|
+
args: list[str] = [i.arg for i in fn_def.args.args]
|
74
|
+
arg_mapping: dict[str, str] = dict(zip(args, arg_names, strict=True))
|
75
|
+
return cast(
|
76
|
+
str,
|
77
|
+
latexify.expression( # noqa: SLF001
|
78
|
+
fn,
|
79
|
+
identifiers=arg_mapping,
|
80
|
+
reduce_assignments=True,
|
81
|
+
)._latex,
|
82
|
+
)
|
83
|
+
|
84
|
+
|
85
|
+
def _table(
|
86
|
+
headers: list[str],
|
87
|
+
rows: list[list[str]],
|
88
|
+
n_columns: int,
|
89
|
+
label: str,
|
90
|
+
short_desc: str,
|
91
|
+
long_desc: str,
|
92
|
+
) -> str:
|
93
|
+
columns = "|".join(["c"] * n_columns)
|
94
|
+
tab = " "
|
95
|
+
|
96
|
+
return "\n".join(
|
97
|
+
[
|
98
|
+
r"\begin{longtable}" + f"{{{columns}}}",
|
99
|
+
tab + " & ".join(headers) + r" \\",
|
100
|
+
tab + r"\hline",
|
101
|
+
tab + r"\endhead",
|
102
|
+
]
|
103
|
+
+ [tab + " & ".join(i) + r" \\" for i in rows]
|
104
|
+
+ [
|
105
|
+
tab + rf"\caption[{short_desc}]{{{long_desc}}}",
|
106
|
+
tab + rf"\label{{table:{label}}}",
|
107
|
+
r"\end{longtable}",
|
108
|
+
]
|
109
|
+
)
|
110
|
+
|
111
|
+
|
112
|
+
def _label(content: str) -> str:
|
113
|
+
return rf"\label{{{content}}}"
|
114
|
+
|
115
|
+
|
116
|
+
def _dmath(content: str) -> str:
|
117
|
+
return rf"""\begin{{dmath*}}
|
118
|
+
{content}
|
119
|
+
\end{{dmath*}}"""
|
120
|
+
|
121
|
+
|
122
|
+
# def _dmath_il(content: str) -> str:
|
123
|
+
# return rf"\begin{{dmath*}}{content}\end{{dmath*}}"
|
124
|
+
|
125
|
+
|
126
|
+
def _part(s: str) -> str:
|
127
|
+
# depth = -1
|
128
|
+
return floatbarrier + rf"\part{{{s}}}"
|
129
|
+
|
130
|
+
|
131
|
+
def _chapter(s: str) -> str:
|
132
|
+
# depth = 0
|
133
|
+
return floatbarrier + rf"\part{{{s}}}"
|
134
|
+
|
135
|
+
|
136
|
+
def _section(s: str) -> str:
|
137
|
+
# depth = 1
|
138
|
+
return floatbarrier + rf"\section{{{s}}}"
|
139
|
+
|
140
|
+
|
141
|
+
def _section_(s: str) -> str:
|
142
|
+
# depth = 1
|
143
|
+
return floatbarrier + rf"\section*{{{s}}}"
|
144
|
+
|
145
|
+
|
146
|
+
def _subsection(s: str) -> str:
|
147
|
+
# depth = 2
|
148
|
+
return floatbarrier + rf"\subsection{{{s}}}"
|
149
|
+
|
150
|
+
|
151
|
+
def _subsection_(s: str) -> str:
|
152
|
+
# depth = 2
|
153
|
+
return floatbarrier + rf"\subsection*{{{s}}}"
|
154
|
+
|
155
|
+
|
156
|
+
def _subsubsection(s: str) -> str:
|
157
|
+
# depth = 3
|
158
|
+
return floatbarrier + rf"\subsubsection{{{s}}}"
|
159
|
+
|
160
|
+
|
161
|
+
def _subsubsection_(s: str) -> str:
|
162
|
+
# depth = 3
|
163
|
+
return floatbarrier + rf"\subsubsection*{{{s}}}"
|
164
|
+
|
165
|
+
|
166
|
+
def _paragraph(s: str) -> str:
|
167
|
+
# depth = 4
|
168
|
+
return rf"\paragraph{{{s}}}"
|
169
|
+
|
170
|
+
|
171
|
+
def _subparagraph(s: str) -> str:
|
172
|
+
# depth = 5
|
173
|
+
return rf"\subparagraph{{{s}}}"
|
174
|
+
|
175
|
+
|
176
|
+
def _math_il(s: str) -> str:
|
177
|
+
return f"${s}$"
|
178
|
+
|
179
|
+
|
180
|
+
def _math(s: str) -> str:
|
181
|
+
return f"$${s}$$"
|
182
|
+
|
183
|
+
|
184
|
+
def _mathrm(s: str) -> str:
|
185
|
+
return rf"\mathrm{{{s}}}"
|
186
|
+
|
187
|
+
|
188
|
+
def _bold(s: str) -> str:
|
189
|
+
return rf"\textbf{{{s}}}"
|
190
|
+
|
191
|
+
|
192
|
+
def _clearpage() -> str:
|
193
|
+
return r"\clearpage"
|
194
|
+
|
195
|
+
|
196
|
+
def _latex_list(rows: list[str]) -> str:
|
197
|
+
return "\n\n".join(rows)
|
198
|
+
|
199
|
+
|
200
|
+
def _latex_list_as_sections(
|
201
|
+
rows: list[tuple[str, str]], sec_fn: Callable[[str], str]
|
202
|
+
) -> str:
|
203
|
+
return "\n\n".join(
|
204
|
+
[
|
205
|
+
"\n".join(
|
206
|
+
(
|
207
|
+
sec_fn(_escape_non_math(name)),
|
208
|
+
content,
|
209
|
+
)
|
210
|
+
)
|
211
|
+
for name, content in rows
|
212
|
+
]
|
213
|
+
)
|
214
|
+
|
215
|
+
|
216
|
+
def _latex_list_as_bold(rows: list[tuple[str, str]]) -> str:
|
217
|
+
return "\n\n".join(
|
218
|
+
[
|
219
|
+
"\n".join(
|
220
|
+
(
|
221
|
+
_bold(_escape_non_math(name)) + r"\\",
|
222
|
+
content,
|
223
|
+
r"\vspace{20pt}",
|
224
|
+
)
|
225
|
+
)
|
226
|
+
for name, content in rows
|
227
|
+
]
|
228
|
+
)
|
229
|
+
|
230
|
+
|
231
|
+
def _stoichiometries_to_latex(stoich: Mapping[str, float | Derived]) -> str:
|
232
|
+
def optional_factor(k: str, v: float) -> str:
|
233
|
+
if v == 1:
|
234
|
+
return _mathrm(k)
|
235
|
+
if v == -1:
|
236
|
+
return f"-{_mathrm(k)}"
|
237
|
+
return f"{v} {cdot} {_mathrm(k)}"
|
238
|
+
|
239
|
+
def latex_for_empty(s: str) -> str:
|
240
|
+
if len(s) == 0:
|
241
|
+
return empty_set
|
242
|
+
return s
|
243
|
+
|
244
|
+
line = []
|
245
|
+
for k, v in stoich.items():
|
246
|
+
if isinstance(v, int | float):
|
247
|
+
line.append(optional_factor(k, v))
|
248
|
+
else:
|
249
|
+
line.append(_fn_to_latex(v.fn, [_rename_latex(i) for i in v.args]))
|
250
|
+
|
251
|
+
line_str = f"{line[0]}"
|
252
|
+
for element in line[1:]:
|
253
|
+
if element.startswith("-"):
|
254
|
+
line_str += f" {element}"
|
255
|
+
else:
|
256
|
+
line_str += f" + {element}"
|
257
|
+
return _math_il(line_str)
|
258
|
+
|
259
|
+
|
260
|
+
@dataclass
|
261
|
+
class TexReaction:
|
262
|
+
"""Collection for reaction."""
|
263
|
+
|
264
|
+
fn: RateFn
|
265
|
+
args: list[str]
|
266
|
+
|
267
|
+
|
268
|
+
@dataclass
|
269
|
+
class TexExport:
|
270
|
+
"""Container for LaTeX export."""
|
271
|
+
|
272
|
+
parameters: dict[str, float]
|
273
|
+
variables: dict[str, float]
|
274
|
+
derived: dict[str, Derived]
|
275
|
+
reactions: dict[str, TexReaction]
|
276
|
+
stoichiometries: dict[str, Mapping[str, float | Derived]]
|
277
|
+
|
278
|
+
@staticmethod
|
279
|
+
def _diff_parameters(
|
280
|
+
p1: dict[str, float],
|
281
|
+
p2: dict[str, float],
|
282
|
+
) -> dict[str, float]:
|
283
|
+
return {k: v for k, v in p2.items() if k not in p1 or p1[k] != v}
|
284
|
+
|
285
|
+
@staticmethod
|
286
|
+
def _diff_variables(p1: dict[str, float], p2: dict[str, float]) -> dict[str, float]:
|
287
|
+
return {k: v for k, v in p2.items() if k not in p1 or p1[k] != v}
|
288
|
+
|
289
|
+
@staticmethod
|
290
|
+
def _diff_derived(
|
291
|
+
p1: dict[str, Derived],
|
292
|
+
p2: dict[str, Derived],
|
293
|
+
) -> dict[str, Derived]:
|
294
|
+
return {k: v for k, v in p2.items() if k not in p1 or p1[k] != v}
|
295
|
+
|
296
|
+
@staticmethod
|
297
|
+
def _diff_reactions(
|
298
|
+
p1: dict[str, TexReaction],
|
299
|
+
p2: dict[str, TexReaction],
|
300
|
+
) -> dict[str, TexReaction]:
|
301
|
+
return {k: v for k, v in p2.items() if k not in p1 or p1[k] != v}
|
302
|
+
|
303
|
+
def __sub__(self, other: object) -> TexExport:
|
304
|
+
"""Return difference of two tex exports."""
|
305
|
+
if not isinstance(other, TexExport):
|
306
|
+
raise TypeError
|
307
|
+
|
308
|
+
return TexExport(
|
309
|
+
parameters=self._diff_parameters(self.parameters, other.parameters),
|
310
|
+
variables=self._diff_variables(self.variables, other.variables),
|
311
|
+
derived=self._diff_derived(self.derived, other.derived),
|
312
|
+
reactions=self._diff_reactions(self.reactions, other.reactions),
|
313
|
+
stoichiometries={
|
314
|
+
k: v
|
315
|
+
for k, v in other.stoichiometries.items()
|
316
|
+
if self.stoichiometries.get(k, {}) != v
|
317
|
+
},
|
318
|
+
)
|
319
|
+
|
320
|
+
def rename_with_glossary(self, gls: dict[str, str]) -> TexExport:
|
321
|
+
"""Rename all elements according to glossary."""
|
322
|
+
|
323
|
+
def _add_gls_if_found(k: str) -> str:
|
324
|
+
if (new := gls.get(k)) is not None:
|
325
|
+
return _abbrev_and_full(new)
|
326
|
+
return k
|
327
|
+
|
328
|
+
return TexExport(
|
329
|
+
parameters={gls.get(k, k): v for k, v in self.parameters.items()},
|
330
|
+
variables={gls.get(k, k): v for k, v in self.variables.items()},
|
331
|
+
derived={
|
332
|
+
gls.get(k, k): Derived(fn=v.fn, args=[gls.get(i, i) for i in v.args])
|
333
|
+
for k, v in self.derived.items()
|
334
|
+
},
|
335
|
+
reactions={
|
336
|
+
_add_gls_if_found(k): TexReaction(
|
337
|
+
fn=v.fn,
|
338
|
+
args=[gls.get(i, i) for i in v.args],
|
339
|
+
)
|
340
|
+
for k, v in self.reactions.items()
|
341
|
+
},
|
342
|
+
stoichiometries={
|
343
|
+
_add_gls_if_found(k): {gls.get(k2, k2): v2 for k2, v2 in v.items()}
|
344
|
+
for k, v in self.stoichiometries.items()
|
345
|
+
},
|
346
|
+
)
|
347
|
+
|
348
|
+
def export_variables(self) -> str:
|
349
|
+
"""Export variables."""
|
350
|
+
return _table(
|
351
|
+
headers=["Model name", "Initial concentration"],
|
352
|
+
rows=[
|
353
|
+
[
|
354
|
+
k,
|
355
|
+
f"{v:.2e}",
|
356
|
+
]
|
357
|
+
for k, v in self.variables.items()
|
358
|
+
],
|
359
|
+
n_columns=2,
|
360
|
+
label="model-vars",
|
361
|
+
short_desc="Model variables",
|
362
|
+
long_desc="Model variables",
|
363
|
+
)
|
364
|
+
|
365
|
+
def export_parameters(self) -> str:
|
366
|
+
"""Export parameters."""
|
367
|
+
return _table(
|
368
|
+
headers=["Parameter name", "Parameter value"],
|
369
|
+
rows=[
|
370
|
+
[_math_il(_mathrm(_escape_non_math(_rename_latex(k)))), f"{v:.2e}"]
|
371
|
+
for k, v in sorted(self.parameters.items())
|
372
|
+
],
|
373
|
+
n_columns=2,
|
374
|
+
label="model-pars",
|
375
|
+
short_desc="Model parameters",
|
376
|
+
long_desc="Model parameters",
|
377
|
+
)
|
378
|
+
|
379
|
+
def export_derived(self) -> str:
|
380
|
+
"""Export derived quantities."""
|
381
|
+
return _latex_list(
|
382
|
+
rows=[
|
383
|
+
_dmath(
|
384
|
+
f"{_rename_latex(k)} = {_fn_to_latex(v.fn, [_rename_latex(i) for i in v.args])}"
|
385
|
+
)
|
386
|
+
for k, v in sorted(self.derived.items())
|
387
|
+
]
|
388
|
+
)
|
389
|
+
|
390
|
+
def export_reactions(self) -> str:
|
391
|
+
"""Export reactions."""
|
392
|
+
return _latex_list(
|
393
|
+
rows=[
|
394
|
+
_dmath(
|
395
|
+
f"{_rename_latex(k)} = {_fn_to_latex(v.fn, [_rename_latex(i) for i in v.args])}"
|
396
|
+
)
|
397
|
+
for k, v in sorted(self.reactions.items())
|
398
|
+
]
|
399
|
+
)
|
400
|
+
|
401
|
+
def export_stoichiometries(self) -> str:
|
402
|
+
"""Export stoichiometries."""
|
403
|
+
return _table(
|
404
|
+
headers=["Rate name", "Stoichiometry"],
|
405
|
+
rows=[
|
406
|
+
[
|
407
|
+
_escape_non_math(_rename_latex(k)),
|
408
|
+
_stoichiometries_to_latex(v),
|
409
|
+
]
|
410
|
+
for k, v in sorted(self.stoichiometries.items())
|
411
|
+
],
|
412
|
+
n_columns=2,
|
413
|
+
label="model-stoichs",
|
414
|
+
short_desc="Model stoichiometries.",
|
415
|
+
long_desc="Model stoichiometries.",
|
416
|
+
)
|
417
|
+
|
418
|
+
def export_all(self) -> str:
|
419
|
+
"""Export all model parts."""
|
420
|
+
sections = []
|
421
|
+
if len(self.variables) > 0:
|
422
|
+
sections.append(
|
423
|
+
(
|
424
|
+
"Variables",
|
425
|
+
self.export_variables(),
|
426
|
+
)
|
427
|
+
)
|
428
|
+
if len(self.parameters) > 0:
|
429
|
+
sections.append(
|
430
|
+
(
|
431
|
+
"Parameters",
|
432
|
+
self.export_parameters(),
|
433
|
+
)
|
434
|
+
)
|
435
|
+
if len(self.derived) > 0:
|
436
|
+
sections.append(
|
437
|
+
(
|
438
|
+
"Derived",
|
439
|
+
self.export_derived(),
|
440
|
+
)
|
441
|
+
)
|
442
|
+
if len(self.reactions) > 0:
|
443
|
+
sections.append(
|
444
|
+
(
|
445
|
+
"Reactions",
|
446
|
+
self.export_reactions(),
|
447
|
+
)
|
448
|
+
)
|
449
|
+
sections.append(
|
450
|
+
(
|
451
|
+
"Stoichiometries",
|
452
|
+
self.export_stoichiometries(),
|
453
|
+
)
|
454
|
+
)
|
455
|
+
return _latex_list_as_sections(sections, _subsubsection_)
|
456
|
+
|
457
|
+
def export_document(
|
458
|
+
self,
|
459
|
+
author: str = "modelbase",
|
460
|
+
title: str = "Model construction",
|
461
|
+
) -> str:
|
462
|
+
"""Export latex document."""
|
463
|
+
content = self.export_all()
|
464
|
+
return rf"""\documentclass{{article}}
|
465
|
+
\usepackage[english]{{babel}}
|
466
|
+
\usepackage[a4paper,top=2cm,bottom=2cm,left=2cm,right=2cm,marginparwidth=1.75cm]{{geometry}}
|
467
|
+
\usepackage{{amsmath, amssymb, array, booktabs,
|
468
|
+
breqn, caption, longtable, mathtools, placeins,
|
469
|
+
ragged2e, tabularx, titlesec, titling}}
|
470
|
+
\newcommand{{\sectionbreak}}{{\clearpage}}
|
471
|
+
\setlength{{\parindent}}{{0pt}}
|
472
|
+
|
473
|
+
\title{{{title}}}
|
474
|
+
\date{{}} % clear date
|
475
|
+
\author{{{author}}}
|
476
|
+
\begin{{document}}
|
477
|
+
\maketitle
|
478
|
+
{content}
|
479
|
+
\end{{document}}
|
480
|
+
"""
|
481
|
+
|
482
|
+
|
483
|
+
def _to_tex_export(self: Model) -> TexExport:
|
484
|
+
return TexExport(
|
485
|
+
parameters=self.parameters,
|
486
|
+
variables=self.variables,
|
487
|
+
derived=self.derived,
|
488
|
+
reactions={k: TexReaction(v.fn, v.args) for k, v in self.reactions.items()},
|
489
|
+
stoichiometries={k: v.stoichiometry for k, v in self.reactions.items()},
|
490
|
+
)
|
491
|
+
|
492
|
+
|
493
|
+
def to_tex(
|
494
|
+
model: Model,
|
495
|
+
gls: dict[str, str] | None = None,
|
496
|
+
) -> str:
|
497
|
+
"""Export as LaTeX."""
|
498
|
+
gls = default_init(gls)
|
499
|
+
return _to_tex_export(model).rename_with_glossary(gls).export_document()
|
500
|
+
|
501
|
+
|
502
|
+
def get_model_tex_diff(
|
503
|
+
m1: Model,
|
504
|
+
m2: Model,
|
505
|
+
gls: dict[str, str] | None = None,
|
506
|
+
) -> str:
|
507
|
+
"""Create LaTeX diff of two models."""
|
508
|
+
gls = default_init(gls)
|
509
|
+
section_label = "sec:model-diff"
|
510
|
+
|
511
|
+
return f"""{' start autogenerated ':%^60}
|
512
|
+
{_clearpage()}
|
513
|
+
{_subsubsection('Model changes')}{_label(section_label)}
|
514
|
+
{(
|
515
|
+
(_to_tex_export(m1) - _to_tex_export(m2))
|
516
|
+
.rename_with_glossary(gls)
|
517
|
+
.export_all()
|
518
|
+
)}
|
519
|
+
{_clearpage()}
|
520
|
+
{' end autogenerated ':%^60}
|
521
|
+
"""
|