def test_pset_names(): p1 = Parameter('Y', 9) p2 = Parameter('X', 3) p3 = Parameter('Z', 1) pset = ParameterSet([p1, p2, p3]) assert pset.names == ['Y', 'X', 'Z'] assert pset.symbols == [symbol('Y'), symbol('X'), symbol('Z')]
def test_minimal(datadir): path = datadir / 'minimal.mod' model = Model(path) assert len(model.statements) == 1 assert model.statements[0].expression == symbol('THETA(1)') + symbol('ETA(1)') + symbol( 'EPS(1)' )
def merge_normal_distributions(self, fill=0, create_cov_params=False): """Merge all normal distributed rvs together into one joint normal Set new covariances (and previous 0 covs) to 'fill' """ cov_to_params = dict() cov_number = 1 means, M, names, others = self._calc_covariance_matrix() if fill != 0: for row, col in itertools.product(range(M.rows), range(M.cols)): if M[row, col] == 0: M[row, col] = fill elif create_cov_params: for row, col in itertools.product(range(M.rows), range(M.cols)): if M[row, col] == 0 and row > col: param_1 = M[row, row] param_2 = M[col, col] cov_name = f'COV{cov_number}' cov_number += 1 cov_to_params[cov_name] = (str(param_1), str(param_2)) M[row, col] = symbol(cov_name) M[col, row] = symbol(cov_name) new_rvs = JointNormalSeparate(names, means, M) self.__init__(new_rvs + others) return cov_to_params
def has_combined_error(model): """Check if a model has a combined additive and proportinal error model Parameters ---------- model : Model The model to check """ y = model.dependent_variable expr = model.statements.full_expression_after_odes(y) rvs = model.random_variables.epsilons rvs_in_y = { symbols.symbol(rv.name) for rv in rvs if symbols.symbol(rv.name) in expr.free_symbols } if len(rvs_in_y) != 2: return False eps1 = rvs_in_y.pop() eps2 = rvs_in_y.pop() canc1 = ((expr - eps1) / (eps2 + 1)).simplify() canc2 = ((expr - eps2) / (eps1 + 1)).simplify() return ( eps1 not in canc1.free_symbols and eps2 not in canc1.free_symbols or eps1 not in canc2.free_symbols and eps2 not in canc2.free_symbols )
def parameter_translation(self, reverse=False, remove_idempotent=False, as_symbols=False): """Get a dict of NONMEM name to Pharmpy parameter name i.e. {'THETA(1)': 'TVCL', 'OMEGA(1,1)': 'IVCL'} """ self.parameters d = dict() for theta_record in self.control_stream.get_records('THETA'): for key, value in theta_record.name_map.items(): nonmem_name = f'THETA({value})' d[nonmem_name] = key for record in self.control_stream.get_records('OMEGA'): for key, value in record.name_map.items(): nonmem_name = f'OMEGA({value[0]},{value[1]})' d[nonmem_name] = key for record in self.control_stream.get_records('SIGMA'): for key, value in record.name_map.items(): nonmem_name = f'SIGMA({value[0]},{value[1]})' d[nonmem_name] = key if remove_idempotent: d = {key: val for key, val in d.items() if key != val} if reverse: d = {val: key for key, val in d.items()} if as_symbols: d = { symbols.symbol(key): symbols.symbol(val) for key, val in d.items() } return d
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 test_copy(datadir): path = datadir / 'minimal.mod' model = Model(path) copy = model.copy() assert id(model) != id(copy) assert model.statements[0].expression == symbol('THETA(1)') + symbol('ETA(1)') + symbol( 'EPS(1)' )
def test_rv(): omega1 = symbol('OMEGA(1,1)') x = stats.Normal('ETA(1)', 0, sympy.sqrt(omega1)) rvs = RandomVariables([x]) assert len(rvs) == 1 retrieved = rvs['ETA(1)'] assert retrieved.name == 'ETA(1)' assert retrieved.pspace.distribution.mean == 0 assert rvs.free_symbols == {symbol('ETA(1)'), omega1}
def test_validate_parameters(): a, b, c, d = (symbol('a'), symbol('b'), symbol('c'), symbol('d')) rvs = JointNormalSeparate(['ETA(1)', 'ETA(2)'], [0, 0], [[a, b], [b, c]]) rvs = RandomVariables(rvs) rvs.add(stats.Normal('ETA(3)', 0.5, d)) params = {'a': 2, 'b': 0.1, 'c': 1, 'd': 23} assert rvs.validate_parameters(params) params2 = {'a': 2, 'b': 2, 'c': 1, 'd': 23} assert not rvs.validate_parameters(params2)
def __init__(self, src, **kwargs): super().__init__() parser = NMTranParser() self.source = src if not self.source.filename_extension: self.source.filename_extension = '.ctl' self._name = self.source.path.stem self.control_stream = parser.parse(src.code) self._initial_individual_estimates_updated = False self._updated_etas_file = None self._dataset_updated = False self._modelfit_results = None self.dependent_variable_symbol = symbols.symbol('Y') self.individual_prediction_symbol = symbols.symbol('CIPREDI')
def _preparations(model): stats = model.statements y = model.dependent_variable f = model.statements.find_assignment(y.name).expression for eps in model.random_variables.epsilons: f = f.subs({symbols.symbol(eps.name): 0}) return stats, y, f
def free_symbols(self): symbs = set() for rv in self: free = {s for s in rv.pspace.free_symbols if s.name != rv.name} symbs |= free symbs.add(symbol(rv.name)) return symbs
def test_pset_getitem(): p = Parameter('Y', 9) pset = Parameters((p, )) assert len(pset) == 1 assert pset['Y'] is p p2 = Parameter('Z', 5) pset.append(p2) assert len(pset) == 2 # Check that the parameter set keeps the insertion order upon iteration for i, param in enumerate(pset): if i == 0: assert param is p else: assert param is p2 assert pset[symbol('Z')] == p2 p3 = Parameter('K', 19) with pytest.raises(KeyError): pset[p3] assert len(pset[[p]]) == 1
def test_all_parameters(): omega1 = symbol('OMEGA(1,1)') eta1 = stats.Normal('ETA(1)', 0, sympy.sqrt(omega1)) omega2 = symbol('OMEGA(2,2)') eta2 = stats.Normal('ETA(2)', 0, sympy.sqrt(omega2)) sigma = symbol('SIGMA(1,1)') eps = stats.Normal('EPS(1)', 0, sympy.sqrt(sigma)) rvs = RandomVariables([eta1, eta2, eps]) assert len(rvs) == 3 params = rvs.all_parameters() assert len(params) == 3 assert params == ['OMEGA(1,1)', 'OMEGA(2,2)', 'SIGMA(1,1)']
def test_has_same_order(): omega1 = symbol('OMEGA(1,1)') eta1 = stats.Normal('ETA(1)', 0, sympy.sqrt(omega1)) omega2 = symbol('OMEGA(2,2)') eta2 = stats.Normal('ETA(2)', 0, sympy.sqrt(omega2)) omega3 = symbol('OMEGA(1,1)') eta3 = stats.Normal('ETA(3)', 0, sympy.sqrt(omega3)) rvs_full = RandomVariables([eta1, eta2, eta3]) assert rvs_full.are_consecutive(rvs_full) rvs_sub = RandomVariables([eta1, eta2]) assert rvs_full.are_consecutive(rvs_sub) rvs_rev = RandomVariables([eta3, eta2, eta1]) assert not rvs_full.are_consecutive(rvs_rev)
def _calculate_covariate_baselines(model, covariates): exprs = [ ass.expression.args[0][0] for ass in model.statements if symbols.symbol('FREMTYPE') in ass.free_symbols and ass.symbol.name == 'IPRED' ] exprs = [ expr.subs(dict(model.modelfit_results.parameter_estimates)).subs( model.parameters.inits) for expr in exprs ] new = [] for expr in exprs: for symb in expr.free_symbols: stat = model.statements.find_assignment(symb.name) if stat is not None: expr = expr.subs(symb, stat.expression) new.append(expr) exprs = new def fn(row): return [np.float64(expr.subs(dict(row))) for expr in exprs] df = model.modelfit_results.individual_estimates.apply( fn, axis=1, result_type='expand') df.columns = covariates return df
def free_symbols(self): free = {symbols.symbol('t')} for (_, _, rate) in self._g.edges.data('rate'): free |= rate.free_symbols for node in self._g.nodes: free |= node.free_symbols return free
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 add_rate_assignment_if_missing(model, name, value, source, dest, synonyms=None): added = define_parameter(model, name, value, synonyms=synonyms) if added: model.statements.ode_system.add_flow(source, dest, symbol(name))
def has_proportional_error(model): """Check if a model has a proportional error model Parameters ---------- model : Model The model to check """ y = model.dependent_variable expr = model.statements.full_expression_after_odes(y) rvs = model.random_variables.epsilons rvs_in_y = { symbols.symbol(rv.name) for rv in rvs if symbols.symbol(rv.name) in expr.free_symbols } if len(rvs_in_y) != 1: return False eps = rvs_in_y.pop() return eps not in (expr / (1 + eps)).simplify().free_symbols
def update_abbr_record(model, rv_trans): trans = dict() if not rv_trans: return trans for rv in model.random_variables: rv_symb = symbol(rv.name) abbr_pattern = re.match(r'ETA_(\w+)', rv.name) if abbr_pattern and '_' not in abbr_pattern.group(1): parameter = abbr_pattern.group(1) nonmem_name = rv_trans[rv_symb] abbr_name = f'ETA({parameter})' trans[rv_symb] = symbol(abbr_name) abbr_record = f'$ABBR REPLACE {abbr_name}={nonmem_name}\n' model.control_stream.insert_record(abbr_record) elif not re.match(r'(ETA|EPS)\([0-9]\)', rv.name): warnings.warn( f'Not valid format of name {rv.name}, falling back to NONMEM name. If custom name, ' f'follow the format "ETA_X" to get "ETA(X)" in $ABBR.') return trans
def force_des(model, odes): """Switch to $DES if necessary""" if isinstance(odes, ExplicitODESystem): return amounts = {sympy.Function(amt.name)(symbol('t')) for amt in odes.amounts} if odes.atoms(sympy.Function) & amounts: modeling.explicit_odes(model) new = model.statements.ode_system to_des(model, new)
def expression(self, expr, parameters): """Replace all symbols with same names as rvs with the corresponding rvs or indexed variables for joint distributions and replace parameter values """ d = dict() i = 1 for rvs, dist in self.distributions(): if len(rvs) > 1: joint_name = f'__J{i}' mu = dist.mu.subs(parameters) sigma = dist.sigma.subs(parameters) x = stats.Normal(joint_name, mu, sigma) d.update({symbol(rv.name): x[n] for n, rv in enumerate(rvs)}) i += 1 else: mean = dist.mean.subs(parameters) std = dist.std.subs(parameters) d[symbol(rvs[0].name)] = stats.Normal(rvs[0].name, mean, std) return expr.subs(d)
def __init__(self, symbol, expression): """symbol can be either string or sympy symbol symbol can be a string and a real symbol will be created """ try: symbol.is_Symbol self.symbol = symbol except AttributeError: self.symbol = symbols.symbol(symbol) self.expression = sympy.sympify(expression)
def subs(self, substitutions): d = { sympy.Function(str(key))(symbols.symbol('t')): value for key, value in substitutions.items() } self.odes = [ode.subs(d) for ode in self.odes] self.ics = { key.subs(d): value.subs(d) for key, value in self.ics.items() }
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 force_des(model, odes): """Switch to $DES if necessary""" if isinstance(odes, ExplicitODESystem): return # Import put here to avoid circular import in Python 3.6 import pharmpy.modeling as modeling amounts = {sympy.Function(amt.name)(symbol('t')) for amt in odes.amounts} if odes.atoms(sympy.Function) & amounts: modeling.explicit_odes(model) new = model.statements.ode_system to_des(model, new)
def calculate_results_using_cov_sampling( frem_model, continuous, categorical, cov_model=None, force_posdef_samples=500, force_posdef_covmatrix=False, samples=1000, rescale=True, seed=None, ): """Calculate the FREM results using covariance matrix for uncertainty :param cov_model: Take the parameter uncertainty covariance matrix from this model instead of the frem model. :param force_posdef_samples: The number of sampling tries before stopping to use rejection sampling and instead starting to shift values so that the frem matrix becomes positive definite. Set to 0 to always force positive definiteness. :param force_posdef_covmatrix: Set to force the covariance matrix of the frem movdel or the cov model to be positive definite. Default is to raise in this case. :param samples: The number of parameter vector samples to use. """ if cov_model is not None: uncertainty_results = cov_model.modelfit_results else: uncertainty_results = frem_model.modelfit_results _, dist = frem_model.random_variables.iiv.distributions()[-1] sigma_symb = dist.sigma parameters = [ s for s in frem_model.modelfit_results.parameter_estimates.index if symbols.symbol(s) in sigma_symb.free_symbols ] parvecs = sample_from_covariance_matrix( frem_model, modelfit_results=uncertainty_results, force_posdef_samples=force_posdef_samples, force_posdef_covmatrix=force_posdef_covmatrix, parameters=parameters, n=samples, seed=seed, ) res = calculate_results_from_samples(frem_model, continuous, categorical, parvecs, rescale=rescale) return res
def test_initialization(name, init, lower, upper, fix): param = Parameter(name, init, lower, upper, fix) assert param.name == name assert param.symbol == symbol(name) assert param.init == init if lower is not None: assert param.lower == lower else: assert param.lower == -sympy.oo if upper is not None: assert param.upper == upper else: assert param.upper == sympy.oo assert param.fix == bool(fix)
def rv_translation(self, reverse=False, remove_idempotent=False, as_symbols=False): self.random_variables d = dict() for record in self.control_stream.get_records('OMEGA'): for key, value in record.eta_map.items(): nonmem_name = f'ETA({value})' d[nonmem_name] = key for record in self.control_stream.get_records('SIGMA'): for key, value in record.eta_map.items(): nonmem_name = f'EPS({value})' d[nonmem_name] = key if remove_idempotent: d = {key: val for key, val in d.items() if key != val} if reverse: d = {val: key for key, val in d.items()} if as_symbols: d = { symbols.symbol(key): symbols.symbol(val) for key, val in d.items() } return d