Source code for emat.model.core_python.core_python_api

# -*- coding: utf-8 -*-
import yaml
import os
import time
import inspect
import pandas

from typing import Union, Mapping, Callable, Collection
from ...workbench.em_framework import Model as WorkbenchModel

from ...scope.scope import Scope
from ...database.database import Database
from ...model.core_model import AbstractCoreModel
from ...model.core_python.core_python_examples import Dummy

from ...util.docstrings import copydoc


# def filter_dict(dict_to_filter, thing_with_kwargs):
#     sig = inspect.signature(thing_with_kwargs)
#     filter_keys = [param.name for param in sig.parameters.values() if param.kind == param.POSITIONAL_OR_KEYWORD]
#     filtered_dict = {filter_key:dict_to_filter[filter_key] for filter_key in filter_keys}
#     return filtered_dict



[docs]class PythonCoreModel(AbstractCoreModel, WorkbenchModel): """ An EMAT interface for a core model that is a Python function. Args: function (callable): The Python function to be evaluated. This function must accept keyword arguments that include all of the uncertainties and levers, and return a dict that contains key-value pairs that map the names of performance measures to the computed performance measure outputs. configuration (str, dict, or None): The configuration for this core model. This can be passed as a dict, or as a str which gives the filename of a yaml file that will be loaded. A core model that is a stand-alone Python function will not often not require any configuration. scope (Scope or str): The Scope for this exploratory analysis. Can be given as an explicit Scope object, or as a str which gives the filename of a yaml file that will be loaded. safe (bool): Load the configuration yaml file in 'safe' mode. This can be disabled if the configuration requires custom Python types or is otherwise not compatible with safe mode. Loading configuration files with safe mode off is not secure and should not be done with files from untrusted sources. db (Database): An optional default Database to store experiments and results. name (str): A name for this model, given as an alphanumeric string. The name is required by ema_workbench operations. If not given, the name of the function is extracted, or failing that, "EMAT" is used. metamodel_id: An identifier for this model, if it is a meta-model. Defaults to 0 (i.e., not a meta-model). """ xl_di = {} def __init__(self, function:Callable, configuration:Union[str,Mapping,None]=None, scope:Union[Scope,str]=None, safe:bool=True, db:Database=None, name:str='EMAT', metamodel_id=None, ): if scope is None: raise ValueError('must give scope') if name == 'EMAT': try: _name = self.function.__name__ except: pass else: if _name.isalnum(): name = _name elif _name.replace("_","").replace(" ","").isalnum(): name = _name.replace("_","").replace(" ","") AbstractCoreModel.__init__(self, configuration, scope, safe, db, metamodel_id=metamodel_id) self.archive_path = self.config.get('archive_path', None) if self.archive_path is not None: os.makedirs(self.archive_path, exist_ok=True) # If no archive path is given, a temporary directory is created. # All archive files will be lost when this CoreDummy is deleted. if self.archive_path is None: import tempfile self._temp_archive = tempfile.TemporaryDirectory() self.archive_path = self._temp_archive.name WorkbenchModel.__init__(self, name, function) def __repr__(self): content = [] if len(self.scope._c_list): content.append(f"{len(self.scope._c_list)} constants") if len(self.scope._x_list): content.append(f"{len(self.scope._x_list)} uncertainties") if len(self.scope._l_list): content.append(f"{len(self.scope._l_list)} levers") if len(self.scope._m_list): content.append(f"{len(self.scope._m_list)} measures") metamodel_tag = "" if (self.metamodel_id==0 or self.metamodel_id is None) else f", metamodel_id={self.metamodel_id}" return f'<emat.PythonCoreModel "{self.name}"{metamodel_tag} with {", ".join(content)}>' @copydoc(AbstractCoreModel.setup) def setup(self, params): self.xl_di = params @copydoc(AbstractCoreModel.get_experiment_archive_path) def get_experiment_archive_path(self, experiment_id=None, makedirs=False, parameters=None): ''' Path is defined with scope name and experiment id ''' if experiment_id is None: if parameters is None: raise ValueError("must give `experiment_id` or `parameters`") db = getattr(self, 'db', None) if db is not None: experiment_id = db.get_experiment_id(self.scope.name, parameters) mod_results_path = os.path.join( self.archive_path, f"scp_{self.scope.name}", f"exp_{experiment_id}" ) if makedirs: os.makedirs(mod_results_path, exist_ok=True) return mod_results_path @copydoc(AbstractCoreModel.run) def run(self): self.outcomes_output = self.function(**self.xl_di) @copydoc(AbstractCoreModel.post_process) def post_process(self, params, measure_names, output_path=None): """Not used for PythonCoreModel objects.""" @copydoc(AbstractCoreModel.load_measures) def load_measures( self, measure_names: Collection[str]=None, *, rel_output_path=None, abs_output_path=None, ): result = self.outcomes_output if measure_names is None: return result.copy() pm_dict = {} for pm in measure_names: if pm not in result.keys(): raise KeyError('Measure {0} not supported'.format(pm)) pm_dict[pm] = result[pm] return pm_dict @copydoc(AbstractCoreModel.archive) def archive(self, params, model_results_path=None, experiment_id:int=0): """archive only records experiment values""" if not os.path.exists(model_results_path): os.makedirs(model_results_path) # record experiment definitions xl_df = pandas.DataFrame(params, index=[experiment_id]) xl_df.to_csv(model_results_path + r'_def.csv')
[docs] def run_experiment(self, experiment): """ Running a single instantiated model experiment. The results are passed through the performance measure processing steps to generate results. Args: experiment (dict-like) Returns: dict """ self.outcomes_output = super().run_experiment(experiment) return self.outcomes_output
def __getattr__(self, item): """ Pass through getattr to the function. """ try: f = object.__getattribute__(self, 'function') if hasattr(f, item): return getattr(f, item) except: pass raise AttributeError(item)