def test_simres_dataframe(): """ Test SimulationResult.dataframe() """ tspan1 = np.linspace(0, 100, 100) tspan2 = np.linspace(50, 100, 50) tspan3 = np.linspace(100, 150, 100) model = tyson_oscillator.model sim = ScipyOdeSimulator(model, integrator='lsoda') simres1 = sim.run(tspan=tspan1) # Check retrieving a single simulation dataframe df_single = simres1.dataframe # Generate multiple trajectories trajectories1 = simres1.species trajectories2 = sim.run(tspan=tspan2).species trajectories3 = sim.run(tspan=tspan3).species # Try a simulation result with two different tspan lengths sim = ScipyOdeSimulator(model, param_values={'k6' : [1.,1.]}, integrator='lsoda') simres = SimulationResult(sim, [tspan1, tspan2], [trajectories1, trajectories2]) df = simres.dataframe assert df.shape == (len(tspan1) + len(tspan2), len(model.species) + len(model.observables)) # Next try a simulation result with two identical tspan lengths, stacked # into a single 3D array of trajectories simres2 = SimulationResult(sim, [tspan1, tspan3], np.stack([trajectories1, trajectories3])) df2 = simres2.dataframe assert df2.shape == (len(tspan1) + len(tspan3), len(model.species) + len(model.observables))
def run(self, tspan=None, initials=None, param_values=None): """ Run a simulation and returns the result (trajectories) .. note:: In early versions of the Simulator class, ``tspan``, ``initials`` and ``param_values`` supplied to this method persisted to future :func:`run` calls. This is no longer the case. Parameters ---------- tspan initials param_values See parameter definitions in :class:`ScipyOdeSimulator`. Returns ------- A :class:`SimulationResult` object """ super(ScipyOdeSimulator, self).run(tspan=tspan, initials=initials, param_values=param_values, _run_kwargs=[]) n_sims = len(self.param_values) trajectories = np.ndarray( (n_sims, len(self.tspan), len(self._model.species))) for n in range(n_sims): self._logger.info('Running simulation %d of %d', n + 1, n_sims) if self.integrator == 'lsoda': trajectories[n] = scipy.integrate.odeint( self.func, self.initials[n], self.tspan, Dfun=self.jac_fn, args=(self.param_values[n], ), **self.opts) else: self.integrator.set_initial_value(self.initials[n], self.tspan[0]) # Set parameter vectors for RHS func and Jacobian self.integrator.set_f_params(self.param_values[n]) if self._use_analytic_jacobian: self.integrator.set_jac_params(self.param_values[n]) trajectories[n][0] = self.initials[n] i = 1 while self.integrator.successful() and self.integrator.t < \ self.tspan[-1]: self._logger.log(EXTENDED_DEBUG, 'Simulation %d/%d Integrating t=%g', n + 1, n_sims, self.integrator.t) trajectories[n][i] = self.integrator.integrate( self.tspan[i]) i += 1 if self.integrator.t < self.tspan[-1]: trajectories[n, i:, :] = 'nan' tout = np.array([self.tspan] * n_sims) self._logger.info('All simulation(s) complete') return SimulationResult(self, tout, trajectories)
def test_save_load_observables_expressions(): buff = io.BytesIO() tspan = np.linspace(0, 100, 100) sim = ScipyOdeSimulator(tyson_oscillator.model, tspan).run() sim.save(buff, include_obs_exprs=True) sim2 = SimulationResult.load(buff) assert len(sim2.observables) == len(tspan) # Tyson oscillator doesn't have expressions assert_raises(ValueError, lambda: sim2.expressions)
def run(self, tspan=None, initials=None, param_values=None, n_runs=1, method='ssa', output_dir=None, output_file_basename=None, cleanup=None, population_maps=None, **additional_args): """ Simulate a model using BioNetGen Parameters ---------- tspan: vector-like time span of simulation initials: vector-like, optional initial conditions of model param_values : vector-like or dictionary, optional Values to use for every parameter in the model. Ordering is determined by the order of model.parameters. If not specified, parameter values will be taken directly from model.parameters. n_runs: int number of simulations to run method : str Type of simulation to run. Must be one of: * 'ssa' - Stochastic Simulation Algorithm (direct method with propensity sorting) * 'nf' - Stochastic network free simulation with NFsim. Performs Hybrid Particle/Population simulation if population_maps argument is supplied * 'pla' - Partioned-leaping algorithm (variant of tau-leaping algorithm) * 'ode' - ODE simulation (Sundials CVODE algorithm) output_dir : string, optional Location for temporary files generated by BNG. If None (the default), uses a temporary directory provided by the system. A temporary directory with a random name is created within the supplied location. output_file_basename : string, optional This argument is used as a prefix for the temporary BNG output directory, rather than the individual files. cleanup : bool, optional If True (default), delete the temporary files after the simulation is finished. If False, leave them in place (Useful for debugging). The default value, None, means to use the value specified in :py:func:`__init__`. population_maps: list of PopulationMap List of :py:class:`PopulationMap` objects for hybrid particle/population modeling. Only used when method='nf'. additional_args: kwargs, optional Additional arguments to pass to BioNetGen Examples -------- Simulate a model using network free simulation (NFsim): >>> from pysb.examples import robertson >>> from pysb.simulator.bng import BngSimulator >>> model = robertson.model >>> sim = BngSimulator(model, tspan=np.linspace(0, 1)) >>> x = sim.run(n_runs=1, method='nf') """ super(BngSimulator, self).run(tspan=tspan, initials=initials, param_values=param_values, _run_kwargs=locals()) if cleanup is None: cleanup = self.cleanup if method not in self._SIMULATOR_TYPES: raise ValueError("Method must be one of " + str(self._SIMULATOR_TYPES)) if method != 'nf' and population_maps: raise ValueError('population_maps argument is only used when ' 'method is "nf"') if method == 'nf': if population_maps is not None and ( not isinstance(population_maps, collections.Iterable) or any(not isinstance(pm, PopulationMap) for pm in population_maps)): raise ValueError('population_maps should be a list of ' 'PopulationMap objects') model_additional_species = self.initials_dict.keys() else: model_additional_species = None tspan_lin_spaced = np.allclose( self.tspan, np.linspace(self.tspan[0], self.tspan[-1], len(self.tspan))) if method == 'nf' and (not tspan_lin_spaced or self.tspan[0] != 0.0): raise SimulatorException('NFsim requires tspan to be linearly ' 'spaced starting at t=0') # BNG requires t_start even when supplying sample_times additional_args['t_start'] = self.tspan[0] if tspan_lin_spaced: # Just supply t_end and n_steps additional_args['n_steps'] = len(self.tspan) - 1 additional_args['t_end'] = self.tspan[-1] else: additional_args['sample_times'] = self.tspan additional_args['method'] = method additional_args['print_functions'] = True verbose_bool = self._logger.logger.getEffectiveLevel() <= logging.DEBUG extended_debug = self._logger.logger.getEffectiveLevel() <= \ EXTENDED_DEBUG additional_args['verbose'] = extended_debug params_names = [g.name for g in self._model.parameters] n_param_sets = self.initials_length total_sims = n_runs * n_param_sets self._logger.info('Running %d BNG %s simulations' % (total_sims, method)) model_to_load = None hpp_bngl = None if population_maps: self._logger.debug('Generating hybrid particle-population model') hpp_bngl = generate_hybrid_model(self._model, population_maps, model_additional_species, verbose=extended_debug) else: model_to_load = self._model with BngFileInterface( model_to_load, verbose=verbose_bool, output_dir=output_dir, output_prefix=output_file_basename, cleanup=cleanup, model_additional_species=model_additional_species) as bngfile: if hpp_bngl: hpp_bngl_filename = os.path.join(bngfile.base_directory, 'hpp_model.bngl') self._logger.debug('HPP BNGL:\n\n' + hpp_bngl) with open(hpp_bngl_filename, 'w') as f: f.write(hpp_bngl) if method != 'nf': # TODO: Write existing netfile if already generated bngfile.action('generate_network', overwrite=True, verbose=extended_debug) if output_file_basename is None: prefix = 'pysb' else: prefix = output_file_basename sim_prefix = 0 for pset_idx in range(n_param_sets): for n in range(len(self.param_values[pset_idx])): bngfile.set_parameter(params_names[n], self.param_values[pset_idx][n]) for cp, values in self.initials_dict.items(): if population_maps: for pm in population_maps: if pm.complex_pattern.is_equivalent_to(cp): cp = pm.counter_species break bngfile.set_concentration(cp, values[pset_idx]) for sim_rpt in range(n_runs): tmp = additional_args.copy() tmp['prefix'] = '{}{}'.format(prefix, sim_prefix) bngfile.action('simulate', **tmp) bngfile.action('resetConcentrations') sim_prefix += 1 if hpp_bngl: bngfile.execute(reload_netfile=hpp_bngl_filename, skip_file_actions=True) else: bngfile.execute() if method != 'nf': load_equations(self.model, bngfile.net_filename) list_of_yfull = \ BngFileInterface.read_simulation_results_multi( [bngfile.base_filename + str(n) for n in range(total_sims)]) tout = [] species_out = [] obs_exp_out = [] for i in range(total_sims): yfull = list_of_yfull[i] yfull_view = yfull.view(float).reshape(len(yfull), -1) tout.append(yfull_view[:, 0]) if method == 'nf': obs_exp_out.append(yfull_view[:, 1:]) else: species_out.append(yfull_view[:, 1:(len(self.model.species) + 1)]) if len(self.model.observables) or len(self.model.expressions): obs_exp_out.append( yfull_view[:, (len(self.model.species) + 1):(len(self.model.species) + 1) + len(self.model.observables) + len(self.model.expressions_dynamic())]) return SimulationResult(self, tout=tout, trajectories=species_out, observables_and_expressions=obs_exp_out, simulations_per_param_set=n_runs)
def test_save_load(): tspan = np.linspace(0, 100, 101) model = tyson_oscillator.model test_unicode_name = u'Hello \u2603 and \U0001f4a9!' model.name = test_unicode_name sim = ScipyOdeSimulator(model, integrator='lsoda') simres = sim.run(tspan=tspan, param_values={'k6': 1.0}) sim_rob = ScipyOdeSimulator(robertson.model, integrator='lsoda') simres_rob = sim_rob.run(tspan=tspan) # Reset equations from any previous network generation robertson.model.reset_equations() A = robertson.model.monomers['A'] # NFsim without expressions nfsim1 = BngSimulator(robertson.model) nfres1 = nfsim1.run(n_runs=2, method='nf', tspan=np.linspace(0, 1)) # Test attribute saving (text, float, list) nfres1.custom_attrs['note'] = 'NFsim without expressions' nfres1.custom_attrs['pi'] = 3.14 nfres1.custom_attrs['some_list'] = [1, 2, 3] # NFsim with expressions nfsim2 = BngSimulator(expression_observables.model) nfres2 = nfsim2.run(n_runs=1, method='nf', tspan=np.linspace(0, 100, 11)) with tempfile.NamedTemporaryFile() as tf: # Cannot have two file handles on Windows tf.close() simres.save(tf.name, dataset_name='test', append=True) # Try to reload when file contains only one dataset and group SimulationResult.load(tf.name) simres.save(tf.name, append=True) # Trying to overwrite an existing dataset gives a ValueError assert_raises(ValueError, simres.save, tf.name, append=True) # Trying to write to an existing file without append gives an IOError assert_raises(IOError, simres.save, tf.name) # Trying to write a SimulationResult to the same group with a # different model name results in a ValueError assert_raises(ValueError, simres_rob.save, tf.name, dataset_name='robertson', group_name=model.name, append=True) simres_rob.save(tf.name, append=True) # Trying to load from a file with more than one group without # specifying group_name should raise a ValueError assert_raises(ValueError, SimulationResult.load, tf.name) # Trying to load from a group with more than one dataset without # specifying a dataset_name should raise a ValueError assert_raises(ValueError, SimulationResult.load, tf.name, group_name=model.name) # Load should succeed when specifying group_name and dataset_name simres_load = SimulationResult.load(tf.name, group_name=model.name, dataset_name='test') assert simres_load._model.name == test_unicode_name # Saving network free results requires include_obs_exprs=True, # otherwise a warning should be raised with warnings.catch_warnings(record=True) as w: warnings.simplefilter("always") nfres1.save(tf.name, dataset_name='nfsim_no_obs', append=True) assert len(w) == 1 assert issubclass(w[-1].category, UserWarning) nfres1.save(tf.name, include_obs_exprs=True, dataset_name='nfsim test', append=True) # NFsim load nfres1_load = SimulationResult.load(tf.name, group_name=nfres1._model.name, dataset_name='nfsim test') # NFsim with expression nfres2.save(tf.name, include_obs_exprs=True, append=True) nfres2_load = SimulationResult.load(tf.name, group_name=nfres2._model.name) _check_resultsets_equal(simres, simres_load) _check_resultsets_equal(nfres1, nfres1_load) _check_resultsets_equal(nfres2, nfres2_load)
def run(self, tspan=None, initials=None, param_values=None): """Perform a set of integrations. Returns a :class:`.SimulationResult` object. Parameters ---------- tspan : list-like, optional Time values at which the integrations are sampled. The first and last values define the time range. initials : list-like, optional Initial species concentrations for all simulations. Dimensions are number of simulation x number of species. param_values : list-like, optional Parameters for all simulations. Dimensions are number of simulations x number of parameters. Returns ------- A :class:`SimulationResult` object Notes ----- 1. An exception is thrown if `tspan` is not defined in either `__init__`or `run`. 2. If neither `initials` nor `param_values` are defined in either `__init__` or `run` a single simulation is run with the initial concentrations and parameter values defined in the model. """ super(CupSodaSimulator, self).run(tspan=tspan, initials=initials, param_values=param_values, _run_kwargs=[]) # Create directories for cupSODA input and output files _outdirs = {} _indirs = {} start_time = time.time() cmtx = self._get_cmatrix() outdir = tempfile.mkdtemp(prefix=self._prefix + '_', dir=self._base_dir) self._logger.debug("Output directory is %s" % outdir) # Set up chunking (enforce max # sims per GPU per run) n_sims = len(self.param_values) chunksize_gpu = self.opts.get('chunksize', None) if chunksize_gpu is None: chunksize_gpu = n_sims chunksize_total = chunksize_gpu * len(self.gpu) tout = None trajectories = None chunks = np.array_split(range(n_sims), np.ceil(n_sims / chunksize_total)) try: for chunk_idx, chunk in enumerate(chunks): self._logger.debug('cupSODA chunk {} of {}'.format( (chunk_idx + 1), len(chunks))) # Split chunk equally between GPUs sims = dict(zip(self.gpu, np.array_split(chunk, len(self.gpu)))) tout, trajectories = self._run_chunk(self.gpu, outdir, chunk_idx, cmtx, sims, trajectories, tout) finally: if self._cleanup: shutil.rmtree(outdir) end_time = time.time() self._logger.info("cupSODA + I/O time: {} seconds".format(end_time - start_time)) return SimulationResult(self, tout, trajectories)
def run(self, tspan=None, initials=None, param_values=None, n_runs=1, algorithm='ssa', output_dir=None, num_processors=1, seed=None, method=None, stats=False, epsilon=None, threshold=None): """ Run a simulation and returns the result (trajectories) .. note:: ``tspan``, ``initials`` and ``param_values`` values supplied to this method will persist to future :func:`run` calls. Parameters ---------- tspan initials param_values See parameter definitions in :class:`StochKitSimulator`. n_runs : int The number of simulation runs per parameter set. The total number of simulations is therefore n_runs * max(len(initials), len(param_values)) algorithm : str Choice of 'ssa' (Gillespie's stochastic simulation algorithm) or 'tau_leaping' (Tau leaping algorithm) output_dir : str or None Directory for StochKit output, or None for a system-specific temporary directory num_processors : int Number of CPU cores for StochKit to use (default: 1) seed : int or None A random number seed for StochKit. Set to any integer value for deterministic behavior. method : str or None StochKit "method" argument, default None. Only used by StochKit 2.1 (not yet released at time of writing). stats : bool Ask StochKit to generate simulation summary statistics if True epsilon : float or None Tolerance parameter for tau-leaping algorithm threshold : int or None Threshold parameter for tau-leaping algorithm Returns ------- A :class:`SimulationResult` object """ super(StochKitSimulator, self).run(tspan=tspan, initials=initials, param_values=param_values) self._logger.info('Running StochKit with {:d} parameter sets, ' '{:d} repeats ({:d} simulations total)'.format( len(self.initials), n_runs, len(self.initials) * n_runs)) if output_dir is None: self._outdir = tempfile.mkdtemp() else: self._outdir = output_dir # Calculate time intervals and validate t_range = self.tspan[-1] - self.tspan[0] t_length = len(self.tspan) if not np.allclose(self.tspan, np.linspace(0, self.tspan[-1], t_length)): raise SimulatorException('StochKit requires tspan to be linearly ' 'spaced starting at t=0') try: trajectories = self._run_stochkit(t=t_range, number_of_trajectories=n_runs, t_length=t_length, seed=seed, algorithm=algorithm, method=method, num_processors=num_processors, stats=stats, epsilon=epsilon, threshold=threshold) finally: if self.cleanup: try: shutil.rmtree(self._outdir) except OSError: pass # set output time points trajectories_array = np.array(trajectories) self.tout = trajectories_array[:, :, 0] + self.tspan[0] # species species = trajectories_array[:, :, 1:] return SimulationResult(self, self.tout, species, simulations_per_param_set=n_runs)
def test_save_load(): tspan = np.linspace(0, 100, 101) # Make a copy of model so other tests etc. don't see the changed name. model = copy.deepcopy(tyson_oscillator.model) test_unicode_name = u'Hello \u2603 and \U0001f4a9!' model.name = test_unicode_name sim = ScipyOdeSimulator(model, integrator='lsoda') simres = sim.run(tspan=tspan, param_values={'k6': 1.0}) sim_rob = ScipyOdeSimulator(robertson.model, integrator='lsoda') simres_rob = sim_rob.run(tspan=tspan) # Reset equations from any previous network generation robertson.model.reset_equations() A = robertson.model.monomers['A'] # NFsim without expressions nfsim1 = BngSimulator(robertson.model) nfres1 = nfsim1.run(n_runs=2, method='nf', tspan=np.linspace(0, 1)) # Test attribute saving (text, float, list) nfres1.custom_attrs['note'] = 'NFsim without expressions' nfres1.custom_attrs['pi'] = 3.14 nfres1.custom_attrs['some_list'] = [1, 2, 3] # NFsim with expressions nfsim2 = BngSimulator(expression_observables.model) nfres2 = nfsim2.run(n_runs=1, method='nf', tspan=np.linspace(0, 100, 11)) with tempfile.NamedTemporaryFile() as tf: # Cannot have two file handles on Windows tf.close() simres.save(tf.name, dataset_name='test', append=True) # Try to reload when file contains only one dataset and group SimulationResult.load(tf.name) simres.save(tf.name, append=True) # Trying to overwrite an existing dataset gives a ValueError assert_raises(ValueError, simres.save, tf.name, append=True) # Trying to write to an existing file without append gives an IOError assert_raises(IOError, simres.save, tf.name) # Trying to write a SimulationResult to the same group with a # different model name results in a ValueError assert_raises(ValueError, simres_rob.save, tf.name, dataset_name='robertson', group_name=model.name, append=True) simres_rob.save(tf.name, append=True) # Trying to load from a file with more than one group without # specifying group_name should raise a ValueError assert_raises(ValueError, SimulationResult.load, tf.name) # Trying to load from a group with more than one dataset without # specifying a dataset_name should raise a ValueError assert_raises(ValueError, SimulationResult.load, tf.name, group_name=model.name) # Load should succeed when specifying group_name and dataset_name simres_load = SimulationResult.load(tf.name, group_name=model.name, dataset_name='test') assert simres_load._model.name == test_unicode_name # Saving network free results requires include_obs_exprs=True, # otherwise a warning should be raised with warnings.catch_warnings(record=True) as w: warnings.simplefilter("always") nfres1.save(tf.name, dataset_name='nfsim_no_obs', append=True) assert len(w) == 1 assert issubclass(w[-1].category, UserWarning) nfres1.save(tf.name, include_obs_exprs=True, dataset_name='nfsim test', append=True) # NFsim load nfres1_load = SimulationResult.load(tf.name, group_name=nfres1._model.name, dataset_name='nfsim test') # NFsim with expression nfres2.save(tf.name, include_obs_exprs=True, append=True) nfres2_load = SimulationResult.load(tf.name, group_name=nfres2._model.name) _check_resultsets_equal(simres, simres_load) _check_resultsets_equal(nfres1, nfres1_load) _check_resultsets_equal(nfres2, nfres2_load)
def run(self, tspan=None, initials=None, param_values=None): """Perform a set of integrations. Returns a :class:`.SimulationResult` object. Parameters ---------- tspan : list-like, optional Time values at which the integrations are sampled. The first and last values define the time range. initials : list-like, optional Initial species concentrations for all simulations. Dimensions are number of simulation x number of species. param_values : list-like, optional Parameters for all simulations. Dimensions are number of simulations x number of parameters. Returns ------- A :class:`SimulationResult` object Notes ----- 1. An exception is thrown if `tspan` is not defined in either `__init__`or `run`. 2. If neither `initials` nor `param_values` are defined in either `__init__` or `run` a single simulation is run with the initial concentrations and parameter values defined in the model. """ super(CupSodaSimulator, self).run(tspan=tspan, initials=initials, param_values=param_values, _run_kwargs=[]) # Create directories for cupSODA input and output files self.outdir = tempfile.mkdtemp(prefix=self._prefix + '_', dir=self._base_dir) self._logger.debug("Output directory is %s" % self.outdir) _cupsoda_infiles_dir = os.path.join(self.outdir, "INPUT") os.mkdir(_cupsoda_infiles_dir) self._cupsoda_outfiles_dir = os.path.join(self.outdir, "OUTPUT") # Path to cupSODA executable bin_path = get_path('cupsoda') # Create cupSODA input files self._create_input_files(_cupsoda_infiles_dir) # Build command # ./cupSODA input_model_folder blocks output_folder simulation_ # file_prefix gpu_number fitness_calculation memory_use dump command = [ bin_path, _cupsoda_infiles_dir, str(self.n_blocks), self._cupsoda_outfiles_dir, self._prefix, str(self.gpu), '0', self._memory_usage, str(self._cupsoda_verbose) ] self._logger.info("Running cupSODA: " + ' '.join(command)) start_time = time.time() # Run simulation and return trajectories p = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE) # for line in iter(p.stdout.readline, b''): # if 'Running time' in line: # self._logger.info(line[:-1]) (p_out, p_err) = p.communicate() p_out = p_out.decode('utf-8') p_err = p_err.decode('utf-8') logger_level = self._logger.logger.getEffectiveLevel() if logger_level <= logging.INFO: run_time_match = self._running_time_regex.search(p_out) if run_time_match: self._logger.info('cupSODA reported time: {} ' 'seconds'.format(run_time_match.group(1))) self._logger.debug('cupSODA stdout:\n' + p_out) if p_err: self._logger.error('cupsoda strerr:\n' + p_err) if p.returncode: raise SimulatorException( p_out.rstrip("at line") + "\n" + p_err.rstrip()) tout, trajectories = self._load_trajectories( self._cupsoda_outfiles_dir) if self._cleanup: shutil.rmtree(self.outdir) end_time = time.time() self._logger.info("cupSODA + I/O time: {} seconds".format(end_time - start_time)) return SimulationResult(self, tout, trajectories)
def run(self, tspan=None, initials=None, param_values=None, num_processors=1): """ Run a simulation and returns the result (trajectories) .. note:: In early versions of the Simulator class, ``tspan``, ``initials`` and ``param_values`` supplied to this method persisted to future :func:`run` calls. This is no longer the case. Parameters ---------- tspan initials param_values See parameter definitions in :class:`ScipyOdeSimulator`. num_processors : int Number of processes to use (default: 1). Set to a larger number (e.g. the number of CPU cores available) for parallel execution of simulations. This is only useful when simulating with more than one set of initial conditions and/or parameters. Returns ------- A :class:`SimulationResult` object """ super(ScipyOdeSimulator, self).run(tspan=tspan, initials=initials, param_values=param_values, _run_kwargs=[]) n_sims = len(self.param_values) num_species = len(self._model.species) num_odes = len(self._model.odes) results = [] if num_processors == 1: self._logger.debug('Single processor (serial) mode') else: self._logger.debug('Multi-processor (parallel) mode using {} ' 'processes'.format(num_processors)) with SerialExecutor() if num_processors == 1 else \ ProcessPoolExecutor(max_workers=num_processors) as executor: for n in range(n_sims): results.append( executor.submit( _integrator_process, self._code_eqs, self._jac_eqs, num_species, num_odes, self.initials[n], self.tspan, self.param_values[n], self._init_kwargs.get('integrator', 'vode'), compiler=self._compiler, integrator_opts=self.opts, compiler_directives=self._compiler_directives)) trajectories = [r.result() for r in results] tout = np.array([self.tspan] * n_sims) self._logger.info('All simulation(s) complete') return SimulationResult(self, tout, trajectories)
def run(self, tspan=None, initials=None, param_values=None, num_processors=1): """ Run a simulation and returns the result (trajectories) .. note:: In early versions of the Simulator class, ``tspan``, ``initials`` and ``param_values`` supplied to this method persisted to future :func:`run` calls. This is no longer the case. Parameters ---------- tspan initials param_values See parameter definitions in :class:`ScipyOdeSimulator`. num_processors : int Number of processes to use (default: 1). Set to a larger number (e.g. the number of CPU cores available) for parallel execution of simulations. This is only useful when simulating with more than one set of initial conditions and/or parameters. Returns ------- A :class:`SimulationResult` object """ super(AmiciSimulator, self).run(tspan=tspan, initials=initials, param_values=param_values, _run_kwargs=[]) n_sims = len(self.param_values) num_processors = min(n_sims, num_processors) if num_processors == 1: self._logger.debug('Single processor (serial) mode') else: if not amici.compiledWithOpenMP(): raise EnvironmentError( 'AMICI/model was not compiled with openMP support, which ' 'is required for parallel simulation. Please see ' 'https://github.com/ICB-DCM/AMICI/blob/master' '/documentation/PYTHON.md for details on how to compile ' 'AMICI with openMP support.') self._logger.debug('Multi-processor (parallel) mode using {} ' 'processes'.format(num_processors)) edatas = self._simulationspecs_to_edatas() rdatas = amici.runAmiciSimulations(model=self.amici_model, solver=self.amici_solver, edata_list=edatas, failfast=False, num_threads=num_processors) self._logger.info('All simulation(s) complete') return SimulationResult(self, np.array([self.tspan] * n_sims), self._rdatas_to_trajectories(rdatas))
def run(self, tspan=None, initials=None, param_values=None, n_runs=1, output_dir=None, output_file_basename=None, cleanup=None, **additional_args): """ Simulate a model using Kappa Parameters ---------- tspan: vector-like time span of simulation initials: vector-like, optional initial conditions of model param_values : vector-like or dictionary, optional Values to use for every parameter in the model. Ordering is determined by the order of model.parameters. If not specified, parameter values will be taken directly from model.parameters. n_runs: int number of simulations to run output_dir : string, optional Location for temporary files generated by Kappa. If None (the default), uses a temporary directory provided by the system. A temporary directory with a random name is created within the supplied location. output_file_basename : string, optional This argument is used as a prefix for the temporary Kappa output directory, rather than the individual files. cleanup : bool, optional If True (default), delete the temporary files after the simulation is finished. If False, leave them in place (Useful for debugging). The default value, None, means to use the value specified in :py:func:`__init__`. additional_args: kwargs, optional Additional arguments to pass to Kappa * seed : int, optional Random number seed for Kappa simulation * perturbation : string, optional Optional perturbation language syntax to be appended to the Kappa file. See KaSim manual for more details. Examples -------- >>> import numpy as np >>> from pysb.examples import michment >>> from pysb.simulator import KappaSimulator >>> sim = KappaSimulator(michment.model, tspan=np.linspace(0, 1)) >>> x = sim.run(n_runs=1) """ super(KappaSimulator, self).run(tspan=tspan, initials=initials, param_values=param_values, _run_kwargs=locals()) if cleanup is None: cleanup = self.cleanup tspan_lin_spaced = np.allclose( self.tspan, np.linspace(self.tspan[0], self.tspan[-1], len(self.tspan))) if not tspan_lin_spaced or self.tspan[0] != 0.0: raise SimulatorException('Kappa requires tspan to be linearly ' 'spaced starting at t=0') points = len(self.tspan) time = self.tspan[-1] plot_period = time / (len(self.tspan) - 1) if output_file_basename is None: output_file_basename = 'tmpKappa_%s_' % self.model.name base_directory = tempfile.mkdtemp(prefix=output_file_basename, dir=output_dir) base_filename = os.path.join(base_directory, self.model.name) kappa_filename_pattern = base_filename + '_{}.ka' out_filename_pattern = base_filename + '_{}_run{}.out' base_args = ['-u', 'time', '-l', str(time), '-p', '%.5f' % plot_period] if 'seed' in additional_args: seed = additional_args.pop('seed') base_args.extend(['-seed', str(seed)]) kasim_path = pf.get_path('kasim') n_param_sets = self.initials_length gen = KappaGenerator(self.model, _exclude_ic_param=True) file_data_base = gen.get_content() # Check if a perturbation has been set try: perturbation = additional_args.pop('perturbation') except KeyError: perturbation = None # Check no unknown arguments have been set if additional_args: raise ValueError('Unknown argument(s): {}'.format(', '.join( additional_args.keys()))) # Kappa column names, for sanity check kappa_col_names = tuple(['time'] + [o.name for o in self.model.observables]) tout = [] observable_traj = [] try: for pset_idx in range(n_param_sets): file_data = file_data_base + '' for param, param_value in zip(self.model.parameters, self.param_values[pset_idx]): file_data += "%var: '{}' {:e}\n".format( param.name, param_value) file_data += '\n' for cp, values in self.initials_dict.items(): file_data += "%init: {} {}\n".format( values[pset_idx], gen.format_complexpattern(cp)) # If any perturbation language code has been passed in, add it # to the Kappa file: if perturbation: file_data += '%s\n' % perturbation # Generate the Kappa model code from the PySB model and write # it to the Kappa file: kappa_filename = kappa_filename_pattern.format(pset_idx) with open(kappa_filename, 'w') as kappa_file: self._logger.debug('Kappa file contents:\n\n' + file_data) kappa_file.write(file_data) for sim_rpt in range(n_runs): # Run Kappa out_filename = out_filename_pattern.format( pset_idx, sim_rpt) args = [kasim_path] + base_args + [ '-i', kappa_filename, '-o', out_filename ] # Run KaSim self._logger.debug('Running: {}'.format(' '.join(args))) p = subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE, cwd=base_directory) for line in p.stdout: self._logger.debug('@@' + line.decode('utf8')[:-1]) (p_out, p_err) = p.communicate() if p.returncode: raise KasimInterfaceError( p_out.decode('utf8') + '\n' + p_err.decode('utf8')) # The simulation data, as a numpy array data = _parse_kasim_outfile(out_filename) # Sanity check that observables are in correct order assert data.dtype.names == kappa_col_names data = data.view('<f8') # Handle case with single row output if data.ndim == 1: data.shape = (1, data.shape[0]) # Parse into format tout.append(data[:, 0]) observable_traj.append(data[:, 1:]) finally: if cleanup: shutil.rmtree(base_directory) return SimulationResult(self, tout=tout, observables_and_expressions=observable_traj, simulations_per_param_set=n_runs)