Quick links: Tutorial - Examples - Files - Symbols.
Classes: Hierarchy - Index - List - Members.
Namespaces: Index - base - cs - display.
This page is available in English.

Manipulation de règles et contraintes

Une règle de graphes conceptuels (ainsi qu'une contrainte) est formée de deux graphes conceptuels ainsi que d'un ensemble de points d'attache [Salvat et Mugnier, 1996]. Dans Cogitant, la structure de données pour représenter une règle est calquée sur la définition du modèle. Ainsi la classe cogitant::Rule dispose de deux attributs qui sont des graphes (l'hypothèse et la conclusion) et d'un ensemble de couples qui sont les points d'attache. Les contraintes sont elles représentées sous la forme de graphes bicolores [Baget et al, 1999b] (voir Représentation d'une contrainte).

Représentation d'une règle

Une règle est donc composée de deux graphes, l'hypothèse et la conclusion. Plus précisément, la méthode cogitant::Rule::hypothesis() permet d'accéder au graphe hypothèse, et cogitant::Rule::conclusion() au graphe conclusion. La méthode cogitant::Rule::connectionPoints() retourne un cogitant::SetOfCouples, c'est-à-dire un ensemble de couples. Chacun de ces couples représente un point d'attache : le premier élément du couple est l'identificateur du sommet dans le graphe hypothèse, et le second élément est l'identificateur du sommet dans le graphe conclusion.

Construction d'une règle

Le constructeur de cogitant::Rule permet de créer une règle vide (le graphe hypothèse comme le graphe conclusion sont vides, et il n'y a aucun point d'attache). Habituellement, pour créer une règle, on préfèrera utiliser la méthode cogitant::Environment::newRule() plutôt que d'utiliser directement le constucteur cogitant::Rule::Rule(). De cette façon, la règle est gérée par l'Environment, et il est plus simple d'y accéder et de lancer des opérations.

Pour construire une règle, il est possible de modifier directement les graphes hypothèse et conclusion : les méthodes cogitant::Rule::hypothesis() et cogitant::Rule::conclusion() permettent d'accéder aux graphes, et les méthodes de cogitant::Graph telles que cogitant::Graph::newGenericConcept() ou cogitant::Graph::newRelation() permettent de constuire les graphes en question. Si les graphes hypothèse et conclusion existent par ailleurs (chargés à partir d'un fichier), il peut être plus simple de les utiliser directement en utilisant l'opérateur d'affectation (cf. ci-dessous) ou la méthode cogitant::Rule::setHypothesisConclusion() (dans ce dernier cas, les graphes passés en paramètre ne doivent pas être dans l'environnement, voir la documentation de la méthode).

// soit r une règle vide
// soit hy l'identificateur dans l'environnement env du graphe hypothèse
// et co l'identificateur de la conclusion
*(r.hypothesis()) = * env.graphs(hy);
*(r.conclusion()) = * env.graphs(co);

Une fois que l'hypothèse et la conclusion sont construits, il faut fixer les points d'attache en appelant la méthode cogitant::Rule::addConnectionPoint().

Exemple. Construction de la règle si un Local a est in_front_of un Local b alors a est near b. Le support est tout d'abord chargé à partir d'un fichier BCGCT. Une règle (vide) est alors créée. Un premier graphe est créé, il va être utilisé pour construire l'hypothèse de la règle. Ce graphe est constuit d'une façon classique par des appels à cogitant::Graph::newGenericConcept(), cogitant::Graph::newRelation(), cogitant::Graph::link(). Ce graphe est ensuite utilisé comme hypothèse de la règle : remarquer l'appel à la méthode cogitant::Rule::hypothesis() et l'utilisation de l'opérateur d'affectation sur les graphes pour copier le graphe qui vient d'être construit dans le graphe hypothèse de la règle. Notez qu'après cette opération, une copie du graphe a été incorporée à la règle, le graphe temporaire qui a été utilisé pour la constuction peut donc être détruit. Pour construire la conclusion, une autre possibilité est utilisée : le graphe conclusion (vide à la création de la règle) est modifié directement. Cette deuxième méthode est sans doute plus simple que la première. Enfin, les deux points d'attache (a et b) sont créés à partir des identificateurs des sommets de 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;
}


Notez que les variables s1 et s2 sont utilisées en tant qu'identificateurs de sommets de l'hypothèse alors qu'il s'agit d'identificateurs du graphe temporaire graph1tmp. Ceci est possible parce que l'opérateur d'affectation de Graph préserve les identificateurs, ainsi si s1 désigne un sommet s de graph1tmp, la même valeur s1 désigne la copie de s dans la copie de graph1tmp (i.e. le graphe hypothèse).

Mais la méthode la plus simple et efficace pour créer une règle est sans doute d'écrire un fichier BCGCT ou CoGXML et de charger ce fichier. En effet, ces deux formats permettent de représenter des règles, et les Opérations d'entrées/sorties de CoGITaNT permettent de charger et sauver des règles très simplement. Les méthodes cogitant::Environment::writeGraph(), cogitant::Environment::writeGraphs() peuvent prendre comme paramètres des identificateurs de règles, et génèrent la forme BCGCT ou CoGXML des règles sélectionnées. De même, la méthode cogitant::Environment::readGraphs() permet de lire des fichiers contenant des règles.

Opérations sur les règles

Comme pour les autres objets du modèle, les méthodes de cogitant::Rule ne fournissent que les fonctions de base de manipulation. Les opérations du modèle sont accessibles à travers des sous-classes de cogitant::Operation. Dans le cas des règles, il s'agit plus précisément :

Plutôt que d'utiliser directement ces opérations, il est plus simple d'utiliser les méthodes "raccourcis" disponibles dans la classe cogitant::Environment. Le programme ci-dessous calcule et affiche les applications d'une règle sur un graphe.

#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;
}

Dans certains cas, on désire appliquer un ensemble de règles sur un graphe afin de le saturer. On appelle le graphe obtenu la fermeture du graphe par l'ensemble de règles. Cela signifie que toutes les applications de règles qui peuvent être faites (rappelons que si une règle peut être appliquée une fois sur un graphe, elle peut être appliquée indéfiniment) ne font qu'ajouter de la redondance. La méthode cogitant::Environment::rulesClosure() permet de saturer facilement un graphe. Par défaut, comme c'est le cas dans l'exemple ci-dessous, elle utilise toutes les règles de l'environnement sur le graphe passé, mais il est possible de choisir les règles qui sont prises en compte, en utilisant le deuxième paramètre (optionnel) de cette méthode qui est un ensemble de règles.

#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;
}

Représentation d'une contrainte

Le fichier ci-dessous contient 2 contraintes au format BCGCT définies sur le support Sisyphus. La première exprime qu'un Office doit être relié au moins une fois par une relation local_caracteristic à un Local_attribute. Il s'agit d'une contrainte positive : Toute projection de la condition de la contrainte sur le graphe à vérifier doit pouvoir être étendue en une projection de l'obligation sur le graphe a vérifier. La deuxième contrainte est une contrainte négative, elle indique qu'un bureau ne peut être à la fois petit et grand.

{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

Il est possible de charger des contraintes à partir d'un fichier BCGCT, mais aussi de créer directement en mémoire une contrainte en appelant les méthodes de la bibliothèque. Pour créer une contrainte, il faut appeler la méthode cogitant::Environment::newConstraint(). Cette méthode prend un paramètre booléen qui vaut false pour créer une contrainte négative, et true pour une contrainte positive. Ensuite, des sommets peuvent être ajoutés aux graphes de la même façon que dans un cogitant::Rule.

Opérations de vérification d'un graphe par rapport à une contrainte

Il est possible de vérifier simplement si un graphe vérifie une contrainte en appelant la méthode cogitant::Environment::constraintSatisfaction(). Cette méthode prend comme paramètres un pointeur sur le graphe à vérifier et un pointeur sur la contrainte. Elle retoure true ou false selon que le graphe vérifie ou pas la contrainte. Le programme ci-dessous vérifie que le graphe locals.bcg vérifie la contrainte de nom localattribute du fichier 2contraints.bcc présenté ci-dessus.

#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;
}

Dans certains cas, on est intéressé par "plus" qu'un simple résultat booléen: on désire connaître le nombre d'"erreurs", ou les erreurs elles mêmes. Par erreurs, on entend ici les causes de non respect de la contrainte. Qu'il s'agisse d'une contrainte positive ou d'une contrainte négative, quand un graphe ne vérifie pas une contrainte, ceci peut être représenté par une ou deux projections. Dans le cas d'une contrainte négative, il s'agit d'une projection de la condition qui peut être étendue en une projection de l'obligation (2 projections). Dans le cas d'une contrainte positive, il s'agit d'une projection de la condition de la contrainte dans le graphe qui ne peut pas être étendue à une projection de l'obligation (une projection). La méthode cogitant::Environment::constraintSatisfaction() prend un troisième et quatrième paramètres, optionnels, qui sont des pointeurs sur cogitant::ResultOpeProjection. Si ce pointeur est non NULL, l'opération de vérification utilise l'objet passé pour stocker ls projections qui font que la contrainte n'est pas vérifiée. Il est ainsi possible de récupérer le nombre de projections qui font que la contrainte n'est pas vérifiée, ou les projections elles-mêmes. Récupérer les projections permet d'afficher un message plus explicite que "la contrainte n'est pas vérifiée", car chaque projection permet de connaître précisément quels sommets du graphe à vérifier sont la cause du problème.

Le programme suivant construit une contrainte négative, composée d'un seul sommet Office, puis vérifie que le graphe locals.bcg vérifie cette contrainte. Ici, il ne le vérifie pas car le graphe contient plusieurs sommets de type Office. Il y a plusieurs projections de la contrainte dans le graphe, celles-ci ne sont pas mémorisées (appel à cogitant::ResultOpeProjection::memoProjections()), mais elles sont comptées, sans limite (appel à cogitant::ResultOpeProjection::maxSize()). Il est donc possible d'afficher le nombre d' "erreurs" (c.-à-d. nombre de 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;
}