Annotate a model¶
libCellML does not provide annotation functionality directly, but it can help with some of the things that you might need to know in order to implement your own. Each annotation is attached to a unique ID locator which can be retrieved and edited, or created automatically if you haven’t already set them yourself. Since CellML is a subset of XML, any item may have an ID attribute, including operations within the MathML blocks, CellML entities, and CellML non-entity items (connections, encapsulations etc). These ID attributes themselves must follow the specification rules: please see 1.2.5 XML ID attributes for details.
At present, items within MathML blocks which have id attributes are not retrieved or edited by the annotation class.
Any editing or manipulation must be done external to libCellML.
Annotation tools for entity items¶
Entity item types are:
Modelitems;Componentitems;Variableitems;Unitsitems;Resetitems; andImportSourceitems.
The id attribute for each of these entity types can be retrieved using the id() function for the object, or set through its setId() function.
// Set the ID attribute for a Model item.
model->setId("myModelId");
// Retrieve the ID attribute from a Model item.
std::string myModelIdString = model->id();
# Set the ID attribute for a Model item.
model.setId("myModelId")
# Retrieve the ID attribute from a Model item.
my_model_id_string = model.id()
Annotation tools for other item types¶
Connections and variable equivalence¶
Items which specify the relationship between two entities (such as connections between components, and mappings between variables) are accessed and identified by the pair of entities. Consider the simple example shown below.
model:
├─ component: name = "c1", id = "c1id"
│ └ variable: name = "v1", id = "v1id" <╴╴╴┐
└─ component: name = "c2", id = "c2id" equivalent variables
└ variable: name = "v2", id = "v2id" <╴╴╴┘
Show CellML syntax
<model>
<component name="c1" id="c1id">
<variable name="v1" id="v1id" />
</component>
<component name="c2" id="c2id">
<variable name="v2" id="v2id" />
</component>
<!-- The variable equivalence is stored separately to the variables themselves. -->
<connection component1="c1" component2="c2" id="c1c2id" >
<map_variables variable1="v1" variable2="v2" id="v1v2id" />
</connection>
</model>
// Set the ID of the mapping between variables v1 and v2.
Variable::setEquivalentVariableId(v1, v2, "v1v2Id");
// Get the ID of the mapping between variables v1 and v2. Note that
// equivalences and connections go both ways, so the ID is the same
// whichever order the variables are specified.
std::string v1v2IdString = Variable::equivalentVariableId(v1, v2);
// Since a connection item between two components will only exist
// when there is a variable equivalence between at least one each
// of their children. The connection is accessed through those
// child variable pairs, just as the equivalence mapping itself is.
Variable::setConnectionId(v1, v2, "c1c2id");
// Get the ID of the connection between the parent components of
// equivalent variable pair. Note that equivalences and connections
// go both ways, so the ID is the same whichever order the variables
// are specified.
std::string c1c2IdString = Variable::connectionId(v1, v2);
# Set the ID of the mapping between variables v1 and v2.
Variable.setEquivalentVariableId(v1, v2, "v1v2Id")
# Get the ID of the mapping between variables v1 and v2.
# Note that equivalences and connections go both ways, so
# the ID is the same whichever order the variables are specified.
v1v2_id_string = Variable.equivalentVariableId(v1, v2)
# Since a connection item between two components will only exist
# when there is a variable equivalence between at least one each
# of their children. The connection is accessed through those
# child variable pairs, just as the equivalence mapping itself is.
Variable.setConnectionId(v1, v2, "c1c2id")
# Get the ID of the connection between the parent components
# of equivalent variable pair.
# Note that equivalences and connections go both ways, so
# the ID is the same whichever order the variables are specified.
c1c2IdString = Variable.connectionId(v1, v2)
Encapsulation and component references¶
The model’s encapsulation and the component references which it contains may have ID attributes too.
These are accessed through the encapsulationId() functions, as shown below.
model:
└ encapsulation:
└ component: grandparent
└ component: parent
└ component: child
Show CellML syntax
<model>
<component name="grandparent" id="grandparentComponentId" />
<component name="parent" id="parentComponentId" />
<component name="child" id="childComponentId" />
<!-- The ids of the components in the encapsulation structure are distinct
from the ids on the components themselves. -->
<encapsulation id="encapsId">
<component_ref component="grandparent" id="grandparentEncapsId" >
<component_ref component="parent" id="parentEncapsId" >
<component_ref component="child" id="childEncapsId" >
</component_ref>
</component_ref>
</encapsulation>
</model>
There is only one encapsulation in a model, and its id attribute is accessed using the very simple functions from the model instance.
The position of each component within that encapsulation structure is referenced using the same functions, but on the component instance instead.
// Set the encapsulation ID.
model->setEncapsulationId("encapsId");
grandparentComponent->setEncapsulationId("grandparentEncapsId");
// Get the encapsulation ID.
std::string modelEncapsulationId = model->encapsulationId();
std::string grandparentEncapsulationId = grandparentComponent->encapsulationId();
# Set the encapsulation ID.
model.setEncapsulationId("encapsId")
grandparent_component.setEncapsulationId("grandparentEncapsId")
# Get the encapsulation ID.
model_encapsulation_id = model.encapsulationId()
grandparent_encapsulation_id = grandparent_component.encapsulationId()
Unit item children; reset_value, test_value item children¶
Some items are most readily accessed through their entity-type parents, these being:
unititems, a collection of which defines aUnitsitem; andtest_valueandreset_valuechildren ofResetitems.
Unit children of Units items can be accessed either through the streamlined ID-only functions unitId(index) and setUnitId(index), or through the unitAttributes family of functions.
Note that there are several overloads of the arguments for this function; please see the complete documentation on the Units functions API page.
// Create a Units item representing millimetre^3 per second.
auto mm3PerSecond = libcellml::Units::create("mm3PerSecond");
// Add the per second part with the ID "perSecondUnitId".
mm3PerSecond->addUnit("second", 0, -1.0, 1.0, "perSecondUnitId");
// Add the mm^3 part with with the ID "mmCubedUnitId".
mm3PerSecond->addUnit("metre", "milli", 3.0, 1.0, "mmCubedUnitId");
// Check that the ID has been assigned to the Unit children.
auto checkId1 = mm3PerSecond->unitId(0); // returns "perSecondUnitId".
auto checkId2 = mm3PerSecond->unitId(1); // returns "mmCubedUnitId".
// Change the ID of the second (ie: index = 1) child to be "millimetreCubedUnitId":
mm3PerSecond->setUnitId(1, "millimetreCubedUnitId");
// Retrieve the unit attributes for the first (index = 0) unit item, including the ID attribute:
std::string unitReference;
std::string unitPrefix;
double unitExponent;
double unitMultiplier;
std::string unitId;
mm3PerSecond->unitAttributes(0, unitReference, unitPrefix, unitExponent, unitMultiplier, unitId);
# Create a Units item representing millimetre^3 per second.
mm3_per_second = Units.create("mm3PerSecond")
# Add the per second part with the ID "perSecondUnitId".
mm3_per_second.addUnit("second", 0, -1.0, 1.0, "perSecondUnitId")
# Add the mm^3 part with with the ID "mmCubedUnitId".
mm3_per_second.addUnit("metre", "milli", 3.0, 1.0, "mmCubedUnitId")
# Retrieve both ids from the child units.
check_1 = mm3_per_second.unitId(0) # returns "perSecondUnitId"
check_2 = mm3_per_second.unitId(1) # returns "mmCubedUnitId"
# Change the ID of the second (ie: index = 1) Unit child to be "millimetreCubedUnitId".
mm3_per_second.setUnitId(1, "millimetreCubedUnitId")
# Retrieve the unit attributes for the second (index = 0) unit item, including the ID attribute:
mm3PerSecond.unitAttributes(0, unitReference, unitPrefix, unitExponent, unitMultiplier, unitId)
Annotator Type and returned types¶
enumeration value |
Object type in the “any” container. |
COMPONENT |
Pointer to a |
COMPONENT_REF |
Pointer to a |
CONNECTION |
|
ENCAPSULATION |
Pointer to the |
IMPORT |
Pointer to an |
ISSUE |
Pointer to an |
MAP_VARIABLES |
|
MODEL |
Pointer to the |
RESET |
Pointer to the |
RESET_VALUE |
Pointer to the parent |
TEST_VALUE |
Pointer to the parent |
UNIT |
|
UNITS |
Pointer to a |
VARIABLE |
Pointer to a |
Useful snippets for annotation¶
Automatically assign unique ids to items
Because the id attribute is simply a token, its contents (provided they follow the XML rules) could be anything.
Ids are not intended to be interacted with by humans; they are there as a placeholder or location specifier so that other (more human-friendly) documentation can be added to the right objects.
With this in mind, it’s possible to automate the creation of unique ids for a single item, a set of items of the same type (eg: all the Component items), or for every annotatable item in a CellML model.
// Create an Annotator instance.
auto annotator = libcellml::Annotator::create();
// ---------- OPTION 1: Automatic ids for everything -------------------------
//
// Submit the model to the Annotator, and instruct it to create and assign a
// set of unique ids to any annotatable item in the model.
// Note that this function replaces any previously stored model within the
// Annotator, and rebuilds the internal index with respect to this model.
// There is no need to either build the index first (as all the ids will
// be altered anyway) or afterwards (as it's built during the assignment
// process).
annotator->assignAllIds(model);
// ---------- OPTION 2: Automatic ids by type -----------------------
//
// First submit the model to the Annotator and build its index.
annotator->buildModelIndex(model);
// Next, specify the type of item to assign automatic ids to using the
// Type enumeration options. This will assign every Variable in the model
// a unique id string.
annotator->assignIds(libcellml::Annotator::Type::VARIABLE);
// ---------- OPTION 3: Automatic id by item -------------------------
//
// Finally, you can submit single items for automatic id.
// There are two ways to do this as shown below. Note that when you use the
// built-in Annotator functions to assign ids, there is no need to rebuild the
// annotator's index.
//
// The two options below are identical.
//
// EITHER Submit an AnyItem struct (a pair of Type enum, and the item) ...
AnyItem myComponentRef = std::make_pair(libcellml::Annotator::Type::COMPONENT_REF, model->component(0));
annotator->assignId(myComponentRef);
// ... OR submit both the Type enum and the item as separate arguments.
annotator->assignId(libcellml::Annotator::Type::COMPONENT_REF, model->component(0));
# Create an Annotator instance.
from libcellml import Annotator
annotator = Annotator()
# ---------- OPTION 1: Automatic ids for everything -------------------------
#
# Submit the model to the Annotator, and instruct it to create and assign a
# set of unique ids to any annotatable item in the model.
# Note that this function replaces any previously stored model within the
# Annotator, and rebuilds the internal index with respect to this model.
# There is no need to either build the index first (as all the ids will
# be altered anyway) or afterwards (as it's built during the assignment
# process).
annotator.assignAllIds(model)
# ---------- OPTION 2: Automatic ids by type -----------------------
#
# First submit the model to the Annotator and build its index.
annotator.buildModelIndex(model)
# Next, specify the type of item to assign automatic ids to using the
# Type enumeration options. This will assign every Variable in the model
# a unique id string.
annotator.assignIds(Annotator.Type.VARIABLE)
# ---------- OPTION 3: Automatic id by item -------------------------
#
# Finally, you can submit single items for automatic id.
# There are two ways to do this as shown below. Note that when you use the
# built-in Annotator functions to assign ids, there is no need to rebuild the
# annotator's index.
#
# Submit both the Type enum and the item as separate arguments.
annotator.assignId(Annotator.Type.COMPONENT_REF, model.component(0))
Remove ids from all items
In addition to creating id attributes, the Annotator can be used to clear them from all items in the model.
There are two ways in which this can be done.
First, a Model instance can be passed to the annotator for clearing.
This process will replace any previously stored model with the new one, as well as clearing all ids from the given model.
Alternatively, if a model is already stored in the annotator instance, the ids of that stored model can be cleared by calling the clearAllIds() function without arguments.
// Assuming we have two models, with references model1, and model2.
// Create an Annotator instance.
auto annotator = libcellml::Annotator::create();
// EITHER: Pass a new Model to the annotator so that its ids can be cleared.
annotator->clearAllIds(model1); // This will clear ids in model1,
// and associate model1 with the annotator.
// Note: There is no need to build the annotator's index beforehand as
// clearing the ids will also clear the index. The model1 given will be
// stored as the current model within the annotator.
// OR: Clear all ids in a model which is was previously associated with the annotator.
annotator->buildModelIndex(model2);
annotator->clearAllIds(); // This will clear ids in model2, the model stored in the annotator.
# Assuming we have two models, with references model1, and model2.
# Create an Annotator instance.
annotator = Annotator()
# EITHER: Pass a new Model to the annotator so that its ids can be cleared.
annotator.clearAllIds(model1) # This will clear ids in model1,
# and associate model1 with the annotator.
# Note: There is no need to build the annotator's index beforehand as
# clearing the ids will also clear the index. The model1 given will be
# stored as the current model within the annotator.
# OR: Clear all ids in a model which is was previously associated with the annotator.
annotator.buildModelIndex(model2)
annotator.clearAllIds() # This will clear ids in model2, the model stored in the annotator.
Retrieve an item of known type by id
Where you know the type of item (eg: Component, Variable etc) before retrieving it, a collection of helper functions exist, as demonstrated below.
Where the known type is an entity type, a pointer to the item is returned.
Where the type is a non-entity type, pointers to significant related items are returned, as discussed earlier.
// Create an Annotator.
auto annotator = libcellml::Annotator::create();
// Build the annotator to work with the model.
annotator->buildModelIndex(model);
// Retrieve entity items of known type using their id attribute.
auto myComponent = annotator->component("myComponentId");
auto myVariable = annotator->variable("myVariableId");
auto myReset = annotator->reset("myResetId");
auto myUnits = annotator->units("myUnitsId");
auto myImportSource = annotator->importSource("myImportSourceId");
auto myModel = annotator->model("myModelId");
// Retrieve non-entity items of known type using their id attribute.
// NOTE that the type of object returned by retrieving a non-entity item
// is defined in the text above.
// Connections are returned as a VariablePair type, where the first and second
// items in the pair define Variables in the first and second components of the
// connection. Note that as multiple variable pairs could exist between two
// given components, the return value for this function is not unique.
auto myConnection = annotator->connection("myConnectionId");
// Variable equivalences (from the map_variables) are returned as a VariablePair,
// where the first and second items in the pair define the Variables which are made
// equivalent by this mapping.
auto myMappedVariables = annotator->mapVariables("myMapVariablesId");
// Unit items are returned as a Unit pair, where the first item is a pointer to
// the parent Units item, and the second is the index at which the child unit item's
// attributes can be found.
auto myUnitItem = annotator->unit("myUnitId");
std::string myUnitsReference;
std::string myPrefix;
std::string myId;
double myExponent;
double myMultiplier;
// The first item in the pair is a pointer to the parent Units.
myUnitItem.first->unitAttributes(myUnitItem.second, // The second item in the pair is the index.
myReference, myPrefix, myExponent,
myMultiplier,myId);
// The location of a component in an encapsulation hierarchy is set by a component_ref
// block. Retrieving a component_ref item by id will return a pointer to the
// Component item which is located at that position in the encapsulation.
auto myReferencedComponent = annotator->componentRef("myComponentReferenceId");
// The reset_value and test_value block children of a Reset item are returned as a
// pointer to their parent Reset item.
auto myTestValueParent = annotator->testValue("myTestValueId");
auto myResetValueParent = annotator->resetValue("myResetValueId");
// Their values can then be retrieved using the test_value() and
// reset_value() functions on that Reset item parent.
auto myTestValue = myTestValueParent->test_value();
auto myResetValue = myResetValueParent->reset_value();
// An encapsulation item is returned as a pointer to its parent model.
auto myEncapsulatedModel = annotator->encapsulation("myEncapsulationId");
# Create an Annotator.
annotator = Annotator()
# Build the annotator to work with the model.
annotator.buildModelIndex(model)
# Retrieve entity items of known type using their id attribute.
my_component = annotator.component("myComponentId")
my_variable = annotator.variable("myVariableId")
my_reset = annotator.reset("myResetId")
my_units = annotator.units("myUnitsId")
my_import_source = annotator.importSource("myImportSourceId")
my_model = annotator.model("myModelId")
# Retrieve non-entity items of known type using their id attribute.
# NOTE that the type of object returned by retrieving a non-entity item
# is defined in the text above.
# Connections are returned as a VariablePair type, where the first and second
# items in the pair define Variables in the first and second components of the
# connection. Note that as multiple variable pairs could exist between two
# given components, the return value for this function is not unique.
my_connection = annotator.connection("myConnectionId")
# Variable equivalences (from the map_variables) are returned as a VariablePair,
# where the first and second items in the pair define the Variables which are made
# equivalent by this mapping.
my_mapped_variables = annotator.mapVariables("myMapVariablesId")
# Unit items are returned as a Unit pair, where the first item is a pointer to
# the parent Units item, and the second is the index at which the child unit item's
# attributes can be found.
my_unit_item = annotator.unit("myUnitId")
# The first item in the pair is a pointer to the parent Units.
# TODO: check how Python handles the std::pair class.
# The location of a component in an encapsulation hierarchy is set by a component_ref
# block. Retrieving a component_ref item by id will return a pointer to the
# Component item which is located at that position in the encapsulation.
my_referenced_component = annotator.componentRef("myComponentReferenceId")
# The reset_value and test_value block children of a Reset item are returned as a
# pointer to their parent Reset item.
my_test_value_parent = annotator.testValue("myTestValueId")
my_reset_value_parent = annotator.resetValue("myResetValueId")
# Their values can then be retrieved using the test_value() and
# reset_value() functions on that Reset item parent.
my_test_value = my_test_value_parent.test_value()
my_reset_value = my_reset_value_parent.reset_value()
# An encapsulation item is returned as a pointer to its parent model.
my_encapsulated_model = annotator.encapsulation("myEncapsulationId")
Retrieve an item of unknown type by id
In situations where you have an id string, but don’t know the type of the object it identifies, the item can be retrieved using a general Annotator::item(itemId) function.
The general item function will return an AnyItem pair.
The first part of the pair is an enum of the type of the object.
The second part is an std::any type object, which can be cast into the correct type.
This is demonstrated below.
// Create an Annotator.
auto annotator = libcellml::Annotator::create();
// Build the annotator to work with the model.
annotator->buildModelIndex(model);
// Retrieve an item of unknown type from the annotator.
auto anyItem = annotator->item("findThisId");
// Depending on the item's type (which is stored in the first part of the AnyItem
// pair), cast the second part to the appropriate pointer type. Note that this means
// first declaring a range of variables of different types to which the cast
// pointer can be assigned.
libcellml::ComponentPtr itemComponent;
libcellml::VariablePtr itemVariable;
libcellml::ResetPtr itemReset;
libcellml::UnitsPtr itemUnits;
libcellml::ImportSourcePtr itemImportSource;
libcellml::VariablePair itemVariablePair;
libcellml::Unit itemUnit;
switch (anyItem.first) {
case libcellml::Annotator::Type::COMPONENT:
case libcellml::Annotator::Type::COMPONENT_REF:
itemComponent = std::any_cast<libcellml::ComponentPtr>(anyItem.second);
break;
case libcellml::Annotator::Type::CONNECTION:
case libcellml::Annotator::Type::MAP_VARIABLES:
itemVariablePair = std::any_cast<libcellml::VariablePair>(anyItem.second);
break;
case libcellml::Annotator::Type::IMPORT:
itemImportItem = std::any_cast<libcellml::ImportSourcePtr>(anyItem.second);
break;
case libcellml::Annotator::Type::ENCAPSULATION:
case libcellml::Annotator::Type::MODEL:
itemModel = std::any_cast<libcellml::ModelPtr>(anyItem.second);
break;
case libcellml::Annotator::Type::RESET:
case libcellml::Annotator::Type::RESET_VALUE:
case libcellml::Annotator::Type::TEST_VALUE:
itemReset = std::any_cast<libcellml::ResetPtr>(anyItem.second);
break;
case libcellml::Annotator::Type::UNIT:
itemUnit = std::any_cast<libcellml::Unit>(anyItem.second);
break;
case libcellml::Annotator::Type::UNITS:
itemUnits = std::any_cast<libcellml::UnitsPtr>(anyItem.second);
break;
case libcellml::Annotator::Type::VARIABLE:
itemVariable = std::any_cast<libcellml::VariablePtr>(anyItem.second);
break;
}
}
// Note also that attempting to cast into the wrong type will trigger a
// "bad any cast" exception.
try {
assert(itemVariable.first == Annotator::Type::VARIABLE);
auto thisWillNotWork = std::any_cast<libcellml::UnitsPtr>(itemVariable.second);
}
catch(const std::bad_any_cast& e) {
// Depending on your system, this will return a "bad any cast", "bad any_cast"
// or "bad_any_cast" message.
std::cout << e.what() << std::endl;
}
# TODO
Locate items with duplicate ids
The Annotator functionality can be used to report id strings which have been duplicated within a model’s scope, as well as to return all items associated with a duplicated id string.
// Create an Annotator instance.
auto annotator = libcellml::Annotator::create();
// Create a model, with the id string "duplicateId" used on a component
// and a variable item, and an id string "anotherDuplicateId" on a units
// and encapsulation item.
auto model = libcellml::Model::create("myModelName");
auto component = libcellml::Component::create("myComponentName");
auto variable = libcellml::Variable::create("myVariableName");
auto units = libcellml::Units::create("myUnitsName");
model->addComponent(component);
model->addUnits(units);
component->addVariable(variable);
// Set the ids.
component->setId("duplicateId");
variable->setId("duplicateId");
units->setId("anotherDuplicateId");
model->setEncapsulationId("anotherDuplicateId");
// Pass the model to the annotator and build the index.
annotator->buildModelIndex(model);
// Retrieve a list of duplicated ids from the annotator.
auto duplicateIdList = annotator->duplicateIds();
// Now duplicateIdList is a vector of strings of the ids which are duplicated.
// In this example it will contain "duplicateId", and "anotherDuplicateId".
# Create an Annotator instance.
annotator = Annotator()
# Create a model, with the id string "duplicateId" used on a component
# and a variable item, and an id string "anotherDuplicateId" on a units
# and encapsulation item.
model = Model("myModelName")
component = Component("myComponentName")
variable = Variable("myVariableName")
units = Units("myUnitsName")
model.addComponent(component)
model.addUnits(units)
component.addVariable(variable)
# Set the ids.
component.setId("duplicateId")
variable.setId("duplicateId")
units.setId("anotherDuplicateId")
model.setEncapsulationId("anotherDuplicateId")
# Pass the model to the annotator and build the index.
annotator.buildModelIndex(model)
# Retrieve a list of duplicated ids from the annotator.
duplicateIdList = annotator.duplicateIds()
# Now duplicateIdList is a vector of strings of the ids which are duplicated.
# In this example it will contain "duplicateId", and "anotherDuplicateId".
Retrieve items with duplicated ids
Items with a unique id can be retrieved using the item("uniqueId") function, items whose ids are not unique must be retrieved with the items("duplicatedId") function instead.
// Create an Annotator instance.
auto annotator = libcellml::Annotator::create();
// Pass the model to the annotator and build the index.
annotator->buildModelIndex(model);
auto duplicatedIdItems = annotator->items("duplicatedId");
// The duplicateIdItems is a vector of AnyItem items; pairs whose first
// attribute is an Annotator::Type enum, and the second is an std::any cast
// of the item.
# Create an Annotator instance.
annotator = Annotator()
# Pass the model to the annotator and build the index.
annotator.buildModelIndex(model)
duplicatedIdItems = annotator.items("duplicatedId")
# The duplicateIdItems is a vector of items with "duplicatedId"
# as an id attribute.