Skip to content

Commit

Permalink
Merge pull request #170 from PEtab-dev/release_0.1.28
Browse files Browse the repository at this point in the history
Release 0.1.28
  • Loading branch information
dweindl authored Jul 26, 2022
2 parents 286494a + 84d4e5f commit 6ffb315
Show file tree
Hide file tree
Showing 22 changed files with 316 additions and 242 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/ci_tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ jobs:
strategy:
matrix:
platform: [windows-latest, macos-latest, ubuntu-latest]
python-version: ["3.7", "3.10"]
python-version: ["3.8", "3.10"]
runs-on: ${{ matrix.platform }}

steps:
Expand Down
16 changes: 16 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,22 @@

## 0.1 series

### 0.1.28

* Fixed validation for output parameters columns in the condition table
by @dweindl in https://github.com/PEtab-dev/libpetab-python/pull/161
* Added Python support policy
by @dweindl in https://github.com/PEtab-dev/libpetab-python/pull/162
* Fixed typehints and deprecation warning
by @dweindl in https://github.com/PEtab-dev/libpetab-python/pull/165
* Fixed SBML validation
by @dweindl in https://github.com/PEtab-dev/libpetab-python/pull/168
* Fixed deprecation warning from `get_model_for_condition`
by @dweindl in https://github.com/PEtab-dev/libpetab-python/pull/169

**Full Changelog**:
https://github.com/PEtab-dev/libpetab-python/compare/v0.1.27...v0.1.28

### 0.1.27

Features:
Expand Down
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@ and the easiest way to install it is running

pip3 install petab

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

Development versions of the PEtab library can be installed using

Expand Down
9 changes: 3 additions & 6 deletions petab/conditions.py
Original file line number Diff line number Diff line change
Expand Up @@ -93,9 +93,8 @@ def get_parametric_overrides(condition_df: pd.DataFrame) -> List[str]:
Returns:
List of parameter IDs that are mapped in a condition-specific way
"""
constant_parameters = list(
set(condition_df.columns.values.tolist()) - {CONDITION_ID,
CONDITION_NAME})
constant_parameters = (set(condition_df.columns.values.tolist())
- {CONDITION_ID, CONDITION_NAME})
result = []

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

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

for x in floatified:
if not isinstance(x, float):
result.append(x)
result.extend(x for x in floatified if not isinstance(x, float))
return result
7 changes: 4 additions & 3 deletions petab/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
'create_combine_archive', 'unique_preserve_order']


def get_simulation_df(simulation_file: str) -> pd.DataFrame:
def get_simulation_df(simulation_file: Union[str, Path]) -> pd.DataFrame:
"""Read PEtab simulation table
Arguments:
Expand All @@ -33,7 +33,7 @@ def get_simulation_df(simulation_file: str) -> pd.DataFrame:
float_precision='round_trip')


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


def flatten_timepoint_specific_output_overrides(
petab_problem: 'petab.problem.Problem') -> None:
petab_problem: 'petab.problem.Problem',
) -> None:
"""Flatten timepoint-specific output parameter overrides.
If the PEtab problem definition has timepoint-specific
Expand Down
78 changes: 38 additions & 40 deletions petab/lint.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,9 +60,7 @@ def _check_df(df: pd.DataFrame, req_cols: Iterable, name: str) -> None:
Raises:
AssertionError: if a column is missing
"""
cols_set = df.columns.values
missing_cols = set(req_cols) - set(cols_set)
if missing_cols:
if missing_cols := set(req_cols) - set(df.columns.values):
raise AssertionError(
f"DataFrame {name} requires the columns {missing_cols}.")

Expand All @@ -85,12 +83,16 @@ def assert_no_leading_trailing_whitespace(


def check_condition_df(
df: pd.DataFrame, model: Optional[Model] = None) -> None:
df: pd.DataFrame,
model: Optional[Model] = None,
observable_df: Optional[pd.DataFrame] = None
) -> None:
"""Run sanity checks on PEtab condition table
Arguments:
df: PEtab condition DataFrame
model: Model for additional checking of parameter IDs
observable_df: PEtab observables DataFrame
Raises:
AssertionError: in case of problems
Expand All @@ -101,7 +103,7 @@ def check_condition_df(
_check_df(df, req_cols, "condition")

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

if model is not None:
allowed_cols = set(model.get_valid_ids_for_condition_table())
if observable_df is not None:
allowed_cols |= set(petab.get_output_parameters(
model=model, observable_df=observable_df))
for column_name in df.columns:
if column_name != CONDITION_NAME \
and column_name not in allowed_cols:
Expand Down Expand Up @@ -154,14 +159,9 @@ def check_measurement_df(df: pd.DataFrame,
df[column_name].values, column_name)

if observable_df is not None:
# Check all observables are defined
observables_defined = set(observable_df.index.values)
observables_used = set(df[OBSERVABLE_ID])
observables_undefined = observables_used - observables_defined
if observables_undefined:
raise ValueError(f"Observables {observables_undefined} used in "
"measurement table but not defined in "
"observables table.")
assert_measured_observables_defined(df, observable_df)
measurements.assert_overrides_match_parameter_count(
df, observable_df)

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

if observable_df is not None:
assert_measured_observables_defined(df, observable_df)
measurements.assert_overrides_match_parameter_count(
df, observable_df)

assert_measurements_not_null(df)
assert_measurements_numeric(df)

Expand All @@ -206,7 +201,7 @@ def check_parameter_df(

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

if not df.index.name == PARAMETER_ID:
if df.index.name != PARAMETER_ID:
raise AssertionError(
f"Parameter table has wrong index {df.index.name}."
f"expected {PARAMETER_ID}.")
Expand All @@ -232,10 +227,11 @@ def check_parameter_df(
f"but column {NOMINAL_VALUE} is missing.")
try:
df.loc[non_estimated_par_ids, NOMINAL_VALUE].apply(float)
except ValueError:
raise AssertionError("Expected numeric values for "
f"`{NOMINAL_VALUE}` in parameter table for "
"all non-estimated parameters.")
except ValueError as e:
raise AssertionError(
f"Expected numeric values for `{NOMINAL_VALUE}` in parameter "
"table for all non-estimated parameters."
) from e

assert_parameter_id_is_string(df)
assert_parameter_scale_is_valid(df)
Expand Down Expand Up @@ -285,8 +281,9 @@ def check_observable_df(observable_df: pd.DataFrame) -> None:
try:
sp.sympify(obs)
except sp.SympifyError as e:
raise AssertionError(f"Cannot parse expression '{obs}' "
f"for observable {row.Index}: {e}")
raise AssertionError(
f"Cannot parse expression '{obs}' "
f"for observable {row.Index}: {e}") from e

noise = getattr(row, NOISE_FORMULA)
try:
Expand All @@ -297,9 +294,10 @@ def check_observable_df(observable_df: pd.DataFrame) -> None:
raise AssertionError(f"No or non-finite {NOISE_FORMULA} "
f"given for observable {row.Index}.")
except sp.SympifyError as e:
raise AssertionError(f"Cannot parse expression '{noise}' "
f"for noise model for observable "
f"{row.Index}: {e}")
raise AssertionError(
f"Cannot parse expression '{noise}' "
f"for noise model for observable " f"{row.Index}: {e}"
) from e


def assert_all_parameters_present_in_parameter_df(
Expand Down Expand Up @@ -346,7 +344,8 @@ def assert_all_parameters_present_in_parameter_df(

def assert_measured_observables_defined(
measurement_df: pd.DataFrame,
observable_df: pd.DataFrame) -> None:
observable_df: pd.DataFrame
) -> None:
"""Check if all observables in the measurement table have been defined in
the observable table
Expand All @@ -360,12 +359,11 @@ def assert_measured_observables_defined(

used_observables = set(measurement_df[OBSERVABLE_ID].values)
defined_observables = set(observable_df.index.values)
undefined_observables = used_observables - defined_observables

if undefined_observables:
if undefined_observables := (used_observables - defined_observables):
raise AssertionError(
"Undefined observables in measurement file: "
f"{undefined_observables}.")
f"Observables {undefined_observables} used in "
"measurement table but not defined in observables table."
)


def condition_table_is_parameter_free(condition_df: pd.DataFrame) -> bool:
Expand Down Expand Up @@ -540,9 +538,10 @@ def assert_parameter_prior_parameters_are_valid(
pars = tuple(
float(val) for val in pars_str.split(PARAMETER_SEPARATOR)
)
except ValueError:
except ValueError as e:
raise AssertionError(
f"Could not parse prior parameters '{pars_str}'.")
f"Could not parse prior parameters '{pars_str}'.") from e

# all distributions take 2 parameters
if len(pars) != 2:
raise AssertionError(
Expand Down Expand Up @@ -795,7 +794,8 @@ def lint_problem(problem: 'petab.Problem') -> bool:
if problem.condition_df is not None:
logger.info("Checking condition table...")
try:
check_condition_df(problem.condition_df, problem.model)
check_condition_df(problem.condition_df, problem.model,
problem.observable_df)
except AssertionError as e:
logger.error(e)
errors_occurred = True
Expand Down Expand Up @@ -925,9 +925,7 @@ def assert_measurement_conditions_present_in_condition_table(
used_conditions |= \
set(measurement_df[PREEQUILIBRATION_CONDITION_ID].dropna().values)
available_conditions = set(condition_df.index.values)
missing_conditions = used_conditions - available_conditions

if missing_conditions:
if missing_conditions := (used_conditions - available_conditions):
raise AssertionError("Measurement table references conditions that "
"are not specified in the condition table: "
+ str(missing_conditions))
Expand Down
16 changes: 8 additions & 8 deletions petab/measurements.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
# noqa: F405

import itertools
import math
import numbers
from pathlib import Path
from typing import Dict, List, Union
Expand Down Expand Up @@ -201,7 +202,7 @@ def create_measurement_df() -> pd.DataFrame:
Created DataFrame
"""

df = pd.DataFrame(data={
return pd.DataFrame(data={
OBSERVABLE_ID: [],
PREEQUILIBRATION_CONDITION_ID: [],
SIMULATION_CONDITION_ID: [],
Expand All @@ -213,8 +214,6 @@ def create_measurement_df() -> pd.DataFrame:
REPLICATE_ID: []
})

return df


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

def assert_overrides_match_parameter_count(
measurement_df: pd.DataFrame,
observable_df: pd.DataFrame) -> None:
observable_df: pd.DataFrame
) -> None:
"""Ensure that number of parameters in the observable definition matches
the number of overrides in ``measurement_df``
Arguments:
measurement_df: PEtab measurement table
observable_df: PEtab observable table
"""

# sympify only once and save number of parameters
observable_parameters_count = {
obs_id: len(observables.get_formula_placeholders(
Expand All @@ -260,10 +259,11 @@ def assert_overrides_match_parameter_count(
# check observable parameters
try:
expected = observable_parameters_count[row[OBSERVABLE_ID]]
except KeyError:
except KeyError as e:
raise ValueError(
f"Observable {row[OBSERVABLE_ID]} used in measurement table "
f"is not defined.")
f"is not defined.") from e

actual = len(split_parameter_replacement_list(
row.get(OBSERVABLE_PARAMETERS, None)))
# No overrides are also allowed
Expand All @@ -289,7 +289,7 @@ def assert_overrides_match_parameter_count(
except KeyError:
# no overrides defined, but a numerical sigma can be provided
# anyways
if not len(replacements) == 1 \
if len(replacements) != 1 \
or not isinstance(replacements[0], numbers.Number):
raise AssertionError(
f'No placeholders have been specified in the noise model '
Expand Down
6 changes: 2 additions & 4 deletions petab/models/sbml_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
from . import MODEL_TYPE_SBML
from .model import Model
from ..sbml import (get_sbml_model, is_sbml_consistent, load_sbml_from_string,
log_sbml_errors, write_sbml)
write_sbml)


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

def is_valid(self) -> bool:
valid = is_sbml_consistent(self.sbml_model.getSBMLDocument())
log_sbml_errors(self.sbml_model.getSBMLDocument())
return valid
return is_sbml_consistent(self.sbml_model.getSBMLDocument())

def is_state_variable(self, id_: str) -> bool:
return (self.sbml_model.getSpecies(id_) is not None
Expand Down
Loading

0 comments on commit 6ffb315

Please sign in to comment.