Skip to content

Commit 9e65449

Browse files
committed
Merge branch 'develop' into 330_truncated
2 parents 51367db + 8614c02 commit 9e65449

26 files changed

+2617
-872
lines changed

doc/conf.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@
4747
]
4848

4949
intersphinx_mapping = {
50+
"petab": ("https://petab.readthedocs.io/en/latest/", None),
5051
"pandas": ("https://pandas.pydata.org/docs/", None),
5152
"numpy": ("https://numpy.org/devdocs/", None),
5253
"sympy": ("https://docs.sympy.org/latest/", None),
@@ -62,6 +63,7 @@
6263
exclude_patterns = [
6364
"build/doctrees",
6465
"build/html",
66+
"build/jupyter_execute",
6567
"**.ipynb_checkpoints",
6668
"logo/LICENSE.md",
6769
]
@@ -71,10 +73,10 @@
7173
autosummary_generate = True
7274

7375
autodoc_default_options = {
74-
"members": None,
76+
"members": True,
7577
"imported-members": ["petab"],
76-
"inherited-members": None,
77-
"show-inheritance": None,
78+
"show-inheritance": True,
79+
"undoc-members": True,
7880
}
7981

8082
# For some reason causes sphinx import errors otherwise

doc/development.rst

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,3 +24,67 @@ Python compatibility
2424
--------------------
2525

2626
We follow `numpy's Python support policy <https://numpy.org/neps/nep-0029-deprecation_policy.html>`_.
27+
28+
Release process
29+
---------------
30+
31+
1. Update the version number in ``petab/version.py``.
32+
33+
2. Update the changelog in ``doc/CHANGELOG.md``.
34+
The update content can be generated automatically:
35+
draft a new dummy GitHub release with a dummy tag and the ``develop``
36+
branch, then click :guilabel:`Generate release notes`.
37+
38+
3. Create a pull request with the to-be-released changes to the main branch
39+
(usually from ``develop``).
40+
41+
4. Once the pull request is merged, create a new release on GitHub.
42+
Make sure to set the tag to the version number prefixed with 'v'
43+
(e.g., ``v1.0.0``), and the release title to ``libpetab-python $RELEASE_TAG``
44+
(e.g., ``libpetab-python v1.0.0``).
45+
46+
5. Check that the release is now available on PyPI.
47+
The upload to PyPI is performed automatically by a GitHub Actions workflow,
48+
which may take a few minutes to complete.
49+
50+
6. Merge the main branch back into the `develop` branch.
51+
52+
Style guide
53+
-----------
54+
55+
Code style
56+
~~~~~~~~~~
57+
58+
We use pre-commit with ruff to enforce code style. To install pre-commit and
59+
the pre-commit hooks, run:
60+
61+
.. code-block:: bash
62+
63+
pip install pre-commit
64+
pre-commit install
65+
66+
To run the pre-commit checks manually on all, not just the modified files, run:
67+
68+
.. code-block:: bash
69+
70+
pre-commit run --all-files
71+
72+
Documentation style
73+
~~~~~~~~~~~~~~~~~~~
74+
75+
We use `Sphinx <https://www.sphinx-doc.org/>`_ to generate the documentation.
76+
The documentation is written in `reStructuredText <https://www.sphinx-doc.org/en/master/usage/restructuredtext/index.html>`_.
77+
78+
We use the `sphinx docstring-style <https://sphinx-rtd-tutorial.readthedocs.io/en/latest/docstrings.html>`__ for new code.
79+
The ``:param [ParamName]:`` and ``:return:`` statements are important when
80+
applicable.
81+
Manual type annotations (``:type [ParamName]:``) are redundant and should be
82+
avoided.
83+
84+
To build the documentation, run:
85+
86+
.. code-block:: bash
87+
88+
cd doc
89+
make html
90+
# then open `build/html/index.html` in a browser

doc/modules.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ API Reference
1616
petab.v1.core
1717
petab.v1.distributions
1818
petab.v1.lint
19+
petab.v1.math
1920
petab.v1.measurements
2021
petab.v1.models
2122
petab.v1.observables
@@ -31,6 +32,7 @@ API Reference
3132
petab.v1.yaml
3233
petab.v2
3334
petab.v2.C
35+
petab.v2.core
3436
petab.v2.experiments
3537
petab.v2.lint
3638
petab.v2.models

petab/v1/lint.py

Lines changed: 26 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,9 @@
5353
"observable_table_has_nontrivial_noise_formula",
5454
]
5555

56+
#: Regular expression pattern for valid PEtab IDs
57+
_petab_id_pattern = re.compile(r"^[a-zA-Z_]\w*$")
58+
5659

5760
def _check_df(df: pd.DataFrame, req_cols: Iterable, name: str) -> None:
5861
"""Check if given columns are present in DataFrame
@@ -903,21 +906,6 @@ def lint_problem(problem: "petab.Problem") -> bool:
903906
else:
904907
logger.warning("Model not available. Skipping.")
905908

906-
if problem.measurement_df is not None:
907-
logger.info("Checking measurement table...")
908-
try:
909-
check_measurement_df(problem.measurement_df, problem.observable_df)
910-
911-
if problem.condition_df is not None:
912-
assert_measurement_conditions_present_in_condition_table(
913-
problem.measurement_df, problem.condition_df
914-
)
915-
except AssertionError as e:
916-
logger.error(e)
917-
errors_occurred = True
918-
else:
919-
logger.warning("Measurement table not available. Skipping.")
920-
921909
if problem.condition_df is not None:
922910
logger.info("Checking condition table...")
923911
try:
@@ -950,6 +938,21 @@ def lint_problem(problem: "petab.Problem") -> bool:
950938
else:
951939
logger.warning("Observable table not available. Skipping.")
952940

941+
if problem.measurement_df is not None:
942+
logger.info("Checking measurement table...")
943+
try:
944+
check_measurement_df(problem.measurement_df, problem.observable_df)
945+
946+
if problem.condition_df is not None:
947+
assert_measurement_conditions_present_in_condition_table(
948+
problem.measurement_df, problem.condition_df
949+
)
950+
except AssertionError as e:
951+
logger.error(e)
952+
errors_occurred = True
953+
else:
954+
logger.warning("Measurement table not available. Skipping.")
955+
953956
if problem.parameter_df is not None:
954957
logger.info("Checking parameter table...")
955958
try:
@@ -1041,10 +1044,13 @@ def assert_model_parameters_in_condition_or_parameter_table(
10411044
mapping_df[MODEL_ENTITY_ID],
10421045
strict=True,
10431046
)
1044-
# mapping table entities mapping to already allowed parameters
1045-
if to_id in allowed_in_condition_cols
1046-
# mapping table entities mapping to species
1047-
or model.is_state_variable(to_id)
1047+
if not pd.isna(to_id)
1048+
and (
1049+
# mapping table entities mapping to already allowed parameters
1050+
to_id in allowed_in_condition_cols
1051+
# mapping table entities mapping to species
1052+
or model.is_state_variable(to_id)
1053+
)
10481054
}
10491055

10501056
allowed_in_parameter_table = (
@@ -1186,7 +1192,7 @@ def is_valid_identifier(x: str) -> bool:
11861192
if pd.isna(x):
11871193
return False
11881194

1189-
return re.match(r"^[a-zA-Z_]\w*$", x) is not None
1195+
return _petab_id_pattern.match(x) is not None
11901196

11911197

11921198
def check_ids(ids: Iterable[str], kind: str = "") -> None:

petab/v1/math/SympyVisitor.py

Lines changed: 37 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -39,8 +39,12 @@
3939
}
4040
_unary_funcs = {
4141
"exp": sp.exp,
42-
"log10": lambda x: -sp.oo if x.is_zero is True else sp.log(x, 10),
43-
"log2": lambda x: -sp.oo if x.is_zero is True else sp.log(x, 2),
42+
"log10": lambda x, evaluate=True: -sp.oo
43+
if x.is_zero is True
44+
else sp.log(x, 10, evaluate=evaluate),
45+
"log2": lambda x, evaluate=True: -sp.oo
46+
if x.is_zero is True
47+
else sp.log(x, 2, evaluate=evaluate),
4448
"ln": sp.log,
4549
"sqrt": sp.sqrt,
4650
"abs": sp.Abs,
@@ -75,8 +79,14 @@ class MathVisitorSympy(PetabMathExprParserVisitor):
7579
7680
For a general introduction to ANTLR4 visitors, see:
7781
https://github.com/antlr/antlr4/blob/7d4cea92bc3f7d709f09c3f1ac77c5bbc71a6749/doc/python-target.md
82+
83+
:param evaluate: Whether to evaluate the expression.
7884
"""
7985

86+
def __init__(self, evaluate=True):
87+
super().__init__()
88+
self.evaluate = evaluate
89+
8090
def visitPetabExpression(
8191
self, ctx: PetabMathExprParser.PetabExpressionContext
8292
) -> sp.Expr | sp.Basic:
@@ -101,9 +111,17 @@ def visitMultExpr(
101111
operand1 = bool2num(self.visit(ctx.getChild(0)))
102112
operand2 = bool2num(self.visit(ctx.getChild(2)))
103113
if ctx.ASTERISK():
104-
return operand1 * operand2
114+
return sp.Mul(operand1, operand2, evaluate=self.evaluate)
105115
if ctx.SLASH():
106-
return operand1 / operand2
116+
return (
117+
operand1 / operand2
118+
if self.evaluate
119+
else sp.Mul(
120+
operand1,
121+
sp.Pow(operand2, -1, evaluate=False),
122+
evaluate=False,
123+
)
124+
)
107125

108126
raise AssertionError(f"Unexpected expression: {ctx.getText()}")
109127

@@ -112,9 +130,9 @@ def visitAddExpr(self, ctx: PetabMathExprParser.AddExprContext) -> sp.Expr:
112130
op1 = bool2num(self.visit(ctx.getChild(0)))
113131
op2 = bool2num(self.visit(ctx.getChild(2)))
114132
if ctx.PLUS():
115-
return op1 + op2
133+
return sp.Add(op1, op2, evaluate=self.evaluate)
116134
if ctx.MINUS():
117-
return op1 - op2
135+
return sp.Add(op1, -op2, evaluate=self.evaluate)
118136

119137
raise AssertionError(
120138
f"Unexpected operator: {ctx.getChild(1).getText()} "
@@ -146,28 +164,32 @@ def visitFunctionCall(
146164
f"Unexpected number of arguments: {len(args)} "
147165
f"in {ctx.getText()}"
148166
)
149-
return _trig_funcs[func_name](*args)
167+
return _trig_funcs[func_name](*args, evaluate=self.evaluate)
150168
if func_name in _unary_funcs:
151169
if len(args) != 1:
152170
raise AssertionError(
153171
f"Unexpected number of arguments: {len(args)} "
154172
f"in {ctx.getText()}"
155173
)
156-
return _unary_funcs[func_name](*args)
174+
return _unary_funcs[func_name](*args, evaluate=self.evaluate)
157175
if func_name in _binary_funcs:
158176
if len(args) != 2:
159177
raise AssertionError(
160178
f"Unexpected number of arguments: {len(args)} "
161179
f"in {ctx.getText()}"
162180
)
163-
return _binary_funcs[func_name](*args)
181+
return _binary_funcs[func_name](*args, evaluate=self.evaluate)
164182
if func_name == "log":
165183
if len(args) not in [1, 2]:
166184
raise AssertionError(
167185
f"Unexpected number of arguments: {len(args)} "
168186
f"in {ctx.getText()}"
169187
)
170-
return -sp.oo if args[0].is_zero is True else sp.log(*args)
188+
return (
189+
-sp.oo
190+
if args[0].is_zero is True
191+
else sp.log(*args, evaluate=self.evaluate)
192+
)
171193

172194
if func_name == "piecewise":
173195
if (len(args) - 1) % 2 != 0:
@@ -184,7 +206,7 @@ def visitFunctionCall(
184206
args[::2], args[1::2], strict=True
185207
)
186208
)
187-
return sp.Piecewise(*sp_args)
209+
return sp.Piecewise(*sp_args, evaluate=self.evaluate)
188210

189211
raise ValueError(f"Unknown function: {ctx.getText()}")
190212

@@ -203,7 +225,7 @@ def visitPowerExpr(
203225
)
204226
operand1 = bool2num(self.visit(ctx.getChild(0)))
205227
operand2 = bool2num(self.visit(ctx.getChild(2)))
206-
return sp.Pow(operand1, operand2)
228+
return sp.Pow(operand1, operand2, evaluate=self.evaluate)
207229

208230
def visitUnaryExpr(
209231
self, ctx: PetabMathExprParser.UnaryExprContext
@@ -240,7 +262,7 @@ def visitComparisonExpr(
240262
if op in ops:
241263
lhs = bool2num(lhs)
242264
rhs = bool2num(rhs)
243-
return ops[op](lhs, rhs)
265+
return ops[op](lhs, rhs, evaluate=self.evaluate)
244266

245267
raise AssertionError(f"Unexpected operator: {op}")
246268

@@ -301,4 +323,6 @@ def num2bool(x: sp.Basic | sp.Expr) -> sp.Basic | sp.Expr:
301323
return sp.false
302324
if x.is_zero is False:
303325
return sp.true
326+
if isinstance(x, Boolean):
327+
return x
304328
return sp.Piecewise((True, x != 0.0), (False, True))

petab/v1/math/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
"""Functions for parsing and evaluating mathematical expressions."""
22

3+
from .printer import PetabStrPrinter, petab_math_str # noqa: F401
34
from .sympify import sympify_petab # noqa: F401

0 commit comments

Comments
 (0)