#!/usr/bin/env python # coding: utf-8 # https://libcellml.org/documentation/v0.4.0/user/tutorials/hh_tutorial1/index # In[1]: from libcellml import Analyser, Component, Model, Printer, Units, Validator, Variable, cellmlElementTypeAsString from combine_notebooks.cellml_utilities import print_model # Setup the model # In[2]: # Setup useful things. math_header = '\n' math_footer = '' # The first step is to create a Model item which will later contain the component and # the units it needs. model = Model() # Each CellML element must have a name, which is set using the setName() function. model.setName('GateModel') # We'll create a wrapper component whose only job is to encapsulate the other components. # This makes is a lot easier for this model to be reused, as the connections between # components internal to this one won't need to be re-established. # Note that the constructor for all named CellML entities is overloaded, so # you can pass it the name string at the time of creation. # Create a component named 'gate'. gate = Component('gate') # Finally we need to add the component to the model. This sets it at the top-level of # the components' encapsulation hierarchy. All other components need to be added # to this component, rather than the model. # Add the component to the model using the Model::addComponent() function. model.addComponent(gate) # Print the model to the terminal using the print_model helper function and # check it is what you'd expect. print_model(model) # Create the gateEquations component # In[3]: # Create a gateEquations component and name it 'gateEquations'. gateEquations = Component('gateEquations') # Add the new gateEquations component to the gate component. gate.addComponent(gateEquations) # Add the mathematics to the gateEquations component. equation = \ ' \n'\ ' \n'\ ' t\n'\ ' X\n'\ ' \n'\ ' \n'\ ' \n'\ ' alpha_X\n'\ ' \n'\ ' 1\n'\ ' X\n'\ ' \n'\ ' \n'\ ' \n'\ ' beta_X\n'\ ' X\n'\ ' \n'\ ' \n'\ ' \n' gateEquations.setMath(math_header) gateEquations.appendMath(equation) gateEquations.appendMath(math_footer) # Print the model to the terminal using the print_model helper function and # check it is what you'd expect. Include the second argument as True so that # the maths is included. print_model(model, True) # Once the mathematics has been added to the component, and the component to the # model, we can make use of the diagnostic messages within the Validator class # to tell us what else needs to be done. # Validate the model # In[4]: # Create a Validator instance, and pass it your model for processing using the # validateModel function. validator = Validator() validator.validateModel(model) # Add the variables # In[5]: # Create items for the missing variables and add them to the gateEquations component. # You will need to be sure to give them names which match exactly those reported by the # validator, or are present in the MathML string. gateEquations.addVariable(Variable('t')) gateEquations.addVariable(Variable('alpha_X')) gateEquations.addVariable(Variable('beta_X')) gateEquations.addVariable(Variable('X')) # Validate again, and expect errors relating to missing units. # Note that you can use the helper function print_issues(validator) to print your # issues to the screen instead of repeating the code from 3.b. validator.validateModel(model) # Add the units # In[7]: # The validator has reported that the four variables are missing units attributes. # In this example none of the units exist yet, so we need to create all of them. # The variables' units should be: # - t, time has units of *milliseconds* # - X, gate status has units of *dimensionless* # - alpha_X and beta_X, rates, have units of *per millisecond*. # Create the units which will be needed by your variables and add them to the model. ms = Units('ms') per_ms = Units('per_ms') # Add Unit items to the units you created to define them. ms.addUnit('second', 'milli') per_ms.addUnit('second', 'milli', -1) # Add the Units to the model (not the component) so that other components can make # use of them too. model.addUnits(ms) model.addUnits(per_ms) # Use the setUnits function to associate them with the appropriate variables. gateEquations.variable('t').setUnits(ms) gateEquations.variable('alpha_X').setUnits(per_ms) gateEquations.variable('beta_X').setUnits(per_ms) gateEquations.variable('X').setUnits('dimensionless') # Validate again, and expect no errors. validator.validateModel(model) # Print the model to the terminal and include the optional second argument of true # to include the MathML. print_model(model, True) # Analyse the model # In[10]: # Create an Analyser item and submit the model for processing. analyser = Analyser() analyser.analyseModel(model) # In order to avoid hard-coding values here, we will need to connect to external # values to initialise the X variable and provide the value for alpha_X and beta_X. # This means that: # - we need to create an external component to hold variable values # - we need to create external variables in that component # - we need to specify the connections between variables and # - we need to permit external connections on the variables. # Create a component which will store the hard-coded values for initialisation. # Name it 'gateParameters', and add it to the top-level gate component as a sibling # of the gateEquations component. gateParameters = Component('gateParameters') gate.addComponent(gateParameters) # Create appropriate variables in this component, and set their units. # Use the setInitialValue function to initialise them. X = Variable('X') X.setUnits('dimensionless') X.setInitialValue(0) gateParameters.addVariable(X) alpha = Variable('alpha') alpha.setUnits(per_ms) alpha.setInitialValue(0.1) gateParameters.addVariable(alpha) beta = Variable('beta') beta.setUnits(per_ms) beta.setInitialValue(0.5) gateParameters.addVariable(beta) # Specify a variable equivalence between the gateEquations variables and the parameter variables. # Validate the model again, expecting errors related to the variable interface types. Variable.addEquivalence(gateEquations.variable('X'), gateParameters.variable('X')) Variable.addEquivalence(gateEquations.variable('alpha_X'), gateParameters.variable('alpha')) Variable.addEquivalence(gateEquations.variable('beta_X'), gateParameters.variable('beta')) validator.validateModel(model) # Set the variable interface type according to the recommendation from the validator. # This can either be done individually using the Variable::setInterfaceType() function, or # en masse for all the model's interfaces using the Model::fixVariableInterfaces() function. # Validate and analyse again, expecting no errors. model.fixVariableInterfaces() validator.validateModel(model) analyser.analyseModel(model) # Sanity check # In[11]: # Print the model to the terminal using the helper function print_model. print_model(model) # Looking at the printout we see that the top-level component has no variables. # Even though this is clearly a valid situation (as proved by 4.f), it's not # going to make this model easy to reuse. We need to make sure that any input and # output variables are also connected into the top level gate component. # Create intermediate variables for time t and gate status X in the gate component, # and ensure they have a public and private interface to enable two-way connection. # You may also need to set a public and private connection onto t and X in the # equations component too. gate.addVariable(gateEquations.variable('t').clone()) gate.addVariable(gateEquations.variable('X').clone()) gate.variable('t').setInterfaceType('public_and_private') gate.variable('X').setInterfaceType('public_and_private') gateEquations.variable('t').setInterfaceType('public_and_private') gateEquations.variable('X').setInterfaceType('public_and_private') # Connect the intermediate variables to their respective partners in the equations # component, and recheck the model. Variable.addEquivalence(gate.variable('t'), gateEquations.variable('t')) Variable.addEquivalence(gate.variable('X'), gateEquations.variable('X')) validator.validateModel(model) analyser.analyseModel(model) # Serialise and output the model # In[15]: from combine_notebooks import RESULTS_DIR from pathlib import Path cellml_path: Path = RESULTS_DIR / 'hello_world_cellml.cellml' # Create a Printer instance and use it to serialise the model. This creates a string # containing the CellML-formatted version of the model. Write this to a file called # 'hello_world_cellml.cellml'. printer = Printer() write_file = open(cellml_path, 'w') write_file.write(printer.printModel(model)) write_file.close() print('The created model has been written to hello_world_cellml.cellml')