diff --git a/petab/v2/petab1to2.py b/petab/v2/petab1to2.py index bd731691..20263434 100644 --- a/petab/v2/petab1to2.py +++ b/petab/v2/petab1to2.py @@ -15,6 +15,7 @@ from pandas.io.common import get_handle, is_url from .. import v1, v2 +from ..v1.parameters import scale, unscale from ..v1.yaml import get_path_prefix, load_yaml from ..v1.yaml import validate as validate_yaml from ..versions import get_major_version @@ -516,7 +517,36 @@ def update_prior(row) -> tuple[str, str | None]: pscale = v1.C.LIN if prior_type not in v1.C.PARAMETER_SCALE_PRIOR_TYPES: - return prior_type, prior_pars + # v1 logNormal / logLaplace are based on the natural logarithm, + # just like v2 log-normal / log-laplace + return { + v1.C.LOG_NORMAL: v2.C.LOG_NORMAL, + v1.C.LOG_LAPLACE: v2.C.LOG_LAPLACE, + }.get(prior_type, prior_type), prior_pars + + if prior_type == v1.C.PARAMETER_SCALE_UNIFORM and pscale != v1.C.LIN: + # A uniform distribution of ln(x) (log10(x)) on [a, b] is a + # log-uniform distribution of x on [e^a, e^b] ([10^a, 10^b]). + # v2 log-uniform takes the bounds of x as parameters. + # Missing parameters default to the parameter bounds (see + # `update_prior_pars` below), as in v1. + if not pd.isna(prior_pars): + # Clip [a, b] to the bounds before unscaling, as v1 does + # (see `petab.v1.priors.Prior`). This avoids overflow for + # intervals that exceed the floating-point range when + # unscaled. + low, high = map( + float, str(prior_pars).split(v1.C.PARAMETER_SEPARATOR) + ) + lb, ub = row[v1.C.LOWER_BOUND], row[v1.C.UPPER_BOUND] + low = lb if low <= scale(lb, pscale) else unscale(low, pscale) + high = ( + ub if high >= scale(ub, pscale) else unscale(high, pscale) + ) + prior_pars = ( + f"{float(low)}{v2.C.PARAMETER_SEPARATOR}{float(high)}" + ) + return v2.C.LOG_UNIFORM, prior_pars if pscale == v1.C.LOG10 and prior_type in ( v1.C.PARAMETER_SCALE_NORMAL, diff --git a/tests/v2/test_conversion.py b/tests/v2/test_conversion.py index a09acb22..68685c37 100644 --- a/tests/v2/test_conversion.py +++ b/tests/v2/test_conversion.py @@ -197,24 +197,41 @@ def simulate(measurement_df): "ignore:.*Parameter scales are not supported in PEtab v2.*:UserWarning" ) @pytest.mark.parametrize( - "parameter_scale, prior_type", + "parameter_scale, prior_type, prior_parameters", [ - (v1.C.LOG10, v1.C.PARAMETER_SCALE_NORMAL), - (v1.C.LOG10, v1.C.PARAMETER_SCALE_LAPLACE), - (v1.C.LOG, v1.C.PARAMETER_SCALE_NORMAL), - (v1.C.LOG, v1.C.PARAMETER_SCALE_LAPLACE), - (v1.C.LIN, v1.C.PARAMETER_SCALE_NORMAL), + (v1.C.LOG10, v1.C.PARAMETER_SCALE_NORMAL, "-0.5;0.7"), + (v1.C.LOG10, v1.C.PARAMETER_SCALE_LAPLACE, "-0.5;0.7"), + (v1.C.LOG, v1.C.PARAMETER_SCALE_NORMAL, "-0.5;0.7"), + (v1.C.LOG, v1.C.PARAMETER_SCALE_LAPLACE, "-0.5;0.7"), + (v1.C.LIN, v1.C.PARAMETER_SCALE_NORMAL, "-0.5;0.7"), + # parameterScaleUniform within the bounds, exceeding the bounds + # (also beyond the floating-point range when unscaled), + # and defaulting to the bounds + (v1.C.LOG10, v1.C.PARAMETER_SCALE_UNIFORM, "-0.5;0.7"), + (v1.C.LOG10, v1.C.PARAMETER_SCALE_UNIFORM, "-5;0.7"), + (v1.C.LOG10, v1.C.PARAMETER_SCALE_UNIFORM, "-400;400"), + (v1.C.LOG10, v1.C.PARAMETER_SCALE_UNIFORM, np.nan), + (v1.C.LOG, v1.C.PARAMETER_SCALE_UNIFORM, "-0.5;0.7"), + (v1.C.LOG, v1.C.PARAMETER_SCALE_UNIFORM, "-10;0.7"), + (v1.C.LOG, v1.C.PARAMETER_SCALE_UNIFORM, "-1000;1000"), + (v1.C.LOG, v1.C.PARAMETER_SCALE_UNIFORM, np.nan), + (v1.C.LIN, v1.C.PARAMETER_SCALE_UNIFORM, "0.1;0.7"), + # logNormal / logLaplace do not depend on the parameter scale + (v1.C.LOG10, v1.C.LOG_NORMAL, "-0.5;0.7"), + (v1.C.LOG10, v1.C.LOG_LAPLACE, "-0.5;0.7"), + (v1.C.LIN, v1.C.LOG_NORMAL, "-0.5;0.7"), + (v1.C.LIN, v1.C.LOG_LAPLACE, "-0.5;0.7"), ], ) -def test_petab1to2_parameter_scale_priors( - tmp_path, parameter_scale, prior_type +def test_petab1to2_priors( + tmp_path, parameter_scale, prior_type, prior_parameters ): - """Test that parameterScale{Normal,Laplace} priors are converted to - priors with the same density.""" + """Test that objective priors are converted to priors with the same + density.""" k1 = _K1 | { v1.C.PARAMETER_SCALE: parameter_scale, v1.C.OBJECTIVE_PRIOR_TYPE: prior_type, - v1.C.OBJECTIVE_PRIOR_PARAMETERS: "-0.5;0.7", + v1.C.OBJECTIVE_PRIOR_PARAMETERS: prior_parameters, } yaml_file = _write_v1_problem( tmp_path,