Skip to content

Commit 3c6b45b

Browse files
authored
v2: handle merged observable.observableTransformation observable.nois… (#376)
* v2: handle merged observable.observableTransformation observable.noiseDistribution Update to changes in PEtab v2 draft, see PEtab-dev/PEtab#619. Closes #375. * upconversion
1 parent 87cec8c commit 3c6b45b

File tree

6 files changed

+68
-36
lines changed

6 files changed

+68
-36
lines changed

petab/v2/C.py

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -147,8 +147,6 @@
147147
OBSERVABLE_FORMULA = "observableFormula"
148148
#: Noise formula column in the observable table
149149
NOISE_FORMULA = "noiseFormula"
150-
#: Observable transformation column in the observable table
151-
OBSERVABLE_TRANSFORMATION = "observableTransformation"
152150
#: Noise distribution column in the observable table
153151
NOISE_DISTRIBUTION = "noiseDistribution"
154152

@@ -162,7 +160,6 @@
162160
#: Optional columns of observable table
163161
OBSERVABLE_DF_OPTIONAL_COLS = [
164162
OBSERVABLE_NAME,
165-
OBSERVABLE_TRANSFORMATION,
166163
NOISE_DISTRIBUTION,
167164
]
168165

@@ -181,8 +178,6 @@
181178
LOG = "log"
182179
#: Logarithmic base 10 transformation
183180
LOG10 = "log10"
184-
#: Supported observable transformations
185-
OBSERVABLE_TRANSFORMATIONS = [LIN, LOG, LOG10]
186181

187182

188183
# NOISE MODELS
@@ -232,7 +227,7 @@
232227

233228

234229
#: Supported noise distributions
235-
NOISE_MODELS = [NORMAL, LAPLACE]
230+
NOISE_DISTRIBUTIONS = [NORMAL, LAPLACE, LOG_NORMAL, LOG_LAPLACE]
236231

237232

238233
# VISUALIZATION

petab/v2/core.py

Lines changed: 4 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,6 @@
3232
__all__ = [
3333
"Observable",
3434
"ObservableTable",
35-
"ObservableTransformation",
3635
"NoiseDistribution",
3736
"Change",
3837
"Condition",
@@ -87,20 +86,6 @@ def _valid_petab_id(v: str) -> str:
8786
return v
8887

8988

90-
class ObservableTransformation(str, Enum):
91-
"""Observable transformation types.
92-
93-
Observable transformations as used in the PEtab observables table.
94-
"""
95-
96-
#: No transformation
97-
LIN = C.LIN
98-
#: Logarithmic transformation (natural logarithm)
99-
LOG = C.LOG
100-
#: Logarithmic transformation (base 10)
101-
LOG10 = C.LOG10
102-
103-
10489
class ParameterScale(str, Enum):
10590
"""Parameter scales.
10691
@@ -122,6 +107,10 @@ class NoiseDistribution(str, Enum):
122107
NORMAL = C.NORMAL
123108
#: Laplace distribution
124109
LAPLACE = C.LAPLACE
110+
#: Log-normal distribution
111+
LOG_NORMAL = C.LOG_NORMAL
112+
#: Log-Laplace distribution
113+
LOG_LAPLACE = C.LOG_LAPLACE
125114

126115

127116
class PriorDistribution(str, Enum):
@@ -173,10 +162,6 @@ class Observable(BaseModel):
173162
name: str | None = Field(alias=C.OBSERVABLE_NAME, default=None)
174163
#: Observable formula.
175164
formula: sp.Basic | None = Field(alias=C.OBSERVABLE_FORMULA, default=None)
176-
#: Observable transformation.
177-
transformation: ObservableTransformation = Field(
178-
alias=C.OBSERVABLE_TRANSFORMATION, default=ObservableTransformation.LIN
179-
)
180165
#: Noise formula.
181166
noise_formula: sp.Basic | None = Field(alias=C.NOISE_FORMULA, default=None)
182167
#: Noise distribution.
@@ -193,9 +178,7 @@ class Observable(BaseModel):
193178
"name",
194179
"formula",
195180
"noise_formula",
196-
"noise_formula",
197181
"noise_distribution",
198-
"transformation",
199182
mode="before",
200183
)
201184
@classmethod

petab/v2/lint.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -326,12 +326,12 @@ class CheckPosLogMeasurements(ValidationTask):
326326
log-transformation are positive."""
327327

328328
def run(self, problem: Problem) -> ValidationIssue | None:
329-
from .core import ObservableTransformation as ot
329+
from .core import NoiseDistribution as nd
330330

331331
log_observables = {
332332
o.id
333333
for o in problem.observable_table.observables
334-
if o.transformation in [ot.LOG, ot.LOG10]
334+
if o.noise_distribution in [nd.LOG_NORMAL, nd.LOG_LAPLACE]
335335
}
336336
if log_observables:
337337
for m in problem.measurement_table.measurements:

petab/v2/petab1to2.py

Lines changed: 56 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -104,14 +104,23 @@ def petab_files_1to2(yaml_config: Path | str, output_dir: Path | str):
104104
# sub-problems
105105
for problem_config in new_yaml_config.problems:
106106
# copy files that don't need conversion
107-
# (models, observables, visualizations)
107+
# (models, visualizations)
108108
for file in chain(
109-
problem_config.observable_files,
110109
(model.location for model in problem_config.model_files.values()),
111110
problem_config.visualization_files,
112111
):
113112
_copy_file(get_src_path(file), Path(get_dest_path(file)))
114113

114+
# Update observable table
115+
for observable_file in problem_config.observable_files:
116+
observable_df = v1.get_observable_df(get_src_path(observable_file))
117+
observable_df = v1v2_observable_df(
118+
observable_df,
119+
)
120+
v2.write_observable_df(
121+
observable_df, get_dest_path(observable_file)
122+
)
123+
115124
# Update condition table
116125
for condition_file in problem_config.condition_files:
117126
condition_df = v1.get_condition_df(get_src_path(condition_file))
@@ -339,3 +348,48 @@ def v1v2_condition_df(
339348
)
340349

341350
return condition_df
351+
352+
353+
def v1v2_observable_df(observable_df: pd.DataFrame) -> pd.DataFrame:
354+
"""Convert observable table from petab v1 to v2.
355+
356+
Perform all updates that can be done solely on the observable table:
357+
* drop observableTransformation, update noiseDistribution
358+
"""
359+
df = observable_df.copy().reset_index()
360+
361+
# drop observableTransformation, update noiseDistribution
362+
# if there is no observableTransformation, no need to update
363+
if v1.C.OBSERVABLE_TRANSFORMATION in df.columns:
364+
df[v1.C.OBSERVABLE_TRANSFORMATION] = df[
365+
v1.C.OBSERVABLE_TRANSFORMATION
366+
].fillna(v1.C.LIN)
367+
368+
if v1.C.NOISE_DISTRIBUTION in df:
369+
df[v1.C.NOISE_DISTRIBUTION] = df[v1.C.NOISE_DISTRIBUTION].fillna(
370+
v1.C.NORMAL
371+
)
372+
else:
373+
df[v1.C.NOISE_DISTRIBUTION] = v1.C.NORMAL
374+
375+
# merge observableTransformation into noiseDistribution
376+
def update_noise_dist(row):
377+
dist = row.get(v1.C.NOISE_DISTRIBUTION)
378+
trans = row.get(v1.C.OBSERVABLE_TRANSFORMATION)
379+
380+
if trans == v1.C.LIN:
381+
new_dist = dist
382+
else:
383+
new_dist = f"{trans}-{dist}"
384+
385+
if new_dist not in v2.C.NOISE_DISTRIBUTIONS:
386+
raise NotImplementedError(
387+
f"Noise distribution `{new_dist}' for "
388+
f"observable `{row[v1.C.OBSERVABLE_ID]}'"
389+
f" is not supported in PEtab v2."
390+
)
391+
392+
df[v2.C.NOISE_DISTRIBUTION] = df.apply(update_noise_dist, axis=1)
393+
df.drop(columns=[v1.C.OBSERVABLE_TRANSFORMATION], inplace=True)
394+
395+
return df

petab/v2/problem.py

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -903,7 +903,6 @@ def add_observable(
903903
formula: str,
904904
noise_formula: str | float | int = None,
905905
noise_distribution: str = None,
906-
transform: str = None,
907906
name: str = None,
908907
**kwargs,
909908
):
@@ -914,7 +913,6 @@ def add_observable(
914913
formula: The observable formula
915914
noise_formula: The noise formula
916915
noise_distribution: The noise distribution
917-
transform: The observable transformation
918916
name: The observable name
919917
kwargs: additional columns/values to add to the observable table
920918
@@ -929,8 +927,7 @@ def add_observable(
929927
record[NOISE_FORMULA] = noise_formula
930928
if noise_distribution is not None:
931929
record[NOISE_DISTRIBUTION] = noise_distribution
932-
if transform is not None:
933-
record[OBSERVABLE_TRANSFORMATION] = transform
930+
934931
record.update(kwargs)
935932

936933
self.observable_table += core.Observable(**record)

tests/v2/test_conversion.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,9 @@ def test_benchmark_collection(problem_id):
4040
pytest.skip("Too slow. Re-enable once we are faster.")
4141

4242
yaml_path = benchmark_models_petab.get_problem_yaml_path(problem_id)
43-
problem = petab1to2(yaml_path)
43+
try:
44+
problem = petab1to2(yaml_path)
45+
except NotImplementedError as e:
46+
pytest.skip(str(e))
4447
assert isinstance(problem, Problem)
4548
assert len(problem.measurement_table.measurements)

0 commit comments

Comments
 (0)