Skip to content

Commit 7ec2a9b

Browse files
committed
no conditionName, no operationType
1 parent e5e1097 commit 7ec2a9b

File tree

9 files changed

+188
-133
lines changed

9 files changed

+188
-133
lines changed

petab/v1/lint.py

+7-4
Original file line numberDiff line numberDiff line change
@@ -1041,10 +1041,13 @@ def assert_model_parameters_in_condition_or_parameter_table(
10411041
mapping_df[MODEL_ENTITY_ID],
10421042
strict=True,
10431043
)
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)
1044+
if not pd.isna(to_id)
1045+
and (
1046+
# mapping table entities mapping to already allowed parameters
1047+
to_id in allowed_in_condition_cols
1048+
# mapping table entities mapping to species
1049+
or model.is_state_variable(to_id)
1050+
)
10481051
}
10491052

10501053
allowed_in_parameter_table = (

petab/v1/math/sympify.py

+6-2
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

+3-14
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

@@ -382,6 +368,9 @@
382368
PETAB_ENTITY_ID = "petabEntityId"
383369
#: Model entity ID column in the mapping table
384370
MODEL_ENTITY_ID = "modelEntityId"
371+
#: Arbitrary name
372+
NAME = "name"
373+
385374
#: Required columns of the mapping table
386375
MAPPING_DF_REQUIRED_COLS = [PETAB_ENTITY_ID, MODEL_ENTITY_ID]
387376

petab/v2/core.py

+51-42
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,18 @@
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,
16+
BeforeValidator,
1317
ConfigDict,
1418
Field,
1519
ValidationInfo,
@@ -29,7 +33,6 @@
2933
"Change",
3034
"Condition",
3135
"ConditionsTable",
32-
"OperationType",
3336
"ExperimentPeriod",
3437
"Experiment",
3538
"ExperimentsTable",
@@ -43,6 +46,20 @@
4346
]
4447

4548

49+
def is_finite_or_neg_inf(v: float, info: ValidationInfo) -> float:
50+
if not np.isfinite(v) and v != -np.inf:
51+
raise ValueError(
52+
f"{info.field_name} value must be finite or -inf but got {v}"
53+
)
54+
return v
55+
56+
57+
def _convert_nan_to_none(v):
58+
if isinstance(v, float) and np.isnan(v):
59+
return None
60+
return v
61+
62+
4663
class ObservableTransformation(str, Enum):
4764
"""Observable transformation types.
4865
@@ -248,16 +265,6 @@ def __iadd__(self, other: Observable) -> ObservablesTable:
248265
return self
249266

250267

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-
261268
class Change(BaseModel):
262269
"""A change to the model or model state.
263270
@@ -266,17 +273,13 @@ class Change(BaseModel):
266273
267274
>>> Change(
268275
... target_id="k1",
269-
... operation_type=OperationType.SET_CURRENT_VALUE,
270276
... target_value="10",
271277
... ) # doctest: +NORMALIZE_WHITESPACE
272-
Change(target_id='k1', operation_type='setCurrentValue',
273-
target_value=10.0000000000000)
278+
Change(target_id='k1', target_value=10.0000000000000)
274279
"""
275280

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

@@ -290,14 +293,11 @@ class Change(BaseModel):
290293
@model_validator(mode="before")
291294
@classmethod
292295
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}")
296+
target_id = data.get("target_id", data.get(C.TARGET_ID))
297+
298+
if not is_valid_identifier(target_id):
299+
raise ValueError(f"Invalid ID: {target_id}")
300+
301301
return data
302302

303303
@field_validator("target_value", mode="before")
@@ -323,13 +323,12 @@ class Condition(BaseModel):
323323
... changes=[
324324
... Change(
325325
... target_id="k1",
326-
... operation_type=OperationType.SET_CURRENT_VALUE,
327326
... target_value="10",
328327
... )
329328
... ],
330329
... ) # doctest: +NORMALIZE_WHITESPACE
331-
Condition(id='condition1', changes=[Change(target_id='k1',
332-
operation_type='setCurrentValue', target_value=10.0000000000000)])
330+
Condition(id='condition1',
331+
changes=[Change(target_id='k1', target_value=10.0000000000000)])
333332
"""
334333

335334
#: The condition ID.
@@ -352,13 +351,13 @@ def _validate_id(cls, v):
352351
def __add__(self, other: Change) -> Condition:
353352
"""Add a change to the set."""
354353
if not isinstance(other, Change):
355-
raise TypeError("Can only add Change to ChangeSet")
354+
raise TypeError("Can only add Change to Condition")
356355
return Condition(id=self.id, changes=self.changes + [other])
357356

358357
def __iadd__(self, other: Change) -> Condition:
359358
"""Add a change to the set in place."""
360359
if not isinstance(other, Change):
361-
raise TypeError("Can only add Change to ChangeSet")
360+
raise TypeError("Can only add Change to Condition")
362361
self.changes.append(other)
363362
return self
364363

@@ -379,11 +378,11 @@ def __getitem__(self, condition_id: str) -> Condition:
379378
@classmethod
380379
def from_df(cls, df: pd.DataFrame) -> ConditionsTable:
381380
"""Create a ConditionsTable from a DataFrame."""
382-
if df is None:
381+
if df is None or df.empty:
383382
return cls(conditions=[])
384383

385384
conditions = []
386-
for condition_id, sub_df in df.groupby(C.CONDITION_ID):
385+
for condition_id, sub_df in df.reset_index().groupby(C.CONDITION_ID):
387386
changes = [Change(**row.to_dict()) for _, row in sub_df.iterrows()]
388387
conditions.append(Condition(id=condition_id, changes=changes))
389388

@@ -422,13 +421,13 @@ def to_tsv(self, file_path: str | Path) -> None:
422421
def __add__(self, other: Condition) -> ConditionsTable:
423422
"""Add a condition to the table."""
424423
if not isinstance(other, Condition):
425-
raise TypeError("Can only add ChangeSet to ConditionsTable")
424+
raise TypeError("Can only add Conditions to ConditionsTable")
426425
return ConditionsTable(conditions=self.conditions + [other])
427426

428427
def __iadd__(self, other: Condition) -> ConditionsTable:
429428
"""Add a condition to the table in place."""
430429
if not isinstance(other, Condition):
431-
raise TypeError("Can only add ChangeSet to ConditionsTable")
430+
raise TypeError("Can only add Conditions to ConditionsTable")
432431
self.conditions.append(other)
433432
return self
434433

@@ -441,21 +440,20 @@ class ExperimentPeriod(BaseModel):
441440
"""
442441

443442
#: 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
443+
time: Annotated[float, AfterValidator(is_finite_or_neg_inf)] = Field(
444+
alias=C.TIME
445+
)
447446
#: The ID of the condition to be applied at the start time.
448-
condition_id: str = Field(alias=C.CONDITION_ID)
447+
condition_id: str | None = Field(alias=C.CONDITION_ID, default=None)
449448

450449
#: :meta private:
451450
model_config = ConfigDict(populate_by_name=True)
452451

453452
@field_validator("condition_id", mode="before")
454453
@classmethod
455454
def _validate_id(cls, condition_id):
456-
# TODO to be decided if optional
457-
if pd.isna(condition_id):
458-
return ""
455+
if pd.isna(condition_id) or not condition_id:
456+
return None
459457
# if not condition_id:
460458
# raise ValueError("ID must not be empty.")
461459
if not is_valid_identifier(condition_id):
@@ -633,12 +631,17 @@ def _validate_id(cls, v, info: ValidationInfo):
633631
)
634632
@classmethod
635633
def _sympify_list(cls, v):
634+
if v is None:
635+
return []
636+
636637
if isinstance(v, float) and np.isnan(v):
637638
return []
639+
638640
if isinstance(v, str):
639641
v = v.split(C.PARAMETER_SEPARATOR)
640-
else:
642+
elif not isinstance(v, Sequence):
641643
v = [v]
644+
642645
return [sympify_petab(x) for x in v]
643646

644647

@@ -710,7 +713,13 @@ class Mapping(BaseModel):
710713
#: PEtab entity ID.
711714
petab_id: str = Field(alias=C.PETAB_ENTITY_ID)
712715
#: Model entity ID.
713-
model_id: str = Field(alias=C.MODEL_ENTITY_ID)
716+
model_id: Annotated[str | None, BeforeValidator(_convert_nan_to_none)] = (
717+
Field(alias=C.MODEL_ENTITY_ID, default=None)
718+
)
719+
#: Arbitrary name
720+
name: Annotated[str | None, BeforeValidator(_convert_nan_to_none)] = Field(
721+
alias=C.NAME, default=None
722+
)
714723

715724
#: :meta private:
716725
model_config = ConfigDict(populate_by_name=True)

0 commit comments

Comments
 (0)