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
        Pharmpy model
    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