"""Runnable model."""
import inspect
import warnings
from typing import Union
import sciunit.capabilities as cap
from .backends import Backend, available_backends
from .base import Model
[docs]class RunnableModel(Model, cap.Runnable):
"""A model which can be run to produce simulation results."""
[docs] def __init__(
self,
name, # Name of the model
backend=None, # Backend to run the models
attrs=None, # Optional dictionary of model attributes
):
super(RunnableModel, self).__init__(name=name)
self.skip_run = False # Backend can use this to skip simulation
self.run_params = {} # Should be reset between tests
self.print_run_params = False # Print the run parameters with each run
self.default_run_params = {} # Should be applied to all tests
if attrs and not isinstance(attrs, dict):
raise TypeError("Model 'attrs' must be a dictionary.")
self.attrs = attrs if attrs else {}
self.set_backend(backend)
self.use_default_run_params()
[docs] def get_backend(self):
"""Return the simulation backend."""
return self._backend
[docs] def set_backend(self, backend: Union[str, tuple, list, None]):
"""Set the simulation backend.
Args:
backend (Union[str, tuple, list, None]): One or more name(s) of backend(s).
The name of backend should be registered before using.
Raises:
TypeError: Backend must be string, tuple, list, or None
Exception: The backend was not found.
"""
if inspect.isclass(backend) and Backend in backend.__bases__:
name = backend.__name__
args = []
kwargs = {}
available_backends[name] = backend
elif isinstance(backend, str):
name = backend if len(backend) else None
args = []
kwargs = {}
elif isinstance(backend, (tuple, list)):
name = ""
args = []
kwargs = {}
for i in range(len(backend)):
if i == 0:
name = backend[i]
else:
if isinstance(backend[i], dict):
kwargs.update(backend[i])
else:
args += backend[i]
elif backend is None:
name = None
args = []
kwargs = {}
else:
raise TypeError(
"The backend must be of type Backend, string, tuple, list, or None"
)
if name in available_backends:
self.backend = name
self._backend = available_backends[name]()
elif name is None:
# The base class should not be called.
warnings.warn(
"The `None` backend was selected and will have limited functionality"
)
else:
raise Exception("Backend %s not found in backends.py" % name)
self._backend.model = self
self._backend.init_backend(*args, **kwargs)
[docs] def run(self, **run_params) -> None:
"""Run the simulation (or lookup the results in the cache)."""
self.use_default_run_params()
self.set_run_params(**run_params)
if self.print_run_params:
print("Run Params:", self.run_params)
self.results = self._backend.backend_run()
[docs] def set_attrs(self, **attrs) -> None:
"""Set model attributes, e.g. input resistance of a cell."""
self.attrs.update(attrs)
self._backend.set_attrs(**attrs)
[docs] def set_run_params(self, **run_params) -> None:
"""Set run-time parameters, e.g. the somatic current to inject."""
self.run_params.update(run_params)
self.check_run_params()
self._backend.set_run_params(**run_params)
[docs] def check_run_params(self) -> None:
"""Check if run parameters are reasonable for this model class.
Raise a sciunit.BadParameterValueError if any of them are not.
"""
[docs] def reset_run_params(self) -> None:
self.run_params = {}
[docs] def set_default_run_params(self, **params) -> None:
self.default_run_params.update(params)
[docs] def use_default_run_params(self) -> None:
for key, value in self.default_run_params.items():
if key not in self.run_params:
self.set_run_params(**{key: value})
[docs] def reset_default_run_params(self) -> None:
self.default_run_params = {}
@property
def state(self):
return self._state(keys=["name", "url", "attrs", "run_params", "backend"])
[docs] def __del__(self) -> None:
if hasattr(self, "temp_dir"):
self.temp_dir.cleanup() # Delete the temporary directory
s = super(RunnableModel, self)
if hasattr(s, "__del__"):
s.__del__()