Files
secondo/Algebras/PropertyGraph2/PropertyGraphMem.cpp
2026-01-23 17:03:45 +08:00

779 lines
24 KiB
C++

/*
----
This file is part of SECONDO.
Copyright (C) 2012, University in Hagen
Faculty of Mathematic and Computer Science,
Database Systems for New Applications.
SECONDO is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
SECONDO is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with SECONDO; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
----
*/
#include <string>
#include "Algebra.h"
#include "NestedList.h"
#include "ListUtils.h"
#include "NList.h"
#include "LogMsg.h"
#include "QueryProcessor.h"
#include "ConstructorTemplates.h"
#include "StandardTypes.h"
#include "TypeMapUtils.h"
#include "Symbols.h"
#include "../Relation-C++/RelationAlgebra.h"
#include "../OrderedRelation/OrderedRelationAlgebra.h"
#include "MPointer.h"
#include "Mem.h"
#include "MemoryObject.h"
#include "MemCatalog.h"
#include "PropertyGraphQueryProcessor.h"
#include "Utils.h"
#include "PropertyGraphMem.h"
#include "PropertyGraph2.h"
#include "RelationSchemaInfo.h"
using namespace std;
extern NestedList* nl;
extern QueryProcessor *qp;
namespace pgraph2 {
//-----------------------------------------------------------------------------
void RelationRegistry::Clear()
{
Counter=0;
RelationIds.clear();
//
for(auto&& r : RelationInfos) delete r;
RelationInfos.clear();
}
//-----------------------------------------------------------------------------
bool RelationRegistry::IsIndexed(std::string relation,std::string attr)
{
RelationInfo *ri=GetRelationInfo(relation);
if (ri!=NULL)
{
AttrInfo *ai =ri->RelSchema.GetAttrInfo(attr);
if (ai!=NULL)
return ai->Indexed;
}
return false;
}
//-----------------------------------------------------------------------------
RelationInfo *RelationRegistry::AddRelation(string name,
RelationRoleType role)
{
RelationInfo *ri=new RelationInfo();
ri->Name=name;
ri->roleType = role;
ri->RelId=Counter++;
RelationInfos.push_back(ri);
RelationIds[ri->Name]=ri->RelId;
return ri;
}
//----------------------------------------------------------------------------
int RelationRegistry::GetRelationId(string name)
{
if (RelationIds.find(name)==RelationIds.end())
return -1;
return RelationIds[name];
}
//----------------------------------------------------------------------------
RelationInfo* RelationRegistry::GetRelationInfo(string name)
{
int id=GetRelationId(name);
if (id<0) return NULL;
return RelationInfos[id];
}
//----------------------------------------------------------------------------
RelationInfo* RelationRegistry::GetRelationInfo(int id)
{
return RelationInfos[id];
}
//----------------------------------------------------------------------------
void AdjacencyList::AddNodeTuple(int relId, Tuple *tuple)
{
LOGOP(30, "AdjacencyList::AddNodeTuple","relId ",relId);
tuple->IncReference();
NodeList.push_back(tuple);
NodeRelIdList.push_back(relId);
NodeGlobalCounter++;
}
//-----------------------------------------------------------------------------
void AdjacencyList::AddEdgeTuple(int relId, Tuple *tuple,
RelationInfo *fromrel, int fromID, RelationInfo *torel, int toID)
{
LOGOP(30, "AdjacencyList::AddEdgeTuple","relId ",relId, " | ",
fromID,"->",toID);
tuple->IncReference();
// ignore if not exiting
if (fromrel->IdTranslationTable.find(fromID)==fromrel->
IdTranslationTable.end())
return;
if (torel->IdTranslationTable.find(toID)==torel->IdTranslationTable.end())
return;
// get from object
int globfrom = fromrel->IdTranslationTable[fromID];
int globto = torel->IdTranslationTable[toID];
LOGOP(30, "AdjacencyList::AddEdgeTuple","translated ",globfrom,"->",globto);
// keep tuple
EdgeList.push_back(tuple);
EdgeInfo.push_back(new Edge(EdgeGlobalCounter, relId, globfrom, globto));
// add to outgoing/incoming lists
LOGOP(30, "AdjacencyList::AddEdgeTuple","In/OutGoingRels ");
vector<int> *list;
//
list = &OutGoingRels[globfrom];
list->push_back(EdgeGlobalCounter);
//
list = &InGoingRels[globto];
list->push_back(EdgeGlobalCounter);
//
EdgeGlobalCounter++;
}
//-----------------------------------------------------------------------------
AdjacencyList::~AdjacencyList()
{
Clear();
}
//-----------------------------------------------------------------------------
void AdjacencyList::Clear()
{
LOGOP(30,"AdjacencyList::Clear");
NodeGlobalCounter=0;
EdgeGlobalCounter=0;
NodeRelIdList.clear();
OutGoingRels.clear();
InGoingRels.clear();
//
for(auto&& tuple : NodeList) tuple->DeleteIfAllowed();
NodeList.clear();
//
for(auto&& tuple : EdgeList) tuple->DeleteIfAllowed();
EdgeList.clear();
//
for(auto&& edge : EdgeInfo) delete edge;
EdgeInfo.clear();
}
//-----------------------------------------------------------------------------
RelationInfo::~RelationInfo()
{
if (statistics!=NULL) { delete statistics; statistics=NULL; };
}
//-----------------------------------------------------------------------------
void RelationInfo::InitRelSchema(ListExpr tupleinfo)
{
RelSchema.LoadFromList(tupleinfo);
}
//-----------------------------------------------------------------------------
void RelationInfo::AddGlobalIndex(int global, int id)
{
LOGOP(30, "NodeRelationInfo::AddGlobalIndex","id ",id," -> glob: ", global);
IdTranslationTable[id]=global;
}
//-----------------------------------------------------------------------------
string MemoryGraphObject::DumpInfo()
{
string info="PGRAPHMEM Information\n";
info += " - initstate: "+std::to_string(initstate) + "\n";
info += " - statistics: \n";
info += " - noderelations: \n";
for(auto&& n: RelRegistry.RelationInfos)
{
RelStatistics *rs=n->statistics;
if (n->roleType==RoleNode) {
info += " - "+n->Name;
if (rs!=NULL)
info+=+" CARD: "+to_string(rs->cardinality);
info += " \n";
}
}
info += " - edgerelations: \n";
for(auto&& n: RelRegistry.RelationInfos)
{
RelStatistics *rs=n->statistics;
if (n->roleType==RoleEdge) {
info += " - "+n->Name;
if (rs!=NULL)
info+=+" CARDFW: "+to_string(rs->avgcardForward)+
" CARDBW: "+to_string(rs->avgcardBackward);
info += "\n";
}
}
return info;
}
//-----------------------------------------------------------------------------
MemoryGraphObject::~MemoryGraphObject()
{
Clear();
}
//-----------------------------------------------------------------------------
void MemoryGraphObject::LoadNodeRelation(string memrelname,
NodeRelInfo *nrelinfo, bool rebuildStatistics)
{
LOGOP(10,"MemoryGraphObject::LoadNodeRelation", "loading "+
nrelinfo->name);
// get relation info
ListExpr reltype =
SecondoSystem::GetCatalog()->GetObjectTypeExpr(nrelinfo->name);
if (nl->IsEmpty(reltype))
throw SecondoException("relation not found");
// register relation
RelationInfo* relinfo=RelRegistry.AddRelation(nrelinfo->name,
RelationRoleType::RoleNode);
relinfo->InitRelSchema(reltype);
relinfo->IdFieldName=nrelinfo->idattr;
//
relinfo->IdAttrIndex=
relinfo->RelSchema.GetAttrInfo(nrelinfo->idattr)->Index;
//
mm2algebra::MemoryRelObject* relation=QueryMemRelation(memrelname);
if (relation==NULL) PGraph2Exception("couldn't open relation:"+memrelname);
int counter=0;
for(auto&& tuple : *relation->getmmrel() )
{
// get nodeid
int id=((CcInt*)tuple->GetAttribute(relinfo->IdAttrIndex))->GetValue();
// add to adjacency list and put global id to translation table
AdjList.AddNodeTuple(relinfo->RelId, tuple);
relinfo->AddGlobalIndex(AdjList.NodeGlobalCounter-1, id);
counter++;
memSize+=tuple->GetSize();
}
// update as counted anyways (will be persistent)
nrelinfo->StatCardinality=counter;
relinfo->statistics=new RelStatistics();
relinfo->statistics->cardinality=counter;
}
//-----------------------------------------------------------------------------
int MemoryGraphObject::GetRelCount(std::string relname) {
Word resultword;
string querystring="(count (feed " +relname+ " ))";
QueryProcessor::ExecuteQuery(querystring, resultword);
int relcount = ((CcInt*)resultword.addr)->GetIntval();
return relcount;
}
//-----------------------------------------------------------------------------
void MemoryGraphObject::LoadNodeRelation2(string memrelname,
NodeRelInfo *nrelinfo, bool rebuildStatistics)
{
LOGOP(10,"MemoryGraphObject::LoadNodeRelation", "loading "+
nrelinfo->name);
// get relation info
ListExpr reltype =
SecondoSystem::GetCatalog()->GetObjectTypeExpr(nrelinfo->name);
if (nl->IsEmpty(reltype))
throw SecondoException("relation not found");
// register relation
RelationInfo* relinfo=RelRegistry.AddRelation(nrelinfo->name,
RelationRoleType::RoleNode);
relinfo->InitRelSchema(reltype);
relinfo->IdFieldName=nrelinfo->idattr;
//
relinfo->IdAttrIndex=
relinfo->RelSchema.GetAttrInfo(nrelinfo->idattr)->Index;
if (rebuildStatistics)
{
int counter=GetRelCount(memrelname);
// update as counted anyways (will be persistent)
nrelinfo->StatCardinality=counter;
}
relinfo->statistics=new RelStatistics();
relinfo->statistics->cardinality=nrelinfo->StatCardinality;
}
//-----------------------------------------------------------------------------
void MemoryGraphObject::LoadEdgeRelation(string memrelname,
EdgeRelInfo *erelinfo, bool rebuildStatistics)
{
LOGOP(10,"MemoryGraphObject::LoadEdgeRelation", "loading "+erelinfo
->EdgeRelName);
ListExpr reltype= SecondoSystem::GetCatalog()
->GetObjectTypeExpr(erelinfo->EdgeRelName);
if (nl->IsEmpty(reltype))
throw SecondoException("relation not found");
// register relation
RelationInfo* relinfo=RelRegistry.AddRelation(erelinfo->EdgeRelName,
RelationRoleType::RoleEdge);
relinfo->InitRelSchema(reltype);
relinfo->FromName=erelinfo->FromRelName;
relinfo->ToName=erelinfo->ToRelName;
//
RelationInfo *fromrel = RelRegistry.GetRelationInfo(erelinfo->FromRelName);
RelationInfo *torel = RelRegistry.GetRelationInfo(erelinfo->ToRelName);
if (fromrel==NULL)
throw PGraph2Exception("Relation not found: "+erelinfo->FromRelName);
if (torel==NULL)
throw PGraph2Exception("Relation not found: "+erelinfo->ToRelName);
mm2algebra::MemoryRelObject* relation=QueryMemRelation(memrelname);
if (relation==NULL) PGraph2Exception("could not open relation: "+memrelname);
relinfo->FromAttrIndex=
relinfo->RelSchema.GetAttrInfo(erelinfo->FromIdName)->Index;
relinfo->ToAttrIndex=
relinfo->RelSchema.GetAttrInfo(erelinfo->ToIdName)->Index;
for(auto&& tuple : *relation->getmmrel() )
{
// get fromid
int fromid=((CcInt*)
tuple->GetAttribute(relinfo->FromAttrIndex))->GetValue();
int toid=((CcInt*)tuple->GetAttribute(relinfo->ToAttrIndex))->GetValue();
// add to adjacency list
// (from,toid are relative and need to be translated to global id)
AdjList.AddEdgeTuple(relinfo->RelId, tuple, fromrel, fromid,
torel, toid);
}
// get persistent statistics
relinfo->statistics=new RelStatistics();
relinfo->statistics->avgcardForward=erelinfo->StatAvgForward;
relinfo->statistics->avgcardBackward=erelinfo->StatAvgBackward;
// Loading statistics if necessary
if(rebuildStatistics)
{
LOGOP(10,"MemoryGraphObject::LoadEdgeRelation","getting edge statistics");
double d=0;
string query="(avg (groupby2 (smouterjoin (rename (mfeed "+mrelprefix+
fromrel->Name+") n) (rename (mfeed "+mrelprefix+erelinfo->EdgeRelName+
") e) "+fromrel->IdFieldName+"_n "+erelinfo->FromIdName+"_e) ("+
fromrel->IdFieldName+"_n) ( (C (fun (t TUPLE) (agg int) "
" (ifthenelse (isempty (attr t "+erelinfo->FromIdName+"_e)) agg "
"(+ agg 1)) ) 0 ) ) ) C) ";
LOGOP(30,"MemoryGraphObject::LoadEdgeRelation",
"getting avg of forward relations per "+fromrel->Name,query);
if (!queryValueDouble(query,d))
{
cout << "failed." << endl;
};
relinfo->statistics->avgcardForward=d;
d=0;
query="(avg (groupby2 (smouterjoin (rename (mfeed "+
mrelprefix+torel->Name+") n) (rename (mfeed "+
mrelprefix+erelinfo->EdgeRelName+") e) "+torel->IdFieldName+"_n "+
erelinfo->ToIdName+"_e) ("+torel->IdFieldName+
"_n) ( (C (fun (t TUPLE) (agg int) (ifthenelse (isempty (attr t "+
erelinfo->ToIdName+"_e)) agg (+ agg 1)) ) 0 ) ) ) C) ";
LOGOP(30,"MemoryGraphObject::LoadEdgeRelation",
"getting avg of backward relations per "+torel->Name,query);
queryValueDouble(query,d);
relinfo->statistics->avgcardBackward=d;
// keep persistent
erelinfo->StatAvgForward=relinfo->statistics->avgcardForward;
erelinfo->StatAvgBackward=relinfo->statistics->avgcardBackward;
}
LOGOP(30,"MemoryGraphObject::LoadEdgeRelation","finished" );
}
//-----------------------------------------------------------------------------
void MemoryGraphObject::LoadEdgeRelation2(string memrelname,
EdgeRelInfo *erelinfo, bool rebuildStatistics, Supplier s)
{
LOGOP(10,"MemoryGraphObject::LoadEdgeRelation", "loading "+erelinfo
->EdgeRelName);
ListExpr reltype= SecondoSystem::GetCatalog()
->GetObjectTypeExpr(erelinfo->EdgeRelName);
if (nl->IsEmpty(reltype))
throw SecondoException("relation not found");
// register relation
RelationInfo* relinfo=RelRegistry.AddRelation(erelinfo->EdgeRelName,
RelationRoleType::RoleEdge);
relinfo->InitRelSchema(reltype);
relinfo->FromName=erelinfo->FromRelName;
relinfo->ToName=erelinfo->ToRelName;
//
RelationInfo *fromrel = RelRegistry.GetRelationInfo(erelinfo->FromRelName);
RelationInfo *torel = RelRegistry.GetRelationInfo(erelinfo->ToRelName);
if (fromrel==NULL)
throw PGraph2Exception("Relation not found: "+erelinfo->FromRelName);
if (torel==NULL)
throw PGraph2Exception("Relation not found: "+erelinfo->ToRelName);
relinfo->FromAttrIndex=
relinfo->RelSchema.GetAttrInfo(erelinfo->FromIdName)->Index;
relinfo->ToAttrIndex=
relinfo->RelSchema.GetAttrInfo(erelinfo->ToIdName)->Index;
// get persistent statistics
relinfo->statistics=new RelStatistics();
relinfo->statistics->cardinality=erelinfo->StatCardinality;
relinfo->statistics->avgcardForward=erelinfo->StatAvgForward;
relinfo->statistics->avgcardBackward=erelinfo->StatAvgBackward;
size_t maxmemory = qp->GetMemorySize(s) * 1024 * 1024; //max memory in bytes
size_t availableMem = maxmemory/4;
// Loading statistics if necessary
if(rebuildStatistics)
{
LOGOP(10,"MemoryGraphObject::LoadEdgeRelation","getting edge statistics");
double d=0;
string query="(avg (groupby2 (smouterjoin (rename (feed "+
fromrel->Name+") n) (rename (feed "+erelinfo->EdgeRelName+
") e) "+fromrel->IdFieldName+"_n "+erelinfo->FromIdName+"_e) ("+
fromrel->IdFieldName+"_n) ( (C (fun (t TUPLE) (agg int) "
" (ifthenelse (isempty (attr t "+erelinfo->FromIdName+"_e)) agg "
"(+ agg 1)) ) 0 ) ) ) C) ";
LOGOP(30,"MemoryGraphObject::LoadEdgeRelation",
"getting avg of forward relations per "+fromrel->Name,query);
if (!queryValueDouble(query,d,availableMem))
{
cout << "failed." << endl;
};
relinfo->statistics->avgcardForward=d;
d=0;
query="(avg (groupby2 (smouterjoin (rename (feed "+
torel->Name+") n) (rename (feed "+
erelinfo->EdgeRelName+") e) "+torel->IdFieldName+"_n "+
erelinfo->ToIdName+"_e) ("+torel->IdFieldName+
"_n) ( (C (fun (t TUPLE) (agg int) (ifthenelse (isempty (attr t "+
erelinfo->ToIdName+"_e)) agg (+ agg 1)) ) 0 ) ) ) C) ";
LOGOP(30,"MemoryGraphObject::LoadEdgeRelation",
"getting avg of backward relations per "+torel->Name,query);
queryValueDouble(query,d,availableMem);
relinfo->statistics->avgcardBackward=d;
int counter=GetRelCount(memrelname);
relinfo->statistics->cardinality=counter;
// keep persistent
erelinfo->StatCardinality=counter;
erelinfo->StatAvgForward=relinfo->statistics->avgcardForward;
erelinfo->StatAvgBackward=relinfo->statistics->avgcardBackward;
}
LOGOP(30,"MemoryGraphObject::LoadEdgeRelation","finished" );
}
//-----------------------------------------------------------------------------
void MemoryGraphObject::Clear()
{
initstate=0;
AdjList.Clear();
RelRegistry.Clear();
//
memSize=0;
}
//-----------------------------------------------------------------------------
void MemoryGraphObject::LoadData(PGraph2 *pg, bool forcerebuildStatistics)
{
if (initstate==1)
{
Clear();
}
mrelprefix=pg->name+"_rel_";
// load relations as memory objects
for(auto&& item:pg->_nodeRelations)
{
LOGOP(10,"MemoryGraphObject::LoadData", " load to memory relation: "+
item.second->name);
DoLet( mrelprefix +item.second->name, "(mconsume (feed "+
item.second->name+"))");
}
for(auto&& item:pg->_edgeRelations)
{
LOGOP(10,"MemoryGraphObject::LoadData", " load to memory relation: "+
item.second->EdgeRelName);
DoLet( mrelprefix+item.second->EdgeRelName, "(mconsume (feed "+
item.second->EdgeRelName+"))");
}
// load relations as memory objects
for(auto&& item:pg->_Indexes)
{
string idxname=pg->name+"_idx_"+item.first;
ReplaceStringInPlace(idxname,".","_");
string isdefined="(isdefined (deleteObject \""+idxname+"\"))";
Word resultword;
QueryProcessor::ExecuteQuery(isdefined, resultword);
CcBool* zeiger = (CcBool*) resultword.addr;
delete zeiger;
string expr="(mcreateAVLtree "+mrelprefix+item.second->name+" "+
item.second->attr+")";
LOGOP(10,"MemoryGraphObject::LoadData", " create index: "+idxname);
//LOGOP(10,"MemoryGraphObject::LoadData", " create index: "+expr);
DoLet( idxname, expr);
}
// check for missing statistics
if (!forcerebuildStatistics)
{
for(auto&& item:pg->_nodeRelations)
if (item.second->StatCardinality<0)
{ forcerebuildStatistics=true; break;};
for(auto&& item:pg->_edgeRelations)
if (item.second->StatAvgForward<0 || item.second->StatAvgBackward<0)
{ forcerebuildStatistics=true; break;};
}
if (forcerebuildStatistics)
LOGOP(10,"MemoryGraphObject::LoadData", "needs to update statistics ...");
// load node relations
for(auto&& item:pg->_nodeRelations)
LoadNodeRelation( mrelprefix+item.second->name, item.second,
forcerebuildStatistics);
// preallocate vectors
LOGOP(10,"MemoryGraphObject::LoadData", " resize adjlist ",
AdjList.NodeGlobalCounter);
AdjList.OutGoingRels.resize( AdjList.NodeGlobalCounter );
AdjList.InGoingRels.resize( AdjList.NodeGlobalCounter );
// load edge relations
for(auto&& item : pg->_edgeRelations)
LoadEdgeRelation( mrelprefix+item.second->EdgeRelName, item.second,
forcerebuildStatistics);
// mark attributes as indexed
for(auto&& item:pg->_Indexes)
{
RelationInfo *ri=RelRegistry.GetRelationInfo(item.second->name);
if (ri!=NULL)
{
AttrInfo *ai = ri->RelSchema.GetAttrInfo(item.second->attr);
if (ai!=NULL)
ai->Indexed=true;
}
}
//change structure
pg->structure="memory";
//
initstate=1;
}
//-----------------------------------------------------------------------------
void MemoryGraphObject::LoadData2(PGraph2 *pg,
bool forcerebuildStatistics, Supplier s)
{
if (initstate==1)
{
Clear();
}
for(auto&& item:pg->_Indexes)
{
string indexname=pg->name+"_idx_"+item.first;
ReplaceStringInPlace(indexname,".","_");
string orgrelname=item.second->name+"P";
string isdefined="(isdefined (deleteObject \""+indexname+"\"))";
Word resultword;
QueryProcessor::ExecuteQuery(isdefined, resultword);
CcBool* zeiger = (CcBool*) resultword.addr;
delete zeiger;
//orgrelname=orgrelname.substr(0, orgrelname.size()-5);
string querystring = "(createbtree "+orgrelname+" "+item.second->attr+")";
DoLet(indexname,querystring);
}
// check for missing statistics
if (!forcerebuildStatistics)
{
for(auto&& item:pg->_nodeRelations)
if (item.second->StatCardinality<0)
{ forcerebuildStatistics=true; break;};
for(auto&& item:pg->_edgeRelations)
if (item.second->StatAvgForward<0 || item.second->StatAvgBackward<0)
{ forcerebuildStatistics=true; break;};
}
if (forcerebuildStatistics)
LOGOP(10,"MemoryGraphObject::LoadData", "needs to update statistics ...");
// load node relations
for(auto&& item:pg->_nodeRelations)
LoadNodeRelation2( item.second->name, item.second,
forcerebuildStatistics);
// load edge relations
for(auto&& item : pg->_edgeRelations)
LoadEdgeRelation2( item.second->EdgeRelName, item.second,
forcerebuildStatistics, s);
// mark attributes as indexed
for(auto&& item:pg->_Indexes)
{
RelationInfo *ri=RelRegistry.GetRelationInfo(item.second->name);
if (ri!=NULL)
{
AttrInfo *ai = ri->RelSchema.GetAttrInfo(item.second->attr);
if (ai!=NULL)
ai->Indexed=true;
}
}
//change structure
pg->structure="orel";
//
initstate=1;
}
//----------------------------------------------------------------------------
void MemoryGraphObject::DumpGraphDot(string filename)
{
ostringstream *data=new ostringstream();
*data << "digraph g {\n";
*data << " rankdir = TB; \n";
*data << " subgraph { \n";
map<string,vector<string>> ranks;
for(unsigned int i=0; i<AdjList.NodeList.size(); i++) {
RelationInfo *relinfo=RelRegistry.GetRelationInfo(
AdjList.NodeRelIdList[i]);
Tuple *tuple=AdjList.NodeList[i];
// node
*data <<"n"<< i << + "[label=<:"+relinfo->Name << "["
<< to_string(i) << "]" "<BR/>";
for (int a=0; a<relinfo->RelSchema.GetAttrCount();a++)
{
AttrInfo *ai=relinfo->RelSchema.GetAttrInfo(a);
*data << ai->Name <<" = " << ai->GetStringVal(tuple) << "<BR/>\n";
}
*data<<">]\n";
// rank
ranks[relinfo->Name].push_back("n"+to_string(i));
// outgoing
for (unsigned int a=0; a<AdjList.OutGoingRels[i].size(); a++)
{
Edge *e= AdjList.EdgeInfo[AdjList.OutGoingRels[i].at(a)];
*data<<"n"<<i<<" -> n"<< e->ToNodeId <<"\n";
*data << " [label=<";
RelationInfo *reledge=RelRegistry.GetRelationInfo(e->RelId);
*data<<":"<<reledge->Name << " ["<< e->EdgeId <<"]";
*data<<">]\n";
}
}
for(auto&& r : ranks)
{
*data << "{rank = same; ";
for(auto&& rn : r.second)
*data<<rn<<";";
*data<<" }\n";
}
//
*data<<"}}\n";
std::ofstream outfile;
outfile.open(filename, std::ios_base::trunc);
outfile << data->str();
delete data;
}
}//namespace