1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
// This file is part of OpenCV project.
// It is subject to the license terms in the LICENSE file found in the top-level directory
// of this distribution and at http://opencv.org/license.html.
//
// Copyright (C) 2019 Intel Corporation
#include <unordered_set>
#include "pattern_matching.hpp"
namespace {
using Graph = cv::gimpl::GModel::Graph;
using Metadata = typename Graph::CMetadataT;
using VisitedMatchings = std::list<std::pair<ade::NodeHandle, ade::NodeHandle>>;
using LabeledNodes = std::unordered_map
< // reader node
ade::NodeHandle
// if the reader node above is:
// - DATA node: then vector is 1-element vector containing port number of
// the input edge
// - OP node: then vector is ports' vector of current connections between
// this node and an parent active DATA node
, std::vector<std::size_t>
, ade::HandleHasher<ade::Node>
>;
using MultipleMatchings = std::unordered_map
// pattern OP node
< ade::NodeHandle
// nodes in the test graph which match to the pattern OP node above
, std::vector<ade::NodeHandle>
, ade::HandleHasher<ade::Node>
>;
// Returns true if two DATA nodes are semantically and structurally identical:
// - both nodes have the same GShape
// - both nodes are produced by the same port numbers
// - both nodes have the same number of output edges
// (output edges' ports are not checked here)
//
// @param first - first node to compare
// @param firstPorts - a single element vector with first DATA node's producer output port
// @param firstMeta - metadata of first
// @param second - second node to compare
// @param secondPorts - a single element vector with second DATA node's producer output port
// @param secondMeta - metadata of second
bool compareDataNodes(const ade::NodeHandle& first, const std::vector<std::size_t>& firstPorts,
const Metadata& firstMeta,
const ade::NodeHandle& second, const std::vector<std::size_t>& secondPorts,
const Metadata& secondMeta) {
if (secondMeta.get<cv::gimpl::NodeType>().t != cv::gimpl::NodeType::DATA) {
throw std::logic_error("NodeType of passed node as second argument"
"shall be NodeType::DATA!");
}
if (firstMeta.get<cv::gimpl::Data>().shape != secondMeta.get<cv::gimpl::Data>().shape) {
return false;
}
if (*firstPorts.begin() != *secondPorts.begin()) {
return false;
}
const auto& firstOutputEdges = first->outEdges();
const auto& secondOutputEdges = second->outEdges();
if (firstOutputEdges.size() != secondOutputEdges.size()) {
return false;
}
// FIXME: Because of new changes which introduce existence of unused DATA nodes
// check that first and second nodes have the same type of DATA::Storage.
return true;
};
// Returns true if two OP nodes semantically and structurally identical:
// - both nodes have the same kernel name
// - both nodes are produced by the same port numbers
// - if any of the nodes are in the array with visited matchings, then:
// first node is equal to found matching first argument and
// second node is equal to found matching second argument
//
// @param first - first node to compare
// @param firstPorts - ports' vector of current connections between first node and an parent active
// DATA node
// @param firstMeta - metadata of first
// @param second - second node to compare
// @param secondPorts - ports' vector of current connections between second node and an parent
// active DATA node
// @param secondMeta - metadata of second
// @param [out] isAlreadyVisited - set to true if first and second nodes have been already visited
bool compareOpNodes(const VisitedMatchings& matchedVisitedNodes,
const ade::NodeHandle& first, std::vector<std::size_t> firstPorts,
const Metadata& firstMeta,
const ade::NodeHandle& second, std::vector<std::size_t> secondPorts,
const Metadata& secondMeta,
bool& isAlreadyVisited) {
if (secondMeta.get<cv::gimpl::NodeType>().t != cv::gimpl::NodeType::OP) {
throw std::logic_error("NodeType of passed node as second argument shall be NodeType::OP!");
}
// Assuming that if kernels names are the same then
// output DATA nodes counts from kernels are the same.
// Assuming that if kernels names are the same then
// input DATA nodes counts to kernels are the same.
if (firstMeta.get<cv::gimpl::Op>().k.name != secondMeta.get<cv::gimpl::Op>().k.name) {
return false;
}
std::sort(firstPorts.begin(), firstPorts.end());
std::sort(secondPorts.begin(), secondPorts.end());
if (firstPorts != secondPorts) {
return false;
}
// Shall work, but it is good to test on the cases where multiple start pattern OP nodes
// maps to the test's one.
auto foundIt = std::find_if(matchedVisitedNodes.begin(), matchedVisitedNodes.end(),
[&first, &second](const std::pair<ade::NodeHandle,
ade::NodeHandle>& match)
{return first == match.first || second == match.second; });
if (foundIt != matchedVisitedNodes.end()) {
if (first != foundIt->first || second != foundIt->second) {
return false;
}
isAlreadyVisited = true;
}
return true;
};
// Retrieves and return sample from the cartesian product of candidates sets
VisitedMatchings sampleFromProduct(std::size_t sampleIdx, // index of the sample in the product
const MultipleMatchings& candidatesSets) // map of nodes to sets
// of candidates
{
VisitedMatchings matchingsSample;
std::size_t quo = sampleIdx;
for (const auto& setForNode : candidatesSets) {
// TODO: order is not determined: for ex., for last node.
// May be use ordered set and map to ensure order?
auto size = setForNode.second.size();
// The below code block decodes sampleIdx into a particular sample from cartesian product
// of candidates sets.
std::size_t index = quo % size;
quo = quo / size;
const auto& candidate = setForNode.second[index];
matchingsSample.push_back({ setForNode.first, candidate });
}
return matchingsSample;
}
// Depending on type of the node retrieve port number (IN/OUT) of the edge entering this node.
std::size_t labelOf (const ade::NodeHandle& node, // reader node
const ade::EdgeHandle& edge, // edge entering the reader node
const Graph& graph) // graph containing node and edge
{
if (graph.metadata(node).get<cv::gimpl::NodeType>().t == cv::gimpl::NodeType::OP) {
return graph.metadata(edge).get<cv::gimpl::Input>().port;
}
else {
return graph.metadata(edge).get<cv::gimpl::Output>().port;
}
};
inline bool IS_STARTPOINT(const ade::NodeHandle& nh){
return nh->inEdges().empty();
}
inline bool IS_ENDPOINT(const ade::NodeHandle& nh){
// FIXME: Because of new changes which introduce existence of unused DATA nodes
// Try to rely on the nh Data::Storage::OUTPUT
return nh->outEdges().empty();
}
} // anonymous namespace
// Routine relies on the logic that 1 DATA node may have only 1 input edge.
cv::gimpl::SubgraphMatch
cv::gimpl::findMatches(const cv::gimpl::GModel::Graph& patternGraph,
const cv::gimpl::GModel::Graph& testGraph) {
//TODO: Possibly, we may add N^2 check whether this graph may match or not at all.
// Check that all pattern OP nodes exist in computational graph.
//---------------------------------------------------------------
// Identify operations which start and end our pattern
SubgraphMatch::S patternStartOpNodes, patternEndOpNodes;
const auto& patternInputDataNodes = patternGraph.metadata().get<cv::gimpl::Protocol>().in_nhs;
const auto& patternOutputDataNodes = patternGraph.metadata().get<cv::gimpl::Protocol>().out_nhs;
for (const auto& node : patternInputDataNodes) {
auto opNodes = node->outNodes();
patternStartOpNodes.insert(opNodes.begin(), opNodes.end());
}
for (const auto& node : patternOutputDataNodes) {
auto opNodes = node->inNodes();
// May be switched to patternEndOpNodes.insert(*opNodes.begin());
patternEndOpNodes.insert(opNodes.begin(), opNodes.end());
}
std::unordered_map<ade::NodeHandle, // pattern OP node
std::vector<ade::NodeHandle>, // nodes in the test graph which match
// to the pattern OP node
ade::HandleHasher<ade::Node>> allMatchingsForStartOpNodes;
//Filling of allMatchingsForStartOpNodes
std::size_t possibleStartPointsCount = 1;
// For every starting OP node of pattern identify matching candidates(there may be many)
// in test graph.
auto testOpNodes = ade::util::filter(testGraph.nodes(),
[&](const ade::NodeHandle& node) {
return testGraph.metadata(node).
get<cv::gimpl::NodeType>().t
== cv::gimpl::NodeType::OP;
});
for (const auto& patternStartOpNode : patternStartOpNodes) {
const auto& patternOpMeta = patternGraph.metadata(patternStartOpNode);
auto& possibleMatchings = allMatchingsForStartOpNodes[patternStartOpNode];
std::copy_if(testOpNodes.begin(), testOpNodes.end(), std::back_inserter(possibleMatchings),
[&](const ade::NodeHandle& testOpNode) {
const auto& testOpMeta = testGraph.metadata(testOpNode);
bool stub = false;
return compareOpNodes({ },
patternStartOpNode, { }, patternOpMeta,
testOpNode, { }, testOpMeta,
stub);
});
if (possibleMatchings.size() == 0) {
// Pattern graph is not matched
return SubgraphMatch { };
}
possibleStartPointsCount *= possibleMatchings.size();
}
SubgraphMatch::M subgraphStartOps;
SubgraphMatch::M subgraphEndOps;
// FIXME: consider moving to S
std::list<ade::NodeHandle> subgraphInternals;
// Structural matching first, semantic matching second.
// 'patternFound' means pattern is matched.
bool patternFound = false;
std::size_t i = 0;
while (!patternFound && (i < possibleStartPointsCount)) {
subgraphStartOps.clear();
subgraphEndOps.clear();
subgraphInternals.clear();
// List of the pairs representing matchings of pattern node to the test node.
VisitedMatchings matchedVisitedNodes;
// Cartesian product of candidate sets for start OP nodes gives set of samples
// as possible matchings for start OP nodes.
// Let allMatchingsForStartOpNodes looks like: x1 : [ y1 ]
// x2 : [ y2, y3 ]
// Cartesian product of two these candidates sets (for x1 and x2 pattern nodes
// correspondingly) produces two samples of matchings for x1, x2:
// [ (x1, y1), (x2, y2) ]
// [ (x1, y1), (x2, y3) ]
//
// Here we fill matchedVisitedNodes list with the next sample from the cartesian product
// of candidates sets.
// i is traversing full cartesian product of candidates sets.
matchedVisitedNodes = sampleFromProduct(i, allMatchingsForStartOpNodes);
bool stop = false;
// matchIt is an iterator to a pair of pattern ade::NodeHandle to test's ade::nodeHandle.
auto matchIt = matchedVisitedNodes.begin();
std::size_t size = matchedVisitedNodes.size();
while (!stop) {
// The following loop traverses through the current level of matchings.
// Every iteration we consider only one certain pair of matched nodes.
for (std::size_t index = 0u; index < size && !stop; ++index, ++matchIt) {
// Check if a given matchIt->first node is an pattern-ending OP node.
// If it is just remember it in a special map.
bool cond1 = std::find(patternEndOpNodes.begin(),
patternEndOpNodes.end(),
matchIt->first)
!= patternEndOpNodes.end();
if (cond1) {
subgraphEndOps[matchIt->first] = matchIt->second;
}
// Check if a given matchIt->first node is an pattern-starting OP node.
// If it is just remember it in a special map.
bool cond2 = std::find(patternStartOpNodes.begin(),
patternStartOpNodes.end(),
matchIt->first)
!= patternStartOpNodes.end();
if (cond2) {
subgraphStartOps[matchIt->first] = matchIt->second;
}
// If neither of conditions are true mark the test node as an internal one.
if (!cond1 && !cond2) {
subgraphInternals.push_back(matchIt->second);
}
//-------------------------------------------------------------------------------
// Given the current pattern/test matching of nodes, traverse their descendants.
// For every descendant store the port of the edge connecting to it.
// NOTE: the nature of port number may vary: it may be either IN for OP nodes
// or OUT for DATA ones
LabeledNodes patternOutputNodesLabeled;
LabeledNodes testOutputNodesLabeled;
auto patternOutputEdges = matchIt->first->outEdges();
auto testOutputEdges = matchIt->second->outEdges();
for (const auto& patternOutputEdge : patternOutputEdges) {
const auto& dstNh = patternOutputEdge->dstNode();
if (!IS_ENDPOINT(dstNh)) {
//Assuming that there is no case for the op node without output data nodes.
patternOutputNodesLabeled[dstNh].
push_back(labelOf(dstNh, patternOutputEdge, patternGraph));
}
}
for (const auto& testOutputEdge : testOutputEdges) {
const auto& dstNh = testOutputEdge->dstNode();
testOutputNodesLabeled[dstNh].
push_back(labelOf(dstNh, testOutputEdge, testGraph));
}
//---------------------------------------------------------------------------------
// Traverse through labeled descendants of pattern node and for every descedant
// find a matching in labeled descendants of corresponding test node
for (const auto& patternNode : patternOutputNodesLabeled) {
bool isAlreadyVisited = false;
const auto& patternNodeMeta = patternGraph.metadata(patternNode.first);
auto testIt = std::find_if(testOutputNodesLabeled.begin(),
testOutputNodesLabeled.end(),
[&](const std::pair<const ade::NodeHandle,
std::vector<std::size_t>>& testNode) {
const auto& testNodeMeta = testGraph.metadata(testNode.first);
auto patternNodeType = patternNodeMeta.get<cv::gimpl::NodeType>().t;
switch(patternNodeType) {
case cv::gimpl::NodeType::DATA:
return compareDataNodes(patternNode.first, patternNode.second,
patternNodeMeta,
testNode.first, testNode.second,
testNodeMeta);
case cv::gimpl::NodeType::OP:
return compareOpNodes(matchedVisitedNodes,
patternNode.first, patternNode.second,
patternNodeMeta,
testNode.first, testNode.second,
testNodeMeta,
isAlreadyVisited);
default:
GAPI_Assert(false && "Unsupported Node type!");
}
return false;
});
if (testIt == testOutputNodesLabeled.end()) {
stop = true;
break;
}
// Update matchedVisitedNodes list with found pair of nodes if the pair
// has not been visited before.
if (!isAlreadyVisited) {
matchedVisitedNodes.push_back({ patternNode.first, testIt->first });
}
} // Loop traversed patternOutputNodesLabeled
} // Loop traversed matchedVisitedNodes
// Suppose, pattern and test graphs' structures without input DATA nodes look like:
// Pattern graph Test graph
// op1 op2 t_op1 t_op2
// +-----+ +-----+ +-----+ +-----+
// v v v v v v v v
// d1 d2 d3 d4 t_d1 t_d2 t_d3 t_d4
// v v v v v v v v
// ... ... ... ... ... ... ... ...
// matchedVisitedNodes content before previous loop execution:
// op1 <--> t_op1, op2 <--> t_op2
// matchedVisitedNodes content after previous loop execution (extended with the next
// level of matchings):
// op1 <--> t_op1, op2 <--> t_op2 | d1 <--> t_d1, d2 <--> t_d2, d3 <--> t_d3, d4 <--> t_d4
// ^
// |
// matchIt
//
// matchIt iterator points to the first matching in next level if the next level exists.
// If there is no next level, matchIt == matchedVisitedNodes.end() and all pattern
// levels (except ones for IN/OUT data nodes) have been already processed, so,
// pattern subgraph is found.
if (!stop) {
// Check if pattetn subgraph is found
if (matchIt == matchedVisitedNodes.end()) {
// Found
stop = true;
patternFound = true;
}
// Update 'size' with the size of the new level of matchings
size = static_cast<std::size_t>(std::distance(matchIt, matchedVisitedNodes.end()));
}
}
if (!patternFound){
// Pattern subgraph is not matched.
// Switch to the next combination of starting points
++i;
continue;
}
SubgraphMatch::M inputApiMatch;
SubgraphMatch::M outputApiMatch;
// Traversing current result for starting OPs
for (auto it = subgraphStartOps.begin();
it != subgraphStartOps.end() && patternFound; ++it) {
const auto& match = *it;
auto patternInputEdges = match.first->inEdges();
auto testInputEdges = match.second->inEdges();
SubgraphMatch::S patternUniqInNodes(match.first->inNodes().begin(),
match.first->inNodes().end());
SubgraphMatch::S testUniqInNodes(match.second->inNodes().begin(),
match.second->inNodes().end());
if (patternUniqInNodes.size() < testUniqInNodes.size()) {
inputApiMatch.clear();
patternFound = false;
break;
}
// Else, patternInNodes.size() > testInNodes.size() is considered as valid case.
// Match pattern input DATA nodes with boundary matched test DATA nodes.
for (const auto& patternInEdge : patternInputEdges) {
// Not all start OP nodes are located in the beginning of the pattern graph
// Start OP may have one input DATA node as an Protocol IN node and other
// input DATA nodes produced from another operations
if (!IS_STARTPOINT(patternInEdge->srcNode())) {
continue;
}
auto patternInputPort =
patternGraph.metadata(patternInEdge).get<cv::gimpl::Input>().port;
auto matchedIt = std::find_if(testInputEdges.begin(), testInputEdges.end(),
[&](const ade::EdgeHandle& testInEdge) -> bool {
auto testInputPort =
testGraph.metadata(testInEdge).get<cv::gimpl::Input>().port;
if (patternInputPort != testInputPort) {
return false;
}
auto foundIt = inputApiMatch.find(patternInEdge->srcNode());
if (foundIt != inputApiMatch.end()) {
if (testInEdge->srcNode() != foundIt->second) {
return false;
}
return true;
}
// Update inputApiMatch map only if the pair of nodes isn't in the map already
inputApiMatch[patternInEdge->srcNode()] = testInEdge->srcNode();
return true;
});
if (matchedIt == testInputEdges.end()) {
inputApiMatch.clear();
patternFound = false;
break;
}
} // Loop traversed patternInputEdges
} // Loop traversed sugraphStartOps
if (!patternFound) {
// Pattern IN data nodes can not be matched.
// Switch to the next combination of starting points
++i;
continue;
}
// Create vector with the correctly ordered IN data nodes in the test subgraph
std::vector<ade::NodeHandle> inputTestDataNodes;
for (const auto& patternInNode : patternInputDataNodes) {
inputTestDataNodes.push_back(inputApiMatch.at(patternInNode));
}
// Traversing current result for ending OPs
// There is an assumption that if the pattern subgraph is matched, then
// OUT data nodes shall be definitely matched
for (const auto& match : subgraphEndOps) {
auto patternOutputEdges = match.first->outEdges();
auto testOutputEdges = match.second->outEdges();
GAPI_Assert(patternOutputEdges.size() == testOutputEdges.size()
&&
"Ending OP nodes are matched, so OPs' outputs count shall be the same!");
// Match pattern output DATA nodes with boundary matched test DATA nodes.
for (const auto& patternOutEdge : patternOutputEdges) {
// Not all end OP nodes are located in the ending of the pattern graph
// End OP node may have one output DATA node as an Protocol OUT node and other
// output DATA nodes as input for another operations
if (!IS_ENDPOINT(patternOutEdge->dstNode())) {
continue;
}
auto patternOutputPort =
patternGraph.metadata(patternOutEdge).get<cv::gimpl::Output>().port;
auto matchedIt = std::find_if(testOutputEdges.begin(), testOutputEdges.end(),
[&](const ade::EdgeHandle& testOutEdge) -> bool {
auto testOutputPort =
testGraph.metadata(testOutEdge).get<cv::gimpl::Output>().port;
if (patternOutputPort != testOutputPort) {
return false;
}
outputApiMatch[patternOutEdge->dstNode()] = testOutEdge->dstNode();
return true;
});
GAPI_Assert(matchedIt != testOutputEdges.end()
&&
"There shall be a match for every OUT data node from ending OP node,"
"if ending OP node matches");
}
}
// Create vector with the correctly ordered OUT data nodes in the test subgraph
std::vector<ade::NodeHandle> outputTestDataNodes;
for (const auto& patternOutNode : patternOutputDataNodes) {
outputTestDataNodes.push_back(outputApiMatch.at(patternOutNode));
}
SubgraphMatch subgraph;
subgraph.inputDataNodes = std::move(inputApiMatch);
subgraph.startOpNodes = std::move(subgraphStartOps);
subgraph.internalLayers = std::move(subgraphInternals);
subgraph.finishOpNodes = std::move(subgraphEndOps);
subgraph.outputDataNodes = std::move(outputApiMatch);
subgraph.inputTestDataNodes = std::move(inputTestDataNodes);
subgraph.outputTestDataNodes = std::move(outputTestDataNodes);
return subgraph;
}
return SubgraphMatch { };
}