Source code for mira.modeling

"""Modeling module for MIRA.

The top level contains the Model class, toghether with the Variable,
Transition, and ModelParameter classes, used to represent a Model.
"""
__all__ = ["Model", "Transition", "Variable", "ModelParameter"]

import logging
from typing import Dict, Hashable, Mapping, Optional

from mira.metamodel import *

logger = logging.getLogger(__name__)


# TODO: Consider using dataclasses


[docs]class Transition: """A transition between two concepts, with a rate law. Attributes ---------- key : tuple[str] A tuple of the form (consumed, produced, control, rate) consumed : tuple[Variable] The variables consumed by the transition produced : tuple[Variable] The variables produced by the transition control : tuple[Variable] The variables that control the transition """ def __init__( self, key, consumed, produced, control, rate, template_type, template: Template, ): self.key = key self.consumed = consumed self.produced = produced self.control = control self.rate = rate self.template_type = template_type self.template = template
[docs]class Variable: """A variable representation of a concept in Model Attributes ---------- key : tuple[str, ...] A tuple of strings representing the concept name, grounding, and context data : dict A dictionary of data about the variable concept : Concept The concept associated with the variable """ def __init__(self, key, data, concept: Concept): self.key = key self.data = data self.concept = concept
[docs]class ModelParameter: """A parameter for a model. Attributes ---------- key : tuple[str, ...] A tuple of strings representing the transition key and the parameter type display_name : str The display name of the parameter. (optional) description : str A description of the parameter. (optional) value : float The value of the parameter. (optional) distribution : str The distribution of the parameter. (optional) placeholder : bool Whether the parameter is a placeholder. (optional) concept : Concept The concept associated with the parameter. (optional) """ def __init__(self, key, display_name=None, description=None, value=None, distribution=None, placeholder=None, concept=None): self.key = key self.display_name = display_name self.description = description self.value = value self.distribution = distribution self.placeholder = placeholder self.concept = concept
class ModelObservable: """An observable for a model. Attributes ---------- observable : Observable The observable parameters : tuple[str, ...] The parameters of the observable """ def __init__(self, observable, parameters): self.observable = observable self.parameters = parameters def get_transition_key(concept_keys, action): return concept_keys + (action,) def get_parameter_key(transition_key, action): return transition_key + (action,) UNHANDLED_TYPES = set()
[docs]class Model: """A model representation generated from a template model. Attributes ---------- template_model : TemplateModel The template model used to generate the model variables : dict[Hashable, Variable] A dictionary mapping from variable keys to variables parameters : dict[Hashable, ModelParameter] A dictionary mapping from parameter keys to parameters transitions : dict[Hashable, Transition] A dictionary mapping from transition keys to transitions observables : dict[Hashable, ModelObservable] A dictionary mapping from observable keys to observables """ def __init__(self, template_model: TemplateModel): """ Parameters ---------- template_model : A template model to generate a model from """ self.template_model = template_model self.variables: Dict[Hashable, Variable] = {} self.parameters: Dict[Hashable, ModelParameter] = {} self.transitions: Dict[Hashable, Transition] = {} self.observables: Dict[Hashable, ModelObservable] = {} self.make_model()
[docs] def assemble_variable( self, concept: Concept, initials: Optional[Mapping[str, Initial]] = None, ) -> Variable: """Assemble a variable from a concept and optional dictionary of initial values. Parameters ---------- concept : The concept for the variable initials : A dictionary mapping from names of concepts to objects representing their initial values Returns ------- : A variable object, representing a concept and its initial value """ grounding_key = sorted( ("identity", f"{k}:{v}") for k, v in concept.get_included_identifiers().items() ) context_key = sorted(concept.context.items()) key = [concept.name] + grounding_key + context_key key = tuple(key) if len(key) > 1 else key[0] if key in self.variables: return self.variables[key] # We don't assume that the initial dict key is the same as the # name of the given concept the initial applies to, so we check # concept name match instead of key match. initial_expr = None if initials: for k, v in initials.items(): if v.concept.name == concept.name: initial_expr = v.expression data = { 'name': concept.name, 'identifiers': grounding_key, 'context': context_key, 'expression': initial_expr } var = Variable(key, data=data, concept=concept) self.variables[key] = var return var
[docs] def assemble_parameter(self, template: Template, tkey) -> ModelParameter: """Assemble a parameter from a template and a transition key. Parameters ---------- template : The template to assemble a parameter from tkey : tuple[str, ...] The transition key to assemble a parameter from Returns ------- : A model parameter """ rate_parameters = sorted( self.template_model.get_parameters_from_rate_law(template.rate_law)) if rate_parameters: model_parameters = [] for key in rate_parameters: param = self.template_model.parameters[key] model_parameters.append( self.get_create_parameter( ModelParameter(key, value=param.value, distribution=param.distribution, description=param.description, display_name=param.display_name, placeholder=False, concept=param))) if len(model_parameters) == 1: return model_parameters[0] # add in an implied mass action parameter, applicable # both if there are no rate parameters or if there are # more than one value = None key = get_parameter_key(tkey, 'rate') return self.get_create_parameter(ModelParameter(key, value, placeholder=True))
[docs] def make_model(self): """Initialize the model""" for name, observable in self.template_model.observables.items(): params = sorted( observable.get_parameter_names(self.template_model.parameters)) self.observables[observable.name] = \ ModelObservable(observable, params) for key in params: value = self.template_model.parameters[key].value distribution = self.template_model.parameters[key].distribution display_name = self.template_model.parameters[key].display_name description = self.template_model.parameters[key].description self.get_create_parameter( ModelParameter(key, display_name=display_name, description=description, distribution=distribution, value=value, concept=self.template_model.parameters[key], placeholder=False)) for idx, template in enumerate(self.template_model.templates): if isinstance(template, StaticConcept): self.assemble_variable(template.subject, self.template_model.initials) continue # Handle subjects if has_subject(template): s = self.assemble_variable(template.subject, self.template_model.initials) if is_replication(template): produced, produced_key = (s,), s.key consumed, consumed_key = tuple(), None else: consumed, consumed_key = (s,), s.key else: consumed, consumed_key = tuple(), None # Handle controllers if num_controllers(template) == 1: c = self.assemble_variable(template.controller, self.template_model.initials) if is_replication(template): s = self.assemble_variable(template.subject, self.template_model.initials) control = (c, s) control_key = (c.key, s.key) else: control = (c,) control_key = c.key elif num_controllers(template) > 1: control = tuple( self.assemble_variable(controller, self.template_model.initials) for controller in template.controllers ) control_key = tuple(c.key for c in control) elif is_replication(template): s = self.assemble_variable(template.subject, self.template_model.initials) control, control_key = (s,), s.key else: control = tuple() control_key = None # Handle outcomes if has_outcome(template): o = self.assemble_variable(template.outcome, self.template_model.initials) produced, produced_key = (o,), o.key elif not is_replication(template): produced, produced_key = tuple(), None tkey_elements = tuple( element for element in [consumed_key, produced_key, control_key, str(idx)] if element is not None ) tkey = get_transition_key(tkey_elements, template.type) p = self.assemble_parameter(template, tkey) self.get_create_transition(Transition( tkey, consumed=consumed, produced=produced, control=control, rate=p, template_type=template.type, template=template, )) for key, parameter in self.template_model.parameters.items(): if key not in self.parameters: value = self.template_model.parameters[key].value distribution = self.template_model.parameters[key].distribution display_name = self.template_model.parameters[key].display_name description = self.template_model.parameters[key].description self.get_create_parameter( ModelParameter(key, display_name=display_name, description=description, distribution=distribution, value=value, concept=self.template_model.parameters[key], placeholder=False))
def get_create_parameter(self, parameter: ModelParameter) -> ModelParameter: if parameter.key not in self.parameters: self.parameters[parameter.key] = parameter return self.parameters[parameter.key] def get_create_transition(self, transition: Transition) -> Transition: if transition.key not in self.transitions: self.transitions[transition.key] = transition return self.transitions[transition.key]