Source code for pharmpy.modeling.units

from __future__ import annotations

from typing import TypeVar, Union

from pharmpy.basic import Unit
from pharmpy.deps import sympy
from pharmpy.internals.expr.subs import subs
from pharmpy.internals.expr.tree import prune
from pharmpy.model import Assignment, CompartmentalSystem, Model

T = TypeVar('T')


def _extract_minus(expr):
    if expr.is_Mul and expr.args[0] == -1:
        return sympy.Mul(*expr.args[1:])
    else:
        return expr


[docs] def get_unit_of(model: Model, variable: Union[str, sympy.Symbol]) -> Unit: """Derive the physical unit of a variable in the model Unit information for the dataset needs to be available. The variable can be defined in the code, a dataset olumn, a parameter or a random variable. Parameters ---------- model : Model Pharmpy model object variable : str or Symbol Find physical unit of this variable Returns ------- Unit A unit expression Examples -------- >>> from pharmpy.modeling import load_example_model, get_unit_of >>> model = load_example_model("pheno") >>> get_unit_of(model, "Y") milligram/liter >>> get_unit_of(model, "VC") liter >>> get_unit_of(model, "WGT") kilogram """ if isinstance(variable, str): symbol = sympy.Symbol(variable) else: symbol = variable variable = variable.name di = model.datainfo if variable in di.names: return di[variable].unit # FIXME: Handle other DVs? y = sympy.sympify(list(model.dependent_variables.keys())[0]) input_units = {sympy.Symbol(col.name): col.unit._expr for col in di} pruned_nodes = {sympy.exp} def pruning_predicate(e: sympy.Basic) -> bool: return e.func in pruned_nodes unit_eqs = [] # FIXME: Using private _expr in some places. sympify doesn't work for some reason. a = di[di.dv_column.name].unit._expr unit_eqs.append(y - a) d = {} for s in model.statements: if isinstance(s, Assignment): expr = sympy.expand( subs( prune(pruning_predicate, sympy.sympify(s.expression)), input_units, simultaneous=True, ) ) if expr.is_Add: for term in expr.args: unit_eqs.append(sympy.sympify(s.symbol) - _extract_minus(term)) else: unit_eqs.append(sympy.sympify(s.symbol) - _extract_minus(expr)) elif isinstance(s, CompartmentalSystem): amt_unit = di[di.typeix['dose'][0].name].unit._expr time_unit = di[di.idv_column.name].unit._expr for e in s.compartmental_matrix.diagonal(): e = sympy.sympify(e) if e.is_Add: for term in e.args: unit_eqs.append(amt_unit / time_unit - _extract_minus(term)) elif e == 0: pass else: unit_eqs.append(amt_unit / time_unit - _extract_minus(e)) for a in s.amounts: sy = sympy.Symbol(a.name) d[a] = sy unit_eqs.append(amt_unit - sy) filtered_unit_eqs = [eq.subs(d) for eq in unit_eqs] # NOTE: For some reason telling sympy to solve for "symbol" does not work sol = sympy.solve(filtered_unit_eqs, dict=True) return Unit(sol[0][symbol])