Skip to content

Commit 3b8fc54

Browse files
committed
no conditionName, no operationType
1 parent e5e1097 commit 3b8fc54

File tree

8 files changed

+93
-118
lines changed

8 files changed

+93
-118
lines changed

petab/v1/math/sympify.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,9 +31,13 @@ def sympify_petab(expr: str | int | float) -> sp.Expr | sp.Basic:
3131
if isinstance(expr, float) or isinstance(expr, np.floating):
3232
return sp.Float(expr)
3333

34-
# Set error listeners
35-
input_stream = InputStream(expr)
34+
try:
35+
input_stream = InputStream(expr)
36+
except TypeError as e:
37+
raise TypeError(f"Error parsing {expr!r}: {e.args[0]}") from e
38+
3639
lexer = PetabMathExprLexer(input_stream)
40+
# Set error listeners
3741
lexer.removeErrorListeners()
3842
lexer.addErrorListener(MathErrorListener())
3943

petab/v2/C.py

Lines changed: 0 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -125,28 +125,14 @@
125125

126126
#: Condition ID column in the condition table
127127
CONDITION_ID = "conditionId"
128-
# TODO: removed?
129-
#: Condition name column in the condition table
130-
CONDITION_NAME = "conditionName"
131128
#: Column in the condition table with the ID of an entity that is changed
132129
TARGET_ID = "targetId"
133-
#: Column in the condition table with the operation type
134-
OPERATION_TYPE = "operationType"
135130
#: Column in the condition table with the new value of the target entity
136131
TARGET_VALUE = "targetValue"
137-
# operation types:
138-
OT_CUR_VAL = "setCurrentValue"
139-
OT_NO_CHANGE = "noChange"
140-
141-
OPERATION_TYPES = [
142-
OT_CUR_VAL,
143-
OT_NO_CHANGE,
144-
]
145132

146133
CONDITION_DF_COLS = [
147134
CONDITION_ID,
148135
TARGET_ID,
149-
OPERATION_TYPE,
150136
TARGET_VALUE,
151137
]
152138

petab/v2/core.py

Lines changed: 37 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,16 @@
22

33
from __future__ import annotations
44

5+
from collections.abc import Sequence
56
from enum import Enum
67
from pathlib import Path
8+
from typing import Annotated
79

810
import numpy as np
911
import pandas as pd
1012
import sympy as sp
1113
from pydantic import (
14+
AfterValidator,
1215
BaseModel,
1316
ConfigDict,
1417
Field,
@@ -29,7 +32,6 @@
2932
"Change",
3033
"Condition",
3134
"ConditionsTable",
32-
"OperationType",
3335
"ExperimentPeriod",
3436
"Experiment",
3537
"ExperimentsTable",
@@ -43,6 +45,14 @@
4345
]
4446

4547

48+
def is_finite_or_neg_inf(v: float, info: ValidationInfo) -> float:
49+
if not np.isfinite(v) and v != -np.inf:
50+
raise ValueError(
51+
f"{info.field_name} value must be finite or -inf but got {v}"
52+
)
53+
return v
54+
55+
4656
class ObservableTransformation(str, Enum):
4757
"""Observable transformation types.
4858
@@ -248,16 +258,6 @@ def __iadd__(self, other: Observable) -> ObservablesTable:
248258
return self
249259

250260

251-
# TODO remove?!
252-
class OperationType(str, Enum):
253-
"""Operation types for model changes in the PEtab conditions table."""
254-
255-
# TODO update names
256-
SET_CURRENT_VALUE = "setCurrentValue"
257-
NO_CHANGE = "noChange"
258-
...
259-
260-
261261
class Change(BaseModel):
262262
"""A change to the model or model state.
263263
@@ -266,17 +266,13 @@ class Change(BaseModel):
266266
267267
>>> Change(
268268
... target_id="k1",
269-
... operation_type=OperationType.SET_CURRENT_VALUE,
270269
... target_value="10",
271270
... ) # doctest: +NORMALIZE_WHITESPACE
272-
Change(target_id='k1', operation_type='setCurrentValue',
273-
target_value=10.0000000000000)
271+
Change(target_id='k1', target_value=10.0000000000000)
274272
"""
275273

276274
#: The ID of the target entity to change.
277275
target_id: str | None = Field(alias=C.TARGET_ID, default=None)
278-
# TODO: remove?!
279-
operation_type: OperationType = Field(alias=C.OPERATION_TYPE)
280276
#: The value to set the target entity to.
281277
target_value: sp.Basic | None = Field(alias=C.TARGET_VALUE, default=None)
282278

@@ -290,14 +286,11 @@ class Change(BaseModel):
290286
@model_validator(mode="before")
291287
@classmethod
292288
def _validate_id(cls, data: dict):
293-
if (
294-
data.get("operation_type", data.get(C.OPERATION_TYPE))
295-
!= C.OT_NO_CHANGE
296-
):
297-
target_id = data.get("target_id", data.get(C.TARGET_ID))
298-
299-
if not is_valid_identifier(target_id):
300-
raise ValueError(f"Invalid ID: {target_id}")
289+
target_id = data.get("target_id", data.get(C.TARGET_ID))
290+
291+
if not is_valid_identifier(target_id):
292+
raise ValueError(f"Invalid ID: {target_id}")
293+
301294
return data
302295

303296
@field_validator("target_value", mode="before")
@@ -323,13 +316,12 @@ class Condition(BaseModel):
323316
... changes=[
324317
... Change(
325318
... target_id="k1",
326-
... operation_type=OperationType.SET_CURRENT_VALUE,
327319
... target_value="10",
328320
... )
329321
... ],
330322
... ) # doctest: +NORMALIZE_WHITESPACE
331-
Condition(id='condition1', changes=[Change(target_id='k1',
332-
operation_type='setCurrentValue', target_value=10.0000000000000)])
323+
Condition(id='condition1',
324+
changes=[Change(target_id='k1', target_value=10.0000000000000)])
333325
"""
334326

335327
#: The condition ID.
@@ -352,13 +344,13 @@ def _validate_id(cls, v):
352344
def __add__(self, other: Change) -> Condition:
353345
"""Add a change to the set."""
354346
if not isinstance(other, Change):
355-
raise TypeError("Can only add Change to ChangeSet")
347+
raise TypeError("Can only add Change to Condition")
356348
return Condition(id=self.id, changes=self.changes + [other])
357349

358350
def __iadd__(self, other: Change) -> Condition:
359351
"""Add a change to the set in place."""
360352
if not isinstance(other, Change):
361-
raise TypeError("Can only add Change to ChangeSet")
353+
raise TypeError("Can only add Change to Condition")
362354
self.changes.append(other)
363355
return self
364356

@@ -379,11 +371,11 @@ def __getitem__(self, condition_id: str) -> Condition:
379371
@classmethod
380372
def from_df(cls, df: pd.DataFrame) -> ConditionsTable:
381373
"""Create a ConditionsTable from a DataFrame."""
382-
if df is None:
374+
if df is None or df.empty:
383375
return cls(conditions=[])
384376

385377
conditions = []
386-
for condition_id, sub_df in df.groupby(C.CONDITION_ID):
378+
for condition_id, sub_df in df.reset_index().groupby(C.CONDITION_ID):
387379
changes = [Change(**row.to_dict()) for _, row in sub_df.iterrows()]
388380
conditions.append(Condition(id=condition_id, changes=changes))
389381

@@ -422,13 +414,13 @@ def to_tsv(self, file_path: str | Path) -> None:
422414
def __add__(self, other: Condition) -> ConditionsTable:
423415
"""Add a condition to the table."""
424416
if not isinstance(other, Condition):
425-
raise TypeError("Can only add ChangeSet to ConditionsTable")
417+
raise TypeError("Can only add Conditions to ConditionsTable")
426418
return ConditionsTable(conditions=self.conditions + [other])
427419

428420
def __iadd__(self, other: Condition) -> ConditionsTable:
429421
"""Add a condition to the table in place."""
430422
if not isinstance(other, Condition):
431-
raise TypeError("Can only add ChangeSet to ConditionsTable")
423+
raise TypeError("Can only add Conditions to ConditionsTable")
432424
self.conditions.append(other)
433425
return self
434426

@@ -441,21 +433,20 @@ class ExperimentPeriod(BaseModel):
441433
"""
442434

443435
#: The start time of the period in time units as defined in the model.
444-
# TODO: Only finite times and -inf are allowed as start time
445-
time: float = Field(alias=C.TIME)
446-
# TODO: decide if optional
436+
time: Annotated[float, AfterValidator(is_finite_or_neg_inf)] = Field(
437+
alias=C.TIME
438+
)
447439
#: The ID of the condition to be applied at the start time.
448-
condition_id: str = Field(alias=C.CONDITION_ID)
440+
condition_id: str | None = Field(alias=C.CONDITION_ID, default=None)
449441

450442
#: :meta private:
451443
model_config = ConfigDict(populate_by_name=True)
452444

453445
@field_validator("condition_id", mode="before")
454446
@classmethod
455447
def _validate_id(cls, condition_id):
456-
# TODO to be decided if optional
457-
if pd.isna(condition_id):
458-
return ""
448+
if pd.isna(condition_id) or not condition_id:
449+
return None
459450
# if not condition_id:
460451
# raise ValueError("ID must not be empty.")
461452
if not is_valid_identifier(condition_id):
@@ -633,12 +624,17 @@ def _validate_id(cls, v, info: ValidationInfo):
633624
)
634625
@classmethod
635626
def _sympify_list(cls, v):
627+
if v is None:
628+
return []
629+
636630
if isinstance(v, float) and np.isnan(v):
637631
return []
632+
638633
if isinstance(v, str):
639634
v = v.split(C.PARAMETER_SEPARATOR)
640-
else:
635+
elif not isinstance(v, Sequence):
641636
v = [v]
637+
642638
return [sympify_petab(x) for x in v]
643639

644640

petab/v2/lint.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -518,13 +518,13 @@ class CheckExperimentConditionsExist(ValidationTask):
518518
def run(self, problem: Problem) -> ValidationIssue | None:
519519
messages = []
520520
available_conditions = {
521-
c.id
522-
for c in problem.conditions_table.conditions
523-
if not pd.isna(c.id)
521+
c.id for c in problem.conditions_table.conditions
524522
}
525523
for experiment in problem.experiments_table.experiments:
526524
missing_conditions = {
527-
period.condition_id for period in experiment.periods
525+
period.condition_id
526+
for period in experiment.periods
527+
if period.condition_id is not None
528528
} - available_conditions
529529
if missing_conditions:
530530
messages.append(
@@ -672,7 +672,7 @@ def get_valid_parameters_for_parameter_table(
672672
blackset |= placeholders
673673

674674
if condition_df is not None:
675-
blackset |= set(condition_df.columns.values) - {CONDITION_NAME}
675+
blackset |= set(condition_df.columns.values)
676676

677677
# don't use sets here, to have deterministic ordering,
678678
# e.g. for creating parameter tables

petab/v2/petab1to2.py

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -226,8 +226,9 @@ def create_experiment_id(sim_cond_id: str, preeq_cond_id: str) -> str:
226226
validation_issues = v2.lint_problem(new_yaml_file)
227227

228228
if validation_issues:
229+
validation_issues = "\n".join(map(str, validation_issues))
229230
raise ValueError(
230-
"Generated PEtab v2 problem did not pass linting: "
231+
"The generated PEtab v2 problem did not pass linting: "
231232
f"{validation_issues}"
232233
)
233234

@@ -287,7 +288,7 @@ def v1v2_condition_df(
287288
condition_df = condition_df.copy().reset_index()
288289
with suppress(KeyError):
289290
# conditionName was dropped in PEtab v2
290-
condition_df.drop(columns=[v2.C.CONDITION_NAME], inplace=True)
291+
condition_df.drop(columns=[v1.C.CONDITION_NAME], inplace=True)
291292

292293
condition_df = condition_df.melt(
293294
id_vars=[v1.C.CONDITION_ID],
@@ -301,7 +302,6 @@ def v1v2_condition_df(
301302
columns=[
302303
v2.C.CONDITION_ID,
303304
v2.C.TARGET_ID,
304-
v2.C.OPERATION_TYPE,
305305
v2.C.TARGET_VALUE,
306306
]
307307
)
@@ -320,5 +320,4 @@ def v1v2_condition_df(
320320
f"Unable to determine value type {target} in the condition "
321321
"table."
322322
)
323-
condition_df[v2.C.OPERATION_TYPE] = v2.C.OT_CUR_VAL
324323
return condition_df

0 commit comments

Comments
 (0)