Program Listing for File DependencyGraph.cpp
↰ Return to documentation for file (src/flamegpu/model/DependencyGraph.cpp
)
#include "flamegpu/model/DependencyGraph.h"
namespace flamegpu {
DependencyGraph::DependencyGraph(ModelData* _model) : model(_model) {
}
DependencyGraph::DependencyGraph(const DependencyGraph& other) : model(other.model) {
for (const auto& root : other.roots) {
roots.push_back(root);
}
}
bool DependencyGraph::operator==(const DependencyGraph& rhs) {
std::function<bool(DependencyNode*, DependencyNode*)> checkEqual;
checkEqual = [&checkEqual] (DependencyNode* lhs, DependencyNode* rhs) {
const auto& lhsDeps = lhs->getDependents();
const auto& rhsDeps = rhs->getDependents();
// Different number of dependents -> not same
if (lhsDeps.size() != rhsDeps.size()) {
return false;
} else {
// Check children are equal
for (unsigned int i = 0; i < lhsDeps.size(); i++) {
if (lhsDeps[i] != rhsDeps[i]) {
return false;
}
if (!checkEqual(lhsDeps[i], rhsDeps[i])) {
return false;
}
}
}
return true;
};
// Check equal number of roots
const auto& lhsRoots = roots;
const auto& rhsRoots = rhs.roots;
if (lhsRoots.size() != rhsRoots.size()) {
return false;
} else {
for (unsigned int i = 0; i < lhsRoots.size(); i++) {
if (!checkEqual(lhsRoots[i], rhsRoots[i])) {
return false;
}
}
}
return true;
}
void DependencyGraph::addRoot(DependencyNode& root) {
roots.push_back(&root);
}
bool DependencyGraph::validateDependencyGraph() const {
std::vector<DependencyNode*> functionStack;
if (roots.size() == 0) {
THROW exception::InvalidDependencyGraph("Warning! Agent function dependency graph is empty!");
}
for (const auto& root : roots) {
if (root->getDependencies().size() != 0) {
THROW exception::InvalidDependencyGraph("Warning! Root agent function has dependencies!");
}
if (!validateSubTree(root, functionStack)) {
THROW exception::InvalidDependencyGraph("Warning! Dependency graph validation failed! Does the graph have a cycle?");
}
}
return true;
}
LayerDescription DependencyGraph::newModelLayer() {
std::shared_ptr<ModelData> modelDataAsShared = model->shared_from_this();
auto rtn = std::shared_ptr<LayerData>(new LayerData(modelDataAsShared, "", static_cast<unsigned int>(modelDataAsShared->layers.size())));
model->layers.push_back(rtn);
return LayerDescription(rtn);
}
void DependencyGraph::generateLayers() {
// Check model doesn't already have layers attached
if (model->layers.size() > 0) {
THROW exception::InvalidDependencyGraph("DependencyGraph cannot generate layers on a model which already has layers attached!");
}
// Check dependency graph is valid before we attempt to build layers
validateDependencyGraph();
checkForUnattachedFunctions();
// Lambda to walk the graph and set minimum layer depths of nodes
std::function<void(DependencyNode*, int)> setMinLayerDepths;
setMinLayerDepths = [&setMinLayerDepths] (DependencyNode* node, int depth) {
if (depth >= node->getMinimumLayerDepth()) {
node->setMinimumLayerDepth(depth);
}
for (auto child : node->getDependents()) {
setMinLayerDepths(child, depth + 1);
}
};
// Set minimum layer depths
for (auto root : roots) {
setMinLayerDepths(root, 0);
}
// Build list of functions in their respective ideal layers assuming no conflicts
std::vector<std::vector<DependencyNode*>> idealLayers;
std::function<void(DependencyNode*)> buildIdealLayers;
buildIdealLayers = [&buildIdealLayers, &idealLayers] (DependencyNode* node) {
// New layers required
std::vector<std::vector<DependencyNode*>>::size_type nodeDepth = node->getMinimumLayerDepth();
while (nodeDepth >= idealLayers.size()) {
idealLayers.push_back(std::vector<DependencyNode*>());
}
// Add node to relevant layer if node hasn't already been added
bool alreadyAdded = false;
for (auto& includedNode : idealLayers[nodeDepth]) {
if (node == includedNode) {
alreadyAdded = true;
}
}
if (!alreadyAdded) {
idealLayers[nodeDepth].push_back(node);
}
// Repeat for children
for (auto child : node->getDependents()) {
buildIdealLayers(child);
}
};
for (auto root : roots) {
buildIdealLayers(root);
}
// idealLayers now contains DependencyNode pointers in their ideal layers, i.e. assuming no conflicts.
// Now iterate structure attempting to add functions to layers.
// If we encounter conflicts, introduce additional layers as necessary
constructedLayers.clear();
for (auto idealLayer : idealLayers) {
// Request a new layer from the model
LayerDescription layer = newModelLayer();
constructedLayers.emplace_back();
// Attempt to add each node in the idealLayer to the layer
for (auto node : idealLayer) {
// Add node based on its concrete type
// Agent function
if (AgentFunctionDescription* afd = dynamic_cast<AgentFunctionDescription*>(node)) {
try {
layer.addAgentFunction(*afd);
constructedLayers.back().emplace_back(DependencyGraph::getNodeName(afd));
} catch (const exception::InvalidAgentFunc&) {
// Conflict, create new layer and add to that instead
LayerDescription newLayer = newModelLayer();
newLayer.addAgentFunction(*afd);
constructedLayers.emplace_back();
constructedLayers.back().emplace_back(DependencyGraph::getNodeName(afd));
// printf("New function execution layer created - exception::InvalidAgentFunc exception\n");
} catch (const exception::InvalidLayerMember&) {
LayerDescription newLayer = newModelLayer();
newLayer.addAgentFunction(*afd);
constructedLayers.emplace_back();
constructedLayers.back().emplace_back(DependencyGraph::getNodeName(afd));
// printf("New function execution layer created - exception::InvalidLayerMember exception\n");
}
}
// Submodel
if (SubModelDescription* smd = dynamic_cast<SubModelDescription*>(node)) {
try {
layer.addSubModel(*smd);
constructedLayers.back().emplace_back(DependencyGraph::getNodeName(smd));
} catch (const exception::InvalidLayerMember&) {
LayerDescription newLayer = newModelLayer();
newLayer.addSubModel(*smd);
constructedLayers.emplace_back();
constructedLayers.back().emplace_back(DependencyGraph::getNodeName(smd));
// printf("New submodel layer created - exception::InvalidLayerMember exception\n");
} catch (const exception::InvalidSubModel&) {
LayerDescription newLayer = newModelLayer();
newLayer.addSubModel(*smd);
constructedLayers.emplace_back();
constructedLayers.back().emplace_back(DependencyGraph::getNodeName(smd));
// printf("New submodel layer created - exception::InvalidSubModel exception\n");
}
}
// Host function
if (HostFunctionDescription* hdf = dynamic_cast<HostFunctionDescription*>(node)) {
// function ptr, callback object should be mutually exclusive. Callback only used for SWIG, ptr only for non-SWIG.
// If ptr is available, use that
if (hdf->getFunctionPtr() != nullptr) {
try {
layer.addHostFunction(hdf->getFunctionPtr());
constructedLayers.back().emplace_back(DependencyGraph::getNodeName(hdf));
} catch (const exception::InvalidLayerMember&) {
LayerDescription newLayer = newModelLayer();
newLayer.addHostFunction(hdf->getFunctionPtr());
constructedLayers.emplace_back();
constructedLayers.back().emplace_back(DependencyGraph::getNodeName(hdf));
// printf("New host function layer created - exception::InvalidLayerMember exception\n");
}
} else {
try {
layer._addHostFunction(hdf->getCallbackObject());
constructedLayers.back().emplace_back(DependencyGraph::getNodeName(hdf));
} catch (const exception::InvalidLayerMember&) {
LayerDescription newLayer = newModelLayer();
newLayer._addHostFunction(hdf->getCallbackObject());
constructedLayers.emplace_back();
constructedLayers.back().emplace_back(DependencyGraph::getNodeName(hdf));
// printf("New host function layer created - exception::InvalidLayerMember exception\n");
}
}
}
}
}
}
bool DependencyGraph::validateSubTree(DependencyNode* node, std::vector<DependencyNode*>& functionStack) const {
// Check if the function we are looking at already exists on the stack - if so we have a cycle
if (doesFunctionExistInStack(node, functionStack)) {
return false;
}
// No cycle detected, check if all child nodes form valid subtrees
functionStack.push_back(node);
for (auto child : node->getDependents()) {
if (!validateSubTree(child, functionStack))
return false;
}
// No problems, tree formed by this node and its children is valid
// Pop this function from the stack and return
functionStack.pop_back();
return true;
}
bool DependencyGraph::doesFunctionExistInStack(DependencyNode* function, std::vector<DependencyNode*>& functionStack) const {
// Iterating vector probably faster than using constant lookup structure for likely number of elements
for (const auto fn : functionStack) {
if (fn == function) {
return true;
}
}
return false;
}
void DependencyGraph::checkForUnattachedFunctions() {
// Build set of model's agent functions
std::set<AgentFunctionData*> modelFunctions;
for (const auto& agent : model->agents) {
for (const auto& func : agent.second->functions) {
modelFunctions.insert(func.second.get());
}
}
// Build set of functions present in the dependency graph
std::set<AgentFunctionData*> graphFunctions;
std::function<void(DependencyNode*)> captureFunctions;
captureFunctions = [&captureFunctions, &graphFunctions] (DependencyNode* node) {
if (CAgentFunctionDescription* afd = dynamic_cast<CAgentFunctionDescription*>(node)) {
graphFunctions.insert(afd->function.get());
}
for (const auto& child : node->getDependents()) {
captureFunctions(child);
}
};
for (auto& root : roots) {
captureFunctions(root);
}
// Compare sets
if (modelFunctions != graphFunctions) {
std::cerr << "WARNING: Not all agent functions are used in the dependency graph - have you forgotten to add one?" << std::flush;
}
}
std::string DependencyGraph::getNodeName(DependencyNode* node) {
if (AgentFunctionDescription* afd = dynamic_cast<AgentFunctionDescription*>(node)) {
return afd->getName();
} else if (HostFunctionDescription* hfd = dynamic_cast<HostFunctionDescription*>(node)) {
return hfd->getName();
} else if (SubModelDescription* smd = dynamic_cast<SubModelDescription*>(node)) {
return smd->getName();
} else {
return std::string("DependencyNode without concrete type!");
}
}
void DependencyGraph::generateDOTDiagram(std::string outputFileName) const {
validateDependencyGraph();
std::ofstream DOTFile(outputFileName);
if (DOTFile.is_open()) {
// File preamble
DOTFile << "digraph {" << std::endl;
// Lambda to recursively print nodes
std::function<void(DependencyNode*)> printNodes;
printNodes = [&printNodes, &DOTFile] (DependencyNode* node) {
std::string nodeName = DependencyGraph::getNodeName(node);
if (dynamic_cast<AgentFunctionDescription*>(node)) {
DOTFile << " " << nodeName << "[style = filled, color = red];" << std::endl;
} else if (dynamic_cast<HostFunctionDescription*>(node)) {
DOTFile << " " << nodeName << "[style = filled, color = yellow];" << std::endl;
} else if (dynamic_cast<SubModelDescription*>(node)) {
DOTFile << " " << nodeName << "[style = filled, color = green];" << std::endl;
}
for (auto child : node->getDependents()) {
printNodes(child);
}
};
// Lambda to recursively print relations
std::function<void(DependencyNode*)> printRelations;
printRelations = [&printRelations, &DOTFile] (DependencyNode* node) {
// Get this node's name
std::string parentName = DependencyGraph::getNodeName(node);
// For each child, print DOT relation and recurse
for (auto child : node->getDependents()) {
DOTFile << " " << parentName << " -> " << getNodeName(child) << ";" << std::endl;
printRelations(child);
}
};
// Recursively print nodes
for (auto root : roots) {
printNodes(root);
}
// Recursively print relations
for (auto root : roots) {
printRelations(root);
}
// EOF
DOTFile << "}";
}
}
std::string DependencyGraph::getConstructedLayersString() const {
std::stringstream ss;
unsigned int layerCount = 0;
for (const auto& layer : constructedLayers) {
ss << "--------------------" << std::endl;
ss << "Layer " << layerCount << std::endl;
ss << "--------------------" << std::endl;
for (const auto& item : layer) {
ss << item << std::endl;
}
ss << std::endl;
layerCount++;
}
return ss.str();
}
} // namespace flamegpu