Skip to content
78 changes: 56 additions & 22 deletions src/nodes/edge.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

#include "nodes/edge.h"
#include "nodes/graph.h"
#include "nodes/inputs.h"
#include "nodes/loopBack.h"
#include "nodes/outputs.h"

Expand Down Expand Up @@ -31,18 +32,49 @@ class EdgeConstructor : public Edge
// Create an edge from the supplied definition
std::unique_ptr<Edge> Edge::create(Graph *parent, const EdgeDefinition &definition)
{
// Get target node
auto targetNode = parent->findNode(definition.targetNode);
Comment thread
RobBuchananCompPhys marked this conversation as resolved.
if (!targetNode)
{
Messenger::error("Target node '{}' does not exist in the graph.\n", definition.targetNode);
return {};
}

// Disallow circular edges (mostly a check for Graph -> Graph connections)
if (targetNode == parent)
{
Messenger::error("Target node is graph '{}' and cannot be the owner of the edge.", definition.targetNode);
return {};
}

// Get source node and output
auto sourceNode = parent->findNode(definition.sourceNode);
if (!sourceNode)
{
Messenger::error("Source node '{}' does not exist in the graph.\n", definition.sourceNode);
return {};
}

auto sourceOutput = sourceNode->findOutput(definition.sourceOutput);
if (!sourceOutput)
{
Messenger::error("Source node '{}' has no output parameter '{}'.\n", definition.sourceNode, definition.sourceOutput);
return {};
// If the source node is a Graph's own Inputs node, we will create an edge on the fly - else, throw an error
if (!dynamic_cast<InputsNode *>(sourceNode))
{
Messenger::error("Source node '{}' has no output parameter '{}'.\n", definition.sourceNode,
definition.sourceOutput);
return {};
}

// The target node is the parent Graph's own Inputs node, so create a parameter link from the mapped input to the
// targetInput
auto link = targetNode->findInput(definition.targetInput)->createParameterLink(definition.sourceOutput);
if (!parent->addProxyInput(link.inputParameter, link.outputParameter))
{
Messenger::error("Failed to add mapped input '{}'.\n", definition.targetInput);
return {};
}
sourceOutput = parent->proxyInputs().findOutput(definition.sourceOutput);
}

// Confirm that the source is actually an output
Expand All @@ -53,35 +85,26 @@ std::unique_ptr<Edge> Edge::create(Graph *parent, const EdgeDefinition &definiti
return {};
}

// Get target node and input
auto targetNode = parent->findNode(definition.targetNode);
if (!targetNode)
{
Messenger::error("Target node '{}' does not exist in the graph.\n", definition.targetNode);
return {};
}

// Disallow circular edges (mostly a check for Graph -> Graph connections)
if (targetNode == parent)
{
Messenger::error("Target node is graph '{}' and cannot be the owner of the edge.", definition.targetNode);
return {};
}

// We need to check carefully the target node, since we need to permit outside connections to the Graph object itself as
// well as its Outputs node explicitly.
std::shared_ptr<ParameterBase> targetInput{nullptr};
if (dynamic_cast<Graph *>(targetNode))
{
// The target node is a Graph: create a parameter link from the sourceOutput and from it a mapped input
auto graphNode = dynamic_cast<Graph *>(targetNode);
auto link = sourceOutput->createParameterLink(definition.targetInput);
if (!graphNode->addProxyInput(link.inputParameter, link.outputParameter))
auto existingTargetInput = graphNode->findInput(definition.targetInput);
if (!existingTargetInput.get())
{
Messenger::error("Failed to add mapped input '{}'.\n", definition.targetInput);
return {};
auto link = sourceOutput->createParameterLink(definition.targetInput);
if (!graphNode->addProxyInput(link.inputParameter, link.outputParameter))
{
Messenger::error("Failed to add mapped input '{}'.\n", definition.targetInput);
return {};
}
targetInput = link.inputParameter;
}
targetInput = link.inputParameter;
else
targetInput = existingTargetInput;
}
else if (dynamic_cast<OutputsNode *>(targetNode))
{
Expand Down Expand Up @@ -288,3 +311,14 @@ void Edge::deserialise(const SerialisedValue &node)
throw std::runtime_error("Cannot directly deserialise edges. Please contact the Dissolve development team if you are "
"seeing this error - this is a bug and NOT your fault.\n");
}

// Express as a serialisable value
void LoopEdge::serialise(std::string tag, SerialisedValue &target) const
{
definition().serialise(tag, target);
target[tag]["targetNode"] = "LoopBacks";
target[tag]["analogue"] = analogue_;
}

// Read values from a serialisable value
void LoopEdge::deserialise(const SerialisedValue &node) { Edge::deserialise(node); }
9 changes: 9 additions & 0 deletions src/nodes/edge.h
Original file line number Diff line number Diff line change
Expand Up @@ -115,4 +115,13 @@ class LoopEdge : public Edge
*
*/
ParameterBase *analogue_;

/*
* Serialisation
*/
public:
// Express as a serialisable value
void serialise(std::string tag, SerialisedValue &target) const override;
// Read values from a serialisable value
void deserialise(const SerialisedValue &node) override;
};
72 changes: 55 additions & 17 deletions src/nodes/iterableGraph.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,12 @@ LoopEdge *IterableGraph::findLoopEdge(const EdgeDefinition &definition) const
return {};
}

// Add edge between nodes
bool IterableGraph::addLoopEdge(std::unique_ptr<Edge> edge, std::string_view source)
{
return addOutputLoopEdge(source, loopEdges_.emplace_back(LoopEdge::makeLoopEdge(edge.release(), proxyInputs())).get());
}

// Add edge to node map
Edge *IterableGraph::addOutputLoopEdge(std::string_view sourceOutput, Edge *edge)
{
Expand Down Expand Up @@ -128,23 +134,26 @@ Edge *IterableGraph::removeOutputLoopEdge(std::string_view sourceOutput, Edge *e
// Add edge between nodes
bool IterableGraph::addEdge(const EdgeDefinition &definition)
{
if (dynamic_cast<InputsNode *>(parentGraph()->findNode(definition.sourceNode)))
setLoopBacks();
else if (loopBacks_->findInput(definition.targetInput))
{
auto edge =
Edge::create(this, {definition.sourceNode, definition.sourceOutput, definition.targetNode, definition.targetInput});
if (!edge)
return false;

loopEdges_.emplace_back(LoopEdge::makeLoopEdge(edge.release(), proxyInputs()));

addOutputLoopEdge(definition.sourceOutput, loopEdges_.back().get());

return true;
}

return Graph::addEdge(definition);
// Refresh the graph loopbacks
setLoopBacks();

// Check if the connection is invertible.
// Invertibility is satisfied when the source node (internal to the graph) can output to an existing loopback,
// which discounts any edge for which no loopbacks correspond to the target input, as well as the graphs own InputsNode.
auto nonInvertible = dynamic_cast<InputsNode *>(parentGraph()->findNode(definition.sourceNode)) ||
!loopBacks_->findInput(definition.targetInput);

// If not invertible, create and return a standard edge
if (nonInvertible)
return Graph::addEdge(definition);

// Create loop edge
auto edge =
Edge::create(this, {definition.sourceNode, definition.sourceOutput, definition.targetNode, definition.targetInput});
if (!edge)
return false;

return addLoopEdge(std::move(edge), definition.sourceOutput);
}

// Remove edge between nodes
Expand Down Expand Up @@ -183,3 +192,32 @@ NodeConstants::ProcessResult IterableGraph::process()

return NodeConstants::ProcessResult::Success;
}

/*
* Serialisation
*/

// Express as a serialisable value
void IterableGraph::serialise(std::string tag, SerialisedValue &target) const
{
Graph::serialise(tag, target);
auto &result = target[tag];
fromVector(loopEdges_, "loopEdges", result);
}

// Read values from a serialisable value
void IterableGraph::deserialise(const SerialisedValue &node)
{
Graph::deserialise(node);
toVector(node, "loopEdges",
[this](const auto &value)
{
auto definition = toml::get<EdgeDefinition>(value);
auto edge = Edge::create(
this, {definition.sourceNode, definition.sourceOutput, definition.targetNode, definition.targetInput});
if (!edge)
return false;

return addLoopEdge(std::move(edge), definition.sourceOutput);
});
}
11 changes: 11 additions & 0 deletions src/nodes/iterableGraph.h
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@ class IterableGraph : public Graph
void releaseLoopBack(const std::string &name);

private:
// Add edge between nodes
bool addLoopEdge(std::unique_ptr<Edge> edge, std::string_view source);
// Add edge to node map
Edge *addOutputLoopEdge(std::string_view sourceOutput, Edge *edge);
// Remove edge from node map
Expand Down Expand Up @@ -80,4 +82,13 @@ class IterableGraph : public Graph
protected:
// Perform processing
NodeConstants::ProcessResult process() override;

/*
* Serialisation
*/
public:
// Express as a serialisable value
void serialise(std::string tag, SerialisedValue &target) const override;
// Read values from a serialisable value
void deserialise(const SerialisedValue &node) override;
};
Loading