# -*- 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"]
import logging
import os
import pickle
import torch
from macrostat.core.behavior import Behavior
from macrostat.core.parameters import Parameters
from macrostat.core.scenarios import Scenarios
from macrostat.core.variables import Variables
logger = logging.getLogger(__name__)
[docs]
class Model:
"""A general class to represent a macroeconomic model.
This class provides a wrapper for users to write their underlying model
behavior while maintaining a uniformly accessible interface.
Attributes
----------
parameters : macrostat.core.parameters.Parameters
The parameters of the model.
scenarios : macrostat.core.scenarios.Scenarios
The scenarios of the model.
variables : macrostat.core.variables.Variables
The variables of the model.
behavior : macrostat.core.behavior.Behavior
The behavior class of the model.
name : str
The name of the model.
Example
-------
A general workflow for a model might look like:
>>> model = Model()
>>> output = model.simulate()
>>> model.save()
"""
[docs]
def __init__(
self,
parameters: Parameters | dict | None = None,
hyperparameters: dict | None = None,
scenarios: Scenarios | dict = None,
variables: Variables | dict = None,
behavior: Behavior = Behavior,
name: str = "model",
log_level: int = logging.INFO,
log_file: str = "macrostat_model.log",
):
"""Initialization of the model class.
Parameters
----------
parameters: macrostat.core.parameters.Parameters | dict
The parameters of the model.
hyperparameters: dict (optional)
The hyperparameters of the model.
scenarios: macrostat.core.scenarios.Scenarios | dict (optional)
The scenarios of the model.
variables: macrostat.core.variables.Variables | dict (optional)
The variables of the model.
behavior: macrostat.core.behavior.Behavior (optional)
The behavior of the model.
name: str (optional)
The name of the model.
log_level: int (optional)
The log level, defaults to logging.INFO but can be set to logging.DEBUG
for more verbose output.
log_file: str (optional)
The log file, defaults to "macrostat_model.log" in the current working
directory.
"""
# Essential attributes
if isinstance(parameters, dict):
self.parameters = Parameters(
parameters=parameters, hyperparameters=hyperparameters
)
elif isinstance(parameters, Parameters):
self.parameters = parameters
if hyperparameters is not None:
self.parameters.hyper.update(hyperparameters)
else:
logger.warning("No parameters provided, using default parameters")
self.parameters = Parameters()
if isinstance(scenarios, Scenarios):
self.scenarios = scenarios
else:
logger.warning("No scenarios provided, using default scenarios")
self.scenarios = Scenarios(parameters=self.parameters)
if isinstance(variables, Variables):
self.variables = variables
else:
logger.warning("No variables provided, using default variables")
self.variables = Variables(parameters=self.parameters)
if behavior is not None and issubclass(behavior, Behavior):
self.behavior = behavior
else:
logger.warning("No behavior provided, using default behavior")
self.behavior = Behavior
self.name = name
logging.basicConfig(level=log_level, filename=log_file)
[docs]
@classmethod
def from_json(
cls,
parameter_file: str,
scenario_file: str,
variable_file: str,
*args,
**kwargs,
):
"""Initialize the model from a JSON file."""
parameters = Parameters.from_json(parameter_file)
scenarios = Scenarios.from_json(scenario_file, parameters=parameters)
variables = Variables.from_json(variable_file, parameters=parameters)
return cls(parameters=parameters, scenarios=scenarios, variables=variables)
[docs]
@classmethod
def load(cls, path: os.PathLike):
"""Class method to load a model instance from a pickled file.
Parameters
----------
path: os.PathLike
path to the targeted file containing the model.
Notes
-----
.. note:: This implementation is dependent on your pickling version
"""
with open(path, "rb") as f:
model = pickle.load(f)
return model
[docs]
def save(self, path: os.PathLike):
"""Save the model object as a pickled file
Parameters
----------
path: os.PathLike
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
"""
with open(path, "wb") as f:
pickle.dump(self, f)
[docs]
def simulate(self, scenario: int | str = 0, *args, **kwargs):
"""Simulate the model.
Parameters
----------
scenario: int (optional)
The scenario to use for the model run, defaults to 0, which
represents the default scenario (no shocks).
"""
if isinstance(scenario, str):
scenario = self.scenarios.get_scenario_index(scenario)
logging.info(f"Starting simulation. Scenario: {scenario}")
behavior = self.behavior(
self.parameters,
self.scenarios,
self.variables,
scenario=scenario,
*args,
**kwargs,
)
with torch.no_grad():
return behavior.forward(*args, **kwargs)
[docs]
def to_json(self, file_path: os.PathLike, *args, **kwargs):
"""Convert the model to a JSON file split into parameters, scenarios,
and variables.
Parameters
----------
file_path: os.PathLike
The path to the file to save the model to.
"""
self.parameters.to_json(f"{file_path}_params.json")
self.scenarios.to_json(f"{file_path}_scenarios.json")
self.variables.to_json(f"{file_path}_variables.json")