NEML2 2.0.0
Loading...
Searching...
No Matches
DependencyResolver.h
1// Copyright 2024, UChicago Argonne, LLC
2// All Rights Reserved
3// Software Name: NEML2 -- the New Engineering material Model Library, version 2
4// By: Argonne National Laboratory
5// OPEN SOURCE LICENSE (MIT)
6//
7// Permission is hereby granted, free of charge, to any person obtaining a copy
8// of this software and associated documentation files (the "Software"), to deal
9// in the Software without restriction, including without limitation the rights
10// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11// copies of the Software, and to permit persons to whom the Software is
12// furnished to do so, subject to the following conditions:
13//
14// The above copyright notice and this permission notice shall be included in
15// all copies or substantial portions of the Software.
16//
17// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
23// THE SOFTWARE.
24
25#pragma once
26
27#include <vector>
28#include <map>
29#include <algorithm>
30
31#include "neml2/base/DependencyDefinition.h"
32
33namespace neml2
34{
43template <typename Node, typename ItemType>
45{
46public:
51 struct Item
52 {
54 : parent(node),
55 value(std::move(item))
56 {
57 }
58
60 Node * const parent;
61
64
66 bool operator==(const Item & other) const
67 {
68 return parent == other.parent && value == other.value;
69 }
70
72 bool operator!=(const Item & other) const
73 {
74 return parent != other.parent || value != other.value;
75 }
76
78 bool operator<(const Item & other) const
79 {
80 return parent != other.parent ? (parent < other.parent) : (value < other.value);
81 }
82 };
83
84 DependencyResolver() = default;
85
88
91
94
96 void resolve();
97
99 const std::vector<Node *> & resolution() const { return _resolution; }
100
105 const std::map<Item, std::set<Item>> & item_providers() const { return _item_provider_graph; }
106
111 const std::map<Item, std::set<Item>> & item_consumers() const { return _item_consumer_graph; }
112
117 const std::map<Node *, std::set<Node *>> & node_providers() const { return _node_provider_graph; }
118
123 const std::map<Node *, std::set<Node *>> & node_consumers() const { return _node_consumer_graph; }
124
126 const std::set<Node *> & end_nodes() const { return _end_nodes; }
127
129 const std::set<Item> & outbound_items() const { return _out_items; }
130
132 const std::set<Node *> & start_nodes() const { return _start_nodes; }
133
135 const std::set<Item> & inbound_items() const { return _in_items; }
136
138 bool & unique_item_provider() { return _unique_item_provider; }
139
141 bool & unique_item_consumer() { return _unique_item_consumer; }
142
143private:
145 void build_graph();
146
148 void resolve(Node *);
149
151 bool _unique_item_provider = true;
152
154 bool _unique_item_consumer = false;
155
157 std::set<Node *> _nodes;
158
160 std::set<Item> _consumed_items;
161
163 std::set<Item> _provided_items;
164
167 std::map<Item, std::set<Item>> _item_provider_graph;
168
171 std::map<Item, std::set<Item>> _item_consumer_graph;
172
175 std::map<Node *, std::set<Node *>> _node_provider_graph;
176
179 std::map<Node *, std::set<Node *>> _node_consumer_graph;
180
182 std::set<Node *> _end_nodes;
183
185 std::set<Node *> _start_nodes;
186
188 std::set<Item> _out_items;
189
191 std::set<Item> _in_items;
192
194 std::vector<Node *> _resolution;
195
197 std::map<Node *, int> _status;
198
200 std::map<Node *, size_t> _priority;
201};
202
203template <typename Node, typename ItemType>
204void
206{
207 auto node = dynamic_cast<Node *>(def);
208 _nodes.emplace(node);
209
210 for (const auto & item : node->consumed_items())
211 _consumed_items.emplace(node, item);
212
213 for (const auto & item : node->provided_items())
214 _provided_items.emplace(node, item);
215}
216
217template <typename Node, typename ItemType>
218void
220{
221 _consumed_items.emplace(nullptr, item);
222}
223
224template <typename Node, typename ItemType>
225void
227 size_t priority)
228{
229 auto node = dynamic_cast<Node *>(def);
230 _priority[node] = priority;
231}
232
233template <typename Node, typename ItemType>
234void
236{
237 // Clear the previous graph
238 _item_provider_graph.clear();
239 _item_consumer_graph.clear();
240 _node_provider_graph.clear();
241 _node_consumer_graph.clear();
242 _start_nodes.clear();
243 _end_nodes.clear();
244 _in_items.clear();
245 _out_items.clear();
246
247 // Build the adjacency matrix for item providers and node providers
248 for (const auto & itemi : _consumed_items)
249 {
250 std::vector<Item> providers;
251
252 for (const auto & itemj : _provided_items)
253 {
254 // Match consumer with provider
255 if (itemi.value != itemj.value)
256 continue;
257
258 // No self dependency
259 if (itemi.parent == itemj.parent)
260 continue;
261
262 // Enforce priority
263 if (_priority[itemi.parent] > _priority[itemj.parent])
264 continue;
265
266 providers.push_back(itemj);
267 }
268
269 // If the user asks for unique providers, we should error if multiple providers have been
270 // found. Otherwise, just put the first provider into the graph.
271 if (!providers.empty())
272 {
273 if (_unique_item_provider)
275 providers.size() == 1, "Multiple providers have been found for item ", itemi.value);
276 _item_provider_graph[itemi].insert(providers[0]);
277 if (itemi.parent)
278 _node_provider_graph[itemi.parent].insert(providers[0].parent);
279 }
280 }
281
282 // Build the adjacency matrix for item consumers
283 for (const auto & itemi : _provided_items)
284 {
285 std::vector<Item> consumers;
286
287 for (const auto & itemj : _consumed_items)
288 {
289 // Skip additional outbound item
290 if (!itemj.parent)
291 continue;
292
293 // Match provider with consumer
294 if (itemi.value != itemj.value)
295 continue;
296
297 // No self dependency
298 if (itemi.parent == itemj.parent)
299 continue;
300
301 // Enforce priority
302 if (_priority[itemi.parent] < _priority[itemj.parent])
303 continue;
304
305 consumers.push_back(itemj);
306 }
307
308 // If the user asks for unique consumers, we should error if multiple consumers have been
309 // found. Otherwise, just put the first consumer into the graph.
310 if (!consumers.empty())
311 {
312 if (_unique_item_consumer)
314 consumers.size() == 1, "Multiple consumers have been found for item ", itemi.value);
315 _item_consumer_graph[itemi].insert(consumers[0]);
316 _node_consumer_graph[itemi.parent].insert(consumers[0].parent);
317 }
318 }
319
320 // Find start nodes
321 for (const auto & node : _nodes)
322 if (_node_provider_graph.count(node) == 0)
323 _start_nodes.insert(node);
324
325 // Find end nodes
326 for (const auto & node : _nodes)
327 if (_node_consumer_graph.count(node) == 0)
328 _end_nodes.insert(node);
329
330 // Find inbound items
331 for (const auto & item : _consumed_items)
332 if (_item_provider_graph.count(item) == 0)
333 _in_items.insert(item);
334
335 // Find outbound items
336 for (const auto & item : _provided_items)
337 if (_item_consumer_graph.count(item) == 0)
338 _out_items.insert(item);
339
340 // Additional outbound items
341 for (const auto & item : _consumed_items)
342 if (!item.parent)
343 {
344 neml_assert(_item_provider_graph.count(item),
345 "Unable to find provider of the additional outbound item ",
346 item.value);
347 for (const auto & provider : _item_provider_graph[item])
348 {
349 _out_items.insert(provider);
350 _end_nodes.insert(provider.parent);
351 }
352 }
353}
354
355template <typename Node, typename ItemType>
356void
358{
359 build_graph();
360
361 _status.clear();
362 _resolution.clear();
363 for (const auto & node : _end_nodes)
364 if (!_status[node])
365 resolve(node);
366
367 // Make sure each node appears in the resolution once and only once
368 for (const auto & node : _nodes)
369 {
370 auto count = std::count(_resolution.begin(), _resolution.end(), node);
371 neml_assert(count > 0,
372 "Each node must appear in the dependency resolution. Node ",
373 node->name(),
374 " is missing. This is an internal error -- consider filing a bug report.");
375 neml_assert(count == 1,
376 "Each node must appear in the dependency resolution once and only once. Node ",
377 node->name(),
378 " appeared ",
379 count,
380 " times. This indicates cyclic dependency.");
381 }
382}
383
384template <typename Node, typename ItemType>
385void
387{
388 // Mark the current node as visiting (so that we know there is circular dependency when a
389 // "visiting" node is visited again).
390 _status[node] += 1;
391
392 // Recurse for all the dependent nodes
393 if (_node_provider_graph.count(node))
394 for (const auto & dep : _node_provider_graph[node])
395 {
396 // The dependent node must either be "not visited" or "visited".
397 // If the dependent node is "being visited", there must be cyclic dependency.
398 neml_assert(_status[dep] != 1,
399 "While resolving dependency, two nodes '",
400 node->name(),
401 "' and '",
402 dep->name(),
403 "' have (possibly indirect) cyclic dependency. The cyclic dependency can be "
404 "resolved by explicitly setting the node priorities.");
405
406 if (!_status[dep])
407 resolve(dep);
408 }
409
410 // At this point, all the dependent nodes must have been pushed into the resolution. It is
411 // therefore safe to push the current node into the resolution.
412 _resolution.push_back(node);
413
414 // Finished visiting this node
415 _status[node] += 1;
416}
417} // namespace neml2
The wrapper (decorator) for cross-referencing unresolved values at parse time.
Definition CrossRef.h:54
CrossRef()=default
The DependencyResolver identifies and resolves the dependencies among a set of objects derived from D...
Definition DependencyResolver.h:45
const std::map< Node *, std::set< Node * > > & node_consumers() const
Definition DependencyResolver.h:123
const std::map< Node *, std::set< Node * > > & node_providers() const
Definition DependencyResolver.h:117
const std::set< Item > & inbound_items() const
The items consumed by the overall dependency graph, i.e., the items that are not provided by any node...
Definition DependencyResolver.h:135
const std::map< Item, std::set< Item > > & item_consumers() const
Definition DependencyResolver.h:111
void add_additional_outbound_item(const ItemType &item)
Add an additional outbound item that the dependency graph provides
Definition DependencyResolver.h:219
void set_priority(DependencyDefinition< ItemType > *, size_t)
Set a node's priority, useful for resolving cyclic dependency.
Definition DependencyResolver.h:226
bool & unique_item_provider()
Definition DependencyResolver.h:138
void resolve()
Resolve nodal dependency and find an evaluation order.
Definition DependencyResolver.h:357
const std::set< Node * > & end_nodes() const
End nodes which are not consumed by anyone else.
Definition DependencyResolver.h:126
const std::vector< Node * > & resolution() const
The resolved (nodal) evaluation order following which all consumed items of the current node.
Definition DependencyResolver.h:99
const std::set< Item > & outbound_items() const
The items provided by the overall dependency graph, i.e., the items that are not consumed by any node...
Definition DependencyResolver.h:129
void add_node(DependencyDefinition< ItemType > *)
Add a node (defining consumed/provided items) in the dependency graph.
Definition DependencyResolver.h:205
const std::set< Node * > & start_nodes() const
Start nodes which do not consume anyone else.
Definition DependencyResolver.h:132
const std::map< Item, std::set< Item > > & item_providers() const
Definition DependencyResolver.h:105
bool & unique_item_consumer()
Definition DependencyResolver.h:141
Definition CrossRef.cxx:31
void neml_assert(bool assertion, Args &&... args)
Definition error.h:64
Definition DependencyResolver.h:52
Node *const parent
Node which defines this item.
Definition DependencyResolver.h:60
bool operator<(const Item &other) const
An arbitrary comparator so that items can be sorted (for consistency)
Definition DependencyResolver.h:78
bool operator==(const Item &other) const
Test for equality between two items.
Definition DependencyResolver.h:66
bool operator!=(const Item &other) const
Test for inequality between two items.
Definition DependencyResolver.h:72
const ItemType value
The consumed/provided item.
Definition DependencyResolver.h:63
Item(Node *const node, ItemType item)
Definition DependencyResolver.h:53