Skip to content

Commit 6ffb315

Browse files
authored
Merge pull request #170 from PEtab-dev/release_0.1.28
Release 0.1.28
2 parents 286494a + 84d4e5f commit 6ffb315

22 files changed

+316
-242
lines changed

.github/workflows/ci_tests.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ jobs:
1111
strategy:
1212
matrix:
1313
platform: [windows-latest, macos-latest, ubuntu-latest]
14-
python-version: ["3.7", "3.10"]
14+
python-version: ["3.8", "3.10"]
1515
runs-on: ${{ matrix.platform }}
1616

1717
steps:

CHANGELOG.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,22 @@
22

33
## 0.1 series
44

5+
### 0.1.28
6+
7+
* Fixed validation for output parameters columns in the condition table
8+
by @dweindl in https://github.com/PEtab-dev/libpetab-python/pull/161
9+
* Added Python support policy
10+
by @dweindl in https://github.com/PEtab-dev/libpetab-python/pull/162
11+
* Fixed typehints and deprecation warning
12+
by @dweindl in https://github.com/PEtab-dev/libpetab-python/pull/165
13+
* Fixed SBML validation
14+
by @dweindl in https://github.com/PEtab-dev/libpetab-python/pull/168
15+
* Fixed deprecation warning from `get_model_for_condition`
16+
by @dweindl in https://github.com/PEtab-dev/libpetab-python/pull/169
17+
18+
**Full Changelog**:
19+
https://github.com/PEtab-dev/libpetab-python/compare/v0.1.27...v0.1.28
20+
521
### 0.1.27
622

723
Features:

README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,8 @@ and the easiest way to install it is running
2222

2323
pip3 install petab
2424

25-
It will require Python>=3.7.1 to run.
25+
It will require Python>=3.8 to run. (We are following the
26+
[numpy Python support policy](https://numpy.org/neps/nep-0029-deprecation_policy.html)).
2627

2728
Development versions of the PEtab library can be installed using
2829

petab/conditions.py

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -93,9 +93,8 @@ def get_parametric_overrides(condition_df: pd.DataFrame) -> List[str]:
9393
Returns:
9494
List of parameter IDs that are mapped in a condition-specific way
9595
"""
96-
constant_parameters = list(
97-
set(condition_df.columns.values.tolist()) - {CONDITION_ID,
98-
CONDITION_NAME})
96+
constant_parameters = (set(condition_df.columns.values.tolist())
97+
- {CONDITION_ID, CONDITION_NAME})
9998
result = []
10099

101100
for column in constant_parameters:
@@ -104,7 +103,5 @@ def get_parametric_overrides(condition_df: pd.DataFrame) -> List[str]:
104103

105104
floatified = condition_df.loc[:, column].apply(core.to_float_if_float)
106105

107-
for x in floatified:
108-
if not isinstance(x, float):
109-
result.append(x)
106+
result.extend(x for x in floatified if not isinstance(x, float))
110107
return result

petab/core.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
'create_combine_archive', 'unique_preserve_order']
2121

2222

23-
def get_simulation_df(simulation_file: str) -> pd.DataFrame:
23+
def get_simulation_df(simulation_file: Union[str, Path]) -> pd.DataFrame:
2424
"""Read PEtab simulation table
2525
2626
Arguments:
@@ -33,7 +33,7 @@ def get_simulation_df(simulation_file: str) -> pd.DataFrame:
3333
float_precision='round_trip')
3434

3535

36-
def write_simulation_df(df: pd.DataFrame, filename: str) -> None:
36+
def write_simulation_df(df: pd.DataFrame, filename: Union[str, Path]) -> None:
3737
"""Write PEtab simulation table
3838
3939
Arguments:
@@ -91,7 +91,8 @@ def get_notnull_columns(df: pd.DataFrame, candidates: Iterable):
9191

9292

9393
def flatten_timepoint_specific_output_overrides(
94-
petab_problem: 'petab.problem.Problem') -> None:
94+
petab_problem: 'petab.problem.Problem',
95+
) -> None:
9596
"""Flatten timepoint-specific output parameter overrides.
9697
9798
If the PEtab problem definition has timepoint-specific

petab/lint.py

Lines changed: 38 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -60,9 +60,7 @@ def _check_df(df: pd.DataFrame, req_cols: Iterable, name: str) -> None:
6060
Raises:
6161
AssertionError: if a column is missing
6262
"""
63-
cols_set = df.columns.values
64-
missing_cols = set(req_cols) - set(cols_set)
65-
if missing_cols:
63+
if missing_cols := set(req_cols) - set(df.columns.values):
6664
raise AssertionError(
6765
f"DataFrame {name} requires the columns {missing_cols}.")
6866

@@ -85,12 +83,16 @@ def assert_no_leading_trailing_whitespace(
8583

8684

8785
def check_condition_df(
88-
df: pd.DataFrame, model: Optional[Model] = None) -> None:
86+
df: pd.DataFrame,
87+
model: Optional[Model] = None,
88+
observable_df: Optional[pd.DataFrame] = None
89+
) -> None:
8990
"""Run sanity checks on PEtab condition table
9091
9192
Arguments:
9293
df: PEtab condition DataFrame
9394
model: Model for additional checking of parameter IDs
95+
observable_df: PEtab observables DataFrame
9496
9597
Raises:
9698
AssertionError: in case of problems
@@ -101,7 +103,7 @@ def check_condition_df(
101103
_check_df(df, req_cols, "condition")
102104

103105
# Check for correct index
104-
if not df.index.name == CONDITION_ID:
106+
if df.index.name != CONDITION_ID:
105107
raise AssertionError(
106108
f"Condition table has wrong index {df.index.name}."
107109
f"expected {CONDITION_ID}.")
@@ -119,6 +121,9 @@ def check_condition_df(
119121

120122
if model is not None:
121123
allowed_cols = set(model.get_valid_ids_for_condition_table())
124+
if observable_df is not None:
125+
allowed_cols |= set(petab.get_output_parameters(
126+
model=model, observable_df=observable_df))
122127
for column_name in df.columns:
123128
if column_name != CONDITION_NAME \
124129
and column_name not in allowed_cols:
@@ -154,14 +159,9 @@ def check_measurement_df(df: pd.DataFrame,
154159
df[column_name].values, column_name)
155160

156161
if observable_df is not None:
157-
# Check all observables are defined
158-
observables_defined = set(observable_df.index.values)
159-
observables_used = set(df[OBSERVABLE_ID])
160-
observables_undefined = observables_used - observables_defined
161-
if observables_undefined:
162-
raise ValueError(f"Observables {observables_undefined} used in "
163-
"measurement table but not defined in "
164-
"observables table.")
162+
assert_measured_observables_defined(df, observable_df)
163+
measurements.assert_overrides_match_parameter_count(
164+
df, observable_df)
165165

166166
if OBSERVABLE_TRANSFORMATION in observable_df:
167167
# Check for positivity of measurements in case of
@@ -176,11 +176,6 @@ def check_measurement_df(df: pd.DataFrame,
176176
f'transformation {trafo} must be '
177177
f'positive, but {measurement} <= 0.')
178178

179-
if observable_df is not None:
180-
assert_measured_observables_defined(df, observable_df)
181-
measurements.assert_overrides_match_parameter_count(
182-
df, observable_df)
183-
184179
assert_measurements_not_null(df)
185180
assert_measurements_numeric(df)
186181

@@ -206,7 +201,7 @@ def check_parameter_df(
206201

207202
_check_df(df, PARAMETER_DF_REQUIRED_COLS[1:], "parameter")
208203

209-
if not df.index.name == PARAMETER_ID:
204+
if df.index.name != PARAMETER_ID:
210205
raise AssertionError(
211206
f"Parameter table has wrong index {df.index.name}."
212207
f"expected {PARAMETER_ID}.")
@@ -232,10 +227,11 @@ def check_parameter_df(
232227
f"but column {NOMINAL_VALUE} is missing.")
233228
try:
234229
df.loc[non_estimated_par_ids, NOMINAL_VALUE].apply(float)
235-
except ValueError:
236-
raise AssertionError("Expected numeric values for "
237-
f"`{NOMINAL_VALUE}` in parameter table for "
238-
"all non-estimated parameters.")
230+
except ValueError as e:
231+
raise AssertionError(
232+
f"Expected numeric values for `{NOMINAL_VALUE}` in parameter "
233+
"table for all non-estimated parameters."
234+
) from e
239235

240236
assert_parameter_id_is_string(df)
241237
assert_parameter_scale_is_valid(df)
@@ -285,8 +281,9 @@ def check_observable_df(observable_df: pd.DataFrame) -> None:
285281
try:
286282
sp.sympify(obs)
287283
except sp.SympifyError as e:
288-
raise AssertionError(f"Cannot parse expression '{obs}' "
289-
f"for observable {row.Index}: {e}")
284+
raise AssertionError(
285+
f"Cannot parse expression '{obs}' "
286+
f"for observable {row.Index}: {e}") from e
290287

291288
noise = getattr(row, NOISE_FORMULA)
292289
try:
@@ -297,9 +294,10 @@ def check_observable_df(observable_df: pd.DataFrame) -> None:
297294
raise AssertionError(f"No or non-finite {NOISE_FORMULA} "
298295
f"given for observable {row.Index}.")
299296
except sp.SympifyError as e:
300-
raise AssertionError(f"Cannot parse expression '{noise}' "
301-
f"for noise model for observable "
302-
f"{row.Index}: {e}")
297+
raise AssertionError(
298+
f"Cannot parse expression '{noise}' "
299+
f"for noise model for observable " f"{row.Index}: {e}"
300+
) from e
303301

304302

305303
def assert_all_parameters_present_in_parameter_df(
@@ -346,7 +344,8 @@ def assert_all_parameters_present_in_parameter_df(
346344

347345
def assert_measured_observables_defined(
348346
measurement_df: pd.DataFrame,
349-
observable_df: pd.DataFrame) -> None:
347+
observable_df: pd.DataFrame
348+
) -> None:
350349
"""Check if all observables in the measurement table have been defined in
351350
the observable table
352351
@@ -360,12 +359,11 @@ def assert_measured_observables_defined(
360359

361360
used_observables = set(measurement_df[OBSERVABLE_ID].values)
362361
defined_observables = set(observable_df.index.values)
363-
undefined_observables = used_observables - defined_observables
364-
365-
if undefined_observables:
362+
if undefined_observables := (used_observables - defined_observables):
366363
raise AssertionError(
367-
"Undefined observables in measurement file: "
368-
f"{undefined_observables}.")
364+
f"Observables {undefined_observables} used in "
365+
"measurement table but not defined in observables table."
366+
)
369367

370368

371369
def condition_table_is_parameter_free(condition_df: pd.DataFrame) -> bool:
@@ -540,9 +538,10 @@ def assert_parameter_prior_parameters_are_valid(
540538
pars = tuple(
541539
float(val) for val in pars_str.split(PARAMETER_SEPARATOR)
542540
)
543-
except ValueError:
541+
except ValueError as e:
544542
raise AssertionError(
545-
f"Could not parse prior parameters '{pars_str}'.")
543+
f"Could not parse prior parameters '{pars_str}'.") from e
544+
546545
# all distributions take 2 parameters
547546
if len(pars) != 2:
548547
raise AssertionError(
@@ -795,7 +794,8 @@ def lint_problem(problem: 'petab.Problem') -> bool:
795794
if problem.condition_df is not None:
796795
logger.info("Checking condition table...")
797796
try:
798-
check_condition_df(problem.condition_df, problem.model)
797+
check_condition_df(problem.condition_df, problem.model,
798+
problem.observable_df)
799799
except AssertionError as e:
800800
logger.error(e)
801801
errors_occurred = True
@@ -925,9 +925,7 @@ def assert_measurement_conditions_present_in_condition_table(
925925
used_conditions |= \
926926
set(measurement_df[PREEQUILIBRATION_CONDITION_ID].dropna().values)
927927
available_conditions = set(condition_df.index.values)
928-
missing_conditions = used_conditions - available_conditions
929-
930-
if missing_conditions:
928+
if missing_conditions := (used_conditions - available_conditions):
931929
raise AssertionError("Measurement table references conditions that "
932930
"are not specified in the condition table: "
933931
+ str(missing_conditions))

petab/measurements.py

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
# noqa: F405
33

44
import itertools
5+
import math
56
import numbers
67
from pathlib import Path
78
from typing import Dict, List, Union
@@ -201,7 +202,7 @@ def create_measurement_df() -> pd.DataFrame:
201202
Created DataFrame
202203
"""
203204

204-
df = pd.DataFrame(data={
205+
return pd.DataFrame(data={
205206
OBSERVABLE_ID: [],
206207
PREEQUILIBRATION_CONDITION_ID: [],
207208
SIMULATION_CONDITION_ID: [],
@@ -213,8 +214,6 @@ def create_measurement_df() -> pd.DataFrame:
213214
REPLICATE_ID: []
214215
})
215216

216-
return df
217-
218217

219218
def measurements_have_replicates(measurement_df: pd.DataFrame) -> bool:
220219
"""Tests whether the measurements come with replicates
@@ -235,15 +234,15 @@ def measurements_have_replicates(measurement_df: pd.DataFrame) -> bool:
235234

236235
def assert_overrides_match_parameter_count(
237236
measurement_df: pd.DataFrame,
238-
observable_df: pd.DataFrame) -> None:
237+
observable_df: pd.DataFrame
238+
) -> None:
239239
"""Ensure that number of parameters in the observable definition matches
240240
the number of overrides in ``measurement_df``
241241
242242
Arguments:
243243
measurement_df: PEtab measurement table
244244
observable_df: PEtab observable table
245245
"""
246-
247246
# sympify only once and save number of parameters
248247
observable_parameters_count = {
249248
obs_id: len(observables.get_formula_placeholders(
@@ -260,10 +259,11 @@ def assert_overrides_match_parameter_count(
260259
# check observable parameters
261260
try:
262261
expected = observable_parameters_count[row[OBSERVABLE_ID]]
263-
except KeyError:
262+
except KeyError as e:
264263
raise ValueError(
265264
f"Observable {row[OBSERVABLE_ID]} used in measurement table "
266-
f"is not defined.")
265+
f"is not defined.") from e
266+
267267
actual = len(split_parameter_replacement_list(
268268
row.get(OBSERVABLE_PARAMETERS, None)))
269269
# No overrides are also allowed
@@ -289,7 +289,7 @@ def assert_overrides_match_parameter_count(
289289
except KeyError:
290290
# no overrides defined, but a numerical sigma can be provided
291291
# anyways
292-
if not len(replacements) == 1 \
292+
if len(replacements) != 1 \
293293
or not isinstance(replacements[0], numbers.Number):
294294
raise AssertionError(
295295
f'No placeholders have been specified in the noise model '

petab/models/sbml_model.py

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
from . import MODEL_TYPE_SBML
1010
from .model import Model
1111
from ..sbml import (get_sbml_model, is_sbml_consistent, load_sbml_from_string,
12-
log_sbml_errors, write_sbml)
12+
write_sbml)
1313

1414

1515
class SbmlModel(Model):
@@ -109,9 +109,7 @@ def symbol_allowed_in_observable_formula(self, id_: str) -> bool:
109109
return self.sbml_model.getElementBySId(id_) or id_ == 'time'
110110

111111
def is_valid(self) -> bool:
112-
valid = is_sbml_consistent(self.sbml_model.getSBMLDocument())
113-
log_sbml_errors(self.sbml_model.getSBMLDocument())
114-
return valid
112+
return is_sbml_consistent(self.sbml_model.getSBMLDocument())
115113

116114
def is_state_variable(self, id_: str) -> bool:
117115
return (self.sbml_model.getSpecies(id_) is not None

0 commit comments

Comments
 (0)