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

Operations

This section describes the use of operations available in the library. Some of these operations (projection, input/output) have already been seen in the preceding sections, in the form of methods of the cogitant::Environment class. But these methods are merely shortcuts for using cogitant::Operation (and its sub-classes), this is why, in the frame of an advanced use of the library, it is better to know the use of this class in order to understand well the functioning of these methods corresponding to the operations defined based on the model, on input/output features as well as to define new operations. In this part, we explain the main library operations, and describe how to use these operations as instances of subclasses of cogitant::Operation and through "shortcuts" methods of cogitant::Environment.

The approach chosen to offer operations in the library is then to use objects instance of cogitant::Operation (or rather of its subclasses, because it is an abstract class). This solution has been preferred to the definition of methods in the cogitant::Graph or cogitant::Rule class as it offers more flexibility. Indeed, whether you have to define new operations (for example, managing a new file format) or you have to provide a new implementation of existing operations (for example a more effective projection algorithm), the solution of using cogitant::Graph methods require the definition of a new subclass, in which the method corresponding to the operation is redefined. This obviously raises the problem of the definition of several new operations: in this case, how to organize the inheritance relationship on (the) new class(es). On the contrary, the kept solution can define new operation classes that receive model objects as parameters. Thus, you can easily define new implementations of existing or new operations. In addition, the library handling references to instances of (subclasses of) cogitant::Operation, it is very easy to incorporate these new operations to the library, and to use methods of the library that will call, in an absolutely transparent way, new operations.

Generally, operations are used by instanciating an object of a subclass of cogitant::Operation, by calling setParamxxx() methods to fix the different parameters of the operation, then by calling the run() method which carries out the calculation. Finally, getResultxxx() methods can retrieve the result.

Conceptual graph operations

Connected component determination

Conceptual graphs handled in Cogitant may be unconnected. However structuring a graph in connected components can be useful, this is why several methods of the library can determine whether a graph is connected or not, how many connected components of a graph and what are these connected components (vertices contained in each component). All this information can be calculated by calling cogitant::Environment::isConnected() (graph connectivity), cogitant::Environment::connectedComponentsSize() (number of connected components) and cogitant::Environment::connectedComponents() (connected components) methods. All these methods take as a parameter a (pointer to a) cogitant::Graph and a cogitant::iSet locating the cogitant::InternalGraph identifier whose connectivity must be determined (by default 0, i.e. the graph of level 0). Indeed, in the case of nested graphs, a cogitant::Graph contains several "graphs," and if there is no interest in determining the number of connected components of all these graphs, it may be interesting to determine this information on one of them (and not just on the graph of level 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;
}

Calculating the disjoint sum

The disjoint sum is simply accessible from the cogitant::Environment::disjointSum() method which takes as parameters two (pointers to) cogitant::Graph and which modifies the first for adding it the second (which is not modified). This method actually uses the cogitant::OpeDisjointSum class, whose documentation describes the various features offered by this class that can make operations more complex than the simple disjoint sum (this operation is especially used when loading files when a reference to an already existing graph is done in a nesting (the graph must then be copied as nested graph, and coreference classes may be updated) and when applying a rule according to a projection (adding the conclusion and merging vertices, see below)).

Graph checkouts

Projection calculation

Search of projections from a graph into another is done thanks to the cogitant::Environment::projections() method, to which one must pass the projected graph and the graph in which one projects. This method is actually overloaded and can take as parameters graph identifiers (cogitant::iSet identifying the graph in the environment) or pointers to cogitant::Graph. The third parameter of this method, a cogitant::ResultOpeProjection passed by reference, will contain, after the call, the result of the method execution. But this object can also set up the search. Indeed, according to the uses, one can be interested in various searches, which are more or less costly in time and memory. On can mainly distinguish 3 uses, which are presented here from the less to the most expensive one, and illustrated with an example.

  1. If you are just interested in the existence of a projection between the two graphs, the search operation doesn't have to search several graphs or to store couples of vertices composing each found projection. In this case, it is appropriate to call methods cogitant::ResultOpeProjection::memoProjections with false as a parameter (to indicate that projections should not be stored), and cogitant::ResultOpeProjection::maxSize (cogitant::nSet) with 1 as a parameter (to indicate that the search must be stopped as soon as a projection is found). Both methods should be called on cogitant::ResultOpeProjection before calling a cogitant::Environment::projections(). Once this method has been called, it is necessary to call cogitant::ResultOpeProjection::isEmpty() to determine whether a projection was found.
  2. In case where one just seeks to know the number of projections without trying to store couples of vertices, one have to call cogitant::ResultOpeProjection::memoProjections() with false as a parameter before the call to cogitant::Environment::projections(). After the call, the method cogitant::ResultOpeProjection::size() allows you to know the number of found projections.
  3. Finally, in the general case, we seek all projections from a graph into another, and we are interested in the projections themselves, i.e., for each found projection, by the image of each vertex of the projected graph. In this case, no particular method of cogitant::ResultOpeProjection should be called. After the call cogitant::Environment::projections(), the method cogitant::ResultOpeProjection::size() can provide the number of found projections, cogitant::ResultOpeProjection::projections() to all found projections, i.e. an instance of cogitant::Set<cogitant::Projection*>. It is then possible to traverse this set (cf. Containers classes) to perform a special processing on each found projection.

Example.The example below shows the three cases presented above. The display of projections in the third case uses the output operator of cogitant::Projection which displays couples of vertex identifiers in graphs: the first element of each couple is the identifier (iSet) of a cogitant::GraphObject of the projected graph and the second element is the identifier of its 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;
}

Projection iterator
With the use of a cogitant::OpeProjectionBundle and a cogitant::ProjectionIterator, projections are not calculated by a simple call (such as in the cogitant::Environment::projections() method): after an initialization by a call to cogitant::OpeProjectionBundle::begin(), a cogitant::ProjectionIterator calculates the next cogitant::Projection each time that its ++ operator is called. Please note that modifying the two graphs while projections are computed is strictly forbidden. Note also that an cogitant::OpeProjectionBundle can only calculate projections between two graphs at a time (even if two cogitant::ProjectionIterator are declared). So, in order to calculate simulaneaously projections between several couples of graphs, several cogitant::OpeProjectionBundle are needed, and they can be obtained by cogitant::Environment::newOpeProjectionBundle().

Example.

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

Changes in the projection search operation
Like all other methods of cogitant::Environment which give access to operations, cogitant::Environment::projections() is merely a shortcut that uses subclasses of cogitant::Operation. More specifically, this is the cogitant::OpeProjection class which takes charge of calculating projections. This operation searches projections by a backtrack algorithm that calculates in a first-time possible image lists (of each cogitant::GraphObject of the projected graph in cogitant::GraphObject objects of the graph in which we seeks projections), then filters these lists. Filtering is done by choosing a vertex o1 of the projected graph, and one of its images o2 among its list of possible images. By choosing this couple as part of the projection, this induces constraints on neighbors of o1: if there is an edge labeled i between o1 and o3, then o3 can have as images only vertices o4 such that there is an edge labeled i between o2 and o4. The list of possible images of o3 can then be filtered.

The cogitant::OpeProjection operation merely defines the main scheme of projection search. The run() method of this method actually makes calls to other operations, specialized in a task. Thus, to modify the projection search operation, you "just" have to write a subclass of cogitant::OpeProjection (at worst) or a subclass of one of the following operations to change a part of the search behaviour:

The "personalization" of the projection calculation can then be done by writing a subclass of one or more classes presented above. Obviously, in this case, it is necessary to specify to cogitant::OpeProjection that this new class should be used. For this, it is necessary to instantiate the new class and to pass an instance to the instance of the used cogitant::OpeProjection in order to inform it to use the new operation. But this requires an access to an instance of cogitant::OpeProjection. Therefore you have to instantiate this class and to provide it instances of all side operations, whether it is "standard" classes or new subclasses. This way of doing things is not very pleasant to use because it requires to instantiate several classes, and requires to directly use operations with setParamxxx() methods. Moreover, in this way, the cogitant::Environment::projections() method always uses the "standard" projection. This is why it is preferable to "better" integrate the projection variant to the library, and to comply with the usual usage for the projection search. Indeed, usually, one do not instantiate cogitant::OpeProjection, because cogitant::Environment::projections() should be use to calculate projections. In fact, this method uses an instance of cogitant::OpeProjection (as well as all other classes above). Thus, it is this instance that must be modified so that a new operation is taken into account by cogitant::Environment::projections(), as shown in the example below.

Example.The program below redefines the compatibility operation between concept vertices (and only concept vertices): for each couple of 2 labels of concept vertices, these 2 labels are compatible. In other words, a concept vertex can have as an image any concept vertex. In the example, a subclass of cogitant::OpeGraphObjectCompatibility is thus defined, and associated with the operation of searching environment projections.

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

Example. The program below redefines the filter operation of lists of possible images to filter certain projections. Actually, this example only forbids the 45th update of a list of possible images (and force thus the backtrack at this time), so that some of the projections (among the 360 that are normally found between these 2 graphs) are never reached.

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

Example. The program below redefines the compatibility between concept vertices and uses this new compatibility operation in order to calculate a max join between two graphs.

#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 of a graph through a projection

Normal form and normalization

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

Operations on graph rules

Input/output operations

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

Input/output operations do not inevitably act on files, but can take as parameters C++ streams, i.e. instances of subclasses std::istream and std::ostream. By the way, most methods are overloaded in order to receive either a filename (in the form of a string) or a stream (input or output, depending on whether it is an input or an output operation).
A classic use of this possibility is to write on the screen a support or graphs for the purpose of debugging or quick viewing of a result. To get this result, simply call the cogitant::Environment::writeGraph() or cogitant::Environment::writeSupport() methods which take as a parameter a std::ostream and pass them std::court. This possibility is used in several examples of this documentation.
Note that the format detection being performed by the cogitant::IOHandler from the extension of the filename, it cannot be done from the stream. That is why methods taking a stream as a parameter also take a file format as a parameter.

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

Input/output in a buffer

Classes std::istringstream and std::ostringstream enable to consider a buffer in memory as a stream, and, of course input/output operations of Cogitant can be used on such streams. These streams enable, in addition, to access their content as a string, which enables many uses, as a series of bytes can easily be sent to another tool. This function is used in the example below to store and read support and graph into a PostgreSQL database.

Example. The program below uses a PostgreSQL database (http://www.postgresql.org) for storing Cogitant objects (a support and a graph in the example) in the CoGXML format into a BLOB (called BYTEA in PostgreSQL). More specifically, a table (CogitantStreams) is created, with two columns: id contains an object identifier enabling to regain it and cogxml is a series of bytes containing the CoGXML serialization of the object. Various operations are done by the program to demonstrate the use of the table.
Note that this program requires the libpqxx (http://thaiopensource.org/development/libpqxx/), a C++ API for the DBMS PostgreSQL. If you want to compile this program, don't forget to link your executable to the library in addition to the Cogitant library (a -lpqxx passed to the linker is enough under Unix).
Even if different operations of reading and writing are done, it's the same principle that is done each time:

/* 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);
}

Of course, the same mechanism can be used to make input/output in a DBMS other than PostgreSQL (or with another API of PostgreSQL) or with other tools that can take their input and ouput in strings.

Definition of new operations

(This section will be soon completed)