Source code for pharmpy.modeling.pd

"""
:meta private:
"""

from __future__ import annotations

from typing import Literal

from pharmpy.basic import Expr
from pharmpy.model import (
    Assignment,
    Compartment,
    CompartmentalSystem,
    CompartmentalSystemBuilder,
    Model,
    Statements,
    get_and_check_odes,
    output,
)
from pharmpy.modeling import get_central_volume_and_clearance, set_initial_condition

from .error import set_proportional_error_model
from .odes import add_individual_parameter, set_initial_estimates

PDTypes = Literal['linear', 'emax', 'sigmoid', 'step', 'loglin']


[docs] def add_effect_compartment(model: Model, expr: PDTypes): r"""Add an effect compartment. Implemented PD models are: * Linear: .. math:: E = B \cdot (1 + \text{slope} \cdot C) * Emax: .. math:: E = B \cdot \Bigg(1 + \frac {E_{max} \cdot C } { EC_{50} + C} \Bigg) * Step effect: .. math:: E = \Biggl \lbrace {B \quad \text{if C} \leq 0 \atop B \cdot (1+ E_{max}) \quad \text{else}} * Sigmoidal: .. math:: E=\Biggl \lbrace {B \cdot \Bigl(1+\frac{E_{max} \cdot C^n}{EC_{50}^n+C^n}\Bigl) \quad \ \text{if C}>0 \atop B \quad \text{else}} * Log-linear: .. math:: E = \text{slope} \cdot \text{log}(C + C_0) :math:`B` is the baseline effect Parameters ---------- model : Model Pharmpy model expr : {'linear', 'emax', 'sigmoid', 'step', 'loglin'} Name of the PD effect function. Return ------ Model Pharmpy model object Examples -------- >>> from pharmpy.modeling import * >>> model = load_example_model("pheno") >>> model = add_effect_compartment(model, "linear") >>> model.statements.ode_system.find_compartment("EFFECT") Compartment(EFFECT, amount=A_EFFECT(t), input=KE0*A_CENTRAL(t)/VC) """ vc, cl = get_central_volume_and_clearance(model) odes = get_and_check_odes(model) central = odes.central_compartment cb = CompartmentalSystemBuilder(odes) ke0 = Expr.symbol("KE0") met = Expr.symbol('MET') model = add_individual_parameter(model, met.name) ke0_ass = Assignment.create(ke0, 1 / met) effect = Compartment.create("EFFECT", input=ke0 * central.amount / vc) cb.add_compartment(effect) cb.add_flow(effect, output, ke0) model = model.replace( statements=Statements( model.statements.before_odes + ke0_ass + CompartmentalSystem(cb) + model.statements.after_odes ) ) model = _add_effect(model, expr, effect.amount) return model
[docs] def set_direct_effect(model: Model, expr: PDTypes): r"""Add an effect to a model. Implemented PD models are: * Linear: .. math:: E = B \cdot (1 + \text{slope} \cdot C) * Emax: .. math:: E = B \cdot \Bigg(1 + \frac {E_{max} \cdot C } { EC_{50} + C} \Bigg) * Step effect: .. math:: E = \Biggl \lbrace {B \quad \text{if C} \leq 0 \atop B \cdot (1+ E_{max}) \quad \text{else}} * Sigmoidal: .. math:: E=\Biggl \lbrace {B \cdot \Bigl(1+\frac{E_{max} \cdot C^n}{EC_{50}^n+C^n}\Bigl) \quad \ \text{if C}>0 \atop B \quad \text{else}} * Log-linear: .. math:: E = \text{slope} \cdot \text{log}(C + C_0) :math:`B` is the baseline effect Parameters ---------- model : Model Pharmpy model expr : {'linear', 'emax', 'sigmoid', 'step', 'loglin'} Name of PD effect function. Return ------ Model Pharmpy model object Examples -------- >>> from pharmpy.modeling import * >>> model = load_example_model("pheno") >>> model = set_direct_effect(model, "linear") >>> model.statements.find_assignment("E") ⎛SLOPE⋅A_CENTRAL(t) ⎞ B⋅⎜────────────────── + 1⎟ E = ⎝ VC ⎠ """ vc, cl = get_central_volume_and_clearance(model) odes = get_and_check_odes(model) conc = odes.central_compartment.amount / vc model = _add_effect(model, expr, conc) return model
def _add_effect(model: Model, expr: str, conc): e0 = Expr.symbol("B") model = add_individual_parameter(model, e0.name) # Add effect E if expr == "linear": s = Expr.symbol("SLOPE") model = add_individual_parameter(model, s.name) E = Assignment(Expr.symbol('E'), e0 * (1 + (s * conc))) elif expr == "emax": emax = Expr.symbol("E_MAX") model = add_individual_parameter(model, emax.name) ec50 = Expr.symbol("EC_50") model = add_individual_parameter(model, ec50.name) E = Assignment(Expr.symbol("E"), e0 * (1 + (emax * conc / (ec50 + conc)))) elif expr == "step": emax = Expr.symbol("E_MAX") model = add_individual_parameter(model, emax.name) E = Assignment(Expr.symbol("E"), Expr.piecewise((e0, conc <= 0), (e0 * (1 + emax), True))) elif expr == "sigmoid": emax = Expr.symbol("E_MAX") model = add_individual_parameter(model, emax.name) ec50 = Expr.symbol("EC_50") model = add_individual_parameter(model, ec50.name) n = Expr.symbol("N") # Hill coefficient model = add_individual_parameter(model, n.name) model = set_initial_estimates(model, {"POP_N": 1}) E = Assignment.create( Expr.symbol("E"), Expr.piecewise( ((e0 * (1 + (emax * conc**n / (ec50**n + conc**n)))), conc > 0), (e0, True) ), ) elif expr == "loglin": s = Expr.symbol("SLOPE") model = add_individual_parameter(model, s.name) E = Assignment(Expr.symbol("E"), s * (conc + (e0 / s).exp()).log()) else: raise ValueError(f'Unknown model "{expr}".') # Add dependent variable Y_2 y_2 = Expr.symbol('Y_2') y = Assignment.create(y_2, E.symbol) dvs = model.dependent_variables.replace(y_2, 2) model = model.replace(statements=model.statements + E + y, dependent_variables=dvs) # Add error model model = set_proportional_error_model(model, dv=2, zero_protection=False) return model
[docs] def add_indirect_effect( model: Model, expr: Literal['linear', 'emax', 'sigmoid', 'step'], prod: bool = True, ): r"""Add indirect (turnover) effect The concentration :math:`C_c` has an impact on the production or degradation rate of the response R: * Production: .. math:: \frac {dR}{dt} = k_{in} \cdot (1 + f(C_c)) - k_{out} \cdot R * Degradation: .. math:: \frac {dR}{dt} = k_{in} - k_{out} \cdot (1 + f(C_c)) \cdot R :math:`k_{in}` and :math:`k_{out}` can either be inhibited or stimulated. Baseline :math:`B = R(0) = R_0 = k_{in}/k_{out}`. Models: * Linear: .. math:: f(C_c) = \text{slope} \cdot C_c * Emax: .. math:: f(C_c) = \frac {E_{max} \cdot C_c } { EC_{50} + C_c } * Sigmoidal: .. math:: f(C_c) = \frac{E_{max} \cdot C_c^n}{EC_{50}^n+C^n} Parameters ---------- model : Model Pharmpy model prod : bool Production (True) (default) or degradation (False) expr : {'linear', 'emax', 'sigmoid', 'step'} Name of PD effect function. Return ------ Model Pharmpy model object Examples -------- >>> from pharmpy.modeling import * >>> model = load_example_model("pheno") >>> model = add_indirect_effect(model, expr='linear', prod=True) """ vc, cl = get_central_volume_and_clearance(model) odes = get_and_check_odes(model) central = odes.central_compartment conc_c = central.amount / vc response = Compartment.create("RESPONSE") a_response = response.amount kin = Expr.symbol("K_IN") kout = Expr.symbol("K_OUT") met = Expr.symbol('MET') model = add_individual_parameter(model, met.name) b = Expr.symbol("B") # baseline model = add_individual_parameter(model, b.name) kout_ass = Assignment.create(kout, 1 / met) kin_ass = Assignment.create(kin, kout * b) if expr == 'linear': s = Expr.symbol("SLOPE") model = add_individual_parameter(model, s.name) R = Expr.symbol("SLOPE") * conc_c elif expr == 'emax': emax = Expr.symbol("E_MAX") model = add_individual_parameter(model, emax.name) ec50 = Expr.symbol("EC_50") model = add_individual_parameter(model, ec50.name) R = emax * conc_c / (ec50 + conc_c) elif expr == 'sigmoid': emax = Expr.symbol("E_MAX") ec50 = Expr.symbol("EC_50") n = Expr.symbol("N") model = add_individual_parameter(model, n.name) model = set_initial_estimates(model, {"POP_N": 1}) model = add_individual_parameter(model, ec50.name) model = add_individual_parameter(model, emax.name) R = emax * conc_c**n / (ec50**n + conc_c**n) else: raise ValueError(f'Unknown model "{expr}".') cb = CompartmentalSystemBuilder(odes) if prod: response = Compartment.create("RESPONSE", input=kin * (1 + R)) cb.add_compartment(response) cb.add_flow(response, output, kout) elif not prod: response = Compartment.create("RESPONSE", input=kin) cb.add_compartment(response) cb.add_flow(response, output, kout * (1 + R)) model = model.replace( statements=Statements( model.statements.before_odes + kout_ass + kin_ass + CompartmentalSystem(cb) + model.statements.after_odes ) ) model = set_initial_condition(model, "RESPONSE", b) # Add dependent variable Y_2 y_2 = Expr.symbol('Y_2') y = Assignment(y_2, a_response) dvs = model.dependent_variables.replace(y_2, 2) model = model.replace(statements=model.statements + y, dependent_variables=dvs) # Add error model model = set_proportional_error_model(model, dv=2, zero_protection=False) return model.update_source()
[docs] def set_baseline_effect(model: Model, expr: str = 'const'): r"""Create baseline effect model. Currently implemented baseline effects are: Constant baseline effect (const): .. math:: E = B Parameters ---------- model : Model Pharmpy model expr : str Name of baseline effect function. Return ------ Model Pharmpy model object Examples -------- >>> from pharmpy.modeling import * >>> model = load_example_model("pheno") >>> model = set_baseline_effect(model, expr='const') >>> model.statements.find_assignment("E") E = B """ e0 = Expr.symbol("B") model = add_individual_parameter(model, e0.name) E = Assignment(Expr.symbol('E'), e0) # Add dependent variable Y_2 y_2 = Expr.symbol('Y_2') y = Assignment.create(y_2, E.symbol) dvs = model.dependent_variables.replace(y_2, 2) model = model.replace(statements=model.statements + E + y, dependent_variables=dvs) # Add error model model = set_proportional_error_model(model, dv=2, zero_protection=False) return model