Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions src/nodes/debug.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
// SPDX-License-Identifier: GPL-3.0-or-later
// Copyright (c) 2025 Team Dissolve and contributors

#include "nodes/debug.h"
#include <format>

namespace GraphDebug
{
// Current indent for printing
int indent_{0};
// Return current indent
std::string indent() { return std::format("{:02d}{}", indent_, std::string(std::max(indent_, 0) * 2, ' ')); }
// Increase current indent
void increaseIndent() { ++indent_; }
// Decrease current indent
void decreaseIndent() { --indent_; }
}; // namespace GraphDebug
15 changes: 15 additions & 0 deletions src/nodes/debug.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// SPDX-License-Identifier: GPL-3.0-or-later
// Copyright (c) 2025 Team Dissolve and contributors

#pragma once
#include <string>

namespace GraphDebug
{
// Return current indent
std::string indent();
// Increase current indent
void increaseIndent();
// Decrease current indent
void decreaseIndent();
}; // namespace GraphDebug
17 changes: 16 additions & 1 deletion src/nodes/edge.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,9 @@ Node &Edge::targetNode() const { return targetNode_; }
// Return target input parameter
ParameterBase &Edge::targetInput() const { return targetInput_; }

// Return version of the source node when this edge was last pulled by the target node.
int Edge::sourceNodeVersionIndex() const { return sourceNodeVersionIndex_; }

// Return definition for the edge
EdgeDefinition Edge::definition() const
{
Expand Down Expand Up @@ -193,6 +196,12 @@ void EdgeDefinition::deserialise(const SerialisedValue &node)
targetInput = toml::find<std::string>(node, "targetInput");
}

// Return whether the edge links to updated data and requires a pull
bool Edge::requiresPull() const
{
return (!sourceNode_.isUpToDate()) || (sourceNodeVersionIndex_ != sourceNode_.versionIndex());
}

// Pull the data from the source node to the target, returning a ProcessResult
NodeConstants::ProcessResult Edge::pull()
{
Expand All @@ -201,7 +210,7 @@ NodeConstants::ProcessResult Edge::pull()
* itself we need to pull from or run the source node to get the updated output. We can then store the source node's
* current versionIndex ready for next time.
*/
if (!sourceNode_.isUpToDate() || (sourceNodeVersionIndex_ != sourceNode_.versionIndex()))
if (requiresPull())
{
auto result = sourceNode_.run();
if (result != NodeConstants::ProcessResult::Success && result != NodeConstants::ProcessResult::Unchanged)
Expand All @@ -219,6 +228,12 @@ NodeConstants::ProcessResult Edge::pull()

return NodeConstants::ProcessResult::Success;
}
else
{
// Copy the parameter data over
if (!targetInput_.assign(&sourceOutput_))
return NodeConstants::ProcessResult::Failed;
}

return NodeConstants::ProcessResult::Unchanged;
}
Expand Down
4 changes: 4 additions & 0 deletions src/nodes/edge.h
Original file line number Diff line number Diff line change
Expand Up @@ -67,8 +67,12 @@ class Edge : public Serialisable<>
Node &targetNode() const;
// Return target input parameter
ParameterBase &targetInput() const;
// Return version of the source node when this edge was last pulled by the target node.
int sourceNodeVersionIndex() const;
// Return definition for the edge
EdgeDefinition definition() const;
// Return whether the edge links to updated data and requires a pull
bool requiresPull() const;
// Pull the data from the source node to the target, returning a ProcessResult
NodeConstants::ProcessResult pull();
// Ensure next call to pull() will retrieve the data from the source node
Expand Down
29 changes: 14 additions & 15 deletions src/nodes/graph.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -35,34 +35,33 @@ NodeConstants::ProcessResult Graph::process()
* one of two ways. Either 1) We cycle over Edge connections to inputs on our Outputs node and pull() those in, or 2) we
* look for any nodes that don't have any edge connections to their Outputs and try to run() them one at a time. The
* latter case is important if a Graph has no defined Outputs, and so no external dependence on running the child nodes.
* This logic boils down to the same check - if a given node has no outputs (as is always the case for the OutputsNode)
* then we run() it.
*/

// Pull outputs first
auto outputsResult = proxyOutputs_->run();
if (outputsResult == NodeConstants::ProcessResult::Failed)
return outputsResult;

// Check each node for output edges - any that have zero output edges need to be run()
auto terminalNodeResult = NodeConstants::ProcessResult::Unchanged;
auto result = NodeConstants::ProcessResult::Unchanged;
for (auto &&[nodeName, node] : nodes_)
if (!node->outputEdges().empty())
{
if (node->outputEdges().empty())
{
debug("{}Graph::process() - Node '{}' has no output edges and will be run...", GraphDebug::indent(), node->name());
switch (node->run())
{
case (NodeConstants::ProcessResult::Failed):
return NodeConstants::ProcessResult::Failed;
case (NodeConstants::ProcessResult::InputsNotSatisfied):
/* This should never happen? */
return NodeConstants::ProcessResult::Failed;
break;
case (NodeConstants::ProcessResult::Success):
terminalNodeResult = NodeConstants::ProcessResult::Success;
result = NodeConstants::ProcessResult::Success;
break;
case (NodeConstants::ProcessResult::Unchanged):
break;
case (NodeConstants::ProcessResult::InputsNotSatisfied):
/* This should never happen? */
break;
}
}
}

return outputsResult == terminalNodeResult ? outputsResult : NodeConstants::ProcessResult::Success;
return result;
}

// Flag that the node data needs to be updated
Expand All @@ -87,7 +86,7 @@ void Graph::setUpdateRequired()
bool Graph::addProxyInput(std::shared_ptr<ParameterBase> &input, std::shared_ptr<ParameterBase> &output)
{
// We (the Graph) own the input and the proxyInputs_ owns the output
return ownParameter(input) && proxyInputs_->ownParameter(output, true);
return ownParameter(input) && proxyInputs_->addProxy(output);
}

// Add supplied proxy output, setting ownership of the parameters appropriately
Expand Down
4 changes: 2 additions & 2 deletions src/nodes/graph.h
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ class Graph : public Node
/*
* Inputs, Outputs, and Options
*/
private:
protected:
// Proxy input and output nodes
InputsNode *proxyInputs_{nullptr};
OutputsNode *proxyOutputs_{nullptr};
Expand All @@ -66,7 +66,7 @@ class Graph : public Node
using ReverseNodes = std::map<const Node *, std::string>;
using Edges = std::vector<std::unique_ptr<Edge>>;

private:
protected:
// Map of node names to nodes
Nodes nodes_;
// Map of nodes to node names
Expand Down
30 changes: 29 additions & 1 deletion src/nodes/inputs.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

#include "nodes/inputs.h"

InputsNode::InputsNode(Graph *parentGraph) : Node(parentGraph) {}
InputsNode::InputsNode(Graph *parentGraph) : Node(parentGraph) { propagateUpdateRequests_ = false; }

/*
* Definition (Virtuals)
Expand All @@ -22,6 +22,34 @@ std::string_view InputsNode::summary() const { return "Maps graph inputs to loca
// Perform processing
NodeConstants::ProcessResult InputsNode::process() { return NodeConstants::ProcessResult::Success; }

// Run the node
NodeConstants::ProcessResult InputsNode::run()
{
// We never pull inputs and processing is zero so just update and return Success
upToDate_ = true;
++versionIndex_;

return NodeConstants::ProcessResult::Success;
}

/*
* Inputs, Outputs, and Options
*/

// Add a proxy output, creating a loopback input for it at the same time
bool InputsNode::addProxy(std::shared_ptr<ParameterBase> &output)
{
// Own the output end of the proxy connection
if (!ownParameter(output, true))
return false;

// Create a loopback input of the same name
auto param = inputs_.emplace(std::make_pair(output->name(), output)).first->second;
param->setFlags(ParameterBase::ParameterFlags::Input);

return true;
}

/*
* Serialisation
*/
Expand Down
9 changes: 9 additions & 0 deletions src/nodes/inputs.h
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,15 @@ class InputsNode : public Node
protected:
// Perform processing
NodeConstants::ProcessResult process() override;
// Run the node
NodeConstants::ProcessResult run() override;

/*
* Inputs, Outputs, and Options
*/
public:
// Add a proxy output, creating a loopback input for it at the same time
bool addProxy(std::shared_ptr<ParameterBase> &output);

/*
* Serialisation
Expand Down
67 changes: 67 additions & 0 deletions src/nodes/loop.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
// SPDX-License-Identifier: GPL-3.0-or-later
// Copyright (c) 2025 Team Dissolve and contributors

#include "nodes/loop.h"
#include "nodes/edge.h"
#include "nodes/inputs.h"

LoopGraph::LoopGraph(Graph *parentGraph) : Graph(parentGraph)
{
addOption<Number>("Iterations", "Number of iterations to perform", iterations_);
}

/*
* Definitions (Virtuals)
*/

// Return type of the node
std::string_view LoopGraph::type() const { return "Loop"; }

// Return short summary of the node's purpose
std::string_view LoopGraph::summary() const { return "A graph which iterates"; }

/*
* Processing & Validity
*/

// Perform processing
NodeConstants::ProcessResult LoopGraph::process()
{
// Before arriving here we will have pulled in any external input edges, and this is the data we should use on the
// first iteration of the loop. So, we only pull from inputs to our InputsNode (proxyInputs_) after the first
// iteration. Furthermore, we do this manually here rather than let InputsNode pull its own edges as otherwise we
// get into an infinite loop when the graph tries to run. In this sense the

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems like there was more to come here!

for (auto n = 0; n < iterations_.asInteger(); ++n)
{
debug("{}LoopGraph({})::process() - iteration {}", GraphDebug::indent(), name(), n);

// Pull edges connected to our InputsNode *if*
if (n > 0)
{
for (auto &[inputName, edges] : proxyInputs_->inputEdges())
{
for (const auto edge : edges)
{
debug("{}LoopGraph::process() - Pulling edge {}...\n", GraphDebug::indent(), edge->definition().asString());
switch (edge->pull())
{
case (NodeConstants::ProcessResult::Failed):
case (NodeConstants::ProcessResult::InputsNotSatisfied):
return NodeConstants::ProcessResult::Failed;
case (NodeConstants::ProcessResult::Success):
case (NodeConstants::ProcessResult::Unchanged):
break;
}
}
}
}

// Process child nodes, returning early if we encounter an error
auto result = Graph::process();
if (result == NodeConstants::ProcessResult::Failed || result == NodeConstants::ProcessResult::InputsNotSatisfied)
return NodeConstants::ProcessResult::Failed;
}
Comment on lines +35 to +64
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When we had discussed your proposal during the stand-up, I had misunderstood and thought that you were putting this logic into the input nodes. Having this in the node answers 95% of my concerns.


return NodeConstants::ProcessResult::Success;
}
37 changes: 37 additions & 0 deletions src/nodes/loop.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
// SPDX-License-Identifier: GPL-3.0-or-later
// Copyright (c) 2025 Team Dissolve and contributors

#pragma once

#include "nodes/graph.h"

// Loop Graph
class LoopGraph : public Graph
{
public:
LoopGraph(Graph *parentGraph);
~LoopGraph() = default;

/*
* Definition (Virtuals)
*/
public:
// Return type of the node
std::string_view type() const override;
// Return short summary of the node's purpose
std::string_view summary() const override;

/*
* Data
*/
public:
// Number of iterations to loop for
Number iterations_{1};

/*
* Processing & Validity
*/
public:
// Perform processing
NodeConstants::ProcessResult process();
};
Loading
Loading