mxlpy 0.10.0__py3-none-any.whl → 0.11.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 +2 -0
- mxlpy/report.py +209 -0
- {mxlpy-0.10.0.dist-info → mxlpy-0.11.0.dist-info}/METADATA +1 -1
- {mxlpy-0.10.0.dist-info → mxlpy-0.11.0.dist-info}/RECORD +6 -5
- {mxlpy-0.10.0.dist-info → mxlpy-0.11.0.dist-info}/WHEEL +0 -0
- {mxlpy-0.10.0.dist-info → mxlpy-0.11.0.dist-info}/licenses/LICENSE +0 -0
mxlpy/__init__.py
CHANGED
mxlpy/report.py
ADDED
@@ -0,0 +1,209 @@
|
|
1
|
+
"""Generate a report comparing two models."""
|
2
|
+
|
3
|
+
from collections.abc import Callable
|
4
|
+
from datetime import UTC, datetime
|
5
|
+
from pathlib import Path
|
6
|
+
from typing import cast
|
7
|
+
|
8
|
+
import sympy
|
9
|
+
|
10
|
+
from mxlpy.meta.source_tools import fn_to_sympy
|
11
|
+
from mxlpy.model import Model
|
12
|
+
|
13
|
+
__all__ = ["AnalysisFn", "markdown"]
|
14
|
+
|
15
|
+
type AnalysisFn = Callable[[Model, Model, Path], tuple[str, Path]]
|
16
|
+
|
17
|
+
|
18
|
+
def _list_of_symbols(args: list[str]) -> list[sympy.Symbol | sympy.Expr]:
|
19
|
+
return [sympy.Symbol(arg) for arg in args]
|
20
|
+
|
21
|
+
|
22
|
+
def _new_removed_changed[T](
|
23
|
+
d1: dict[str, T], d2: dict[str, T]
|
24
|
+
) -> tuple[dict[str, T], list[str], dict[str, tuple[T, T]]]:
|
25
|
+
s1 = set(d1)
|
26
|
+
s2 = set(d2)
|
27
|
+
|
28
|
+
removed = sorted(s1 - s2)
|
29
|
+
new = {k: d2[k] for k in s2 - s1}
|
30
|
+
changed = {k: (v1, v2) for k in s1 - set(removed) if (v1 := d1[k]) != (v2 := d2[k])}
|
31
|
+
return new, removed, changed
|
32
|
+
|
33
|
+
|
34
|
+
def markdown(
|
35
|
+
m1: Model,
|
36
|
+
m2: Model,
|
37
|
+
analyses: list[AnalysisFn] | None = None,
|
38
|
+
rel_change: float = 1e-2,
|
39
|
+
img_path: Path = Path(),
|
40
|
+
) -> str:
|
41
|
+
"""Generate a markdown report comparing two models.
|
42
|
+
|
43
|
+
Args:
|
44
|
+
m1: The first model to compare.
|
45
|
+
m2: The second model to compare.
|
46
|
+
analyses: A list of functions that take a Path and return a tuple of a string and a Path. Defaults to None.
|
47
|
+
rel_change: The relative change threshold for numerical differences. Defaults to 1e-2.
|
48
|
+
img_path: The path to save images. Defaults to Path().
|
49
|
+
|
50
|
+
"""
|
51
|
+
content: list[str] = [
|
52
|
+
f"# Report: {datetime.now(UTC).strftime('%Y-%m-%d')}",
|
53
|
+
]
|
54
|
+
|
55
|
+
# Variables
|
56
|
+
new_variables, removed_variables, changed_variables = _new_removed_changed(
|
57
|
+
m1.variables, m2.variables
|
58
|
+
)
|
59
|
+
variables = []
|
60
|
+
variables.extend(
|
61
|
+
f"| <span style='color:green'>{k}<span> | - | {v} |"
|
62
|
+
for k, v in new_variables.items()
|
63
|
+
)
|
64
|
+
variables.extend(
|
65
|
+
f"| <span style='color: orange'>{k}</span> | {v1} | {v2} |"
|
66
|
+
for k, (v1, v2) in changed_variables.items()
|
67
|
+
)
|
68
|
+
variables.extend(
|
69
|
+
f"| <span style='color:red'>{k}</span> | - | - |" for k in removed_variables
|
70
|
+
)
|
71
|
+
if len(variables) >= 1:
|
72
|
+
content.extend(
|
73
|
+
(
|
74
|
+
"## Variables\n",
|
75
|
+
"| Name | Old Value | New Value |",
|
76
|
+
"| ---- | --------- | --------- |",
|
77
|
+
)
|
78
|
+
)
|
79
|
+
content.append("\n".join(variables))
|
80
|
+
|
81
|
+
# Parameters
|
82
|
+
new_parameters, removed_parameters, changed_parameters = _new_removed_changed(
|
83
|
+
m1.parameters, m2.parameters
|
84
|
+
)
|
85
|
+
pars = []
|
86
|
+
pars.extend(
|
87
|
+
f"| <span style='color:green'>{k}<span> | - | {v} |"
|
88
|
+
for k, v in new_parameters.items()
|
89
|
+
)
|
90
|
+
pars.extend(
|
91
|
+
f"| <span style='color: orange'>{k}</span> | {v1} | {v2} |"
|
92
|
+
for k, (v1, v2) in changed_parameters.items()
|
93
|
+
)
|
94
|
+
pars.extend(
|
95
|
+
f"| <span style='color:red'>{k}</span> | - | - |" for k in removed_parameters
|
96
|
+
)
|
97
|
+
if len(pars) >= 1:
|
98
|
+
content.extend(
|
99
|
+
(
|
100
|
+
"## Parameters\n",
|
101
|
+
"| Name | Old Value | New Value |",
|
102
|
+
"| ---- | --------- | --------- |",
|
103
|
+
)
|
104
|
+
)
|
105
|
+
content.append("\n".join(pars))
|
106
|
+
|
107
|
+
# Derived
|
108
|
+
new_derived, removed_derived, changed_derived = _new_removed_changed(
|
109
|
+
m1.derived, m2.derived
|
110
|
+
)
|
111
|
+
derived = []
|
112
|
+
for k, v in new_derived.items():
|
113
|
+
expr = sympy.latex(fn_to_sympy(v.fn, _list_of_symbols(v.args)))
|
114
|
+
derived.append(f"| <span style='color:green'>{k}<span> | - | ${expr}$ |")
|
115
|
+
for k, (v1, v2) in changed_derived.items():
|
116
|
+
expr1 = sympy.latex(fn_to_sympy(v1.fn, _list_of_symbols(v1.args)))
|
117
|
+
expr2 = sympy.latex(fn_to_sympy(v2.fn, _list_of_symbols(v2.args)))
|
118
|
+
derived.append(
|
119
|
+
f"| <span style='color: orange'>{k}</span> | ${expr1}$ | ${expr2}$ |"
|
120
|
+
)
|
121
|
+
derived.extend(
|
122
|
+
f"| <span style='color:red'>{k}</span> | - | - |" for k in removed_derived
|
123
|
+
)
|
124
|
+
if len(derived) >= 1:
|
125
|
+
content.extend(
|
126
|
+
(
|
127
|
+
"## Derived\n",
|
128
|
+
"| Name | Old Value | New Value |",
|
129
|
+
"| ---- | --------- | --------- |",
|
130
|
+
)
|
131
|
+
)
|
132
|
+
content.append("\n".join(derived))
|
133
|
+
|
134
|
+
# Reactions
|
135
|
+
new_reactions, removed_reactions, changed_reactions = _new_removed_changed(
|
136
|
+
m1.reactions, m2.reactions
|
137
|
+
)
|
138
|
+
reactions = []
|
139
|
+
for k, v in new_reactions.items():
|
140
|
+
expr = sympy.latex(fn_to_sympy(v.fn, _list_of_symbols(v.args)))
|
141
|
+
reactions.append(f"| <span style='color:green'>{k}<span> | - | ${expr}$ |")
|
142
|
+
for k, (v1, v2) in changed_reactions.items():
|
143
|
+
expr1 = sympy.latex(fn_to_sympy(v1.fn, _list_of_symbols(v1.args)))
|
144
|
+
expr2 = sympy.latex(fn_to_sympy(v2.fn, _list_of_symbols(v2.args)))
|
145
|
+
reactions.append(
|
146
|
+
f"| <span style='color: orange'>{k}</span> | ${expr1}$ | ${expr2}$ |"
|
147
|
+
)
|
148
|
+
reactions.extend(
|
149
|
+
f"| <span style='color:red'>{k}</span> | - | - |" for k in removed_reactions
|
150
|
+
)
|
151
|
+
|
152
|
+
if len(reactions) >= 1:
|
153
|
+
content.extend(
|
154
|
+
(
|
155
|
+
"## Reactions\n",
|
156
|
+
"| Name | Old Value | New Value |",
|
157
|
+
"| ---- | --------- | --------- |",
|
158
|
+
)
|
159
|
+
)
|
160
|
+
content.append("\n".join(reactions))
|
161
|
+
|
162
|
+
# Now check for any numerical differences
|
163
|
+
dependent = []
|
164
|
+
d1 = m1.get_dependent()
|
165
|
+
d2 = m2.get_dependent()
|
166
|
+
rel_diff = ((d1 - d2) / d1).dropna()
|
167
|
+
for k, v in rel_diff.loc[rel_diff.abs() >= rel_change].items():
|
168
|
+
k = cast(str, k)
|
169
|
+
dependent.append(
|
170
|
+
f"| <span style='color:orange'>{k}</span> | {d1[k]:.2f} | {d2[k]:.2f} | {v:.1%} "
|
171
|
+
)
|
172
|
+
if len(dependent) >= 1:
|
173
|
+
content.extend(
|
174
|
+
(
|
175
|
+
"## Numerical differences of dependent values\n",
|
176
|
+
"| Name | Old Value | New Value | Relative Change | ",
|
177
|
+
"| ---- | --------- | --------- | --------------- | ",
|
178
|
+
)
|
179
|
+
)
|
180
|
+
content.append("\n".join(dependent))
|
181
|
+
|
182
|
+
rhs = []
|
183
|
+
r1 = m1.get_right_hand_side()
|
184
|
+
r2 = m2.get_right_hand_side()
|
185
|
+
rel_diff = ((r1 - r2) / r1).dropna()
|
186
|
+
for k, v in rel_diff.loc[rel_diff.abs() >= rel_change].items():
|
187
|
+
k = cast(str, k)
|
188
|
+
rhs.append(
|
189
|
+
f"| <span style='color:orange'>{k}</span> | {r1[k]:.2f} | {r2[k]:.2f} | {v:.1%} "
|
190
|
+
)
|
191
|
+
if len(rhs) >= 1:
|
192
|
+
content.extend(
|
193
|
+
(
|
194
|
+
"## Numerical differences of right hand side values\n",
|
195
|
+
"| Name | Old Value | New Value | Relative Change | ",
|
196
|
+
"| ---- | --------- | --------- | --------------- | ",
|
197
|
+
)
|
198
|
+
)
|
199
|
+
content.append("\n".join(rhs))
|
200
|
+
|
201
|
+
# Comparison functions
|
202
|
+
if analyses is not None:
|
203
|
+
for f in analyses:
|
204
|
+
name, img_path = f(m1, m2, img_path)
|
205
|
+
content.append(name)
|
206
|
+
# content.append(f"")
|
207
|
+
content.append(f"<img src='{img_path}' alt='{name}' width='500'/>")
|
208
|
+
|
209
|
+
return "\n".join(content)
|
@@ -1,4 +1,4 @@
|
|
1
|
-
mxlpy/__init__.py,sha256=
|
1
|
+
mxlpy/__init__.py,sha256=XZYNFyDC5rWcKi6139mq04cROI7LwJvxB2_3ApKwcvY,4194
|
2
2
|
mxlpy/distributions.py,sha256=ce6RTqn19YzMMec-u09fSIUA8A92M6rehCuHuXWcX7A,8734
|
3
3
|
mxlpy/fit.py,sha256=vJ0AWCvERxPkxgwuOmL9rsH4vXnlBSco4vG-5X98RK8,8085
|
4
4
|
mxlpy/fns.py,sha256=ct_RFj9koW8vXHyr27GnbZUHUS_zfs4rDysybuFiOaU,4599
|
@@ -14,6 +14,7 @@ mxlpy/parameterise.py,sha256=2jMhhO-bHTFP_0kXercJekeATAZYBg5FrK1MQ_mWGpk,654
|
|
14
14
|
mxlpy/paths.py,sha256=TK2wO4N9lG-UV1JGfeB64q48JVDbwqIUj63rl55MKuQ,1022
|
15
15
|
mxlpy/plot.py,sha256=z1JW7Si1JQyNMj_MMLkgbLkOkSjVcfAZJGjm_WqCgT4,24355
|
16
16
|
mxlpy/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
17
|
+
mxlpy/report.py,sha256=TYG-cKo9u6izlnjB4vgu6I2hYrtS9e8wYc_A4MrHq-g,6982
|
17
18
|
mxlpy/scan.py,sha256=3k4084d4eg-3Ok24cL8KWPF7P1L6fbEtiaOM9VC5Eko,19210
|
18
19
|
mxlpy/simulator.py,sha256=T9t2jZ6U5NyK1ICF1UkST8M8v4EPV_H98kzZ4TvQK-w,20115
|
19
20
|
mxlpy/types.py,sha256=ksdn76Sdw0XhQEpQepcETvuGqcJolfrmbIRBT0R_2Bg,13612
|
@@ -43,7 +44,7 @@ mxlpy/surrogates/_torch.py,sha256=E_1eDUlPSVFwROkdMDCqYwwHE-61pjNMJWotnhjzge0,58
|
|
43
44
|
mxlpy/symbolic/__init__.py,sha256=3hQjCMw8-6iOxeUdfnCg8449fF_BRF2u6lCM1GPpkRY,222
|
44
45
|
mxlpy/symbolic/strikepy.py,sha256=r6nRtckV1nxKq3i1bYYWZOkzwZ5XeKQuZM5ck44vUo0,20010
|
45
46
|
mxlpy/symbolic/symbolic_model.py,sha256=YL9noEeP3_0DoKXwMPELtfmPuP6mgNcLIJgDRCkyB7A,2434
|
46
|
-
mxlpy-0.
|
47
|
-
mxlpy-0.
|
48
|
-
mxlpy-0.
|
49
|
-
mxlpy-0.
|
47
|
+
mxlpy-0.11.0.dist-info/METADATA,sha256=N1-dMC1k0QUlSF2ZN_URFUyg119f9QUz7xcBS2Ia_PQ,4536
|
48
|
+
mxlpy-0.11.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
49
|
+
mxlpy-0.11.0.dist-info/licenses/LICENSE,sha256=bEzjyjy1stQhfRDVaVHa3xV1x-V8emwdlbMvYO8Zo84,35073
|
50
|
+
mxlpy-0.11.0.dist-info/RECORD,,
|
File without changes
|
File without changes
|