- Templates
- Observables
- Initials
- Template model operations
- Add a parameter
- Retrieve concepts
- Add a template
- Model stratification
- Model composition
Templates are stored in the templates
attribute of template model objects
templates
:List[Templates]
Users can index on the list of templates or iterate throughout the list of templates to access templates.
Example: Accessing templates through list indexing and iteration
from mira.examples.sir import sir_petrinet as sir
# access the second template using list indexing
template = sir.templates[1]
# go through all the templates in the model using a for loop
for template in sir.templates:
pass
All template objects have 3 optional attributes
rate_law
:Optional[SympyExprStr]
- The rate law that governs the template
name
:Optional[str]
- The name of the template
display_name
:Optional[str]
- An alternative name of the template
The least error-prone way to create a rate-law to be used in a template is to use the
safe_parse_expr
method while passing in a local_dict
mapping of symbols used in the rate law to
their sympy equivalent. The returned expression from safe_parse_expr
can either be used as
stand-alone expression to be used in templates or passed to the SympyExprStr
constructor.
- Documentation
safe_parse_expr(s, local_dict)
s
:str
- The string expression to be parsed
local_dict
:Dict[str, sympy.Symbol]
- The mapping of string symbols present in the rate-law to their sympy equivalent
- Return type
sympy.Expr
- The sympy expression
Creating a rate-law using safe_parse_expr
import sympy
from mira.metamodel.utils import safe_parse_expr
# Create symbols to be used as values in the local_dict mapping
beta_symbol = sympy.Symbol("beta")
mu_symbol = sympy.Symbol("mu")
# Create the string expression to be parsed and converted to a sympy expression
str_expression = "5*beta*mu"
# Define the mapping of symbols
local_dict = {"beta": beta_symbol, "mu": mu_symbol}
# Create a sympy.Expr object
expression = safe_parse_expr(str_expression, local_dict)
The expression created can now be passed into any method or constructor that requires a rate-law. We can also pass the
result of safe_parse_expr
to the SympyExprStr
constructor which accepts types str
, float
,int
,
and sympy.Expr
to convert it to a SympyExprStr
object which is a subclass of the sympy.Expr
class.
sympy.Expr
and SympyExprStr
objects can be used interchangeably.
Additionally, there will be code examples used in this document that pass in a sympy.Symbol
, sympy.Float
, or sympy.Integer
object to an argument that expects a sympy.Expr
or SympyExprStr
object. This is allowed because the three former
sympy object classes mentioned are all subclasses of sympy.Expr
.
Creating a rate-law using safe_parse_expr
and passing it into SympyExprStr
import sympy
from mira.metamodel.utils import SympyExprStr, safe_parse_expr
beta_symbol = sympy.Symbol("beta")
mu_symbol = sympy.Symbol("mu")
str_expression = "5*beta*mu"
local_dict = {"beta": beta_symbol, "mu": mu_symbol}
expression = safe_parse_expr(str_expression, local_dict)
# We now have a SympyExprStr expression that can be used interchangeably with the result from safe_parse_expr
expression = SympyExprStr(expression)
- Degradation
- These templates represent the degradation of a species
- They have the
subject
attributesubject
:Concept
- The subject that is being degraded
- Production
- These template represent the production of a species
- They have the
outcome
attributeoutcome
:Concept
- The outcome that is being produced
- Conversion
- These templates represent the conversion of one species to another
- They have both the
subject
andoutcome
attributessubject
:Concept
- The subject that is being converted
outcome
:Concept
- The result of the conversion
- Natural
- Specifies a process that occurs at a constant rate
- Controlled
- Species a process controlled by a single controller
- These templates have the
controller
attribute which represents a single compartmentcontroller
:Concept
- The controller that influence the template
- Group controlled
- Species a process controlled by several controllers
- These templates have the
controllers
attributes that represent a list of compartmentscontrollers
:List[Concept]
- The list of controllers that influence the template
We can extract all the concepts in template by using the get_concepts
method.
- Documentation
get_concepts()
- Return type
List[Union[Concept, List[Concept]]]
- A list of concepts present in the template
- Return type
Example: Return a list of all concepts in the template
from mira.examples.sir import sir_petrinet as sir
concepts_list = sir.templates[0].get_concepts()
We can get all the controllers in a template by employing the get_controllers
method.
- Documentation
get_controllers()
- Return type
List[Concept]
- A list of controllers present in the template
- Return type
Example: Get all the controllers present in a template
from mira.examples.sir import sir_petrinet as sir
controller_list = sir.templates[0].get_controllers()
We can change the rate law of a template using the template instance method set_rate_law
.
- Documentation
set_rate_law(rate_law, local_dict)
rate_law
:Union[str, sympy.Expr, SympyExprStr]
- The new rate law to use for a template
local_dict
:Optional[Dict[str,str]]
- An optional mapping of symbols to substitute into the new rate law
Example: Setting a custom rate-law for a template
import sympy
from mira.metamodel.utils import SympyExprStr,safe_parse_expr
from mira.examples.sir import sir_petrinet as sir
# Define a dictionary of symbols present in the new rate-law to sympy symbol objects
# for parsing of rate-law strings
local_dict = {"I": sympy.Symbol("I"), "beta": sympy.Symbol("beta")}
# We can just pass in the return value from safe_parse_expr as well
# The SympyExprStr constructor can take in a string, int, float, or sympy.Expr object
sir.templates[0].set_rate_law(SympyExprStr(safe_parse_expr("I*beta", local_dict)))
We can update the names of parameters in a rate law using the template instance method update_parameter_name
.
- Documentation
update_parameter_name(old_name, new_name)
old_name
:str
- The old name of the parameter
new_name
:str
- The new name of the parameter
Example: Updating the parameter name in a template's rate-law
from mira.examples.sir import sir_petrinet as sir
sir.templates[0].update_parameter_name("beta", "sigma")
Observables associated with a template model can be accessed using the
observables
attribute of a template model object.
observables
:Dict[str,Observable]
- The dictionary of observables in a template model
- While we don't have any direct methods to add, remove, or modify observables,
we can use the
observables
attribute to perform any observable specific operations.
A user might want to add a new observable to keep track of a new combination of compartment values
Users can define a key-value pair where the key represents the id of the observable and the value is a newly created observable object to add to the template model. We create a new observable object with name and expression to keep track of the total number of infected in a SIR epidemiology model.
If there already exists a key-value pair in the observables
dictionary using
the same key, then the old observable object will
be overwritten by the new one.
Example: Adding a single observable using key-based indexing
import sympy
from mira.metamodel import *
from mira.examples.sir import sir_petrinet as sir
key = "total_infections"
# Since the expression is only represented by a single symbol "I", we can pass in a
# sympy.Symbol object to the expression field when creating the observable
expression = sympy.Symbol("I")
total_infections_observable = Observable(name=key, expression=expression)
sir.observables[key] = total_infections_observable
Users can also add multiple observables at once using the Python dictionary
update
method. The update
method is a dictionary instance method that can
take in another dictionary and combines both dictionaries.
The passed-in dictionary takes priority and will overwrite the key-value pairs of the original dictionary if they share the same key.
Example: Adding multiple observables using the dictionary update method
import sympy
from mira.metamodel import *
from mira.examples.sir import sir_petrinet as sir
key_infections = "total_infections"
expression_infections = sympy.Symbol("I")
total_infections_observable = Observable(name=key_infections,
expression=expression_infections)
key_susceptible = "total_susceptible"
expression_susceptible = sympy.Symbol("S")
total_susceptible_observable = Observable(name=key_susceptible,
expression=expression_susceptible)
new_observables = {key_infections: total_infections_observable,
key_susceptible: total_susceptible_observable}
sir.observables.update(new_observables)
A user might want to remove an observable because it's no longer needed.
We can utilize the dictionary pop
method that takes in a key and removes
the key-value pair from the dictionary if
it exists in the dictionary.
Example: Removing an observable using the dictionary pop method
import sympy
from mira.metamodel import *
from mira.examples.sir import sir_petrinet as sir
key = "total_infections"
expression = sympy.Symbol("I")
total_infections_observable = Observable(name=key, expression=expression)
sir.observables[key] = total_infections_observable
sir.observables.pop(key)
A user might want to modify the expression of an observable to keep track of a different combination of compartments
We can use the Python dictionary method get
on the observables dictionary which takes in a key and returns a
reference to the observable object
that we'd like to modify if its key exists in the observables
dictionary.
Example: Modifying the expression of an existing observable
import sympy
from mira.metamodel import *
from mira.examples.sir import sir_petrinet as sir
# Add the observable
key = "total_infections"
expression = sympy.Symbol("I")
total_infections_observable = Observable(name=key, expression=expression)
sir.observables[key] = total_infections_observable
# stratify to add a species specific strata for the infected compartment
model = stratify(sir, "species", ["human", "pet"], concepts_to_stratify=["I"])
local_dict = {"I_human": sympy.Symbol("I_human"), "I_pet": sympy.Symbol("I_pet")}
# keep track of both human and pet infections for the total number of infected
new_expression = safe_parse_expr("I_human+I_pet", local_dict)
sir.observables.get(key).expression = SympyExprStr(new_expression)
Initials associated with a template model can be accessed using the initials
attribute of a template model object.
initials
:Dict[str,Initials]
- The dictionary of initials in a template model
- Like observables, we don't have any direct methods to add, remove, or modify initials,
but we can utilize the
initials
attribute of the template model object to add, remove, or modify initials just like how we do for observables.
A user might want to add a new initial to introduce a starting value for a compartment for simulation purposes.
Users can define a key-value pair where the key represents the id of the initial and the value is a newly created initial object to add to the template model. We create a new initial object with name and expression to keep track of the total number of infected in a SIR epidemiology model.
If there already exists a key-value pair in the initial dictionary using the same key, then the old initial object will be overwritten by the new one.
Example: Adding a single initial for the susceptible compartment in a SIR model using key-based indexing
import sympy
from mira.metamodel import *
from mira.examples.sir import sir_petrinet as sir
susceptible_concept = sir.get_concept("S")
key_susceptible = susceptible_concept.name
# Though initial values for compartments can be numbers, the Python object type
# passed into the expression argument for the Initial constructor must be of type
# (SympyExprStr, sympy.Expr), sympy.Float and sympy.Integer are subclasses of sympy.Expr
initial_expression = SympyExprStr(sympy.Float(1000))
# The Initial constructor takes in a concept object
susceptible_initial = Initial(concept=susceptible_concept,
expression=initial_expression)
sir.initials[key_susceptible] = susceptible_initial
Users can also add multiple initials at once using the Python dictionary update method. The update method is a dictionary instance method that can take in another dictionary and combines both dictionaries.
The passed-in dictionary takes priority and will overwrite the key-value pairs of the original dictionary if they share the same key.
Example: Adding multiple initials using the dictionary update method
import sympy
from mira.metamodel import *
from mira.examples.sir import sir_petrinet as sir
susceptible_concept = sir.get_concept("S")
infected_concept = sir.get_concept("I")
key_susceptible = susceptible_concept.name
key_infected = infected_concept.name
susceptible_initial_expression = SympyExprStr(sympy.Float(1000))
infected_initial_expression = SympyExprStr(sympy.Float(0))
susceptible_initial = Initial(concept=susceptible_concept,
expression=susceptible_initial_expression)
infection_initial = Initial(concept=infected_concept,
expression=infected_initial_expression)
sir.initials[key_susceptible] = susceptible_initial
sir.initials[key_infected] = infection_initial
new_initials = {key_susceptible: susceptible_initial,
key_infected: infection_initial}
sir.initials.update(new_initials)
A user might want to remove an initial because the compartment value it represents is no longer used for simulation purposes.
We can utilize the dictionary pop
method that takes in a key and removes the key-value pair from the dictionary if it exists in the dictionary.
Example: Removing an initial using the dictionary pop method
import sympy
from mira.metamodel import *
from mira.examples.sir import sir_petrinet as sir
susceptible_concept = sir.get_concept("S")
key_susceptible = susceptible_concept.name
susceptible_initial_expression = SympyExprStr(sympy.Float(5))
susceptible_initial = Initial(concept=susceptible_concept,
expression=susceptible_initial_expression)
sir.initials[key_susceptible] = susceptible_initial
sir.initials.pop(key_susceptible)
A user might want to modify the initial of an expression to change the starting value for a compartment during simulation.
We can use the Python dictionary method get
on the initials dictionary
which takes in a key and returns a reference to the initial object that we’d
like to modify if its key exists in the initials
dictionary.
There are two types of values that an initial object's expression can take. Users can either pass in a value or parameter to an initial expression to represent the initial value for a compartment.
Though we can use a number to represent the initial expression semantically, we must pass in
a sympy
object to the expression field for the Initial
constructor.
Example: Setting the expression of an initial to be represented by a number
import sympy
from mira.metamodel import *
from mira.examples.sir import sir_petrinet as sir
susceptible_concept = sir.get_concept("S")
key_susceptible = susceptible_concept.name
sir.initials.get(key_susceptible).expression = SympyExprStr(sympy.Float(1000))
We can define the expression of an initial to be represented by an actual expression.
Example: Setting the expression of an initial to be represented by a parameter
import sympy
from mira.metamodel import *
from mira.examples.sir import sir_petrinet as sir
susceptible_concept = sir.get_concept("S")
key_susceptible = susceptible_concept.name
# Add the parameter that represents the initial value for the susceptible compartment
# to the template model
sir.add_parameter(parameter_id="S0", value=1000)
local_dict = {"S0": sympy.Symbol("S0")}
# Set the parameter "S0" to represent the initial value for the susceptible compartment
sir.initials.get(key_susceptible).expression = SympyExprStr(
safe_parse_expr("S0", local_dict))
There are multiple ways in which a user can retrieve concepts present in a template model object. We can either retrieve a single concept object by name or return a mapping of concept keys to concept objects.
We can use the get_concept
method to return a single concept object.
- Documentation
get_concept(name)
name
:str
- The concept name that represents the concept we want to retrieve
- Return type
Concept
- The specified concept
- If there doesn't exist a concept in the template model with the name supplied to the method,
a
None
object will be returned.
Example: Retrieve a single concept object by name
from mira.examples.sir import sir_petrinet as sir
susceptible_concept = sir.get_concept("S")
If we want to retrieve all the concepts present in a template model,
we can use the get_concepts_map
method to return a mapping of concepts.
- Documentation
get_concepts_map()
- Return type
Dict[str, Concept]
- The mapping of concepts
- Return type
Example: Return the mapping of concepts in a template model
from mira.examples.sir import sir_petrinet as sir
concepts_map = sir.get_concepts_map()
Users can use the add_parameter
method which is a template model instance
method that adds a parameter
to the template model. The only required parameter is the id of the parameter.
Example: Adding a parameter only using a parameter id to the template model
from mira.examples.sir import sir_petrinet as sir
sir.add_parameter("mu")
- Documentation
add_parameter(parameter_id, name, description, value, distribution, units_mathml)
parameter_id
:str
- The id of the parameter
name
:Optional[str]
- An alternative name for the parameter
description
:Optional[str]
- The description of the parameter
value
:Optional[float]
- The value of the parameter
distribution
:Optional[Dict[str, Any]]
- The distribution of the parameter
units_mathml
:Optional[str]
- The units of the parameter in mathml form
If we added pet-specific compartments to a human-centric SIR epidemiology model, but don't have accompanying parameters, we can add pet specific parameters with values for simulation purposes.
Example: Add a new parameter with a value to the template model
from mira.examples.sir import sir_petrinet as sir
sir.add_parameter("mu_pet", value=0.0003)
There are three ways that a template can be added to a template model object. A user might want to add a template to an existing template model object to extend its capabilities or customize the model to fit the specific problem scenario.
We can use the add_template
template model instance method to add a template.
- Documentation
add_template(template, parameter_mapping, initial_mapping)
template
:Template
- The template to add to the template model
parameter_mapping
:Optional[Mapping[str, Parameter]]
- A mapping of parameters that appear in the template to add to the template model
initial_mapping
:Optional[Mapping[str,Initial]]
- A mapping of initials that appear in the template to add to the template model
- Return type
TemplateModel
- The template model with the new template added
Example: Using the add_template
method to add a template to the model
import sympy
from mira.examples.sir import sir_petrinet as sir
from mira.metamodel.templates import *
from mira.metamodel.template_model import *
from mira.metamodel.utils import SympyExprStr, safe_parse_expr
recovered_concept = sir.get_concept("R")
local_dict = {"eta": sympy.Symbol("eta"), "R": sympy.Symbol("R")}
# Define the template to add
template = NaturalConversion(subject=recovered_concept,
outcome=sir.get_concept("I"),
rate_law=safe_parse_expr("eta*R", local_dict))
# Add the new template "eta" that appears in the added rate law
parameter_mapping = {"eta": Parameter(name="eta", value=5)}
# Update the initial for the recovered compartment
initial_mapping = {"R": Initial(concept=recovered_concept,
expression=SympyExprStr(sympy.Float(5)))}
sir = sir.add_template(template, parameter_mapping=parameter_mapping,
initial_mapping=initial_mapping)
We can use the add_transition
template model instance method to infer the template type to be added from
the arguments passed and add it to the template model. Currently, this method only supports adding natural type templates.
- Documentation
add_transition(transition_name, subject_concept, outcome_concept, rate_law_sympy, params_dict, mass_action_parameter)
transition_name
:Optional[str]
- The name of the template
subject_concept
:Optional[Concept]
- The subject of the template
outcome_concept
:Optional[Concept]
- The outcome of the template
rate_law_sympy
:Optional[SympyExprStr]
- The rate law that governs the template in sympy form
params_dict
:Optional[Mapping[str, Mapping[str,Any]]]
- The mapping of parameter names to their respective attributes to be added to the model
mass_action_parameter
:Optional[Parameter]
- The mass action parameter that will be set the template's mass action rate law if provided
- Return type
TemplateModel
- The template model with the new natural type template added
Example: Using the add_transition
method to add a template to the model
import sympy
from mira.examples.sir import sir_petrinet as sir
from mira.metamodel.utils import SympyExprStr, safe_parse_expr
recovered_concept = sir.get_concept("I")
susceptible_concept = sir.get_concept("S")
local_dict = {"eta": sympy.Symbol("eta"), "R": sympy.Symbol("R")}
rate_law_sympy = SympyExprStr(safe_parse_expr("eta*R", local_dict))
params_dict = {"eta": {"display_name": "eta", "value": 5}}
sir = sir.add_transition(subject_concept=recovered_concept, outcome_concept=susceptible_concept,
rate_law_sympy=rate_law_sympy, params_dict=params_dict)
Add a template to the list of templates stored in the templates
attribute of a template model object
Rather than using a method, users can also add templates by directly appending a template to the templates
attribute
which is a list of templates.
Example: Adding a template directly accessing the templates
attribute
import sympy
from mira.examples.sir import sir_petrinet as sir
from mira.metamodel.templates import *
from mira.metamodel.utils import SympyExprStr, safe_parse_expr
recovered_concept = sir.get_concept("R")
local_dict = {"eta": sympy.Symbol("eta"), "R": sympy.Symbol("R")}
# Define the template to add
template = NaturalConversion(subject=recovered_concept,
outcome=sir.get_concept("I"),
rate_law=SympyExprStr(
safe_parse_expr("R*eta", local_dict)))
sir.templates.append(template)
# After the template it added, manually add the new parameter that appears in the template
sir.add_parameter(parameter_id="eta", value=.02)
The stratification method can take in an exhaustive list of arguments and multiplies a template model into several strata.
The three required arguments are the input template model, key, and strata
Example: Stratification on a basic SIR model by vaccination status
from mira.examples.sir import sir_petrinet as sir
from mira.metamodel.ops import stratify
key = "vaccination_status"
strata = ["unvaccinated", "vaccinated"]
sir = stratify(sir, key, strata)
- Documentation
stratify(model, key, strata)
model
:TemplateModel
- The input template model to be stratified
key
:str
- The singular name of the stratification
strata
:Collections[str]
- The list of values used for stratification
- Return type
TemplateModel
- The stratified template model
- If you want to not stratify certain parameters or concepts in the template
model, you can pass in an optional collection of concept and parameter names
to the stratify
method under arguments
concepts_to_stratify
andparams_to_stratify
respectively.concepts_to_stratify
:Optional[Collection[str]]
- The list of concept names that will be stratified
params_to_stratify
:Optional[Collection[str]]
- The list of parameter names that will be stratified
- If an argument isn't supplied, then all concepts and/or parameters will be stratified
Example: Stratification on a SIR model while selecting certain concepts and parameters to stratify
from mira.examples.sir import sir_petrinet as sir
from mira.metamodel.ops import stratify
key = "vaccination_status"
strata = ["unvaccinated", "vaccinated"]
sir = stratify(sir, key, strata, concepts_to_stratify=["S", "I"],
params_to_stratify=["c", "beta", "m"])
-
If the number of concepts and parameters that require stratification is too long, users can alternatively opt to select the concepts and parameters they'd like to preserve from stratification. Users can pass in an optional collection of concept and parameter names to stratify method under arguments
concepts_to_preserve
andparameters_to_preserve
respectively.concepts_to_preserve
:Optional[Collection[str]]
- The list of concept names to not stratify
parameters_to_preserve
:Optional[Collection[str]]
- The list of parameter names to not stratify
- If an argument isn't supplied, then all concepts and/or parameters will be stratified
- Users aren't required to pass in both a collection of concepts/parameters to stratify and a collection of concepts/parameters to preserve. Only one set of concepts/parameters are required. Any concepts/parameters not listed in the set of model attributes to preserve will be stratified.
Example: Stratification on a SIR model while selecting certain concepts and parameters to preserve
from mira.examples.sir import sir_petrinet as sir
from mira.metamodel.ops import stratify
key = "vaccination_status"
strata = ["unvaccinated", "vaccinated"]
sir = stratify(sir, key, strata, concepts_to_preserve=["S", "I"],
params_to_preserve=["c", "beta", "m"])
- By default, the stratify operator will rename stratified concepts to include
the name of the strata and not rename parameters to include the strata names.
We can
set the values of the
modify_names
andparam_renaming_uses_strata_names
flags.modify_name
:Optional[bool]
- Setting this to true will rename concept names to include strata names
param_renaming_uses_strata_names
:Optional[bool]
- Setting this to true will rename parameter names to include strata names
Example: Stratification on a SIR model while renaming specific compartment and parameters to include strata name
from mira.examples.sir import sir_petrinet as sir
from mira.metamodel.ops import stratify
key = "vaccination_status"
strata = ["unvaccinated", "vaccinated"]
sir = stratify(sir, key, strata, concepts_to_stratify=["S", "I"],
params_to_stratify=["c", "beta", "m"],
param_renaming_uses_strata_names=True,
modify_names=True)
- If you wanted to specify certain pairs of compartments to have a directed
transition structure where
each of the compartments in the pair will be appropriately stratified
according to the strata, then
an optional iterable of tuple pairs containing compartment names can be passed
to the
structure
argument.structure
:Optional[Iterable[Tuple[str, str]]]
- The list of tuple pairs that denote a directed edge from the first
compartment in each tuple
to the second compartment in the same tuple
- If no argument is supplied, the stratify method will assume a complete transition network structure between strata
- If no structure is necessary, then pass in an empty list
- The list of tuple pairs that denote a directed edge from the first
compartment in each tuple
to the second compartment in the same tuple
An example where we wouldn't want any structure is if we were to stratify the model by age. This is because for the purpose of modeling, people do not age.
Example: Stratifying a SIR model by age with no transitions between strata
from mira.examples.sir import sir_petrinet as sir
from mira.metamodel.ops import stratify
key = "age"
strata = ["under50", "50+"]
sir = stratify(sir, key, strata, structure=[])
An example where we would want to specify some structure but not assume complete transition network structure is if we were to stratify a model based on vaccination status. This is because people can transition from being unvaccinated to vaccinated; however, it's impossible once someone is vaccinated, to transition to unvaccinated.
We would pass in an iterable that contains a single tuple pair
("unvaccinated", "vaccinated")
that represents
people getting vaccinated in a SIR epidemiological model.
Example: Stratifying a SIR model by vaccination status with some transitions between strata
from mira.examples.sir import sir_petrinet as sir
from mira.metamodel.ops import stratify
key = "vaccination_status"
strata = ["unvaccinated", "vaccinated"]
sir = stratify(sir, key, strata, structure=[("unvaccinated", "vaccinated")])
- Setting the
cartesian_control
argument to true will split all control based relationships based on the stratification.cartesian_control
:Optional[bool]
- Setting this to true splits all control relationships in the model
The cartesian_control
argument should be set to true for a SIR epidemiology model stratified on
age. As the transition from the susceptible
to the infected compartment for a certain age group is controlled by the
infected compartment of other age groups.
Example: Stratifying a SIR model by age while splitting control based relationships
from mira.examples.sir import sir_petrinet as sir
from mira.metamodel.ops import stratify
key = "age"
strata = ["under50", "50+"]
sir = stratify(sir, key, strata, cartesian_control=True)
We would set cartesian_control
to false for a SIR epidemiology model based on
city, since the infected population in one city will not
affect the infection of the susceptible population in another city.
Example: Stratifying a SIR model by city with no splitting of control based relationships
from mira.examples.sir import sir_petrinet as sir
from mira.metamodel.ops import stratify
key = "city"
strata = ["Boston", "Miami"]
sir = stratify(sir, key, strata, cartesian_control=False)
The composition method takes in a list of template models and composes them into a single template model. The list must contain at least two template models.
Example: Compose a list of two SIR based epidemiological models
from mira.examples.sir import sir, sir_2_city
from mira.metamodel.composition import compose
tm_list = [sir, sir_2_city]
composed_tm = compose(tm_list)
- Documentation
tm_list
:List[TemplateModel]
- A list of template models to be composed
- Return type
TemplateModel
- The composed template model
The composition functionality prioritizes template model attributes (parameters, initials, templates, annotation, time, model time, etc.) of the first template model in the list.
If we had five different template models representing variations of the base SIR epidemiological model, we can combine them using model composition.
Example: Compose five different SIR based models
from mira.metamodel import *
susceptible = Concept(name="susceptible_population",
identifiers={"ido": "0000514"})
hospitalized = Concept(name="hospitalized", identifiers={"ncit": "C25179"})
infected = Concept(name="infected_population", identifiers={"ido": "0000511"})
recovered = Concept(name="immune_population", identifiers={"ido": "0000592"})
dead = Concept(name="dead", identifiers={"ncit": "C28554"})
quarantined = Concept(name="quarantined", identifiers={})
infection = ControlledConversion(
subject=susceptible,
outcome=infected,
controller=infected,
)
recovery = NaturalConversion(
subject=infected,
outcome=recovered,
)
reinfection = ControlledConversion(
subject=recovered,
outcome=infected,
controller=infected,
)
to_quarantine = NaturalConversion(
subject=susceptible,
outcome=quarantined
)
from_quarantine = NaturalConversion(
subject=quarantined,
outcome=susceptible
)
dying = NaturalConversion(
subject=infected,
outcome=dead
)
hospitalization = NaturalConversion(
subject=infected,
outcome=hospitalized
)
hospitalization_to_recovery = NaturalConversion(
subject=hospitalized,
outcome=recovered
)
hospitalization_to_death = NaturalConversion(
subject=hospitalized,
outcome=dead
)
sir = TemplateModel(
templates=[
infection,
recovery,
]
)
sir_reinfection = TemplateModel(
templates=[
infection,
recovery,
reinfection
]
)
sir_quarantined = TemplateModel(
templates=[
infection,
to_quarantine,
from_quarantine,
recovery
]
)
sir_dying = TemplateModel(
templates=[
infection,
dying,
recovery,
]
)
sir_hospitalized = TemplateModel(
templates=[
infection,
hospitalization,
hospitalization_to_recovery,
hospitalization_to_death,
]
)
model_list = [
sir_reinfection,
sir_quarantined,
sir_dying,
sir_hospitalized,
sir,
]
composed_model = compose(tm_list=model_list)
In this section we will discuss the behavior of how concepts are composed under different circumstances.
- If two concepts have the same name and identifiers
- If two concepts from different template models being composed having matching names and identifiers, then they will be composed into one concept.
- If two concepts have the same name but mismatched identifiers
- They will be treated as separate concepts
- If two concepts don't have the same name but matching identifiers
- They will be composed into the same concept, with the name of the first concept taking priority.
- If two concepts have matching names and one has identifiers and the other
doesn't
- The concepts are composed into the same concept while preserving the identifiers
- The concepts have matching names and neither has identifiers
- The concepts are composed into the same concept
from mira.metamodel import *
S1 = Concept(name="Susceptible", identifiers={"ido": "0000514"})
I1 = Concept(name="Infected", identifiers={"ido": "0000511"})
R1 = Concept(name="Recovery", identifiers={"ido": "0000592"})
S2 = Concept(name="Susceptible", identifiers={"ido": "0000513"})
I2 = Concept(name="Infected", identifiers={"ido": "0000512"})
R2 = Concept(name="Recovery", identifiers={"ido": "0000593"})
S3 = Concept(name="S", identifiers={"ido": "0000514"})
I3 = Concept(name="I", identifiers={"ido": "0000511"})
R3 = Concept(name="R", identifiers={"ido": "0000592"})
S4 = Concept(name="S")
I4 = Concept(name="I")
R4 = Concept(name="R")
model_A1 = TemplateModel(
templates=[
ControlledConversion(
name='Infection',
subject=S1,
outcome=I1,
controller=I1
)
],
parameters={
'b': Parameter(name='b', value=1.0)
}
)
model_B1 = TemplateModel(
templates=[
NaturalConversion(
name='Recovery',
subject=I1,
outcome=R1
)
],
parameters={
'g': Parameter(name='g', value=1.0)
}
)
model_B2 = TemplateModel(
templates=[
NaturalConversion(
name='Recovery',
subject=I2,
outcome=R2
)
],
parameters={
'g': Parameter(name='g', value=1.0)
}
)
model_B3 = TemplateModel(
templates=[
NaturalConversion(
name='Recovery',
subject=I3,
outcome=R3
)
],
parameters={
'g': Parameter(name='g', value=1.0)
}
)
model_B4 = TemplateModel(
templates=[
NaturalConversion(
name='Recovery',
subject=I4,
outcome=R4
)
],
parameters={
'g': Parameter(name='g', value=1.0)
}
)
model_A3 = TemplateModel(
templates=[
ControlledConversion(
name='Infection',
subject=S3,
outcome=I3,
controller=I3
)
],
parameters={
'b': Parameter(name='b', value=1.0)
}
)
model_A4 = TemplateModel(
templates=[
ControlledConversion(
name='Infection',
subject=S4,
outcome=I4,
controller=I4
)
],
parameters={
'b': Parameter(name='b', value=1.0)
}
)
# (matching name and ids)
# composed into a single concept
model_AB11 = compose([model_A1, model_B1])
# matching names, mismatched ids
# composed into a separate concepts
model_AB12 = compose([model_A1, model_B2])
# mismatched names, matching ids
# composed into a single concept
model_AB13 = compose([model_A1, model_B3])
# matching names, no id + yes id
# composed into a single concept
model_AB34 = compose([model_A3, model_B4])
# matching names, no id + no id
# composed into a single concept
model_AB44 = compose([model_A4, model_B4])