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

Opérations

Cette section décrit l'utilisation des opérations fournies dans la bibliothèque. Certaines de ces opérations (projection, entrées/sorties) ont déjà été vues dans les sections précédentes, sous la forme de méthodes de la classe cogitant::Environment. Mais ces méthodes ne sont que des raccourcis pour l'utilisation de cogitant::Operation (et ses sous-classes), c'est pourquoi, dans le cadre d'une utilisation avancée de la bibliothèque, il est préférable de connaître l'utilisation de cette classe pour bien comprendre le fonctionnement des méthodes correspondant aux opérations définies sur le modèle, les fonctionnalités d'entrées/sorties ainsi que pour définir de nouvelles opérations. Dans cette partie, nous détaillons les principales opérations de la bibliothèque, et décrivons la façon d'utiliser ces opérations en tant qu'instances de sous-classes de cogitant::Operation et à travers les méthodes "raccourcis" de cogitant::Environment.

L'approche choisie pour offrir des opérations dans la bibliothèque consiste donc à utiliser des objets instances de cogitant::Operation (ou plutôt de ses sous-classes, car il s'agit d'une classe abstraite). Cette solution a été préférée à la définition de méthodes dans la classe cogitant::Graph ou cogitant::Rule car elle offre plus de flexibilité. En effet, que ce soit pour définir de nouvelles opérations (par exemple la gestion d'un nouveau format de fichier) ou pour fournir une nouvelle implantation d'opérations existantes (par exemple un algorithme de projection plus efficace), la solution consistant à utiliser des méthodes de cogitant::Graph demande de définir une nouvelle sous classe, dans laquelle la méthode correspondant à l'opération est redéfinie. Ceci pose évidemment le problème de la définition de plusieurs nouvelles opérations : dans ce cas, comment organiser la relation d'héritage sur la (les) nouvelle(s) classe(s). Au contraire, la solution retenue permet de définir de nouvelles classes d'opérations qui recoivent comme paramètres les objets du modèle. Ainsi, il est très simple de définir de nouvelles implantations d'opérations existantes ou de nouvelles opérations. De plus, la bibliothèque manipulant des références sur des instances de (sous classes de) cogitant::Operation, il est très simple d'incorporer ces nouvelles opérations à la bibliothèque, et d'utiliser des méthodes de la bibliothèque qui appeleront de façon totalement transparente les nouvelles opérations.

De façon générale, les opérations s'utilisent en instanciant un objet d'une sous-classe de cogitant::Operation, en appelant les méthodes setParamxxx() pour fixer les différents paramètres de l'opération, puis en appelant la méthode run() qui effectue le calcul. Enfin, les méthodes getResultxxx() permettent de récupérer le résultat.

Opérations de graphes conceptuels

Détermination des composantes connexes

Les graphes conceptuels manipulés dans Cogitant peuvent être non connexes. Cependant la structuration d'un graphe en composantes connexes peut être utile, c'est pourquoi plusieurs méthodes de la bibliothèque permettent de déterminer si un graphe est connexe, quel est le nombre de composantes connexes d'un graphe et quelles sont ces composantes connexes (les sommets contenus dans chaque composante). Ces différentes informations peuvent être calculées en appelant les méthodes cogitant::Environment::isConnected() (connexité du graphe), cogitant::Environment::connectedComponentsSize() (nombre de composantes connexes) et cogitant::Environment::connectedComponents() (composantes connexes). Toutes ces méthodes prennent comme paramètre un (pointeur sur un) cogitant::Graph et un cogitant::iSet repérant l'identificateur du cogitant::InternalGraph dont la connexité doit être déterminée (par défaut 0, c'est à dire le graphe de niveau 0). En effet, dans le cas de graphes emboîtés, un cogitant::Graph contient plusieurs "graphes", et s'il n'y a aucun intérêt à déterminer le nombre de composantes connexes de tous ces graphes, il peut être intéressant de déterminer cette information sur l'un d'entre eux (et pas seulement sur le graphe de niveau 0).

#include <iostream>
using namespace std;
int main(int, char* [])
{
env.readSupport("bcgct/sisyphus/sisyphus.bcs");
cogitant::iSet ig = env.readGraphs("bcgct/sisyphus/query.bcg");
cogitant::Graph * g = env.graphs(ig);
if (env.isConnected(g))
cout << "graph is connected." << endl;
else
cout << "graph is not connected." << endl;
cout << "graph has " << env.connectedComponentsSize(g) << " connected components." << endl;
vector<cogitant::GraphSubset> cc;
env.connectedComponents(g, cc);
cout << "components:" << endl;
for (vector<cogitant::GraphSubset>::const_iterator i=cc.begin(); i!=cc.end(); i++)
cout << (*i).selectedNodes() << " nodes: " << (*i) << endl;
return 0;
}

Calcul de la somme disjointe

La somme disjointe est accessible simplement à partir de la méthode cogitant::Environment::disjointSum() qui prend comme paramètres deux (pointeurs sur des) cogitant::Graph et qui modifie le premier pour lui rajouter le second (qui n'est pas modifié). Cette méthode utilise en fait la classe cogitant::OpeDisjointSum, dont la documentation décrit les différentes fonctionnalités offertes par cette classe qui permet d'effectuer des opérations plus complexes que la simple somme disjointe (cette opération est notamment utilisée lors de chargement de fichiers quand une référence à un graphe déjà existant est faite dans un emboîtement (le graphe doit alors être copié comme graphe emboîté, et les classes de coréférences doivent être éventuellement mises à jour) et lors de l'application d'une règle selon une projection (ajout de la conclusion et fusion de sommets, voir plus bas)).

Vérifications d'un graphe

Calcul des projections

La recherche de projections d'un graphe dans un autre se fait à l'aide de la méthode cogitant::Environment::projections(), à qui on doit passer le graphe projeté et le graphe dans lequel on projette. Cette méthode est en fait surchargée et peut prendre comme paramètres des identificateurs de graphes (cogitant::iSet identifiant le graphe dans l'environnement) ou des pointeurs sur des cogitant::Graph. Le troisième paramètre de cette méthode, un cogitant::ResultOpeProjection passé par référence, contiendra après l'appel le résultat de l'exécution de la méthode. Mais cet objet permet aussi de configurer la recherche. En effet, selon les usages, on peut être intéressé par différentes recherches, qui seront plus ou moins couteuses en temps et en mémoire. On peut distinguer principalement 3 usages, qui sont présentés ici du moins couteux au plus couteux, et illustrés plus bas par un exemple.

  1. Si on est simplement intéressé par l'existence d'une projection entre les deux graphes, il est inutile que l'opération de recherche des projections en cherche plusieurs ou mémorise les couples de sommets composant chaque projection trouvée. Dans ce cas, il est pertinent d'appeler les méthodes cogitant::ResultOpeProjection::memoProjections avec false comme paramètre (pour signaler que les projections ne doivent pas être mémorisées), et cogitant::ResultOpeProjection::maxSize(cogitant::nSet) avec comme paramètre 1 (pour signaler que la recherche doit être interrompue dès qu'une projection est trouvée). Ces deux méthodes doivent être appelées sur le cogitant::ResultOpeProjection avant l'appel à cogitant::Environment::projections(). Une fois que cette méthode a été appelée, il est nécessaire d'appeler cogitant::ResultOpeProjection::isEmpty() pour savoir si une projection a été trouvée.
  2. Dans le cas où on cherche simplement à savoir le nombre de projections sans chercher à mémoriser les couples de sommets, il faut appeler cogitant::ResultOpeProjection::memoProjections() avec false comme paramètre avant l'appel à cogitant::Environment::projections(). Après l'appel, la méthode cogitant::ResultOpeProjection::size() permet de connaitre le nombre de projections trouvées.
  3. Enfin, dans le cas général, on cherche toutes les projections d'un graphe dans un autre, et on est intéressé par les projections elles-mêmes, c'est-à-dire, pour chaque projection trouvée, par l'image de chaque sommet du graphe projeté. Dans ce cas, aucune méthode particulière de cogitant::ResultOpeProjection ne doit être appelée. Après l'appel cogitant::Environment::projections(), la méthode cogitant::ResultOpeProjection::size() permet d'accéder au nombre de projections trouvées, cogitant::ResultOpeProjection::projections() à l'ensemble des projections trouvées, c'est-à-dire une instance de cogitant::Set<cogitant::Projection*>. Il est ensuite possible de parcourir cet ensemble (cf. Les classes conteneurs) pour effectuer un traitement particulier sur chaque projection trouvée.

Exemple. L'exemple ci-dessous illustre les trois cas présentés ci-dessus. L'affichage des projections dans le troisième cas fait appel à l'opérateur de sortie de cogitant::Projection qui affiche des couples d'identificateurs de sommets dans les graphes : le premier élément de chaque couple est l'identificateur (iSet) d'un cogitant::GraphObject du graphe projeté et le second élément est l'identificateur de son image.

#include <iostream>
using namespace std;
using namespace cogitant;
int main(int, char* [])
{
env.readSupport("bcgct/bucolic/bucolic.bcs");
iSet g1 = env.readGraphs("bcgct/bucolic/sapquery.bcg");
iSet g2 = env.readGraphs("bcgct/bucolic/sleepandfish.bcg");
// 1
proj1.memoProjections(false);
proj1.maxSize(1);
env.projections(g1, g2, proj1);
if (proj1.isEmpty())
cout << "Pas de projection" << endl;
else
cout << "Il y a (au moins) une projection" << endl;
// 2
proj2.memoProjections(false);
env.projections(g1, g2, proj2);
cout << "Nombre de projections " << proj2.size() << endl;
// 3
env.projections(g1, g2, proj3);
Set<Projection *> const * projs = proj3.projections();
for (iSet i = projs->iBegin(); i != projs->iEnd(); projs->iNext(i))
{
cout << (i+1) << " : ";
cout << (* projs->iGetContent(i)) << endl;
}
return 0;
}

Itérateur de projections
A l'aide des classes cogitant::OpeProjectionBundle et cogitant::ProjectionIterator, les projections ne sont pas calculées directement par un simple appel (comme c'est le cas avec cogitant::Environment::projections()) : Après une initialisation par un appel à cogitant::OpeProjectionBundle::begin(), un cogitant::ProjectionIterator calcule la cogitant::Projection suivante à chaque fois que son opérateur ++ est appelé. Notez que la modification des deux graphs alors que les projections sont en cours de calcul est strictement interdite. Notez aussi qu'un cogitant::OpeProjectionBundle ne peut calculer, à un moment donné, seulement les projections entre deux graphes, même si deux cogitant::ProjectionIterator sont déclarés. Donc, afin de calculer simultanément les projections entre plusieurs couples de graphes, plusieurs cogitant::OpeProjectionBundle sont nécessaires, et ils peuvent être obtenus par cogitant::Environment::newOpeProjectionBundle().

Exemple.

#include <iostream>
using namespace std;
using namespace cogitant;
int main(int, char* [])
{
env.readSupport("bcgct/sisyphus/sisyphus.bcs");
iSet g1 = env.readGraphs("bcgct/sisyphus/localsgen2.bcg");
iSet g2 = env.readGraphs("bcgct/sisyphus/localsgen.bcg");
unsigned long nbproj = 0;
for (ProjectionIterator i=opeproj->begin(g1, g2); i!=opeproj->end(); i++, nbproj++)
cout << *i << endl;
cout << nbproj << " projections found." << endl;
delete opeproj;
return 0;
}

Modifications de l'opération de recherche de projections
Comme toutes les autres méthodes de cogitant::Environment qui donnent accès à des opérations, cogitant::Environment::projections() n'est qu'un raccourci qui utilise des sous-classes de cogitant::Operation. Plus précisément, c'est la classe cogitant::OpeProjection qui se charge du calcul des projections. Cette opération recherche les projections par un algorithme de backtrack qui calcule dans un premier temps les listes d'images possibles (de chaque cogitant::GraphObject du graphe projeté dans les cogitant::GraphObject du graphe dans lequel on cherche les projections), puis filtre ces listes. Le filtre se fait en choisissant un sommet o1 du graphe projeté, et une de ses images o2 parmi sa liste d'images possibles. En choisissant ce couple comme faisant partie de la projection, cela induit des contraintes sur les voisins de o1 : si il existe une arête étiquetée i entre o1 et o3, alors o3 ne peut avoir comme images que des sommets o4 tels qu'il existe une arête étiquetée i entre o2 et o4. La liste des images possibles de o3 peut donc être filtrée.

L'opération cogitant::OpeProjection ne définit en fait que le schéma principal de recherche des projections. La méthode run() de cette méthode fait en effet appel à d'autres opérations, spécialisées dans une tâche. Ainsi, pour modifier l'opération de recherche de projections, il "suffit" d'écrire une sous-classe de cogitant::OpeProjection (au pire) ou une sous-classe d'une des opérations suivantes pour modifier une partie du comportement de la recherche :

La "personnalisation" du calcul des projections peut donc passer par l'écriture d'une sous classe de l'une ou plusieurs des classes présentées ci-dessus. Évidemment, dans ce cas, il est nécessaire de préciser à cogitant::OpeProjection que cette nouvelle classe doit être utilisée. Pour cela, il est nécessaire d'instancier la nouvelle classe et de passer une instance à l'instance de cogitant::OpeProjection utilisée pour lui signaler d'utiliser la nouvelle opération. Mais il faut pour cela avoir accès à une instance de cogitant::OpeProjection. Il faut donc instancier cette classe et lui fournir des instances de toutes les opérations annexes, qu'il s'agisse des classes "standard" ou de nouvelles sous-classes. Cette façon de faire n'est pas très agréable à utiliser car elle demande d'instancier plusieurs classes, et demande d'utiliser directement les opérations avec les méthodes setParamxxx(). De plus, de cette façon, la méthode cogitant::Environment::projections() utilise toujours la projection "standard". C'est pourquoi, il est préférable de "mieux" intégrer la variante de la projection à la bibliothèque, et se conformer à l'usage habituel pour la recherche de projections. En effet, habituellement, on n'instancie pas cogitant::OpeProjection, car on utilise cogitant::Environment::projections() pour calculer les projections. En fait, cette méthode utilise une instance de cogitant::OpeProjection (ainsi que toutes les autres classes ci-dessus). C'est cette instance qu'il faut donc modifier pour qu'une nouvelle opération soit prise en compte par cogitant::Environment::projections(), comme l'illustre l'exemple ci-dessous.

Exemple. Le programme ci-dessous redéfinit l'opération de compatibilité entre sommets concepts (et uniquement sommets concepts) : quelles que soient 2 étiquettes de sommets concepts, ces 2 étiquettes sont compatibles. En d'autres termes un sommet concept peut avoir comme image tout sommet concept. Dans l'exemple, une sous classe de cogitant::OpeGraphObjectCompatibility est donc définie, et associée à l'opération de recherche des projections de l'environnement.

#include <iostream>
using namespace std;
using namespace cogitant;
class MyOpeGraphObjectCompatibility: public cogitant::OpeGraphObjectCompatibility
{
public:
MyOpeGraphObjectCompatibility(Environment * env)
{};
void run()
{
// The two objects to compare are i_1 and i_2.
// The result of the comparison must be stored in o_result.
// In the case where i_1 and i_2 are not concept vertices,
// the super method is called for the default behaviour
if ((i_1->objectType() == GraphObject::OT_CONCEPT)
&& (i_2->objectType() == GraphObject::OT_CONCEPT))
o_result = true;
else
OpeGraphObjectCompatibility::run();
}
};
static void run(Environment & env, iSet g1, iSet g2)
{
env.projections(g1, g2, proj);
cout << proj.size() << " projections" << endl;
Set<Projection *> const * projs = proj.projections();
for (iSet i = projs->iBegin(); i != projs->iEnd(); projs->iNext(i))
{
cout << (i+1) << " : ";
cout << (* projs->iGetContent(i)) << endl;
}
}
int main(int, char* [])
{
MyOpeGraphObjectCompatibility myope(&env);
env.readSupport("bcgct/bucolic/bucolic.bcs");
iSet g1 = env.readGraphs("bcgct/bucolic/sleepandfish.bcg");
// We are looking for projections of g1 in g1
cout << "Projection standard" << endl;
// There is one here.
run(env, g1, g1);
cout << "Modified projection" << endl;
env.opeProjectionBundle()->m_opeprojection->setParamOpeCompatibility(&myope);
// There are two here because [Person:Peter]<-(agent)<-[Sleep] can be
// projected in [Peter]<-(agent)<-[Fish]->(in)->[Lake].
run(env, g1, g1);
cout << "Return to the standard projection" << endl;
// And again, there is one projection.
run(env, g1, g1);
return 0;
}

Exemple. Le programme ci-dessous redéfinit l'opération de filtre de listes d'images possibles pour filtrer certaines projections. En fait, cet exemple ne fait qu'interdire la 45ème mise à jour d'une liste d'image possibles (et force donc le backtrack à ce moment là), ce qui fait que certaines des projections (parmi les 360 qui sont normalement trouvées entre ces 2 graphes) ne sont jamais atteintes.

#include <iostream>
using namespace std;
using namespace cogitant;
/* This program prevents the 45th update of a LIP while calculating projections.
* (This is of no interest, except to demonstrate the use of OpeProjAcceptableLIPs). */
class MyOpeProjAcceptableLIPs: public cogitant::OpeProjAcceptableLIPs
{
public:
MyOpeProjAcceptableLIPs(Environment * env)
{};
public:
void run()
{
switch (i_step)
{
case INITLIP:
cout << "InitLIP " << i_o << endl; break;
case INITLIPEND:
cout << "InitLIPEnd" << endl; break;
case UPDATELIP:
cout << "UpdateLIP " << i_o << endl; break;
case UPDATELIPBACKTRACK:
cout << "UpdateLIPBacktrack " << i_o << endl; break;
}
o_result = true;
if (i_step == UPDATELIP)
{
static int cpt = 0;
cpt++;
if (cpt == 45)
o_result = false;
}
}
};
static void run(Environment & env, iSet g1, iSet g2)
{
env.projections(g1, g2, proj);
cout << proj.size() << " projections" << endl;
/*
Set<Projection *> const * projs = proj.projections();
for (iSet i = projs->iBegin(); i != projs->iEnd(); projs->iNext(i))
{
cout << (i+1) << " : ";
cout << (* projs->iGetContent(i)) << endl;
}
*/
}
int main(int, char* [])
{
MyOpeProjAcceptableLIPs myope(&env);
env.readSupport("bcgct/sisyphus/sisyphus.bcs");
iSet g1 = env.readGraphs("bcgct/sisyphus/localsgen2.bcg");
iSet g2 = env.readGraphs("bcgct/sisyphus/localsgen.bcg");
// We are looking for projections from g1 to g2
cout << "Standard projection" << endl;
// Here, we have 360 projections.
run(env, g1, g2);
cout << "Modified projection" << endl;
env.opeProjectionBundle()->m_opeprojection->setParamOpeAcceptableLIPs(&myope);
// There are 348 projections
run(env, g1, g2);
cout << "Return to the standard projection" << endl;
// again, 360
run(env, g1, g2);
return 0;
}

Exemple. Le programme ci-dessous redéfinit l'opération de compatibilité entre deux sommets concepts et utilise cette nouvelle compatibilité pour calculer un joint max entre deux graphes.

#include <iostream>
using namespace std;
using namespace cogitant;
class OpeGraphObjectCompatibilityComparable: public OpeGraphObjectCompatibility
{
public:
OpeGraphObjectCompatibilityComparable(Environment * env)
{};
void run()
{
if ((i_1->objectType() == GraphObject::OT_CONCEPT) && (i_2->objectType() == GraphObject::OT_CONCEPT))
{
Concept const * i_1c = i_1->asConcept();
Concept const * i_2c = i_2->asConcept();
o_result = environment()->support()->conceptTypesOrder()->comparison(i_1c->primitiveType(), i_2c->primitiveType()) != PartialOrder::UNCOMPARABLE;
if (o_result && (i_1c->referentType() == Concept::RT_INDIVIDUAL) && (i_2c->referentType() == Concept::RT_INDIVIDUAL))
o_result = i_1c->individual() == i_2c->individual();
}
else if ((i_1->objectType() == GraphObject::OT_RELATION) && (i_2->objectType() == GraphObject::OT_RELATION))
o_result = environment()->support()->relationTypesOrder()->comparison(i_1->asRelation()->type(), i_2->asRelation()->type()) != PartialOrder::UNCOMPARABLE;
else
OpeGraphObjectCompatibility::run();
}
};
void addRemoveNode(bool add, Graph * g, GraphSubset & subgraph, iSet node)
{
if (add)
subgraph.select(node);
else
subgraph.deselect(node);
if (g->nodes(node)->objectType() == GraphObject::OT_CONCEPT)
{
Set_Simple_Final<Edge> const * edges = g->edges(node);
for (iSet i=edges->iBegin(); i!=edges->iEnd(); edges->iNext(i))
if (edges->iGetContent(i).isEdge())
{
if (add)
subgraph.select(edges->iGetContent(i).m_end);
else
subgraph.deselect(edges->iGetContent(i).m_end);
}
}
}
static bool projectionSubGraph(Environment & env, GraphSubset const & subgrapha, Graph const * a, Graph const * b)
{
rop.memoProjections(true); rop.maxSize(1);
op->setParamG(a);
op->setParamH(b);
op->setParamProj(NULL);
op->setParamList(&subgrapha);
op->setParamResult(&rop);
// Disable OptimizeIndividual, because a new OpeGraphObjectCompatibility has been defined.
op->run();
if (!rop.isEmpty()) cout << (*(rop.projections(0)));
return !rop.isEmpty();
}
static void runMaxJoin(Environment & env, Graph * grapha, Graph * graphb, GraphSubset currentsubgrapha, GraphSubset & bestsubgrapha)
{
static int depth=0;
depth++;
for (int id=0; id<depth; id++) cout << " ";
cout << "Trying " << currentsubgrapha << " ";
if (projectionSubGraph(env, currentsubgrapha, grapha, graphb))
{
cout << " => Ok ! (New max join)" << endl;
bestsubgrapha = currentsubgrapha;
}
else
{
cout << "No projection" << endl;
if (currentsubgrapha.selectedNodes() > bestsubgrapha.selectedNodes())
{
for (iSet i=currentsubgrapha.first(); i!=ISET_NULL; i=currentsubgrapha.next(i))
{
if ((grapha->nodes(i)->objectType() == GraphObject::OT_CONCEPT) || (grapha->nodes(i)->objectType() == GraphObject::OT_RELATION))
{
GraphSubset copycurrentsubgrapha(currentsubgrapha);
addRemoveNode(false, grapha, copycurrentsubgrapha, i);
if ((copycurrentsubgrapha.selectedNodes() > bestsubgrapha.selectedNodes()) && env.isConnected(grapha, grapha->root(), &copycurrentsubgrapha))
runMaxJoin(env, grapha, graphb, copycurrentsubgrapha, bestsubgrapha);
}
}
}
}
depth--;
}
int main(int argc, char* argv[])
{
string prefix;
// argv[1] is the "bcgct/sisphus" directory
if (argc == 2) prefix = argv[1]; else prefix = "../bcgct/sisyphus";
env.readSupport(prefix + "/sisyphus.bcs");
iSet igrapha = env.readGraphs(prefix + "/query_noanswer.bcg");
iSet igraphb = env.readGraphs(prefix + "/locals.bcg");
GraphSubset subgrapha(env.graphs(igrapha)), bestsubgrapha(env.graphs(igrapha));
subgrapha.fill(); bestsubgrapha.clear();
OpeGraphObjectCompatibilityComparable myopecompatibility(&env);
env.opeProjectionBundle()->m_opeprojection->setParamOpeCompatibility(&myopecompatibility);
runMaxJoin(env, env.graphs(igrapha), env.graphs(igraphb), subgrapha, bestsubgrapha);
cout << "max join: " << bestsubgrapha.selectedNodes() << " " << bestsubgrapha << endl;
}

Image d'un graphe par une projection

Forme normale et normalisation

#include <iostream>
using namespace std;
int main(int, char* [])
{
env.readSupport("bcgct/sisyphus/sisyphus.bcs");
cogitant::iSet ifact = env.readGraphs("bcgct/sisyphus/localsgen.bcg");
cogitant::Graph * fact = env.graphs(ifact);
// First, we test if "fact" is in normal form.
bool nf = env.verifyNormalForm(fact);
if (nf)
cout << "Graph is in normal form." << endl;
// Now, we change some concept nodes, and the result graph will not be in
// normal form.
cogitant::iSet typelocal = env.support()->findConceptType("Local");
cogitant::iSet ind113 = env.support()->findIndividual("C5_113");
cogitant::Graph::concept_iterator j=fact->conceptBegin();
for (int i=0; j != fact->conceptEnd(); j++)
if (((*j)->referentType() == cogitant::Concept::RT_GENERIC) && ((*j)->primitiveType() == typelocal))
{
// Each concept node typed by "Local" is now labeled by the
// "C5_113" individual marker.
(*j)->setReferent(cogitant::Concept::RT_INDIVIDUAL, ind113);
i++;
}
// "fact" is no more in normal form. The third parameter of verifNormalForm
// returns a OperationVerification::ListErrors. This list contains a set of
// OpeVerification::ErrorISet, and each instance of this class contains
// an id of (concept) node that cause the graph is not in normal form.
// ListError also has an operator<<, so cout << errors should be easier to
// use for debug purposes.
nf = env.verifyNormalForm(fact, true, &errors);
if (!nf)
{
cout << "Graph is not in normal form, because of the following concept nodes: ";
for (list<cogitant::OpeVerification::ErrorInfo *>::const_iterator i=errors.elements().begin(); i!=errors.elements().end(); i++)
cout << dynamic_cast<cogitant::OpeVerification::ErrorISet const *>(*i)->m_iset << " ";
cout << endl;
}
// Finally, we normalize "fact", and we print it on standard ouput.
env.normalize(fact);
nf = env.verifyNormalForm(fact);
if (nf)
cout << "Graph is in normal form again." << endl;
return 0;
}

Opérations sur les règles de graphes

Opérations d'entrées/sorties

#include <iostream>
using namespace std;
int main(int argc, char* argv[])
{
if (argc != 3)
{
cerr << "Usage: support_convert source dest" << endl;
return 0;
}
try
{
cout << "Loading support: " << argv[1] << endl;
env.readSupport(string(argv[1]));
cout << "Saving support: " << argv[2] << endl;
env.writeSupport(string(argv[2]));
cout << "ok" << endl;
}
catch (cogitant::Exception & e)
{
cerr << e.toString() << endl;
}
return 0;
}
#include <iostream>
using namespace std;
int main(int argc, char* argv[])
{
if (argc != 4)
{
cerr << "Usage: graph_convert support source dest" << endl;
return 0;
}
try
{
cout << "Loading support: " << argv[1] << endl;
env.readSupport(string(argv[1]));
cout << "Loading graph: " << argv[2] << endl;
cogitant::iSet i = env.readGraphs(string(argv[2]));
cout << "Saving graph: " << argv[3] << endl;
env.writeGraph(string(argv[3]), i);
cout << "ok" << endl;
}
catch (cogitant::Exception & e)
{
cerr << e.toString() << endl;
}
return 0;
}

Les opérations d'entrées/sorties n'agissent pas obligatoirement sur des fichiers, mais peuvent prendre comme paramètres des flux C++, c'est-à-dire des instances de sous-classes std::istream et std::ostream. La plupart des méthodes sont d'ailleurs surchargées afin de recevoir au choix un nom de fichier (sous la forme d'une chaîne de caractères) ou un flux (de sortie ou d'entrée, selon s'il s'agit d'une opération de sortie ou d'entrée).
Une utilisation classique de cette possibilité est l'écriture à l'écran d'un support ou de graphes à des fins de débogage ou visualisation rapide d'un résultat. Pour obtenir ce résultat, il suffit d'appeler les méthodes cogitant::Environment::writeGraph() ou cogitant::Environment::writeSupport() prenant comme paramètre un std::ostream et leur passer std::cout. Cette possiblité est utilisée dans plusieurs exemples de cette documentation.
Il est à noter que la détection de format s'effectuant par le cogitant::IOHandler à partir de l'extension du nom de fichier, elle devient impossible à partir des flux. C'est pourquoi les méthodes prenant comme paramètre un flux prennent aussi comme paramètre un format de fichier.

#include <iostream>
using namespace std;
int main(int argc, char* argv[])
{
if (argc != 3)
{
cerr << "Usage: " << argv[0] << " rdffile cgfile" << endl;
return 0;
}
try
{
vector<cogitant::iSet> graphs;
cout << "Loading rdf file: " << argv[1] << endl;
env.read(string(argv[1]), &graphs);
cout << "Saving cg file: " << argv[2] << endl;
env.ioHandler()->write(string(argv[2]), cogitant::OperationOutput::SO_ALL, true, cogitant::IOHandler::AUTO, 0, &graphs);
cout << "ok" << endl;
}
catch (cogitant::Exception & e)
{
cerr << e.toString() << endl;
}
return 0;
}

Entrées/sorties dans un buffer

Les classes std::istringstream et std::ostringstream permettent de considérer un buffer en mémoire comme un flux, et, évidemment les opérations d'entrées/sorties de Cogitant peuvent être utilisées sur de tels flux. Ces flux permettent, de plus, d'accéder à leur contenu comme une chaîne de caractères, ce qui permet de nombreux usages, car une suite d'octets peut facilement être transmise à un autre outil. Cette fonction est utilisée dans l'exemple ci-dessous pour stocker et lire support et graphe dans une base de données PostgreSQL.

Exemple. Le programme ci-dessous utilise une base de données PostgreSQL (http://www.postgresql.org) pour stocker des objets Cogitant (un support et un graphe dans l'exemple) au format CoGXML dans un BLOB (appelé BYTEA dans PostgreSQL). Plus précisément, une table (CogitantStreams) est créée, avec deux colonnes : id contient un identifiant d'objet permettant de le retrouver et cogxml est une suite d'octets contenant la sérialisation CoGXML de l'objet. Diverses opérations sont effectées par le programme pour démontrer l'utilisation de la table.
A noter que ce programme nécessite la libpqxx (http://thaiopensource.org/development/libpqxx/), une API C++ pour le SGBD PostgreSQL. Si vous désirez compiler ce programme, il ne faut donc pas oublier de lier votre exécutable à cette bibliothèque en plus de la bibliothèque Cogitant (un -lpqxx passé à l'éditeur de liens suffit sous Unix).
Même si différentes opérations de lecture et d'écriture sont effectuées, c'est le même principe qui est mis en oeuvre à chaque fois :

/* This example requires:
- The pqxx ( library http://pqxx.org/development/libpqxx/ )
(package libpqxx-dev under Debian)
- a PostgreSQL server running on 127.0.0.1.
- a user "cogitant" (password: "cogitant") on the server
- a database "cogitant"
Under Debian, user and database are created by the following:
su -c "su postgres"
createuser -A -D -P cogitant
(password: cogitant)
createdb -O cogitant cogitant
User and database are dropped by the following:
su -c "su postgres"
dropdb cogitant
dropuser cogitant
*/
#include <sstream>
#include <iostream>
#include <pqxx/pqxx>
using namespace pqxx;
using namespace std;
using namespace cogitant;
class PgSqlIO
{
private:
connection* m_pgsqlconnection;
work* m_pgsqltransaction;
public:
PgSqlIO()
{
string args="dbname=cogitant user=cogitant password=cogitant host=127.0.0.1";
m_pgsqlconnection = new connection(args);
m_pgsqltransaction = new work(*m_pgsqlconnection);
};
~PgSqlIO()
{
m_pgsqltransaction->commit();
delete m_pgsqltransaction;
delete m_pgsqlconnection;
};
void createTable()
{
m_pgsqltransaction->exec("CREATE TABLE CogitantStreams(\
id VARCHAR(50) CONSTRAINT cskey PRIMARY KEY,\
cogxml BYTEA)");
};
void dropTable()
{
m_pgsqltransaction->exec("DROP TABLE IF EXISTS CogitantStreams");
};
void prepareStatements()
{
if (!m_pgsqlconnection->supports(connection_base::cap_prepared_statements))
{
cout << "Backend version does not support prepared statements.";
exit(1);
}
m_pgsqlconnection->prepare("storeobject", "INSERT INTO CogitantStreams VALUES ($1, $2)")
("varchar", prepare::treat_string)("bytea", prepare::treat_binary);
m_pgsqlconnection->prepare_now("storeobject");
m_pgsqlconnection->prepare("readobject", "SELECT cogxml FROM CogitantStreams WHERE id=$1")
("varchar", prepare::treat_string);
m_pgsqlconnection->prepare_now("readobject");
m_pgsqlconnection->prepare("updateobject", "UPDATE CogitantStreams SET cogxml=$2 WHERE id=$1")
("varchar", prepare::treat_string)("bytea", prepare::treat_binary);
m_pgsqlconnection->prepare_now("updateobject");
};
void storeObject(string const & id, ostringstream const & value)
{
m_pgsqltransaction->prepared("storeobject")(id)(value.str()).exec();
}
void updateObject(string const & id, ostringstream const & value)
{
if (m_pgsqltransaction->prepared("updateobject")(id)(value.str()).exec().affected_rows() != 1)
cerr << "Warning: updateObject " << id << endl;
}
bool readObject(string const & id, istringstream & input)
{
const result r(m_pgsqltransaction->prepared("readobject")(id).exec());
if (r.empty()) return false;
binarystring bs(r.front()[0]);
input.str(bs.str());
return true;
}
};
void fillDatabase(PgSqlIO & tdb, string const & prefix)
{
// A support and a graph are read from files in "env" (BCGCT format)
env.readSupport(prefix + "/bcgct/bucolic/bucolic.bcs");
iSet gtmp = env.readGraphs(prefix + "/bcgct/bucolic/simplequery.bcg");
// Support is stored in the database (under the "s1" id) (CoGXML format)
ostringstream sstr1;
env.writeSupport(sstr1, IOHandler::COGXML);
tdb.storeObject("s1", sstr1);
// Graph is stored in the database (under the "g1" id) (CoGXML format)
ostringstream sstr2;
env.writeGraph(sstr2, gtmp, IOHandler::COGXML);
tdb.storeObject("g1", sstr2);
}
void updateDatabase(PgSqlIO & tdb)
{
// This environment reads its support from the database.
{
istringstream input;
tdb.readObject("s1", input);
env2.readSupport(input, "s1", IOHandler::COGXML);
}
// ... and a graph
iSet ig1;
{
istringstream input;
tdb.readObject("g1", input);
ig1 = env2.readGraphs(input, "g1", NULL, IOHandler::COGXML);
}
// env2 adds new concept nodes to g1 and updates the DB
for (int i=0; i<5; i++)
{
env2.graphs(ig1)->newGenericConcept("Person");
ostringstream output;
env2.writeGraph(output, ig1, IOHandler::COGXML);
// and updates the database (5 times)
tdb.updateObject("g1", output);
}
}
void readDatabase(PgSqlIO & tdb)
{
// First, support loading
{
istringstream input;
tdb.readObject("s1", input);
env.readSupport(input, "s1", IOHandler::COGXML);
}
// ig1 is loaded from the database
iSet ig1;
{
istringstream input;
tdb.readObject("g1", input);
ig1 = env.readGraphs(input, "g1", NULL, IOHandler::COGXML);
}
// ig1 is written on standard output, and contains 5 "Person" nodes.
env.writeGraph(cout, ig1, IOHandler::LINEARFORM);
}
int main(int argc, char* argv[])
{
string prefix;
// argv[1] is the "samples" directory
if (argc == 2) prefix = argv[1]; else prefix = "..";
PgSqlIO tdb;
tdb.dropTable();
tdb.createTable();
tdb.prepareStatements();
fillDatabase(tdb, prefix);
updateDatabase(tdb);
readDatabase(tdb);
}

Bien évidemment, le même mécanisme peut être utilisé pour effectuer des entrées/sorties dans un SGBD autre que PostgreSQL (ou avec une autre API de PostgreSQL) ou d'autres outils qui peuvent prendre leurs entrées et sorties dans des chaînes de caractères.

Définition de nouvelles opérations

(Cette section sera complétée prochainement)