file src/depresolver.cpp
[No description available] More…
Namespaces
Name |
---|
Gambit TODO: see if we can use this one: |
Gambit::DRes |
Classes
Name | |
---|---|
class | Gambit::DRes::edgeWriter |
class | Gambit::DRes::labelWriter |
Defines
Name | |
---|---|
OBSLIKE_VERTEXID | |
NORMAL_DEPENDENCY | |
LOOP_MANAGER_DEPENDENCY |
Detailed Description
Author:
- Christoph Weniger (c.weniger@uva.nl)
- Pat Scott (patscott@physics.mcgill.ca)
- Ben Farmer (benjamin.farmer@monash.edu)
- Tomas Gonzalo (gonzalo@physik.rwth-aachen.de)
- Patrick Stoecker (stoecker@physik.rwth-aachen.de)
Date:
- 2013 May, Jun, Jul, Sep
- 2014 Feb, Mar, Apr
- 2013 May, Jul, Aug, Nov 2014 Jan, Mar, Apr, Dec 2018 Sep, Nov
- 2013 Sep
- 2017 June 2019 May 2020 June 2021 Sep
- 2020 May
Dependency resolution with boost graph library
unravels the un-unravelable
Authors (add name and date if you modify):
Macros Documentation
define OBSLIKE_VERTEXID
#define OBSLIKE_VERTEXID 58915032
define NORMAL_DEPENDENCY
#define NORMAL_DEPENDENCY 1
define LOOP_MANAGER_DEPENDENCY
#define LOOP_MANAGER_DEPENDENCY 2
Source code
// GAMBIT: Global and Modular BSM Inference Tool
// *********************************************
/// \file
///
/// Dependency resolution with boost graph library
///
/// unravels the un-unravelable
///
/// *********************************************
///
/// Authors (add name and date if you modify):
///
/// \author Christoph Weniger
/// (c.weniger@uva.nl)
/// \date 2013 May, Jun, Jul, Sep
/// \date 2014 Feb, Mar, Apr
///
/// \author Pat Scott
/// (patscott@physics.mcgill.ca)
/// \date 2013 May, Jul, Aug, Nov
/// 2014 Jan, Mar, Apr, Dec
/// 2018 Sep, Nov
///
/// \author Ben Farmer
/// (benjamin.farmer@monash.edu)
/// \date 2013 Sep
///
/// \author Tomas Gonzalo
/// (gonzalo@physik.rwth-aachen.de)
/// \date 2017 June
/// 2019 May
/// 2020 June
/// 2021 Sep
///
/// \author Patrick Stoecker
/// (stoecker@physik.rwth-aachen.de)
/// \date 2020 May
///
/// *********************************************
#include "gambit/Core/depresolver.hpp"
#include "gambit/Models/models.hpp"
#include "gambit/Utils/stream_overloads.hpp"
#include "gambit/Utils/util_functions.hpp"
#include "gambit/Utils/version.hpp"
#include "gambit/Utils/bibtex_functions.hpp"
#include "gambit/Utils/citation_keys.hpp"
#include "gambit/Logs/logger.hpp"
#include "gambit/Backends/backend_singleton.hpp"
#include "gambit/cmake/cmake_variables.hpp"
#include <sstream>
#include <fstream>
#include <iomanip>
#include <regex>
#include <boost/format.hpp>
#include <boost/algorithm/string/replace.hpp>
#ifdef HAVE_GRAPHVIZ
#include <boost/graph/graphviz.hpp>
#endif
// This vertex ID is reserved for nodes that correspond to
// likelihoods/observables/etc (observables of interest)
#define OBSLIKE_VERTEXID 58915032
// Dependency types
#define NORMAL_DEPENDENCY 1
#define LOOP_MANAGER_DEPENDENCY 2
// Debug flag
//#define DEPRES_DEBUG
// Verbose flag (not debug per se, just basic dependency resolution information)
//#define VERBOSE_DEP_RES
namespace Gambit
{
namespace DRes
{
using namespace LogTags;
///////////////////////
// Auxiliary functions
///////////////////////
//
// Functions that act on a resolved dependency graph
//
// Collect parent vertices recursively (excluding root vertex)
void getParentVertices(const VertexID & vertex, const
DRes::MasterGraphType & graph, std::set<VertexID> & myVertexList)
{
graph_traits<DRes::MasterGraphType>::in_edge_iterator it, iend;
for (boost::tie(it, iend) = in_edges(vertex, graph);
it != iend; ++it)
{
if ( std::find(myVertexList.begin(), myVertexList.end(), source(*it, graph)) == myVertexList.end() )
{
myVertexList.insert(source(*it, graph));
getParentVertices(source(*it, graph), graph, myVertexList);
}
}
}
// Sort given list of vertices (according to topological sort result)
std::vector<VertexID> sortVertices(const std::set<VertexID> & set,
const std::list<VertexID> & topoOrder)
{
std::vector<VertexID> result;
for(std::list<VertexID>::const_iterator it = topoOrder.begin(); it != topoOrder.end(); it++)
{
if (set.find(*it) != set.end())
result.push_back(*it);
}
return result;
}
// Get sorted list of parent vertices
std::vector<VertexID> getSortedParentVertices(const VertexID & vertex, const
DRes::MasterGraphType & graph, const std::list<VertexID> & topoOrder)
{
std::set<VertexID> set;
getParentVertices(vertex, graph, set);
set.insert(vertex);
return sortVertices(set, topoOrder);
}
//
// Functions that compare ini-file entries and observables
//
// Check whether quantity matches observableType
// Matches capability and type
bool quantityMatchesIniEntry(const sspair & quantity, const IniParser::ObservableType & observable, const Utils::type_equivalency & eq)
{
// Compares dependency specifications of rules entries or observable
// entries with capability (capabilities have to be unique for these
// lists)
return ( stringComp( observable.capability, quantity.first ) and
typeComp ( observable.type, quantity.second, eq ));
}
// Check whether quantity matches observableType
// Matches capability
bool capabilityMatchesIniEntry(const sspair & quantity, const IniParser::ObservableType & observable)
{
// Compares dependency specifications of rules entries or observable
// entries with capability (capabilities have to be unique for these
// lists)
return ( stringComp( observable.capability, quantity.first ) );
}
// Check whether functor matches ObservableType
// Matches capability, type, function and module name
bool moduleFuncMatchesIniEntry(functor *f, const IniParser::ObservableType &e, const Utils::type_equivalency & eq)
{
return (e.capability != "" ? stringComp(e.capability, f->capability()) : true)
and (e.type != "" ? typeComp (e.type, f->type(), eq) : true)
and (e.function != "" ? stringComp(e.function, f->name()) : true)
and (e.module != "" ? stringComp(e.module, f->origin()) : true);
}
// Check whether functor matches ObservableType
// Matches capability, type, function and backend name
bool backendFuncMatchesIniEntry(functor *f, const IniParser::ObservableType &e, const Utils::type_equivalency & eq)
{
return (e.capability != "" ? stringComp(e.capability, f->capability()) : true)
and (e.type != "" ? typeComp (e.type, f->type(), eq) : true)
and (e.function != "" ? stringComp(e.function, f->name()) : true)
and (e.backend != "" ? stringComp(e.backend, f->origin()) : true)
and (e.version != "" ? stringComp(e.version, f->version()) : true);
}
// Get entry level relevant for options
int getEntryLevelForOptions(const IniParser::ObservableType &e)
{
int z = 0;
if ( e.module != "" ) z = 1;
if ( e.capability != "" ) z = 2;
if ( e.type != "" ) z = 3;
if ( e.function != "" ) z = 4;
return z;
}
// Check whether functor matches rules
// Matches function name and type
bool matchesRules( functor *f, const Rule & rule)
{
#ifdef DEPRES_DEBUG
cout << (*f).name() << " vs " << rule.function << endl;
cout << (*f).origin() << " vs " << rule.module << endl;
#endif
// Rule matches functor if the capability or module function
// is present and they (and the module if given) match
bool matches = false;
if (not rule.capability.empty())
matches = stringComp( rule.capability, f->capability() );
if (not rule.function.empty())
matches = stringComp( rule.function, f->name() );
if (not rule.module.empty())
matches = matches and stringComp( rule.module, f->origin());
return matches;
}
//
// Graphviz output
//
// Graphviz output for edges/dependencies
class edgeWriter
{
public:
edgeWriter(const DRes::MasterGraphType*) {};
void operator()(std::ostream&, const EdgeID&) const
{
//out << "[style=\"dotted\"]";
}
};
// Graphviz output for individual vertices/nodes/module functions
class labelWriter
{
private:
const DRes::MasterGraphType * myGraph;
public:
labelWriter(const DRes::MasterGraphType * masterGraph) : myGraph(masterGraph) {};
void operator()(std::ostream& out, const VertexID& v) const
{
str type = Utils::fix_type((*myGraph)[v]->type());
boost::replace_all(type, str("&"), str("&"));
boost::replace_all(type, str("<"), str("<"));
boost::replace_all(type, str(">"), str(">"));
out << "[fillcolor=\"#F0F0D0\", style=\"rounded,filled\", shape=box,";
out << "label=< ";
out << "<font point-size=\"20\" color=\"red\">" << (*myGraph)[v]->capability() << "</font><br/>";
out << "Type: " << type << "<br/>";
out << "Function: " << (*myGraph)[v]->name() << "<br/>";
out << "Module: " << (*myGraph)[v]->origin();
out << ">]";
}
};
//
// Misc
//
/// Global flag for regex use
bool use_regex;
// Return runtime estimate for a set of nodes
double getTimeEstimate(const std::set<VertexID> & vertexList, const DRes::MasterGraphType &graph)
{
double result = 0;
for (std::set<VertexID>::iterator it = vertexList.begin(); it != vertexList.end(); ++it)
{
result += graph[*it]->getRuntimeAverage();
}
return result;
}
// Check whether s1 (wildcard + regex allowed) matches s2
bool stringComp(const str & s1, const str & s2, bool with_regex)
{
if ( s1 == s2 ) return true;
if ( s1 == "" ) return true;
if ( s1 == "*" ) return true;
try
{
if (with_regex) if (std::regex_match(s2, std::regex(s1))) return true;
}
catch (std::regex_error & err)
{
std::ostringstream errmsg;
errmsg << "ERROR during regex string comparison." << std::endl;
errmsg << " Comparing regular expression: " << s1 << std::endl;
errmsg << " with test string: " << s2 << std::endl;
dependency_resolver_error().raise(LOCAL_INFO,errmsg.str());
}
return false;
}
// Same thing for types (taking into account equivalence classes)
bool typeComp(str s1, str s2, const Utils::type_equivalency & eq, bool with_regex)
{
bool match1, match2;
// Loop over all the default versions of BOSSed backends and replace any corresponding *_default leading namespace with the explicit version.
if ((s1.find("_default") != std::string::npos) || (s2.find("_default") != std::string::npos))
{
for (auto it = Backends::backendInfo().default_safe_versions.begin(); it != Backends::backendInfo().default_safe_versions.end(); ++it)
{
s1 = Utils::replace_leading_namespace(s1, it->first+"_default", it->first+"_"+it->second);
s2 = Utils::replace_leading_namespace(s2, it->first+"_default", it->first+"_"+it->second);
}
}
// Does it just match?
if (stringComp(s1, s2, with_regex)) return true;
// Otherwise loop over equivalence classes.
for (auto it1 = eq.equivalency_classes.begin(); it1 != eq.equivalency_classes.end(); it1++)
{
match1 = match2 = false;
for (auto it2 = it1->begin(); it2 != it1->end(); it2++)
{
if (s2 == *it2) match1 = true;
if (stringComp(s1, *it2, with_regex)) match2 = true;
}
if (match1 and match2) return true;
}
return false;
}
///////////////////////////////////////////////////
// Public definitions of DependencyResolver class
///////////////////////////////////////////////////
// Constructor
DependencyResolver::DependencyResolver(const gambit_core &core,
const Models::ModelFunctorClaw &claw,
const IniParser::IniFile &iniFile,
const Utils::type_equivalency &equiv_classes,
Printers::BasePrinter &printer)
: boundCore(&core),
boundClaw(&claw),
boundIniFile(&iniFile),
boundTEs(&equiv_classes),
boundPrinter(&printer),
index(get(vertex_index,masterGraph)),
activeFunctorGraphFile(Utils::runtime_scratch()+"GAMBIT_active_functor_graph.gv")
{
addFunctors();
logger() << LogTags::dependency_resolver << endl;
logger() << "#######################################" << endl;
logger() << "# List of Type Equivalency Classes #" << endl;
logger() << "#######################################";
for (std::set<std::set<str> >::const_iterator it = boundTEs->equivalency_classes.begin(); it != boundTEs->equivalency_classes.end(); ++it)
{
logger() << endl << *it;
}
logger() << EOM;
}
//
// Initialization stage
//
// Main dependency resolution
void DependencyResolver::doResolution()
{
const IniParser::ObservablesType & observables = boundIniFile->getObservables();
// (cap., typ) --> dep. vertex map
std::queue<QueueEntry> parQueue;
QueueEntry queueEntry;
// Set up list of target ObsLikes
logger() << LogTags::dependency_resolver << endl;
logger() << "#######################################" << endl;
logger() << "# List of Target ObsLikes #" << endl;
logger() << "# #" << endl;
logger() << "# format: Capability (Type) [Purpose] #" << endl;
logger() << "#######################################";
for (auto it = observables.begin(); it != observables.end(); ++it)
{
// Format output
logger() << LogTags::dependency_resolver << endl << it->capability << " (" << it->type << ") [" << it->purpose << "]";
queueEntry.first.first = it->capability;
queueEntry.first.second = it->type;
queueEntry.second = OBSLIKE_VERTEXID;
queueEntry.printme = it->printme;
parQueue.push(queueEntry);
}
logger() << EOM;
// Activate functors compatible with model we scan over (and deactivate the rest)
makeFunctorsModelCompatible();
// Generate dependency tree (the core of the dependency resolution)
generateTree(parQueue);
// Find one execution order for activated vertices that is compatible
// with dependency structure
function_order = run_topological_sort();
// Loop manager initialization: Notify them about their nested functions
for (std::map<VertexID, std::set<VertexID>>::iterator it =
loopManagerMap.begin(); it != loopManagerMap.end(); ++it)
{
// Generate topologically sorted list of vertex IDs that are nested
// within loop manager (*it) ...
std::vector<VertexID> vertexList = sortVertices(it->second, function_order);
// ... map this on functor pointers...
std::vector<functor*> functorList;
for (std::vector<VertexID>::iterator jt = vertexList.begin(); jt != vertexList.end(); ++jt)
{
functorList.push_back(masterGraph[*jt]);
}
// ...and store it into loop manager functor
masterGraph[it->first]->setNestedList(functorList);
}
// Initialise the printer object with a list of functors that are set to print
initialisePrinter();
#ifdef HAVE_GRAPHVIZ
// Generate graphviz plot if running in dry-run mode.
if (boundCore->show_runorder)
{
std::ofstream outf(activeFunctorGraphFile);
write_graphviz(outf, masterGraph, labelWriter(&masterGraph), edgeWriter(&masterGraph));
}
#endif
// Pre-compute the individually ordered vertex lists for each of the ObsLike entries.
std::vector<VertexID> order = getObsLikeOrder();
for(auto it = order.begin(); it != order.end(); ++it)
{
SortedParentVertices[*it] = getSortedParentVertices(*it, masterGraph, function_order);
}
// Print list of backends required
if (boundCore->show_backends)
{
printRequiredBackends();
}
// Get BibTeX key entries for backends, modules, etc
getCitationKeys();
// Get the scanID
set_scanID();
// Done
}
/// List of masterGraph content
void DependencyResolver::printFunctorList()
{
// Activate functors compatible with model we scan over (and deactivate the rest)
makeFunctorsModelCompatible();
graph_traits<DRes::MasterGraphType>::vertex_iterator vi, vi_end;
const str formatString = "%-20s %-32s %-32s %-32s %-15s %-7i %-5i %-5i\n";
logger() << LogTags::dependency_resolver << endl << "Vertices registered in masterGraph" << endl;
logger() << "----------------------------------" << endl;
logger() << boost::format(formatString)%
"MODULE (VERSION)"% "FUNCTION"% "CAPABILITY"% "TYPE"% "PURPOSE"% "STATUS"% "#DEPs"% "#BE_REQs";
for (boost::tie(vi, vi_end) = vertices(masterGraph); vi != vi_end; ++vi)
{
logger() << boost::format(formatString)%
((*masterGraph[*vi]).origin() + " (" + (*masterGraph[*vi]).version() + ")") %
(*masterGraph[*vi]).name()%
(*masterGraph[*vi]).capability()%
(*masterGraph[*vi]).type()%
(*masterGraph[*vi]).purpose()%
(*masterGraph[*vi]).status()%
(*masterGraph[*vi]).dependencies().size()%
(*masterGraph[*vi]).backendreqs().size();
}
logger() << "Registered Backend vertices" << endl;
logger() << "---------------------------" << endl;
logger() << printGenericFunctorList(boundCore->getBackendFunctors());
logger() << EOM;
}
// Pretty print function evaluation order
void DependencyResolver::printFunctorEvalOrder(bool toterminal)
{
// Running this lets us check the order of execution. Also helps
// to verify that we actually have pointers to all the required
// functors.
// Get order of evaluation
std::set<VertexID> parents;
std::set<VertexID> done; //set of vertices already accounted for
std::vector<VertexID> order = getObsLikeOrder();
str formatString = "%-5s %-25s %-25s %-25s\n";
// Might need to check if terminal supports unicode characters...
str formatString0 = "%-7s %-23s %-25s %-25s %-25s %-6s\n"; // header
str formatString1a= "%-9s %-21s %-25s %-25s %-25s %-6s\n"; // target functors
str formatString1b= "%-4s \u2514\u2500\u2500> %-21s %-25s %-25s %-25s %-6s\n"; // target functors
str formatString2a= " \u250C\u2500 %-23s %-25s %-25s %-25s %-6s\n"; // parents
str formatString2b= " \u251C\u2500 %-23s %-25s %-25s %-25s %-6s\n";
str formatString3a= " \u250CX %-23s %-25s %-25s %-25s %-6s\n"; // "already done" parents
str formatString3b= " \u251CX %-23s %-25s %-25s %-25s %-6s\n";
int i = 0;
// Show the order in which the target functors will be attacked.
std::ostringstream ss;
ss << endl << "Initial target functor evaluation order" << endl;
ss << "----------------------------------" << endl;
ss << boost::format(formatString)% "#"% "FUNCTION"% "CAPABILITY"% "ORIGIN";
for (std::vector<VertexID>::const_iterator
vi = order.begin();
vi != order.end(); ++vi)
{
ss << boost::format(formatString)%
i%
(*masterGraph[*vi]).name()%
(*masterGraph[*vi]).capability()%
(*masterGraph[*vi]).origin();
i++;
}
ss << endl;
i = 0; // Reset counter
// Do another loop to show the full initial sequence of functor evaluation
// This doesn't figure out the sequence within each target functor group; I'm not 100% sure where that is determined. This does, however, show which groups get evaluated first, and which functors are already evaluated.
ss << endl << "Full initial functor evaluation order" << endl;
ss << "----------------------------------" << endl;
ss << boost::format(formatString0)% "#"% "FUNCTION"% "CAPABILITY"% "TYPE"% "ORIGIN"% "PRINT?";
for (std::vector<VertexID>::const_iterator
vi = order.begin();
vi != order.end(); ++vi)
{
// loop through parents of each target functor
parents.clear();
getParentVertices(*vi, masterGraph, parents);
parents.insert(*vi);
bool first = true;
for (std::set<VertexID>::const_iterator
vi2 = parents.begin();
vi2 != parents.end(); ++vi2)
{
str formatstr;
bool dowrite = false;
// Check if parent functor has been ticked off the list
bool is_done = done.find(*vi2) != done.end();
if( (not is_done) and (*vi != *vi2) )
{
formatstr = formatString2b;
if (first) formatstr = formatString2a;
dowrite = true;
}
else if( *vi != *vi2)
{
// Might be better to just do nothing here, i.e. set dowrite=false. For now just flagging functor as done with a special format string.
formatstr = formatString3b;
if (first) formatstr = formatString3a;
dowrite = true;
}
if (dowrite)
{
ss << boost::format(formatstr)%
(*masterGraph[*vi2]).name()%
(*masterGraph[*vi2]).capability()%
(*masterGraph[*vi2]).type()%
(*masterGraph[*vi2]).origin()%
(*masterGraph[*vi2]).requiresPrinting();
}
done.insert(*vi2); // tick parent functor off the list
first = false;
}
// Now show target functor info
str formatstr;
if(parents.size()==1) { formatstr = formatString1a; }
else { formatstr = formatString1b; }
ss << boost::format(formatstr)%
i%
(*masterGraph[*vi]).name()%
(*masterGraph[*vi]).capability()%
(*masterGraph[*vi]).type()%
(*masterGraph[*vi]).origin()%
(*masterGraph[*vi]).requiresPrinting();
i++;
done.insert(*vi); // tick this target functor off the list
}
ss << "(\"X\" indicates that the functor is pre-evaluated before the marked position)" << endl << endl;
if (toterminal)
{
// There is a command line flag to get this information, since it is very
// handy to check before launching a full job. It can always be checked via
// the logs, but this feature is more convenient.
cout << ss.str();
#ifdef HAVE_GRAPHVIZ
cout << "To get postscript plot of active functors, please run: " << endl;
cout << GAMBIT_DIR << "/Core/scripts/./graphviz.sh " << activeFunctorGraphFile << " no-loners" << endl;
#else
cout << "To get postscript plot of active functors, please install graphviz, rerun cmake and remake GAMBIT." << endl << endl;
#endif
}
logger() << LogTags::dependency_resolver << ss.str() << EOM;
}
// Print the list of required backends
void DependencyResolver::printRequiredBackends()
{
// Lists the required backends, indicating where several backends
// can fulfil the same requirement
std::stringstream ss;
ss << endl << "Required backends to run file " << boundIniFile->filename() << std::endl;
ss << "At least one backend candidate per row is required" << std::endl;
ss << "--------------------------------------------------" << std::endl << std::endl;
for(auto reqs : backendsRequired)
{
for(auto backend : reqs)
{
ss << boost::format("%-25s")%("("+backend.first+", "+backend.second+")");
}
ss << std::endl;
}
ss << std::endl;
// Print to terminal
std::cout << ss.str();
// Print to logs
logger() << LogTags::dependency_resolver << ss.str() << EOM;
}
// Print the BibTeX citation keys
void DependencyResolver::printCitationKeys()
{
// If the list is empty do not print anything
if(citationKeys.empty()) return;
std::stringstream ss;
// Location of the bibtex file
str bibtex_file_location = boundIniFile->getValueOrDef<str>(GAMBIT_DIR "/config/bibtex_entries.bib", "dependency_resolution", "bibtex_file_location");
ss << "The scan you are about to run uses backends. Please make sure to cite all of them in your work." << std::endl;
// Create a list of entries in the bibtex file
BibTeX bibtex_file(bibtex_file_location);
std::vector<str> entries = bibtex_file.getBibTeXEntries();
// Make sure that each key has an entry on the bibtex file
for(const auto& key : citationKeys)
{
// Now find each key in the list of entries
if(std::find(entries.begin(), entries.end(), key) == entries.end())
{
std::ostringstream errmsg;
errmsg << "The reference with key " << key << " cannot be found in the bibtex file " << bibtex_file_location << endl;
errmsg << "Please make sure that the bibtex file contains the relevant bibtex entries." << endl;
dependency_resolver_error().raise(LOCAL_INFO,errmsg.str());
}
}
// Drop a bibtex file with the citation entries
str bibtex_output_file = boundIniFile->getValueOrDef<str>("GAMBIT.bib", "dependency_resolution", "bibtex_output_file");
bibtex_file.dropBibTeXFile(citationKeys, bibtex_output_file);
// Drop a sample TeX file citing all backens
str tex_output_file = boundIniFile->getValueOrDef<str>("GAMBIT.tex", "dependency_resolution", "tex_output_file");
bibtex_file.dropTeXFile(citationKeys, tex_output_file, bibtex_output_file);
ss << "You can find the list of references to include in " << bibtex_output_file << ". And and example TeX file in " << tex_output_file << std::endl << std::endl;
// Print to terminal
std::cout << ss.str();
// Print to logs
logger() << LogTags::dependency_resolver << ss.str() << EOM;
}
//
// Runtime
//
// Returns list of ObsLike vertices in order of runtime
std::vector<VertexID> DependencyResolver::getObsLikeOrder()
{
std::vector<VertexID> unsorted;
std::vector<VertexID> sorted;
std::set<VertexID> parents, colleages, colleages_min;
// Copy unsorted vertexIDs --> unsorted
for (std::vector<OutputVertexInfo>::iterator it = outputVertexInfos.begin();
it != outputVertexInfos.end(); it++)
{
unsorted.push_back(it->vertex);
}
// Sort iteratively (unsorted --> sorted)
while (unsorted.size() > 0)
{
double t2p_now;
double t2p_min = -1;
std::vector<VertexID>::iterator it_min;
for (std::vector<VertexID>::iterator it = unsorted.begin(); it !=
unsorted.end(); ++it)
{
parents.clear();
getParentVertices(*it, masterGraph, parents);
parents.insert(*it);
// Remove vertices that were already calculated from the ist
for ( auto cit = colleages.begin(); cit != colleages.end(); cit++)
{
parents.erase(*cit);
}
t2p_now = (double) getTimeEstimate(parents, masterGraph);
t2p_now /= masterGraph[*it]->getInvalidationRate();
if (t2p_min < 0 or t2p_now < t2p_min)
{
t2p_min = t2p_now;
it_min = it;
colleages_min = parents;
}
}
// Extent list of calculated vertices
colleages.insert(colleages_min.begin(), colleages_min.end());
double prop = masterGraph[*it_min]->getInvalidationRate();
logger() << LogTags::dependency_resolver << "Estimated T [s]: " << t2p_min*prop << EOM;
logger() << LogTags::dependency_resolver << "Estimated p: " << prop << EOM;
sorted.push_back(*it_min);
unsorted.erase(it_min);
}
return sorted;
}
// Evaluates ObsLike vertex, and everything it depends on, and prints results
void DependencyResolver::calcObsLike(VertexID vertex)
{
if (SortedParentVertices.find(vertex) == SortedParentVertices.end())
core_error().raise(LOCAL_INFO, "Tried to calculate a function not in or not at top of dependency graph.");
std::vector<VertexID> order = SortedParentVertices.at(vertex);
for (std::vector<VertexID>::iterator it = order.begin(); it != order.end(); ++it)
{
std::ostringstream ss;
ss << "Calling " << masterGraph[*it]->name() << " from " << masterGraph[*it]->origin() << "...";
logger() << LogTags::dependency_resolver << LogTags::info << LogTags::debug << ss.str() << EOM;
masterGraph[*it]->calculate();
if (boundIniFile->getValueOrDef<bool>(
false, "dependency_resolution", "log_runtime") )
{
double T = masterGraph[*it]->getRuntimeAverage();
logger() << LogTags::dependency_resolver << LogTags::info <<
"Runtime, averaged over multiple calls [s]: " << T << EOM;
}
invalid_point_exception* e = masterGraph[*it]->retrieve_invalid_point_exception();
if (e != NULL) throw(*e);
}
// Reset the cout output precision, in case any backends have messed with it during the ObsLike evaluation.
cout << std::setprecision(boundCore->get_outprec());
}
// Prints the results of an ObsLike vertex
void DependencyResolver::printObsLike(VertexID vertex, const int pointID)
{
// pointID is supplied by the scanner, and is used to tell the printer which model
// point the results should be associated with.
if (SortedParentVertices.find(vertex) == SortedParentVertices.end())
core_error().raise(LOCAL_INFO, "Tried to calculate a function not in or not at top of dependency graph.");
std::vector<VertexID> order = SortedParentVertices.at(vertex);
for (std::vector<VertexID>::iterator it = order.begin(); it != order.end(); ++it)
{
std::ostringstream ss;
ss << "Printing " << masterGraph[*it]->name() << " from " << masterGraph[*it]->origin() << "...";
logger() << LogTags::dependency_resolver << LogTags::info << LogTags::debug << ss.str() << EOM;
if (not typeComp(masterGraph[*it]->type(), "void", *boundTEs, false))
{
// Note that this prints from thread index 0 only, i.e. results created by
// threads other than the main one need to be accessed with
// masterGraph[*it]->print(boundPrinter,pointID,index);
// where index is some integer s.t. 0 <= index <= number of hardware threads.
// At the moment GAMBIT only prints results of thread 0, under the expectation
// that nested module functions are all designed to gather their results into
// thread 0.
masterGraph[*it]->print(boundPrinter,pointID);
}
}
}
/// Getter for print_timing flag (used by LikelihoodContainer)
bool DependencyResolver::printTiming() { return print_timing; }
// Get the functor corresponding to a single VertexID
functor* DependencyResolver::get_functor(VertexID id)
{
graph_traits<DRes::MasterGraphType>::vertex_iterator vi, vi_end;
for (boost::tie(vi, vi_end) = vertices(masterGraph); vi != vi_end; ++vi)
{
if (*vi == id) return masterGraph[id];
}
return NULL;
}
// Ensure that the type of a given vertex is equivalent to at least one of a provided list, and return the match.
str DependencyResolver::checkTypeMatch(VertexID vertex, const str& purpose, const std::vector<str>& types)
{
for (auto it = types.begin(); it != types.end(); ++it)
{
if (typeComp(*it, masterGraph[vertex]->type(), *boundTEs, false)) return *it;
}
std::stringstream msg;
msg << "All quantities with purpose \"" << purpose << "\" in your yaml file must have one " << endl
<< "of the following types: " << endl << " " << types << endl
<< "You have tried to assign this purpose to " << masterGraph[vertex]->origin() << "::"
<< masterGraph[vertex]->name() << "," << endl << "which has capability: " << endl
<< " " << masterGraph[vertex]->capability() << endl << "and result type: " << endl
<< " [" << masterGraph[vertex]->type() << "]" << endl << "Please assign a different purpose to this entry.";
core_error().raise(LOCAL_INFO, msg.str());
return "If you make core errors non-fatal you deserve what you get.";
}
// Tell functor that it invalidated the current point in model space (due to a large or NaN contribution to lnL)
void DependencyResolver::invalidatePointAt(VertexID vertex, bool isnan)
{
if (isnan)
{
masterGraph[vertex]->notifyOfInvalidation("NaN returned for likelihood value.");
}
else
{
masterGraph[vertex]->notifyOfInvalidation("Cumulative log-likelihood pushed below threshold.");
}
}
// Returns pointer to ini-file entry associated with ObsLike
const IniParser::ObservableType * DependencyResolver::getIniEntry(VertexID v)
{
for (std::vector<OutputVertexInfo>::iterator it = outputVertexInfos.begin();
it != outputVertexInfos.end(); it++)
{
if (it->vertex == v)
return it->iniEntry;
}
return NULL;
}
// Resets all active functors and deletes existing results
void DependencyResolver::resetAll()
{
graph_traits<DRes::MasterGraphType>::vertex_iterator vi, vi_end;
for (boost::tie(vi, vi_end) = vertices(masterGraph); vi != vi_end; ++vi)
{
if (masterGraph[*vi]->status() == 2) masterGraph[*vi]->reset();
}
}
////////////////////////////////////////////////////
// Private definitions of DependencyResolver class
////////////////////////////////////////////////////
str DependencyResolver::printQuantityToBeResolved(const sspair & quantity, const DRes::VertexID & vertex)
{
str s = quantity.first + " (" + quantity.second + ")";
s += ", required by ";
if ( vertex != OBSLIKE_VERTEXID )
{
s += (*masterGraph[vertex]).capability() + " (";
s += (*masterGraph[vertex]).type() + ") [";
s += (*masterGraph[vertex]).name() + ", ";
s += (*masterGraph[vertex]).origin() + "]";
}
else
s += "Core";
return s;
}
str DependencyResolver::printGenericFunctorList(const std::vector<VertexID> & vertexIDs)
{
std::vector<functor*> functorList;
for ( auto it = vertexIDs.begin(); it != vertexIDs.end(); ++it )
{
functorList.push_back(masterGraph[*it]);
}
return printGenericFunctorList(functorList);
}
// Generic printer of the contents of a functor list
str DependencyResolver::printGenericFunctorList(const std::vector<functor*>& functorList)
{
const str formatString = "%-20s %-32s %-48s %-32s %-7i\n";
std::ostringstream stream;
stream << boost::format(formatString)%"ORIGIN (VERSION)"% "FUNCTION"% "CAPABILITY"% "TYPE"% "STATUS";
for (std::vector<functor *>::const_iterator
it = functorList.begin();
it != functorList.end();
++it)
{
stream << boost::format(formatString)%
((*it)->origin() + " (" + (*it)->version() + ")") %
(*it)->name()%
(*it)->capability()%
(*it)->type()%
(*it)->status();
}
return stream.str();
}
// Add module and primary model functors in bound core to class-internal
// masterGraph object
void DependencyResolver::addFunctors()
{
// Add primary model functors to masterGraph
for (std::vector<primary_model_functor *>::const_iterator
it = boundCore->getPrimaryModelFunctors().begin();
it != boundCore->getPrimaryModelFunctors().end();
++it)
{
// Ignore functors with status set to 0 or less in order to ignore primary_model_functors
// that are not to be used for the scan.
if ( (*it)->status() > 0 )
{
boost::add_vertex(*it, this->masterGraph);
}
}
// Add module functors to masterGraph
for (std::vector<functor *>::const_iterator
it = boundCore->getModuleFunctors().begin();
it != boundCore->getModuleFunctors().end();
++it)
{
boost::add_vertex(*it, this->masterGraph);
}
}
/// Activate functors that are allowed to be used with one or more of the models being scanned.
/// Also activate the model-conditional dependencies and backend requirements of those functors.
void DependencyResolver::makeFunctorsModelCompatible()
{
// Run just once
static bool already_run = false;
if (already_run) return;
graph_traits<DRes::MasterGraphType>::vertex_iterator vi, vi_end;
std::vector<functor *>::const_iterator fi, fi_end = boundCore->getBackendFunctors().end();
std::set<str> modelList = boundClaw->get_activemodels();
// Activate those module functors that match the combination of models being scanned.
for (boost::tie(vi, vi_end) = vertices(masterGraph); vi != vi_end; ++vi)
{
if (masterGraph[*vi]->status() >= 0 and masterGraph[*vi]->modelComboAllowed(modelList))
{
for (std::set<str>::iterator it = modelList.begin(); it != modelList.end(); ++it)
{
masterGraph[*vi]->notifyOfModel(*it);
masterGraph[*vi]->setStatus(1);
}
}
}
// Activate those backend functors that match one of the models being scanned.
for (std::set<str>::iterator it = modelList.begin(); it != modelList.end(); ++it)
{
for (fi = boundCore->getBackendFunctors().begin(); fi != fi_end; ++fi)
{
// Activate if the backend vertex permits the model and has not been (severely) disabled by the backend system
if ( (*fi)->status() >= 0 and (*fi)->modelAllowed(*it) )
{
(*fi)->setStatus(1);
}
}
}
already_run = true;
}
/// Set up printer object
// (i.e. give it the list of functors that need printing)
void DependencyResolver::initialisePrinter()
{
// Send the state of the "print_unitcube" flag to the printer
boundPrinter->set_printUnitcube(print_unitcube);
std::vector<int> functors_to_print;
graph_traits<MasterGraphType>::vertex_iterator vi, vi_end;
//IndexMap index = get(vertex_index, masterGraph); // Now done in the constructor
//Err does that make sense? There is nothing in masterGraph at that point surely... maybe put this back.
//Ok well it does seem to work in the constructor, not sure why though...
for (boost::tie(vi, vi_end) = vertices(masterGraph); vi != vi_end; ++vi)
{
// Inform the active functors of the vertex ID that the masterGraph has assigned to them
// (so that later on they can pass this to the printer object to identify themselves)
//masterGraph[*vi]->setVertexID(index[*vi]); // Ugh cannot do this, needs to be consistent with get_param_id
std::string label = masterGraph[*vi]->label();
masterGraph[*vi]->setVertexID(Printers::get_param_id(label));
// Same for timing output ID, but get ID number from printer system
std::string timing_label = masterGraph[*vi]->timingLabel();
masterGraph[*vi]->setTimingVertexID(Printers::get_param_id(timing_label));
// Check for non-void type and status==2 (after the dependency resolution) to print only active, printable functors.
// TODO: this doesn't currently check for non-void type; that is done at the time of printing in calcObsLike.
if( masterGraph[*vi]->requiresPrinting() and (masterGraph[*vi]->status()==2) )
{
functors_to_print.push_back(index[*vi]); // TODO: Probably obsolete
boundPrinter->addToPrintList(label); // Needed mainly by postprocessor.
// Trigger a dummy print call for all printable functors. This is used by some printers
// to set up buffers for each of these output streams.
//logger() << LogTags::dependency_resolver << "Triggering dummy print for functor '"<<masterGraph[*vi]->capability()<<"' ("<<masterGraph[*vi]->type()<<")..." << EOM;
//masterGraph[*vi]->print(boundPrinter,-1);
}
}
// Force-reset the printer to erase the dummy calls
// (but don't do this if we are in resume mode!)
//if(not boundCore->resume) boundPrinter->reset(true);
//boundPrinter->reset(true); // Actually *do* do it in resume mode as well. Printers should only reset new data, not destroy old data.
// sent vector of ID's of functors to be printed to printer.
// (if we want to only print functor output sometimes, and dynamically
// switch this on and off, we'll have to rethink the strategy here a
// little... for now if the print function of a functor does not get
// called, it is up to the printer how it deals with the missing result.
// Similarly for extra results, i.e. from any functors not in this
// initial list, whose "requiresPrinting" flag later gets set to 'true'
// somehow.)
boundPrinter->initialise(functors_to_print); // TODO: Probably obsolete
}
std::vector<DRes::VertexID> DependencyResolver::closestCandidateForModel(std::vector<DRes::VertexID> candidates)
{
// In case of doubt (and if not explicitely disabled in the ini-file), prefer functors
// that are more specifically tailored for the model being scanned. Do not consider functors
// that are accessible via INTERPRET_AS_X links, as these are all considered to be equally 'far'
// from the model being scanned, with the 'distance' being one step further than the most distant
// ancestor.
// Work up the model ancestry one step at a time, and stop as soon as one or more valid model-specific functors is
// found at a given level in the hierarchy.
std::vector<DRes::VertexID> newCandidates;
std::set<str> s = boundClaw->get_activemodels();
std::vector<str> parentModelList(s.begin(), s.end());
while (newCandidates.size() == 0 and not parentModelList.empty())
{
for (std::vector<str>::iterator mit = parentModelList.begin(); mit != parentModelList.end(); ++mit)
{
// Test each vertex candidate to see if it has been explicitly set up to work with the model *mit
for (std::vector<DRes::VertexID>::iterator it = candidates.begin(); it != candidates.end(); ++it)
{
if ( masterGraph[*it]->modelExplicitlyAllowed(*mit) ) newCandidates.push_back(*it);
}
// Step up a level in the model hierarchy for this model.
*mit = boundClaw->get_parent(*mit);
}
parentModelList.erase(std::remove(parentModelList.begin(), parentModelList.end(), "none"), parentModelList.end());
}
if (newCandidates.size() != 0)
return newCandidates;
else
return candidates;
}
/// Collect ini options
Options DependencyResolver::collectIniOptions(const DRes::VertexID & vertex)
{
YAML::Node nodes;
YAML::Node zlevels;
#ifdef DEPRES_DEBUG
cout << "Searching options for " << masterGraph[vertex]->capability() << endl;
#endif
const IniParser::ObservablesType & entries = boundIniFile->getRules();
for (IniParser::ObservablesType::const_iterator it =
entries.begin(); it != entries.end(); ++it)
{
if (moduleFuncMatchesIniEntry(masterGraph[vertex], *it, *boundTEs))
{
#ifdef DEPRES_DEBUG
cout << "Getting option from: " << it->capability << " " << it->type << endl;
#endif
for (auto jt = it->options.begin(); jt != it->options.end(); ++jt)
{
if ( not nodes[jt->first.as<std::string>()] )
{
#ifdef DEPRES_DEBUG
cout << jt->first.as<std::string>() << ": " << jt->second << endl;
#endif
nodes[jt->first.as<std::string>()] = jt->second;
zlevels[jt->first.as<std::string>()] = getEntryLevelForOptions(*it);
}
else
{
if ( zlevels[jt->first.as<std::string>()].as<int>() < getEntryLevelForOptions(*it) )
{
#ifdef DEPRES_DEBUG
cout << "Replaced : " << jt->first.as<std::string>() << ": " << jt->second << endl;
#endif
zlevels[jt->first.as<std::string>()] = getEntryLevelForOptions(*it);
nodes[jt->first.as<std::string>()] = jt->second;
}
else if ( zlevels[jt->first.as<std::string>()].as<int>() == getEntryLevelForOptions(*it) )
{
std::ostringstream errmsg;
errmsg << "ERROR! Multiple option entries with same level for key: " << jt->first.as<std::string>();
dependency_resolver_error().raise(LOCAL_INFO,errmsg.str());
}
}
}
}
}
return Options(nodes);
}
/// Collect sub-capabilities
Options DependencyResolver::collectSubCaps(const DRes::VertexID & vertex)
{
#ifdef DEPRES_DEBUG
cout << "Searching for subcaps of " << masterGraph[vertex]->capability() << endl;
#endif
YAML::Node nodes;
const IniParser::ObservablesType& entries = boundIniFile->getObservables();
// Iterate over the ObsLikes entries
for (auto it = entries.begin(); it != entries.end(); ++it)
{
// Select only those entries that match the current graph vertex (i.e. module function)
if (moduleFuncMatchesIniEntry(masterGraph[vertex], *it, *boundTEs) and not it->subcaps.IsNull())
{
#ifdef DEPRES_DEBUG
cout << "Found subcaps for " << it->capability << " " << it->type << " " << it->module << ":" << endl;
#endif
// The user has given just a single entry as a subcap
if (it->subcaps.IsScalar())
{
str key = it->subcaps.as<str>();
if (nodes[key]) dependency_resolver_error().raise(LOCAL_INFO,"Duplicate sub-capability for " + key + ".");
nodes[key] = YAML::Node();
}
// The user has passed a simple list of subcaps
else if (it->subcaps.IsSequence())
{
for (auto jt = it->subcaps.begin(); jt != it->subcaps.end(); ++jt)
{
if (not jt->IsScalar())
dependency_resolver_error().raise(LOCAL_INFO,"Attempt to pass map using sequence syntax for subcaps of "+it->capability+".");
str key = jt->as<str>();
if (nodes[key]) dependency_resolver_error().raise(LOCAL_INFO,"Duplicate sub-capability for " + key + ".");
nodes[key] = YAML::Node();
}
}
// The user has passed some more complicated subcap structure than just a list of strings
else if (it->subcaps.IsMap())
{
for (auto jt = it->subcaps.begin(); jt != it->subcaps.end(); ++jt)
{
str key = jt->first.as<str>();
if (nodes[key]) dependency_resolver_error().raise(LOCAL_INFO,"Duplicate sub-capability for " + key + ".");
nodes[key] = jt->second.as<YAML::Node>();
}
}
#ifdef DEPRES_DEBUG
cout << nodes << endl;
#endif
}
}
return Options(nodes);
}
/// Resolve dependency
// Can resolve:
// - capability, type pair (requires toVertex)
// Rules ordering:
// [Capability, Type] --> [Module, Function]
DRes::VertexID DependencyResolver::resolveDependencyFromRules(
const DRes::VertexID & toVertex, const sspair & quantity)
{
graph_traits<DRes::MasterGraphType>::vertex_iterator vi, vi_end;
// List of candidate vertices
std::vector<DRes::VertexID> vertexCandidates; // enabled
std::vector<DRes::VertexID> disabledVertexCandidates; // disabled
// Rules
std::vector<Rule> rules;
std::vector<Rule> strong_rules;
// Candidate vertices after applying rules
std::vector<DRes::VertexID> filteredVertexCandidates;
std::vector<DRes::VertexID> filteredVertexCandidates2;
// Make list of candidate vertices.
for (tie(vi, vi_end) = vertices(masterGraph); vi != vi_end; ++vi)
{
// Match capabilities and types (no type comparison when no types are
// given; this can only apply to output nodes or loop managers).
if ( stringComp(masterGraph[*vi]->capability(), quantity.first) and
*vi != toVertex and // No self-resolution
( quantity.second == "" or quantity.second == "*" or quantity.second == "any" or
typeComp(masterGraph[*vi]->type(), quantity.second, *boundTEs, false) ) )
{
// Add vertex to appropriate candidate list
if (masterGraph[*vi]->status() > 0)
vertexCandidates.push_back(*vi);
// If we only want the list of backends, also add vertex for ini function
else if (masterGraph[*vi]->status() == -4 and boundCore->show_backends)
vertexCandidates.push_back(*vi);
// If we only want the list of backends, also add disabled backends whose classes are needed
else if (masterGraph[*vi]->status() == -3 and boundCore->show_backends)
vertexCandidates.push_back(*vi);
// Otherwise
else
disabledVertexCandidates.push_back(*vi);
}
}
if (vertexCandidates.size() == 0)
{
std::ostringstream errmsg;
errmsg << "No candidates found while trying to resolve:" << endl;
errmsg << printQuantityToBeResolved(quantity, toVertex) << endl;
if (disabledVertexCandidates.size() != 0)
{
errmsg << "\nNote that viable candidates exist but have been disabled:\n"
<< printGenericFunctorList(disabledVertexCandidates)
<< endl
<< "Status flags:" << endl
<< " 0: This function is not compatible with any model you are scanning." << endl
<< "-3: This function requires a BOSSed class that is missing. The " << endl
<< " backend that provides the class is missing (most likely), the " << endl
<< " class is missing from the backend, or the factory functions" << endl
<< " for this class have not been BOSSed and loaded correctly." << endl;
}
errmsg << "Please check your yaml file for typos, and make sure that the" << endl
<< "models you are scanning are compatible with at least one function" << endl
<< "that provides this capability (they may all have been deactivated" << endl
<< "due to having ALLOW_MODELS declarations which are" << endl
<< "incompatible with the models selected for scanning)." << endl;
dependency_resolver_error().raise(LOCAL_INFO,errmsg.str());
}
logger() << LogTags::dependency_resolver;
logger() << "List of candidate vertices:" << endl;
logger() << printGenericFunctorList(vertexCandidates) << EOM;
if (toVertex != OBSLIKE_VERTEXID)
{
// Make list of all relevant 1st and 2nd level dependency rules.
const IniParser::ObservablesType & entries = boundIniFile->getRules();
for (IniParser::ObservablesType::const_iterator
it = entries.begin(); it != entries.end(); ++it)
{
{
// Evaluate "dependencies" section
if (moduleFuncMatchesIniEntry(masterGraph[toVertex], *it, *boundTEs) and
(it->capability != "" or it->function != "" or
it->type != "" or it->module != ""))
{
for (IniParser::ObservablesType::const_iterator
it2 = (*it).dependencies.begin();
it2 != (*it).dependencies.end(); ++it2)
{
if (quantityMatchesIniEntry(quantity, *it2, *boundTEs) and
(it2->capability != "" or it2->type != "") and
(it2->function != "" or it2->module != ""))
{
rules.push_back(Rule(*it2));
if (not it->weakrule and not it2->weakrule)
strong_rules.push_back(Rule(*it2));
}
}
}
// Evaluate "functionChain:" section
if (moduleFuncMatchesIniEntry(masterGraph[toVertex], *it, *boundTEs) and
it->capability != "" and
it->function == "" and
(*it).functionChain.size() > 1)
{
for (auto it2 = (*it).functionChain.begin();
it2 != (*it).functionChain.end() - 1; ++it2)
{
if ((*it2) == masterGraph[toVertex]->name())
{
Rule rule(*(it2+1), masterGraph[toVertex]->origin());
rules.push_back(rule);
if (not it->weakrule)
strong_rules.push_back(rule);
}
}
}
// Evaluate second order rules
if (quantityMatchesIniEntry(quantity, *it, *boundTEs) and
it->dependencies.size()==0 and
(it->capability != "" or it->type != "") and
(it->function != "" or it->module != ""))
{
rules.push_back(Rule(*it));
if (not it->weakrule)
strong_rules.push_back(Rule(*it));
}
}
}
}
else
{
// Add entries in ObsLike and Rules section as 2nd order
const IniParser::ObservablesType & entries = boundIniFile->getObservables();
for (IniParser::ObservablesType::const_iterator it =
entries.begin(); it != entries.end(); ++it)
{
if (quantityMatchesIniEntry(quantity, *it, *boundTEs) and
(it->capability != "" or it->type != "") and
(it->function != "" or it->module != ""))
{
rules.push_back(Rule(*it));
if (not it->weakrule)
strong_rules.push_back(Rule(*it));
}
// FIXME: Throw error if dependency or options entry exists
}
const IniParser::ObservablesType & entries2 = boundIniFile->getRules();
for (IniParser::ObservablesType::const_iterator it =
entries2.begin(); it != entries2.end(); ++it)
{
if (quantityMatchesIniEntry(quantity, *it, *boundTEs) and
it->dependencies.size()==0 and
(it->capability != "" or it->type != "") and
(it->function != "" or it->module != ""))
{
rules.push_back(Rule(*it));
if (not it->weakrule)
strong_rules.push_back(Rule(*it));
}
}
}
logger() << "Number of identified rules: " << rules.size() << endl
<< "Number of these rules that are marked as !weak: "
<< rules.size()-strong_rules.size() << EOM;
// Make filtered lists
for (std::vector<DRes::VertexID>::const_iterator
it = vertexCandidates.begin();
it != vertexCandidates.end(); it ++)
{
bool valid = true;
for (std::vector<Rule>::const_iterator it2 = rules.begin();
it2 != rules.end(); it2 ++)
{
if ( not matchesRules(masterGraph[*it], *it2) )
{
valid = false;
}
}
if (valid)
filteredVertexCandidates.push_back(*it);
valid = true;
for (std::vector<Rule>::const_iterator it2 = strong_rules.begin();
it2 != strong_rules.end(); it2 ++)
{
if ( not matchesRules(masterGraph[*it], *it2) )
{
valid = false;
}
}
if (valid)
filteredVertexCandidates2.push_back(*it);
}
if (rules.size() > 0 and filteredVertexCandidates.size() > 0)
{
logger() << "Candidate vertices that fulfill all rules:" << endl;
logger() << printGenericFunctorList(filteredVertexCandidates) << EOM;
}
if (filteredVertexCandidates.size() == 0)
{
filteredVertexCandidates = filteredVertexCandidates2;
logger() << "Ignoring rules declared as '!weak'" << endl;
logger() << "Candidate vertices that fulfill all non-weak rules:" << endl;
logger() << printGenericFunctorList(filteredVertexCandidates) << EOM;
}
// Apply tailor-made filter
if (boundIniFile->getValueOrDef<bool>(
true, "dependency_resolution", "prefer_model_specific_functions")
and filteredVertexCandidates.size() > 1)
{
filteredVertexCandidates = closestCandidateForModel(filteredVertexCandidates);
logger() << "A subset of vertex candidates is tailor-made for the scanned model." << endl;
logger() << "This is used as additional constraint since the YAML rules alone" << endl;
logger() << "are not constraining enough. These vertices are:" << endl;
logger() << printGenericFunctorList(filteredVertexCandidates) << EOM;
}
// Nothing left?
if ( filteredVertexCandidates.size() == 0 )
{
str errmsg = "None of the vertex candidates for";
errmsg += "\n" + printQuantityToBeResolved(quantity, toVertex);
errmsg += "\nfulfills all rules in the YAML file.";
errmsg += "\nPlease check your YAML file for contradictory rules, and";
errmsg += "\nensure that you have built GAMBIT in the first place with";
errmsg += "\nall of the components that you are trying to use.";
dependency_resolver_error().raise(LOCAL_INFO,errmsg);
}
// Did vertices survive?
if ( filteredVertexCandidates.size() == 1 )
return filteredVertexCandidates[0]; // And done!
str errmsg = "Unfortunately, the dependency resolution for";
errmsg += "\n" + printQuantityToBeResolved(quantity, toVertex);
errmsg += "\nis still ambiguous.\n";
errmsg += "\nThe candidate vertices are:\n";
errmsg += printGenericFunctorList(vertexCandidates) +"\n";
errmsg += "See logger output for details on the attempted (but failed) dependency resolution.\n";
errmsg += "\nAn entry in your YAML file that would e.g. select";
errmsg += "\nthe first of the above candidates could read ";
if ( toVertex != OBSLIKE_VERTEXID )
{
errmsg += "as a targeted rule (in the Rules section):\n";
errmsg += "\n - capability: "+masterGraph[toVertex]->capability();
errmsg += "\n function: "+masterGraph[toVertex]->name();
errmsg += "\n dependencies:";
errmsg += "\n - capability: " +masterGraph[vertexCandidates[0]]->capability();
errmsg += "\n function: " +masterGraph[vertexCandidates[0]]->name();
errmsg += "\n module: " +masterGraph[vertexCandidates[0]]->origin() +"\n\nor ";
}
errmsg += "as an untargeted rule (in the Rules or ObsLike section):\n";
errmsg += "\n - capability: "+masterGraph[vertexCandidates[0]]->capability();
errmsg += "\n type: "+masterGraph[vertexCandidates[0]]->type();
errmsg += "\n function: "+masterGraph[vertexCandidates[0]]->name();
errmsg += "\n module: " +masterGraph[vertexCandidates[0]]->origin() +"\n";
if ( toVertex == OBSLIKE_VERTEXID )
{
errmsg += "\n(Note that 1st class rules are not possible for vertices on which the core depends only.)\n";
}
dependency_resolver_error().raise(LOCAL_INFO,errmsg);
return 0;
}
/// Resolve dependency
boost::tuple<const IniParser::ObservableType *, DRes::VertexID>
DependencyResolver::resolveDependency( DRes::VertexID toVertex, sspair quantity)
{
graph_traits<DRes::MasterGraphType>::vertex_iterator vi, vi_end;
const IniParser::ObservableType *auxEntry = NULL; // Ptr. on ini-file entry of the dependent vertex (if existent)
const IniParser::ObservableType *depEntry = NULL; // Ptr. on ini-file entry that specifies how to resolve 'quantity'
std::vector<DRes::VertexID> vertexCandidates;
bool entryExists = false; // Ini-file entry to resolve 'quantity' found?
// First, we check whether the dependent vertex has a unique
// correspondence in the inifile. Final (output) vertices have to be
// treated different from all other vertices, since they do not appear
// as dependencies in the rules section of the inifile. For them,
// we just use the entry from the observable/likelihood section for the
// resolution of ambiguities. A pointer to the relevant inifile entry
// is stored in depEntry.
if ( toVertex == OBSLIKE_VERTEXID )
{
depEntry = findIniEntry(quantity, boundIniFile->getObservables(), "ObsLike");
entryExists = true;
}
// for all other vertices use the rules entries
else
{
auxEntry = findIniEntry(toVertex, boundIniFile->getRules(), "Rules");
if ( auxEntry != NULL )
depEntry = findIniEntry(quantity, (*auxEntry).dependencies, "dependency");
if ( auxEntry != NULL and depEntry != NULL )
{
entryExists = true;
}
}
// Loop over all available vertices in masterGraph, and make a list of
// functors that fulfill the dependency requirement.
for (tie(vi, vi_end) = vertices(masterGraph); vi != vi_end; ++vi)
{
// Don't allow resolution by deactivated functors
if (masterGraph[*vi]->status() > 0)
{
// Without inifile entry, just match capabilities and types (no type
// comparison when no types are given; this should only happen for
// output nodes)
if ( ( stringComp(masterGraph[*vi]->capability(), quantity.first) and
( quantity.second == "" or quantity.second == "*" or
typeComp(masterGraph[*vi]->type(), quantity.second, *boundTEs, false) ) )
// with inifile entry, we check capability, type, function name and
// module name.
and ( entryExists ? moduleFuncMatchesIniEntry(masterGraph[*vi], *depEntry, *boundTEs) : true ) )
{
// Add to vertex candidate list
vertexCandidates.push_back(*vi);
}
}
}
// Die if there is no way to fulfill this dependency.
if ( vertexCandidates.size() == 0 )
{
if ( not entryExists )
{
str errmsg = "I could not find any module function that provides ";
errmsg += quantity.first + " (" + quantity.second + ")"
+ "\nCheck your inifile for typos, your modules for consistency, etc.";
dependency_resolver_error().raise(LOCAL_INFO,errmsg);
}
else
{
str errmsg = "I could not find any module function that provides ";
errmsg += quantity.first + " (" + quantity.second + ") ["
+ depEntry->function + ", " + depEntry->module + "]"
+ "\nCheck your inifile for typos, your modules for consistency, etc.";
dependency_resolver_error().raise(LOCAL_INFO,errmsg);
}
}
// In case of doubt (and if not explicitely disabled in the ini-file), prefer functors
// that are more specifically tailored for the model being scanned. Do not consider functors
// that are accessible via INTERPRET_AS_X links, as these are all considered to be equally 'far'
// from the model being scanned, with the 'distance' being one step further than the most distant
// ancestor.
if ( vertexCandidates.size() > 1 and boundIniFile->getValueOrDef<bool>(true, "dependency_resolution", "prefer_model_specific_functions") )
{
// Work up the model ancestry one step at a time, and stop as soon as one or more valid model-specific functors is
// found at a given level in the hierarchy.
std::vector<DRes::VertexID> newVertexCandidates;
std::set<str> s = boundClaw->get_activemodels();
std::vector<str> parentModelList(s.begin(), s.end());
while (newVertexCandidates.size() == 0 and not parentModelList.empty())
{
for (std::vector<str>::iterator mit = parentModelList.begin(); mit != parentModelList.end(); ++mit)
{
// Test each vertex candidate to see if it has been explicitly set up to work with the model *mit
for (std::vector<DRes::VertexID>::iterator it = vertexCandidates.begin(); it != vertexCandidates.end(); ++it)
{
if ( masterGraph[*it]->modelExplicitlyAllowed(*mit) ) newVertexCandidates.push_back(*it);
}
// Step up a level in the model hierarchy for this model.
*mit = boundClaw->get_parent(*mit);
}
parentModelList.erase(std::remove(parentModelList.begin(), parentModelList.end(), "none"), parentModelList.end());
}
if (newVertexCandidates.size() != 0) vertexCandidates = newVertexCandidates;
}
if ( vertexCandidates.size() > 1 )
{
str errmsg = "";
if ( not entryExists )
{
errmsg += "I found too many module functions that provide ";
errmsg += quantity.first + " (" + quantity.second + ")"
+ "\nCheck your inifile for typos, your modules for consistency, etc.";
}
else
{
errmsg += "I found too many module functions that provide ";
errmsg += quantity.first + " (" + quantity.second + ") \n"
+ "\nneeded by " + depEntry->module + "::" + depEntry->function
+ "\nCheck your inifile for typos, your modules for consistency, etc.";
}
if (not boundIniFile->getValueOrDef<bool>(true, "dependency_resolution", "prefer_model_specific_functions"))
errmsg += "\nAlso consider turning on prefer_model_specific_functions in your inifile.";
errmsg += "\nCandidate module functions are:";
for (std::vector<DRes::VertexID>::iterator it = vertexCandidates.begin(); it != vertexCandidates.end(); ++it)
{
errmsg += "\n [" + masterGraph[*it]->name() + "," + masterGraph[*it]->origin() + "]";
}
dependency_resolver_error().raise(LOCAL_INFO,errmsg);
}
return boost::tie(depEntry, vertexCandidates[0]);
}
/// Set up dependency tree
void DependencyResolver::generateTree( std::queue<QueueEntry> parQueue)
{
OutputVertexInfo outInfo;
DRes::VertexID fromVertex, toVertex;
DRes::EdgeID edge;
// Inifile entry of ObsLike (if relevant)
const IniParser::ObservableType * iniEntry;
bool ok;
sspair quantity;
int dependency_type;
bool printme;
logger() << LogTags::dependency_resolver << endl;
logger() << "################################################" << endl;
logger() << "# Starting dependency resolution #" << endl;
logger() << "# #" << endl;
logger() << "# format: Capability (Type) [Function, Module] #" << endl;
logger() << "################################################" << EOM;
// Print something to stdout as well
#ifdef DEPRES_DEBUG
std::cout << "Resolving dependency graph..." << std::endl;
#endif
// Read ini entries
use_regex = boundIniFile->getValueOrDef<bool>(true, "dependency_resolution", "use_regex");
print_timing = boundIniFile->getValueOrDef<bool>(false, "print_timing_data");
print_unitcube = boundIniFile->getValueOrDef<bool>(false, "print_unitcube");
if ( use_regex ) logger() << "Using regex for string comparison." << endl;
if ( print_timing ) logger() << "Will output timing information for all functors (via printer system)" << EOM;
if ( print_unitcube ) logger() << "Printing of unitCubeParameters will be enabled." << EOM;
//
// Main loop: repeat until dependency queue is empty
//
while (not parQueue.empty())
{
// Retrieve capability, type and vertex ID of dependency of interest
quantity = parQueue.front().first; // (capability, type) pair
toVertex = parQueue.front().second; // dependent vertex
dependency_type = parQueue.front().third; // Normal or loop-manager
printme = parQueue.front().printme; // bool
// Print information about required quantity and dependent vertex
logger() << LogTags::dependency_resolver;
logger() << "Resolving ";
logger() << printQuantityToBeResolved(quantity, toVertex) << endl << endl;
// Extra verbose output to terminal
#ifdef VERBOSE_DEP_RES
std::cout << "Resolving dependency "<<printQuantityToBeResolved(quantity, toVertex)<<"..." <<std::endl;
#endif
// Check that ObsLike vertices have non-empty capability
if ( toVertex == OBSLIKE_VERTEXID and quantity.first == "" )
{
str errmsg = "ObsLike entry without without capability "
"information encountered.\n";
dependency_resolver_error().raise(LOCAL_INFO,errmsg);
}
// Figure out how to resolve dependency
if ( boundIniFile->getValueOrDef<bool>(false, "dependency_resolution", "use_old_routines") )
{
boost::tie(iniEntry, fromVertex) = resolveDependency(toVertex, quantity);
}
else
{
fromVertex = resolveDependencyFromRules(toVertex, quantity);
}
// Print user info.
logger() << LogTags::dependency_resolver;
logger() << "Resolved by: [";
logger() << (*masterGraph[fromVertex]).name() << ", ";
logger() << (*masterGraph[fromVertex]).origin() << "]" << endl;
// Extra verbose output to terminal
#ifdef VERBOSE_DEP_RES
std::cout << " ...resolved by ["<<(*masterGraph[fromVertex]).name()<<", "<<(*masterGraph[fromVertex]).origin()<<"]"<<std::endl;
#endif
// Check if we wanted to output this observable to the printer system.
if ( toVertex==OBSLIKE_VERTEXID ) masterGraph[fromVertex]->setPrintRequirement(printme);
// Check if the flag to output timing data is set
if(print_timing) masterGraph[fromVertex]->setTimingPrintRequirement(true);
// Apply resolved dependency to masterGraph and functors
if ( toVertex != OBSLIKE_VERTEXID )
{
// Resolve dependency on functor level...
//
// In case the fromVertex is a loop manager, store nested function
// temporarily in loopManagerMap (they have to be sorted later)
if (dependency_type == LOOP_MANAGER_DEPENDENCY)
{
// Check whether fromVertex is allowed to manage loops
if (not masterGraph[fromVertex]->canBeLoopManager())
{
str errmsg = "Trying to resolve dependency on loop manager with\n"
"module function that is not declared as loop manager.\n"
+ printGenericFunctorList(initVector<functor*>(masterGraph[fromVertex]));
dependency_resolver_error().raise(LOCAL_INFO,errmsg);
}
std::set<DRes::VertexID> v;
if (loopManagerMap.count(fromVertex) == 1)
{
v = loopManagerMap[fromVertex];
}
v.insert(toVertex);
loopManagerMap[fromVertex] = v;
(*masterGraph[toVertex]).resolveLoopManager(masterGraph[fromVertex]);
// Take any dependencies of loop-managed vertices that have already been resolved,
// and add them as "hidden" dependencies to this loop manager.
if (edges_to_force_on_manager.find(toVertex) != edges_to_force_on_manager.end())
{
for (auto it = edges_to_force_on_manager.at(toVertex).begin();
it != edges_to_force_on_manager.at(toVertex).end(); ++it)
{
logger() << "Dynamically adding dependency of " << (*masterGraph[fromVertex]).origin()
<< "::" << (*masterGraph[fromVertex]).name() << " on "
<< (*masterGraph[*it]).origin() << "::" << (*masterGraph[*it]).name() << endl;
boost::tie(edge, ok) = add_edge(*it, fromVertex, masterGraph);
}
}
}
// Default is to resolve dependency on functor level of toVertex
else
{
(*masterGraph[toVertex]).resolveDependency(masterGraph[fromVertex]);
}
// ...and on masterGraph level.
boost::tie(edge, ok) = add_edge(fromVertex, toVertex, masterGraph);
// In the case that toVertex is a nested function, add fromVertex to
// the edges of toVertex's loop manager.
str to_lmcap = (*masterGraph[toVertex]).loopManagerCapability();
str to_lmtype = (*masterGraph[toVertex]).loopManagerType();
str from_lmcap = (*masterGraph[fromVertex]).loopManagerCapability();
str from_lmtype = (*masterGraph[fromVertex]).loopManagerType();
bool is_same_lmcap = to_lmcap == from_lmcap;
bool is_same_lmtype = to_lmtype == "any" or from_lmtype == "any" or to_lmtype == from_lmtype;
if (to_lmcap != "none")
{
// This function runs nested. Check if its loop manager has been resolved yet.
if ((*masterGraph[toVertex]).loopManagerName() == "none")
{
// toVertex's loop manager has not yet been determined.
// Add the edge to the list to deal with when the loop manager dependency is resolved,
// as long as toVertex and fromVertex cannot end up inside the same loop.
if (!is_same_lmcap or !is_same_lmtype)
{
if (edges_to_force_on_manager.find(toVertex) == edges_to_force_on_manager.end())
edges_to_force_on_manager[toVertex] = std::set<DRes::VertexID>();
edges_to_force_on_manager.at(toVertex).insert(fromVertex);
}
}
else
{
// toVertex's loop manager has already been resolved.
// If fromVertex is not the manager itself, and is not
// itself a nested function that has the possibility to
// end up in the same loop as toVertex, then add
// fromVertex as an edge of the manager.
str name = (*masterGraph[toVertex]).loopManagerName();
str origin = (*masterGraph[toVertex]).loopManagerOrigin();
bool is_itself = (name == (*masterGraph[fromVertex]).name() and origin == (*masterGraph[fromVertex]).origin());
if (!is_itself and (!is_same_lmcap or !is_same_lmtype) )
{
// Hunt through the edges of toVertex and find the one that corresponds to its loop manager.
graph_traits<DRes::MasterGraphType>::in_edge_iterator ibegin, iend;
boost::tie(ibegin, iend) = in_edges(toVertex, masterGraph);
if (ibegin != iend)
{
DRes::VertexID managerVertex;
for (; ibegin != iend; ++ibegin)
{
managerVertex = source(*ibegin, masterGraph);
if ((*masterGraph[managerVertex]).name() == name and
(*masterGraph[managerVertex]).origin() == origin) break;
}
logger() << "Dynamically adding dependency of " << (*masterGraph[managerVertex]).origin()
<< "::" << (*masterGraph[managerVertex]).name() << " on "
<< (*masterGraph[fromVertex]).origin() << "::" << (*masterGraph[fromVertex]).name() << endl;
boost::tie(edge, ok) = add_edge(fromVertex, managerVertex, masterGraph);
}
else
{
core_error().raise(LOCAL_INFO, "toVertex has no edges! So its loop manager hasn't been added as a dependency?!");
}
}
}
}
}
else // if output vertex
{
iniEntry = findIniEntry(quantity, boundIniFile->getObservables(), "ObsLike");
outInfo.vertex = fromVertex;
outInfo.iniEntry = iniEntry;
outputVertexInfos.push_back(outInfo);
// Don't need subcaps during dry-run
if (not boundCore->show_runorder)
{
Options mySubCaps = collectSubCaps(fromVertex);
masterGraph[fromVertex]->notifyOfSubCaps(mySubCaps);
}
}
// If fromVertex is new, activate it
if ( (*masterGraph[fromVertex]).status() != 2 )
{
logger() << LogTags::dependency_resolver << "Activate new module function" << endl;
masterGraph[fromVertex]->setStatus(2); // activate node
resolveVertexBackend(fromVertex);
resolveVertexClassLoading(fromVertex);
// Don't need options during dry-run, so skip this (just to simplify terminal output)
if(not boundCore->show_runorder)
{
if ( boundIniFile->getValueOrDef<bool>( false, "dependency_resolution", "use_old_routines") )
{
// Generate options object from ini-file entry that corresponds to
// fromVertex (overwrite iniEntry) and pass it to the fromVertex for later use
iniEntry = findIniEntry(fromVertex, boundIniFile->getRules(), "Rules");
if ( iniEntry != NULL )
{
Options myOptions(iniEntry->options);
masterGraph[fromVertex]->notifyOfIniOptions(myOptions);
}
}
else
{
Options myOptions = collectIniOptions(fromVertex);
masterGraph[fromVertex]->notifyOfIniOptions(myOptions);
}
}
// Fill parameter queue with dependencies of fromVertex
fillParQueue(&parQueue, fromVertex);
}
// Done.
logger() << EOM;
parQueue.pop();
}
}
/// Push module function dependencies onto the parameter queue
void DependencyResolver::fillParQueue( std::queue<QueueEntry> *parQueue,
DRes::VertexID vertex)
{
// Set the default printing flag for functors to pass to the parQueue constructor.
bool printme_default = false;
// Tell the logger what the following messages are about.
logger() << LogTags::dependency_resolver;
// Digest capability of loop manager (if defined)
str lmcap = (*masterGraph[vertex]).loopManagerCapability();
str lmtype = (*masterGraph[vertex]).loopManagerType();
if (lmcap != "none")
{
logger() << "Adding module function loop manager to resolution queue:" << endl;
logger() << lmcap << " ()" << endl;
parQueue->push(QueueEntry(sspair(lmcap, lmtype), vertex, LOOP_MANAGER_DEPENDENCY, printme_default));
}
// Digest regular dependencies
std::set<sspair> s = (*masterGraph[vertex]).dependencies();
if (s.size() > 0) logger() << "Add dependencies of new module function to queue" << endl;
for (std::set<sspair>::iterator it = s.begin(); it != s.end(); ++it)
{
// If the loop manager requirement exists and is type-specific, it is a true depencency,
// and thus appears in the output of functor.dependencies(). So, we need to take care
// not to double-count it for entry into the parQueue.
if (lmcap == "none" or lmtype == "any" or lmcap != it->first or lmtype != it->second)
{
logger() << it->first << " (" << it->second << ")" << endl;
parQueue->push(QueueEntry(*it, vertex, NORMAL_DEPENDENCY, printme_default));
}
}
// Tell the logger we're done here.
logger() << EOM;
}
/// Boost lib topological sort
std::list<VertexID> DependencyResolver::run_topological_sort()
{
std::list<VertexID> topo_order;
topological_sort(masterGraph, front_inserter(topo_order));
return topo_order;
}
/// Find rules entry that matches vertex
const IniParser::ObservableType * DependencyResolver::findIniEntry(DRes::VertexID toVertex,
const IniParser::ObservablesType &entries, const str & errtag)
{
std::vector<const IniParser::ObservableType*> auxEntryCandidates;
for (IniParser::ObservablesType::const_iterator it =
entries.begin(); it != entries.end(); ++it)
{
if ( moduleFuncMatchesIniEntry(masterGraph[toVertex], *it, *boundTEs) and it->capability != "" )
{
auxEntryCandidates.push_back(&(*it));
}
}
if ( auxEntryCandidates.size() == 0 ) return NULL;
else if ( auxEntryCandidates.size() != 1 )
{
str errmsg = "Found multiple " + errtag + " entries for ";
errmsg += masterGraph[toVertex]->capability() +" (" +
masterGraph[toVertex]->type() + ") [" +
masterGraph[toVertex]->name() + ", " +
masterGraph[toVertex]->origin() + "]";
dependency_resolver_error().raise(LOCAL_INFO, errmsg);
}
return auxEntryCandidates[0]; // auxEntryCandidates.size() == 1
}
/// Find observable entry that matches capability/type
const IniParser::ObservableType* DependencyResolver::findIniEntry(
sspair quantity, const IniParser::ObservablesType & entries, const str & errtag)
{
std::vector<const IniParser::ObservableType*> obsEntryCandidates;
for (IniParser::ObservablesType::const_iterator it =
entries.begin(); it != entries.end(); ++it)
{
if ( capabilityMatchesIniEntry(quantity, *it) ) // use same criteria than for normal dependencies
{
obsEntryCandidates.push_back(&(*it));
}
}
if ( obsEntryCandidates.size() == 0 ) return NULL;
else if ( obsEntryCandidates.size() != 1 )
{
str errmsg = "Found multiple " + errtag + " entries for ";
errmsg += quantity.first + " (" + quantity.second + ")";
dependency_resolver_error().raise(LOCAL_INFO,errmsg);
}
return obsEntryCandidates[0]; // obsEntryCandidates.size() == 1
}
/// Node-by-node backend resolution
void DependencyResolver::resolveVertexBackend(VertexID vertex)
{
functor* solution;
std::vector<functor*> previous_successes;
std::set<str> remaining_groups;
std::set<sspair> remaining_reqs;
bool allow_deferral = true;
// If there are no backend requirements, and thus nothing to do, return.
if ((*masterGraph[vertex]).backendreqs().size() == 0) return;
// Get started.
logger() << LogTags::dependency_resolver << "Doing backend function resolution..." << EOM;
// Check whether this vertex is mentioned in the inifile.
const IniParser::ObservableType * auxEntry = findIniEntry(vertex, boundIniFile->getRules(), "Rules");
// Collect the list of groups that the backend requirements of this vertex exist in.
std::set<str> groups = (*masterGraph[vertex]).backendgroups();
// Collect the list of orphan (i.e. groupless) backend requirements.
std::set<sspair> orphan_reqs = (*masterGraph[vertex]).backendreqs("none");
// Loop until no further backend resolutions are possible, or no more are required.
while ( not ( groups.empty() and orphan_reqs.empty() ) )
{
// Loop over all groups, including the null group (group="none").
for (std::set<str>::iterator it = groups.begin(); it != groups.end(); ++it)
{
// Switch depending on whether this is a real group or not.
if (*it == "none")
{
// Loop over all the orphan requirements.
for (std::set<sspair>::iterator req = orphan_reqs.begin(); req != orphan_reqs.end(); ++req)
{
logger() << LogTags::dependency_resolver;
logger() << "Resolving ungrouped requirement " << req->first;
logger() << " (" << req->second << ")..." << EOM;
// Find a backend function that fulfills the backend requirement.
std::set<sspair> reqsubset;
reqsubset.insert(*req);
solution = solveRequirement(reqsubset,auxEntry,vertex,previous_successes,allow_deferral);
// Check if a valid solution has been returned
if (solution != NULL)
{
// It has, so resolve the backend requirement with that function and add it to the list of successful resolutions.
resolveRequirement(solution,vertex);
previous_successes.push_back(solution);
// If *req is in remaining_reqs, remove it
if (remaining_reqs.find(*req) != remaining_reqs.end())
{
remaining_reqs.erase(*req);
}
}
else // No valid solution found, but deferral has been suggested - so defer resolution of this group until later.
{
remaining_reqs.insert(*req);
logger() << LogTags::dependency_resolver;
logger() << "Resolution of ungrouped requirement " << req->first;
logger() << " (" << req->second << ") deferred until later." << EOM;
}
}
if (not remaining_reqs.empty()) remaining_groups.insert(*it);
}
else
{
logger() << LogTags::dependency_resolver;
logger() << "Resolving from group " << *it << "..." << EOM;
// Collect the list of backend requirements in this group.
std::set<sspair> reqs = (*masterGraph[vertex]).backendreqs(*it);
// Find a backend function that fulfills one of the backend requirements in the group.
solution = solveRequirement(reqs,auxEntry,vertex,previous_successes,allow_deferral,*it);
// Check if a valid solution has been returned
if (solution != NULL)
{
// It has, so resolve the backend requirement with that function and add it to the list of successful resolutions.
resolveRequirement(solution,vertex);
previous_successes.push_back(solution);
}
else // No valid solution found, but deferral has been suggested - so defer resolution of this group until later.
{
remaining_groups.insert(*it);
logger() << LogTags::dependency_resolver;
logger() << "Resolution from group " << *it;
logger() << "deferred until later." << EOM;
}
}
}
// If there has been no improvement this round, turn off deferral and make the next round the last attempt.
if (orphan_reqs == remaining_reqs and groups == remaining_groups)
{
allow_deferral = false;
}
else // Otherwise try again to resolve the remaining groups and orphan requirements, now that some others are known.
{
orphan_reqs = remaining_reqs;
groups = remaining_groups;
remaining_reqs.clear();
remaining_groups.clear();
}
}
}
/// Find a backend function that matches any one of a vector of capability-type pairs.
functor* DependencyResolver::solveRequirement(std::set<sspair> reqs,
const IniParser::ObservableType * auxEntry, VertexID vertex, std::vector<functor*> previous_successes,
bool allow_deferral, str group)
{
std::vector<functor*> vertexCandidates;
std::vector<functor*> vertexCandidatesWithIniEntry;
std::vector<functor*> disabledVertexCandidates;
// Loop over all existing backend vertices, and make a list of
// functors that are available and fulfill the backend requirement
for (std::vector<functor *>::const_iterator
itf = boundCore->getBackendFunctors().begin();
itf != boundCore->getBackendFunctors().end();
++itf)
{
const IniParser::ObservableType * reqEntry = NULL;
bool entryExists = false;
// Find relevant iniFile entry from Rules section
if ( auxEntry != NULL ) reqEntry = findIniEntry((*itf)->quantity(), (*auxEntry).backends, "backend");
if ( reqEntry != NULL) entryExists = true;
// Look for a match to at least one backend requirement, taking into account type equivalency classes.
bool simple_match = false;
for (std::set<sspair>::const_iterator
itr = reqs.begin();
itr != reqs.end();
++itr)
{
if ((*itf)->capability() == itr->first and typeComp((*itf)->type(), itr->second, *boundTEs))
{
simple_match = true;
break;
}
}
// If there is a relevant inifile entry, we also check for a match to the capability, type, function name and backend name in that entry.
if ( simple_match and ( entryExists ? backendFuncMatchesIniEntry(*itf, *reqEntry, *boundTEs) : true ) )
{
// Has the backend vertex already been disabled by the backend system?
bool disabled = ( (*itf)->status() <= 0 );
// Is it permitted to be used to fill this backend requirement?
// First we create the backend-version pair for the backend vertex and its semi-generic form (where any version is OK).
sspair itf_signature((*itf)->origin(), (*itf)->version());
sspair itf_generic((*itf)->origin(), "any");
// Then we find the set of backend-version pairs that are permitted.
std::set<sspair> permitted_bes = (*masterGraph[vertex]).backendspermitted((*itf)->quantity());
// Then we see if any match. First we test for generic matches, where any version of any backend is allowed.
bool permitted = ( permitted_bes.empty()
// Next we test for semi-generic matches, where the backend matches and any version of that backend is allowed.
or std::find(permitted_bes.begin(), permitted_bes.end(), itf_generic) != permitted_bes.end()
// Finally we test for specific matches, where both the backend and version match what is allowed.
or std::find(permitted_bes.begin(), permitted_bes.end(), itf_signature) != permitted_bes.end() );
// If the backend vertex is able and allowed,
if (permitted and not disabled)
{
// add it to the overall vertex candidate list
vertexCandidates.push_back(*itf);
// if it has an inifile entry, add it to the candidate list with inifile entries
if (entryExists) vertexCandidatesWithIniEntry.push_back(*itf);
}
else if (permitted and boundCore->show_backends) // If the backend is able and we only want to show the list of backends
{
// add it to the overall vertex candidate list
vertexCandidates.push_back(*itf);
// if it has an inifile entry, add it to the candidate list with inifile entries
if (entryExists) vertexCandidatesWithIniEntry.push_back(*itf);
}
else
{
// otherwise, add it to disabled vertex candidate list
if (not disabled) (*itf)->setStatus(1);
disabledVertexCandidates.push_back(*itf);
}
}
}
// If too many candidates, prefer those with entries in the inifile.
if (vertexCandidates.size() > 1 and vertexCandidatesWithIniEntry.size() >= 1)
{
// Loop over the remaining candidates, and disable those without entries in the inifile.
for (std::vector<functor *>::iterator it = vertexCandidates.begin(); it != vertexCandidates.end(); ++it)
{
if (std::find(vertexCandidatesWithIniEntry.begin(), vertexCandidatesWithIniEntry.end(), *it) == vertexCandidatesWithIniEntry.end() )
disabledVertexCandidates.push_back(*it);
}
// Set the new list of vertex candidates to be only those with inifile entries.
vertexCandidates = vertexCandidatesWithIniEntry;
}
// Purge all candidates that conflict with a backend-matching rule.
// Start by making a new vector to hold the candidates that survive the purge.
std::vector<functor *> survivingVertexCandidates;
// Loop over the current candidates.
for (std::vector<functor *>::const_iterator it = vertexCandidates.begin(); it != vertexCandidates.end(); ++it)
{
// Set up a flag to keep track of whether anything has indicated that the candidate should be thrown out.
bool keeper = true;
// Retrieve the tags of the candidate.
std::set<str> tags = (*masterGraph[vertex]).backendreq_tags((*it)->quantity());
// Loop over the tags
for (std::set<str>::iterator tagit = tags.begin(); tagit != tags.end(); ++tagit)
{
// Find out which other backend requirements exhibiting this tag must be filled from the same backend as the req this candidate would fill.
std::set<sspair> must_match = (*masterGraph[vertex]).forcematchingbackend(*tagit);
// Set up a flag to keep track of whether any of the other backend reqs have already been filled.
bool others_filled = false;
// Set up a string to keep track of which backend the other backend reqs have been filled from (if any).
str common_backend_and_version;
// Loop over the other backend reqs.
for (std::set<sspair>::iterator mit = must_match.begin(); mit != must_match.end(); ++mit)
{
// Set up a flag to indicate if the other backend req in question has been filled yet.
bool other_filled = false;
// Set up a string to keep track of which backend the other backend req in question has been filled from (if any).
str filled_from;
// Loop over the backend functors that have successfully filled backend reqs already for this funcition
for (std::vector<functor*>::const_iterator
itf = previous_successes.begin();
itf != previous_successes.end();
++itf)
{
// Check if the current previous successful resolution (itf) was of the same backend requirement as the
// current one of the backend requirements (mit) that must be filled from the same backend as the current candidate (it).
if ((*itf)->quantity() == *mit)
{
// Note that mit (the current backend req that must be filled from the same backend as the current candidate) has indeed been filled, by itf
other_filled = true;
// Note which backend mit has been filled from (i.e. where does itf come from?)
filled_from = (*itf)->origin() + " v" + (*itf)->version();
break;
}
}
// If the other req has been filled, updated the tracker of whether any of the reqs linked to this flag have been filled,
// and compare the filling backend to the one used to fill any other reqs associated with this tag.
if (other_filled)
{
others_filled = true;
if (common_backend_and_version.empty()) common_backend_and_version = filled_from; // Save the filling backend
if (filled_from != common_backend_and_version) // Something buggy has happened and the rule is already broken(!)
{
str errmsg = "A backend-matching rule has been violated!";
errmsg += "\nFound whilst checking which backends have been used"
"\nto fill requirements with tag " + *tagit + " in function "
"\n" + (*masterGraph[vertex]).name() + " of " + (*masterGraph[vertex]).origin() + "."
"\nOne requirement was filled from " + common_backend_and_version + ", "
"\nwhereas another was filled from " + filled_from + "."
"\nThis should not happen and is probably a bug in GAMBIT.";
dependency_resolver_error().raise(LOCAL_INFO,errmsg);
}
}
}
// Try to keep this candidate if it comes from the same backend as those already filled, or if none of the others are filled yet.
keeper = (not others_filled or common_backend_and_version == (*it)->origin() + " v" + (*it)->version());
if (not keeper) break;
}
if (keeper) survivingVertexCandidates.push_back(*it); else disabledVertexCandidates.push_back(*it);
}
// Replace the previous list of candidates with the survivors.
vertexCandidates = survivingVertexCandidates;
// Only print the status flags -5 or -6 if any of the disabled vertices has it
bool printMathematicaStatus = false;
for(unsigned int j=0; j < disabledVertexCandidates.size(); j++)
if(disabledVertexCandidates[j]->status() == -5)
printMathematicaStatus = true;
bool printPythonStatus = false;
for(unsigned int j=0; j < disabledVertexCandidates.size(); j++)
if(disabledVertexCandidates[j]->status() == -6)
printPythonStatus = true;
// No candidates? Death.
if (vertexCandidates.size() == 0)
{
std::ostringstream errmsg;
errmsg
<< "Found no candidates for backend requirements of "
<< masterGraph[vertex]->origin() << "::" << masterGraph[vertex]->name() << ":\n"
<< reqs << "\nfrom group: " << group;
if (disabledVertexCandidates.size() != 0)
{
errmsg << "\nNote that viable candidates exist but have been disabled:\n"
<< printGenericFunctorList(disabledVertexCandidates)
<< endl
<< "Status flags:" << endl
<< " 1: This function is available, but the backend version is not compatible with all your requests." << endl
<< " 0: This function is not compatible with any model you are scanning." << endl
<< "-1: The backend that provides this function is missing." << endl
<< "-2: The backend is present, but function is absent or broken." << endl;
if(printMathematicaStatus)
errmsg << "-5: The backend requires Mathematica, but Mathematica is absent." << endl;
if(printPythonStatus)
errmsg << "-6: The backend requires Python, but pybind11 is absent." << endl;
errmsg << endl
<< "Make sure to check your YAML file, especially the rules" << endl
<< "pertaining to backends." << endl
<< endl
<< "Please also check that all shared objects exist for the" << endl
<< "necessary backends, and that they contain all the" << endl
<< "necessary functions required for this scan. You may" << endl
<< "check the status of different backends by running" << endl
<< " ./gambit backends" << endl
<< "You may also wish to check the specified search paths for each" << endl
<< "backend shared library in " << endl;
if (Backends::backendInfo().custom_locations_exist())
{
errmsg << " " << Backends::backendInfo().backend_locations() << endl << "and" << endl;
}
errmsg << " " << Backends::backendInfo().default_backend_locations() << endl;
}
dependency_resolver_error().raise(LOCAL_INFO,errmsg.str());
}
// Still more than one candidate...
if (vertexCandidates.size() > 1)
{
// Check whether any of the remaining candidates is subject to a backend-matching rule,
// and might therefore be uniquely chosen over the other(s) if resolution for this req is attempted again, after
// another of the reqs subject to the same rule is resolved.
bool rule_exists = false;
// Loop over the remaining candidates.
for (std::vector<functor *>::const_iterator it = vertexCandidates.begin(); it != vertexCandidates.end(); ++it)
{
// Retrieve the tags of the candidate.
std::set<str> tags = (*masterGraph[vertex]).backendreq_tags((*it)->quantity());
// Loop over the tags
for (std::set<str>::iterator tagit = tags.begin(); tagit != tags.end(); ++tagit)
{
// Find if there is a backend-matching rule associated with this tag.
rule_exists = not (*masterGraph[vertex]).forcematchingbackend(*tagit).empty();
if (rule_exists) break;
}
if (rule_exists) break;
}
// If deferral is allowed and appears to be potentially useful, defer resolution until later.
if (allow_deferral and rule_exists)
{
return NULL;
}
// If not, we have just one more trick up our sleeves... use the models scanned to narrow things down.
if (boundIniFile->getValueOrDef<bool>(true, "dependency_resolution", "prefer_model_specific_functions"))
{
// Prefer backend functors that are more specifically tailored for the model being scanned. Do not
// consider backend functors that are accessible via INTERPRET_AS_X links, as these are all considered
// to be equally 'far' from the model being scanned, with the 'distance' being one step further than
// the most distant ancestor.
std::vector<functor*> newCandidates;
std::set<str> s = boundClaw->get_activemodels();
std::vector<str> parentModelList(s.begin(), s.end());
while (newCandidates.size() == 0 and not parentModelList.empty())
{
for (std::vector<str>::iterator mit = parentModelList.begin(); mit != parentModelList.end(); ++mit)
{
// Test each vertex candidate to see if it has been explicitly set up to work with the model *mit
for (std::vector<functor*>::iterator it = vertexCandidates.begin(); it != vertexCandidates.end(); ++it)
{
if ( (*it)->modelExplicitlyAllowed(*mit) ) newCandidates.push_back(*it);
}
// Step up a level in the model hierarchy for this model.
*mit = boundClaw->get_parent(*mit);
}
parentModelList.erase(std::remove(parentModelList.begin(), parentModelList.end(), "none"), parentModelList.end());
}
if (newCandidates.size() != 0) vertexCandidates = newCandidates;
}
// Still more than one candidate, so the game is up.
// Don't worry about too many candidates if we only want the list of required backends
if (vertexCandidates.size() > 1 and not boundCore->show_backends)
{
str errmsg = "Found too many candidates for backend requirement ";
if (reqs.size() == 1) errmsg += reqs.begin()->first + " (" + reqs.begin()->second + ")";
else errmsg += "group " + group;
errmsg += " of module function " + masterGraph[vertex]->origin() + "::" + masterGraph[vertex]->name()
+ "\nViable candidates are:\n" + printGenericFunctorList(vertexCandidates);
errmsg += "\nIf you don't need all the above backends, you can resolve the ambiguity simply by";
errmsg += "\nuninstalling the backends you don't use.";
errmsg += "\n\nAlternatively, you can add an entry in your YAML file that selects which backend";
errmsg += "\nthe module function " + masterGraph[vertex]->origin() + "::" + masterGraph[vertex]->name() + " should use. A YAML file entry";
errmsg += "\nthat selects e.g. the first candidate above could read\n";
errmsg += "\n - capability: "+masterGraph[vertex]->capability();
errmsg += "\n function: "+masterGraph[vertex]->name();
errmsg += "\n backends:";
errmsg += "\n - {capability: "+vertexCandidates.at(0)->capability()+", type: "
+vertexCandidates.at(0)->type()+", backend: "
+vertexCandidates.at(0)->origin()+", version: "
+vertexCandidates.at(0)->version()+"}\n";
dependency_resolver_error().raise(LOCAL_INFO,errmsg);
}
}
// Store the resolved backend requirements
std::vector<sspair> resolvedBackends;
for(auto vertex : vertexCandidates)
{
sspair backend(vertex->origin(), vertex->version());
resolvedBackends.push_back(backend);
}
bool found = false;
for(auto br = backendsRequired.begin(); br != backendsRequired.end(); ++br)
{
found = true;
for(auto backend : resolvedBackends)
{
if(std::find(br->begin(), br->end(), backend) == br->end())
found = false;
}
if(found) break;
}
if(not found)
{
backendsRequired.push_back(resolvedBackends);
}
// Just one candidate. Jackpot.
return vertexCandidates[0];
}
/// Resolve a backend requirement of a specific module function using a specific backend function.
void DependencyResolver::resolveRequirement(functor* func, VertexID vertex)
{
(*masterGraph[vertex]).resolveBackendReq(func);
logger() << LogTags::dependency_resolver;
logger() << "Resolved by: [" << func->name() << ", ";
logger() << func->origin() << " (" << func->version() << ")]";
logger() << EOM;
}
/// Check for unused rules and options
void DependencyResolver::checkForUnusedRules(int mpi_rank)
{
std::vector<Rule> unusedRules;
const IniParser::ObservablesType & entries = boundIniFile->getRules();
for(IniParser::ObservableType entry : entries)
{
#ifdef DEPRES_DEBUG
std::cout << "checking rule with capability " << entry.capability << std::endl;
#endif
graph_traits<DRes::MasterGraphType>::vertex_iterator vi, vi_end;
bool unused = true;
for (boost::tie(vi, vi_end) = vertices(masterGraph); vi != vi_end; ++vi)
{
// Check only for enabled functors
if (masterGraph[*vi]->status() == 2)
{
if ( matchesRules(masterGraph[*vi], Rule(entry)) )
{
#ifdef DEPRES_DEBUG
std::cout << "rule for capability " << entry.capability <<" used by vertex " << masterGraph[*vi]->capability() << std::endl;
#endif
unused = false;
continue;
}
}
}
if (unused)
unusedRules.push_back(Rule(entry));
}
if(unusedRules.size() > 0)
{
std::stringstream msg;
msg << "The following rules and options are not used in the current scan. This will not affect the results of the scan, but if you wish to avoid this warning you must remove all unused rules and options from the yaml file." << endl;
for(auto rule :unusedRules)
{
if(not rule.capability.empty()) msg << " capability: " << rule.capability << endl;
if(not rule.function.empty()) msg << " function: " << rule.function<< endl;
if(not rule.module.empty()) msg << " module: " << rule.module << endl;
if(not rule.type.empty()) msg << " type: " << rule.type << endl;
if(not rule.backend.empty()) msg << " backend: " << rule.backend << endl;
if(not rule.version.empty()) msg << " version: " << rule.version << endl;
if (rule.options.getNames().size() > 0)
{
msg << " options:" << endl;
msg << rule.options.toString(2);
}
msg << endl;
}
logger() << msg.str() << EOM;
if(mpi_rank == 0) std::cout << msg.str() << std::endl;
}
}
/// Construct metadata information from used observables, rules and options
/// Note: No keys can be identical (or differing only by capitalisation)
/// to those printed in the main file, otherwise the sqlite printer fails
map_str_str DependencyResolver::getMetadata()
{
map_str_str metadata;
// Gambit version
metadata["GAMBIT"] = gambit_version();
// Date
auto now = std::chrono::system_clock::now();
auto in_time_t = std::chrono::system_clock::to_time_t(now);
std::stringstream ss;
ss << std::put_time(std::localtime(&in_time_t), "%Y-%m-%d %H:%M");
metadata["Date"] = ss.str();
// scanID
if (boundIniFile->getValueOrDef<bool>(true, "print_scanID"))
{
ss.str("");
ss << scanID;
metadata["Scan_ID"] = ss.str();
}
// Parameters
YAML::Node parametersNode = boundIniFile->getParametersNode();
Options(parametersNode).toMap(metadata, "Parameters");
// Priors
YAML::Node priorsNode = boundIniFile->getPriorsNode();
Options(priorsNode).toMap(metadata, "Priors");
// Printer
YAML::Node printerNode = boundIniFile->getPrinterNode();
Options(printerNode).toMap(metadata, "Printer");
// Scanners
YAML::Node scanNode = boundIniFile->getScannerNode();
str scanner = scanNode["use_scanner"].as<str>();
metadata["Scanner::scanner"] = scanner;
for(auto it = scanNode.begin(); it != scanNode.end(); ++it)
{
if(it->first.as<str>() == "scanners")
Options(scanNode["scanners"][scanner]).toMap(metadata, "Scanner::options");
else if(it->first.as<str>() != "use_scanner")
Options(*it).toMap(metadata, "Scanner::" + it->first.as<str>());
}
// ObsLikes
const IniParser::ObservablesType &obslikes = boundIniFile->getObservables();
for (IniParser::ObservableType obslike : obslikes)
{
str key = "ObsLikes::" + obslike.capability;
metadata[key + "::capability"] = obslike.capability;
if(not obslike.purpose.empty()) metadata[key + "::purpose"] = obslike.purpose;
if(not obslike.function.empty()) metadata[key + "::function"] = obslike.function;
if(not obslike.type.empty()) metadata[key + "::type"] = obslike.type;
if(not obslike.module.empty()) metadata[key + "::module"] = obslike.module;
if(obslike.subcaps.size() and obslike.subcaps.IsSequence())
{
std::stringstream subcaps;
subcaps << "[";
for (auto subcap = obslike.subcaps.begin(); subcap != obslike.subcaps.end(); ++subcap)
subcaps << *subcap << ",";
subcaps << "]";
metadata[key + "::subcaps"] = subcaps.str();
}
}
// Used rules and options
const IniParser::ObservablesType &rules = boundIniFile->getRules();
for (IniParser::ObservableType rule : rules)
{
graph_traits<DRes::MasterGraphType>::vertex_iterator vi, vi_end;
for (boost::tie(vi, vi_end) = vertices(masterGraph); vi != vi_end; ++vi)
{
// Check only for enabled functors
if (masterGraph[*vi]->status() == 2)
{
if (matchesRules(masterGraph[*vi], Rule(rule)))
{
str key = "Rules";
if(not rule.capability.empty())
{
key += "::" + rule.capability;
metadata[key + "::capability"] = rule.capability;
}
if(not rule.function.empty())
{
if (not rule.capability.empty()) key += "::" + rule.function;
metadata[key+"::function"] = rule.function;
}
if(not rule.module.empty()) metadata[key+"::module"] = rule.module;
if(not rule.type.empty()) metadata[key+"::type"] = rule.type;
if(not rule.backend.empty()) metadata[key+"::backend"] = rule.backend;
if(not rule.version.empty()) metadata[key+"::version"] = rule.version;
if(rule.options.getNames().size()) rule.options.toMap(metadata, key+"::options");
}
}
}
}
// Logger
YAML::Node logNode = boundIniFile->getLoggerNode();
Options(logNode).toMap(metadata,"Logger");
// KeyValues
YAML::Node keyvalue = boundIniFile->getKeyValuePairNode();
Options(keyvalue).toMap(metadata,"KeyValue");
// YAML file
ss.str("");
ss << boundIniFile->getYAMLNode();
metadata["YAML"] = ss.str();
return metadata;
}
// Resolve a dependency on backend classes
void DependencyResolver::resolveVertexClassLoading(VertexID vertex)
{
// If there are no backend class loading requirements, and thus nothing to do, return.
if ((*masterGraph[vertex]).backendclassloading().size() == 0) return;
// If the backend is not present, this vertex has already been disabled, so from now just assume it hasn't
// Unless the list of required backends is requested, in which case it is enabled, but it won't run a scan, so no worries
// Add to the logger
logger() << LogTags::dependency_resolver << "Doing backend class loading resolution..." << EOM;
// Add the backends to list of required backends
std::vector<sspair> resolvedBackends;
for(auto backend : (*masterGraph[vertex]).backendclassloading())
resolvedBackends.push_back(backend);
bool found = false;
for(auto br = backendsRequired.begin(); br != backendsRequired.end(); ++br)
{
found = true;
for(auto backend : resolvedBackends)
{
if(std::find(br->begin(), br->end(), backend) == br->end())
found = false;
}
if(found) break;
}
if(not found)
{
backendsRequired.push_back(resolvedBackends);
}
}
// Set the Scan ID
void DependencyResolver::set_scanID()
{
// Get the scanID from the yaml node.
scanID = boundIniFile->getValueOrDef<int>(-1, "scanID");
// If scanID is supplied by user, use that
if (scanID != -1)
{
return;
}
else
{
const std::chrono::time_point<std::chrono::system_clock> now = std::chrono::system_clock::now();
std::time_t in_time_t = std::chrono::system_clock::to_time_t(now);
std::chrono::milliseconds ms = std::chrono::duration_cast<std::chrono::milliseconds> (now.time_since_epoch()) - std::chrono::duration_cast<std::chrono::seconds> (now.time_since_epoch());
std::stringstream ss;
ss << std::put_time(std::localtime(&in_time_t), "%H%M%S");
ss << ms.count();
ss >> scanID;
}
}
// Get BibTeX citation keys for backends, modules, etc
void DependencyResolver::getCitationKeys()
{
// First add the necessary citation keys to use GAMBIT
citationKeys.insert(citationKeys.end(), gambit_citation_keys.begin(), gambit_citation_keys.end());
// Get the keys for the required backends
for(auto backend : backendsRequired)
{
str bibkey = "";
// Run over references of loaded backends
for(auto beref : boundCore->getBackendCitationKeys())
{
str origin = beref.first.first;
str version = beref.first.second;
if (backend[0].first == origin and backend[0].second == version)
{
bibkey = beref.second;
if (bibkey != "" and bibkey != "REFERENCE")
{
logger() << LogTags::dependency_resolver << "Found bibkey for backend " << origin << " version " << version << ": " << bibkey << EOM;
BibTeX::addCitationKey(citationKeys, bibkey);
}
}
}
if (bibkey == "" or bibkey == "REFERENCE")
{
std::ostringstream errmsg;
errmsg << "Missing reference for backend " << backend[0].first << "(" << backend[0].second << ")." << endl;
errmsg << "Please add the bibkey to the frontend header, and full bibtex entry to ";
errmsg << boundIniFile->getValueOrDef<str>("config/bibtex_entries.bib", "dependecy_resolution", "bibtex_file_location") << "." << endl;
dependency_resolver_error().raise(LOCAL_INFO,errmsg.str());
}
}
// Now look over activated vertices in the mastergraph and add any references to module, module functions, etc
std::vector<VertexID> order = getObsLikeOrder();
for (std::vector<VertexID>::const_iterator
vi = order.begin();
vi != order.end(); ++vi)
{
std::set<VertexID> parents;
getParentVertices(*vi, masterGraph, parents);
parents.insert(*vi);
for (std::set<VertexID>::const_iterator
vi2 = parents.begin();
vi2 != parents.end(); ++vi2)
{
// Add citation key for used modules
for(const auto &key : boundCore->getModuleCitationKeys())
{
if(key.first == masterGraph[*vi2]->origin())
{
BibTeX::addCitationKey(citationKeys, key.second);
}
}
// Add citation key for specific module functions
if(masterGraph[*vi2]->citationKey() != "")
{
BibTeX::addCitationKey(citationKeys, masterGraph[*vi2]->citationKey());
}
}
}
}
}
}
Updated on 2023-06-26 at 21:36:55 +0000