Formalizing the modeling language – Introduction
To be able to reason about your models through the back-end, it is necessary to have a definition of their semantics. Concretely, the semantics specifies the logical “meaning” of your model, and, in particular, what interests us for this application is that this meaning be executable so that we can run queries w.r.t. the semantics. The back-end utilizes several finite-domain constraint solvers to analyze the models and extract information. The exact mechanism for querying your model will be detailed in the Writing Queries section of this wiki.
To begin, it is necessary to determine what the logical semantics of your model are, either by looking for proposals in the literature or by otherwise defining them yourself if you are working with a new/unique/extended language. In particular, these semantics must be expressible in such a way that the logical theory they produce constitutes a constraint satisfaction problem over the finite domain of integers/booleans. Your semantics must therefore be based on the abstract syntax of your language, in such a way that your models can then be translated into a logical theory, whose “models” (in the model-theoretic sense) will correspond to satisfying assignments of the variables associated to the abstract syntactic elements of your model.
Once such a formalization of the language has been constructed, it is possible to begin defining the semantics of the language for use by the semantic translator. To do this, your language must first have a complete definition of its abstract syntax within VariaMos. Once this has been done, we can begin associating the semantics to the models.
A first example – Feature Models
Feature models have well defined semantics that have been presented in many occasions in the literature. For now, we will not delve into the attributes extension to these models, nor into the semantics of individual cardinalities for features, though both can be expressed (the reason for this omission is that not only does this bring added complexity but it also has implications for solver behavior when enumerating solutions).
The figure above presents a feature model with an intentional defects (it is in fact void). To able to detect these defects through automated analysis we must be able to express the semantics of the different types of constructions that exist within feature models. Let us then first consider the abstract syntax of the language so that we can have a clearer picture of the constituent components of Feature Models.
{
"elements": {
"Bundle": {"properties": [...,{"name": "Type","possibleValues": "And,Or,Xor,Range",...},...]},
"RootFeature": {"properties": [...,{"name": "Selected",...}]},
"AbstractFeature": {"properties": [...,{"name": "Selected",...}]},
"ConcreteFeature": {
"properties": [...,{"name": "Selected",...}]}
},
"relationships": {
"Bundle_Feature": {"source": "Bundle","target": ["AbstractFeature","ConcreteFeature"],...},
"RootFeature_Bundle": {"source": "RootFeature","target": ["Bundle"],...},
"RootFeature_Feature": {
"source": "RootFeature", "target": ["AbstractFeature","ConcreteFeature"],
"properties": [
{
"name": "Type",
"possibleValues": "Mandatory,Optional,Includes,Excludes,..."
},...
]
},
"AbstractFeature_Bundle": {"source": "AbstractFeature","target": ["Bundle"],...},
"ConcreteFeature_Bundle": {"source": "ConcreteFeature","target": ["Bundle"],...},
"AbstractFeature_Feature": {
"source": "AbstractFeature", "target": ["AbstractFeature","ConcreteFeature"],
"properties": [
{
"name": "Type",
"possibleValues": "Mandatory,Optional,Includes,Excludes,..."
},...
]
},
"ConcreteFeature_Feature": {
"source": "ConcreteFeature", "target": ["ConcreteFeature","AbstractFeature"],
"properties": [
{
"name": "Type",
"possibleValues": "Mandatory,Optional,Includes,Excludes,..."
},...
]
}
}
}
On the basis of this abstract syntax we can begin to construct the representation of our model in CLIF. Our goal is to provide a representation in logic for each semantically significant element of the model.
Defining the translation for elements
“elementTypes” and “elementTranslationRules”
The simplest translation that can be done is to directly represent a model element as a formula. This formula can only draw upon the information of the element, though it can also make use of the properties to, e.g., generate the symbols for an enumeration, in which case we can specify this as well. Simply put, all we’re doing is defining which element types (i.e. the types declared in the abstract syntax) are going to be translated only on their own basis. We define these types in the “element_types” attribute which is a list of these types.
For each of the types in that list, we define a set of (object) attributes that specify how to construct the CLIF formulas from the information in the model. We have one attribute named param
that defines the placeholder for the element’s ID in the actual formula.
With this placeholder, we can then write the constraint
attribute, which specifies the actual CLIF formula providing the semantics.
In general, when it comes to the elements themselves, we use their translation as a way to use their IDs as finite domain variables for the solver (since this is what interests us after all). We therefore translate them as unary predicates that serve as type constraints on these variables. In the specific case of enumerations, the type constraint predicate is actually binary, since we need to define the allowed symbols for the enumeration. With this in mind, we can take these values from one of the properties defined on the element. In this case, there is an attribute named enumMapping
that takes its values from the PossibleValues
property (searched by name, so make sure your properties have unique names for a given element type), and then provide a placeholder which will be used in the constraint
to define the list of symbols in the enumeration. We do expect the property to be declared (FOR NOW) as having a String
type and having a set of values in a single string connected by commas with no spaces. For now the predicates we can handle for the types are:
(bool <someID>)
Boolean variable.- `(enum () ) Enumeration Variable.
(int <someID>)
Integer Variable.
Let’s now examine how we translate the three types of features available in our Feature Model:
"elementTypes": [
"ConcreteFeature",
"RootFeature",
"AbstractFeature"
],
"elementTranslationRules": {
"RootFeature": {
"param": "F",
"constraint": "(and (bool UUID_F) (= UUID_F 1))",
"selectedConstraint": "(and (bool UUID_F) (= UUID_F 1))",
"deselectedConstraint": "(= 0 1)"
},
"AbstractFeature": {
"param": "F",
"constraint": "(bool UUID_F)",
"selectedConstraint": "(and (bool UUID_F) (= UUID_F 1))",
"deselectedConstraint": "(and (bool UUID_F) (= UUID_F 0))"
},
"ConcreteFeature": {
"param": "F",
"constraint": "(bool UUID_F)",
"selectedConstraint": "(and (bool UUID_F) (= UUID_F 1))",
"deselectedConstraint": "(and (bool UUID_F) (= UUID_F 0))"
}
},
Here we first define an attribute elementTypes
that contains a list of all the element types that are to be translated. Note that we have not yet talked about the bundles, since they are translated in a different manner. So, for now, we focus primarily only on the features.
With this done, we define the elementTranslationRules
, an object whose attributes define how each of the element types define before in the elementTypes
list is translated into a CLIF sentence. Considering what was mentioned before, it should be clear that each feature type has its own equation template. In this case we prepend UUID_
to the template since translations into the solvers require that we guarantee that a letter is the first character of a variable, moreover, for SWI-Prolog in particular, it must be a capital letter, hence we simply add this prefix to ensure that everything is represented correctly for the solvers. NOTE: This is a temporary restriction and will be fixed in a future release.
In the case of feature models, since selection status is a special property that is highly important, we also provide two additional attributes that can be set: selectedConstraint
and deselectedConstraint
which are alternative constraints that are used in place of the default constraint in case a feature has been selected or deselected respectively.
Attributes, Individual Cardinalities, and hierarchyTypes
and hierarchyTranslationRules
NOTE: These will be presented in the second example case. For various reasons, the behavior of the solvers when attributes are present on feature models is less than ideal, in particular, they will simply cycle through all the possible values of the attributes as distinct solutions instead of enumerating different feature combinations as a priority. It is for this reason that while available, it is not yet desirable to use the features attribute’s for analysis
NOTE 2: Individual cardinalities pose similar problems as before. These are available in the semantics but they involve some more complex manipulations that are unnecessary for a basic understanding of the system, they are therefore ignored for now
NOTE 3: There exist translation rules for elements whose translation is sensitive to their position in a tree-like hierarchy, like the root features, but that require information about their parents/descendants. Therefore, for now, these are not used for feature models but are a required parameter for the semantics specification, they are therefore just listed as empty.
"hierarchyTypes":[],
"hierarchyTranslationRules":{},
“relationTypes”, “relationTranslationRules” and “relationPropertySchema”
These rules are designed specifically to provide translations for edges between nodes in the model. We suppose the edges themselves have no variable associated to them and instead, we obtain two bindings. The first is that of the source of the edge and the second is always bound to the target of the edge. We can then use these nodes’ IDs in the constraint
.
"relationTypes": [
"Mandatory",
"Optional",
"Includes",
"Excludes"
"Range",
"Xor",
"And",
"Or"
],
"relationPropertySchema": {
"type": {
"index": 0,
"key": "value"
}
},
"relationTranslationRules": {
"Excludes": {
"params": [
"F1",
"F2"
],
"constraint": "(=< (UUID_F1 + UUID_F2) 1)"
},
"Includes": {
"params": [
"F1",
"F2"
],
"constraint": "(>= UUID_F2 UUID_F1)"
},
"Optional": {
"params": [
"F1",
"F2"
],
"constraint": "(>= UUID_F1 UUID_F2)"
},
"Mandatory": {
"params": [
"F1",
"F2"
],
"constraint": "(= UUID_F1 UUID_F2)"
}
}
It’s important to recognize that feature models type the relations through an attribute of the relation. For this reason, we provide a special attribute in the semantic definition relationPropertySchema
whose attribute type
tells the system that instead of using the default relation type to determine the type of the relation, it must perform a lookup on the properties of the relation to determine its type. These allowed types are then listed in the list relationTypes
. Concretely, this definition defines that it must look in the properties list of the relation and check the 1st (0 in programming order) element, and lookup the attribute whose key is value
. NOTE: This is unfortunately tied to limitations of the mxgraph system, and will hopefully be improved in a future release.
“relationReificationTypes”, “relationReificationTranslationRules”, “relationReificationTypeDependentExpansions”, “relationReificationPropertySchema”, and “relationReificationExpansions”
Here we come to the more complex translations, specifically those that have represent reified relations. This has several meanings, and has been designed so that we can accomodate both of the potential cases. Concretely, by reified relation we mean elements that represent a many-to-many, many-to-one or one-to-many relationship between other elements. The two possible cases are whether the relationship itself has a value and corresponding variable or not. This is not the case for bundles in Feature Models that merely represent one to many relationships.
First, relationReificationTypes
is, as before, a list representing the types that will be treated in this manner. Next, we come to the relationReificationTranslationRules
, where we continue with the earlier pattern, and associate an object to each type we declared before. Here we have a parameter list (param
) and a mapping for these parameters (paramMapping
) that behave in such a manner that we make one mapping for incoming edges and one for outgoing. Either of these can be a single edge, where we’d mark the mapping with "unique": true
. Finally, another special characteristic of the constraints we can employ here, is the fact that we can have additional parameters that benefit from a lookup within the element’s attributes, as is the case of the Bundle elements in Feature Models. When we mark a variable as not being unique, it is to be understood as a list.
To obtain, for example, the min
value for the Cardinality elements, we define first in "relationReificationTypeDependentExpansions"
the types that will have these additional expansions and then we provide a lookup schema in "relationReificationPropertySchema"
, which basically explains where to look in the element’s properties for the value to replace the placeholder with as was explained in an earlier section. This unfortunately means, that for now, at least, a minimal understanding of how the properties will be indexed by VariaMos is necessary. In addition, we define the more general expansions related to the nodes our element is connected with. In this case we declare that we will have the len and sum expansions, which are those that have been defined so far:
sum(<Var>)
will transform the list that corresponds toVar
into an actual sum, and thus this will create a sum constraint over the IDs contained in the list, i.e. UUID_F1 + UUID_F2 and so on.len(<Var>)
will replace itself with the number of elements in the list.
Finally, it is worth noting that since the bundle can have multiple types, it has been defined that the constraint can be an object and not just a string. Therefore, the relationReificationPropertySchema
‘s definition of ‘type’ will be used to determine the correct constraint to apply given the type defined in the attibute. NOTE: This behavior is therefore different from that used for determining the types of the other relations and unfortunately is again tied to limitations of how VariaMos Types elements.
"relationReificationTypes": [
"Bundle"
],
"relationReificationTranslationRules":{
"Bundle": {
"param": [
"F",
"Xs",
"min",
"max"
],
"paramMapping": {
"inboundEdges": {
"unique": true,
"var": "F"
},
"outboundEdges": {
"unique": false,
"var": "Xs"
}
},
"constraint": {
"Or": "(and (=< F (sum(Xs))) (=< (sum(Xs)) (F * len(Xs))) )",
"And": "(= (sum(Xs)) (F * len(Xs)))",
"Xor": "(= (sum(Xs)) F )",
"Range": "(and (=< (F * min) (sum(Xs))) (=< (sum(Xs)) (F * max)) )"
}
}
},
"relationReificationTypeDependentExpansions": {
"Bundle": {
"Range": [
"max",
"min"
]
}
},
"relationReificationPropertySchema": {
"type": {
"index": 0,
"key": "value"
},
"min": {
"index": 1,
"key": "value"
},
"max": {
"index": 2,
"key": "value"
}
},
"relationReificationExpansions": {
"params": [
"Xs"
],
"functions": [
"sum",
"len"
]
},
A second example case – RECOCOS
We will examine the construction of the logical semantics of a complex language, namely the RECOCOS language that is an evolution of the KAOS variant presented in “Using constraint programming to manage configurations in self-adaptive systems” by Sawyer et al.
Here is the model reconstructed in VariaMos:
Here is an abbreviated version of the abstract syntax including the primary elements that interest us:
{
"elements": {
"Goal": { "properties": [...,{ "name": "Selected", ... }] },
"Claim": { "properties": [...,{ "name": "Selected", ... }] },
"SoftGoal": {...},
"Cardinality": {"properties": [{"name": "From",...},{"name": "To",...}]},
"SoftInfluence": { "properties": [...,{ "name": "Selected", ... }] },
"ContextVariable": {"properties": [{"name": "SetValue",...},{"name": "PossibleValues",...}]},
"Operationalization": { "properties": [...,{ "name": "Selected", ... }] },
},
"restrictions": {},
"relationships": {
"SubGoal": {..., "source": "Goal", "target": ["Goal"],},
"SoftGoal_SoftGoal": {...},
"Claim_SoftGoal": {...},
"Cardinality_Goal": {...},
"SoftInfluence_SoftGoal": {...,
"properties": [{..., "name": "SatisfactionLevel","possibleValues": "--,-,=,+,++"}]},
"ContextVariable_SoftInfluence": { ..., "properties": [{"name": "Value",...}] },
"Operationalization_Claim_Goal": {..., "properties": [{"name": "Value", "possibleValues": " ,--,-,=,+,++" }]}
}
}
Once again, on the basis of this abstract syntax we can begin to construct the representation of our model in CLIF.
Building the RECOCOS semantics specification JSON
The JSON we must construct is composed of a series of attributes that define how the translations are to be carried out. We will analyze step by step how to write the translation rules for this language by going over the complete example of the RECOCOS language.
Keep in mind that every attribute we’ll examine will be part of a larger object which forms the complete semantics specification. So to use the example provided below, you would need to combine all of these subsections.
“elementTypes” and “elementTranslationRules”
As explained before for feature models, we provide the translation of the elements that depend only on themselves and their attributes.
Here then, we present the translations of three element types, Goals (Green parallelograms on the left) and Operationalizations (hexagons), which correspond to boolean variables in the semantics, and Context Variables (blue squares) which correspond to enumerations.
"elementTypes": [
"ContextVariable",
"Goal",
"Operationalization"
],
"elementTranslationRules": {
"ContextVariable": {
"param": "C",
"constraint": "(enum (Xs) UUID_C)",
"enumMapping": {
"var": "Xs",
"attribute":"PossibleValues"
}
},
"Goal": {
"param": "F",
"constraint": "(bool UUID_F)"
},
"Operationalization": {
"param": "F",
"constraint": "(bool UUID_F)"
}
},
“attributeTypes” and “attributeTranslationRules”
These two attributes work in a very similar way as the two above for the elements themselves. However, they are designed to provide translations for the properties associated to elements. They exist as a separate translation rule-set since not every language will necessarily need to use the properties for translation. The attributeTypes
attribute defines the element types (i.e., as before, the types declared in the abstract syntax) for which the properties should be translated. For now we assume a homogeneous treatment of all the attributes with the same name, if they were to occur in multiple elements of different types (though this might change to better reflect other types of models).
The attributeTranslationRules
attribute defines, just like the elementTranslationRules
attribute above, how to transform a given attribute into a CLIF formula. In this case, since RECOCOS needs some specific values for the context for it to be able to search for an optimal configuration, we have a property that defines the current value of the context variable (whose possible values come from the enumeration that is translated in the subsection above) named SetValue
. It acts just like the constraint
defined above, with the sole difference of being able to refer to the element’s id through the parent
attribute that provides a placeholder name for it. The param
attribute defines which field in the property will provide the concrete value that the template
placeholder will replace.
"attributeTypes": ["ContextVariable"],
"attributeTranslationRules": {
"SetValue": {
"parent": "C",
"param": "value",
"template": "V",
"constraint": "(= UUID_C V)"
}
},
“hierarchyTypes” and “hierarchyTranslationRules”
Next we have another type of translation that we may need to construct for certain languages, which is, in particular, necessary for our case: hierarchies. In this case, we envisage elements whose translation may vary depending on whether or not they are at the root or at the leaves of a hierarchical (tree-like) structure. For the RECOCOS model this is the case for the SoftGoals. We have a type definition as before through the hierarchyTypes
attribute.
Once we’ve defined these types, we define the translation rules depending on whether or not an element is a leaf in the hierarchy (Note that there may be several of these hierarchies). Simply put, the leafRule
attribute contains a definitions that works exactly the same as it does in the elementTranslationRules
. For the nodeRule
attribute, however, we must have an object that defines two parameters (placeholders for the constraint): the first, corresponding to the node’s ID, and, another, that will use information from the nodes one level down (or up, depending on how the hierarchy is constructed). An additional attribute named paramMapping
defines how the information for both the node’s ID and those of the other nodes in the hierarchy are related to the placeholders. Inside this paramMapping
attribute, node
corresponds to the placeholder for the node’s own ID and var
is for the list of the other nodes, with incoming
determining whether the hierarchy is looked for in the incoming or outgoing edges (since the node can also connect with other nodes of different types).
Finally, with these mappings, a constraint
can be written. This constraint can also use a series of expansions that have been defined and that operate on the lists of other nodes. For now we have defined:
sum(<Var>)
will transform the list that corresponds toVar
into an actual sum, and thus this will create a sum constraint over the IDs contained in the list.len(<Var>)
will replace itself with the number of elements in the list.
These expansions are treated in a bit more detail in the following subsection. Here we create a constraint
that says as you go up the hierarchy, a nodes value is the average of those underneath, or its own value if it’s a leaf in the hierarchy.
"hierarchyTypes": [
"SoftGoal"
],
"hierarchyTranslationRules": {
"SoftGoal": {
"nodeRule": {
"param": [
"F",
"Xs"
],
"paramMapping": {
"incoming": true,
"var": "Xs",
"node":"F"
},
"constraint": "(and (int (0 4) UUID_F) (= UUID_F ( ( sum(Xs) )/len(Xs) ) ) )"
},
"leafRule": {
"param": "F",
"constraint": "(int (0 4) UUID_F)"
}
}
},
“relationReificationTypes”, “relationReificationTranslationRules”, “relationReificationTypeDependentExpansions”, “relationReificationPropertySchema”, and “relationReificationExpansions”
In the case of RECOCOS, some of these relation reification elements also have a truth value, as is the case of claims, which therefore require supporting associating a boolean variable to the element itself (contrast the cardinality elements where the element itself has no meaning beyond the relation it reifies with Claims that do have their own truth value). With this in mind, and recognizing that the representation of certain aspects of the model are made more complex due to the limitations of mxGraph, we need to introduce additional constructs to, for example, disambiguate the types.
relationReificationTypes
is, as before, a list representing the types that will be treated in this manner. Next, we come to the relationReificationTranslationRules
, where we continue with the earlier pattern, and associate an object to each type we declared before. Here we have a parameter list (param
) and a mapping for these parameters (paramMapping
) that behave in precisely the same as in the case hierarchical types, with the difference being that we now make one mapping for incoming edges and one for outgoing, and not just one. Either of these can be a single edge, where we’d mark the mapping with "unique": true
. Finally, another special characteristic of the constraints we can employ here, is the fact that we can have additional parameters that benefit from a lookup within the element’s attributes, as is the case of the Cardinality elements in RECOCOS.
To obtain, for example, the min
value for the Cardinality elements, we define first in "relationReificationTypeDependentExpansions"
the types that will have these additional expansions and then we provide a lookup schema in "relationReificationPropertySchema"
, which basically explains where to look in the element’s properties for the value to replace the placeholder with. This unfortunately means, that for now, at least, a minimal understanding of how the properties will be indexed by VariaMos is necessary. In addition, we define the more general expansions related to the nodes our element is connected with. In this case we declare that we will have the len and sum expansions mentioned in the subsection above.
Missing ::/2 and forall
It is worth noting that there are additional constructs that can be accessed within the constraint
so that one can truly make use of every element of the model. The first of these is the edge(_)
function, which allows one to refer specifically to the edge connecting to a particular element from the element we’re translating. Let’s examine in detail the case of the Claims. We have a constraint that reads:
(and (bool C) (iff (= C 1) (forall (x:Xs) (if (= x 1) (=< F edge(x)::Value) ) ) ) )
If we analyze this formula in detail, we can observe that we reference the edge of an x
placeholder (whose nature will be detailed below). This x
refers to another node in our model and, therefore, by invoking edge(x)
in our template, we are referencing the edge (incoming or outgoing) to this node. Now, in this case in particular, we are interested in the value of the Value
attribute set on this edge. We therefore introduce our second operator, the double colon ::
operator, whose purpose is precisely that of giving access to a node’s attributes to generate a constraint. In this way, when we use the expression edge(x)::Value
we will see the entire expression be replaced with whatever value we have set for the attribute in question. We must therefore be vigilant to only use attribute whose presence we are certain of.
In addition, in this formula, we also observe the expression forall (x:Xs)
. This expression is rather important, as it allows us to be able to refer to the specific elements of a given set of nodes without simply using expression expansions like sum
or len
. Specifically, what it allows us to do is construct the conjunction of the inner expression of the quantifier by instantiating the variable to each value in the set. In this case, since we quantify over Xs
which represent the set of incoming edges, we will see each edge’s ID replace x
in the set of conjuncts. Moreover, tying this to the edge
and ::
operators, we can make use of all the information of adjacent nodes in the graph.
"relationReificationTypes": [
"Cardinality",
"Claim",
"SoftInfluence"
],
"relationReificationTranslationRules": {
"Cardinality": {
"param": [
"F",
"Xs",
"min",
"max"
],
"paramMapping": {
"inboundEdges": {
"unique": false,
"var": "Xs"
},
"outboundEdges": {
"unique": true,
"var": "F"
}
},
"constraint": {
"Cardinality": "(and (=< (F * min) (sum(Xs))) (= (sum(Xs)) (F * max)) )"
}
},
"Claim": {
"param": [
"C",
"F",
"Xs"
],
"paramMapping": {
"node": "C",
"inboundEdges": {
"unique": false,
"var": "Xs"
},
"outboundEdges": {
"unique": true,
"var": "F"
}
},
"constraint": {
"Claim": "(and (bool C) (iff (= C 1) (forall (x:Xs) (if (= x 1) (=< F edge(x)::Value) ) ) ) )"
}
},
"SoftInfluence": {
"param": [
"S",
"F",
"Xs"
],
"paramMapping": {
"node": "S",
"inboundEdges": {
"unique": true,
"var": "F"
},
"outboundEdges": {
"unique": false,
"var": "Xs"
}
},
"constraint": {
"SoftInfluence": "(and (bool S) (iff (= S 1) (if (= F edge(F)::Value) (forall (x:Xs) (= x edge(x)::SatisfactionLevel) ) ) ) )"
}
}
},
"relationReificationTypeDependentExpansions": {
"Cardinality": {
"Cardinality": [
"max",
"min"
]
}
},
"relationReificationPropertySchema": {
"min": {
"index": 0,
"key": "value"
},
"max": {
"index": 1,
"key": "value"
}
},
"relationReificationExpansions": {
"params": [
"Xs"
],
"functions": [
"sum",
"len"
]
},
“relationTypes” and “relationTranslationRules”
These rules are designed specifically to provide translations for edges between nodes in the model. We suppose the edges themselves have no variable associated to them and instead, we obtain two bindings. The first is that of the source of the edge and the second is always bound to the target of the edge. We can then use these nodes’ IDs in the constraint
.
"relationTypes": [
"SubGoal"
],
"relationTranslationRules": {
"SubGoal": {
"params": [
"F1",
"F2"
],
"constraint": "(= UUID_F1 UUID_F2)"
}
},
“ignoredRelationTypes” and “symbolMap”
For the sake of completeness, we must declare all model elements that do not have a specific translation and that is safe to ignore. We do this by referencing their types in “ignoredRelationTypes”.
In addition, before the translation is complete, it is possible that in our model we use symbols that do not translate well into the solvers and must therefore be replaced. In particular, for RECOCOS, we use “–“, “-“, “=”, “+”, and “++” to symbolize the scale 0-4. Since we cannot directly send these elements to the solver, it is necessary to transform them into their numeric counterparts. To do this, the translator makes an additional pass at the end of translation where it uses the symbolMap
object to determine the replacements for all the symbols that are mentioned there.
"ignoredRelationTypes": [
"SoftGoal_SoftGoal",
"Claim_SoftGoal",
"Cardinality_Goal",
"SoftInfluence_SoftGoal",
"ContextVariable_SoftInfluence",
"Operationalization_Claim_Goal"
],
"symbolMap":{
"++":"4",
"+": "3",
"=": "2",
"-": "1",
"--": "0"
}