Quick links: Tutorial - Examples - Files - Symbols.
Classes: Hierarchy - Index - List - Members.
Namespaces: Index - base - cs - display.
Cette page est disponible en français.

Rules and constraints manipulation

A conceptual graph rule (and a constraint) consists of two conceptual graphs, as well as a set of attachment points [Salvat and Mugnier, 1996]. In Cogitant, the data structure used to represent a rule is modelled on the model definition. So the cogitant::Rule class has two attributes that are graphs (the hypothesis and the conclusion) and a set of couples that are attachment points.

Representation of a rule

A rule is thus composed of two graphs, the hypothesis and the conclusion. Specifically, the cogitant::Rule::hypothesis() method provides an access to the hypothesis graph, and cogitant::Rule::conclusion() to the conclusion graph. The cogitant::Rule::connectionPoints() method returns a cogitant::SetOfCouples, i.e. a set of couples. Each of these couples represents an attachment point: the first couple element is the vertex identifier in the hypothesis graph, and the second element is the vertex identifier in the conclusion graph.

Construction of a rule

The cogitant::Rule constructor lets you create an empty rule (the hypothesis graph as well as the conclusion graph are empty, and there is no attachment point). Usually, to create a rule, one prefer using the cogitant::Environment::newRule() method rather than directly using the cogitant::Rule::Rule() constuctor. In this way, the rule is handled by the Environment, and you can access it and launch operations more easily.

To construct a rule, you can directly modify hypothesis and conclusion graphs: cogitant::Rule::hypothesis() and cogitant::Rule::Conclusion() methods provide access to graphs, and cogitant::Graph methods such as cogitant::Graph::newGenericConcept() or cogitant::Graph::newRelation() allow you to build graphs in question. If hypothesis and conclusion graphs exist elsewhere (loaded from a file), it may be easier to use them directly via the affectation operator (see below) or the cogitant::Rule::setHypothesisConclusion() method (in the latter case, the graphs passed as a parameter should not be in the environment, see the documentation of the method).

// let r be an empty rule
// let hy be the hypothesis graph identifier in the env environnement
// and co be the conclusion identifier
*(r.hypothesis()) = * env.graphs(hy);
*(r.conclusion()) = * env.graphs(co);

Once the hypothesis and conclusion are built, we must fix the attachment points by calling the cogitant::Rule::addConnectionPoint() method.

Example. Construction of the rule if a Local a is in_front_of a Local b then a is near b. The support is first loaded from a BCGCT file. A rule (empty) is then created. A first graph is created, it will be used to build the rule hypothesis. This graph is built in a classic way by calls to cogitant::Graph::newGenericConcept(), cogitant::Graph::newRelation(), cogitant::Graph::link(). This graph is then used as a hypothesis of the rule: note the call to the cogitant::Rule::hypothesis() method and the use of the affectation operator on graphs to copy the graph that has just been built in the hypothesis graph of the rule. Note that after this operation, a copy of the graph has been integrated into the rule, the temporarily graph that has been used for the building can then be destroyed. To build the conclusion, an alternative is used: the conclusion graph (empty at the rule creation time) is directly modified. This second method is probably easier than the first. Finally, the two attachment points (a and b) are created from identifiers of vertices of type Local.

#include <iostream>
using namespace cogitant;
int main(int, char* [])
{
env.readSupport("bcgct/sisyphus/sisyphus.bcs");
iSet irule = env.newRule();
Rule * rule = env.rules(irule);
// Construction of an hypothesis graph
iSet igraph1tmp = env.newGraph();
Graph * graph1tmp = env.graphs(igraph1tmp);
iSet s1 = graph1tmp->newGenericConcept("Local");
iSet s2 = graph1tmp->newGenericConcept("Local");
iSet s3 = graph1tmp->newRelation("in_front_of");
graph1tmp->link(s3, 1, s1);
graph1tmp->link(s3, 2, s2);
* rule->hypothesis() = *graph1tmp;
env.deleteGraph(igraph1tmp);
// Construction of a conclusion graph
Graph * conc = rule->conclusion();
iSet t1 = conc->newGenericConcept("Local");
iSet t2 = conc->newGenericConcept("Local");
iSet t3 = conc->newRelation("near");
conc->link(t3, 1, t1);
conc->link(t3, 2, t2);
// Connection points
rule->addConnectionPoint(s1, t1);
rule->addConnectionPoint(s2, t2);
// Display in BCGCT format
env.writeGraph(std::cout, irule, IOHandler::BCGCT);
return 0;
}


Note that variables s1 and s2 are used as vertex identifiers of the hypothesis while they actually are temporary identifiers of the graph graph1tmp. This is actually possible because the allocation operator of Graph preserves identifiers, so if s1 refers to a vertex s of graph1tmp, the same value s1 refers to the copy of s in the copy of graph1tmp (i.e. the hypothesis graph).

However, the most easy and effective way to create a rule is probably to write and then load a BCGCT or CoGXML file. Indeed, these two formats enable the representation of rules, and Input/output operations of CoGITaNT can easily load and save rules. cogitant::Environment::writeGraph(), cogitant::Environment::writeGraphs() methods can take as parameters rule identifiers, and generate the BCGCT or CoGXML form of the selected rules. Similarly, the cogitant::Environment::readGraphs() method allows you to read files containing rules.

Operations on rules

As for other model objects, cogitant::Rule methods only provide the basic manipulation functions. Model operations are accessible through subclasses of cogitant::Operation. In the case of rules, it is actually:

Instead of directly using these operations, it is easier to use the "shortcut" methods available in the cogitant::Environment class. The program below calculates and displays the application of a rule on a graph.

#include <iostream>
using namespace std;
using namespace cogitant;
int main(int, char* [])
{
env.readSupport("bcgct/sisyphus/sisyphus.bcs");
iSet fact = env.readGraphs("bcgct/sisyphus/locals.bcg");
iSet rule = env.readGraphs("bcgct/sisyphus/near_3.bcr");
ResultOpeProjection applications;
env.ruleApplications(env.graphs(fact), env.rules(rule), applications, false);
cout << applications.size() << " applications." << endl;
for (Set<Projection*>::const_iterator i=applications.projections()->begin();
i != applications.projections()->end(); ++i)
{
cout << "projection: " << (**i) << endl;
Set<GraphObject *> const * nodeshypt = env.rules(rule)->hypothesis()->nodes();
cout << "images (1st way): ";
for (iSet j=nodeshypt->iBegin(); j!=nodeshypt->iEnd(); nodeshypt->iNext(j))
cout << (*i)->getSecond(j) << " ";
cout << endl;
cout << "images (2nd way): ";
GraphSubset imgs(env.graphs(fact));
(*i)->images(imgs);
for (iSet k=imgs.first(); k!=ISET_NULL; k=imgs.next(k))
cout << k << " ";
cout << endl;
}
return 0;
}

In some cases, we wish to apply a set of rules on a graph in order to saturate it. We call the resulting graph the closure of the graph by the set of rules. This means that all rule applications that can be done (dont forget that if a rule can be applied once on a graph, then it can be indefinitely applied) only add some redundancy. The cogitant::Environment::rulesClosure() method enables to easily saturate a graph. By default, as is the case in the example below, it uses all the environment rules on the passed graph, but you can choose the rules that are taken into account, by using the second parameter (optional) of this method which is a set of rules.

#include <iostream>
using namespace std;
using namespace cogitant;
int main(int, char* [])
{
try
{
env.readSupport("bcgct/sisyphus/sisyphus.bcs");
iSet fact = env.readGraphs("bcgct/sisyphus/locals.bcg");
env.readGraphs("bcgct/sisyphus/near_1.bcr");
env.readGraphs("bcgct/sisyphus/near_2.bcr");
env.readGraphs("bcgct/sisyphus/near_3.bcr");
unsigned long nba = env.rulesClosure(env.graphs(fact));
cout << nba << " applications." << endl;
env.writeGraph(cout, fact, IOHandler::BCGCT);
}
catch (Exception const & e)
{
cout << e << endl;
}
return 0;
}

Representation of a constraint

The file below has 2 constraints, in BCGCT format, defined on the Sisyphus support. The first expresses that an Office needs to be linked by at least once local_caracteristic relation to a Local_attribute. It is a positive constraint: Any projection from the constraint condition into the graph to be checked must be capable of being extended into a projection from the obligation into the graph to be checked. The second constraint is a negative constraint, it says a desk can not be small and large at the same time.

{BCGCT:3;App:"cogitant 5.2.90";Encoding:UTF-8}
Begin
PositiveConstraint:localattribute;
Condition:
Graph:a0;
Concepts:
c1=[Office:*:**];
Relations:
Edges:
EndGraph;
Obligation:
Graph:b0;
Concepts:
c1=[Office:*:**];
c2=[Local_attribute:*:**];
Relations:
r3=(local_caracteristic);
Edges:
r3,c1,1;
r3,c2,2;
EndGraph;
ConnectionPoints:
(c1,c1);
EndPositiveConstraint;
NegativeConstraint:localsize;
Condition:
Graph:a1;
Concepts:
c1=[Office:*:**];
c2=[Big:*:**];
c3=[Small:*:**];
Relations:
r4=(local_caracteristic);
r5=(local_caracteristic);
Edges:
r4,c1,1;
r4,c2,2;
r5,c1,1;
r5,c3,2;
EndGraph;
Interdiction:
Graph:b1;
Concepts:
Relations:
Edges:
EndGraph;
ConnectionPoints:
EndNegativeConstraint;
NegativeConstraint:localsizebis;
Condition:
Graph:a2;
Concepts:
Relations:
Edges:
EndGraph;
Interdiction:
Graph:b2;
Concepts:
c1=[Office:*:**];
c2=[Big:*:**];
c3=[Small:*:**];
Relations:
r4=(local_caracteristic);
r5=(local_caracteristic);
Edges:
r4,c1,1;
r4,c2,2;
r5,c1,1;
r5,c3,2;
EndGraph;
ConnectionPoints:
EndNegativeConstraint;
NegativeConstraint:localsizeter;
Condition:
Graph:a3;
Concepts:
c1=[Local:*:**];
c2=[Big:*:**];
Relations:
r3=(local_caracteristic);
Edges:
r3,c1,1;
r3,c2,2;
EndGraph;
Interdiction:
Graph:b3;
Concepts:
c1=[Central:*:**];
c2=[Office:*:**];
Relations:
r3=(local_caracteristic);
Edges:
r3,c2,1;
r3,c1,2;
EndGraph;
ConnectionPoints:
EndNegativeConstraint;
End

You can load constraints from a BCGCT file, but also directly create a constraint into memory by calling methods of the library. To create a constraint, the method cogitant::Environment::newConstraint() has to be called. This method takes a boolean parameter representing false to create a negative constraint, and true for a positive constraint. Then, vertices can be added to the graphs in the same way as in a cogitant::Rule.

Graph checkout operations according to a constraint

You can simply check if a graph satisfies a constraint by calling the cogitant::Environment::constraintSatisfaction() method. This method takes as a parameter a pointer on the graph to be checked and a pointer on the constraint. It returns true or false depending on whether the graph satisfies the constraint or not. The program below checks that the graph locals.bcg satisfies the constraint named localattribute of the 2contraints.bcc file presented above.

#include <iostream>
using namespace std;
using namespace cogitant;
int main(int, char* [])
{
try
{
env.readSupport("bcgct/sisyphus/sisyphus.bcs");
iSet fact = env.readGraphs("bcgct/sisyphus/locals.bcg");
env.readGraphs("bcgct/sisyphus/2constraints.bcc");
iSet localattribute = env.findObject("localattribute");
if (env.constraintSatisfaction(env.graphs(fact), env.constraints(localattribute)))
cout << "OK" << endl;
else
cout << "Error" << endl;
}
catch (Exception const & e)
{
cout << e << endl;
}
return 0;
}

In some cases, one need "more" than just a boolean result: one need to know the number of "errors", or the errors themselves. By errors, we mean the causes of constraint dissatisfaction. Whether it is a positive or a negative constraint, when a graph does not satisfy a constraint, this can be represented by one or two projections. In the case of a negative constraint, it is a projection from the condition which can be extended as a projection from the interdiction. In the case of a positive constraint, it is a projection from the condition into the graph (projection which can not be extended to the whole constraint-graph) (1 projection). The cogitant::Environment::constraintSatisfaction() method takes a third and a fourth optional parameter which are pointers a cogitant::ResultOpeProjection. If this is not a NULL pointer, the checkout operation uses the passed object to store projections that make the constraint not satisfied. By this way you can retrieve the number of projections that make the constraint not satisfied, or the projections themselves. Retrieving projections enable to display a message more explicit than "the constraint is not satisfied", because each projection lets you know exactly which vertices of the graph to be checked are the cause of the issue.

The following program builds a negative constraint, composed of a single vertex Office, then checks that the graph locals.bcg satisfies this constraint. Here, it does not checks it because the graph contains several vertices of type Office. There are several projections of constraint into the graph, these are not stored (call to cogitant::ResultOpeProjection::memoProjections()), but they are counted, without limit (call to cogitant::ResultOpeProjection::maxSize()). You can therefore display the number of "errors" (i.e. number of projections).

#include <iostream>
using namespace std;
using namespace cogitant;
int main(int, char* [])
{
try
{
env.readSupport("bcgct/sisyphus/sisyphus.bcs");
iSet fact = env.readGraphs("bcgct/sisyphus/locals.bcg");
iSet imyconstraint = env.newConstraint(false);
Constraint * myconstraint = env.constraints(imyconstraint);
myconstraint->second()->newGenericConcept("Office");
result.memoProjections(false);
result.maxSize(0);
bool satisf = env.constraintSatisfaction(env.graphs(fact), myconstraint, & result);
if (satisf)
cout << "OK" << endl;
else
cout << result.size() << " error(s)" << endl;
}
catch (Exception const & e)
{
cout << e << endl;
}
return 0;
}