def set_dtbs_error(model): """Dynamic transform both sides""" theta_as_stdev(model) set_weighted_error_model(model) stats, y, f = _preparations(model) tbs_lambda = Parameter('tbs_lambda', 1) tbs_zeta = Parameter('tbs_zeta', 0.001) model.parameters.append(tbs_lambda) model.parameters.append(tbs_zeta) lam = tbs_lambda.symbol zeta = tbs_zeta.symbol for i, s in enumerate(stats): if isinstance(s, Assignment) and s.symbol == sympy.Symbol('W'): break stats.insert(i + 1, Assignment('W', (f ** zeta) * sympy.Symbol('W'))) ipred = sympy.Piecewise( ((f ** lam - 1) / lam, sympy.And(sympy.Ne(lam, 0), sympy.Ne(f, 0))), (sympy.log(f), sympy.And(sympy.Eq(lam, 0), sympy.Ne(f, 0))), (-1 / lam, sympy.And(sympy.Eq(lam, 0), sympy.Eq(f, 0))), (-1000000000, True), ) stats.insert(i + 2, Assignment('IPRED', ipred)) yexpr = stats.find_assignment(y.name) yexpr.subs({f: sympy.Symbol('IPRED')}) obs = sympy.Piecewise( (sympy.log(y), sympy.Eq(lam, 0)), ((y ** lam - 1) / lam, sympy.Ne(lam, 0)) ) model.observation_transformation = obs return model
def test_str(testdata): s1 = Assignment(S('KA'), S('X') + S('Y')) assert str(s1) == 'KA := X + Y\n' s2 = Assignment(S('X2'), sympy.exp('X')) a = str(s2).split('\n') assert a[0].startswith(' ') assert len(a) == 3 model = Model(testdata / 'nonmem' / 'pheno.mod') assert 'THETA(2)' in str(model.statements) assert 'THETA(2)' in repr(model.statements)
def add_parameters_ratio(model, numpar, denompar, source, dest): statements = model.statements if not statements.find_assignment( numpar) or not statements.find_assignment(denompar): odes = statements.ode_system rate = odes.get_flow(source, dest) numer, denom = rate.as_numer_denom() par1 = Assignment(numpar, numer) par2 = Assignment(denompar, denom) if rate != par1.symbol / par2.symbol: if not statements.find_assignment(numpar): statements.add_before_odes(par1) if not statements.find_assignment(denompar): statements.add_before_odes(par2) odes.add_flow(source, dest, par1.symbol / par2.symbol)
def power(cls): """Power template (for continuous covariates).""" symbol = S('symbol') expression = (S('cov') / S('median'))**S('theta') template = Assignment(symbol, expression) return cls(template)
def test_proportional_error_model_log(testdata): model = Model(testdata / 'nonmem' / 'pheno.mod') model.statements[5] = Assignment('Y', 'F') proportional_error(model, data_trans='log(Y)') model.update_source() assert str(model).split('\n')[11] == 'Y = LOG(F) + EPS(1)' assert str(model).split('\n')[17] == '$SIGMA 0.09 ; sigma'
def linear(cls): """Linear continuous template (for continuous covariates).""" symbol = S('symbol') expression = 1 + S('theta') * (S('cov') - S('median')) template = Assignment(symbol, expression) return cls(template)
def test_add_random_variables_and_statements(pheno_path): model = Model(pheno_path) rvs = model.random_variables pset = model.parameters eta = RandomVariable.normal('ETA_NEW', 'iiv', 0, S('omega')) rvs.append(eta) pset.append(Parameter('omega', 0.1)) eps = RandomVariable.normal('EPS_NEW', 'ruv', 0, S('sigma')) rvs.append(eps) pset.append(Parameter('sigma', 0.1)) model.random_variables = rvs model.parameters = pset sset = model.get_pred_pk_record().statements statement_new = Assignment(S('X'), 1 + S(eps.name) + S(eta.name)) sset.append(statement_new) model.get_pred_pk_record().statements = sset model.update_source() assert str(model.get_pred_pk_record()).endswith('X = 1 + ETA(3) + EPS(2)\n\n')
def exponential(cls): """Exponential template (for continuous covariates).""" symbol = S('symbol') expression = exp(S('theta') * (S('cov') - S('median'))) template = Assignment(symbol, expression) return cls(template)
def categorical(cls, counts): """Linear categorical template (for categorical covariates).""" symbol = S('symbol') most_common = counts.mode().pop(0) categories = counts.unique() values = [1] if np.isnan(most_common): most_common = S('NaN') conditions = [Eq(S('cov'), most_common)] for i, cat in enumerate(categories): if cat != most_common: if len(categories) == 2: values += [1 + S('theta')] else: values += [1 + S(f'theta{i}')] if np.isnan(cat): conditions += [Eq(S('cov'), S('NaN'))] else: conditions += [Eq(S('cov'), cat)] expression = Piecewise(*zip(values, conditions)) template = Assignment(symbol, expression) return cls(template)
def set_weighted_error_model(model): """Encode error model with one epsilon and W as weight""" stats, y, f = _preparations(model) epsilons = model.random_variables.epsilons expr = stats.find_assignment(y.name).expression ssum = 0 q = sympy.Q.real(y) # Dummy predicate for term in expr.args: eps = [x for x in term.free_symbols if x.name in epsilons.names] if len(eps) > 0: eps = eps[0] remaining = term / eps ssum += remaining ** 2 for symb in remaining.free_symbols: q &= sympy.Q.positive(symb) w = sympy.sqrt(ssum) w = sympy.refine(w, q) for i, s in enumerate(stats): if isinstance(s, Assignment) and s.symbol == y: stats.insert(i, Assignment('W', w)) break stats.reassign(y, f + sympy.Symbol('W') * sympy.Symbol(epsilons[0].name)) model.remove_unused_parameters_and_rvs() return model
def test_translate_sympy_piecewise(parser, symbol, expression, buf_expected): buf_original = '$PRED\nY = THETA(1) + ETA(1) + EPS(1)\n' rec = parser.parse(buf_original).records[0] s = Assignment(symbol, expression) statements = rec.statements statements.append(s) rec.statements = statements assert str(rec) == buf_original.strip() + buf_expected
def _f_link_assignment(model, compartment): f = symbol('F') fexpr = compartment.amount pkrec = model.control_stream.get_records('PK')[0] if pkrec.statements.find_assignment('S1'): fexpr = fexpr / symbol('S1') ass = Assignment(f, fexpr) return ass
def _add_parameter(model, name, init=0.1): pops = model.create_symbol(f'POP_{name}') pop_param = Parameter(pops.name, init=init, lower=0) model.parameters.add(pop_param) symb = model.create_symbol(name) ass = Assignment(symb, pop_param.symbol) model.statements.insert(0, ass) return symb
def update_lag_time(model, old, new): new_dosing = new.find_dosing() new_lag_time = new_dosing.lag_time old_lag_time = old.find_dosing().lag_time if new_lag_time != old_lag_time and new_lag_time != 0: ass = Assignment('ALAG1', new_lag_time) model.statements.add_before_odes(ass) new_dosing.lag_time = ass.symbol
def test_statements_setter_add_from_sympy(parser, buf_original, sym, expression, buf_new): rec_original = parser.parse(buf_original).records[0] assignment = Assignment(sym, expression) statements = rec_original.statements statements += [assignment] rec_original.statements = statements assert str(rec_original) == buf_new
def boxcox(cls, no_of_etas): assignments = ModelStatements() for i in range(1, no_of_etas + 1): symbol = S(f'etab{i}') expression = (exp(S(f'eta{i}')) ** S(f'theta{i}') - 1) / (S(f'theta{i}')) assignment = Assignment(symbol, expression) assignments.append(assignment) return cls('boxcox', assignments, 'lambda')
def test_find_assignment(testdata): model = Model(testdata / 'nonmem' / 'pheno_real.mod') statements = model.statements assert str(statements.find_assignment('CL').expression) == 'TVCL*exp(ETA(1))' assert str(statements.find_assignment('S1').expression) == 'V' assert str(statements.find_assignment('EPS(1)', is_symbol=False).symbol) == 'Y' statements.append(Assignment(S('CL'), S('TVCL') + S('V'))) assert str(statements.find_assignment('CL').expression) == 'TVCL + V'
def statements(self): try: return self._statements except AttributeError: pass rec = self.get_pred_pk_record() statements = rec.statements error = self._get_error_record() if error: sub = self.control_stream.get_records('SUBROUTINES')[0] advan = sub.get_option_startswith('ADVAN') trans = sub.get_option_startswith('TRANS') if not trans: trans = 'TRANS1' comp = compartmental_model(self, advan, trans) if comp is not None: cm, link = comp statements += [cm, link] else: statements.append( ODESystem()) # FIXME: Placeholder for ODE-system # FIXME: Dummy link statement statements.append(Assignment('F', symbols.symbol('F'))) statements += error.statements if pharmpy.plugins.nonmem.conf.parameter_names == 'comment': if not hasattr(self, '_parameters'): self._read_parameters() trans = self.parameter_translation(remove_idempotent=True, as_symbols=True) parameter_symbols = {symb for _, symb in trans.items()} clashing_symbols = parameter_symbols & statements.free_symbols if clashing_symbols: warnings.warn( f'The parameter names {clashing_symbols} are also names of variables ' f'in the model code. Falling back to the NONMEM default parameter ' f'names for these.') rev_trans = {val: key for key, val in trans.items()} trans = { nm_symb: symb for nm_symb, symb in trans.items() if symb not in clashing_symbols } for symb in clashing_symbols: self.parameters[symb.name].name = rev_trans[symb].name statements.subs(trans) self._statements = statements self._old_statements = statements.copy() return statements
def test_proportional_error_model(testdata): model = Model(testdata / 'nonmem' / 'pheno.mod') model.statements[5] = Assignment('Y', 'F') proportional_error(model) model.update_source() assert str(model).split('\n')[11] == 'Y=F+F*EPS(1)' assert str(model).split('\n')[17] == '$SIGMA 0.09 ; sigma' model = Model(testdata / 'nonmem' / 'pheno.mod') proportional_error(model) model.update_source() assert str(model).split('\n')[11] == 'Y=F+F*EPS(1)' assert str(model).split('\n')[17] == '$SIGMA 0.013241'
def update_ode_system(model, old, new): """Update ODE system Handle changes from CompartmentSystem to ExplicitODESystem """ if type(old) == CompartmentalSystem and type(new) == ExplicitODESystem: to_des(model, new) elif type(old) == CompartmentalSystem and type(new) == CompartmentalSystem: # subs = model.control_stream.get_records('SUBROUTINES')[0] # old_trans = subs.get_option_startswith('TRANS') # conv_advan, new_advan, new_trans = change_advan(model) update_lag_time(model, old, new) if isinstance(new.find_dosing().dose, Bolus) and 'RATE' in model.dataset.columns: df = model.dataset df.drop(columns=['RATE'], inplace=True) model.dataset = df advan, trans = new_advan_trans(model) pk_param_conversion(model, advan=advan, trans=trans) add_needed_pk_parameters(model, advan, trans) update_subroutines_record(model, advan, trans) update_model_record(model, advan) statements = model.statements if isinstance(new.find_dosing().dose, Infusion) and not statements.find_assignment('D1'): # Handle direct moving of Infusion dose statements.subs({'D2': 'D1'}) if isinstance(new.find_dosing().dose, Infusion) and isinstance( old.find_dosing().dose, Bolus): dose = new.find_dosing().dose if dose.rate is None: # FIXME: Not always D1 here! ass = Assignment('D1', dose.duration) dose.duration = ass.symbol else: raise NotImplementedError( "First order infusion rate is not yet supported") statements = model.statements statements.add_before_odes(ass) df = model.dataset rate = np.where(df['AMT'] == 0, 0, -2) df['RATE'] = rate # FIXME: Adding at end for now. Update $INPUT cannot yet handle adding in middle # df.insert(list(df.columns).index('AMT') + 1, 'RATE', rate) model.dataset = df force_des(model, new)
def create_effect_statement(self, operation_str, statement_original): """Creates statement for addition or multiplication of covariate to parameter, e.g. (if parameter is CL and covariate is WGT): CL = CLWGT + TVCL*EXP(ETA(1))""" operation = self._get_operation(operation_str) symbol = statement_original.symbol expression = statement_original.symbol statement_new = Assignment(symbol, operation(expression, self.template.symbol)) return statement_new
def apply(self, parameter, covariate, thetas, statistics): effect_name = f'{parameter}{covariate}' self.template.symbol = S(effect_name) self.template.subs(thetas) self.template.subs({'cov': covariate}) template_str = [str(symbol) for symbol in self.template.free_symbols] if 'mean' in template_str: self.template.subs({'mean': f'{covariate}_MEAN'}) s = Assignment(S(f'{covariate}_MEAN'), Float(statistics['mean'], 6)) self.statistic_statements.append(s) if 'median' in template_str: self.template.subs({'median': f'{covariate}_MEDIAN'}) s = Assignment(S(f'{covariate}_MEDIAN'), Float(statistics['median'], 6)) self.statistic_statements.append(s) if 'std' in template_str: self.template.subs({'std': f'{covariate}_STD'}) s = Assignment(S(f'{covariate}_STD'), Float(statistics['std'], 6)) self.statistic_statements.append(s)
def john_draper(cls, no_of_etas): assignments = ModelStatements() for i in range(1, no_of_etas + 1): symbol = S(f'etad{i}') eta = S(f'eta{i}') theta = S(f'theta{i}') expression = sign(eta) * (((abs(eta) + 1) ** theta - 1) / theta) assignment = Assignment(symbol, expression) assignments.append(assignment) return cls('johndraper', assignments, 'lambda')
def statements(self): try: return self._statements except AttributeError: pass rec = self.get_pred_pk_record() statements = rec.statements des = self._get_des_record() error = self._get_error_record() if error: sub = self.control_stream.get_records('SUBROUTINES')[0] advan = sub.get_option_startswith('ADVAN') trans = sub.get_option_startswith('TRANS') if not trans: trans = 'TRANS1' comp = compartmental_model(self, advan, trans, des) if comp is not None: cm, link = comp statements += [cm, link] else: statements.append( ODESystem()) # FIXME: Placeholder for ODE-system # FIXME: Dummy link statement statements.append(Assignment('F', S('F'))) statements += error.statements if not hasattr(self, '_parameters'): self._read_parameters() trans_statements, trans_params = self._create_name_trans(statements) for key, value in trans_params.items(): try: self.parameters[key].name = value for theta in self.control_stream.get_records('THETA'): theta.update_name_map(trans_params) for omega in self.control_stream.get_records('OMEGA'): omega.update_name_map(trans_params) for sigma in self.control_stream.get_records('SIGMA'): sigma.update_name_map(trans_params) except KeyError: self.random_variables.subs({S(key): value}) statements.subs(trans_statements) self._statements = statements self._old_statements = statements.copy() return statements
def test_reassign(): s1 = Assignment(S('G'), sympy.Integer(3)) s2 = Assignment(S('M'), sympy.Integer(2)) s3 = Assignment(S('Z'), sympy.Integer(23) + S('M')) s4 = Assignment(S('KA'), S('X') + S('Y')) s = ModelStatements([s1, s2, s3, s4]) s.reassign(S('M'), S('x') + S('y')) assert s == ModelStatements([s1, Assignment('M', S('x') + S('y')), s3, s4]) s5 = Assignment('KA', S('KA') + S('Q') + 1) s = ModelStatements([s1, s2, s3, s4, s5]) s.reassign(S('KA'), S('F')) assert s == ModelStatements([s1, s2, s3, Assignment('KA', S('F'))])
def piecewise_linear(cls): """Piecewise linear ("hockey-stick") template (for continuous covariates).""" symbol = S('symbol') values = [ 1 + S('theta1') * (S('cov') - S('median')), 1 + S('theta2') * (S('cov') - S('median')), ] conditions = [Le(S('cov'), S('median')), Gt(S('cov'), S('median'))] expression = Piecewise((values[0], conditions[0]), (values[1], conditions[1])) template = Assignment(symbol, expression) return cls(template)
def define_parameter(model, name, value, synonyms=None): """Define a parameter in statments if not defined Update if already defined as other value return True if new assignment was added """ if synonyms is None: synonyms = [name] for syn in synonyms: ass = model.statements.find_assignment(syn) if ass: if value != ass.expression and value != symbol(name): ass.expression = value return False new_ass = Assignment(name, value) model.statements.add_before_odes(new_ass) return True
def from_odes(self, ode_system): """Set statements of record given an eplicit ode system""" odes = ode_system.odes[: -1] # Skip last ode as it is for the output compartment functions = [ode.lhs.args[0] for ode in odes] function_map = { f: symbols.symbol(f'A({i + 1})') for i, f in enumerate(functions) } statements = [] for i, ode in enumerate(odes): # For now Piecewise signals zero-order infusions, which are handled with parameters ode = ode.replace(sympy.Piecewise, lambda a1, a2: 0) symbol = symbols.symbol(f'DADT({i + 1})') expression = ode.rhs.subs(function_map) statements.append(Assignment(symbol, expression)) self.statements = statements
def _create_template(effect, model, covariate): """Creates Covariate class objects with effect template.""" if effect == 'lin': return CovariateEffect.linear() elif effect == 'cat': counts = _count_categorical(model, covariate) return CovariateEffect.categorical(counts) elif effect == 'piece_lin': return CovariateEffect.piecewise_linear() elif effect == 'exp': return CovariateEffect.exponential() elif effect == 'pow': return CovariateEffect.power() else: symbol = S('symbol') expression = sympy.sympify(effect) return CovariateEffect(Assignment(symbol, expression))
def tdist(cls, no_of_etas): assignments = ModelStatements() for i in range(1, no_of_etas + 1): symbol = S(f'etat{i}') eta = S(f'eta{i}') theta = S(f'theta{i}') num_1 = eta ** 2 + 1 denom_1 = 4 * theta num_2 = (5 * eta ** 4) + (16 * eta ** 2 + 3) denom_2 = 96 * theta ** 2 num_3 = (3 * eta ** 6) + (19 * eta ** 4) + (17 * eta ** 2) - 15 denom_3 = 384 * theta ** 3 expression = eta * (1 + (num_1 / denom_1) + (num_2 / denom_2) + (num_3 / denom_3)) assignment = Assignment(symbol, expression) assignments.append(assignment) return cls('tdist', assignments, 'df')