# -*- coding: utf-8 -*-"""Generic model class as a wrapper to specific implementations"""__author__=["Karl Naumann-Woleske"]__credits__=["Karl Naumann-Woleske"]__license__="MIT"__version__="0.1.0"__maintainer__=["Karl Naumann-Woleske"]importloggingimportnumpyasnpimportpandasaspdimportpicklelogger=logging.getLogger(__name__)
[docs]classModel:"""A class representing a macroeconomic model. This class provides a wrapper for users to write their underlying model behavior while maintaining a uniformly accessible interface. Specifically, the user is expected to adapt the model.simulate() function to their needs, respecting only that the return of that function is a pandas dataframe. Attributes ---------- name: str Name of the model, such as "model". Used for file and database names parameters : dict Dictionary of all parameters hyper_parameters : dict Dictionary of all hyperparameters output : pd.DataFrame None, or the latest simulation run for given parameters Example ------- A general workflow for a model might look like >>> model = Model(parameters, hyper_parameters) >>> output = model.simulate() >>> model.save() """def__init__(self,parameters:dict,hyper_parameters:dict,name:str="model",):"""Initialization of the model class. If SQL is true, will check for the existing model database or create a new one if none is found. Parameters ---------- parameters : dict dictionary of the named parameters of the model hyper_parameters : dict dictionary of hyper-parameters related to the model name : str (default 'model') name of the model (for use in filenaming) """# Essential attributesself.parameters=parametersself._validate_parameters()self.hyper_parameters=hyper_parametersself.name=name# Attributes generated later onself.output=None
[docs]defsimulate(self,*args,**kwargs)->pd.DataFrame:"""Simulate a model run using the stored parameters This function is designed to be overwritten by the user's specific implementation of their model. Note that it is expected for the user to set the ''self.output'' attribute to the output generated. Returns ------- output : pd.DataFrame Output of the model. Generically it should have a "time"-like index and variables across the columns """raiseNotImplementedError
[docs]defsave(self,path=None):"""Save the model object as a pickled file Parameters ---------- path, optional path where the model will be stored. If it is None then the model's name will be used and the file stored in the working directory. Notes ----- .. note:: This implementation is dependent on your pickling version """ifpathisNone:path=f"{self.name}.pkl"withopen(path,"wb")asf:pickle.dump(self,f)
[docs]@classmethoddefload(cls,path=None):"""Class method to load a model instance from a pickled file. Parameters ---------- path, optional path to the targeted Sampler Notes ----- .. note:: This implementation is dependent on your pickling version """withopen(path,"rb")asf:model=pickle.load(f)returnmodel
def__eq__(self,other):"""Check if two models are equivalent in their core attributes"""ifself.parameters.keys()!=other.parameters.keys():returnFalseelse:parameter_equivalence=[np.allclose(v,other.parameters[k])fork,vinself.parameters.items()]ifself.hyper_parameters.keys()!=other.hyper_parameters.keys():returnFalseelse:hyper_parameter_equivalence=[]fork,vinself.hyper_parameters.items():try:ifnp.issubdtype(np.array(v).dtype,np.number):hyper_parameter_equivalence.append(np.allclose(v,other.hyper_parameters[k]))else:hyper_parameter_equivalence.append(v==other.hyper_parameters[k])exceptException:hyper_parameter_equivalence.append(False)ifall([self.outputisnotNone,other.outputisnotNone]):output_equivalence=np.allclose(self.output,other.output)else:output_equivalence=all([self.outputisNone,other.outputisNone])conditions=[all(parameter_equivalence),all(hyper_parameter_equivalence),self.name==other.name,output_equivalence,]returnall(conditions)def_validate_parameters(self):"""Validate whether all of the parameters are numeric. We assume that the parameter dictionary may contain numeric values, whether these are float/int or arrays. We verify this by checking whether the cast to array of a given """condition={}fork,vinself.parameters.items():condition[k]=np.issubdtype(np.array(v).dtype,np.number)ifnotall(condition.values()):fails=[kfork,vincondition.items()ifnotv]raiseValueError(f"Parameters are not all numeric: {fails}")