Source code for pharmpy.modeling.estimation
from __future__ import annotations
import math
from typing import Union
from pharmpy.deps import numpy as np
from pharmpy.deps import pandas as pd
from pharmpy.model import Model
class UCPScale:
def __init__(self, theta, omega, sigma, lb, range_ul):
self.theta = theta
self.omega = omega
self.sigma = sigma
self.lb = lb
self.range_ul = range_ul
def __repr__(self):
return "<UCPScale object>"
[docs]
def calculate_ucp_scale(model: Model):
"""Calculate a scale for unconstrained parameters for a model
The UCPScale object can be used to calculate unconstrained parameters
back into the normal parameter space.
Parameters
----------
model : Model
Model for which to calculate an ucp scale
Returns
-------
UCPScale
A scale object
See Also
--------
calculate_parameters_from_ucp : Calculate parameters from ucp:s
Example
-------
>>> from pharmpy.modeling import *
>>> model = load_example_model("pheno")
>>> scale = calculate_ucp_scale(model)
"""
omega_symbolic = model.random_variables.etas.covariance_matrix
omega = omega_symbolic.subs(model.parameters.inits)
omega = omega.to_numpy()
scale_omega = _scale_matrix(omega)
sigma_symbolic = model.random_variables.epsilons.covariance_matrix
sigma = sigma_symbolic.subs(model.parameters.inits)
sigma = sigma.to_numpy()
scale_sigma = _scale_matrix(sigma)
theta = []
lb = []
range_ul_vec = []
for p in model.parameters:
if not p.fix:
if p.symbol not in model.random_variables.free_symbols:
upper = p.upper if p.upper < 1000000 else 1000000
lower = p.lower if p.lower > -1000000 else -1000000
range_ul = upper - lower
range_prop = (p.init - lower) / range_ul
scaled = 0.1 - math.log(range_prop / (1.0 - range_prop))
theta.append(scaled)
lb.append(lower)
range_ul_vec.append(range_ul)
return UCPScale(np.array(theta), scale_omega, scale_sigma, np.array(lb), np.array(range_ul_vec))
def _scale_matrix(A):
chol = np.linalg.cholesky(A)
M1 = np.tril(chol)
v1 = np.diag(M1)
v2 = v1 / np.exp(0.1)
M2 = np.diag(v1)
M3 = np.diag(v2)
m_scale = np.abs(10 * (M1 - M2)) + M3
# Convert lower triangle to symmetric
irows, icols = np.triu_indices(len(m_scale), 1)
m_scale[irows, icols] = m_scale[icols, irows]
return m_scale
[docs]
def calculate_parameters_from_ucp(
model: Model, scale: UCPScale, ucps: Union[pd.Series, dict[str, float]]
):
"""Scale parameter values from ucp to normal scale
Parameters
----------
model : Model
Pharmpy model
scale : UCPScale
A parameter scale
ucps : pd.Series or dict
Series of parameter values
Returns
-------
pd.Series
Parameters on the normal scale
Examples
--------
>>> from pharmpy.modeling import *
>>> model = load_example_model("pheno")
>>> scale = calculate_ucp_scale(model)
>>> values = {'POP_CL': 0.1, 'POP_VC': 0.1, 'COVAPGR': 0.1, \
'IIV_CL': 0.1, 'IIV_VC': 0.1, 'SIGMA': 0.1}
>>> calculate_parameters_from_ucp(model, scale, values)
POP_CL 0.004693
POP_VC 1.00916
COVAPGR 0.1
IIV_CL 0.0309626
IIV_VC 0.031127999999999996
SIGMA 0.0130865
dtype: object
See also
--------
calculate_ucp_scale : Calculate the scale for conversion from ucps
"""
fixed_parameters_dict = model.parameters.fixed.inits
for p in model.parameters.names:
if p not in fixed_parameters_dict and p not in ucps:
raise ValueError(f'Parameter "{p}" is neither fixed nor given in ucps.')
if p in fixed_parameters_dict and p in ucps:
raise ValueError(f'Parameter "{p}" cannot both be fixed and given in ucps.')
omega_symbolic = model.random_variables.etas.covariance_matrix
omega = omega_symbolic.subs(dict(ucps))
omega = omega.subs(fixed_parameters_dict)
omega = omega.to_numpy()
descaled_omega = _descale_matrix(omega, scale.omega)
omega_dict = {}
for symb, numb in zip(omega_symbolic, np.nditer(descaled_omega)):
if symb.is_symbol():
omega_dict[symb] = numb
sigma_symbolic = model.random_variables.epsilons.covariance_matrix
sigma = sigma_symbolic.subs(dict(ucps))
sigma = sigma.subs(fixed_parameters_dict)
sigma = sigma.to_numpy()
descaled_sigma = _descale_matrix(sigma, scale.sigma)
sigma_dict = {}
for symb, numb in zip(sigma_symbolic, np.nditer(descaled_sigma)):
if symb.is_symbol():
sigma_dict[symb] = numb
theta = []
for p in model.parameters:
if not p.fix:
if p.symbol not in model.random_variables.free_symbols:
theta.append(ucps[p.name])
theta = np.array(theta)
diff_scale = theta - scale.theta
prop_scale = np.exp(diff_scale) / (1.0 + np.exp(diff_scale))
descaled = prop_scale * scale.range_ul + scale.lb
d = {}
i = 0
for p in model.parameters:
if not p.fix:
if p.symbol not in model.random_variables.free_symbols:
d[p.name] = descaled[i]
i += 1
elif p.symbol in omega_symbolic.free_symbols:
d[p.name] = omega_dict[p.symbol]
else:
d[p.name] = sigma_dict[p.symbol]
return pd.Series(d)
def _descale_matrix(A, S):
exp_diag = np.exp(np.diag(A))
M = A.copy()
np.fill_diagonal(M, exp_diag)
M2 = M * S
M3 = np.tril(M2)
return M3 @ M3.T