Source code for model

"""Factory for creating custom epidemic network-model classes at runtime.

This module provides :func:`create_custom_model`, which dynamically builds a
Python class that combines a user-supplied model definition (states,
transitions, parameters, propensity function) with a chosen simulation engine
(default: :class:`~models.engine_seirspluslike.SeirsPlusLikeEngine`).
"""

from models.engine_seirspluslike import SeirsPlusLikeEngine


[docs] def not_implemented_yet(): """Placeholder that raises NotImplementedError. Used as a sentinel default for ``calc_propensities`` when none is supplied to :func:`create_custom_model`. Raises: NotImplementedError: Always. """ raise NotImplementedError()
[docs] def create_custom_model(clsname, states, state_str_dict, transitions, final_states=[], invisible_states=[], unstable_states=[], init_arguments={}, model_parameters={}, calc_propensities=not_implemented_yet, member_functions=None, engine=SeirsPlusLikeEngine): """Dynamically create an epidemic model class from a model definition. Builds and returns a new Python class named *clsname* that inherits from *engine* and is populated with the supplied model metadata, parameters, and optional member functions. The generated class receives an ``__init__`` that accepts the contact graph ``G`` and keyword arguments corresponding to all declared parameters. Args: clsname (str): Name of the class to create. states (list): Ordered list of integer state codes. state_str_dict (dict): Mapping from state code (int) to label (str). transitions (list): List of ``(from_state, to_state)`` tuples that describe all allowed state transitions. final_states (list, optional): States from which no further transition occurs (absorbing states). Defaults to ``[]``. invisible_states (list, optional): States whose members are excluded from the active population count (e.g. deceased). Defaults to ``[]``. unstable_states (list, optional): States that can still change; used to decide when to terminate a simulation run. Defaults to ``[]``, which is treated as *all* states. init_arguments (dict, optional): Fixed constructor arguments as ``{name: (default, description)}``. Defaults to ``{}``. model_parameters (dict, optional): Per-node model parameters as ``{name: (default, description)}``. Values may be scalars or arrays of length ``num_nodes``. Defaults to ``{}``. calc_propensities (callable, optional): Function with signature ``calc_propensities(model)`` that returns a list of per-node propensity arrays. Used by SEIRS-Plus-Like derived engines. Defaults to :func:`not_implemented_yet`. member_functions (dict, optional): Additional methods to attach to the generated class, as ``{method_name: function}``. Defaults to ``None``. engine (type, optional): Base engine class. Defaults to :class:`~models.engine_seirspluslike.SeirsPlusLikeEngine`. Returns: type: A new class inheriting from *engine* with all model metadata set as class variables and a suitable ``__init__`` attached. """ # dictionary of future class variables attributes = { "states": states, "num_states": len(states), "state_str_dict": state_str_dict, "transitions": transitions, "num_transitions": len(transitions), "final_states": final_states, "invisible_states": invisible_states, "unstable_states": unstable_states or states, "fixed_model_parameters": init_arguments, "model_parameters": model_parameters, "common_arguments": {"random_seed": (None, "random seed value")} } if calc_propensities is None: calc_propensities = not_implemented_yet else: #model_cls.calc_propensities = calc_propensities attributes["calc_propensities"] = calc_propensities if member_functions is not None: assert type(member_functions) == dict attributes.update(member_functions) model_cls = type(clsname, (engine,), attributes) doc_text = """ A class to simulate the Stochastic Network Model Params: G Network adjacency matrix (numpy array) or Networkx graph object \n""" for argname in ("fixed_model_parameters", "model_parameters", "common_arguments"): for param, definition in attributes[argname].items(): param_text = f" {param} {definition[1]}\n" if argname == "model_parameters": param_text += f" (float or np.array)\n" doc_text = doc_text + param_text model_cls.__doc__ = doc_text # __init__ method def init_function(self, G, **kwargs): """Initialise the model on contact graph *G*. Args: G: Contact network. Accepted types: ``numpy.ndarray``, ``scipy.sparse.csr_matrix``, or ``networkx.Graph``. **kwargs: Keyword arguments that override any default declared in ``fixed_model_parameters``, ``model_parameters``, or ``common_arguments``. State initial counts can be supplied as ``init_<STATE_LABEL>=<count>``. """ # 1. set member variables acording to init arguments # definition is couple (default value, description) self.G = G self.graph = G self.A = None self.init_kwargs = kwargs # 2. model initialization self.inicialization() # 3. time and history setup self.setup_series_and_time_keeping() # 4. init states and their counts # print(self.init_state_counts) self.states_and_counts_init() # 5. set callback to None self.periodic_update_callback = None # add __init__ model_cls.__init__ = init_function # # add member functions # function_list = [inicialization, # update_graph, # node_degrees, # setup_series_and_time_keeping, # states_and_counts_init, # set_periodic_update, # update_scenario_flags, # num_contacts, # current_state_count, # current_N, # run_iteration, # run, # finalize_data_series, # increase_data_series_length] # for function in function_list: # setattr(model_cls, function.__name__, function) # def not_implemented_yet(self): # raise NotImplementedError return model_cls