def get_dynamic_component(model_type, id): """ Get a simulation's dynamic component Args: model_type (:obj:`type`): the subclass of `DynamicComponent` (or `obj_tables.Model`) being retrieved id (:obj:`str`): the dynamic component's id Returns: :obj:`DynamicComponent`: the dynamic component Raises: :obj:`MultialgorithmError`: if the dynamic component cannot be found """ if not inspect.isclass(model_type) or not issubclass( model_type, DynamicComponent): model_type = DynamicComponent.get_dynamic_model_type(model_type) if model_type not in DynamicComponent.dynamic_components_objs: raise MultialgorithmError( "model type '{}' not in DynamicComponent.dynamic_components_objs" .format(model_type.__name__)) if id not in DynamicComponent.dynamic_components_objs[model_type]: raise MultialgorithmError( "model type '{}' with id='{}' not in DynamicComponent.dynamic_components_objs" .format(model_type.__name__, id)) return DynamicComponent.dynamic_components_objs[model_type][id]
def validate(self): """ Fully validate a `WCSimulationConfig` instance Returns: :obj:`None`: if no error is found Raises: :obj:`MultialgorithmError`: if validation fails """ self.validate_individual_fields() de_sim_config = self.de_simulation_config # Max time must be positive if de_sim_config.time_max <= 0: raise MultialgorithmError( "Maximum time ({de_sim_config.time_max}) must be positive") if de_sim_config.output_dir is None and self.checkpoint_period is not None: raise MultialgorithmError( f"a data directory (self.de_simulation_config.output_dir) must be " f"provided when a checkpoint_period ({self.checkpoint_period}) is provided" ) # Check that timesteps divide evenly into the simulation duration self.check_periodic_timestep('ode_time_step') self.check_periodic_timestep('dfba_time_step') self.check_periodic_timestep('checkpoint_period') # verbose and profile should not both be set if self.verbose and de_sim_config.profile: raise MultialgorithmError( f"verbose and profile cannot both be true")
def __init__(self, dynamic_model, random_state, wc_lang_compartment, species_ids=None): """ Initialize the volume and density of this :obj:`DynamicCompartment`\ . Args: dynamic_model (:obj:`DynamicModel`): the simulation's dynamic model random_state (:obj:`numpy.random.RandomState`): a random state wc_lang_compartment (:obj:`Compartment`): the corresponding static `wc_lang` `Compartment` species_ids (:obj:`list` of :obj:`str`, optional): the IDs of the species stored in this compartment Raises: :obj:`MultialgorithmError`: if `self.init_volume` or `self.init_density` are not positive numbers """ super(DynamicCompartment, self).__init__(dynamic_model, None, wc_lang_compartment) self.id = wc_lang_compartment.id self.biological_type = wc_lang_compartment.biological_type self.physical_type = wc_lang_compartment.physical_type self.species_ids = species_ids # obtain initial compartment volume by sampling its specified distribution if wc_lang_compartment.init_volume and \ are_terms_equivalent(wc_lang_compartment.init_volume.distribution, onto['WC:normal_distribution']): mean = wc_lang_compartment.init_volume.mean std = wc_lang_compartment.init_volume.std if numpy.isnan(std): std = mean / self.MEAN_TO_STD_DEV_RATIO self.init_volume = max(0., random_state.normal(mean, std)) else: raise MultialgorithmError( 'Initial volume must be normally distributed') if math.isnan(self.init_volume): # pragma no cover: cannot be True raise MultialgorithmError( "DynamicCompartment {}: init_volume is NaN, but must be a positive " "number.".format(self.id)) if self.init_volume <= 0: raise MultialgorithmError( "DynamicCompartment {}: init_volume ({}) must be a positive " "number.".format(self.id, self.init_volume)) if not self._is_abstract(): init_density = wc_lang_compartment.init_density.value if math.isnan(init_density): raise MultialgorithmError( f"DynamicCompartment {self.id}: init_density is NaN, but must " f"be a positive number.") if init_density <= 0: raise MultialgorithmError( f"DynamicCompartment {self.id}: init_density ({init_density}) " f"must be a positive number.") self.init_density = init_density
def __init__(self, model, wc_sim_config, options=None): """ Args: model (:obj:`Model`): the model being simulated wc_sim_config (:obj:`WCSimulationConfig`): a whole-cell simulation configuration options (:obj:`dict`, optional): options for submodels, keyed by submodel class name """ # initialize simulation infrastructure self.simulation = SimulationEngine() self.random_state = RandomStateManager.instance() # create simulation attributes self.model = model self.wc_sim_config = wc_sim_config self.options = options self._skipped_submodels = self.prepare_skipped_submodels() # a model without submodels cannot be simulated submodel_ids = set( [submodel.id for submodel in self.model.get_submodels()]) if not submodel_ids - self.skipped_submodels(): raise MultialgorithmError( f"model {self.model.id} cannot be simulated because it contains" f" no submodels")
def eval(self, time): """ Evaluate this mathematical expression Approach: * Replace references to related Models in `self.wc_sim_tokens` with their values * Join the elements of `self.wc_sim_tokens` into a Python expression * `eval` the Python expression Args: time (:obj:`float`): the simulation time at which the expression should be evaluated Returns: :obj:`float`: the value of this :obj:`DynamicExpression` at time `time` Raises: :obj:`MultialgorithmError`: if Python `eval` raises an exception """ assert hasattr( self, 'wc_sim_tokens'), "'{}' must use prepare() before eval()".format( self.id) for idx, sim_token in enumerate(self.wc_sim_tokens): if sim_token.code == SimTokCodes.dynamic_expression: self.expr_substrings[idx] = str( sim_token.dynamic_expression.eval(time)) try: return eval(''.join(self.expr_substrings), {}, self.local_ns) except BaseException as e: raise MultialgorithmError("eval of '{}' raises {}: {}'".format( self.expression, type(e).__name__, str(e)))
def __init__(self, dynamic_model, local_species_population, wc_lang_model, wc_lang_expression): """ Args: dynamic_model (:obj:`DynamicModel`): the simulation's dynamic model local_species_population (:obj:`LocalSpeciesPopulation`): the simulation's species population store wc_lang_model (:obj:`obj_tables.Model`): the corresponding `wc_lang` `Model` wc_lang_expression (:obj:`ParsedExpression`): an analyzed and validated expression Raises: :obj:`MultialgorithmError`: if `wc_lang_expression` does not contain an analyzed, validated expression """ super().__init__(dynamic_model, local_species_population, wc_lang_model) # wc_lang_expression must have been successfully `tokenize`d. if not wc_lang_expression._obj_tables_tokens: raise MultialgorithmError( "_obj_tables_tokens cannot be empty - ensure that '{}' is valid" .format(wc_lang_model)) # optimization: self.wc_lang_expression will be deleted by prepare() self.wc_lang_expression = wc_lang_expression self.expression = wc_lang_expression.expression
def test_errors(self): msg = 'test msg' with self.assertRaisesRegexp(Error, msg): raise Error(msg) with self.assertRaisesRegexp(MultialgorithmError, msg): raise MultialgorithmError(msg) with self.assertRaisesRegexp(SpeciesPopulationError, msg): raise SpeciesPopulationError(msg) def expected(time, msg): return f"{time}: {msg}" time = 3.5 with self.assertRaisesRegexp(DynamicFrozenSimulationError, expected(time, msg)): raise DynamicFrozenSimulationError(time, msg) with self.assertRaisesRegexp(DynamicMultialgorithmError, expected(time, msg)): raise DynamicMultialgorithmError(time, msg) with self.assertRaisesRegexp(DynamicSpeciesPopulationError, expected(time, msg)): raise DynamicSpeciesPopulationError(time, msg)
def _prepare(self): """ Prepare and validate the model, and create simulation metadata """ # prepare & check the model PrepForWcSimTransform().run(self.model) errors = Validator().run(self.model) if errors: raise MultialgorithmError( indent_forest(['The model is invalid:', [errors]]))
def __init__(self, results_dir): """ Create a `RunResults` Args: results_dir (:obj:`str`): directory storing checkpoints and/or HDF5 file with the simulation run results """ if results_dir is None: raise MultialgorithmError('results_dir is None') if not os.path.isdir(results_dir): raise MultialgorithmError( f'results_dir {results_dir} must be a directory') self.results_dir = results_dir self.run_results = {} # if an HDF file containing the run results does not exist, then # create it from the stored metadata and sequence of checkpoints if not os.path.isfile(self._hdf_file()): # create the HDF file containing the run results population_df, observables_df, functions_df, aggregate_states_df, random_states_s = \ self.convert_checkpoints() # populations population_df.to_hdf(self._hdf_file(), 'populations') # observables observables_df.to_hdf(self._hdf_file(), 'observables') # functions functions_df.to_hdf(self._hdf_file(), 'functions') # aggregate states aggregate_states_df.to_hdf(self._hdf_file(), 'aggregate_states') # random states random_states_s.to_hdf(self._hdf_file(), 'random_states') # metadata self.convert_metadata(SimulationMetadata) self.convert_metadata(WCSimulationMetadata) # load the data in the HDF file containing the run results self._load_hdf_file()
def get_dynamic_model_type(model_type): """ Get a simulation's dynamic component type Obtain a dynamic component type from a corresponding `wc_lang` Model type, instance or string name. Args: model_type (:obj:`Object`): a `wc_lang` Model type represented by a subclass of `obj_tables.Model`, an instance of `obj_tables.Model`, or a string name for a `obj_tables.Model` Returns: :obj:`type`: the dynamic component Raises: :obj:`MultialgorithmError`: if the corresponding dynamic component type cannot be determined """ if isinstance(model_type, type) and issubclass(model_type, obj_tables.Model): if model_type in WC_LANG_MODEL_TO_DYNAMIC_MODEL: return WC_LANG_MODEL_TO_DYNAMIC_MODEL[model_type] raise MultialgorithmError( "model class of type '{}' not found".format( model_type.__name__)) if isinstance(model_type, obj_tables.Model): if model_type.__class__ in WC_LANG_MODEL_TO_DYNAMIC_MODEL: return WC_LANG_MODEL_TO_DYNAMIC_MODEL[model_type.__class__] raise MultialgorithmError("model of type '{}' not found".format( model_type.__class__.__name__)) if isinstance(model_type, str): model_type_type = getattr(wc_lang, model_type, None) if model_type_type is not None: if model_type_type in WC_LANG_MODEL_TO_DYNAMIC_MODEL: return WC_LANG_MODEL_TO_DYNAMIC_MODEL[model_type_type] raise MultialgorithmError( "model of type '{}' not found".format( model_type_type.__name__)) raise MultialgorithmError( "model type '{}' not defined".format(model_type)) raise MultialgorithmError( "model type '{}' has wrong type".format(model_type))
def _check_component(self, component): """ Raise an exception if the component is empty Args: component (:obj:`str`): the name of the component to check Raises: :obj:`MultialgorithmError`: if `component` is empty """ data = self.get(component) if data.empty: raise MultialgorithmError(f"'{component}' component is empty")
def initialize_mass_and_density(self, species_population): """ Initialize the species populations and the mass accounted for by species. Also initialize the fraction of density accounted for by species, `self.accounted_fraction`. Args: species_population (:obj:`LocalSpeciesPopulation`): the simulation's species population store Raises: :obj:`MultialgorithmError`: if `accounted_fraction == 0` or if `self.MAX_ALLOWED_INIT_ACCOUNTED_FRACTION < accounted_fraction` """ self.species_population = species_population self.init_accounted_mass = self.accounted_mass(time=0) if self._is_abstract(): self.init_mass = self.init_accounted_mass else: self.init_mass = self.init_density * self.init_volume self.init_accounted_density = self.init_accounted_mass / self.init_volume # calculate fraction of initial mass or density represented by species self.accounted_fraction = self.init_accounted_density / self.init_density # also, accounted_fraction = self.init_accounted_mass / self.init_mass # usually epsilon < accounted_fraction <= 1, where epsilon depends on how thoroughly # processes in the compartment are characterized if 0 == self.accounted_fraction: raise MultialgorithmError( "DynamicCompartment '{}': initial accounted ratio is 0". format(self.id)) elif 1.0 < self.accounted_fraction <= self.MAX_ALLOWED_INIT_ACCOUNTED_FRACTION: warnings.warn( "DynamicCompartment '{}': initial accounted ratio ({:.3E}) greater than 1.0" .format(self.id, self.accounted_fraction), MultialgorithmWarning) if self.MAX_ALLOWED_INIT_ACCOUNTED_FRACTION < self.accounted_fraction: raise MultialgorithmError( "DynamicCompartment {}: initial accounted ratio ({:.3E}) greater " "than self.MAX_ALLOWED_INIT_ACCOUNTED_FRACTION ({}).". format(self.id, self.accounted_fraction, self.MAX_ALLOWED_INIT_ACCOUNTED_FRACTION))
def __init__(self, id, dynamic_model, reactions, species, dynamic_compartments, local_species_population, default_center_of_mass=None, options=None): """ Initialize an SSA submodel object. Args: id (:obj:`str`): unique id of this dynamic SSA submodel dynamic_model (:obj:`DynamicModel`): the aggregate state of a simulation reactions (:obj:`list` of :obj:`Reaction`): the reactions modeled by this SSA submodel species (:obj:`list` of :obj:`Species`): the species that participate in the reactions modeled by this SSA submodel, with their initial concentrations dynamic_compartments (:obj:`dict`): :obj:`DynamicCompartment`\ s, keyed by id, that contain species which participate in reactions that this SSA submodel models, including adjacent compartments used by its transfer reactions local_species_population (:obj:`LocalSpeciesPopulation`): the store that maintains this SSA submodel's species population default_center_of_mass (:obj:`float`, optional): the center-of-mass for the :obj:`ExponentialMovingAverage` options (:obj:`dict`, optional): SSA submodel options Raises: :obj:`MultialgorithmError`: if the initial SSA wait exponential moving average is not positive """ super().__init__(id, dynamic_model, reactions, species, dynamic_compartments, local_species_population) self.options = options self.num_SsaWaits = 0 # `initial_ssa_wait_ema` must be positive, as otherwise an infinite sequence of SsaWait # messages will be executed at the start of a simulation if no reactions are enabled initial_ssa_wait_ema = config_multialgorithm['initial_ssa_wait_ema'] if initial_ssa_wait_ema <= 0: # pragma: no cover raise MultialgorithmError( f"'initial_ssa_wait_ema' must be positive to avoid infinite sequence of " "SsaWait messages, but it is {initial_ssa_wait_ema}") if default_center_of_mass is None: default_center_of_mass = config_multialgorithm[ 'default_center_of_mass'] self.ema_of_inter_event_time = ExponentialMovingAverage( initial_ssa_wait_ema, center_of_mass=default_center_of_mass) self.random_state = RandomStateManager.instance() self.log_with_time("init: id: {}".format(id)) self.log_with_time("init: species: {}".format( str([s.id for s in species])))
def validate_individual_fields(self): """ Validate individual fields in a `WCSimulationConfig` instance Returns: :obj:`None`: if no error is found Raises: :obj:`MultialgorithmError`: if an attribute fails validation """ # additional validation if self.ode_time_step is not None and self.ode_time_step <= 0: raise MultialgorithmError( f'ode_time_step ({self.ode_time_step}) must be positive') if self.dfba_time_step is not None and self.dfba_time_step <= 0: raise MultialgorithmError( f'dfba_time_step ({self.dfba_time_step}) must be positive') if self.checkpoint_period is not None and self.checkpoint_period <= 0: raise MultialgorithmError( f'checkpoint_period ({self.checkpoint_period}) must be positive' )
def _prepare_computed_components(cls): """ Check and initialize the `COMPUTED_COMPONENTS` Raises: :obj:`MultialgorithmError`: if a value in `self.COMPUTED_COMPONENTS` is not a method in `RunResults` """ for component, method in cls.COMPUTED_COMPONENTS.items(): if hasattr(cls, method): cls.COMPUTED_COMPONENTS[component] = getattr(cls, method) else: raise MultialgorithmError( "'{}' in COMPUTED_COMPONENTS is not a method in {}".format( method, cls.__name__))
def __init__(self, id, dynamic_model, reactions, species, dynamic_compartments, local_species_population, ode_time_step, options=None): """ Initialize an ODE submodel instance Args: id (:obj:`str`): unique id of this dynamic ODE submodel dynamic_model (:obj: `DynamicModel`): the aggregate state of a simulation reactions (:obj:`list` of `wc_lang.Reaction`): the reactions modeled by this ODE submodel species (:obj:`list` of `wc_lang.Species`): the species that participate in the reactions modeled by this ODE submodel dynamic_compartments (:obj: `dict`): `DynamicCompartment`s, keyed by id, that contain species which participate in reactions that this ODE submodel models, including adjacent compartments used by its transfer reactions local_species_population (:obj:`LocalSpeciesPopulation`): the store that maintains this ODE submodel's species population ode_time_step (:obj:`float`): time interval between ODE analyses num_steps (:obj:`int`): number of steps taken options (:obj:`dict`, optional): ODE submodel options """ super().__init__(id, dynamic_model, reactions, species, dynamic_compartments, local_species_population) if ode_time_step <= 0: raise MultialgorithmError( f"OdeSubmodel {self.id}: ode_time_step must be positive, but is " f"{ode_time_step}") self.ode_time_step = ode_time_step self.num_steps = 0 self.set_up_ode_submodel() self.set_up_optimizations() ode_solver_options = { 'atol': self.ABS_ODE_SOLVER_TOLERANCE, 'rtol': self.REL_ODE_SOLVER_TOLERANCE } if options is not None and 'tolerances' in options: if 'atol' in options['tolerances']: ode_solver_options['atol'] = options['tolerances']['atol'] if 'rtol' in options['tolerances']: ode_solver_options['rtol'] = options['tolerances']['rtol'] self.solver = self.create_ode_solver(**ode_solver_options)
def prepare_skipped_submodels(self): """ Prepare the IDs of the submodels that will not be run Returns: :obj:`set` of :obj:`str`: the IDs of submodels that will not run """ if self.wc_sim_config.submodels_to_skip: submodels_to_skip = set(self.wc_sim_config.submodels_to_skip) submodel_ids = set( [submodel.id for submodel in self.model.get_submodels()]) if submodels_to_skip - submodel_ids: raise MultialgorithmError( f"'submodels_to_skip' contains submodels that aren't in the model: " f"{submodels_to_skip - submodel_ids}") return submodels_to_skip else: return set()
def __init__(self, model): """ Args: model (:obj:`str` or `Model`): either a path to file(s) describing a `wc_lang` model, or a `Model` instance Raises: :obj:`MultialgorithmError`: if `model` is invalid """ if isinstance(model, Model): self.model_path = None self.model = model elif isinstance(model, str): # read model self.model_path = os.path.abspath(os.path.expanduser(model)) self.model = Reader().run(self.model_path)[Model][0] else: raise MultialgorithmError( "model must be a `wc_lang Model` or a pathname for a model, " "but its type is {}".format(type(model)))
def check_periodic_timestep(self, periodic_attr): """ Check that simulation duration is an integral multiple of a periodic activity's timestep Args: periodic_attr (:obj:`str`): name of an attribute storing the duration of a periodic activity Returns: :obj:`None`: if no error is found Raises: :obj:`MultialgorithmError`: if the simulation duration is not an integral multiple of the periodic activity's timestep """ de_sim_cfg = self.de_simulation_config if getattr(self, periodic_attr) is not None and \ (de_sim_cfg.time_max - de_sim_cfg.time_init) / getattr(self, periodic_attr) % 1 != 0: raise MultialgorithmError( f'(time_max - time_init) ({de_sim_cfg.time_max} - {de_sim_cfg.time_init}) ' f'must be a multiple of {periodic_attr} ({getattr(self, periodic_attr)})' )
def __init__(self, id, dynamic_model, reactions, species, dynamic_compartments, local_species_population, time_step, options=None): """ Initialize a dFBA submodel instance Args: id (:obj:`str`): unique id of this dynamic dFBA submodel dynamic_model (:obj: `DynamicModel`): the aggregate state of a simulation reactions (:obj:`list` of `wc_lang.Reaction`): the reactions modeled by this dFBA submodel species (:obj:`list` of `wc_lang.Species`): the species that participate in the reactions modeled by this dFBA submodel dynamic_compartments (:obj: `dict`): `DynamicCompartment`s, keyed by id, that contain species which participate in reactions that this dFBA submodel models, including adjacent compartments used by its transfer reactions local_species_population (:obj:`LocalSpeciesPopulation`): the store that maintains this dFBA submodel's species population time_step (:obj:`float`): time between FBA executions options (:obj:`dict`, optional): dFBA submodel options """ super().__init__(id, dynamic_model, reactions, species, dynamic_compartments, local_species_population) self.algorithm = 'FBA' if time_step <= 0: raise MultialgorithmError("time_step must be positive, but is {}".format(time_step)) self.time_step = time_step self.options = options # log initialization data self.log_with_time("init: id: {}".format(id)) self.log_with_time("init: time_step: {}".format(str(time_step))) self.metabolismProductionReaction = None self.exchangedSpecies = None self.cobraModel = None self.thermodynamicBounds = None self.exchangeRateBounds = None self.defaultFbaBound = 1e15 self.reactionFluxes = np.zeros(0)
def get_concentrations(self, compartment_id=None): """ Get species concentrations at checkpoint times Args: compartment_id (:obj:`str`, optional): if provided, obtain concentrations for species in `compartment_id`; otherwise, return the concentrations of all species Returns: :obj:`pandas.DataFrame`: the concentrations of species at checkpoint times, filtered by `compartment_id` if it's provided Raises: :obj:`MultialgorithmError`: if no species are in the compartment """ populations = self.get('populations') self._check_component('populations') if compartment_id is None: # iterate over species in populations, dividing by the right compartment # (as of 0.25.3 pandas doesn't support joins between two MultiIndexes) pop_div_vol = populations.copy() for species_id in populations.columns.values: _, compartment_id = ModelUtilities.parse_species_id(species_id) pop_div_vol.loc[:, species_id] = pop_div_vol.loc[:, species_id] / \ self.get_volumes(compartment_id=compartment_id) concentrations = pop_div_vol / Avogadro return (concentrations) else: compartment_vols = self.get_volumes(compartment_id=compartment_id) # filter to populations for species in compartment_id filter = f'\[{compartment_id}\]$' filtered_populations = populations.filter(regex=filter) if filtered_populations.empty: # pragma: no cover raise MultialgorithmError( f"No species found in compartment '{compartment_id}'") concentrations = filtered_populations.div(compartment_vols, axis='index') / Avogadro return (concentrations)
def create_ode_solver(self, **options): """ Create a `scikits.odes` ODE solver that uses CVODE Args: options (:obj:`dict`): options for the solver; see https://github.com/bmcage/odes/blob/master/scikits/odes/sundials/cvode.pyx Returns: :obj:`scikits.odes.ode`: an ODE solver instance Raises: :obj:`MultialgorithmError`: if the ODE solver cannot be created """ # use CVODE from LLNL's SUNDIALS project (https://computing.llnl.gov/projects/sundials) CVODE_SOLVER = 'cvode' solver = ode(CVODE_SOLVER, self.right_hand_side, old_api=False, **options) if not isinstance(solver, ode): # pragma: no cover raise MultialgorithmError( f"OdeSubmodel {self.id}: scikits.odes.ode() failed") return solver
def get(self, component): """ Provide the specified `component` Args: component (:obj:`str`): the name of the component to return Returns: :obj:`object`: an object containing a component of this `RunResults`, as specified by `component`; simulation time series data are :obj:`pandas.DataFrame` or `pandas.Series` instances; simulation metadata are :obj:`dict` instances. Raises: :obj:`MultialgorithmError`: if `component` is not an element of `RunResults.COMPONENTS` or `RunResults.COMPUTED_COMPONENTS` """ all_components = RunResults.COMPONENTS.union( RunResults.COMPUTED_COMPONENTS) if component not in all_components: raise MultialgorithmError( f"component '{component}' is not an element of {all_components}" ) if component in RunResults.COMPUTED_COMPONENTS: return RunResults.COMPUTED_COMPONENTS[component](self) return self.run_results[component]
def create_dynamic_submodels(self): """ Create dynamic submodels that access shared species Returns: :obj:`list`: list of the simulation's `DynamicSubmodel`\ s Raises: :obj:`MultialgorithmError`: if a submodel cannot be created """ def get_options(self, submodel_class_name): if self.options is not None and submodel_class_name in self.options: return self.options[submodel_class_name] else: return {} # make the simulation's submodels simulation_submodels = {} for lang_submodel in self.model.get_submodels(): if lang_submodel.id in self.skipped_submodels(): continue # don't create a submodel with no reactions if not lang_submodel.reactions: warnings.warn( f"not creating submodel '{lang_submodel.id}': no reactions provided", MultialgorithmWarning) continue if are_terms_equivalent( lang_submodel.framework, onto['WC:stochastic_simulation_algorithm']): simulation_submodel = SsaSubmodel( lang_submodel.id, self.dynamic_model, list(lang_submodel.reactions), lang_submodel.get_children(kind='submodel', __type=Species), self.get_dynamic_compartments(lang_submodel), self.local_species_population, **get_options(self, 'SsaSubmodel')) elif are_terms_equivalent(lang_submodel.framework, onto['WC:next_reaction_method']): simulation_submodel = NrmSubmodel( lang_submodel.id, self.dynamic_model, list(lang_submodel.reactions), lang_submodel.get_children(kind='submodel', __type=Species), self.get_dynamic_compartments(lang_submodel), self.local_species_population, **get_options(self, 'NrmSubmodel')) elif are_terms_equivalent( lang_submodel.framework, onto['WC:dynamic_flux_balance_analysis']): # TODO(Arthur): make DFBA submodels work simulation_submodel = DfbaSubmodel( lang_submodel.id, self.dynamic_model, list(lang_submodel.reactions), lang_submodel.get_children(kind='submodel', __type=Species), self.get_dynamic_compartments(lang_submodel), self.local_species_population, self.wc_sim_config.dfba_time_step, **get_options(self, 'DfbaSubmodel')) elif are_terms_equivalent( lang_submodel.framework, onto['WC:ordinary_differential_equations']): simulation_submodel = OdeSubmodel( lang_submodel.id, self.dynamic_model, list(lang_submodel.reactions), lang_submodel.get_children(kind='submodel', __type=Species), self.get_dynamic_compartments(lang_submodel), self.local_species_population, self.wc_sim_config.ode_time_step, **get_options(self, 'OdeSubmodel')) elif are_terms_equivalent( lang_submodel.framework, onto['WC:deterministic_simulation_algorithm']): # a deterministic simulation algorithm, used for testing simulation_submodel = DsaSubmodel( lang_submodel.id, self.dynamic_model, list(lang_submodel.reactions), lang_submodel.get_children(kind='submodel', __type=Species), self.get_dynamic_compartments(lang_submodel), self.local_species_population, **get_options(self, 'DsaSubmodel')) else: raise MultialgorithmError( f"Unsupported lang_submodel framework '{lang_submodel.framework}'" ) simulation_submodels[simulation_submodel.id] = simulation_submodel # add the submodel to the simulation self.simulation.add_object(simulation_submodel) return simulation_submodels
def run(self, time_max, results_dir=None, progress_bar=True, checkpoint_period=None, seed=None, ode_time_step=None, dfba_time_step=None, profile=False, submodels_to_skip=None, verbose=True, options=None): """ Run one simulation Args: time_max (:obj:`float`): the maximum time of a simulation; a stop condition may end it earlier (sec) results_dir (:obj:`str`, optional): path to a directory in which results are stored progress_bar (:obj:`bool`, optional): whether to show the progress of a simulation in in a real-time bar on a terminal checkpoint_period (:obj:`float`, optional): the period between simulation state checkpoints (sec) ode_time_step (:obj:`float`, optional): time step length of ODE submodel (sec) dfba_time_step (:obj:`float`, optional): time step length of dFBA submodel (sec) profile (:obj:`bool`, optional): whether to output a profile of the simulation's performance created by a Python profiler seed (:obj:`object`, optional): a seed for the simulation's `numpy.random.RandomState`; if provided, `seed` will reseed the simulator's PRNG submodels_to_skip (:obj:`list` of :obj:`str`, optional): submodels that should not be run, identified by their ids verbose (:obj:`bool`, optional): whether to print success output options (:obj:`dict`, optional): options for submodels, passed to `MultialgorithmSimulation` Returns: :obj:`tuple` of (`int`, `str`): number of simulation events, pathname of directory containing the results, or :obj:`tuple` of (`int`, `None`): number of simulation events, `None` if `results_dir is None`, or :obj:`tuple` of (`pstats.Stats`, `None`): profile stats, `None` if `profile is True` Raises: :obj:`MultialgorithmError`: if the simulation raises an exception """ self._prepare() # create simulation configurations # create and validate DE sim configuration self.de_sim_config = SimulationConfig(time_max, output_dir=results_dir, progress=progress_bar, profile=profile) self.de_sim_config.validate() # create and validate WC configuration self.wc_sim_config = WCSimulationConfig( de_simulation_config=self.de_sim_config, random_seed=seed, ode_time_step=ode_time_step, dfba_time_step=dfba_time_step, checkpoint_period=checkpoint_period, submodels_to_skip=submodels_to_skip, verbose=verbose) self.wc_sim_config.validate() # create author metadata for DE sim try: username = getpass.getuser() except KeyError: # pragma: no cover username = '******' self.author_metadata = AuthorMetadata( name='Unknown name', email='Unknown email', username=username, organization='Unknown organization') # create WC sim metadata wc_simulation_metadata = WCSimulationMetadata(self.wc_sim_config) if self.model_path is not None: wc_simulation_metadata.set_wc_model_repo(self.model_path) if seed is not None: RandomStateManager.initialize(seed=seed) # create a multi-algorithmic simulator multialgorithm_simulation = MultialgorithmSimulation( self.model, self.wc_sim_config, options) self.simulation_engine, self.dynamic_model = multialgorithm_simulation.build_simulation( ) self.simulation_engine.initialize() # set stop_condition after the dynamic model is created self.de_sim_config.stop_condition = self.dynamic_model.get_stop_condition( ) # run simulation try: # provide DE config and author metadata to DE sim simulate_rv = self.simulation_engine.simulate( sim_config=self.de_sim_config, author_metadata=self.author_metadata) # add WC sim metadata to the output after the simulation, which requires an empty output dir # TODO: have simulation_engine.simulate() allow certain files in self.de_sim_config.output_dir, and move # this code above if self.de_sim_config.output_dir is not None: WCSimulationMetadata.write_dataclass( wc_simulation_metadata, self.de_sim_config.output_dir) if profile: stats = simulate_rv return stats, None else: num_events = simulate_rv except SimulatorError as e: # pragma: no cover raise MultialgorithmError( f'Simulation terminated with simulator error:\n{e}') except BaseException as e: # pragma: no cover raise MultialgorithmError( f'Simulation terminated with error:\n{e}') if verbose: print(f'Simulated {num_events} events') if results_dir: # summarize results in an HDF5 file in results_dir RunResults(results_dir) if verbose: print(f"Saved checkpoints and run results in '{results_dir}'") return (num_events, results_dir) else: return (num_events, None)
def prepare(self): """ Prepare this dynamic expression for simulation Because they refer to each other, all :obj:`DynamicExpression`\ s must be created before any of them are prepared. Raises: :obj:`MultialgorithmError`: if a Python function used in `wc_lang_expression` does not exist """ # create self.wc_sim_tokens, which contains WcSimTokens that refer to other DynamicExpressions self.wc_sim_tokens = [] # optimization: combine together adjacent obj_tables_token.tok_codes other than obj_id next_static_tokens = '' function_names = set() i = 0 while i < len(self.wc_lang_expression._obj_tables_tokens): obj_tables_token = self.wc_lang_expression._obj_tables_tokens[i] if obj_tables_token.code == ObjTablesTokenCodes.math_func_id: function_names.add(obj_tables_token.token_string) if obj_tables_token.code in self.NON_LANG_OBJ_ID_TOKENS: next_static_tokens = next_static_tokens + obj_tables_token.token_string elif obj_tables_token.code == ObjTablesTokenCodes.obj_id: if next_static_tokens != '': self.wc_sim_tokens.append( WcSimToken(SimTokCodes.other, next_static_tokens)) next_static_tokens = '' try: dynamic_expression = DynamicComponent.get_dynamic_component( obj_tables_token.model, obj_tables_token.model_id) except: raise MultialgorithmError( "'{}.{} must be prepared to create '{}''".format( obj_tables_token.model.__class__.__name__, obj_tables_token.model_id, self.id)) self.wc_sim_tokens.append( WcSimToken(SimTokCodes.dynamic_expression, obj_tables_token.token_string, dynamic_expression)) else: # pragma: no cover assert False, "unknown code {} in {}".format( obj_tables_token.code, obj_tables_token) # advance to the next token i += 1 if next_static_tokens != '': self.wc_sim_tokens.append( WcSimToken(SimTokCodes.other, next_static_tokens)) # optimization: to conserve memory, delete self.wc_lang_expression del self.wc_lang_expression # optimization: pre-allocate and pre-populate substrings for the expression to eval self.expr_substrings = [] for sim_token in self.wc_sim_tokens: if sim_token.code == SimTokCodes.other: self.expr_substrings.append(sim_token.token_string) else: self.expr_substrings.append('') # optimization: pre-allocate Python functions in namespace self.local_ns = {} for func_name in function_names: if func_name in globals()['__builtins__']: self.local_ns[func_name] = globals()['__builtins__'][func_name] elif hasattr(globals()['math'], func_name): self.local_ns[func_name] = getattr(globals()['math'], func_name) else: # pragma no cover, because only known functions are allowed in model expressions raise MultialgorithmError( "loading expression '{}' cannot find function '{}'".format( self.expression, func_name))
def __setattr__(self, name, value): """ Validate an attribute when it is changed """ try: super().__setattr__(name, value) except TypeError as e: raise MultialgorithmError(e)
def __init__(self, model, species_population, dynamic_compartments): """ Prepare a `DynamicModel` for a discrete-event simulation Args: model (:obj:`Model`): the description of the whole-cell model in `wc_lang` species_population (:obj:`LocalSpeciesPopulation`): the simulation's species population store dynamic_compartments (:obj:`dict`): the simulation's :obj:`DynamicCompartment`\ s, one for each compartment in `model` Raises: :obj:`MultialgorithmError`: if the model has no cellular compartments """ self.id = model.id self.dynamic_compartments = dynamic_compartments self.species_population = species_population self.num_submodels = len(model.get_submodels()) # determine cellular compartments self.cellular_dyn_compartments = [] for dynamic_compartment in dynamic_compartments.values(): if dynamic_compartment.biological_type == onto[ 'WC:cellular_compartment']: self.cellular_dyn_compartments.append(dynamic_compartment) if dynamic_compartments and not self.cellular_dyn_compartments: raise MultialgorithmError( f"model '{model.id}' must have at least 1 cellular compartment" ) # === create dynamic objects that are not expressions === # create dynamic parameters self.dynamic_parameters = {} for parameter in model.parameters: self.dynamic_parameters[parameter.id] = \ DynamicParameter(self, self.species_population, parameter, parameter.value) # create dynamic species self.dynamic_species = {} for species in model.get_species(): self.dynamic_species[species.id] = \ DynamicSpecies(self, self.species_population, species) # === create dynamic expressions === # create dynamic observables self.dynamic_observables = {} for observable in model.observables: self.dynamic_observables[observable.id] = \ DynamicObservable(self, self.species_population, observable, observable.expression._parsed_expression) # create dynamic functions self.dynamic_functions = {} for function in model.functions: self.dynamic_functions[function.id] = \ DynamicFunction(self, self.species_population, function, function.expression._parsed_expression) # create dynamic stop conditions self.dynamic_stop_conditions = {} for stop_condition in model.stop_conditions: self.dynamic_stop_conditions[stop_condition.id] = \ DynamicStopCondition(self, self.species_population, stop_condition, stop_condition.expression._parsed_expression) # create dynamic rate laws self.dynamic_rate_laws = {} for rate_law in model.rate_laws: self.dynamic_rate_laws[rate_law.id] = \ DynamicRateLaw(self, self.species_population, rate_law, rate_law.expression._parsed_expression) # create dynamic dFBA Objectives self.dynamic_dfba_objectives = {} ''' # todo: fix: 'DfbaObjReaction.Metabolism_biomass must be prepared to create 'dfba-obj-test_submodel'' for dfba_objective in model.dfba_objs: self.dynamic_dfba_objectives[dfba_objective.id] = \ DynamicDfbaObjective(self, self.species_population, dfba_objective, dfba_objective.expression._parsed_expression) ''' # prepare dynamic expressions for dynamic_expression_group in [ self.dynamic_observables, self.dynamic_functions, self.dynamic_stop_conditions, self.dynamic_rate_laws, self.dynamic_dfba_objectives ]: for dynamic_expression in dynamic_expression_group.values(): dynamic_expression.prepare()