def _release(self, end: Finished, fullrelease: bool = True) -> str: """ Clean data before run ending. A double release will raise an InternalError. Intended to be launched from self.run, not manually @param end: ending message @type end: Finished @param fullrelease: if true, also clean memory (Default value = True) @type fullrelease: bool @return: end message @rtype: str """ if self.initialized: self.statistic.end(end) self.statistic.calcsnapshot(final=True) if fullrelease: self.crn.close() self.param.unlock() self.initialized = False self.status.gcclean() self.signcatch.reset() LOGGER.info(f"System {'fully ' if fullrelease else ''}cleaned") return f"{end} ({LOGGER.runtime} s)" raise InternalError("Attempt to release non-initialized system!")
def __init__(self, model: Model, categorize: bool = True, dropmode: str = "drop"): """Create a collection from a model (for categorizing and building objects). @param model: Model containing all rules @type model: Model @param categorize: shall the objects be automatically categorized (Default value = True) @type categorize: bool @param dropmode: how the unactivated object are dealt ("drop"=> removed, "keep"=> kept in pool) (Default value = "drop") @type dropmode: str """ self.model: Model = model """rule model""" self.dropmode: str = dropmode """drop mode 'keep' or 'drop'""" self.pool: Dict[K, T] = {} """pool of all objects""" self.categories: Dict[str, Set[K]] = defaultdict(set) """categories of objects""" self.active: Dict[K, T] = self.pool if self.dropmode == "drop" else {} """pool of active objects""" self.categorize: bool = categorize """shall the objects be categorized""" LOGGER.debug( f"Created {self} as drop={self.dropmode}, with pool of type {type(self.pool)}" )
def choose(self) -> Tuple[Any, float]: """Choose a probabilistic event according to Gillespie's algorithm. @return: the chosen object, and the delta_t after which the event occured @rtype: Tuple[Any, float] @raise RoundError: if something bad happened (most likely being a rounding error somewhere) """ # First choose a random line in the probability map try: # chosen = random.choice(self._mapobj, p=self._problist / self.probtot) chosen_num = choice(self._problist, self.probtot * self.sysrand.random()) chosen = self._mapobj[chosen_num] if chosen_num == -1: if self.probtot == 0.0: raise NoMore("choice() had nothing to choose!") raise RoundError( "choice() couldn't find a suitable object." f"probtot={self.probtot}=?={self._problist.sum()}; " f"problist={self._problist})") delta_t = np.log(1 / self.sysrand.random()) / self.probtot if chosen is None: LOGGER.error( f"Badly destroyed reaction from {self._mapobj} with proba {self._problist}" ) return (chosen, delta_t) except ValueError as err: raise RoundError( f"(reason: {err}; " f"probtot={self.probtot}=?={self._problist.sum()}; " f"problist={self._problist})")
def set_param(self, **kwd: Any) -> None: """Set run parameters, overriding the previous ones.""" try: self.param.set_param(**kwd) except LockedError: raise InternalError("Parameter set after initialization") LOGGER.info(f"Parameters changed: {self.param}")
def _create_rules(self) -> None: """Read and create rules.""" for rulename in self.reactions: # Get rule from parameter file if rulename in self.param.rules: ruleparam = self.param.rules[rulename] try: # then create it from rule module rule = Rule( name=rulename, reactants=tuple(ruleparam.reactants), builder=( self.rulepath[ruleparam.builder_func], self.rulepath[ruleparam.builder_const], self.rulepath[ruleparam.builder_variant], ), descr=ruleparam.descr, parameters=self.parameters, robust=ruleparam.robust, ) except KeyError: # raise an error if the rule from file is not in the module raise InitError( f"The rule '{rulename}' from '{self.modelparam}'" f"is not defined in '{self.rulepath}'" ) # Register the created rule self._add_rule(rulename, rule) else: LOGGER.warning( f"Reaction '{rulename}' not found in {self.rulepath}, ignored." )
def set_param(self, key: str, val: float, terminate: bool = True) -> None: """Set a parameter value. This will automatically update parameters defined by a relation If an attempt is done to fix the value of a parameter defined by a relation, the request will be ignored *except* if the 'terminate' flag is set to False (do not use this flag manually, this is intended to be used by _param_init; direct use may result in an inconsistent set of parameters) @param key: parameter name @type key: str @param val: parameter value @type val: float @param terminate: do not use manually (see above) @type terminate: bool @raise KeyError: if the parameter does not exists """ if terminate and key in self._relation: LOGGER.warning( f"'{key}' was set by a relation, direct setting request to '{val}' ignored" ) elif key in self._paramdict: if key not in self._updating: self._paramdict[key] = val self._updating.append(key) self._param_init() else: raise KeyError(f"Key {key} not registered") if terminate: self._updating.clear()
def __init__(self, param: Param): """Create a Crn object from a L{Param} parameter object. @param param: parameter object @type param: Param """ # declarations self.probalist: Probalist """list of probalities""" self.comp_collect: CollectofCompound """compounds collection""" self.reac_collect: CollectofReaction """reactions collection""" # update trackers self._reac_update: Set[Reaction] = set() """Track the reactions to be updated in batch""" self._comp_update: Dict[Compound, int] = {} """Track the compounds to be updated in batch""" # Create Crn objects self.model: Model = Model(param.rulemodel, param.reactions, param.parameters) """Model describing the set of rules""" self.vol: float = param.vol """System volume""" self.dropmode: str = param.dropmode """dropmode flag (do we 'drop' or 'keep' inactive reactions?)""" self.init_collect() for compound, pop in param.init.items(): self.comp_collect[compound].change_pop(pop) self.update() LOGGER.debug(f"Initialized with {param}")
def close(self) -> None: """Write all remaining data in hdf5 file, clean it, then close it.""" LOGGER.info(f"File {self.writer.filename} to be written and closed...") self.writesnap() self.writemap() self.writer.close() LOGGER.info(f"...written and closed, done.")
def writesnap(self) -> None: """Write all previous snapshots to hdf5.""" # Correct snapshot sizes nbsnap = MPI_STATUS.max(self._nbsnap) nbcomp = MPI_STATUS.max(self._nbcomp) nbreac = MPI_STATUS.max(self._nbreac) LOGGER.debug(f"resize snapshot with {nbsnap}-{nbcomp}-{nbreac}") self.writer.snapsize(nbcomp, nbreac, nbsnap) # Write snapshots for col, (time, comp, reac) in enumerate( zip(self._snaptimes, self._snapcomp, self._snapreac)): self.writer.add_snapshot(comp, reac, col, time)
def writestat(self) -> None: """Write general statistics at present time to hdf5.""" res = (self.status.info + self.concentrations + [ self.crn.collstat( collection=stats.collection, prop=stats.prop, weight=stats.weight, method=stats.method, full=stats.full, ) for stats in self.param.statparam.values() ]) self.writer.add_data(res) LOGGER.debug(str(res))
def checkmem(self) -> None: """ Check if the process reached its memory limit. If reached, close the MPI gate for 'oom' reason for selectively stopping some threads at the next gathering at the MPI gate. """ if self.gcperio: gc.collect() if self.memuse > self.maxmem / MPI_GATE.mem_divide: LOGGER.warning( f"{self.memuse}>{self.maxmem/MPI_GATE.mem_divide}, call oom") MPI_GATE.close("oom")
def run(self, release: bool = True) -> str: """ Launch a run. The final state can be kept by setting 'release' to false (useful for run directly launched from the command line for further direct data manipulation). It is by default released in order to release memory as soon as a run is finished during batch runs. @param release: Shall the final state be released at run end @type release: bool @return: end message @rtype: str """ LOGGER.reset_timer() if MPI_STATUS.ismpi: LOGGER.info(f"Launching MPI run from thread #{MPI_STATUS.rank}") else: LOGGER.info("Launching single run.") # Setup working environment self._initialize() # Ready, let's run LOGGER.info(f"Run #{MPI_STATUS.rank}={getpid()} launched") with MPI_GATE.launch(): # Do not stop except if asked to exit the gate while MPI_GATE.cont: try: # Log stat info self.status.logstat() # perform a step self._process() # Write data statistics self.statistic.calc() # Check status before next step self.status.checkstep() # Pass gate checkpoint MPI_GATE.checkpoint() # exit the gate because the work is finished. except Finished as the_end: end = self._release(the_end, release) break # exit the gate because asked by collective decision. else: end = self._release( OOMError("Stopped by kind request of OOM killer"), release) # Write and log final data, then exit. LOGGER.info(f"Run #{MPI_STATUS.rank}={getpid()} finished") self.statistic.close() return end
def unregister_reaction(self, reaction: Reaction) -> None: """Unregister a reaction from the compound list of the reactions it is involved in as a reactant. (unregistered from self.reactions) Called at reaction deactivation. @param reaction: reaction object to be unregistered @type reaction: Reaction """ try: self.reactions.remove(reaction) except KeyError: LOGGER.debug( f"Tried to unregister twice {reaction} from {self} (p={self.pop})" )
def __init__(self, description: str, crn: "Crn"): """Create the reaction from its 'description', to be linked to the parent 'crn'. The description is simply the compound name @param description: object description @type description: str @param crn: parent chemical reaction network @type crn: Crn """ super().__init__(description, crn) if self.description == "": LOGGER.error("Created empty compound!!!") self.reactions: Set[Reaction] = set() """Set of reactions in which the compound is a reactant""" self.pop: int = 0 """compound population"""
def add_relation(self, key: str, relation: Paramrel) -> None: """Add a new parameter and set it by a relation. If the key already exists, it will be overriden. @param key: parameter name @type key: str @param relation: relation function @type relation: Paramrel """ try: self.add_param(key) except KeyError: LOGGER.warning( f"Setting '{key}' relation will override already set value ({self[key]})" ) self._relation[key] = relation self._param_init()
def __init__(self, param: Param): """ Generate files structures from parameters. @param param: parameters @type param: Param """ self.name: str = param.name """run name""" # self.timeformat = param.timeformat self.savedir: str = param.savedir if param.savedir else path.curdir """save folder""" if not path.isdir(self.savedir): raise NotAFolder("Bad folder name {self.savedir}") self.logdir: str = param.logdir """log folder""" if not path.isdir(self.logdir): LOGGER.debug( "Logs will be sent to standard output because {self.logdir} is not a folder." )
def _process(self) -> None: """Perform a run step.""" # Check if a cleanup should be done if self.param.autoclean: self.crn.clean() # Process self.maxsteps times for _ in repeat(None, self.param.maxsteps): # ... but stop if step ends if self.status.finished: break # ... or if process is stopped by a signal if not self.signcatch.alive: raise Interrupted( f" by {self.signcatch.signal} at t={self.status.time}") # perform a random event self.status.inc(self.crn.stepping()) else: # had to performed loop until maxsteps LOGGER.warning( f"maxsteps per process (={self.param.maxsteps}) too low")
def _initialize(self) -> None: """Initialize the system before starting the run. Intended to be launched from self.run, not manually @raise InternalError: in case of double initialization. """ if not self.initialized: self.signcatch.listen() self.status.initialize(self.param) self.crn = Crn(self.param) self.param.lock() self.statistic = Statistic(self.crn, self.writer, self.param, self.status, self.comment) self.statistic.startwriter() self.initialized = True LOGGER.info("System fully initialized") else: raise InternalError( "Attempt to initialize already initialized system!")
def __init__( self, param: Param, ): """Create a System from parameters 'param'. @param param: parameters @type param: Param """ LOGGER.debug("Creating the system.") np.seterr(divide="ignore", invalid="ignore") self.initialized = False """Initialized flag""" self.param: Param = param """run parameters""" MPI_STATUS.init(self.param.timeformat) self.output: Output = Output(self.param) """Access to output files and folders""" self.writer: ResultWriter = ResultWriter(self.output.h5file, self.param.maxstrlen, self.param.lengrow) """Writer to HDF5 file.""" self.writer.init_log(self.param.maxlog) LOGGER.setlevel(self.param.loglevel) LOGGER.connect(self.output.logfile) LOGGER.setsaver(self.writer) LOGGER.timeformat = self.param.timeformat self.signcatch: SignalCatcher = SignalCatcher() """Signal catcher for dealing with system interruptions""" self.status: RunStatus = RunStatus() """Interface to run status""" self.comment = self.param.comment """run comment""" MPI_GATE.init(taginit=100, sleeptime=param.sleeptime) LOGGER.info("System created") self.crn: Crn """Chemical Reaction Network""" self.statistic: Statistic """interface to run statistics"""
def end(self, the_end: Finished) -> None: """Write ending message to hdf5. @param the_end: ending message @type the_end: Finished """ self.writer.add_end(the_end, LOGGER.runtime) if isinstance(the_end, HappyEnding): LOGGER.info(str(the_end)) elif isinstance(the_end, BadEnding): LOGGER.error(str(the_end)) else: LOGGER.warning(str(the_end))
def _create_descriptors(self) -> None: """Create the descriptors.""" for rel in self.param.relations: try: self.parameters.add_relation(rel, self.rulepath[rel]) except KeyError: LOGGER.error(f"relation '{rel}' not found in {self.modelparam}") for catname in self.param.categories: try: self.descriptor.add_cat(catname, self.rulepath[catname]) except KeyError: LOGGER.error(f"category '{catname}' not found in {self.modelparam}") for propname in self.param.properties: try: self.descriptor.add_prop(propname, self.rulepath[propname]) except KeyError: LOGGER.error(f"'[property '{propname}' not found in {self.modelparam}")
def logstat(self) -> None: """Log (at INFO level) run information.""" LOGGER.info( f"#{self.step}'{self.dstep}'': {self.time} -> {self.tnext} <{int(self.memuse)}Mb>" )