ArmNN
 25.11
Loading...
Searching...
No Matches
Graph.hpp
Go to the documentation of this file.
1//
2// Copyright © 2017-2024 Arm Ltd and Contributors. All rights reserved.
3// SPDX-License-Identifier: MIT
4//
5#pragma once
6
7#include "LayersFwd.hpp"
9#include "Profiling.hpp"
10
11#include <armnn/Types.hpp>
12#include <armnn/TensorFwd.hpp>
13#include <armnn/NetworkFwd.hpp>
14#include <armnn/Exceptions.hpp>
18
19#include <list>
20#include <map>
21#include <unordered_map>
22#include <unordered_set>
23#include <vector>
24
25namespace armnn
26{
27
28class SubgraphView;
29
30class Graph
31{
32public:
33 template <typename LayerType>
34 static LayerType* PtrCast(Layer* const layer)
35 {
37 }
38
39 template <typename Func>
40 void ForEachLayer(Func func) const
41 {
42 for (auto it = m_Layers.begin(); it != m_Layers.end(); )
43 {
44 auto next = std::next(it);
45 func(*it);
46 it = next;
47 }
48 }
49
50 using LayerList = std::list<Layer*>;
51
52 // Const so pointers in the list can't be modified externally.
53 using Iterator = LayerList::const_iterator;
54 using IteratorDifference = Iterator::difference_type;
55
59
60 /// Wrapper class returned by Graph::GetInputLayers()
62 {
63 explicit InputLayersAccessor(const Graph& graph) : m_Graph(graph) {}
64
66 {
67 return { m_Graph.m_Layers.begin(), &(PtrCast<const InputLayer>) };
68 }
69
71 {
72 return { std::next(m_Graph.m_Layers.begin(), static_cast<IteratorDifference>(m_Graph.GetNumInputs())),
74 }
75
76 const Graph& m_Graph;
77 };
78
79 /// Wrapper class returned by Graph::GetOutputLayers()
81 {
82 explicit OutputLayersAccessor(const Graph& graph) : m_Graph(graph) {}
83
85 {
86 return { std::prev(m_Graph.m_Layers.end(), static_cast<IteratorDifference>(m_Graph.GetNumOutputs())),
88 }
89
91 {
92 return { m_Graph.m_Layers.end(), &(PtrCast<const OutputLayer>) };
93 }
94
95 const Graph& m_Graph;
96 };
97
98 Graph(bool shapeInferenceMethod = false, bool allowExpandedDims = false)
99 : m_LayersInOrder(true)
100 , m_AllowExpandedDims(allowExpandedDims)
101 , m_ShapeInferenceMethod(shapeInferenceMethod ? ShapeInferenceMethod::InferAndValidate :
103 , m_Profiler(std::make_shared<IProfiler>())
104 {}
105
106 Graph(const Graph& other);
107
108 Graph& operator=(const Graph& other) = delete;
109
110 Graph(Graph&& other)
111 {
112 *this = std::move(other);
113 }
114
116 {
117 m_InputIds = std::move(other.m_InputIds);
118 m_OutputIds = std::move(other.m_OutputIds);
119 m_LayersInOrder = std::move(other.m_LayersInOrder);
120 m_Views = std::move(other.m_Views);
121 m_Profiler = std::move(other.m_Profiler);
122 m_AllowExpandedDims = other.m_AllowExpandedDims;
123 m_ShapeInferenceMethod = other.m_ShapeInferenceMethod;
124 other.ForEachLayer([this](Layer* otherLayer)
125 {
126 otherLayer->Reparent(*this, m_Layers.end());
127 });
128
129 if (!other.m_PosInGraphMap.empty())
130 {
131 throw armnn::Exception("assignment positions in graph map must be empty.");
132 }
133
134 if (!other.m_Layers.empty())
135 {
136 throw armnn::Exception("assignment layers must be empty.");
137 }
138
139 return *this;
140 }
141
143 {
144 ForEachLayer([](Layer* layer)
145 {
146 delete layer;
147 });
148 }
149
150 Status Print(bool extended = false) const;
151
152 Status SerializeToDot(std::ostream& stream);
153
154 /// Adds a new layer, of type LayerType, to the graph constructed with the arguments passed.
155 template <typename LayerT, typename... Args>
156 LayerT* AddLayer(Args&&... args);
157
158 /// Inserts a new layer between the output slot currently connected to insertBefore
159 /// and insertBefore itself.
160 template <typename LayerT, typename... Args>
161 LayerT* InsertNewLayer(InputSlot& insertBefore, Args&&... args);
162
163 /// Inserts a new layer between insertAfter and the input slot(s) currently connected to it
164 template <typename LayerT, typename... Args>
165 LayerT* InsertNewLayer(OutputSlot& insertAfter, Args&&... args);
166
167 /// Deletes the layer at the specified position.
168 void EraseLayer(Iterator pos);
169
170 /// Deletes the layer. Sets @a layer to nullptr on return.
171 /// Templated to support pointers to any layer type.
172 template <typename LayerT>
173 void EraseLayer(LayerT*& layer);
174
175 /// Returns iterator pointing to the beginning of the list. Lowercase for range-based for loops.
176 Iterator begin() { return m_Layers.begin(); }
177 /// Returns iterator pointing to the end of the list. Lowercase for range-based for loops.
178 Iterator end() { return m_Layers.end(); }
179
180 /// Returns const iterator pointing to the beginning of the list. Lowercase for range-based for loops.
181 ConstIterator begin() const { return {m_Layers.begin(), &(PtrCast<const Layer>)}; }
182 /// Returns const iterator pointing to the end of the list. Lowercase for range-based for loops.
183 ConstIterator end() const { return {m_Layers.end(), &(PtrCast<const Layer>)}; }
184
185 /// Returns const iterator pointing to the beginning of the list. Lowercase for range-based for loops.
186 ConstIterator cbegin() const { return begin(); }
187 /// Returns const iterator pointing to the end of the list. Lowercase for range-based for loops.
188 ConstIterator cend() const { return end(); }
189
190 /// Sorts layers in topological order and return this.
191 Graph& TopologicalSort() { const_cast<const Graph*>(this)->TopologicalSort(); return *this; }
192 const Graph& TopologicalSort() const;
193
194 size_t GetNumInputs() const { return m_InputIds.size(); }
195 size_t GetNumOutputs() const { return m_OutputIds.size(); }
196
197 /// Returns a wrapper object with begin(), end() methods to iterate over the input layers
198 /// in a range-based for loop.
200
201 /// Returns a wrapper object with begin(), end() methods to iterate over the output layers
202 /// in a range-based for loop.
204
205 size_t GetNumLayers() const { return m_Layers.size(); }
206
207 /// Allocates memory for all tensors under output tensor handers of each layer.
209
210 /// Modifies the graph in-place, removing edges connecting layers using different compute devices,
211 /// and relinking them via an intermediary copy layers.
212 void AddCompatibilityLayers(std::map<BackendId, std::unique_ptr<class IBackendInternal>>& backends,
214
215 /// Substitutes the given sub-graph with either a new layer or a new sub-graph.
216 /// In either case, the given layer or all the layers in the given sub-graph must belong to this graph.
217 void SubstituteSubgraph(SubgraphView& subgraph, IConnectableLayer* substituteLayer);
218 void SubstituteSubgraph(SubgraphView& subgraph, const SubgraphView& substituteSubgraph);
219
220 /// For each ConstantLayer in Graph, ensures TensorInfo is set on all output slots.
221 /// LayerValidationException thrown if no TensorInfo is set.
223
224 void InferTensorInfos();
225
226 void AttachObservable(IGraphObservable* const observable, GraphEvent notifyOnEvent) {
227 m_Views[notifyOnEvent].emplace_back(observable);
228 }
229
230 void DetachObservable(IGraphObservable* const observable, GraphEvent notifyOnEvent) {
231 m_Views[notifyOnEvent].remove(observable);
232 }
233
234 /// Gets the position of a layer in the graph.
236
237 const std::shared_ptr<IProfiler>& GetProfiler() const;
238
239 void SetLayersOutOfOrder();
240
241private:
242 template <typename LayerT>
243 class LayerInGraphBase;
244
245 template <typename LayerT>
246 class LayerInGraph;
247
248 Iterator ForwardToEndOfInputs(Iterator it) const
249 {
250 while ((it != m_Layers.end()) && ((*it)->GetType() == LayerType::Input))
251 {
252 ++it;
253 }
254 return it;
255 }
256 Iterator ForwardToEndOfInputsAndConstants(Iterator it) const
257 {
258 while ((it != m_Layers.end()) &&
259 ((*it)->GetType() == LayerType::Input || (*it)->GetType() == LayerType::Constant))
260 {
261 ++it;
262 }
263 return it;
264 }
265
266 Iterator RewindToBeginOfOutputs(Iterator it) const
267 {
268 while ((it != m_Layers.begin()) && ((*std::prev(it))->GetType() == LayerType::Output))
269 {
270 --it;
271 }
272 return it;
273 }
274
275 void NotifyObservables(GraphEvent event, Layer* graphState)
276 {
277 // Iterate over all observables observing this event
278 for (auto& observable : m_Views[event])
279 {
280 observable->Update(graphState);
281 }
282 }
283
284 std::unordered_set<LayerBindingId> m_InputIds;
285 std::unordered_set<LayerBindingId> m_OutputIds;
286 std::unordered_map<const Layer*, Iterator> m_PosInGraphMap;
287
288 void ReplaceSubgraphConnections(const SubgraphView& subgraph, const SubgraphView& substituteSubgraph);
289 void EraseSubgraphLayers(SubgraphView &subgraph);
290
291 /// Mutable to allow sorting on const object.
292 mutable LayerList m_Layers;
293 mutable bool m_LayersInOrder;
294
295 bool m_AllowExpandedDims;
296
297 std::map<const GraphEvent, std::list<IGraphObservable*>> m_Views;
298 ShapeInferenceMethod m_ShapeInferenceMethod;
299
300 std::shared_ptr<IProfiler> m_Profiler;
301
302 // Throws exception due to a layer input not being connected to an output slot.
303 /// Also verifies weights and bias are set for FullyConnected layers.
304 void ConstructErrorMessageForUnconnectedInputs(Layer* const layer,
305 unsigned int slotIndex);
306
307 friend class SubgraphView;
308};
309
310/// Common base class for layers in the graph.
311template <typename LayerT>
312class Graph::LayerInGraphBase : public LayerT
313{
314protected:
315 template <typename... Args>
316 LayerInGraphBase(Graph& graph, Iterator insertBefore, Args&&... args)
317 : LayerT(std::forward<Args>(args)...), m_Graph(&graph)
318 {
319 Insert(*m_Graph, insertBefore);
320 }
321 ~LayerInGraphBase()
322 {
323 Remove(*m_Graph);
324 }
325
326 void Reparent(Graph& destGraph, Iterator insertBefore) override
327 {
328 Insert(destGraph, insertBefore);
329 Remove(*m_Graph);
330
331 m_Graph = &destGraph;
332 }
333
334private:
335 void Insert(Graph& graph, Iterator insertBefore)
336 {
337 graph.m_PosInGraphMap.emplace(this, graph.m_Layers.emplace(insertBefore, this));
338 }
339
340 void Remove(Graph& graph)
341 {
342 auto layerIt = graph.GetPosInGraph(*this);
343 graph.m_Layers.erase(layerIt);
344
345 const size_t numErased = graph.m_PosInGraphMap.erase(this);
346 if (numErased != 1)
347 {
348 throw armnn::Exception("numErased must be \"1\".");
349 }
350 }
351
352protected:
353 Graph* m_Graph;
354};
355
356/// Input/Output/Constant layers specialize this template.
357template <typename LayerT>
358class Graph::LayerInGraph final : public LayerInGraphBase<LayerT>
359{
360public:
361 template <typename... Args>
362 LayerInGraph(Graph& graph, Args&&... args)
363 : LayerInGraphBase<LayerT>(graph,
364 // Insert at the back of the intermediate layers (before outputs).
365 std::prev(graph.end(), IteratorDifference(graph.GetNumOutputs())),
366 std::forward<Args>(args)...)
367 {
368 }
369 template <typename... Args>
370 LayerInGraph(Graph& graph, Iterator insertBefore, Args&&... args)
371 : LayerInGraphBase<LayerT>(graph,
372 // Make sure it's inserted after all inputs and before all outputs.
373 graph.ForwardToEndOfInputsAndConstants(graph.RewindToBeginOfOutputs(insertBefore)),
374 std::forward<Args>(args)...)
375 {
376 }
377};
378
379template <>
380class Graph::LayerInGraph<ConstantLayer> final : public LayerInGraphBase<ConstantLayer>
381{
382public:
383 template <typename... Args>
384 LayerInGraph(Graph& graph, Args&&... args)
385 : LayerInGraphBase<ConstantLayer>(graph,
386 // Always add to the back of the inputs.
387 std::next(graph.begin(), IteratorDifference(graph.GetNumInputs())),
388 std::forward<Args>(args)...)
389 {}
390 template <typename... Args>
391 LayerInGraph(Graph& graph, Iterator, Args&&... args)
392 // Ignore Iterator argument. Always add to the back of the inputs.
393 : LayerInGraph(graph, std::forward<Args>(args)...)
394 {}
395 ~LayerInGraph() override
396 {}
397};
398
399/// Inputs add/remove their binding id to m_InputIds in the graph.
400template <>
401class Graph::LayerInGraph<InputLayer> final : public LayerInGraphBase<InputLayer>
402{
403public:
404 template <typename... Args>
405 LayerInGraph(Graph& graph, Args&&... args)
406 : LayerInGraphBase<InputLayer>(graph,
407 // Always add to the back of the inputs.
408 std::next(graph.begin(), IteratorDifference(graph.GetNumInputs())),
409 std::forward<Args>(args)...)
410 {
411 const bool isNewId = m_Graph->m_InputIds.emplace(GetBindingId()).second;
412 if (!isNewId)
413 {
414 throw InvalidArgumentException("A layer already exists with the specified id");
415 }
416 }
417 template <typename... Args>
418 LayerInGraph(Graph& graph, Iterator, Args&&... args)
419 // Ignore Iterator argument. Always add to the back of the inputs.
420 : LayerInGraph(graph, std::forward<Args>(args)...)
421 {
422 }
423 ~LayerInGraph() override
424 {
425 const size_t numErased = m_Graph->m_InputIds.erase(GetBindingId());
426 IgnoreUnused(numErased);
427 }
428};
429
430/// Outputs add/remove their binding id to m_OutputIds in the graph.
431template <>
432class Graph::LayerInGraph<OutputLayer> final : public LayerInGraphBase<OutputLayer>
433{
434public:
435 template <typename... Args>
436 LayerInGraph(Graph& graph, Args&&... args)
437 : LayerInGraphBase<OutputLayer>(graph,
438 // Always add to the back of the outputs.
439 graph.end(),
440 std::forward<Args>(args)...)
441 {
442 const bool isNewId = m_Graph->m_OutputIds.emplace(GetBindingId()).second;
443 if (!isNewId)
444 {
445 throw InvalidArgumentException("A layer already exists with the specified id");
446 }
447 }
448 ~LayerInGraph() override
449 {
450 const size_t numErased = m_Graph->m_OutputIds.erase(GetBindingId());
451 IgnoreUnused(numErased);
452 }
453};
454
456{
457 auto it = m_PosInGraphMap.find(&layer);
458 if (it == m_PosInGraphMap.end())
459 {
460 throw armnn::Exception("unable to find layer in graph map.");
461 }
462 return it->second;
463}
464
465template <typename LayerT, typename... Args>
466inline LayerT* Graph::AddLayer(Args&&... args)
467{
468 m_LayersInOrder = m_LayersInOrder &&
470 LayerT* const layer = new LayerInGraph<LayerT>(*this, std::forward<Args>(args)...);
471
472 layer->SetShapeInferenceMethod(m_ShapeInferenceMethod);
473 layer->SetAllowExpandedDims(m_AllowExpandedDims);
474
475 NotifyObservables(GraphEvent::LayerAdded, layer);
476
477 return layer;
478}
479
480template <typename LayerT, typename... Args>
481inline LayerT* Graph::InsertNewLayer(InputSlot& insertBefore, Args&&... args)
482{
483 // Insert after the parent if any, or before the child otherwise, so the topological order is kept.
484 OutputSlot* parentOut = insertBefore.GetConnectedOutputSlot();
485 const Iterator pos = (parentOut != nullptr)
486 ? std::next(GetPosInGraph(parentOut->GetOwningLayer()))
487 : GetPosInGraph(insertBefore.GetOwningLayer());
488 LayerT* const layer = new LayerInGraph<LayerT>(*this, pos, std::forward<Args>(args)...);
489 insertBefore.Insert(*layer);
490
491 NotifyObservables(GraphEvent::LayerAdded, layer);
492
493 return layer;
494}
495
496template <typename LayerT, typename... Args>
497inline LayerT* Graph::InsertNewLayer(OutputSlot& insertAfter, Args&&... args)
498{
499 Layer& owningLayer = insertAfter.GetOwningLayer();
500
501 const Iterator pos = std::next(GetPosInGraph(owningLayer));
502 LayerT* const layer = new LayerInGraph<LayerT>(*this, pos, std::forward<Args>(args)...);
503
504 if (layer->GetNumInputSlots() != 1)
505 {
506 throw armnn::Exception("layer must only one input slot.");
507 }
508
509 insertAfter.MoveAllConnections(layer->GetOutputSlot());
510 insertAfter.Connect(layer->GetInputSlot(0));
511
512 NotifyObservables(GraphEvent::LayerAdded, layer);
513
514 return layer;
515}
516
518{
519 NotifyObservables(GraphEvent::LayerErased, *pos);
520
521 delete *pos;
522}
523
524template <typename LayerT>
525inline void Graph::EraseLayer(LayerT*& layer)
526{
527 if (!layer)
528 {
529 throw armnn::NullPointerException("layer must not be null.");
530 }
531
532 EraseLayer(GetPosInGraph(*layer));
533 layer = nullptr;
534}
535
536} // namespace armnn
A layer that the constant data can be bound to.
Base class for all ArmNN exceptions so that users can filter to just those.
LayerInGraph(Graph &graph, Iterator, Args &&... args)
Definition Graph.hpp:391
LayerInGraph(Graph &graph, Args &&... args)
Definition Graph.hpp:384
LayerInGraph(Graph &graph, Iterator, Args &&... args)
Definition Graph.hpp:418
LayerInGraph(Graph &graph, Args &&... args)
Definition Graph.hpp:405
LayerInGraph(Graph &graph, Args &&... args)
Definition Graph.hpp:436
ConstIterator cend() const
Returns const iterator pointing to the end of the list. Lowercase for range-based for loops.
Definition Graph.hpp:188
friend class SubgraphView
Definition Graph.hpp:307
Status SerializeToDot(std::ostream &stream)
Definition Graph.cpp:146
TransformIterator< decltype(&PtrCast< const InputLayer >), Iterator > ConstIteratorInputs
Definition Graph.hpp:57
Iterator begin()
Returns iterator pointing to the beginning of the list. Lowercase for range-based for loops.
Definition Graph.hpp:176
void InferTensorInfos()
Definition Graph.cpp:645
void DetachObservable(IGraphObservable *const observable, GraphEvent notifyOnEvent)
Definition Graph.hpp:230
LayerT * InsertNewLayer(InputSlot &insertBefore, Args &&... args)
Inserts a new layer between the output slot currently connected to insertBefore and insertBefore itse...
Definition Graph.hpp:481
void AttachObservable(IGraphObservable *const observable, GraphEvent notifyOnEvent)
Definition Graph.hpp:226
Iterator::difference_type IteratorDifference
Definition Graph.hpp:54
Status AllocateDynamicBuffers()
Allocates memory for all tensors under output tensor handers of each layer.
Definition Graph.cpp:207
Status Print(bool extended=false) const
Definition Graph.cpp:68
TransformIterator< decltype(&PtrCast< const OutputLayer >), Iterator > ConstIteratorOutputs
Definition Graph.hpp:58
Graph & operator=(const Graph &other)=delete
size_t GetNumOutputs() const
Definition Graph.hpp:195
ConstIterator end() const
Returns const iterator pointing to the end of the list. Lowercase for range-based for loops.
Definition Graph.hpp:183
void VerifyConstantLayerSetTensorInfo() const
For each ConstantLayer in Graph, ensures TensorInfo is set on all output slots.
Definition Graph.cpp:622
LayerT * AddLayer(Args &&... args)
Adds a new layer, of type LayerType, to the graph constructed with the arguments passed.
Definition Graph.hpp:466
Graph & operator=(Graph &&other)
Definition Graph.hpp:115
const std::shared_ptr< IProfiler > & GetProfiler() const
Definition Graph.cpp:733
size_t GetNumInputs() const
Definition Graph.hpp:194
InputLayersAccessor GetInputLayers() const
Returns a wrapper object with begin(), end() methods to iterate over the input layers in a range-base...
Definition Graph.hpp:199
void EraseLayer(Iterator pos)
Deletes the layer at the specified position.
Definition Graph.hpp:517
ConstIterator cbegin() const
Returns const iterator pointing to the beginning of the list. Lowercase for range-based for loops.
Definition Graph.hpp:186
OutputLayersAccessor GetOutputLayers() const
Returns a wrapper object with begin(), end() methods to iterate over the output layers in a range-bas...
Definition Graph.hpp:203
Graph(bool shapeInferenceMethod=false, bool allowExpandedDims=false)
Definition Graph.hpp:98
std::list< Layer * > LayerList
Definition Graph.hpp:50
void SubstituteSubgraph(SubgraphView &subgraph, IConnectableLayer *substituteLayer)
Substitutes the given sub-graph with either a new layer or a new sub-graph.
Definition Graph.cpp:475
Graph(Graph &&other)
Definition Graph.hpp:110
Iterator end()
Returns iterator pointing to the end of the list. Lowercase for range-based for loops.
Definition Graph.hpp:178
static LayerType * PtrCast(Layer *const layer)
Definition Graph.hpp:34
TransformIterator< decltype(&PtrCast< const Layer >), Iterator > ConstIterator
Definition Graph.hpp:56
void SetLayersOutOfOrder()
Definition Graph.cpp:738
Iterator GetPosInGraph(Layer &layer)
Gets the position of a layer in the graph.
Definition Graph.hpp:455
LayerList::const_iterator Iterator
Definition Graph.hpp:53
Graph & TopologicalSort()
Sorts layers in topological order and return this.
Definition Graph.hpp:191
ConstIterator begin() const
Returns const iterator pointing to the beginning of the list. Lowercase for range-based for loops.
Definition Graph.hpp:181
void ForEachLayer(Func func) const
Definition Graph.hpp:40
void AddCompatibilityLayers(std::map< BackendId, std::unique_ptr< class IBackendInternal > > &backends, TensorHandleFactoryRegistry &registry)
Modifies the graph in-place, removing edges connecting layers using different compute devices,...
Definition Graph.cpp:330
size_t GetNumLayers() const
Definition Graph.hpp:205
Interface for a layer that is connectable to other layers via InputSlots and OutputSlots.
Definition INetwork.hpp:81
A layer user-provided data can be bound to (e.g. inputs, outputs).
void Insert(Layer &layer)
Definition Layer.cpp:53
Layer & GetOwningLayer() const
Definition Layer.hpp:53
const OutputSlot * GetConnectedOutputSlot() const
Definition Layer.hpp:56
virtual void Reparent(Graph &dest, std::list< Layer * >::const_iterator iterator)=0
A layer user-provided data can be bound to (e.g. inputs, outputs).
void MoveAllConnections(OutputSlot &destination)
Moves all connections to another OutputSlot.
Definition Layer.cpp:156
Layer & GetOwningLayer() const
Definition Layer.hpp:132
int Connect(InputSlot &destination)
Definition Layer.cpp:123
The SubgraphView class represents a subgraph of a Graph.
Copyright (c) 2021 ARM Limited and Contributors.
constexpr LayerType LayerEnumOf(const T *=nullptr)
LayerType
When adding a new layer, adapt also the LastLayer enum value in the enum class LayerType below.
Definition Types.hpp:494
Status
enumeration
Definition Types.hpp:43
DestType PolymorphicDowncast(SourceType *value)
Polymorphic downcast for build in pointers only.
ShapeInferenceMethod
The ShapeInferenceMethod modify how the output shapes are treated.
Definition Types.hpp:237
@ InferAndValidate
Infer missing output shapes and validate all output shapes.
Definition Types.hpp:241
@ ValidateOnly
Validate all output shapes.
Definition Types.hpp:239
void IgnoreUnused(Ts &&...)
Wrapper class returned by Graph::GetInputLayers()
Definition Graph.hpp:62
ConstIteratorInputs end() const
Definition Graph.hpp:70
InputLayersAccessor(const Graph &graph)
Definition Graph.hpp:63
ConstIteratorInputs begin() const
Definition Graph.hpp:65
Wrapper class returned by Graph::GetOutputLayers()
Definition Graph.hpp:81
ConstIteratorOutputs begin() const
Definition Graph.hpp:84
ConstIteratorOutputs end() const
Definition Graph.hpp:90
OutputLayersAccessor(const Graph &graph)
Definition Graph.hpp:82