def init(self) -> SimPhase: ''' Initialize this simulation with the cell cluster seeded by a prior call to the :meth:`seed` method and cache this initialization to an output file, specified by the current configuration file. This method *must* be called prior to the :meth:`sim` and :meth:`plot_init` methods, which consume this output as input. Returns ---------- SimPhase High-level simulation phase instance encapsulating all objects internally created by this method to run this phase. ''' # Log this attempt. logs.log_info('Initializing simulation...') # Simulation phase type. phase_kind = SimPhaseKind.INIT if not files.is_file(self._p.seed_pickle_filename): if not self._p.autoInit: #FIXME: Call the _die_unless_file_pickled() function instead #both here and everywhere else we perform similar logic. Bugbears! raise BetseSimException( 'Initialization halted due to missing seed. ' 'Please run "betse seed" and try again.') # Create an instance of world. logs.log_info('Automatically seeding cell cluster...') self.seed() logs.log_info('Now using seed to run initialization.') # Load the seed from cache. cells, p_old = fh.loadWorld(self._p.seed_pickle_filename) logs.log_info('Cell cluster loaded.') # check to ensure compatibility between original and present sim files: self._die_if_seed_differs(p_old, self._p) # Simulation phase, created *AFTER* unpickling these objects above. phase = SimPhase( kind=phase_kind, cells=cells, p=self._p, callbacks=self._callbacks, ) # Initialize core simulation data structures. phase.sim.init_core(phase) # Run this simulation phase. phase.sim.sim_info_report(phase) phase.sim.run_sim_core(phase) # Return this phase. return phase
def sim(self) -> SimPhase: ''' Simulate this simulation with the cell cluster initialized by a prior call to the :meth:`init` method and cache this simulation to an output file, specified by the current configuration file. This method *must* be called prior to the :meth:`:meth:`plot_sim` method, which consumes this output as input. Returns ---------- SimPhase High-level simulation phase instance encapsulating all objects internally created by this method to run this phase. ''' # Log this attempt. logs.log_info('Running simulation...') # Simulation phase type. phase_kind = SimPhaseKind.SIM if not files.is_file(self._p.init_pickle_filename): if not self._p.autoInit: raise BetseSimException( 'Simulation halted due to missing initialization. ' 'Please run "betse init" and try again.') logs.log_info('Automatically initializing cell cluster...') self.init() logs.log_info('Now using initialization to run simulation.') # Load the initialization from cache. sim, cells, p_old = fh.loadSim(self._p.init_pickle_filename) # Ensure compatibility between original and present config files. self._die_if_seed_differs(p_old, self._p) # Simulation phase, created *AFTER* unpickling these objects above. phase = SimPhase( kind=phase_kind, cells=cells, p=self._p, sim=sim, callbacks=self._callbacks, ) # Run and save the simulation to the cache. sim.sim_info_report(phase) sim.run_sim_core(phase) # Return this phase. return phase
def is_dll(pathname: str) -> bool: ''' ``True`` only if the passed path is that of an existing shared library specific to the current platform (e.g., suffixed by ``.so`` on Linux). This function returns ``False`` if either: * This path is *not* that of an existing file. * This pathname has no filetype. * This pathname's filetype is *not* that of a shared library supported by the current platform. Parameters ---------- pathname : str Absolute or relative path of the shared library to inspect. Returns ---------- bool ``True`` only if this path is that of an existing shared library. ''' # Avoid circular import dependencies. from betse.util.path import files, pathnames from betse.util.os import kernels # Filetype of this pathname if any or None otherwise. filetype = pathnames.get_filetype_undotted_or_none(pathname) # Name of this platform's low-level kernel (e.g., "Linux", "Darwin"). kernel_name = kernels.get_name() # This path is that of an existing shared library if and only if... return ( # This pathname has a filetype. filetype is not None and # This filetype is that of a shared library for this platform. filetype in KERNEL_NAME_TO_DLL_FILETYPES[kernel_name] and # This path is that of an existing file. Since testing this requires a # filesystem lookup, this is the slowest test and thus deferred. files.is_file(pathname))
def is_conda() -> bool: ''' ``True`` only if the active Python interpreter is managed by ``conda``, the open-source, cross-platform, language-agnostic package manager provided by the Anaconda and Miniconda distributions. Specifically, this function returns ``True`` only if the ``{sys.prefix}/conda-meta/history`` file exists such that: * ``{sys.prefix}`` is the site-specific Python directory prefix. Under Linux, this is typically: * ``/usr`` for system-wide Python interpreters. * A user-specific directory for ``conda``-based Python interpreters (e.g., ``${HOME}/Miniconda3/envs/${CONDA_ENV_NAME}``). * ``conda-meta/history`` is a file relative to this prefix guaranteed to exist for *all* ``conda`` environments (including the base environment). Since this file is unlikely to be created in non-``conda`` environments, the existence of this file effectively guarantees this interpreter to be managed by ``conda``. See Also ---------- https://stackoverflow.com/a/47730405/2809027 StackOverflow answer strongly inspiring this implementation. https://github.com/conda/constructor/blob/2.0.1/constructor/install.py#L218-L234 Function in the ``conda`` codebase responsible for creating the ``{sys.prefix}/conda-meta/history`` file. ''' # Avoid circular import dependencies. from betse.util.path import files, pathnames # Absolute filename of a file relative to the site-specific Python # directory prefix guaranteed to exist for all "conda" environments. conda_history_filename = pathnames.join(sys.prefix, 'conda-meta', 'history') # Return true only if this file exists. return files.is_file(conda_history_filename)
def start_code_repl() -> None: ''' Start a REPL based on the canonical :mod:`code` module. ''' # Log this invocation, including the same helpful help line preceding the # official Python 3 REPL. logs.log_info('Starting "code"-based REPL...') logs.log_info('Type "help", "copyright", "credits" or ' '"license" for more information.') # Defer heavyweight imports. import code, readline # Absolute path of the file persisting a REPL-specific history of commands. # Note this REPL leverages a "readline"-style history file format. history_filename = appmetaone.get_app_meta().get_repl_history_filename( 'readline') readline.set_history_length(1000) # If this file exists, deserialize this REPL's history from this file. if files.is_file(history_filename): logs.log_debug('Restoring REPL history from: %s', history_filename) readline.read_history_file(history_filename) # Run this REPL. try: code.interact(banner="", local=replenv.REPL_ENV) # When this REPL halts... except SystemExit as exception: # If this REPL halted with error, reraise this exception. if cmdexit.is_failure(exception.code): raise # Else, this REPL halted without error. Silently ignore this exception. # Serialize this REPL's history back to disk regardless of whether an # exception was raised. finally: logs.log_debug('Preserving REPL history to: %s', history_filename) readline.write_history_file(history_filename)
def _do_try(self) -> object: ''' Run the ``try`` subcommand and return the result of doing so. ''' # Basename of the sample configuration file to be created. config_basename = 'sample_sim.yaml' # Relative filename of this file, relative to the current directory. self._args.conf_filename = pathnames.join( 'sample_sim', config_basename) #FIXME: Insufficient. We only want to reuse this file if this file's #version is identical to that of the default YAML configuration file's #version. Hence, this logic should (arguably) be shifted elsewhere -- #probably into "betse.science.sim_config". # If this file already exists, reuse this file. if files.is_file(self._args.conf_filename): logs.log_info( 'Reusing simulation configuration "%s".', config_basename) # Else, create this file. else: self._do_config() # Run all general-purposes phases, thus excluding network-isolated # phases (e.g., "_do_sim_grn"), in the expected order. self._do_seed() self._do_init() self._do_sim() self._do_plot_seed() self._do_plot_init() # Return the value returned by the last such phase, permitting this # subcommand to be memory profiled. While any value would technically # suffice, the value returned by the last such phase corresponds to a # complete simulation run and hence is likely to consume maximal memory. return self._do_plot_sim()
def _die_unless_file_pickled( filename: str, subcommand: str, subcommand_label: str) -> None: ''' Raise an exception containing the passed machine-readable subcommand and human-readable label denoting that subcommand unless the file with the passed filename pickled by a prior run of that subcommand exists. Parameters ---------- filename : str Absolute filename of the pickled file to be validated. subcommand : str Machine-readable case-sensitive name of the prior subcommand expected to have pickled this file (e.g., ``seed``, ``init``). subcommand_label : str Human-readable case-insensitive noun denoting the type of prior subcommand expected to have pickled this file (e.g., ``Seed``, ``initialization``). Raises ---------- BetseSimException If this file does *not* exist. ''' # If this file does *NOT* exist... if not files.is_file(filename): # Uppercase the first character of this label for readability. subcommand_label_cased = strs.uppercase_char_first(subcommand_label) # Raise an exception embedding these parameters. raise BetseSimException( '{} not previously run; ' 'please run "betse {}" and try again.\n' 'Specifically, file not found: {}'.format( subcommand_label_cased, subcommand, filename))
def _run_pyinstaller_command( self, script_basename: str, script_type: str, entry_point, ) -> None: ''' Run the currently configured PyInstaller command for the passed entry point's script wrapper. Attributes ---------- script_basename : str Basename of the executable wrapper script running this entry point. script_type : str Type of the executable wrapper script running this entry point, guaranteed to be either: * If this script is console-specific, ``console`` . * Else, ``gui``. entry_point : EntryPoint Entry point, whose attributes specify the module to be imported and function to be run by this script. ''' # Defer heavyweight imports. from betse.util.io import stderrs from betse.util.os.shell import shellstr from betse.util.path import files, pathnames # If this spec exists, instruct PyInstaller to reuse rather than # recreate this file, thus preserving edits to this file. if files.is_file(self._pyinstaller_spec_filename): print('Reusing spec file "{}".'.format( self._pyinstaller_spec_filename)) # Append the relative path of this spec file. self._pyinstaller_args.append( shellstr.shell_quote(self._pyinstaller_spec_filename)) # Freeze this script with this spec file. self._run_pyinstaller_imported() # Else, instruct PyInstaller to (re)create this spec file. else: # Absolute path of the directory containing this files. pyinstaller_spec_dirname = pathnames.get_dirname( self._pyinstaller_spec_filename) # Absolute path of the current script wrapper. script_filename = pathnames.join(self.install_dir, script_basename) files.die_unless_file( script_filename, 'File "{}" not found. {}'.format(script_filename, freeze.EXCEPTION_ADVICE)) # Inform the user of this action *AFTER* the above validation. # Since specification files should typically be reused rather # than regenerated, do so as a non-fatal warning. stderrs.output_warning('Generating spec file "{}".'.format( self._pyinstaller_spec_filename)) # Append all options specific to spec file generation. self._pyinstaller_args.extend([ # If this is a console script, configure standard input and # output for console handling; else, do *NOT* and, if the # current operating system is OS X, generate an ".app"-suffixed # application bundle rather than a customary executable. '--console' if script_type == 'console' else '--windowed', # Non-default PyInstaller directories. '--additional-hooks-dir=' + shellstr.shell_quote(self._pyinstaller_hooks_dirname), '--specpath=' + shellstr.shell_quote(pyinstaller_spec_dirname), ]) # Append all subclass-specific options. self._pyinstaller_args.extend(self._get_pyinstaller_options()) # Append the absolute path of this script. self._pyinstaller_args.append( shellstr.shell_quote(script_filename)) # Freeze this script and generate a spec file. self._run_pyinstaller_imported() # Absolute path of this file. script_spec_filename = pathnames.join(pyinstaller_spec_dirname, script_basename + '.spec') # Rename this file to have the basename expected by the prior # conditional on the next invocation of this setuptools command. # # Note that "pyinstaller" accepts an option "--name" permitting # the basename of this file to be specified prior to generating # this file. Unfortunately, this option *ALSO* specifies the # basename of the generated executable. While the former is # reliably renamable, the former is *NOT* (e.g., due to code # signing). Hence, this file is manually renamed without passing # this option. files.move_file(script_spec_filename, self._pyinstaller_spec_filename)
def sim_grn(self) -> SimPhase: ''' Initialize and simulate a pure gene regulatory network (GRN) *without* bioelectrics with the cell cluster seeded by a prior call to the :meth:`seed` method and cache this initialization and simulation to output files, specified by the current configuration file. This method *must* be called prior to the :meth:`plot_grn` method, which consumes this output as input. Returns ---------- SimPhase High-level simulation phase instance encapsulating all objects internally created by this method to run this phase. ''' # Simulation phase objects, defaulting to undefined initially. phase = None # Simulation phase type. phase_kind = SimPhaseKind.INIT # Simulator objects initialized below. cells = None sim = None # Log this simulation. logs.log_info( 'Running gene regulatory network "%s" ' 'defined in config file "%s"...', pathnames.get_basename(self._p.grn_config_filename), self._p.conf_basename) # If networking an uninitialized, unsimulated cell cluster... if self._p.grn_unpickle_phase_type is GrnUnpicklePhaseType.SEED: if not files.is_file(self._p.seed_pickle_filename): if not self._p.autoInit: raise BetseSimException( 'Simulation halted due to missing core seed. ' 'Please run "betse seed" and try again.') # Create an instance of world. logs.log_info('Automatically seeding cell cluster...') self.seed() # Load the seed from cache. cells, _ = fh.loadWorld(self._p.seed_pickle_filename) logs.log_info('Running gene regulatory network on betse seed...') logs.log_info('Now using cell cluster to run initialization.') # Simulation phase. phase = SimPhase( kind=phase_kind, callbacks=self._callbacks, cells=cells, p=self._p, ) # Initialize core simulation data structures. phase.sim.init_core(phase) phase.sim.init_dynamics(phase) #FIXME: Shift the following assignments into a new public #"Simulator" method -- say, Simulator.init_core_null(). # Initialize other aspects required for piggyback of GRN on the # sim object. phase.sim.time = [] phase.sim.vm = -50e-3 * np.ones(phase.sim.mdl) # Initialize key fields of simulator required to interface # (dummy init). phase.sim.rho_pump = 1.0 phase.sim.rho_channel = 1.0 phase.sim.conc_J_x = np.zeros(phase.sim.edl) phase.sim.conc_J_y = np.zeros(phase.sim.edl) phase.sim.J_env_x = np.zeros(phase.sim.edl) phase.sim.J_env_y = np.zeros(phase.sim.edl) phase.sim.u_env_x = np.zeros(phase.sim.edl) phase.sim.u_env_y = np.zeros(phase.sim.edl) # Else if networking an initialized but unsimulated cell cluster... elif self._p.grn_unpickle_phase_type is GrnUnpicklePhaseType.INIT: if not files.is_file(self._p.init_pickle_filename): if not self._p.autoInit: raise BetseSimException( 'Simulation halted due to missing core initialization. ' 'Please run "betse init" and try again.') logs.log_info('Automatically initializing cell cluster...') self.init() logs.log_info('Now using initialization to run simulation.') # Load the initialization from cache. logs.log_info('Running gene regulatory network on betse init...') sim, cells, _ = fh.loadSim(self._p.init_pickle_filename) # Else if networking an initialized, simulated cell cluster... elif self._p.grn_unpickle_phase_type is GrnUnpicklePhaseType.SIM: if not files.is_file(self._p.sim_pickle_filename): raise BetseSimException( 'Simulation halted due to missing core simulation. ' 'Please run "betse sim" and try again.') # Load the simulation from cache. logs.log_info('Running gene regulatory network on betse sim...') sim, cells, _ = fh.loadSim(self._p.sim_pickle_filename) # Else, this type of networking is unrecognized. Raise an exception. else: raise BetseSimConfException( 'Gene regulatory network (GRN) unpickle simulation phase ' '"{}" unrecognized.'.format(self._p.grn_unpickle_phase_type)) # If *NOT* defined above, define this simulation phase. if phase is None: phase = SimPhase( kind=phase_kind, callbacks=self._callbacks, cells=cells, p=self._p, sim=sim, ) # Reinitialize all profiles. phase.dyna.init_profiles(phase) phase.dyna.init_events(phase) # If *NOT* restarting from a prior GRN run, start a new GRN. if self._p.grn_unpickle_filename is None: # Log this start. logs.log_info("Initializing the gene regulatory network...") # Create and initialize an instance of master of metabolism. MoG = MasterOfGenes(self._p) MoG.read_gene_config(phase) # Else, restart from a prior GRN run. else: # Log this restart. logs.log_info( 'Reinitializing the gene regulatory network from "%s"...', pathnames.get_basename(self._p.grn_unpickle_filename)) # If this file does *NOT* exist, raise an exception. _die_unless_file_pickled( filename=self._p.grn_unpickle_filename, subcommand='sim-grn', subcommand_label='Gene regulatory network') # Unpickle this file into a high-level "MasterOfGenes" object. MoG, _, _ = pickles.load(self._p.grn_unpickle_filename) # If running on a sim with a cut event, perform this cut... if ( phase.dyna.event_cut is not None and phase.dyna.event_cut.is_fired ): # Log this cutting. logs.log_info( 'A cutting event has been run, ' 'so the GRN object needs to be modified...') # If no prior initialization exists, raise an exception. if not files.is_file(self._p.sim_pickle_filename): logs.log_warning( 'This situation is complex ' 'due to a cutting event being run. ' 'Please have a corresponding init file ' 'to run the GRN simulation!') raise BetseSimException( 'Simulation terminated due to missing core init. ' 'Please alter GRN settings and try again.') # Else, a prior initialization exists. # Log this initialization. logs.log_info( 'Loading betse init from cache ' 'for reference to original cells...') # Load the initialization from cache. sim_old, cells_old, _ = fh.loadSim(self._p.sim_pickle_filename) #FIXME: This phase object would ideally be pickled to and #from the "self._p.sim_pickle_filename" file loaded above, in #which case this local variable would be safely removable. # Original simulation phase. To avoid caller confusion, the # optional "callbacks" parameter is intentionally *NOT* passed. phase_old = SimPhase( kind=phase_kind, p=self._p, cells=cells_old, sim=sim_old, ) # Initialize all tissue profiles on original cells. phase_old.dyna.init_profiles(phase_old) for cut_profile_name in phase_old.p.event_cut_profile_names: logs.log_info( 'Cutting cell cluster via cut profile "%s"...', cut_profile_name) # Object picking the cells removed by this cut profile. tissue_picker = phase_old.dyna.cut_name_to_profile[ cut_profile_name].picker # One-dimensional Numpy arrays of the indices of all # cells and cell membranes to be removed. target_inds_cell, target_inds_mems = ( tissue_picker.pick_cells_and_mems( cells=cells_old, p=self._p)) MoG.core.mod_after_cut_event( phase, target_inds_cell, target_inds_mems) MoG.core.redefine_dynamic_dics(sim, cells, self._p) logs.log_info( 'Reinitializing gene regulatory network ' 'for simulation...') MoG.reinitialize(phase) # if self._p.use_microtubules: # sim.mtubes.mtubes_x = MoG.mtubes_x_time[-1] # sim.mtubes.mtubes_y = MoG.mtubes_y_time[-1] # # sim.mtubes.uxmt, sim.mtubes.uymt = sim.mtubes.mtubes_to_cell( # cells, self._p) logs.log_info('Simulating gene regulatory network...') MoG.run_core_sim(phase) # Return this phase. return phase
def get_package_project_dirname_or_none(package: ModuleType) -> StrOrNoneTypes: ''' **Absolute canonical dirname** (i.e., absolute dirname after resolving symbolic links) of the **root project directory** (i.e., top-level directory containing an installable ``pyproject.toml`` file or ``setup.py`` script) governing the passed top-level Python package if found *or* raise an exception otherwise (i.e., if this directory is *not* found). Equivalently, this is the same as both: * The root directory archived by release tarballs for this application. * The Git-based working tree for this application (i.e., the top-level directory containing this application's ``.git`` subdirectory). Specifically, this function returns non-``None`` only if the parent directory of the passed package's directory contains either: * A ``pyproject.toml`` file, implying this package to be satisfy `PEP 518`_ and hence be installable by at least poetry_. * A ``setup.py`` script, implying this package to be installable by either the standard :mod:`distutils` API or third-party :mod:`setuptools` API. :: _PEP 518: https://snarky.ca/clarifying-pep-518/ :: _poetry: https://github.com/sdispater/poetry Caveats ---------- **This directory typically does not exist.** This directory is only required during installation by non-developers *or* during development by developers. Once this application has been installed in a standard (i.e., non-editable) fashion by non-developers, this directory is no longer required and hence should *not* be assumed to exist. Parameters ---------- package : ModuleType Top-level Python package to be queried. Returns ---------- StrOrNoneTypes Either: * If the parent directory of the passed package's directory contains either a ``pyproject.toml`` or ``setup.py`` script, the absolute canonical dirname of that parent directory. * Else, ``None``. ''' # Avoid circular import dependencies. from betse.util.path import files, pathnames from betse.util.py.module import pymodule # Absolute canonical dirname of the directory providing this package, # canonicalized into a directory rather than symbolic link to increase the # likelihood of obtaining the actual parent directory of this package. package_dirname = pymodule.get_dirname_canonical(package) # Absolute dirname of the parent directory of this directory. package_project_dirname = pathnames.get_dirname(package_dirname) # Absolute filenames of the "pyproject.toml" and "setup.py" files possibly # provided by this parent directory. pyproject_filename = pathnames.join(package_project_dirname, 'pyproject.toml') setup_filename = pathnames.join(package_project_dirname, 'setup.py') # Return this parent directory's absolute dirname if either of these # requisite installation-time files exist *OR* "None" otherwise. return (package_project_dirname if (files.is_file(pyproject_filename) or files.is_file(setup_filename)) else None)