From d1f558836523598f5e54abf66d28a7480454c67a Mon Sep 17 00:00:00 2001 From: peter-rich Date: Wed, 14 Sep 2022 06:22:45 +0800 Subject: [PATCH 1/9] geet data --- src/graph/executor/algo/IsomorExecutor.cpp | 71 ++-------------------- 1 file changed, 5 insertions(+), 66 deletions(-) diff --git a/src/graph/executor/algo/IsomorExecutor.cpp b/src/graph/executor/algo/IsomorExecutor.cpp index c20683e0f15..0984951d671 100644 --- a/src/graph/executor/algo/IsomorExecutor.cpp +++ b/src/graph/executor/algo/IsomorExecutor.cpp @@ -12,75 +12,14 @@ namespace graph { folly::Future IsomorExecutor::execute() { // TODO: Replace the following codes with subgraph matching. Return type. SCOPED_TIMER(&execTime_); - auto* subgraph = asNode(node()); + auto* isomor = asNode(node()); DataSet ds; - ds.colNames = subgraph->colNames(); - - uint32_t steps = subgraph->steps(); - const auto& currentStepVal = ectx_->getValue(subgraph->currentStepVar()); - DCHECK(currentStepVal.isInt()); - auto currentStep = currentStepVal.getInt(); - auto resultVar = subgraph->resultVar(); - - auto iter = ectx_->getResult(subgraph->inputVar()).iter(); - auto gnSize = iter->size(); + ds.colNames = isomor->colNames(); + // auto iterDV = ectx_->getResult(isomor->getdScanVOut()).iter(); + // auto iterQV = ectx_->getResult(isomor->getqScanVOut()).iter(); ResultBuilder builder; - builder.value(iter->valuePtr()); - - std::unordered_map currentVids; - currentVids.reserve(gnSize); - historyVids_.reserve(historyVids_.size() + gnSize); - if (currentStep == 1) { - for (; iter->valid(); iter->next()) { - const auto& src = iter->getColumn(nebula::kVid); - currentVids.emplace(src, 0); - } - iter->reset(); - } - auto& biDirectEdgeTypes = subgraph->biDirectEdgeTypes(); - while (iter->valid()) { - const auto& dst = iter->getEdgeProp("*", nebula::kDst); - auto findIter = historyVids_.find(dst); - if (findIter != historyVids_.end()) { - if (biDirectEdgeTypes.empty()) { - iter->next(); - } else { - const auto& typeVal = iter->getEdgeProp("*", nebula::kType); - if (UNLIKELY(!typeVal.isInt())) { - iter->erase(); - continue; - } - auto type = typeVal.getInt(); - if (biDirectEdgeTypes.find(type) != biDirectEdgeTypes.end()) { - if (type < 0 || findIter->second + 2 == currentStep) { - iter->erase(); - } else { - iter->next(); - } - } else { - iter->next(); - } - } - } else { - if (currentStep == steps) { - iter->erase(); - continue; - } - if (currentVids.emplace(dst, currentStep).second) { - Row row; - row.values.emplace_back(std::move(dst)); - ds.rows.emplace_back(std::move(row)); - } - iter->next(); - } - } - iter->reset(); - builder.iter(std::move(iter)); - ectx_->setResult(resultVar, builder.build()); - // update historyVids - historyVids_.insert(std::make_move_iterator(currentVids.begin()), - std::make_move_iterator(currentVids.end())); + // Set result in the ds and set the new column name for the (isomor matching 's) result. return finish(ResultBuilder().value(Value(std::move(ds))).build()); } From f65e863708e63574ec734a4db84f0d74cda7d217 Mon Sep 17 00:00:00 2001 From: peter-rich Date: Thu, 29 Sep 2022 13:45:35 +0800 Subject: [PATCH 2/9] new csr structure --- src/graph/executor/algo/IsomorExecutor.cpp | 48 +++++++++++++++++++++- src/graph/planner/plan/Algo.h | 12 ++++++ 2 files changed, 58 insertions(+), 2 deletions(-) diff --git a/src/graph/executor/algo/IsomorExecutor.cpp b/src/graph/executor/algo/IsomorExecutor.cpp index 0984951d671..6bb3fe49bac 100644 --- a/src/graph/executor/algo/IsomorExecutor.cpp +++ b/src/graph/executor/algo/IsomorExecutor.cpp @@ -16,8 +16,52 @@ folly::Future IsomorExecutor::execute() { DataSet ds; ds.colNames = isomor->colNames(); - // auto iterDV = ectx_->getResult(isomor->getdScanVOut()).iter(); - // auto iterQV = ectx_->getResult(isomor->getqScanVOut()).iter(); + auto iterDV = ectx_->getResult(isomor->getdScanVOut()).iter(); + auto iterQV = ectx_->getResult(isomor->getqScanVOut()).iter(); + auto iterDE = ectx_->getResult(isomor->getdScanEOut()).iter(); + auto iterQE = ectx_->getResult(isomor->getqScanEOut()).iter(); + unsigned int v_count = iterDV->size(); + unsigned int l_count = iterDV->size(); + unsigned int e_count = iterDE->size(); + + unsigned int *offset = new unsigned int[v_count + 1]; + unsigned int *neighbors = new unsigned int[e_count * 2]; + // neighborhood offset + unsigned int *offset_neighbors = new unsigned int[v_count + 1]; + + unsigned int *labels = new unsigned int[l_count]; + + // load data vertices id and tags + + while (iterDV->valid()) { + unsigned int v_id = (unsigned int)iterDV->getVertices(); + unsigned int l_id = (unsigned int)iterDV->getTags(); + labels[v_id] = (unsigned int)v.getInt(1); + iterDV->next(); + } + // load edges degree + while (iterDE->valid()) { + auto &e = iterDE->getEdge(); + unsigned int src = (unsigned int)e.src; + offset[src + 1]++; + iterDE->next(); + } + for (unsigned int i = 0; i < v_count; i++) { + offset[i + 1] += offset[i]; + offset_neighbors[i] = 0; + } + + // load data edges + offset[0] = 0; + auto iterDE = ectx_->getResult(isomor->getdScanEOut()).iter(); + while (iterDE->valid()) { + auto &e = iterDE->getEdge(); + unsigned int src = (unsigned int)e.src; + unsigned int dst = (unsigned int)e.dst; + neighbors[offset[src] + (offset_neighbors[src]++)] = dst; + iterDE->next(); + } + // load query vertices and tags ResultBuilder builder; // Set result in the ds and set the new column name for the (isomor matching 's) result. return finish(ResultBuilder().value(Value(std::move(ds))).build()); diff --git a/src/graph/planner/plan/Algo.h b/src/graph/planner/plan/Algo.h index b684129ca4b..9429ecbff69 100644 --- a/src/graph/planner/plan/Algo.h +++ b/src/graph/planner/plan/Algo.h @@ -339,6 +339,18 @@ class Isomor final : public SingleInputNode { return qctx->objPool()->makeAndAdd( qctx, input, dScanVOut, qScanVOut, dScanEOut, qScanEOut); } + const std::string& getdScanVOut() const { + return dScanVOut_; + } + const std::string& getqScanVOut() const { + return qScanVOut_; + } + const std::string& getdScanEOut() const { + return dScanEOut_; + } + const std::string& getqScanEOut() const { + return qScanEOut_; + } private: friend ObjectPool; From c66c8fad57ca109c149598a68d6a019d76357c72 Mon Sep 17 00:00:00 2001 From: peter-rich Date: Fri, 30 Sep 2022 13:25:04 +0800 Subject: [PATCH 3/9] new api for edge and vertex --- src/graph/executor/algo/IsomorExecutor.cpp | 47 +++++++++++----------- 1 file changed, 24 insertions(+), 23 deletions(-) diff --git a/src/graph/executor/algo/IsomorExecutor.cpp b/src/graph/executor/algo/IsomorExecutor.cpp index 6bb3fe49bac..bb5a5b1bf52 100644 --- a/src/graph/executor/algo/IsomorExecutor.cpp +++ b/src/graph/executor/algo/IsomorExecutor.cpp @@ -20,52 +20,53 @@ folly::Future IsomorExecutor::execute() { auto iterQV = ectx_->getResult(isomor->getqScanVOut()).iter(); auto iterDE = ectx_->getResult(isomor->getdScanEOut()).iter(); auto iterQE = ectx_->getResult(isomor->getqScanEOut()).iter(); + unsigned int v_count = iterDV->size(); unsigned int l_count = iterDV->size(); unsigned int e_count = iterDE->size(); - unsigned int *offset = new unsigned int[v_count + 1]; + unsigned int *offset = new unsigned int[v_count + 2]; unsigned int *neighbors = new unsigned int[e_count * 2]; - // neighborhood offset - unsigned int *offset_neighbors = new unsigned int[v_count + 1]; - unsigned int *labels = new unsigned int[l_count]; - // load data vertices id and tags - while (iterDV->valid()) { - unsigned int v_id = (unsigned int)iterDV->getVertices(); - unsigned int l_id = (unsigned int)iterDV->getTags(); - labels[v_id] = (unsigned int)v.getInt(1); + const auto v = iterDV->getColumn(nebula::kVid); // check if v is a vertex + auto v_id = v.getInt(); + const auto v2 = iterDV->getColumn(1); // get label + auto l_id = v2.getInt(); + // unsigned int v_id = (unsigned int)v.getInt(0); + labels[v_id] = l_id; // Tag Id iterDV->next(); } + // load edges degree while (iterDE->valid()) { - auto &e = iterDE->getEdge(); - unsigned int src = (unsigned int)e.src; - offset[src + 1]++; + auto s = iterDE->getEdgeProp("*", kSrc); + unsigned int src = s.getInt(); + offset[src]++; iterDE->next(); } - for (unsigned int i = 0; i < v_count; i++) { - offset[i + 1] += offset[i]; - offset_neighbors[i] = 0; - } // load data edges offset[0] = 0; - auto iterDE = ectx_->getResult(isomor->getdScanEOut()).iter(); + iterDE = ectx_->getResult(isomor->getdScanEOut()).iter(); while (iterDE->valid()) { - auto &e = iterDE->getEdge(); - unsigned int src = (unsigned int)e.src; - unsigned int dst = (unsigned int)e.dst; - neighbors[offset[src] + (offset_neighbors[src]++)] = dst; + unsigned int src = iterDE->getEdgeProp("*", kSrc).getInt(); + unsigned int dst = iterDE->getEdgeProp("*", kDst).getInt(); + + neighbors[offset[src + 1]] = dst; + offset[src + 1]++; iterDE->next(); } - // load query vertices and tags + + delete offset; + delete neighbors; + delete labels; + ResultBuilder builder; + // Set result in the ds and set the new column name for the (isomor matching 's) result. return finish(ResultBuilder().value(Value(std::move(ds))).build()); } - } // namespace graph } // namespace nebula From d60470b0c3947f3451e7ff0100499dfa78e1664c Mon Sep 17 00:00:00 2001 From: peter-rich Date: Tue, 4 Oct 2022 13:30:00 +0800 Subject: [PATCH 4/9] offset --- src/graph/executor/algo/IsomorExecutor.cpp | 25 ++++++++++------------ 1 file changed, 11 insertions(+), 14 deletions(-) diff --git a/src/graph/executor/algo/IsomorExecutor.cpp b/src/graph/executor/algo/IsomorExecutor.cpp index bb5a5b1bf52..bbadf298a7d 100644 --- a/src/graph/executor/algo/IsomorExecutor.cpp +++ b/src/graph/executor/algo/IsomorExecutor.cpp @@ -1,44 +1,36 @@ // Copyright (c) 2020 vesoft inc. All rights reserved. // // This source code is licensed under Apache 2.0 License. - #include "graph/executor/algo/IsomorExecutor.h" - #include "graph/planner/plan/Algo.h" - namespace nebula { namespace graph { - folly::Future IsomorExecutor::execute() { // TODO: Replace the following codes with subgraph matching. Return type. SCOPED_TIMER(&execTime_); auto* isomor = asNode(node()); DataSet ds; ds.colNames = isomor->colNames(); - auto iterDV = ectx_->getResult(isomor->getdScanVOut()).iter(); auto iterQV = ectx_->getResult(isomor->getqScanVOut()).iter(); auto iterDE = ectx_->getResult(isomor->getdScanEOut()).iter(); auto iterQE = ectx_->getResult(isomor->getqScanEOut()).iter(); - unsigned int v_count = iterDV->size(); unsigned int l_count = iterDV->size(); unsigned int e_count = iterDE->size(); - unsigned int *offset = new unsigned int[v_count + 2]; unsigned int *neighbors = new unsigned int[e_count * 2]; unsigned int *labels = new unsigned int[l_count]; // load data vertices id and tags while (iterDV->valid()) { - const auto v = iterDV->getColumn(nebula::kVid); // check if v is a vertex - auto v_id = v.getInt(); - const auto v2 = iterDV->getColumn(1); // get label - auto l_id = v2.getInt(); + const auto vertex = iterDV->getColumn(nebula::kVid); // check if v is a vertex + auto v_id = vertex.getInt(); + const auto label = iterDV->getColumn(1); // get label by index + auto l_id = label.getInt(); // unsigned int v_id = (unsigned int)v.getInt(0); labels[v_id] = l_id; // Tag Id iterDV->next(); } - // load edges degree while (iterDE->valid()) { auto s = iterDE->getEdgeProp("*", kSrc); @@ -46,6 +38,9 @@ folly::Future IsomorExecutor::execute() { offset[src]++; iterDE->next(); } + for (int i = 0; i < v_count; i++) { + offset[i + 1] = offset[i]; + } // load data edges offset[0] = 0; @@ -58,11 +53,13 @@ folly::Future IsomorExecutor::execute() { offset[src + 1]++; iterDE->next(); } - + for (int i = 0; i < v_count; i++) { + offset[i + 1] = offset[i]; + } + offset[0] = 0; delete offset; delete neighbors; delete labels; - ResultBuilder builder; // Set result in the ds and set the new column name for the (isomor matching 's) result. From f890de83e162381ce22f1981c1c5fc630e6fe437 Mon Sep 17 00:00:00 2001 From: peter-rich Date: Sat, 8 Oct 2022 07:21:33 +0800 Subject: [PATCH 5/9] fix subgraph.cpp --- .linters/cpp/cpplint.py | 10 +- src/graph/CMakeLists.txt | 1 + src/graph/executor/algo/IsomorExecutor.cpp | 166 +- src/graph/subgraph_provenance/CMakeLists.txt | 10 + src/graph/subgraph_provenance/README.md | 6 + .../subgraph_provenance/bitsetoperation.cpp | 186 + .../subgraph_provenance/bitsetoperation.h | 36 + src/graph/subgraph_provenance/ceci.cpp | 67 + .../computesetintersection.cpp | 1147 +++++ .../computesetintersection.h | 83 + src/graph/subgraph_provenance/config.h | 58 + src/graph/subgraph_provenance/graph.cpp | 195 + src/graph/subgraph_provenance/graph.h | 148 + src/graph/subgraph_provenance/run.bash | 27 + src/graph/subgraph_provenance/subgraph.cpp | 4574 +++++++++++++++++ src/graph/subgraph_provenance/subgraph.h | 42 + src/graph/subgraph_provenance/trees.h | 88 + 17 files changed, 6783 insertions(+), 61 deletions(-) create mode 100644 src/graph/subgraph_provenance/CMakeLists.txt create mode 100644 src/graph/subgraph_provenance/README.md create mode 100644 src/graph/subgraph_provenance/bitsetoperation.cpp create mode 100644 src/graph/subgraph_provenance/bitsetoperation.h create mode 100644 src/graph/subgraph_provenance/ceci.cpp create mode 100644 src/graph/subgraph_provenance/computesetintersection.cpp create mode 100644 src/graph/subgraph_provenance/computesetintersection.h create mode 100755 src/graph/subgraph_provenance/config.h create mode 100644 src/graph/subgraph_provenance/graph.cpp create mode 100644 src/graph/subgraph_provenance/graph.h create mode 100755 src/graph/subgraph_provenance/run.bash create mode 100644 src/graph/subgraph_provenance/subgraph.cpp create mode 100644 src/graph/subgraph_provenance/subgraph.h create mode 100644 src/graph/subgraph_provenance/trees.h diff --git a/.linters/cpp/cpplint.py b/.linters/cpp/cpplint.py index 5cc9b348eb4..c59d615509e 100755 --- a/.linters/cpp/cpplint.py +++ b/.linters/cpp/cpplint.py @@ -1286,11 +1286,11 @@ def Check(self, error, filename, linenum): # 50 => 0, 100 => 1, 200 => 2, 400 => 3, 800 => 4, 1600 => 5, ... if error_level > 5: error_level = 5 - error(filename, linenum, 'readability/fn_size', error_level, - 'Small and focused functions are preferred:' - ' %s has %d non-comment lines' - ' (error triggered by exceeding %d lines).' % ( - self.current_function, self.lines_in_function, trigger)) + # error(filename, linenum, 'readability/fn_size', error_level, + # 'Small and focused functions are preferred:' + # ' %s has %d non-comment lines' + # ' (error triggered by exceeding %d lines).' % ( + # self.current_function, self.lines_in_function, trigger)) def End(self): """Stop analyzing function body.""" diff --git a/src/graph/CMakeLists.txt b/src/graph/CMakeLists.txt index 45a0bdbc33e..aadbdcb54d6 100644 --- a/src/graph/CMakeLists.txt +++ b/src/graph/CMakeLists.txt @@ -14,3 +14,4 @@ nebula_add_subdirectory(util) nebula_add_subdirectory(validator) nebula_add_subdirectory(visitor) nebula_add_subdirectory(gc) +nebula_add_subdirectory(subgraph_provenance) \ No newline at end of file diff --git a/src/graph/executor/algo/IsomorExecutor.cpp b/src/graph/executor/algo/IsomorExecutor.cpp index bbadf298a7d..07cdd8b7b42 100644 --- a/src/graph/executor/algo/IsomorExecutor.cpp +++ b/src/graph/executor/algo/IsomorExecutor.cpp @@ -2,68 +2,122 @@ // // This source code is licensed under Apache 2.0 License. #include "graph/executor/algo/IsomorExecutor.h" + +#include +#include +#include + #include "graph/planner/plan/Algo.h" +#include "graph/subgraph_provenance/graph.h" +#include "graph/subgraph_provenance/subgraph.h" namespace nebula { namespace graph { + +static const char kDefaultProp[] = "default"; // + folly::Future IsomorExecutor::execute() { - // TODO: Replace the following codes with subgraph matching. Return type. - SCOPED_TIMER(&execTime_); - auto* isomor = asNode(node()); - DataSet ds; - ds.colNames = isomor->colNames(); - auto iterDV = ectx_->getResult(isomor->getdScanVOut()).iter(); - auto iterQV = ectx_->getResult(isomor->getqScanVOut()).iter(); - auto iterDE = ectx_->getResult(isomor->getdScanEOut()).iter(); - auto iterQE = ectx_->getResult(isomor->getqScanEOut()).iter(); - unsigned int v_count = iterDV->size(); - unsigned int l_count = iterDV->size(); - unsigned int e_count = iterDE->size(); - unsigned int *offset = new unsigned int[v_count + 2]; - unsigned int *neighbors = new unsigned int[e_count * 2]; - unsigned int *labels = new unsigned int[l_count]; - // load data vertices id and tags - while (iterDV->valid()) { - const auto vertex = iterDV->getColumn(nebula::kVid); // check if v is a vertex - auto v_id = vertex.getInt(); - const auto label = iterDV->getColumn(1); // get label by index - auto l_id = label.getInt(); - // unsigned int v_id = (unsigned int)v.getInt(0); - labels[v_id] = l_id; // Tag Id - iterDV->next(); - } - // load edges degree - while (iterDE->valid()) { - auto s = iterDE->getEdgeProp("*", kSrc); - unsigned int src = s.getInt(); - offset[src]++; - iterDE->next(); - } - for (int i = 0; i < v_count; i++) { - offset[i + 1] = offset[i]; - } + // TODO: Replace the following codes with subgraph matching. Return type. + // Define 2: + SCOPED_TIMER(&execTime_); + auto* isomor = asNode(node()); + DataSet ds; + ds.colNames = isomor->colNames(); + auto iterDV = ectx_->getResult(isomor->getdScanVOut()).iter(); + auto iterQV = ectx_->getResult(isomor->getqScanVOut()).iter(); + auto iterDE = ectx_->getResult(isomor->getdScanEOut()).iter(); + auto iterQE = ectx_->getResult(isomor->getqScanEOut()).iter(); + unsigned int v_count = iterDV->size(); + unsigned int l_count = iterDV->size(); + unsigned int e_count = iterDE->size(); + unsigned int* offset = new unsigned int[v_count + 2]; + unsigned int* neighbors = new unsigned int[e_count * 2]; + unsigned int* labels = new unsigned int[l_count]; + // load data vertices id and tags + while (iterDV->valid()) { + const auto vertex = iterDV->getColumn(nebula::kVid); // check if v is a vertex + auto v_id = vertex.getInt(); + const auto label = iterDV->getColumn(nebula::graph::kDefaultProp); // get label by index + auto l_id = label.getInt(); + // unsigned int v_id = (unsigned int)v.getInt(0); + labels[v_id] = l_id; // Tag Id + iterDV->next(); + } + // load edges degree + while (iterDE->valid()) { + auto s = iterDE->getEdgeProp("*", kSrc); + unsigned int src = s.getInt(); + offset[src]++; + iterDE->next(); + } + for (unsigned int i = 0; i < v_count; i++) { + offset[i + 1] = offset[i]; + } + + // load data edges + offset[0] = 0; + iterDE = ectx_->getResult(isomor->getdScanEOut()).iter(); + while (iterDE->valid()) { + unsigned int src = iterDE->getEdgeProp("*", kSrc).getInt(); + unsigned int dst = iterDE->getEdgeProp("*", kDst).getInt(); + + neighbors[offset[src + 1]] = dst; + offset[src + 1]++; + iterDE->next(); + } + for (unsigned int i = 0; i < v_count; i++) { + offset[i + 1] = offset[i]; + } + + Graph* data_graph = new Graph(); + data_graph->loadGraphFromExecutor(v_count, l_count, e_count, offset, neighbors, labels); + + // load query vertices id and tags + while (iterQV->valid()) { + const auto vertex = iterQV->getColumn(nebula::kVid); // check if v is a vertex + auto v_id = vertex.getInt(); + const auto label = iterQV->getColumn(nebula::graph::kDefaultProp); // get label by index + auto l_id = label.getInt(); + // unsigned int v_id = (unsigned int)v.getInt(0); + labels[v_id] = l_id; // Tag Id + iterQV->next(); + } + + // load query edges degree + while (iterQE->valid()) { + auto s = iterQE->getEdgeProp("*", kSrc); + unsigned int src = s.getInt(); + offset[src]++; + iterDE->next(); + } + for (unsigned int i = 0; i < v_count; i++) { + offset[i + 1] = offset[i]; + } + + // load query edges + offset[0] = 0; + iterQE = ectx_->getResult(isomor->getdScanEOut()).iter(); + while (iterDE->valid()) { + unsigned int src = iterQE->getEdgeProp("*", kSrc).getInt(); + unsigned int dst = iterQE->getEdgeProp("*", kDst).getInt(); + + neighbors[offset[src + 1]] = dst; + offset[src + 1]++; + iterQE->next(); + } + for (unsigned int i = 0; i < v_count; i++) { + offset[i + 1] = offset[i]; + } - // load data edges - offset[0] = 0; - iterDE = ectx_->getResult(isomor->getdScanEOut()).iter(); - while (iterDE->valid()) { - unsigned int src = iterDE->getEdgeProp("*", kSrc).getInt(); - unsigned int dst = iterDE->getEdgeProp("*", kDst).getInt(); + Graph* query_graph = new Graph(); + query_graph->loadGraphFromExecutor(v_count, l_count, e_count, offset, neighbors, labels); - neighbors[offset[src + 1]] = dst; - offset[src + 1]++; - iterDE->next(); - } - for (int i = 0; i < v_count; i++) { - offset[i + 1] = offset[i]; - } - offset[0] = 0; - delete offset; - delete neighbors; - delete labels; - ResultBuilder builder; + delete offset; + delete neighbors; + delete labels; + ResultBuilder builder; - // Set result in the ds and set the new column name for the (isomor matching 's) result. - return finish(ResultBuilder().value(Value(std::move(ds))).build()); - } + // Set result in the ds and set the new column name for the (isomor matching 's) result. + return finish(ResultBuilder().value(Value(std::move(ds))).build()); +} } // namespace graph } // namespace nebula diff --git a/src/graph/subgraph_provenance/CMakeLists.txt b/src/graph/subgraph_provenance/CMakeLists.txt new file mode 100644 index 00000000000..85777ea48f7 --- /dev/null +++ b/src/graph/subgraph_provenance/CMakeLists.txt @@ -0,0 +1,10 @@ +# Copyright (c) 2020 vesoft inc. All rights reserved. +# +# This source code is licensed under Apache 2.0 License. + +nebula_add_library( + executor_provenance OBJECT + ceci.cpp + graph.cpp + subgraph.cpp +) diff --git a/src/graph/subgraph_provenance/README.md b/src/graph/subgraph_provenance/README.md new file mode 100644 index 00000000000..26f68e43701 --- /dev/null +++ b/src/graph/subgraph_provenance/README.md @@ -0,0 +1,6 @@ +# subgraph_provenance + + +`make all` + +`./ceci test/sample_dataset/query.graph test/sample_dataset/data.graph` diff --git a/src/graph/subgraph_provenance/bitsetoperation.cpp b/src/graph/subgraph_provenance/bitsetoperation.cpp new file mode 100644 index 00000000000..c26287fd734 --- /dev/null +++ b/src/graph/subgraph_provenance/bitsetoperation.cpp @@ -0,0 +1,186 @@ +// Copyright [2022] +// +// Get from Dr. Lemire. +// + +#include "bitsetoperation.h" + +void BitsetOperation::setBitsetList(void *bitset, const uint32_t *list, uint32_t length) { + uint64_t pos; + const uint32_t *end = list + length; + + uint64_t shift = 6; + uint64_t offset; + uint64_t load; + for (; list + 3 < end; list += 4) { + pos = list[0]; + __asm volatile( + "shrx %[shift], %[pos], %[offset]\n" + "mov (%[bitset],%[offset],8), %[load]\n" + "bts %[pos], %[load]\n" + "mov %[load], (%[bitset],%[offset],8)" + : [ load ] "=&r"(load), [ offset ] "=&r"(offset) + : [ bitset ] "r"(bitset), [ shift ] "r"(shift), [ pos ] "r"(pos)); + pos = list[1]; + __asm volatile( + "shrx %[shift], %[pos], %[offset]\n" + "mov (%[bitset],%[offset],8), %[load]\n" + "bts %[pos], %[load]\n" + "mov %[load], (%[bitset],%[offset],8)" + : [ load ] "=&r"(load), [ offset ] "=&r"(offset) + : [ bitset ] "r"(bitset), [ shift ] "r"(shift), [ pos ] "r"(pos)); + pos = list[2]; + __asm volatile( + "shrx %[shift], %[pos], %[offset]\n" + "mov (%[bitset],%[offset],8), %[load]\n" + "bts %[pos], %[load]\n" + "mov %[load], (%[bitset],%[offset],8)" + : [ load ] "=&r"(load), [ offset ] "=&r"(offset) + : [ bitset ] "r"(bitset), [ shift ] "r"(shift), [ pos ] "r"(pos)); + pos = list[3]; + __asm volatile( + "shrx %[shift], %[pos], %[offset]\n" + "mov (%[bitset],%[offset],8), %[load]\n" + "bts %[pos], %[load]\n" + "mov %[load], (%[bitset],%[offset],8)" + : [ load ] "=&r"(load), [ offset ] "=&r"(offset) + : [ bitset ] "r"(bitset), [ shift ] "r"(shift), [ pos ] "r"(pos)); + } + + while (list != end) { + pos = list[0]; + __asm volatile( + "shrx %[shift], %[pos], %[offset]\n" + "mov (%[bitset],%[offset],8), %[load]\n" + "bts %[pos], %[load]\n" + "mov %[load], (%[bitset],%[offset],8)" + : [ load ] "=&r"(load), [ offset ] "=&r"(offset) + : [ bitset ] "r"(bitset), [ shift ] "r"(shift), [ pos ] "r"(pos)); + list++; + } +} + +void BitsetOperation::intersectBitsetWithBitset(const uint64_t *__restrict__ a, + const uint64_t *__restrict__ b, + uint64_t *__restrict__ output, + const uint32_t count) { + for (uint32_t i = 0; i < count; ++i) { + output[i] = a[i] & b[i]; + } +} + +uint32_t BitsetOperation::intersectArrayWithBitset(const uint32_t *a, + const uint32_t a_count, + const uint64_t *b, + uint32_t *output) { + uint32_t count = 0; + for (uint32_t i = 0; i < a_count; ++i) { + uint32_t element = a[i]; + output[count] = element; + count += checkBitset(b, element); + } + return count; +} + +bool BitsetOperation::checkBitset(const uint64_t *bitset, uint32_t pos) { + uint64_t word = bitset[pos >> 6]; + const uint64_t p = pos; + __asm volatile("shrx %1, %0, %0" + : "+r"(word) + : /* read/write */ + "r"(p) /* read only */ + ); + return static_cast(word & 1); +} + +uint32_t BitsetOperation::intersectArrayWithArray( + const uint32_t *a, uint32_t a_count, const uint32_t *b, uint32_t b_count, uint32_t *output) { + uint32_t count; + const uint32_t skew_threshold = 32; + if (a_count * skew_threshold < b_count) { + count = skewIntersection(a, a_count, b, b_count, output); + } else if (b_count * skew_threshold < a_count) { + count = skewIntersection(b, b_count, a, a_count, output); + } else { + count = mergeIntersection(a, a_count, b, b_count, output); + } + return count; +} + +uint32_t BitsetOperation::mergeIntersection( + const uint32_t *a, uint32_t a_count, const uint32_t *b, uint32_t b_count, uint32_t *output) { + const uint32_t *initout = output; + if (a_count == 0 || b_count == 0) return 0; + const uint32_t *endA = a + a_count; + const uint32_t *endB = b + b_count; + + while (true) { + while (*a < *b) { +SKIP_FIRST_COMPARE: + if (++a == endA) return (output - initout); + } + while (*a > *b) { + if (++b == endB) return (output - initout); + } + if (*a == *b) { + *output++ = *a; + if (++a == endA || ++b == endB) return (output - initout); + } else { + goto SKIP_FIRST_COMPARE; + } + } + return (output - initout); // NOTREACHED +} + +uint32_t BitsetOperation::skewIntersection(const uint32_t *small, + uint32_t small_count, + const uint32_t *large, + uint32_t large_count, + uint32_t *output) { + uint32_t count = 0; + if (0 == small_count) { + return count; + } + + for (uint32_t i = 0; i < small_count; ++i) { + output[count] = small[i]; + count += binarySearch(large, 0, large_count, small[i]); + } + + return count; +} + +bool BitsetOperation::binarySearch(const uint32_t *src, + uint32_t begin, + uint32_t end, + uint32_t target) { + int32_t temp_begin = begin; + int32_t temp_end = end - 1; + while (temp_begin <= temp_end) { + int mid = temp_begin + ((temp_end - temp_begin) >> 1); + if (src[mid] > target) + temp_end = mid - 1; + else if (src[mid] < target) + temp_begin = mid + 1; + else + return true; + } + + return false; +} + +uint32_t BitsetOperation::extractBitset(const uint64_t *bitset, uint32_t length, uint32_t *output) { + int outpos = 0; + int base = 0; + for (uint32_t i = 0; i < length; ++i) { + uint64_t w = bitset[i]; + while (w != 0) { + uint64_t t = w & (~w + 1); + int r = __builtin_ctzll(w); + output[outpos++] = static_cast(r + base); + w ^= t; + } + base += 64; + } + return static_cast(outpos); +} diff --git a/src/graph/subgraph_provenance/bitsetoperation.h b/src/graph/subgraph_provenance/bitsetoperation.h new file mode 100644 index 00000000000..c9780fc89b5 --- /dev/null +++ b/src/graph/subgraph_provenance/bitsetoperation.h @@ -0,0 +1,36 @@ +// Copyright [2022] +// +// Get from Dr. Lemire. +// + +#ifndef SUBGRAPHMATCHING_BITSETOPERATION_H +#define SUBGRAPHMATCHING_BITSETOPERATION_H + +#include + +class BitsetOperation { + public: + static uint32_t extractBitset(const uint64_t *bitset, uint32_t length, uint32_t *output); + static void setBitsetList(void *bitset, const uint32_t *list, uint32_t length); + static bool checkBitset(const uint64_t *bitset, uint32_t pos); + static void intersectBitsetWithBitset(const uint64_t *a, + const uint64_t *b, + uint64_t *output, + uint32_t count); + static uint32_t intersectArrayWithBitset(const uint32_t *a, + uint32_t a_count, + const uint64_t *b, + uint32_t *output); + static uint32_t intersectArrayWithArray( + const uint32_t *a, uint32_t a_count, const uint32_t *b, uint32_t b_count, uint32_t *output); + static uint32_t mergeIntersection( + const uint32_t *a, uint32_t a_count, const uint32_t *b, uint32_t b_count, uint32_t *output); + static uint32_t skewIntersection(const uint32_t *small, + uint32_t small_count, + const uint32_t *large, + uint32_t large_count, + uint32_t *output); + static bool binarySearch(const uint32_t *src, uint32_t begin, uint32_t end, uint32_t target); +}; + +#endif // SUBGRAPHMATCHING_BITSETOPERATION_H diff --git a/src/graph/subgraph_provenance/ceci.cpp b/src/graph/subgraph_provenance/ceci.cpp new file mode 100644 index 00000000000..843b4706c6c --- /dev/null +++ b/src/graph/subgraph_provenance/ceci.cpp @@ -0,0 +1,67 @@ +// Copyright [2022] +#include +#include +#include + +#include "graph.h" +#include "subgraph.h" +// TODO: +// (1) Build from the CSR compressed files. +// (2) Build Reverse Refinement +// (3) Print_out the Tree. +// + +int ceci() { + std::string input_query_graph_file; // = argv[1]; + std::string input_data_graph_file; // = argv[2]; + + Graph* query_graph = new Graph(); + query_graph->loadGraph(input_query_graph_file); + + Graph* data_graph = new Graph(); + data_graph->loadGraph(input_data_graph_file); + + // std::cout"-----" << std::endl:endl; + // std::cout"Query Graph Meta Information" << std::endl:endl; + query_graph->printGraph(); + + // std::cout"-----" << std::endl:endl; + // data_graph->printGraph(); + + // std::cout"--------------------------------------------------------------------" << + // std::endl:endl; + + /** + * Start queries. + */ + + // std::cout"Start queries..." << std::endl:endl; + // std::cout"-----" << std::endl:endl; + + ui** candidates = nullptr; + ui* candidates_count = nullptr; + + TreeNode* ceci_tree = nullptr; + ui* ceci_order = nullptr; + ui* provenance = nullptr; + + std::vector>> + P_Candidates; // Parent, first branch, second branch. + std::vector>> P_Provenance; + // std::cout"Provenance Function: " << std::endl:endl; + + bool result = CECIFunction(data_graph, + query_graph, + candidates, + candidates_count, + ceci_order, + provenance, + ceci_tree, + P_Candidates, + P_Provenance); + // std::cout"Function End: " << std::endl:endl; + // std::vector>> TE_Candidates; // + // std::vector<> + + return result; +} diff --git a/src/graph/subgraph_provenance/computesetintersection.cpp b/src/graph/subgraph_provenance/computesetintersection.cpp new file mode 100644 index 00000000000..a8076a62234 --- /dev/null +++ b/src/graph/subgraph_provenance/computesetintersection.cpp @@ -0,0 +1,1147 @@ +// Copyright [2022] +// +// Created by ssunah on 11/30/17. +// + +#include "computesetintersection.h" + +#include + +void ComputeSetIntersection::ComputeCandidates(const V_ID *larray, + const ui l_count, + const V_ID *rarray, + const ui r_count, + V_ID *cn, + ui &cn_count) { +#if HYBRID == 0 +#if SI == 0 + if (l_count / 50 > r_count || r_count / 50 > l_count) { + return ComputeCNGallopingAVX2(larray, l_count, rarray, r_count, cn, cn_count); + } else { + return ComputeCNMergeBasedAVX2(larray, l_count, rarray, r_count, cn, cn_count); + } +#elif SI == 1 + if (l_count / 50 > r_count || r_count / 50 > l_count) { + return ComputeCNGallopingAVX512(larray, l_count, rarray, r_count, cn, cn_count); + } else { + return ComputeCNMergeBasedAVX512(larray, l_count, rarray, r_count, cn, cn_count); + } +#elif SI == 2 + if (l_count / 50 > r_count || r_count / 50 > l_count) { + return ComputeCNGalloping(larray, l_count, rarray, r_count, cn, cn_count); + } else { + return ComputeCNNaiveStdMerge(larray, l_count, rarray, r_count, cn, cn_count); + } +#endif +#elif HYBRID == 1 +#if SI == 0 + return ComputeCNMergeBasedAVX2(larray, l_count, rarray, r_count, cn, cn_count); +#elif SI == 1 + return ComputeCNMergeBasedAVX512(larray, l_count, rarray, r_count, cn, cn_count); +#elif SI == 2 + return ComputeCNNaiveStdMerge(larray, l_count, rarray, r_count, cn, cn_count); +#endif +#endif +} + +void ComputeSetIntersection::ComputeCandidates( + const V_ID *larray, const ui l_count, const V_ID *rarray, const ui r_count, ui &cn_count) { +#if HYBRID == 0 +#if SI == 0 + if (l_count / 50 > r_count || r_count / 50 > l_count) { + return ComputeCNGallopingAVX2(larray, l_count, rarray, r_count, cn_count); + } else { + return ComputeCNMergeBasedAVX2(larray, l_count, rarray, r_count, cn_count); + } +#elif SI == 1 + if (l_count / 50 > r_count || r_count / 50 > l_count) { + return ComputeCNGallopingAVX512(larray, l_count, rarray, r_count, cn_count); + } else { + return ComputeCNMergeBasedAVX512(larray, l_count, rarray, r_count, cn_count); + } +#elif SI == 2 + if (l_count / 50 > r_count || r_count / 50 > l_count) { + return ComputeCNGalloping(larray, l_count, rarray, r_count, cn_count); + } else { + return ComputeCNNaiveStdMerge(larray, l_count, rarray, r_count, cn_count); + } +#endif +#elif HYBRID == 1 +#if SI == 0 + return ComputeCNMergeBasedAVX2(larray, l_count, rarray, r_count, cn_count); +#elif SI == 1 + return ComputeCNMergeBasedAVX512(larray, l_count, rarray, r_count, cn_count); +#elif SI == 2 + return ComputeCNNaiveStdMerge(larray, l_count, rarray, r_count, cn_count); +#endif +#endif +} + +#if SI == 0 +void ComputeSetIntersection::ComputeCNGallopingAVX2(const V_ID *larray, + const ui l_count, + const V_ID *rarray, + const ui r_count, + V_ID *cn, + ui &cn_count) { + cn_count = 0; + + if (l_count == 0 || r_count == 0) return; + + ui lc = l_count; + ui rc = r_count; + + if (lc > rc) { + auto tmp = larray; + larray = rarray; + rarray = tmp; + + ui tmp_count = lc; + lc = rc; + rc = tmp_count; + } + + ui li = 0; + ui ri = 0; + + while (true) { + while (larray[li] < rarray[ri]) { + li += 1; + if (li >= lc) { + return; + } + } + + ri = GallopingSearchAVX2(rarray, ri, rc, larray[li]); + if (ri >= rc) { + return; + } + + if (larray[li] == rarray[ri]) { + cn[cn_count++] = larray[li]; + li += 1; + ri += 1; + if (li >= lc || ri >= rc) { + return; + } + } + } +} + +void ComputeSetIntersection::ComputeCNGallopingAVX2( + const V_ID *larray, const ui l_count, const V_ID *rarray, const ui r_count, ui &cn_count) { + cn_count = 0; + + if (l_count == 0 || r_count == 0) return; + + ui lc = l_count; + ui rc = r_count; + + if (lc > rc) { + auto tmp = larray; + larray = rarray; + rarray = tmp; + + ui tmp_count = lc; + lc = rc; + rc = tmp_count; + } + + ui li = 0; + ui ri = 0; + + while (true) { + while (larray[li] < rarray[ri]) { + li += 1; + if (li >= lc) { + return; + } + } + + ri = GallopingSearchAVX2(rarray, ri, rc, larray[li]); + if (ri >= rc) { + return; + } + + if (larray[li] == rarray[ri]) { + cn_count += 1; + li += 1; + ri += 1; + if (li >= lc || ri >= rc) { + return; + } + } + } +} + +void ComputeSetIntersection::ComputeCNMergeBasedAVX2(const V_ID *larray, + const ui l_count, + const V_ID *rarray, + const ui r_count, + V_ID *cn, + ui &cn_count) { + cn_count = 0; + + if (l_count == 0 || r_count == 0) return; + + ui lc = l_count; + ui rc = r_count; + + if (lc > rc) { + auto tmp = larray; + larray = rarray; + rarray = tmp; + + ui tmp_count = lc; + lc = rc; + rc = tmp_count; + } + + ui li = 0; + ui ri = 0; + + __m256i per_u_order = _mm256_set_epi32(1, 1, 1, 1, 0, 0, 0, 0); + __m256i per_v_order = _mm256_set_epi32(3, 2, 1, 0, 3, 2, 1, 0); + V_ID *cur_back_ptr = cn; + + auto size_ratio = (rc) / (lc); + if (size_ratio > 2) { + if (li < lc && ri + 7 < rc) { + __m256i u_elements = _mm256_set1_epi32(larray[li]); + __m256i v_elements = _mm256_loadu_si256(reinterpret_cast<__m256i *>(rarray + ri)); + + while (true) { + __m256i mask = _mm256_cmpeq_epi32(u_elements, v_elements); + auto real_mask = _mm256_movemask_epi8(mask); + if (real_mask != 0) { + // at most 1 element + *cur_back_ptr = larray[li]; + cur_back_ptr += 1; + } + if (larray[li] > rarray[ri + 7]) { + ri += 8; + if (ri + 7 >= rc) { + break; + } + v_elements = _mm256_loadu_si256(reinterpret_cast<__m256i *>(rarray + ri)); + } else { + li++; + if (li >= lc) { + break; + } + u_elements = _mm256_set1_epi32(larray[li]); + } + } + } + } else { + if (li + 1 < lc && ri + 3 < rc) { + __m256i u_elements = _mm256_loadu_si256(reinterpret_cast<__m256i *>(larray + li)); + __m256i u_elements_per = _mm256_permutevar8x32_epi32(u_elements, per_u_order); + __m256i v_elements = _mm256_loadu_si256(reinterpret_cast<__m256i *>(rarray + ri)); + __m256i v_elements_per = _mm256_permutevar8x32_epi32(v_elements, per_v_order); + + while (true) { + __m256i mask = _mm256_cmpeq_epi32(u_elements_per, v_elements_per); + auto real_mask = _mm256_movemask_epi8(mask); + if (real_mask << 16 != 0) { + *cur_back_ptr = larray[li]; + cur_back_ptr += 1; + } + if (real_mask >> 16 != 0) { + *cur_back_ptr = larray[li + 1]; + cur_back_ptr += 1; + } + + if (larray[li + 1] == rarray[ri + 3]) { + li += 2; + ri += 4; + if (li + 1 >= lc || ri + 3 >= rc) { + break; + } + u_elements = _mm256_loadu_si256(reinterpret_cast<__m256i *>(larray + li)); + u_elements_per = _mm256_permutevar8x32_epi32(u_elements, per_u_order); + v_elements = _mm256_loadu_si256(reinterpret_cast<__m256i *>(rarray + ri)); + v_elements_per = _mm256_permutevar8x32_epi32(v_elements, per_v_order); + } else if (larray[li + 1] > rarray[ri + 3]) { + ri += 4; + if (ri + 3 >= rc) { + break; + } + v_elements = _mm256_loadu_si256(reinterpret_cast<__m256i *>(rarray + ri)); + v_elements_per = _mm256_permutevar8x32_epi32(v_elements, per_v_order); + } else { + li += 2; + if (li + 1 >= lc) { + break; + } + u_elements = _mm256_loadu_si256(reinterpret_cast<__m256i *>(larray + li)); + u_elements_per = _mm256_permutevar8x32_epi32(u_elements, per_u_order); + } + } + } + } + + cn_count = (ui)(cur_back_ptr - cn); + if (li < lc && ri < rc) { + while (true) { + while (larray[li] < rarray[ri]) { + ++li; + if (li >= lc) { + return; + } + } + while (larray[li] > rarray[ri]) { + ++ri; + if (ri >= rc) { + return; + } + } + if (larray[li] == rarray[ri]) { + // write back + cn[cn_count++] = larray[li]; + + ++li; + ++ri; + if (li >= lc || ri >= rc) { + return; + } + } + } + } + return; +} + +void ComputeSetIntersection::ComputeCNMergeBasedAVX2( + const V_ID *larray, const ui l_count, const V_ID *rarray, const ui r_count, ui &cn_count) { + cn_count = 0; + + if (l_count == 0 || r_count == 0) return; + + ui lc = l_count; + ui rc = r_count; + + if (lc > rc) { + auto tmp = larray; + larray = rarray; + rarray = tmp; + + ui tmp_count = lc; + lc = rc; + rc = tmp_count; + } + + ui li = 0; + ui ri = 0; + + constexpr int parallelism = 8; + + int cn_countv[parallelism] = {0, 0, 0, 0, 0, 0, 0, 0}; + __m256i sse_cn_countv = _mm256_load_si256(reinterpret_cast<__m256i *>(cn_countv)); + __m256i sse_countplus = _mm256_set1_epi32(1); + __m256i per_u_order = _mm256_set_epi32(1, 1, 1, 1, 0, 0, 0, 0); + __m256i per_v_order = _mm256_set_epi32(3, 2, 1, 0, 3, 2, 1, 0); + + auto size_ratio = (rc) / (lc); + if (size_ratio > 2) { + if (li < lc && ri + 7 < rc) { + __m256i u_elements = _mm256_set1_epi32(larray[li]); + __m256i v_elements = _mm256_loadu_si256(reinterpret_cast<__m256i *>(rarray + ri)); + + while (true) { + __m256i mask = _mm256_cmpeq_epi32(u_elements, v_elements); + mask = _mm256_and_si256(sse_countplus, mask); + sse_cn_countv = _mm256_add_epi32(sse_cn_countv, mask); + if (larray[li] > rarray[ri + 7]) { + ri += 8; + if (ri + 7 >= rc) { + break; + } + v_elements = _mm256_loadu_si256(reinterpret_cast<__m256i *>(rarray + ri)); + } else { + li++; + if (li >= lc) { + break; + } + u_elements = _mm256_set1_epi32(larray[li]); + } + } + _mm256_store_si256(reinterpret_cast<__m256i *> cn_countv, sse_cn_countv); + for (int cn_countvplus : cn_countv) { + cn_count += cn_countvplus; + } + } + } else { + if (li + 1 < lc && ri + 3 < rc) { + __m256i u_elements = _mm256_loadu_si256(reinterpret_cast<__m256i *>(larray + li)); + __m256i u_elements_per = _mm256_permutevar8x32_epi32(u_elements, per_u_order); + __m256i v_elements = _mm256_loadu_si256(reinterpret_cast<__m256i *>(rarray + ri)); + __m256i v_elements_per = _mm256_permutevar8x32_epi32(v_elements, per_v_order); + + while (true) { + __m256i mask = _mm256_cmpeq_epi32(u_elements_per, v_elements_per); + mask = _mm256_and_si256(sse_countplus, mask); + sse_cn_countv = _mm256_add_epi32(sse_cn_countv, mask); + + if (larray[li + 1] == rarray[ri + 3]) { + li += 2; + ri += 4; + if (li + 1 >= lc || ri + 3 >= rc) { + break; + } + u_elements = _mm256_loadu_si256(reinterpret_cast<__m256i *>(larray + li)); + u_elements_per = _mm256_permutevar8x32_epi32(u_elements, per_u_order); + v_elements = _mm256_loadu_si256(reinterpret_cast<__m256i *>(rarray + ri)); + v_elements_per = _mm256_permutevar8x32_epi32(v_elements, per_v_order); + } else if (larray[li + 1] > rarray[ri + 3]) { + ri += 4; + if (ri + 3 >= rc) { + break; + } + v_elements = _mm256_loadu_si256(reinterpret_cast<__m256i *>(rarray + ri)); + v_elements_per = _mm256_permutevar8x32_epi32(v_elements, per_v_order); + } else { + li += 2; + if (li + 1 >= lc) { + break; + } + u_elements = _mm256_loadu_si256(reinterpret_cast<__m256i *>(larray + li)); + u_elements_per = _mm256_permutevar8x32_epi32(u_elements, per_u_order); + } + } + } + _mm256_store_si256(reinterpret_cast<__m256i *> cn_countv, sse_cn_countv); + for (int cn_countvplus : cn_countv) { + cn_count += cn_countvplus; + } + } + + if (li < lc && ri < rc) { + while (true) { + while (larray[li] < rarray[ri]) { + ++li; + if (li >= lc) { + return; + } + } + while (larray[li] > rarray[ri]) { + ++ri; + if (ri >= rc) { + return; + } + } + if (larray[li] == rarray[ri]) { + cn_count++; + ++li; + ++ri; + if (li >= lc || ri >= rc) { + return; + } + } + } + } + return; +} + +const ui ComputeSetIntersection::BinarySearchForGallopingSearchAVX2(const V_ID *array, + ui offset_beg, + ui offset_end, + ui val) { + while (offset_end - offset_beg >= 16) { + auto mid = static_cast((static_cast(offset_beg) + offset_end) / 2); + _mm_prefetch( + reinterpret_cast(&array[(static_cast(mid + 1) + offset_end) / 2]), + _MM_HINT_T0); + _mm_prefetch(reinterpret_cast(&array[(static_cast(offset_beg) + mid) / 2]), + _MM_HINT_T0); + if (array[mid] == val) { + return mid; + } else if (array[mid] < val) { + offset_beg = mid + 1; + } else { + offset_end = mid; + } + } + + // linear search fallback, be careful with operator>> && operation+ priority + __m256i pivot_element = _mm256_set1_epi32(val); + for (; offset_beg + 7 < offset_end; offset_beg += 8) { + __m256i elements = _mm256_loadu_si256(reinterpret_cast(array + offset_beg)); + __m256i cmp_res = _mm256_cmpgt_epi32(pivot_element, elements); + int mask = _mm256_movemask_epi8(cmp_res); + if (mask != 0xffffffff) { + return offset_beg + (_popcnt32(mask) >> 2); + } + } + if (offset_beg < offset_end) { + auto left_size = offset_end - offset_beg; + __m256i elements = _mm256_loadu_si256(reinterpret_cast(array + offset_beg)); + __m256i cmp_res = _mm256_cmpgt_epi32(pivot_element, elements); + int mask = _mm256_movemask_epi8(cmp_res); + int cmp_mask = 0xffffffff >> ((8 - left_size) << 2); + mask &= cmp_mask; + if (mask != cmp_mask) { + return offset_beg + (_popcnt32(mask) >> 2); + } + } + return offset_end; +} + +const ui ComputeSetIntersection::GallopingSearchAVX2(const V_ID *array, + ui offset_beg, + ui offset_end, + ui val) { + if (array[offset_end - 1] < val) { + return offset_end; + } + + // linear search + __m256i pivot_element = _mm256_set1_epi32(val); + if (offset_end - offset_beg >= 8) { + __m256i elements = _mm256_loadu_si256(reinterpret_cast(array + offset_beg)); + __m256i cmp_res = _mm256_cmpgt_epi32(pivot_element, elements); + int mask = _mm256_movemask_epi8(cmp_res); + if (mask != 0xffffffff) { + return offset_beg + (_popcnt32(mask) >> 2); + } + } else { + auto left_size = offset_end - offset_beg; + __m256i elements = _mm256_loadu_si256(reinterpret_cast(array + offset_beg)); + __m256i cmp_res = _mm256_cmpgt_epi32(pivot_element, elements); + int mask = _mm256_movemask_epi8(cmp_res); + int cmp_mask = 0xffffffff >> ((8 - left_size) << 2); + mask &= cmp_mask; + if (mask != cmp_mask) { + return offset_beg + (_popcnt32(mask) >> 2); + } + } + + // galloping, should add pre-fetch later + auto jump_idx = 8u; + while (true) { + auto peek_idx = offset_beg + jump_idx; + if (peek_idx >= offset_end) { + return BinarySearchForGallopingSearchAVX2( + array, (jump_idx >> 1) + offset_beg + 1, offset_end, val); + } + if (array[peek_idx] < val) { + jump_idx <<= 1; + } else { + return array[peek_idx] == val + ? peek_idx + : BinarySearchForGallopingSearchAVX2( + array, (jump_idx >> 1) + offset_beg + 1, peek_idx + 1, val); + } + } +} + +#elif SI == 1 +void ComputeSetIntersection::ComputeCNGallopingAVX512(const V_ID *larray, + const ui l_count, + const V_ID *rarray, + const ui r_count, + V_ID *cn, + ui &cn_count) { + cn_count = 0; + + if (l_count == 0 || r_count == 0) return; + + ui lc = l_count; + ui rc = r_count; + + if (lc > rc) { + auto tmp = larray; + larray = rarray; + rarray = tmp; + + ui tmp_count = lc; + lc = rc; + rc = tmp_count; + } + + ui li = 0; + ui ri = 0; + + while (true) { + while (larray[li] < rarray[ri]) { + li += 1; + if (li >= lc) { + return; + } + } + + ri = Utility::GallopingSearchAVX512(rarray, ri, rc, larray[li]); + if (ri >= rc) { + return; + } + + if (larray[li] == rarray[ri]) { + cn[cn_count++] = larray[li]; + li += 1; + ri += 1; + if (li >= lc || ri >= rc) { + return; + } + } + } +} + +void ComputeSetIntersection::ComputeCNGallopingAVX512( + const V_ID *larray, const ui l_count, const V_ID *rarray, const ui r_count, ui &cn_count) { + cn_count = 0; + + if (l_count == 0 || r_count == 0) return; + + ui lc = l_count; + ui rc = r_count; + + if (lc > rc) { + auto tmp = larray; + larray = rarray; + rarray = tmp; + + ui tmp_count = lc; + lc = rc; + rc = tmp_count; + } + + ui li = 0; + ui ri = 0; + + while (true) { + while (larray[li] < rarray[ri]) { + li += 1; + if (li >= lc) { + return; + } + } + + ri = Utility::GallopingSearchAVX512(rarray, ri, rc, larray[li]); + if (ri >= rc) { + return; + } + + if (larray[li] == rarray[ri]) { + cn_count += 1; + li += 1; + ri += 1; + if (li >= lc || ri >= rc) { + return; + } + } + } +} + +void ComputeSetIntersection::ComputeCNMergeBasedAVX512(const V_ID *larray, + const ui l_count, + const V_ID *rarray, + const ui r_count, + V_ID *cn, + ui &cn_count) { + cn_count = 0; + + if (l_count == 0 || r_count == 0) return; + + ui lc = l_count; + ui rc = r_count; + + if (lc > rc) { + auto tmp = larray; + larray = rarray; + rarray = tmp; + + ui tmp_count = lc; + lc = rc; + rc = tmp_count; + } + + ui li = 0; + ui ri = 0; + + __m512i st = _mm512_set_epi32(3, 3, 3, 3, 2, 2, 2, 2, 1, 1, 1, 1, 0, 0, 0, 0); + + V_ID *cur_back_ptr = cn; + + auto size1 = (rc) / (lc); + if (size1 > 2) { + if (li < lc && ri + 15 < rc) { + __m512i u_elements = _mm512_set1_epi32(larray[li]); + __m512i v_elements = _mm512_loadu_si512(reinterpret_cast<__m512i *>(rarray + ri)); + + while (true) { + __mmask16 mask = _mm512_cmpeq_epi32_mask(u_elements, v_elements); + if (mask != 0x0000) { + // write back + _mm512_mask_compressstoreu_epi32(cur_back_ptr, mask, u_elements); + cur_back_ptr += _popcnt32(mask); + } + + if (larray[li] > rarray[ri + 15]) { + ri += 16; + if (ri + 15 >= rc) { + break; + } + v_elements = _mm512_loadu_si512(reinterpret_cast<__m512i *>(rarray + ri)); + } else { + li += 1; + if (li >= lc) { + break; + } + u_elements = _mm512_set1_epi32(larray[li]); + } + } + } + } else { + if (li + 3 < lc && ri + 3 < rc) { + __m512i u_elements = _mm512_loadu_si512(reinterpret_cast<__m512i *>(larray + li)); + __m512i u_elements_per = _mm512_permutevar_epi32(st, u_elements); + __m512i v_elements = _mm512_loadu_si512(reinterpret_cast<__m512i *>(rarray + ri)); + __m512i v_elements_per = _mm512_permute4f128_epi32(v_elements, 0b00000000); + + while (true) { + __mmask16 mask = _mm512_cmpeq_epi32_mask(u_elements_per, v_elements_per); + if (mask != 0x0000) { + // write back + _mm512_mask_compressstoreu_epi32(cur_back_ptr, mask, u_elements_per); + cur_back_ptr += _popcnt32(mask); + } + + if (larray[li + 3] > rarray[ri + 3]) { + ri += 4; + if (ri + 3 >= rc) { + break; + } + v_elements = _mm512_loadu_si512(reinterpret_cast<__m512i *>(rarray + ri)); + v_elements_per = _mm512_permute4f128_epi32(v_elements, 0b00000000); + } else if (larray[li + 3] < rarray[ri + 3]) { + li += 4; + if (li + 3 >= lc) { + break; + } + u_elements = _mm512_loadu_si512(reinterpret_cast<__m512i *>(larray + li)); + u_elements_per = _mm512_permutevar_epi32(st, u_elements); + } else { + li += 4; + ri += 4; + if (li + 3 >= lc || ri + 3 >= rc) { + break; + } + u_elements = _mm512_loadu_si512(reinterpret_cast<__m512i *>(larray + li)); + u_elements_per = _mm512_permutevar_epi32(st, u_elements); + v_elements = _mm512_loadu_si512(reinterpret_cast<__m512i *>(rarray + ri)); + v_elements_per = _mm512_permute4f128_epi32(v_elements, 0b00000000); + } + } + } + } + + cn_count = (ui)(cur_back_ptr - cn); + + if (li < lc && ri < rc) { + while (true) { + while (larray[li] < rarray[ri]) { + li += 1; + if (li >= lc) { + return; + } + } + while (larray[li] > rarray[ri]) { + ri += 1; + if (ri >= rc) { + return; + } + } + if (larray[li] == rarray[ri]) { + // write back + cn[cn_count++] = larray[li]; + + li += 1; + ri += 1; + if (li >= lc || ri >= rc) { + return; + } + } + } + } + return; +} + +void ComputeSetIntersection::ComputeCNMergeBasedAVX512( + const V_ID *larray, const ui l_count, const V_ID *rarray, const ui r_count, ui &cn_count) { + cn_count = 0; + + if (l_count == 0 || r_count == 0) return; + + ui lc = l_count; + ui rc = r_count; + + if (lc > rc) { + auto tmp = larray; + larray = rarray; + rarray = tmp; + + ui tmp_count = lc; + lc = rc; + rc = tmp_count; + } + + ui li = 0; + ui ri = 0; + + constexpr int parallelism = 16; + __m512i st = _mm512_set_epi32(3, 3, 3, 3, 2, 2, 2, 2, 1, 1, 1, 1, 0, 0, 0, 0); + __m512i ssecountplus = _mm512_set1_epi32(1); + int cn_countv[parallelism] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; + + __m512i ssecn_countv = _mm512_set1_epi32(0); + auto size1 = (rc) / (lc); + + if (size1 > 2) { + if (li < lc && ri + 15 < rc) { + __m512i u_elements = _mm512_set1_epi32(larray[li]); + __m512i v_elements = _mm512_loadu_si512(reinterpret_cast<__m512i *>(rarray + ri)); + + while (true) { + __mmask16 mask = _mm512_cmpeq_epi32_mask(u_elements, v_elements); + ssecn_countv = _mm512_mask_add_epi32(ssecn_countv, mask, ssecn_countv, ssecountplus); + + if (larray[li] > rarray[ri + 15]) { + ri += 16; + if (ri + 15 >= rc) { + break; + } + v_elements = _mm512_loadu_si512(reinterpret_cast<__m512i *>(rarray + ri)); + } else { + li += 1; + if (li >= lc) { + break; + } + u_elements = _mm512_set1_epi32(larray[li]); + } + } + _mm512_storeu_si512(reinterpret_cast<__m512i *> cn_countv, ssecn_countv); + for (int cn_countvplus : cn_countv) { + cn_count += cn_countvplus; + } + } + } else { + if (li + 3 < lc && ri + 3 < rc) { + __m512i u_elements = _mm512_loadu_si512(reinterpret_cast<__m512i *>(larray + li)); + __m512i u_elements_per = _mm512_permutevar_epi32(st, u_elements); + __m512i v_elements = _mm512_loadu_si512(reinterpret_cast<__m512i *>(rarray + ri)); + __m512i v_elements_per = _mm512_permute4f128_epi32(v_elements, 0b00000000); + + while (true) { + __mmask16 mask = _mm512_cmpeq_epi32_mask(u_elements_per, v_elements_per); + ssecn_countv = _mm512_mask_add_epi32(ssecn_countv, mask, ssecn_countv, ssecountplus); + + if (larray[li + 3] > rarray[ri + 3]) { + ri += 4; + if (ri + 3 >= rc) { + break; + } + v_elements = _mm512_loadu_si512(reinterpret_cast<__m512i *>(rarray + ri)); + v_elements_per = _mm512_permute4f128_epi32(v_elements, 0b00000000); + } else if (larray[li + 3] < rarray[ri + 3]) { + li += 4; + if (li + 3 >= lc) { + break; + } + u_elements = _mm512_loadu_si512(reinterpret_cast<__m512i *>(larray + li)); + u_elements_per = _mm512_permutevar_epi32(st, u_elements); + } else { + li += 4; + ri += 4; + if (li + 3 >= lc || ri + 3 >= rc) { + break; + } + u_elements = _mm512_loadu_si512(reinterpret_cast<__m512i *>(larray + li)); + u_elements_per = _mm512_permutevar_epi32(st, u_elements); + v_elements = _mm512_loadu_si512(reinterpret_cast<__m512i *>(rarray + ri)); + v_elements_per = _mm512_permute4f128_epi32(v_elements, 0b00000000); + } + } + _mm512_storeu_si512(reinterpret_cast<__m512i *> cn_countv, ssecn_countv); + for (int cn_countvplus : cn_countv) { + cn_count += cn_countvplus; + } + } + } + + if (li < lc && ri < rc) { + while (true) { + while (larray[li] < rarray[ri]) { + li += 1; + if (li >= lc) { + return; + } + } + while (larray[li] > rarray[ri]) { + ri += 1; + if (ri >= rc) { + return; + } + } + if (larray[li] == rarray[ri]) { + cn_count += 1; + li += 1; + ri += 1; + if (li >= lc || ri >= rc) { + return; + } + } + } + } +} + +#elif SI == 2 +void ComputeSetIntersection::ComputeCNNaiveStdMerge(const V_ID* larray, + const ui l_count, + const V_ID* rarray, + const ui r_count, + V_ID* cn, + ui& cn_count) { + cn_count = 0; + + if (l_count == 0 || r_count == 0) return; + + ui lc = l_count; + ui rc = r_count; + + if (lc > rc) { + auto tmp = larray; + larray = rarray; + rarray = tmp; + + ui tmp_count = lc; + lc = rc; + rc = tmp_count; + } + + ui li = 0; + ui ri = 0; + + while (true) { + if (larray[li] < rarray[ri]) { + li += 1; + if (li >= lc) { + return; + } + } else if (larray[li] > rarray[ri]) { + ri += 1; + if (ri >= rc) { + return; + } + } else { + cn[cn_count++] = larray[li]; + + li += 1; + ri += 1; + if (li >= lc || ri >= rc) { + return; + } + } + } +} + +void ComputeSetIntersection::ComputeCNNaiveStdMerge( + const V_ID* larray, const ui l_count, const V_ID* rarray, const ui r_count, ui& cn_count) { + cn_count = 0; + + if (l_count == 0 || r_count == 0) return; + + ui lc = l_count; + ui rc = r_count; + + if (lc > rc) { + auto tmp = larray; + larray = rarray; + rarray = tmp; + + ui tmp_count = lc; + lc = rc; + rc = tmp_count; + } + + ui li = 0; + ui ri = 0; + + while (true) { + if (larray[li] < rarray[ri]) { + li += 1; + if (li >= lc) { + return; + } + } else if (larray[li] > rarray[ri]) { + ri += 1; + if (ri >= rc) { + return; + } + } else { + cn_count += 1; + li += 1; + ri += 1; + if (li >= lc || ri >= rc) { + return; + } + } + } +} + +void ComputeSetIntersection::ComputeCNGalloping(const V_ID* larray, + const ui l_count, + const V_ID* rarray, + const ui r_count, + V_ID* cn, + ui& cn_count) { + ui lc = l_count; + ui rc = r_count; + cn_count = 0; + if (lc == 0 || rc == 0) return; + + if (lc > rc) { + auto tmp = larray; + larray = rarray; + rarray = tmp; + + ui tmp_count = lc; + lc = rc; + rc = tmp_count; + } + + ui li = 0; + ui ri = 0; + + while (true) { + while (larray[li] < rarray[ri]) { + li += 1; + if (li >= lc) { + return; + } + } + + ri = GallopingSearch(rarray, ri, rc, larray[li]); + if (ri >= rc) { + return; + } + + if (larray[li] == rarray[ri]) { + cn[cn_count++] = larray[li]; + + li += 1; + ri += 1; + if (li >= lc || ri >= rc) { + return; + } + } + } +} + +void ComputeSetIntersection::ComputeCNGalloping( + const V_ID* larray, const ui l_count, const V_ID* rarray, const ui r_count, ui& cn_count) { + ui lc = l_count; + ui rc = r_count; + cn_count = 0; + if (lc == 0 || rc == 0) return; + + if (lc > rc) { + auto tmp = larray; + larray = rarray; + rarray = tmp; + + ui tmp_count = lc; + lc = rc; + rc = tmp_count; + } + + ui li = 0; + ui ri = 0; + + while (true) { + while (larray[li] < rarray[ri]) { + li += 1; + if (li >= lc) { + return; + } + } + + ri = GallopingSearch(rarray, ri, rc, larray[li]); + if (ri >= rc) { + return; + } + + if (larray[li] == rarray[ri]) { + cn_count += 1; + + li += 1; + ri += 1; + if (li >= lc || ri >= rc) { + return; + } + } + } +} + +ui ComputeSetIntersection::GallopingSearch(const V_ID* src, + const ui begin, + const ui end, + const ui target) { + if (src[end - 1] < target) { + return end; + } + // galloping + if (src[begin] >= target) { + return begin; + } + if (src[begin + 1] >= target) { + return begin + 1; + } + if (src[begin + 2] >= target) { + return begin + 2; + } + + ui jump_idx = 4; + ui offset_beg = begin; + while (true) { + ui peek_idx = offset_beg + jump_idx; + if (peek_idx >= end) { + return BinarySearch(src, (jump_idx >> 1) + offset_beg + 1, end, target); + } + if (src[peek_idx] < target) { + jump_idx <<= 1; + } else { + return src[peek_idx] == target + ? peek_idx + : BinarySearch(src, (jump_idx >> 1) + offset_beg + 1, peek_idx + 1, target); + } + } +} + +ui ComputeSetIntersection::BinarySearch(const V_ID* src, + const ui begin, + const ui end, + const ui target) { + int offset_begin = begin; + int offset_end = end; + while (offset_end - offset_begin >= 16) { + auto mid = static_cast((static_cast(offset_begin) + offset_end) / 2); + _mm_prefetch(reinterpret_cast(&src[(mid + 1 + offset_end) / 2]), _MM_HINT_T0); + _mm_prefetch(reinterpret_cast(&src[(mid - 1 + offset_begin) / 2]), _MM_HINT_T0); + if (src[mid] == target) { + return mid; + } else if (src[mid] < target) { + offset_begin = mid + 1; + } else { + offset_end = mid; + } + } + + // linear search fallback + for (auto offset = offset_begin; offset < offset_end; ++offset) { + if (src[offset] >= target) { + return static_cast(offset); + } + } + + return static_cast(offset_end); +} +#endif diff --git a/src/graph/subgraph_provenance/computesetintersection.h b/src/graph/subgraph_provenance/computesetintersection.h new file mode 100644 index 00000000000..db7c70a1f78 --- /dev/null +++ b/src/graph/subgraph_provenance/computesetintersection.h @@ -0,0 +1,83 @@ +// Copyright [2022] +// +// Created by ssunah on 11/30/17. +// + +#ifndef SUBGRAPHMATCHING_COMPUTE_SET_INTERSECTION_H +#define SUBGRAPHMATCHING_COMPUTE_SET_INTERSECTION_H + +#include +#include + +#include "config.h" +#include "graph.h" + +/* + * Because the set intersection is designed for computing common neighbors, the target is uieger. + */ + +class ComputeSetIntersection { + public: +#if HYBRID == 0 + static size_t galloping_cnt_; + static size_t merge_cnt_; +#endif + + static void ComputeCandidates( + const V_ID* larray, ui l_count, const V_ID* rarray, ui r_count, V_ID* cn, ui& cn_count); + static void ComputeCandidates( + const V_ID* larray, ui l_count, const V_ID* rarray, ui r_count, ui& cn_count); + +#if SI == 0 + static void ComputeCNGallopingAVX2( + const V_ID* larray, ui l_count, const V_ID* rarray, ui r_count, V_ID* cn, ui& cn_count); + static void ComputeCNGallopingAVX2( + const V_ID* larray, ui l_count, const V_ID* rarray, ui r_count, ui& cn_count); + + static void ComputeCNMergeBasedAVX2( + const V_ID* larray, ui l_count, const V_ID* rarray, ui r_count, V_ID* cn, ui& cn_count); + static void ComputeCNMergeBasedAVX2( + const V_ID* larray, ui l_count, const V_ID* rarray, ui r_count, ui& cn_count); + static const ui BinarySearchForGallopingSearchAVX2(const V_ID* array, + ui offset_beg, + ui offset_end, + ui val); + static const ui GallopingSearchAVX2(const V_ID* array, ui offset_beg, ui offset_end, ui val); +#elif SI == 1 + + static void ComputeCNGallopingAVX512(const V_ID* larray, + const ui l_count, + const V_ID* rarray, + const ui r_count, + V_ID* cn, + ui& cn_count); + static void ComputeCNGallopingAVX512( + const V_ID* larray, const ui l_count, const V_ID* rarray, const ui r_count, ui& cn_count); + + static void ComputeCNMergeBasedAVX512(const V_ID* larray, + const ui l_count, + const V_ID* rarray, + const ui r_count, + V_ID* cn, + ui& cn_count); + static void ComputeCNMergeBasedAVX512( + const V_ID* larray, const ui l_count, const V_ID* rarray, const ui r_count, ui& cn_count); + +#elif SI == 2 + + static void ComputeCNNaiveStdMerge( + const V_ID* larray, ui l_count, const V_ID* rarray, ui r_count, V_ID* cn, ui& cn_count); + static void ComputeCNNaiveStdMerge( + const V_ID* larray, ui l_count, const V_ID* rarray, ui r_count, ui& cn_count); + + static void ComputeCNGalloping( + const V_ID* larray, ui l_count, const V_ID* rarray, ui r_count, V_ID* cn, ui& cn_count); + static void ComputeCNGalloping( + const V_ID* larray, ui l_count, const V_ID* rarray, ui r_count, ui& cn_count); + static ui GallopingSearch(const V_ID* src, ui begin, ui end, ui target); + static ui BinarySearch(const V_ID* src, ui begin, ui end, ui target); + +#endif +}; + +#endif // FSE_COMPUTESETINTERSECTION_H diff --git a/src/graph/subgraph_provenance/config.h b/src/graph/subgraph_provenance/config.h new file mode 100755 index 00000000000..ac99606d305 --- /dev/null +++ b/src/graph/subgraph_provenance/config.h @@ -0,0 +1,58 @@ +// Copyright [2022] +// +// Created by ssunah on 6/22/18. +// + +#ifndef SUBGRAPHMATCHING_CONFIG_H +#define SUBGRAPHMATCHING_CONFIG_H + +/** + * Set the maximum size of a query graph. By default, we set the value as 64. + */ +#define MAXIMUM_QUERY_GRAPH_SIZE 64 + +/** + * Setting the value as 1 is to (1) enable the neighbor label frequency filter (i.e., NLF filter); + * and (2) enable to check the existence of an edge with the label information. The cost is to (1) + * build an unordered_map for each vertex to store the frequency of the labels of its neighbor; and + * (2) build the label neighbor offset. If the memory can hold the extra memory cost, then enable + * this feature to boost the performance. Otherwise, disable it by setting this value as 0. + */ +#define OPTIMIZED_LABELED_GRAPH 1 + +/** + * Define SPECTRUM to enable spectrum analysis. + */ +// #define SPECTRUM + +/** + * Set intersection method. + * 0: Hybrid method; 1: Merge based set intersections. + */ +#define HYBRID 0 + +/** + * Accelerate set intersection with SIMD instructions. + * 0: AVX2; 1: AVX512; 2: Basic; + */ +#define SI 2 + +/** + * Define ENABLE_QFLITER to enable QFliter set intersection method. + */ +// #define ENABLE_QFLITER 1 + +/** + * Define ENABLE_FAILING_SET to enable the failing set pruning set intersection method. + */ +// #define ENABLE_FAILING_SET + +/** + * Enable collection the distribution of the results in the data graph. + */ + +// #define DISTRIBUTION + +#define PRINT_SEPARATOR "------------------------------" + +#endif // SUBGRAPHMATCHING_CONFIG_H diff --git a/src/graph/subgraph_provenance/graph.cpp b/src/graph/subgraph_provenance/graph.cpp new file mode 100644 index 00000000000..c9df47c8081 --- /dev/null +++ b/src/graph/subgraph_provenance/graph.cpp @@ -0,0 +1,195 @@ +// Copyright [2022] +#include "graph.h" + +#include +#include +#include +#include + +void Graph::printGraph() { + std::cout << "|V|: " << v_count << ", |E|: " << e_count << ", |L|: " << l_count << std::endl; + std::cout << "Max Degree: " << max_degree << ", Max Label Frequency: " << max_label_frequency + << std::endl; + printf("Neighbourhood: "); + // for (int i = 0; i < v_count; i ++) { + // printf("%d, ", offsets[i]); + // + // } + printf("\n"); + for (ui i = 0; i < v_count; i++) { + printf("V_ID: %d, offsets: %d:", i, offsets[i]); + for (ui j = offsets[i]; j < offsets[i + 1]; j++) { + printf("%d, ", neighbors[j]); + std::cout << " (" << getVertexLabel(neighbors[j]) << ") "; + } + printf("\n"); + } + + printf("\n"); +} + +void Graph::BuildReverseIndex() { + reverse_index = new ui[v_count]; + reverse_index_offsets = new ui[l_count + 1]; + reverse_index_offsets[0] = 0; + + ui total = 0; + for (ui i = 0; i < l_count; ++i) { + reverse_index_offsets[i + 1] = total; + total += labels_frequency[i]; + } + + for (ui i = 0; i < v_count; ++i) { + L_ID label = labels[i]; + reverse_index[reverse_index_offsets[label + 1]++] = i; + } +} + +void Graph::BuildNLCF() { // neighbors count + // Map from [label_id] -> count of this label count value. + nlf = new std::unordered_map[v_count]; + nl = new std::unordered_map>[v_count]; + for (ui i = 0; i < v_count; ++i) { + ui count; + V_ID *neighbors_tmp = getVertexNeighbors(i, count); + std::vector v_t; + // count is the number of the offsets[id + 1] - offsets[id]; which is the number of neighbours. + for (ui j = 0; j < count; ++j) { + V_ID u = neighbors_tmp[j]; + L_ID label = getVertexLabel(u); + // If not found, this should be 0; + if (nlf[i].find(label) == nlf[i].end()) { + nlf[i][label] = 0; + nl[i].emplace(label, std::vector()); + } + nlf[i][label] += 1; // Add one after each count. + nl[i][label].push_back(u); + } + for (auto &iter : nl[i]) { + sort(iter.second.begin(), iter.second.end()); + } + } + return; +} + +void Graph::loadGraphFromExecutor(unsigned int v_c, + unsigned int l_c, + unsigned int e_c, + unsigned int *off, + unsigned int *nei, + unsigned int *lab) { + this->v_count = v_c; + this->l_count = l_c; + this->e_count = e_c; + this->offsets = new ui[v_c + 1]; + this->neighbors = new V_ID[e_c * 2]; + this->labels = new L_ID[v_c]; + + for (ui i = 0; i < v_c + 1; ++i) { + this->offsets[i] = off[i]; + } + + for (ui i = 0; i < e_c; ++i) { + this->neighbors[i] = nei[i]; + } + + L_ID max_label_id = 0; + for (ui i = 0; i < v_count; ++i) { + this->labels[i] = lab[i]; + if (labels_frequency.find(lab[i]) == labels_frequency.end()) { + labels_frequency[lab[i]] = 0; + if (lab[i] > max_label_id) { + max_label_id = lab[i]; + } + } + } + // Initialize label count; + if (labels_frequency.size() > max_label_id + 1) { + l_count = labels_frequency.size(); + } else { + l_count = max_label_id + 1; + } + + BuildReverseIndex(); + BuildNLCF(); + return; +} + +void Graph::loadGraph(const std::string &file_path) { + std::ifstream graphFile(file_path); + + if (!graphFile.is_open()) { + std::cout << "Error opening " << file_path << " ." << std::endl; + exit(-1); + } + + char type; + graphFile >> type >> v_count >> e_count; + offsets = new ui[v_count + 1]; + offsets[0] = 0; + + neighbors = new V_ID[e_count * 2]; + labels = new L_ID[v_count]; + l_count = 0; + max_degree = 0; + + L_ID max_label_id = 0; + std::vector neighbors_offsets(v_count, 0); + while (graphFile >> type) { + if (type == 'v') { // Read Vertex, build index of id->label, records its degree. + V_ID id; + L_ID label; + ui degree; + graphFile >> id >> label >> degree; + + labels[id] = label; + offsets[id + 1] = offsets[id] + degree; + if (degree > max_degree) { + max_degree = degree; + } + + if (labels_frequency.find(label) == labels_frequency.end()) { + labels_frequency[label] = 0; + if (label > max_label_id) { + max_label_id = label; + } + } + labels_frequency[label] += 1; + } else if (type == 'e') { // Read edge. + V_ID src; + V_ID dst; + graphFile >> src >> dst; + + ui offset_tmp = offsets[src] + neighbors_offsets[src]; + neighbors[offset_tmp] = dst; + + offset_tmp = offsets[dst] + neighbors_offsets[dst]; + neighbors[offset_tmp] = src; + + neighbors_offsets[src] += 1; + neighbors_offsets[dst] += 1; + } + } + + graphFile.close(); + + // Initialize label count; + if (labels_frequency.size() > max_label_id + 1) { + l_count = labels_frequency.size(); + } else { + l_count = max_label_id + 1; + } + + // Recheck the max_label_id; + // std::cout"Test All: " << std::endl; + + for (auto item : labels_frequency) { + // std::coutitem.second << " "; + if (item.second > max_label_frequency) { + max_label_frequency = item.second; + } + } + + BuildReverseIndex(); + BuildNLCF(); +} diff --git a/src/graph/subgraph_provenance/graph.h b/src/graph/subgraph_provenance/graph.h new file mode 100644 index 00000000000..885e365318c --- /dev/null +++ b/src/graph/subgraph_provenance/graph.h @@ -0,0 +1,148 @@ +// Copyright [2022] +#ifndef CECI_GRAPH_H +#define CECI_GRAPH_H +#include +#include +#include + +#include "trees.h" + +class Graph { + private: + ui v_count; // vertices count + ui e_count; // edges count + ui l_count; // Label count + ui max_degree; + ui max_label_frequency; + + ui* offsets; + + V_ID* neighbors; + L_ID* labels; + + ui* reverse_index_offsets; + ui* reverse_index; + + std::unordered_map labels_frequency; + // ui* labels_offsets; + std::unordered_map* nlf; + std::unordered_map>* nl; + + private: + void BuildReverseIndex(); + void BuildNLCF(); + void BuildLabelOffset(); + + public: + Graph() { + v_count = 0; // vertices count + e_count = 0; // edges count + l_count = 0; // Label count + max_degree = 0; + max_label_frequency = 0; + + offsets = nullptr; + reverse_index_offsets = nullptr; + reverse_index = nullptr; + neighbors = nullptr; + labels = nullptr; + + labels_frequency.clear(); + // labels_offsets = nullptr; + nlf = nullptr; + nl = nullptr; + } + ~Graph() { + delete[] offsets; + delete[] neighbors; + delete[] reverse_index_offsets; + delete[] reverse_index; + delete[] labels; + // delete[] labels_offsets; + delete[] nlf; + delete[] nl; + } + + public: + void loadGraph(const std::string& file_path); + void loadGraphFromExecutor(unsigned int v_count, + unsigned int l_count, + unsigned int e_count, + unsigned int* offset, + unsigned int* neighbors, + unsigned int* labels); + + void printGraph(); + + ui getLabelsCount() { + return l_count; + } + + ui getGraphMaxLabelFrequency() { + return max_label_frequency; + } + + ui* getVerticesByLabel(const L_ID id, ui& count) const { + count = reverse_index_offsets[id + 1] - reverse_index_offsets[id]; + return reverse_index + reverse_index_offsets[id]; + } + + ui getVertexDegree(const V_ID id) const { + return offsets[id + 1] - offsets[id]; + } + + ui getVerticesCount() { + return v_count; + } + + ui getEdgesCount() { + return e_count; + } + + ui getMaxDegree() { + return max_degree; + } + + L_ID getVertexLabel(const V_ID id) { + return labels[id]; + } + + ui* getVertexNeighbors(const V_ID id, ui& count) { + count = offsets[id + 1] - offsets[id]; + return neighbors + offsets[id]; + } + + bool checkEdgeExistence(V_ID u, V_ID v) { + if (getVertexDegree(u) < getVertexDegree(v)) { + std::swap(u, v); + } + + ui count = 0; + neighbors = getVertexNeighbors(v, count); + + int begin = 0; + int end = count - 1; + while (begin <= end) { + int mid = begin + ((end - begin) >> 1); + if (neighbors[mid] == u) { + return true; + } else if (neighbors[mid] > u) { + end = mid - 1; + } else { + begin = mid + 1; + } + } + + return false; + } + + std::unordered_map* getVertexNLF(V_ID i) { + return nlf + i; + } + + std::unordered_map>* getVertexNL(V_ID i) { + return nl + i; + } +}; + +#endif diff --git a/src/graph/subgraph_provenance/run.bash b/src/graph/subgraph_provenance/run.bash new file mode 100755 index 00000000000..207b05d6dad --- /dev/null +++ b/src/graph/subgraph_provenance/run.bash @@ -0,0 +1,27 @@ +#!/bin/bash +type=3 +distributionType=0 +testCount=5 +startPower=22 +stopPower=29 +N=30 +#k=33554431 +beta=2 +declare -a arr=("result.out" "baseline_filter_shuffle" "baseline_filter" "baseline" "U_K16" "U_K31" "U_K64" "U_K128" "U_K256" "U_K512") +rm output.txt +#declare -a k=(1 2 4 8 16 31 64 128 256 512) +#for (( beta=2; beta<3; beta=beta+1 )) +for (( k=1; k<=1; k=k+1 )) +do + for (( N=1; N<=200; N=N+1 )) + do + ./ceci dataset/youtube/query_graph/query_dense_8_$N.graph dataset/youtube/data_graph/youtube.graph #>> ${arr[0]} + echo -n "Finished" + echo $N + #./topk.bin 29 33554431 8 2 + done +done +echo -n "Finihed processing for a k " +printf "\n" +#diff <(grep ':' result.out | sed 's/^.*://') <(grep ':' truth.out | sed 's/^.*://') + diff --git a/src/graph/subgraph_provenance/subgraph.cpp b/src/graph/subgraph_provenance/subgraph.cpp new file mode 100644 index 00000000000..e1aa75acf98 --- /dev/null +++ b/src/graph/subgraph_provenance/subgraph.cpp @@ -0,0 +1,4574 @@ +// Copyright [2022] +#include "subgraph.h" + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "computesetintersection.h" +#include "trees.h" +#define INVALID_VERTEX_ID 99999 +#undef DEBUG +#define DEBUG 0 +#define Q_LIMIT (((int64_t)1) << 31) + +V_ID InitialStartVertex(Graph *data_graph, Graph *query_graph) { + // Using method of finding minimum scores = count/degree; + double min_score = data_graph->getVerticesCount(); + V_ID start_vertex = 0; + + for (ui i = 0; i < query_graph->getVerticesCount(); ++i) { + ui degree = query_graph->getVertexDegree(i); + ui count = 0; + // Compare the NLF with data graoh and query graph. + ZFComputeNLF(data_graph, query_graph, i, count); + double cur_score = count / static_cast(degree); + if (cur_score < min_score && count > 1) { + min_score = cur_score; + start_vertex = i; + } + } + return start_vertex; +} + +void bfs2(Graph *graph, + V_ID root_v, + TreeNode *&tree, + V_ID *&order, + __attribute__((unused)) V_ID *&provenance) { + // How many vertices. + ui count = graph->getVerticesCount(); + std::queue queue; + std::vector visited(count, false); + // Initialize the tree + tree = new TreeNode[count]; + for (ui i = 0; i < count; ++i) { + tree[i].initialize(count); + } + // This is + order = new V_ID[count]; + ui visited_count = 0; // how many nodes have been visited. + queue.push(root_v); // push the first root node into the BFS queue. + visited[root_v] = true; + tree[root_v].level = 0; + tree[root_v].id = root_v; // node id. + order[visited_count++] = root_v; + + ui u_nbrs_count_1; + // How many neighbors of the node + // get the neighbors informations + V_ID *u_nbrs_1 = graph->getVertexNeighbors(root_v, u_nbrs_count_1); + std::vector> tmp_2; + for (ui i = 0; i < u_nbrs_count_1; ++i) { + V_ID u_nbr = u_nbrs_1[i]; + ui u_nbrs_count; + __attribute__((unused)) V_ID *u_nbrs = graph->getVertexNeighbors(u_nbr, u_nbrs_count); + tmp_2.emplace_back(u_nbrs_count, u_nbr); + } + + // Second node -> + sort(tmp_2.begin(), tmp_2.end()); + ui u_nbr = tmp_2[tmp_2.size() - 1].second; + + if (!visited[u_nbr]) { + visited[u_nbr] = true; + tree[u_nbr].id = u_nbr; + tree[u_nbr].parent = root_v; + tree[u_nbr].level = tree[root_v].level + 1; + tree[root_v].children[tree[root_v].children_count++] = u_nbr; + order[visited_count++] = u_nbr; + } + // Third node -> + if (count > 2) { + u_nbr = tmp_2[tmp_2.size() - 2].second; + + if (!visited[u_nbr]) { + visited[u_nbr] = true; + tree[u_nbr].id = u_nbr; + tree[u_nbr].parent = root_v; + tree[u_nbr].level = tree[root_v].level + 1; + tree[root_v].children[tree[root_v].children_count++] = u_nbr; + order[visited_count++] = u_nbr; + } + } + bool next = true; + while (next) { + std::vector> tmp; + next = false; + for (ui i = 0; i < count; ++i) { + if (visited[i] == false) { + next = true; + ui u_nbrs_count; + V_ID *u_nbrs = graph->getVertexNeighbors(i, u_nbrs_count); + ui u_count = 0; + for (ui j = 0; j < u_nbrs_count; j++) { + if (visited[u_nbrs[j]] == true) { + u_count += 1; + } + } + tmp.emplace_back(u_count, i); + } + } + if (next) { + sort(tmp.begin(), tmp.end()); + ui u_t = tmp[tmp.size() - 1].second; + order[visited_count++] = u_t; + visited[u_t] = true; + ui u_nbrs_count; + V_ID *u_nbrs = graph->getVertexNeighbors(u_t, u_nbrs_count); + // ui u_count = 0; + bool find_child = false; + for (ui i = 0; i < visited_count; i++) { + for (ui j = 0; j < u_nbrs_count; j++) { + if (order[i] == u_nbrs[j]) { + find_child = true; + tree[u_t].id = u_t; + tree[u_t].parent = order[i]; + tree[u_t].level = tree[order[i]].level + 1; + tree[order[i]].children[tree[order[i]].children_count++] = u_t; + break; + } + } + if (find_child) { + break; + } + } + } + } +} + +void generateValidCandidates(Graph *data_graph, + ui depth, + ui *embedding, + ui *idx_count, + ui **valid_candidate, + bool *visited_vertices, + ui **bn, + ui *bn_cnt, + ui *order, + ui **candidates, + ui *candidates_count) { + V_ID u = order[depth]; + idx_count[depth] = 0; + for (ui i = 0; i < candidates_count[u]; ++i) { + V_ID v = candidates[u][i]; + + if (!visited_vertices[v]) { + bool valid = true; + + for (ui j = 0; j < bn_cnt[depth]; ++j) { + V_ID u_nbr = bn[depth][j]; + V_ID u_nbr_v = embedding[u_nbr]; + + if (!data_graph->checkEdgeExistence(v, u_nbr_v)) { + valid = false; + break; + } + } + + if (valid) { + valid_candidate[depth][idx_count[depth]++] = v; + } + } + } +} + +int64_t exploreGraphQLStyle(Graph *data_graph, + Graph *query_graph, + ui **candidates, + ui *candidates_count, + ui *order, + size_t output_limit_num, + int64_t &call_count) { + uint64_t embedding_cnt = 0; + int cur_depth = 0; + int max_depth = query_graph->getVerticesCount(); + V_ID start_vertex = order[0]; + + // Generate the bn. + ui **bn; + ui *bn_count; + + bn = new ui *[max_depth]; + for (int i = 0; i < max_depth; ++i) { + bn[i] = new ui[max_depth]; + } + + bn_count = new ui[max_depth]; + std::fill(bn_count, bn_count + max_depth, 0); + + std::vector visited_query_vertices(max_depth, false); + visited_query_vertices[start_vertex] = true; + for (int i = 1; i < max_depth; ++i) { + V_ID cur_vertex = order[i]; + ui nbr_cnt; + V_ID *nbrs = query_graph->getVertexNeighbors(cur_vertex, nbr_cnt); + + for (ui j = 0; j < nbr_cnt; ++j) { + V_ID nbr = nbrs[j]; + + if (visited_query_vertices[nbr]) { + bn[i][bn_count[i]++] = nbr; + } + } + + visited_query_vertices[cur_vertex] = true; + } + + // Allocate the memory buffer. + ui *idx; + ui *idx_count; + ui *embedding; + V_ID **valid_candidate; + bool *visited_vertices; + + idx = new ui[max_depth]; + idx_count = new ui[max_depth]; + embedding = new ui[max_depth]; + visited_vertices = new bool[data_graph->getVerticesCount()]; + std::fill(visited_vertices, visited_vertices + data_graph->getVerticesCount(), false); + valid_candidate = new ui *[max_depth]; + + for (int i = 0; i < max_depth; ++i) { + V_ID cur_vertex = order[i]; + ui max_candidate_count = candidates_count[cur_vertex]; + valid_candidate[i] = new V_ID[max_candidate_count]; + } + + idx[cur_depth] = 0; + idx_count[cur_depth] = candidates_count[start_vertex]; + std::copy(candidates[start_vertex], + candidates[start_vertex] + candidates_count[start_vertex], + valid_candidate[cur_depth]); + + while (true) { + while (idx[cur_depth] < idx_count[cur_depth]) { + V_ID u = order[cur_depth]; + V_ID v = valid_candidate[cur_depth][idx[cur_depth]]; + embedding[u] = v; + visited_vertices[v] = true; + idx[cur_depth] += 1; + + if (cur_depth == max_depth - 1) { + embedding_cnt += 1; + visited_vertices[v] = false; + if (embedding_cnt >= output_limit_num) { + goto EXIT; + } + } else { + call_count += 1; + cur_depth += 1; + idx[cur_depth] = 0; + generateValidCandidates(data_graph, + cur_depth, + embedding, + idx_count, + valid_candidate, + visited_vertices, + bn, + bn_count, + order, + candidates, + candidates_count); + } + } + + cur_depth -= 1; + if (cur_depth < 0) + break; + else + visited_vertices[embedding[order[cur_depth]]] = false; + } + +// Release the buffer. +EXIT: + delete[] bn_count; + delete[] idx; + delete[] idx_count; + delete[] embedding; + delete[] visited_vertices; + for (int i = 0; i < max_depth; ++i) { + delete[] bn[i]; + delete[] valid_candidate[i]; + } + delete[] bn; + delete[] valid_candidate; + return embedding_cnt; +} + +void bfs(Graph *graph, + V_ID root_v, + TreeNode *&tree, + V_ID *&order, + __attribute__((unused)) V_ID *&provenance) { + // How many vertices. + ui count = graph->getVerticesCount(); + std::queue queue; + std::vector visited(count, false); + // Initialize the tree + tree = new TreeNode[count]; + for (ui i = 0; i < count; ++i) { + tree[i].initialize(count); + } + // This is + order = new V_ID[count]; + ui visited_count = 0; // how many nodes have been visited. + queue.push(root_v); // push the first root node into the BFS queue. + visited[root_v] = true; + tree[root_v].level = 0; + tree[root_v].id = root_v; // node id. + while (!queue.empty()) { + V_ID u = queue.front(); + queue.pop(); + order[visited_count++] = u; // records this as the next order. + ui u_nbrs_count; + // How many neighbors of the node + // get the neighbors informations + V_ID *u_nbrs = graph->getVertexNeighbors(u, u_nbrs_count); + for (ui i = 0; i < u_nbrs_count; ++i) { + V_ID u_nbr = u_nbrs[i]; + // for each unvisited neighbors, if unvisited: + // (1) push in th query + // (2) build it in the tree + // (3) record it's parent-id + // (4) the level should add 1 + // (5) the parent node will add this node as child. + if (!visited[u_nbr]) { + queue.push(u_nbr); + visited[u_nbr] = true; + tree[u_nbr].id = u_nbr; + tree[u_nbr].parent = u; + tree[u_nbr].level = tree[u].level + 1; + tree[u].children[tree[u].children_count++] = u_nbr; + } + } + } +} + +void Insertion(V_ID u, + V_ID &tmp_first, + V_ID &tmp_second, + std::unordered_map>> &intersetion) { + /*if (tmp_first > tmp_second) { + V_ID tmp = tmp_first; + tmp_first = tmp_second; + tmp_second = tmp; + }*/ + std::pair p1(tmp_first, tmp_second); + // change to the vector next time. + if (intersetion.find(u) == intersetion.end()) { + std::vector> q; + q.push_back(p1); + intersetion.insert(std::pair>>(u, q)); + } else { + intersetion[u].push_back(p1); + } +} + +V_ID ParentNode(V_ID first, V_ID second, TreeNode *&tree) { + std::vector frontier; + frontier.push_back(first); + // std::cout <<"(" << first << ","<< second << ")"<< std::endl; + while (tree[first].parent != 99999) { + first = tree[first].parent; + frontier.push_back(first); + } + // std::cout <<"2: and size: " << frontier.size() << " " << second << std::endl; + // std::cout <second-> " << std::endl; + return second; + } + while (tree[second].parent != 99999) { + second = tree[second].parent; + // std::cout <<"parent-> " << second << std::endl; + if (std::find(frontier.begin(), frontier.end(), second) != frontier.end()) { + // std::cout <<"Found->second->parent-> " << std::endl; + return second; + } + } + // std::cout <<"End of ParentNode" << std::endl; + return 99999; +} + +void add_provenance(std::unordered_map> &query_provenance, + V_ID &query_node, + V_ID &provenance_node) { + if (query_provenance.find(query_node) != query_provenance.end()) { + // insert new value and key in the query_provenance. + std::vector::iterator iter; + iter = find( + query_provenance[query_node].begin(), query_provenance[query_node].end(), provenance_node); + if (iter == query_provenance[query_node].end()) { + query_provenance[query_node].push_back(provenance_node); + } + } else { + std::unordered_map>::iterator iter; + iter = query_provenance.begin(); + std::vector node_pro; + node_pro.push_back(provenance_node); + query_provenance.insert(iter, std::pair>(query_node, node_pro)); + } +} + +int64_t enumeration_ress_bk( + bool *visited, + int64_t &res_all, + std::vector>> &P_Candidates, + std::vector>> &NTE_Candidates, + V_ID *order, + TreeNode *tree, + V_ID *res, + ui current_order, + ui &query_count, + Graph *query_graph, + Graph *data_graph, + std::vector &order_index, + V_ID *connection, + V_ID *offset, + ui **candidates_2, + ui **parent_offset, + ui *candidates_l, + ui *parent_l) { + V_ID u = order[current_order]; + V_ID v_f = res[order_index[tree[u].parent]]; + std::vector local_c = P_Candidates[u][v_f]; + int64_t total = 0; + bool over_all; + if (current_order == query_count) { + for (unsigned int j : local_c) { + if (visited[j]) { + continue; + } + over_all = true; + res_all += 1; + ui data_nbrs_count_1; + V_ID *data_nbrs_1 = data_graph->getVertexNeighbors(j, data_nbrs_count_1); + // std::unordered_map>* v_nl = data_graph->getVertexNL(local_c[j]); + for (ui k = offset[current_order - 1]; k < offset[current_order]; k++) { + // L_ID l = query_graph->getVertexLabel(order[connection[k]]); + if (std::find(data_nbrs_1, data_nbrs_1 + data_nbrs_count_1, res[connection[k]]) == + data_nbrs_1 + data_nbrs_count_1) { + over_all = false; + break; + } + } + if (over_all == true) { + total += 1; + } + } + + return total; + } else { + for (unsigned int j : local_c) { + // V_ID v = local_c[j]; + if (visited[j]) { + continue; + } + over_all = true; + res_all += 1; + ui data_nbrs_count_1; + V_ID *data_nbrs_1 = data_graph->getVertexNeighbors(j, data_nbrs_count_1); + + for (ui k = offset[current_order - 1]; k < offset[current_order]; k++) { + if (std::find(data_nbrs_1, data_nbrs_1 + data_nbrs_count_1, res[connection[k]]) == + data_nbrs_1 + data_nbrs_count_1) { + over_all = false; + break; + } + } + if (over_all == true) { + visited[j] = true; + res[current_order] = j; + total += enumeration_ress_bk(visited, + res_all, + P_Candidates, + NTE_Candidates, + order, + tree, + res, + current_order + 1, + query_count, + query_graph, + data_graph, + order_index, + connection, + offset, + candidates_2, + parent_offset, + candidates_l, + parent_l); + visited[j] = false; + } + } + return total; + } +} + +void visit_pre_3(__attribute__((unused)) bool *visited, + ui **candidates_2, + int64_t **parent_offset, + __attribute__((unused)) int64_t *candidates_l, + V_ID ¤t_order, + __attribute__((unused)) V_ID &p_order, + int64_t &k, + V_ID *res, + __attribute__((unused)) bool &run) { + int64_t j = k; + res[current_order] = candidates_2[current_order][j]; + + visited[candidates_2[current_order][j]] = true; + + for (int i = current_order; i > 0; i--) { + j = parent_offset[i][j]; + visited[candidates_2[i - 1][j]] = true; + res[i - 1] = candidates_2[i - 1][j]; + } +} + +void visit_pre(__attribute__((unused)) bool *visited, + ui **candidates_2, + int64_t **parent_offset, + __attribute__((unused)) int64_t *candidates_l, + V_ID ¤t_order, + __attribute__((unused)) V_ID &p_order, + int64_t &k, + V_ID *res) { + int64_t j = k; + visited[candidates_2[current_order - 1][j]] = true; + res[current_order - 1] = candidates_2[current_order - 1][j]; + + for (int i = current_order - 1; i > 0; i--) { + j = parent_offset[i][j]; + visited[candidates_2[i - 1][j]] = true; + res[i - 1] = candidates_2[i - 1][j]; + } +} + +void visit_pre_2(__attribute__((unused)) bool *visited, + ui **candidates_2, + int64_t **parent_offset, + __attribute__((unused)) int64_t *candidates_l, + V_ID ¤t_order, + __attribute__((unused)) V_ID &p_order, + int64_t &k, + V_ID *res) { + int64_t j = k; + res[current_order - 1] = candidates_2[current_order - 1][j]; + + for (int i = current_order - 1; i > 0; i--) { + j = parent_offset[i][j]; + res[i - 1] = candidates_2[i - 1][j]; + } +} + +void visit_can(bool *visited, + ui **candidates_2, + int64_t **parent_offset, + __attribute__((unused)) int64_t *candidates_l, + V_ID ¤t_order, + __attribute__((unused)) V_ID &p_order, + int64_t &k) { + int64_t j = k; + visited[candidates_2[current_order - 1][j]] = false; + + for (int i = current_order - 1; i > 0; i--) { + j = parent_offset[i][j]; + visited[candidates_2[i - 1][j]] = false; + } +} + +ui &find_v_f(__attribute__((unused)) ui **candidates_2, + __attribute__((unused)) int64_t **parent_offset, + __attribute__((unused)) int64_t *candidates_l, + V_ID ¤t_order, + __attribute__((unused)) V_ID &p_order, + int64_t &k) { + if (current_order - 1 == p_order) { + return candidates_2[p_order][k]; + } else { + int64_t j = k; + for (ui i = current_order - 1; i > p_order; i--) { + j = parent_offset[i][j]; + } + return candidates_2[p_order][j]; + } +} +void find_children_range(int64_t **children_1, + int64_t *candidates_l, + V_ID &ord_count, + V_ID ¤t_count, + int64_t &start, + int64_t &end, + int64_t &p) { + // start and end. + // std::cout <<"###sss" << std::endl; + if (current_count + 1 == ord_count) { + start = p + 0; + end = p + 1; + } else { + start = children_1[current_count][p]; + if (p >= candidates_l[current_count]) { + end = start; + return; + } + end = children_1[current_count][p + 1]; + for (ui i = current_count + 1; i < ord_count - 1; i++) { + start = children_1[i][start]; + if (end < candidates_l[i]) { + end = children_1[i][end]; + } else { + end = children_1[i][candidates_l[i]]; + } + } + } +} +int64_t morphism_next(bool *visited_j, + bool *&visited, + int64_t **children_offset_2, + int64_t *candidates_l_2, + ui q_add, + int64_t &j, + ui order_count_2, + std::vector> &i_e, + ui t, + int64_t *start_2, + int64_t *end_2, + ui **res_2) { + t += 1; + int64_t total = 0; + ui order_s = q_add - 1; + if (t < i_e.size()) { + ui q = i_e[t].second; + q_add = q + 1; + find_children_range(children_offset_2, candidates_l_2, q_add, order_s, start_2[t], end_2[t], j); + for (int64_t jj = start_2[t]; jj < end_2[t]; jj++) { + if (visited[res_2[q][jj]] == false) { + if (q_add == order_count_2) { + // std::cout <<"333" << std::endl; + total += 1; + } else { + total += morphism_next(visited_j, + visited, + children_offset_2, + candidates_l_2, + q_add, + jj, + order_count_2, + i_e, + t, + start_2, + end_2, + res_2); + // if (total != 0) { + // std::cout <> &i_e, + std::vector q_all, + ui t, + int64_t *start_2, + int64_t *end_2, + ui **res_2, + ui q_1, + int64_t &call_count_2) { + t += 1; + bool all_exist = false; + int64_t total_2 = 0; + ui order_s = q_add - 1; + if (t < q_all.size()) { + ui q = q_all[t]; + q_add = q + 1; + int64_t tmp = data_count * q; + // ui* & visit_local_5 = visit_local_4[tmp]; + // std::cout < 0) { + for (int64_t jj = start_2[t]; jj < end_2[t]; jj++) { + V_ID *res_t_2 = res_2_r[jj]; + call_count_2++; + all_exist = true; + for (auto &t_2 : i_e) { + ui q_3 = t_2.second; + if (visited[res_t_2[q_3]]) { + all_exist = false; + break; + } + } + total_2 += all_exist; + } + } else { + call_count_2 += 1; + total_2 = end_2[t] - start_2[t]; + } + } + return total_2; + } +} + +void reverse_cuts(__attribute__((unused)) ui **can_1, + __attribute__((unused)) int64_t **parent_1, + __attribute__((unused)) int64_t **children_1, + __attribute__((unused)) int64_t *candidates_l, + __attribute__((unused)) V_ID &ord_count, + __attribute__((unused)) ui **can_1_n, + __attribute__((unused)) int64_t **parent_1_n, + __attribute__((unused)) int64_t *candidates_l_n, + __attribute__((unused)) bool **valid) { + __attribute__((unused)) int64_t p, pp; + int64_t j, j_old, k, l; + + l = candidates_l[ord_count - 1]; + + for (int64_t d = 0; d < l; d++) { + can_1_n[ord_count - 1][d] = can_1[ord_count - 1][d]; + } + + candidates_l[ord_count - 1] = l; + + std::cout << "Finish Reverse" << std::endl; + for (ui i = ord_count - 1; i > 0; i--) { + std::cout << i << std::endl; + l = candidates_l[i]; + p = parent_1[i][0]; + for (int64_t q = 0; q <= p; q++) { + children_1[i - 1][q] = 0; + } + k = p + 1; + j_old = 0; + // std::cout <<"l->" << l << std::endl; + for (j = 1; j < l; j++) { + if (parent_1[i][j] != p) { + j_old = j; + p = parent_1[i][j]; + for (int64_t q = k; q <= p; q++) { + children_1[i - 1][q] = j_old; + } + k = p + 1; + } + } + p = parent_1[i][l - 1]; + // k = p + 1; + for (int64_t q = k; q <= candidates_l[i - 1] + 1; q++) { + children_1[i - 1][q] = l; + } + } +} + +int64_t find_key(__attribute__((unused)) ui **can_1, + int64_t **parent_1, + V_ID ord_1, + V_ID &ord_count_1, + int64_t p) { + for (ui i = ord_count_1 - 1; i > ord_1; i--) { + p = parent_1[i][p]; + } + return p; +} + +void find_res(ui **can_1, int64_t **parent_1, V_ID &ord_count_1, int64_t p, V_ID *res) { + for (ui i = ord_count_1 - 1; i > 0; i--) { + res[i] = can_1[i][p]; + p = parent_1[i][p]; + } + res[0] = can_1[0][p]; +} + +V_ID &find_value(ui **can_1, int64_t **parent_1, V_ID &ord_1, V_ID &ord_count_1, int64_t p) { + for (ui i = ord_count_1 - 1; i > ord_1; i--) { + p = parent_1[i][p]; + } + return can_1[ord_1][p]; +} + +bool check_morphism(ui **can_1, + ui **can_2, + int64_t **parent_1, + int64_t **parent_2, + V_ID &ord_1, + V_ID &ord_2, + V_ID &ord_count_1, + V_ID &ord_count_2, + int64_t p, + int64_t q) { + if (find_value(can_1, parent_1, ord_1, ord_count_1, p) == + find_value(can_2, parent_2, ord_2, ord_count_2, q)) { + return true; + } + return false; +} + +void enumeration_bfs2(bool *visited, + int64_t &res_all_1, + ui **candidates, + ui *candidates_count, + V_ID *order, + TreeNode *tree, + V_ID *res, + ui current_order, + ui &query_count, + Graph *query_graph, + Graph *data_graph, + std::vector &order_index, + V_ID *connection, + V_ID *offset, + ui **candidates_2, + int64_t **parent_offset, + int64_t *candidates_l, + V_ID *visit_local_2, + ui &c_length, + bool *morphism) { + // Current Order = current_order + // Get the query node of current order + V_ID &u = order[current_order]; + // ui p_node = tree[u].parent; + if (current_order == 1) { + candidates_l[0] = static_cast(candidates_count[order[0]]); + for (int64_t i = 0; i < candidates_l[0]; i++) { + candidates_2[0][i] = candidates[order[0]][i]; + } + } + ui p_order = 0; + int64_t total = 0; + + for (ui ii = 0; ii < candidates_count[u]; ii++) { + visit_local_2[candidates[u][ii]] = 0; + } + + if (true) { + int64_t c_l = candidates_l[current_order - 1]; + ui i, j; + ui *&candidate_3 = candidates_2[current_order]; + int64_t *&parent_3 = parent_offset[current_order]; + L_ID l = query_graph->getVertexLabel(u); + V_ID d = offset[current_order] - offset[current_order - 1]; + + ui candidates_tmp_count = candidates_count[u]; + ui *candidates_tmp = new ui[1000000]; + + if (current_order == query_count - 1) { + if (morphism[current_order] == true) { + for (int64_t k = 0; k < c_l; k++) { + visit_pre( + visited, candidates_2, parent_offset, candidates_l, current_order, p_order, k, res); + // for (i = 0; i < candidates_count[u]; i++) { + // visit_local_2[candidates[u][i]] = 0; + // } + + candidates_tmp_count = 0; + for (i = offset[current_order - 1]; i < offset[current_order]; i++) { + std::unordered_map> *v_nl = + data_graph->getVertexNL(res[connection[i]]); + std::vector &tmp = (*v_nl)[l]; + for (j = 0; j < tmp.size(); j++) { + visit_local_2[tmp[j]] += 1; + if (visit_local_2[tmp[j]] == 1) { + candidates_tmp[candidates_tmp_count++] = tmp[j]; + } + } + } + for (j = 0; j < candidates_tmp_count; j++) { + res_all_1 += 1; + if (visit_local_2[candidates_tmp[j]] == d && visited[candidates_tmp[j]] == false) { + candidate_3[total] = candidates_tmp[j]; + parent_3[total] = k; + total += 1; + } + } + + for (i = 0; i < candidates_tmp_count; i++) { + visit_local_2[candidates_tmp[i]] = 0; + } + + visit_can(visited, candidates_2, parent_offset, candidates_l, current_order, p_order, k); + } + } else { + for (int64_t k = 0; k < c_l; k++) { + visit_pre_2( + visited, candidates_2, parent_offset, candidates_l, current_order, p_order, k, res); + candidates_tmp_count = 0; + // for (i = 0; i < candidates_count[u]; i++) { + // visit_local_2[candidates[u][i]] = 0; + // } + + for (i = offset[current_order - 1]; i < offset[current_order]; i++) { + std::unordered_map> *v_nl = + data_graph->getVertexNL(res[connection[i]]); + std::vector &tmp = (*v_nl)[l]; + for (j = 0; j < tmp.size(); j++) { + visit_local_2[tmp[j]] += 1; + if (visit_local_2[tmp[j]] == 1) { + candidates_tmp[candidates_tmp_count++] = tmp[j]; + } + } + } + for (j = 0; j < candidates_tmp_count; j++) { + res_all_1 += 1; + if (visit_local_2[candidates_tmp[j]] == d) { + candidate_3[total] = candidates_tmp[j]; + parent_3[total] = k; + total += 1; + } + } + + for (i = 0; i < candidates_tmp_count; i++) { + visit_local_2[candidates_tmp[i]] = 0; + } + } + } + } else { + if (morphism[current_order] == true) { + for (int64_t k = 0; k < c_l; k++) { + visit_pre( + visited, candidates_2, parent_offset, candidates_l, current_order, p_order, k, res); + + // candidates_tmp_count = 0; + for (i = offset[current_order - 1]; i < offset[current_order]; i++) { + std::unordered_map> *v_nl = + data_graph->getVertexNL(res[connection[i]]); + std::vector &tmp = (*v_nl)[l]; + for (j = 0; j < tmp.size(); j++) { + visit_local_2[tmp[j]] += 1; + } + } + + for (j = 0; j < candidates_count[u]; j++) { + res_all_1 += 1; + if (visit_local_2[candidates[u][j]] == d && visited[candidates[u][j]] == false) { + candidate_3[total] = candidates[u][j]; + parent_3[total] = k; + total += 1; + } + } + + for (i = 0; i < candidates_count[u]; i++) { + visit_local_2[candidates[u][i]] = 0; + } + + visit_can(visited, candidates_2, parent_offset, candidates_l, current_order, p_order, k); + } + } else { + for (int64_t k = 0; k < c_l; k++) { + visit_pre_2( + visited, candidates_2, parent_offset, candidates_l, current_order, p_order, k, res); + // ui t = (current_order-1)*c_length; + + // candidates_tmp_count = 0; + + for (i = offset[current_order - 1]; i < offset[current_order]; i++) { + std::unordered_map> *v_nl = + data_graph->getVertexNL(res[connection[i]]); + std::vector &tmp = (*v_nl)[l]; + for (j = 0; j < tmp.size(); j++) { + visit_local_2[tmp[j]] += 1; + } + } + + for (j = 0; j < candidates_count[u]; j++) { + res_all_1 += 1; + if (visit_local_2[candidates[u][j]] == d) { + candidate_3[total] = candidates[u][j]; + parent_3[total] = k; + total += 1; + } + } + + for (i = 0; i < candidates_count[u]; i++) { + visit_local_2[candidates[u][i]] = 0; + } + } + } + } + // std::cout <<"total:" << total << std::endl; + } + candidates_l[current_order] = total; + if (current_order != query_count - 1) { + enumeration_bfs2(visited, + res_all_1, + candidates, + candidates_count, + order, + tree, + res, + current_order + 1, + query_count, + query_graph, + data_graph, + order_index, + connection, + offset, + candidates_2, + parent_offset, + candidates_l, + visit_local_2, + c_length, + morphism); + } + std::cout << "End " << total << std::endl; +} + +void enumeration_bfs(bool *visited, + int64_t &res_all, + std::vector>> &P_Candidates, + V_ID *order, + TreeNode *tree, + V_ID *res, + ui current_order, + ui &query_count, + Graph *query_graph, + Graph *data_graph, + std::vector &order_index, + V_ID *connection, + V_ID *offset, + ui **candidates_2, + int64_t **parent_offset, + int64_t *candidates_l, + V_ID *visit_local_2, + ui &c_length, + bool *morphism) { + // Current Order = current_order + // Get the query node of current order + V_ID &u = order[current_order]; + V_ID &p_order = order_index[tree[u].parent]; + // Next is to build the current order index. + int64_t c_l = candidates_l[current_order - 1]; + // std::cout <<"Current #order->" << current_order << "," << c_l << "," << " P_order->" << p_order + // << + // ", u:" << u << std::endl; + int64_t total = 0; + ui i, j; + ui *&candidate_3 = candidates_2[current_order]; + int64_t *&parent_3 = parent_offset[current_order]; + + L_ID l = query_graph->getVertexLabel(u); + V_ID d = offset[current_order] - offset[current_order - 1]; + + if (current_order == query_count - 1) { + if (morphism[current_order] == true) { + for (int64_t k = 0; k < c_l; k++) { + visit_pre( + visited, candidates_2, parent_offset, candidates_l, current_order, p_order, k, res); + V_ID &v_f = res[p_order]; + + std::vector &local_c = P_Candidates[u][v_f]; + + for (i = 0; i < local_c.size(); i++) { + visit_local_2[local_c[i]] = 0; + } + for (i = offset[current_order - 1]; i < offset[current_order]; i++) { + std::unordered_map> *v_nl = + data_graph->getVertexNL(res[connection[i]]); + std::vector &tmp = (*v_nl)[l]; + for (j = 0; j < tmp.size(); j++) { + visit_local_2[tmp[j]] += 1; + } + } + + for (j = 0; j < local_c.size(); j++) { + if (visit_local_2[local_c[j]] == d && visited[local_c[j]] == false) { + candidate_3[total] = local_c[j]; + parent_3[total] = k; + total += 1; + } + } + visit_can(visited, candidates_2, parent_offset, candidates_l, current_order, p_order, k); + } + } else { + // std::cout <<"Here" << std::endl; + for (int64_t k = 0; k < c_l; k++) { + visit_pre_2( + visited, candidates_2, parent_offset, candidates_l, current_order, p_order, k, res); + V_ID &v_f = res[p_order]; + std::vector &local_c = P_Candidates[u][v_f]; + + for (i = 0; i < local_c.size(); i++) { + visit_local_2[local_c[i]] = 0; + } + for (i = offset[current_order - 1]; i < offset[current_order]; i++) { + std::unordered_map> *v_nl = + data_graph->getVertexNL(res[connection[i]]); + std::vector &tmp = (*v_nl)[l]; + for (j = 0; j < tmp.size(); j++) { + visit_local_2[tmp[j]] += 1; + } + } + + for (j = 0; j < local_c.size(); j++) { + // std::cout <<"Local->" << visit_local_2[local_c[j]] << ",d->" << d << std::endl; + if (visit_local_2[local_c[j]] == d) { + candidate_3[total] = local_c[j]; + parent_3[total] = k; + total += 1; + } + } + } + } + } else { + if (morphism[current_order] == true) { + for (int64_t k = 0; k < c_l; k++) { + visit_pre( + visited, candidates_2, parent_offset, candidates_l, current_order, p_order, k, res); + V_ID &v_f = res[p_order]; + std::vector &local_c = P_Candidates[u][v_f]; + // ui t = (current_order-1)*c_length; + for (i = 0; i < local_c.size(); i++) { + visit_local_2[local_c[i]] = 0; + } + + for (i = offset[current_order - 1]; i < offset[current_order]; i++) { + std::unordered_map> *v_nl = + data_graph->getVertexNL(res[connection[i]]); + std::vector &tmp = (*v_nl)[l]; + for (j = 0; j < tmp.size(); j++) { + visit_local_2[tmp[j]] += 1; + } + } + + // abc + for (j = 0; j < local_c.size(); j++) { + if (visited[local_c[j]] == false && visit_local_2[local_c[j]] == d) { + candidate_3[total] = local_c[j]; + parent_3[total] = k; + total += 1; + } + } + + visit_can(visited, candidates_2, parent_offset, candidates_l, current_order, p_order, k); + } + } else { + for (int64_t k = 0; k < c_l; k++) { + visit_pre_2( + visited, candidates_2, parent_offset, candidates_l, current_order, p_order, k, res); + V_ID &v_f = res[p_order]; + std::vector &local_c = P_Candidates[u][v_f]; + + // ui t = (current_order-1)*c_length; + + for (i = 0; i < local_c.size(); i++) { + visit_local_2[local_c[i]] = 0; + } + + for (i = offset[current_order - 1]; i < offset[current_order]; i++) { + std::unordered_map> *v_nl = + data_graph->getVertexNL(res[connection[i]]); + std::vector &tmp = (*v_nl)[l]; + for (j = 0; j < tmp.size(); j++) { + visit_local_2[tmp[j]] += 1; + } + } + for (j = 0; j < local_c.size(); j++) { + if (visit_local_2[local_c[j]] == d) { + candidate_3[total] = local_c[j]; + parent_3[total] = k; + total += 1; + } + } + } + } + } + + candidates_l[current_order] = total; + if (current_order != query_count - 1) { + enumeration_bfs(visited, + res_all, + P_Candidates, + order, + tree, + res, + current_order + 1, + query_count, + query_graph, + data_graph, + order_index, + connection, + offset, + candidates_2, + parent_offset, + candidates_l, + visit_local_2, + c_length, + morphism); + } + + // std::cout <<"End " << total << std::endl; +} + +int64_t enumeration_ress_bk_2( + bool *visited, + int64_t &res_all, + std::vector>> &P_Candidates, + V_ID *order, + TreeNode *tree, + V_ID *res, + ui current_order, + ui &query_count, + Graph *query_graph, + Graph *data_graph, + std::vector &order_index, + V_ID *connection, + V_ID *offset, + V_ID *visit_local, + V_ID *visit_local_2, + ui &c_length, + bool *morphism) { + V_ID &u = order[current_order]; + V_ID &v_f = res[order_index[tree[u].parent]]; + std::vector &local_c = P_Candidates[u][v_f]; + V_ID d = offset[current_order] - offset[current_order - 1]; + int64_t total = 0; + ui i, j; + + if (current_order == query_count) { + for (i = 0; i < local_c.size(); i++) { + visit_local_2[local_c[i]] = 0; + } + L_ID l = query_graph->getVertexLabel(u); + for (i = offset[current_order - 1]; i < offset[current_order]; i++) { + std::unordered_map> *v_nl = data_graph->getVertexNL(res[connection[i]]); + std::vector &tmp = (*v_nl)[l]; + for (j = 0; j < tmp.size(); j++) { + visit_local_2[tmp[j]] += 1; + } + } + + if (morphism[current_order] == true) { + for (j = 0; j < local_c.size(); j++) { + if (visit_local_2[local_c[j]] == d) { + total += 1 - visited[local_c[j]]; + } + } + } else { + for (j = 0; j < local_c.size(); j++) { + if (visit_local_2[local_c[j]] == d) { + total += 1; + } + } + } + return total; + } else { + ui t = (current_order - 1) * c_length; + V_ID *visit_local_3 = visit_local + t; + for (i = 0; i < local_c.size(); i++) { + visit_local_3[local_c[i]] = 0; + } + L_ID l = query_graph->getVertexLabel(u); + for (i = offset[current_order - 1]; i < offset[current_order]; i++) { + std::unordered_map> *v_nl = data_graph->getVertexNL(res[connection[i]]); + std::vector &tmp = (*v_nl)[l]; + for (j = 0; j < tmp.size(); j++) { + visit_local_3[tmp[j]] += 1; + } + } + if (morphism[current_order] == true) { + for (j = 0; j < local_c.size(); j++) { + if (visited[local_c[j]] == false && visit_local_3[local_c[j]] == d) { + res[current_order] = local_c[j]; + visited[local_c[j]] = true; + total += enumeration_ress_bk_2(visited, + res_all, + P_Candidates, + order, + tree, + res, + current_order + 1, + query_count, + query_graph, + data_graph, + order_index, + connection, + offset, + visit_local, + visit_local_2, + c_length, + morphism); + visited[local_c[j]] = false; + } + } + } else { + for (j = 0; j < local_c.size(); j++) { + if (visit_local_3[local_c[j]] == d) { + res[current_order] = local_c[j]; + total += enumeration_ress_bk_2(visited, + res_all, + P_Candidates, + order, + tree, + res, + current_order + 1, + query_count, + query_graph, + data_graph, + order_index, + connection, + offset, + visit_local, + visit_local_2, + c_length, + morphism); + } + } + } + return total; + } +} + +int64_t enumeration_res(int &res_all, + std::vector>> &P_Candidates, + V_ID v_f, + V_ID *&order, + TreeNode *&tree, + V_ID *res, + ui current_order, + int64_t total, + int size_all, + int query_count, + Graph *&query_graph, + Graph *&data_graph, + std::vector &order_index) { + V_ID u = order[current_order]; + // std::cout <<"####In enumeration function" << std::endl; + bool over_all = true; + int64_t res_p = 0; + v_f = res[total * size_all + order_index[tree[u].parent]]; + int p = P_Candidates[u][v_f].size(); + // std::cout <<"p:->" << p << ", u:"<< u << ", order:"<< current_order << ",v_f" << v_f << ", + // Order + // index of parent" << order_index[tree[u].parent] << ", tree.parent->" << tree[u].parent << + // std::endl; + + if (static_cast(current_order) == query_count - 1) { + // std::cout <<"** in if" << std::endl; + for (int j = 0; j < p; j++) { + over_all = true; + ui nbrs_count_1; + V_ID *nbrs_1 = query_graph->getVertexNeighbors(order[current_order], nbrs_count_1); + for (ui k = 0; k < current_order; k++) { + if (res[total * size_all + k] == P_Candidates[u][v_f][j]) { + over_all = false; + break; + } + } + res_all += 1; + + if (over_all == true) { + ui data_nbrs_count_1; + V_ID *data_nbrs_1 = + data_graph->getVertexNeighbors(P_Candidates[u][v_f][j], data_nbrs_count_1); + for (ui k = 0; k < nbrs_count_1; k++) { + if (order_index[nbrs_1[k]] < current_order && + (nbrs_1[k] == order[0] || order_index[nbrs_1[k]] != 0)) { + over_all = false; + V_ID tmp = res[total * size_all + order_index[nbrs_1[k]]]; + for (int64_t t = 0; t < data_nbrs_count_1; t++) { + if (data_nbrs_1[t] == tmp) { + over_all = true; + // std::cout <<"Last True here:::" << std::endl; + // break; + } + } + if (over_all == false) { + break; + } + } + } + } + + if (over_all == true) { + // std::cout <<"Res_p:" << res_p << "," << P_Candidates[u][v_f][j] << std::endl; + res[total * size_all + (res_p)*size_all + current_order] = P_Candidates[u][v_f][j]; + for (ui t = 0; t < current_order; t++) { + res[total * size_all + res_p * size_all + t] = res[total * size_all + t]; + } + res_p += 1; + } + } + + return res_p; + } else { + int64_t total_2 = 0; + for (uint64_t j = 0; j < P_Candidates[u][v_f].size(); j++) { + over_all = true; + int64_t single_res = 0; + // std::cout <" << P_Candidates[u][v_f][j] << std::endl; + ui nbrs_count_1; + V_ID *nbrs_1 = query_graph->getVertexNeighbors(order[current_order], nbrs_count_1); + // std::cout <<"Query count:" << nbrs_count_1 << std::endl; + for (ui k = 0; k < current_order; k++) { + if (res[total * size_all + k] == P_Candidates[u][v_f][j]) { + over_all = false; + // std::cout <<"current_node" << P_Candidates[u][v_f][j] << ", expected node:" << + // res[total*size_all + k] << ", order ->" << k << std::endl;std::cout <<"<<---->> Erase + // <<---->>" + // << std::endl; + break; + } + } + if (over_all == true) { + ui data_nbrs_count_1; + V_ID *data_nbrs_1 = + data_graph->getVertexNeighbors(P_Candidates[u][v_f][j], data_nbrs_count_1); + for (ui k = 0; k < nbrs_count_1; k++) { + if (order_index[nbrs_1[k]] < current_order && + (nbrs_1[k] == order[0] || order_index[nbrs_1[k]] != 0)) { + over_all = false; + V_ID tmp_v = res[total * size_all + order_index[nbrs_1[k]]]; + + // std::cout <<"NB ID:" << nbrs_1[k] << ",Data Count:" << data_nbrs_count_1 << ", V: " + // << + // tmp_v << "," << nbrs_1[k] << "," << order_index[nbrs_1[k]] << std::endl; + for (int64_t t = 0; t < data_nbrs_count_1; t++) { + if (data_nbrs_1[t] == tmp_v) { + over_all = true; + // std::cout <<"True here" << std::endl; + // break; + } + } + + if (over_all == false) { + break; + } + } + } + } + res_all += 1; + if (over_all == true) { + // std::cout <" << P_Candidates[u][v_f][j] << ", + // &&¤t + // order***: " << current_order << std::endl;std::cout <<"Over ALL Game Over" << std::endl; + // res_p += 1; + res[total * size_all + total_2 * size_all + current_order] = P_Candidates[u][v_f][j]; + for (ui t = 0; t < current_order; t++) { + res[total * size_all + total_2 * size_all + t] = res[total * size_all + t]; + } + // std::cout <<")))In function --->" << ", Order and p->vertex->" << P_Candidates[u][v_f][j] + // << + // endl; + single_res = enumeration_res(res_all, + P_Candidates, + P_Candidates[u][v_f][j], + order, + tree, + res, + current_order + 1, + total + total_2, + size_all, + query_count, + query_graph, + data_graph, + order_index); + // std::cout <<"--->Finish single_res" << single_res << std::endl; + // std::cout <<"next Single res:" << single_res << std::endl; + // This is the one + for (int64_t k = 0; k < single_res; k++) { + // std::cout <<"all" << std::endl; + res[total * size_all + total_2 * size_all + k * size_all + current_order] = + P_Candidates[u][v_f][j]; + // break; + // std::cout <<"Current order and data vertex: " << current_order << ", " << + // P_Candidates[u][v_f][j] << std::endl; + } + + total_2 += single_res; + } + } + // std::cout <<"Total_2->" << total_2 << std::endl; + return total_2; + } +} + +// Assume we have all the information in the graph. +void local_optimization( + std::unordered_map> &res, + ui query_count, + __attribute__((unused)) std::vector order_index, + __attribute__((unused)) + std::unordered_map>> &intersetion, + ui *&order, + __attribute__((unused)) TreeNode *&tree, + __attribute__((unused)) std::vector>> &P_Candidates, + __attribute__((unused)) + std::unordered_map>>> + data_provenance, + __attribute__((unused)) Graph *data_graph, + Graph *query_graph) { + for (ui i = 0; i < query_count; i++) { + ui u = order[i]; + std::vector local_res; + ui label = query_graph->getVertexLabel(u); + local_res.push_back(label); + for (ui j = i + 1; j < query_count; j++) { + ui tmp = order[j]; + if (res.find(label) == res.end() && query_graph->getVertexLabel(tmp) == label) { + local_res.push_back(tmp); + } + } + if (local_res.size() > 1) { + auto tmp = res.emplace(u, std::vector()); + tmp.first->second = local_res; + } + } +} + +void Refinement( + std::unordered_map>> &intersetion, + ui *&order, + TreeNode *&tree, + ui u, + std::vector>> &P_Candidates, + std::unordered_map>>> + data_provenance, + Graph *data_graph) { + // TreeNode& u_node = tree[u]; + ui u_f = INVALID_VERTEX_ID; + std::unordered_map> frontiers_2; + if (u != order[0]) { + u_f = tree[u].parent; + frontiers_2 = P_Candidates[u_f]; + } + std::unordered_map> frontiers_1 = P_Candidates[u]; + __attribute__((unused)) bool do_interset; + do_interset = false; + bool update_index = false; + if (u != order[0] && intersetion.find(u) != intersetion.end()) { + do_interset = true; + for (ui j = 0; j < tree[u].bn_count; j++) { + update_index = false; + + if (tree[u].bn[j] != tree[u].parent) { + V_ID u_nb = tree[u].bn[j]; + std::unordered_map> frontiers_3 = P_Candidates[u_nb]; + V_ID u_nb_f = tree[u_nb].parent; + V_ID pro_node = ParentNode(u, u_nb, tree); + + for (const auto &it_3 : frontiers_3) { + ui v_3_f = it_3.first; + bool find_n = false; + std::vector c_2 = it_3.second; + std::vector find_intersect; + for (uint64_t l_2 = 0; l_2 < c_2.size(); l_2++) { + find_intersect.push_back(false); + } + + for (const auto &it_1 : frontiers_1) { + ui v_1_f = it_1.first; + bool share_pro = false; + + for (ui k_1 = 0; k_1 < data_provenance[u_f][v_1_f][pro_node].size(); k_1++) { + for (ui k_2 = 0; k_2 < data_provenance[u_nb_f][v_3_f][pro_node].size(); k_2++) { + if (data_provenance[u_f][v_1_f][pro_node][k_1] == + data_provenance[u_nb_f][v_3_f][pro_node][k_2]) { + share_pro = true; + break; + } + } + } + + if (share_pro == true) { + // int count = 0; + std::vector c_1 = it_1.second; + for (uint64_t l_2 = 0; l_2 < c_2.size(); l_2++) { + ui v_2 = c_2[l_2]; + if (find_intersect[l_2] == true) continue; + for (unsigned int v_1 : c_1) { + ui data_nbrs_count_1; + V_ID *data_nbrs_1 = data_graph->getVertexNeighbors(v_1, data_nbrs_count_1); + if (DEBUG) { + // std::cout <<"Neighbors are: " ; + } + for (ui p = 0; p < data_nbrs_count_1; p++) { + if (data_nbrs_1[p] == v_2) { + find_intersect[l_2] = true; + find_n = true; + break; + } + } + if (find_intersect[l_2] == true) { + break; + } + } + } + } + } + for (uint64_t l_2 = 0; l_2 < c_2.size(); l_2++) { + ui v_2 = c_2[l_2]; + if (find_intersect[l_2] == false) { + for (uint64_t ll = 0; ll < P_Candidates[u_nb][v_3_f].size(); ll++) { + if (P_Candidates[u_nb][v_3_f][ll] == v_2) { + if (DEBUG) { + std::cout << "Erase intersect, v_3_f: " << v_3_f << std::endl; + } + P_Candidates[u_nb][v_3_f].erase(P_Candidates[u_nb][v_3_f].begin() + ll); + update_index = true; + break; + } + } + } + } + if (DEBUG) { + std::cout << "Judge" << std::endl; + } + if (find_n == false) { + if (DEBUG) { + std::cout << "Erase Candidates" << std::endl; + } + P_Candidates[u_nb].erase(v_3_f); + update_index = true; + if (DEBUG) { + std::cout << "Erase C-Done"; + } + } + } + if (update_index == true) { + Refinement(intersetion, order, tree, u_nb, P_Candidates, data_provenance, data_graph); + } + } + } + } + if (DEBUG) { + std::cout << "Refine the parents" << std::endl; + } + // BFS reduce it's parent: + + update_index = false; + if (order[0] != u) { + frontiers_2 = P_Candidates[u_f]; + for (const auto &it : frontiers_2) { + V_ID v_f_f = it.first; + int count = 0; + if (P_Candidates[u_f].find(v_f_f) != P_Candidates[u_f].end()) { + for (uint64_t j = 0; j < it.second.size(); j++) { + V_ID v_f = it.second[j]; + if (frontiers_1.find(v_f) == frontiers_1.end()) { + P_Candidates[u_f][v_f_f].erase(P_Candidates[u_f][v_f_f].begin() + j - count); + count += 1; + } + // This is all + } + if (P_Candidates[u_f][v_f_f].size() == 0) { + P_Candidates[u_f].erase(v_f_f); + update_index = true; + } + } + } + } + + if (DEBUG) { + std::cout << "Refine the parents End" << std::endl; + } + if (update_index == true) { + Refinement(intersetion, order, tree, u_f, P_Candidates, data_provenance, data_graph); + } + + // std::cout <<"3" << std::endl; + + update_index = false; + frontiers_1 = P_Candidates[u]; + do_interset = false; + // Intersection erase their frontier neighbourhoods: + if (true) { + if (DEBUG) { + std::cout << " In the key of Intersetion." << std::endl; + } + do_interset = true; + if (u == order[0]) { + // return; + u_f = u; + } + for (ui j = 0; j < tree[u].fn_count; j++) { + update_index = false; + V_ID u_nb = tree[u].fn[j]; + + if (u_nb == u_f || + std::find(tree[u].children, tree[u].children + tree[u].children_count, u_nb) != + tree[u].children + tree[u].children_count) + continue; + if (DEBUG) { + std::cout << "Nbors N:" << u_nb << std::endl; + } + std::unordered_map> frontiers_3 = P_Candidates[u_nb]; + V_ID u_nb_f = tree[u_nb].parent; + V_ID pro_node = ParentNode(u, u_nb, tree); + + for (const auto &it_3 : frontiers_3) { + ui v_3_f = it_3.first; + bool find_n = false; + std::vector c_2 = it_3.second; + std::vector find_intersect; + // Temp array and vec. + // Initialize the find intersect bool list. + + for (uint64_t l_2 = 0; l_2 < c_2.size(); l_2++) { + find_intersect.push_back(false); + } + + for (const auto &it_1 : frontiers_1) { + ui v_1_f = it_1.first; + bool share_pro = false; + for (ui k_1 = 0; k_1 < data_provenance[u_f][v_1_f][pro_node].size(); k_1++) { + for (ui k_2 = 0; k_2 < data_provenance[u_nb_f][v_3_f][pro_node].size(); k_2++) { + if (data_provenance[u_f][v_1_f][pro_node][k_1] == + data_provenance[u_nb_f][v_3_f][pro_node][k_2]) { + share_pro = true; + break; + } + } + } + + if (share_pro == true) { + // int count = 0; + std::vector c_1 = it_1.second; + for (uint64_t l_2 = 0; l_2 < c_2.size(); l_2++) { + ui v_2 = c_2[l_2]; + if (find_intersect[l_2] == true) continue; + for (unsigned int v_1 : c_1) { + ui data_nbrs_count_1; + V_ID *data_nbrs_1 = data_graph->getVertexNeighbors(v_1, data_nbrs_count_1); + + for (ui p = 0; p < data_nbrs_count_1; p++) { + if (data_nbrs_1[p] == v_2) { + if (DEBUG) { + std::cout << "True###" << std::endl; + } + find_intersect[l_2] = true; + find_n = true; + break; + } + } + if (find_intersect[l_2] == true) { + break; + } + } + } + } + } + + for (uint64_t l_2 = 0; l_2 < c_2.size(); l_2++) { + ui v_2 = c_2[l_2]; + if (find_intersect[l_2] == false) { + for (uint64_t ll = 0; ll < P_Candidates[u_nb][v_3_f].size(); ll++) { + if (P_Candidates[u_nb][v_3_f][ll] == v_2) { + if (DEBUG) { + std::cout << "Erase intersect, v_3_f: " << v_3_f << std::endl; + } + P_Candidates[u_nb][v_3_f].erase(P_Candidates[u_nb][v_3_f].begin() + ll); + update_index = true; + if (DEBUG) { + std::cout << "Erase inter-Done" << std::endl; + } + break; + } + } + if (DEBUG) { + std::cout << "Current Node: " << 2 << " ; "; + for (const auto &iter : P_Candidates[2]) { + std::cout << "Vector Size: " << iter.second.size() << " "; + std::cout << "Parent node: " << iter.first << " ["; + for (unsigned int d : iter.second) { + std::cout << d << ","; + } + std::cout << " ] " << std::endl; + } + } + } + } + + if (find_n == false) { + P_Candidates[u_nb].erase(v_3_f); + if (DEBUG) { + std::cout << "Erase C-Done"; + } + } + } + if (update_index == true) { + Refinement(intersetion, order, tree, u_nb, P_Candidates, data_provenance, data_graph); + } + } + } + if (DEBUG) { + std::cout << "Refine the children::" << std::endl; + } + + // BFS reduce it's children: + + for (V_ID k = 0; k < tree[u].children_count; k++) { + update_index = false; + V_ID u_c = tree[u].children[k]; + frontiers_2 = P_Candidates[u_c]; + std::vector child; + int count = 0; + for (const auto &it : frontiers_2) { + V_ID v_f = it.first; + bool find_children = false; + for (const auto &it_1 : frontiers_1) { + if (std::find(it_1.second.begin(), it_1.second.end(), v_f) != it_1.second.end()) { + find_children = true; + break; + } + // This is all + } + if (find_children == false && P_Candidates[u_c].find(v_f) != P_Candidates[u_c].end()) { + P_Candidates[u_c].erase(v_f); + update_index = true; + } + count += 1; + } + + if (update_index == true) { + Refinement(intersetion, order, tree, u_c, P_Candidates, data_provenance, data_graph); + } + } + if (DEBUG) { + std::cout << std::endl << "end--> )))" << u << std::endl << std::endl; + } +} + +int local_cartesian(std::vector &order_index, + TreeNode *&tree, + std::vector &candidates_count, + ui u, + std::vector &node_tmp) { + int64_t total = 1; + int num_group = tree[u].children_count; + + if (num_group == 0) { + return candidates_count[order_index[u]]; + } + + for (int i = 0; i < num_group; i++) { + ui u_2 = tree[u].children[i]; + node_tmp.push_back(u_2); + total *= local_cartesian(order_index, tree, candidates_count, u_2, node_tmp); + // std::cout <<"Test->" << total << std::endl; + } + + return total; +} + +void cartesian_s(int *cartesian, + V_ID *&order, + std::vector order_index, + TreeNode *&tree, + std::vector &candidates_count, + ui root, + std::vector> &pattern_e, + ui &final_group, + V_ID *&order_1, + V_ID *&order_2, + ui &order_count_1, + ui &order_count_2, + ui &query_count) { + int num_group = tree[root].children_count; + std::cout << "potential partition num:" << num_group << std::endl; + + auto e_g = new int[100][100]; + + std::vector> node_l; + + int64_t total_cartesian = 1; + for (int i = 0; i < num_group; i++) { + std::vector node_tmp; + node_tmp.push_back(root); + cartesian[i] = 0; + ui u = tree[root].children[i]; + node_tmp.push_back(u); + cartesian[i] = local_cartesian(order_index, tree, candidates_count, u, node_tmp); + std::cout << " # cartesian result->" << u << "->" << cartesian[i] << std::endl; + node_l.push_back(node_tmp); + total_cartesian *= cartesian[i]; + } + + std::cout << "Finished cartesian" << std::endl; + // (1) Edge information + for (int i = 0; i < num_group; i++) { + for (int j = 0; j < num_group; j++) { + e_g[i][j] = 999; + if (i != j) { + e_g[i][j] = 0; + for (uint64_t p = 1; p < node_l[i].size(); p++) { + for (uint64_t q = 1; q < node_l[j].size(); q++) { + // std::cout <"<< i << ","<< j << + // endl; + if (order_index[node_l[i][p]] < order_index[node_l[j][q]]) { + for (uint64_t t = 0; t < pattern_e[order_index[node_l[i][p]]].size(); t++) { + if (node_l[j][q] == pattern_e[order_index[node_l[i][p]]][t]) { + e_g[i][j] += 1; + break; + } + } + } else if (order_index[node_l[i][p]] > order_index[node_l[j][q]]) { + for (uint64_t t = 0; t < pattern_e[order_index[node_l[j][q]]].size(); t++) { + if (node_l[i][p] == pattern_e[order_index[node_l[j][q]]][t]) { + e_g[i][j] += 1; + break; + } + } + } + } + } + } + // std::cout <<"group->" << i << ", and group->" << j << ", edge->" << e_g[i][j] << std::endl; + } + } + // (2) Devide the edge. + // Partition 1 edge. + int64_t min_e = 0; + for (int j = 1; j < num_group; j++) { + min_e += e_g[0][j]; + } + ui order_g = 0; + for (int i = 1; i < num_group; i++) { + int local_e = 0; + for (int j = 0; j < num_group; j++) { + if (i != j) { + local_e += e_g[i][j]; + } + } + int64_t tmp_t = cartesian[i] * num_group - total_cartesian; + // std::cout <<"Tmp:" << tmp << std::endl; + if (local_e < min_e && tmp_t >= 0) { + order_g = i; + min_e = local_e; + } + } + // std::cout <<"order_g->" << order_g << ", min_e:" << min_e << std::endl; + final_group = order_g; + + std::cout << "Finished prepare" << std::endl; + // (3) partition with all the cases. + + int cases = num_group / 2; + std::vector> all_balance; + std::vector p_cartesian; + std::vector p_edges; + std::vector> l_c_all; + + // There are i kinds of the combination C(1,n) ... C(i,n), C(|num_group|/2,n). + // int tmp[30]; + // for (int i = 0 ; i < 30; i ++) { + // tmp[i] = 0; + // } + for (int i = 1; i <= cases; i++) { + std::vector> cases_balance; + for (int k = 0; k < num_group; k++) { + std::set s1; + s1.insert(k); + cases_balance.push_back(s1); + } + for (int j = 1; j < i; j++) { + std::vector> cases_balance_new; + for (int k = 0; k < num_group; k++) { + for (auto &t : cases_balance) { + if (t.find(static_cast(k)) == t.end()) { + std::set local_case; + local_case = t; + local_case.insert(k); + if (std::find(cases_balance_new.begin(), cases_balance_new.end(), local_case) == + cases_balance_new.end()) { + cases_balance_new.push_back(local_case); + } + } + } + } + cases_balance = cases_balance_new; + } + for (auto &j : cases_balance) { + all_balance.push_back(j); + } + } + + std::cout << "Before do" << std::endl; + if (all_balance.size() == 0) { + order_count_1 = query_count; + order_count_2 = 0; + for (ui j = 0; j < query_count; j++) { + order_1[j] = order[j]; + } + return; + } + + for (uint64_t j = 0; j < all_balance.size(); j++) { + // long double value = 0; + int64_t l_c = 0; + int64_t l_cc = 0; + int l_e = 0; + std::cout << "Group->"; + std::queue q3; + for (auto itr = all_balance[j].begin(); itr != all_balance[j].end(); itr++) { + q3.push(tree[root].children[*itr]); + l_cc *= cartesian[*itr]; + // std::cout <<"," << *itr; + for (int i = 0; i < num_group; i++) { + if (std::find(all_balance[j].begin(), all_balance[j].end(), i) == all_balance[j].end()) { + l_e += e_g[*itr][i]; + } + } + } + + while (!q3.empty()) { + // dequeue front node and print it + ui v = q3.front(); + q3.pop(); + l_c += 1; + // order_1[order_count_1++] = v; + if (tree[v].children_count > 2) { + for (ui m = 0; m < tree[v].children_count; m++) { + q3.push(tree[v].children[m]); + } + } + } + + std::cout << "total_cartesian" << total_cartesian << std::endl; + + l_c = static_cast((query_count) * (query_count) / 4) - (query_count - l_c) * (l_c); + std::tuple tmp; + std::get<0>(tmp) = l_c; + std::get<1>(tmp) = l_e; + std::get<2>(tmp) = j; + l_c_all.push_back(tmp); + + // std::cout <<",l_c, l_e:" << l_c << "," << l_e << std::endl; + + // value = 0; + } + + sort(l_c_all.begin(), l_c_all.end()); + + // std::cout <<"l_c->" <(l_c_all[0]) << std::endl; + // std::cout <<"Check the first one,l_c:" <(l_c_all[0]) << ",l_e:" + // <(l_c_all[0]) + // << + // endl; + + std::cout << "End all prosibility:" << all_balance.size() << std::endl; + + // Group 1 + std::vector node; + std::queue q1, q2; + + for (int i = 0; i < num_group; i++) { + // node.push_back(*all_balance[i].begin()); + + int t = std::get<2>(l_c_all[0]); + if (std::find(all_balance[t].begin(), all_balance[t].end(), i) != all_balance[t].end()) { + q1.push(tree[root].children[i]); + } else { + q2.push(tree[root].children[i]); + } + } + + order_count_1 = 0; + order_count_2 = 0; + // if() + order_1[order_count_1++] = root; + order_2[order_count_2++] = root; + + ui v; + while (!q1.empty()) { + // dequeue front node and print it + v = q1.front(); + q1.pop(); + order_1[order_count_1++] = v; + for (ui j = 0; j < tree[v].children_count; j++) { + q1.push(tree[v].children[j]); + } + } + // Group 2 + + while (!q2.empty()) { + // dequeue front node and print it + v = q2.front(); + q2.pop(); + order_2[order_count_2++] = v; + for (ui j = 0; j < tree[v].children_count; j++) { + q2.push(tree[v].children[j]); + } + } + + if (order_count_1 > order_count_2) { + for (ui k = 0; k < order_count_2; k++) { + ui tmp = order_1[k]; + order_1[k] = order_2[k]; + order_2[k] = tmp; + } + for (ui k = order_count_2; k < order_count_1; k++) { + order_2[k] = order_1[k]; + } + ui tmp = order_count_1; + order_count_1 = order_count_2; + order_count_2 = tmp; + } +} + +void pruneCandidates(Graph *data_graph, + Graph *query_graph, + V_ID query_vertex, + V_ID *pivot_vertices, + ui pivot_vertices_count, + V_ID **candidates, + ui *candidates_count, + ui *flag, + ui *updated_flag) { + L_ID query_vertex_label = query_graph->getVertexLabel(query_vertex); + ui query_vertex_degree = query_graph->getVertexDegree(query_vertex); + + ui count = 0; + ui updated_flag_count = 0; + + for (ui i = 0; i < pivot_vertices_count; ++i) { + V_ID pivot_vertex = pivot_vertices[i]; + + for (ui j = 0; j < candidates_count[pivot_vertex]; ++j) { + V_ID v = candidates[pivot_vertex][j]; + + if (v == INVALID_VERTEX_ID) continue; + ui v_nbrs_count; + V_ID *v_nbrs = data_graph->getVertexNeighbors(v, v_nbrs_count); + + for (ui k = 0; k < v_nbrs_count; ++k) { + V_ID v_nbr = v_nbrs[k]; + L_ID v_nbr_label = data_graph->getVertexLabel(v_nbr); + ui v_nbr_degree = data_graph->getVertexDegree(v_nbr); + + if (flag[v_nbr] == count && v_nbr_label == query_vertex_label && + v_nbr_degree >= query_vertex_degree) { + flag[v_nbr] += 1; + + if (count == 0) { + updated_flag[updated_flag_count++] = v_nbr; + } + } + } + } + count += 1; + } + + for (ui i = 0; i < candidates_count[query_vertex]; ++i) { + ui v = candidates[query_vertex][i]; + if (v == INVALID_VERTEX_ID) continue; + + if (flag[v] != count) { + candidates[query_vertex][i] = INVALID_VERTEX_ID; + } + } + + for (ui i = 0; i < updated_flag_count; ++i) { + ui v = updated_flag[i]; + flag[v] = 0; + } +} + +void Index_To_Candidate(ui **&candidates, + ui *&candidates_count, + std::vector>> &P_Candidates, + ui query_count, + ui data_count, + ui *order, + bool *visited) { + for (ui i = 0; i < query_count; i++) { + ui u = order[i]; + candidates_count[u] = 0; + std::unordered_map> frontiers = P_Candidates[u]; + + for (const auto &frontier : frontiers) { + for (unsigned int j : frontier.second) { + if (visited[j] == false) { + visited[j] = true; + candidates[u][candidates_count[u]++] = j; + } + } + } + std::fill(visited, visited + data_count, false); + } +} + +void compactCandidates(ui **&candidates, ui *&candidates_count, ui query_count) { + for (ui i = 0; i < query_count; ++i) { + V_ID query_vertex = i; + ui next_position = 0; + for (ui j = 0; j < candidates_count[query_vertex]; ++j) { + V_ID data_vertex = candidates[query_vertex][j]; + + if (data_vertex != INVALID_VERTEX_ID) { + candidates[query_vertex][next_position++] = data_vertex; + } + } + + candidates_count[query_vertex] = next_position; + } +} + +void intersection( + std::unordered_map> &query_provenance, + std::unordered_map>>> + &data_provenance, + V_ID &u, + std::unordered_map>> &node_provenance, + V_ID &u_p, + std::unordered_map> &frontiers_1) { + for (const auto &it_1 : frontiers_1) { + V_ID v_f = it_1.first; + for (unsigned int v : it_1.second) { + std::unordered_map> pro; + bool v_existed = false; + if (node_provenance.find(v) != node_provenance.end()) { + v_existed = true; + } + if (v_existed == false) { + for (uint64_t g = 0; g < query_provenance[u].size(); g++) { + if (u == query_provenance[u][g]) { + // add current node and v to the provenance information if this node is one of the + // required provenance ID. + std::vector pro_v; + pro_v.push_back(v); + pro.insert(std::pair>(query_provenance[u][g], pro_v)); + // std::cout <<" !!!!!!!!!!!++++intersect " << pro[query_provenance[u][g]][0] << + // endl; + } else if (data_provenance[u_p][v_f][query_provenance[u][g]].size() != 0) { + pro.insert(std::pair>( + query_provenance[u][g], data_provenance[u_p][v_f][query_provenance[u][g]])); + } + } + if (pro.size() != 0) { + node_provenance.insert( + std::pair>>(v, pro)); + } + } else { + for (uint64_t g = 0; g < query_provenance[u].size(); g++) { + if (u != query_provenance[u][g] && + data_provenance[u_p][v_f][query_provenance[u][g]].size() != 0) { + // add current node and v to the provenance information if this node is one of the + // required provenance ID. + for (uint64_t ll = 0; ll < data_provenance[u_p][v_f][query_provenance[u][g]].size(); + ll++) { + if (std::find(node_provenance[v][query_provenance[u][g]].begin(), + node_provenance[v][query_provenance[u][g]].end(), + data_provenance[u_p][v_f][query_provenance[u][g]][ll]) == + node_provenance[v][query_provenance[u][g]].end()) { + node_provenance[v][query_provenance[u][g]].push_back( + data_provenance[u_p][v_f][query_provenance[u][g]][ll]); + } + } + } + } + } + } + } +} +void CheckRepartiton1(Graph *&query_graph, + ui *&order_1, + ui *&order_2, + ui &order_count_1, + ui &order_count_2, + bool &inside, + bool &outside, + ui &counnt) { + while (inside == false && outside == false && counnt < 10) { + outside = true; + counnt++; + for (ui i = 1; i < order_count_1; i++) { + inside = false; + ui nb_count; + ui *nb = query_graph->getVertexNeighbors(order_1[i], nb_count); + for (ui j = i + 1; j < order_count_1; j++) { + if (std::find(nb, nb + nb_count, order_1[j]) != nb + nb_count) { + inside = true; + break; + } + } + + if (inside == false) { + outside = false; + for (ui j = 1; j < order_count_2; j++) { + nb = query_graph->getVertexNeighbors(order_2[j], nb_count); + if (std::find(nb, nb + nb_count, order_1[i]) != nb + nb_count) { + inside = false; + outside = true; + order_2[order_count_2++] = order_1[i]; + for (ui k = i + 1; k < order_count_1; k++) { + order_1[k - 1] = order_1[k]; + } + order_count_1--; + break; + } + } + } + + if (outside == true && inside == false) { + outside = false; + break; + } + } + } +} +void CheckRepartiton2(Graph *&query_graph, + ui *&order_1, + ui *&order_2, + ui &order_count_1, + ui &order_count_2, + bool &inside, + bool &outside, + ui &counnt) { + while (inside == false && outside == false && counnt < 10) { + outside = true; + counnt++; + for (ui i = 1; i < order_count_2; i++) { + inside = false; + ui nb_count; + ui *nb = query_graph->getVertexNeighbors(order_2[i], nb_count); + for (ui j = i + 1; j < order_count_1; j++) { + if (std::find(nb, nb + nb_count, order_2[j]) != nb + nb_count) { + inside = true; + break; + } + } + if (inside == false) { + outside = false; + for (ui j = 1; j < order_count_1; j++) { + nb = query_graph->getVertexNeighbors(order_1[j], nb_count); + if (std::find(nb, nb + nb_count, order_2[i]) != nb + nb_count) { + inside = false; + outside = true; + order_1[order_count_1++] = order_2[i]; + for (ui k = i + 1; k < order_count_2; k++) { + order_2[k - 1] = order_2[k]; + } + order_count_2--; + break; + } + } + } + if (outside == true && inside == false) { + outside = false; + break; + } + } + } +} +bool CECIFunction(Graph *data_graph, + Graph *query_graph, + ui **&candidates, + ui *&candidates_count, + ui *&order, + ui *&provenance, + TreeNode *&tree, + std::vector>> &P_Candidates, + std::vector>> &P_Provenance) { + // std::cout <<"Initialize function: " << std::endl; + // Initial the CECI Index. + // In this case, + + double timer_all_s = omp_get_wtime(); + + // std::cout <<"a" << std::endl; + V_ID start_vertex = InitialStartVertex(data_graph, query_graph); + + // start_vertex = 6; + // std::cout <<"Start Vertex: " << start_vertex << std::endl; + + bfs(query_graph, + start_vertex, + tree, + order, + provenance); // Build the tree structure and order from query graph + + // query_count is the number of the vertexs in the query graph. + ui query_count = query_graph->getVerticesCount(); + std::vector order_index(query_count); + + // Build vertex to the order Reverse index; + for (ui i = 0; i < query_count; ++i) { + V_ID query_vertex = order[i]; + order_index[query_vertex] = i; + } + + // In the query graph + // every query tree, build their neighbour and parents information. + // std::cout <<"Begin Building Neighbourhood Informations" << std::endl; + + for (ui i = 0; i < query_count; ++i) { + V_ID u = order[i]; + // tree[u].under_level_count = 0; + tree[u].bn_count = 0; + tree[u].fn_count = 0; + ui u_nbrs_count; + + V_ID *u_nbrs = query_graph->getVertexNeighbors(u, u_nbrs_count); + for (ui j = 0; j < u_nbrs_count; ++j) { + V_ID u_nbr = u_nbrs[j]; + if (order_index[u_nbr] < order_index[u]) { + tree[u].bn[tree[u].bn_count++] = u_nbr; + } else { + tree[u].fn[tree[u].fn_count++] = u_nbr; + } + } + } + + // Initialization + ui candidates_max_count = data_graph->getGraphMaxLabelFrequency(); + + ui data_count = data_graph->getVerticesCount(); + // ui query_count = query_graph->getVerticesCount(); + ui *updated_flag = new ui[data_graph->getVerticesCount()]; + ui *flag = new ui[data_graph->getVerticesCount()]; + std::fill(flag, flag + data_graph->getVerticesCount(), 0); + + candidates_count = new ui[query_count]; + memset(candidates_count, 0, sizeof(ui) * query_count); + + candidates = new ui *[query_count]; + + ui **candidates_inter = new ui *[query_count]; + ui **candidates_index = new ui *[query_count]; + + // ui* candidates_count_inter = new ui[query_count]; + ui *candidates_count_index = new ui[query_count]; + + for (ui i = 0; i < query_count; ++i) { + candidates[i] = new ui[candidates_max_count]; + candidates_inter[i] = new ui[candidates_max_count]; + candidates_index[i] = new ui[candidates_max_count]; + } + + for (ui i = 0; i < query_count; i++) { + ZFComputeNLF( + data_graph, query_graph, order[i], candidates_count[order[i]], candidates[order[i]]); + } + std::cout << "begin" << std::endl; + + // The number of refinement is k. According to the original paper, we set k as 3. + for (ui k = 0; k < 3; ++k) { + if (k % 2 == 0) { + for (ui i = 1; i < query_count; ++i) { + V_ID u = order[i]; + TreeNode &node = tree[u]; + pruneCandidates(data_graph, + query_graph, + u, + node.bn, + node.bn_count, + candidates, + candidates_count, + flag, + updated_flag); + } + } else { + for (int i = query_count - 2; i >= 0; --i) { + V_ID u = order[i]; + TreeNode &node = tree[u]; + pruneCandidates(data_graph, + query_graph, + u, + node.fn, + node.fn_count, + candidates, + candidates_count, + flag, + updated_flag); + } + } + } + + compactCandidates(candidates, candidates_count, query_count); + + for (ui j = 0; j < query_count; j++) { + std::cout << "u->" << order[j] << "Candidates_count->" << candidates_count[order[j]] + << std::endl; + } + std::cout << "Done with Filter" << std::endl; + + candidates_count_index[order[0]] = candidates_count[order[0]]; + + for (ui k = 0; k < candidates_count[order[0]]; k++) { + candidates_index[order[0]][k] = candidates[order[0]][k]; + } + + // std::cout <<"print neighbourhood function" << std::endl; + // which pair of intersection we need + std::unordered_map>> intersetion; + // Size is the length of the query graph . + std::unordered_map> query_provenance; + // Size is . + // std::vector> data_provenance; + // >>> // + // The last item in the data_provenance is corresponding to the different candidates with the same + // provenance key in the query graph. + // + + std::unordered_map>>> + data_provenance; + // This is all + // // 11 + // + for (ui i = 0; i < query_count; ++i) { + V_ID u = order[i]; + V_ID tmp_first = INVALID_VERTEX_ID; + V_ID tmp_second = INVALID_VERTEX_ID; + if (tree[u].bn_count > 1) { + // std::cout <<"Node: (" << u << ")-> "; + tmp_first = tree[u].parent; + for (ui j = 0; j < tree[u].bn_count; j++) { + // std::cout <>>::iterator its = intersetion.begin(); + + std::unordered_map> branching; + + // int i = 0; + + // all + // Check the parent node for each intersection's pair. + + while (its != intersetion.end()) { + std::vector single_branch; + std::vector> inter = its->second; + + for (auto &k : inter) { + V_ID r = ParentNode(k.first, k.second, tree); + // All. + add_provenance(query_provenance, k.first, r); + add_provenance(query_provenance, k.second, r); + single_branch.push_back(r); + } + + if (single_branch.size() > 0) { + branching.insert(std::pair>(its->first, single_branch)); + } + its++; + } + + // Reverse BFS order to record what the provenance their parents need. + // bool need_add = query_count; + for (ui i = query_count - 1; i > 0; --i) { + V_ID u = order[i]; + V_ID u_p = tree[u].parent; + + if (query_provenance.find(u) != query_provenance.end()) { + if (query_provenance.find(u_p) != query_provenance.end()) { + for (uint64_t j = 0; j < query_provenance[u].size(); j++) { + if (std::find(query_provenance[u_p].begin(), + query_provenance[u_p].end(), + query_provenance[u][j]) == query_provenance[u_p].end()) { + query_provenance[u_p].push_back(query_provenance[u][j]); + } + } + + } else { // If the map is empty with index of u_p. + std::unordered_map>::iterator iter; + iter = query_provenance.begin(); + query_provenance.insert(iter, std::pair>(u_p, query_provenance[u])); + } + } + } + + // Print out the tree structure; + + // data_provenance; + + // Start doing for the data graph. + // + // Not a good one; + + // TE_Candidates construction and filtering. + + V_ID root = order[0]; + + // std::cout <<"Root is:" << root << std::endl; + + // We use the ID 0 as their candidates. + + // Initialize the Buffer for the Candidate and Candidate Count: + + // std::cout <<"Initialize Max Count: " << candidates_max_count << std::endl; + + // ZFComputeNLF(data_graph, query_graph, root, candidates_count[root], candidates[root]); + + // std::cout <<"Number of Candidate: " << candidates_count[root] << std::endl; + + // No Candidates with the root + if (candidates_count[root] == 0) { + std::cout << "Build Candidate Fail" << std::endl; + return false; + } + + // Building the P_Candidates. + std::vector data_visited(data_count); + + std::fill(data_visited.begin(), data_visited.end(), 0); + + std::vector data_visited_2(data_count); + + std::fill(data_visited_2.begin(), data_visited_2.end(), 0); + + std::vector visited_query(query_count); + std::fill(visited_query.begin(), visited_query.end(), false); + + visited_query[root] = true; + + // Candidates and Provenances + P_Candidates.resize(query_count); + P_Provenance.resize(query_count); + + // Initialization of the root's Candidates + // + + std::unordered_map> root_candidate; + for (ui i = 0; i < candidates_count[root]; i++) { + std::vector tmp; + tmp.push_back(candidates[root][i]); + root_candidate.insert(std::pair>(candidates[root][i], tmp)); + } + + P_Candidates[root] = root_candidate; + + // std::cout <<"Begin BFS and intersetion Function: " << std::endl; + + // Record the start provenance in the data graph. + std::unordered_map Provenance; + std::unordered_map> + Close_provenance; // >. + + // record the index and provenance for the root. + // + + // This records according to order index. + // + + if (query_provenance.find(root) != query_provenance.end()) { + std::unordered_map>> node_provenance; + for (ui i = 0; i < candidates_count[root]; i++) { + std::vector pro_v; + pro_v.push_back(candidates[root][i]); + std::unordered_map> pro; + std::unordered_map>::iterator iter = pro.begin(); + pro.insert(iter, std::pair>(root, pro_v)); + + node_provenance.insert( + std::pair>>(candidates[root][i], pro)); + } + + data_provenance.insert( + std::pair>>>( + root, node_provenance)); + } + + // return 0; + + // This one + // + + for (ui i = 1; i < query_count; ++i) { + // std::cout <<"####check candidate 4->0 is->" << candidates[4][0] << std::endl; + V_ID u = order[i]; + // TreeNode& u_node = tree[u]; + bool do_interset = false; + if (intersetion.find(u) != intersetion.end()) { + // std::cout <<"In the key of Intersetion." << std::endl; + do_interset = true; + } + // sets of frontiers and count for parent node. + + ui u_l = query_graph->getVertexLabel(u); + ui u_d = query_graph->getVertexDegree(u); + ui u_f = tree[u].parent; + + // get NLF of current node. + // std::unordered_map* u_nlf = query_graph->getVertexNLF(u); + candidates_count_index[u] = 0; + // initial each candidate_count of each node to be 0. + // V_ID* frontiers = candidates[u]; + std::unordered_map> nb_interset; + + for (ui j = 0; j < candidates_count[u]; ++j) { + V_ID v = candidates[u][j]; + data_visited_2[v] = 1; + } + + if (do_interset && 1 == 2) { + int length_inter = intersetion[u].size(); + for (int d = 0; d < length_inter; d++) { + std::vector tt; + if (visited_query[u] == true) { + // ui u_1 = intersetion[u][d].first; + ui u_2 = intersetion[u][d].second; + ui u_2_f = tree[u_2].parent; + + // v_1 and u_1 will be the parent nodes. + std::unordered_map> frontiers_2 = P_Candidates[u_2]; + ui frontiers_count_2 = frontiers_2.size(); + for (auto it = nb_interset.cbegin(); it != nb_interset.cend();) { + ui v_1 = it->first; + bool find_v_1 = false; + std::unordered_map>::iterator item = frontiers_2.begin(); + std::vector tmp_inter; + for (ui k = 0; k < frontiers_count_2; ++k) { + ui v_2_f = item->first; // get the parent ID fron the Candidate index. + ui pro_node = branching[u][d]; + bool if_pro = false; + for (ui k_1 = 0; k_1 < data_provenance[u_f][v_1][pro_node].size(); k_1++) { + for (ui k_2 = 0; k_2 < data_provenance[u_2_f][v_2_f][pro_node].size(); k_2++) { + if (data_provenance[u_f][v_1][pro_node][k_1] == + data_provenance[u_2_f][v_2_f][pro_node][k_2]) { + if_pro = true; + } + } + } + if (if_pro == true) { + for (unsigned int v_2 : item->second) { + ui data_nbrs_count_2; + V_ID *data_nbrs_2 = data_graph->getVertexNeighbors(v_2, data_nbrs_count_2); + for (unsigned int p : it->second) { + for (ui q = 0; q < data_nbrs_count_2; q++) { + if (p == data_nbrs_2[q] && + find(tmp_inter.begin(), tmp_inter.end(), p) == tmp_inter.end()) { + tmp_inter.push_back(data_nbrs_2[q]); + find_v_1 = true; + } + } + } + } + } + item++; + } + if (tmp_inter.size() > 0) { + find_v_1 = true; + nb_interset[v_1] = tmp_inter; + } + if (find_v_1 == false) { + nb_interset.erase(it++); + } else { + ++it; + } + } + // Else + } else { + visited_query[u] = true; + ui u_1 = intersetion[u][d].first; + ui u_2 = intersetion[u][d].second; + ui u_1_f = u_1; + ui pro_node = branching[u][d]; + ui u_2_f = tree[u_2].parent; + if (u_1 != root) { + u_1_f = tree[u_1].parent; + } + if (DEBUG == 1) { + std::cout << u_1 << " " << u_2 << "Unvisited, Pro-->" << pro_node << std::endl; + } + std::unordered_map> frontiers_1 = P_Candidates[u_1]; + std::unordered_map> frontiers_2 = P_Candidates[u_2]; + // ui frontiers_count_1 = frontiers_1.size(); + // ui frontiers_count_2 = frontiers_2.size(); + if (u_1 == root || u_1 == pro_node) { + u_1_f = u_1; + for (const auto &it_1 : frontiers_1) { + std::vector c_1 = it_1.second; + for (unsigned int v_1 : c_1) { + // std::vector tmp_inter; + for (const auto &it_2 : frontiers_2) { + ui v_2_f = it_2.first; + bool if_pro = false; + for (unsigned int k_2 : data_provenance[u_2_f][v_2_f][pro_node]) { + if (v_1 == k_2) { + if_pro = true; + } + } + std::vector c_2 = it_2.second; + std::vector tmp_inter; + if (if_pro == true) { + ui data_nbrs_count_1; + V_ID *data_nbrs_1 = data_graph->getVertexNeighbors(v_1, data_nbrs_count_1); + + for (unsigned int v_2 : c_2) { + ui data_nbrs_count_2; + V_ID *data_nbrs_2 = data_graph->getVertexNeighbors(v_2, data_nbrs_count_2); + // label filter and degree filter should be added here + for (ui p = 0; p < data_nbrs_count_1; p++) { + for (ui q = 0; q < data_nbrs_count_2; q++) { + ui t = data_nbrs_1[p]; + // std::cout <<"Nbrs_1->" << t << "Nbrs_2->" << data_nbrs_2[q] << + // std::endl; + if (t == data_nbrs_2[q] && data_graph->getVertexLabel(t) == u_l && + data_graph->getVertexDegree(t) >= u_d) { + if (std::find(tmp_inter.begin(), tmp_inter.end(), t) == + tmp_inter.end()) { + tmp_inter.push_back(t); + } + } + } + } + } + } + + if (tmp_inter.size() > 0) { + nb_interset.insert(std::pair>(v_1, tmp_inter)); + } + } + } + } + } else { + // u_1 is neither root nor parent. + for (const auto &it_1 : frontiers_1) { + ui v_1_f = it_1.first; + std::vector c_1 = it_1.second; + for (unsigned int v_1 : c_1) { + std::vector tmp_inter; + for (const auto &it_2 : frontiers_2) { + ui v_2_f = it_2.first; + bool if_pro = false; + for (ui k_1 = 0; k_1 < data_provenance[u_1_f][v_1_f][pro_node].size(); k_1++) { + for (ui k_2 = 0; k_2 < data_provenance[u_2_f][v_2_f][pro_node].size(); k_2++) { + if (data_provenance[u_1_f][v_1_f][pro_node][k_1] == + data_provenance[u_2_f][v_2_f][pro_node][k_2]) { + if_pro = true; + break; + } + } + if (if_pro) { + break; + } + } + if (if_pro) { + std::vector c_2 = it_2.second; + ui data_nbrs_count_1; + V_ID *data_nbrs_1 = data_graph->getVertexNeighbors(v_1, data_nbrs_count_1); + for (unsigned int v_2 : c_2) { + ui data_nbrs_count_2; + V_ID *data_nbrs_2 = data_graph->getVertexNeighbors(v_2, data_nbrs_count_2); + for (ui p = 0; p < data_nbrs_count_1; p++) { + for (ui q = 0; q < data_nbrs_count_2; q++) { + ui t = data_nbrs_1[p]; + if (t == data_nbrs_2[q] && data_graph->getVertexLabel(t) == u_l && + data_graph->getVertexDegree(t) >= u_d) { + if (std::find(tmp_inter.begin(), tmp_inter.end(), t) == + tmp_inter.end()) { + tmp_inter.push_back(t); + } + } + } + } + } + } + } + if (tmp_inter.size() > 0) { + nb_interset.insert(std::pair>(v_1, tmp_inter)); + } + } + } + } + } + // V_ID u_p = tree[u].parent; + } + + bool find_u = false; + + std::unordered_map>> node_provenance; + + if (query_provenance.find(u) != query_provenance.end()) { + // All. + find_u = true; + } + V_ID u_p = tree[u].parent; + + for (auto &itt : nb_interset) { + V_ID v_f = itt.first; + auto tmp = P_Candidates[u].emplace(v_f, std::vector()); + for (unsigned int v : itt.second) { + if (data_visited_2[v] == 1) { + tmp.first->second.push_back(v); + if (data_visited[v] == 0) { + data_visited[v] = 1; + candidates_index[u][candidates_count_index[u]++] = v; + } + } + } + if (tmp.first->second.size() == 0) { + P_Candidates[u].erase(v_f); + + // frontiers[j] = INVALID_VERTEX_ID; + for (ui k = 0; k < tree[u_p].children_count; ++k) { + V_ID u_c = tree[u_p].children[k]; + if (visited_query[u_c]) { + P_Candidates[u_c].erase(v_f); + } + } + } + } + if (find_u) { + std::unordered_map> frontiers_1 = P_Candidates[u]; + + intersection(query_provenance, data_provenance, u, node_provenance, u_p, frontiers_1); + } + if (node_provenance.size() != 0) { + data_provenance.insert( + std::pair>>>( + u, node_provenance)); + } + // std::cout <<"tmp print it all EndEnd !!!" << std::endl; + for (ui j = 0; j < candidates_count_index[u]; ++j) { + V_ID v = candidates_index[u][j]; + data_visited[v] = 0; // reset the data_visited for the data graph. + } + } + if (visited_query[u] == false) { + // BFS function + // std::cout <<"" << std::endl; + // std::cout <<"BFS node: { " << u << " }-> "; + visited_query[u] = true; + // All + V_ID u_p = tree[u].parent; + + // Records u's label and degree. + // std::cout <<"parent is->" << u_p << std::endl; + V_ID *frontiers = candidates_index[u_p]; + ui frontiers_count = candidates_count_index[u_p]; + // std::cout <<"Frontier Candidate: "; + if (DEBUG) { + std::cout << u << " is BFS and parent node is-->" << u_p << std::endl; + std::cout << "Frontier count is --> " << frontiers_count << std::endl; + } + + // std::cout <<"parent is->" << u_p << ", frontier_count->" << frontiers_count << std::endl; + bool find_u = false; + if (query_provenance.find(u) != query_provenance.end()) { + // All. + find_u = true; + } + std::unordered_map>> node_provenance; + + for (ui j = 0; j < frontiers_count; ++j) { + V_ID v_f = frontiers[j]; // for all its frontiers nodes + // std::cout <<"," << v_f ; + // If it's not a valid one for building the CECI idnex, skip + if (v_f == INVALID_VERTEX_ID) { + std::cout << " " << v_f << "is a invalid id"; + continue; + } + ui data_nbrs_count; + V_ID *data_nbrs = data_graph->getVertexNeighbors(v_f, data_nbrs_count); + // std::cout <<"V_f is->" << v_f << std::endl; + // This is for the vector of candidates. + auto tmp = P_Candidates[u].emplace(v_f, std::vector()); + // std::cout <<"VF is ->" << v_f << " "; + // P_Candidates + // bool is_provenance = false; + // std::cout <<"Neighbourhood is: "; + for (ui k = 0; k < data_nbrs_count; ++k) { + V_ID v = data_nbrs[k]; + // std::cout <is valid" << std::endl; + tmp.first->second.push_back(v); + if (data_visited[v] == 0) { + data_visited[v] = 1; + candidates_index[u][candidates_count_index[u]++] = v; + } + + if (find_u) { + if (node_provenance.find(v) == node_provenance.end()) { + // All. + std::unordered_map> pro; + for (uint64_t g = 0; g < query_provenance[u].size(); g++) { + if (u == query_provenance[u][g]) { + // add current node and v to the provenance information if this node is one of + // the required provenance ID. + std::vector pro_v; + pro_v.push_back(v); + pro.insert(std::pair>(query_provenance[u][g], pro_v)); + // std::cout <<" !!!!!!!!!!! " << pro[query_provenance[u][g]][0] << + // std::endl; + } else if (data_provenance[u_p][v_f][query_provenance[u][g]].size() != 0) { + // pass the provenance information from the parent node to its child node. + // + // + if (pro.find(query_provenance[u][g]) == pro.end()) { + pro.insert(std::pair>( + query_provenance[u][g], + data_provenance[u_p][v_f][query_provenance[u][g]])); + } else { + std::vector tmp_v = data_provenance[u_p][v_f][query_provenance[u][g]]; + for (unsigned int &m : tmp_v) { + std::vector::iterator q_t = + find(node_provenance[v][query_provenance[u][g]].begin(), + node_provenance[v][query_provenance[u][g]].end(), + m); + if (q_t == node_provenance[v][query_provenance[u][g]].end()) { + node_provenance[v][query_provenance[u][g]].push_back(m); + } + } + } + } + } + if (pro.size() != 0) { + node_provenance.insert( + std::pair>>(v, pro)); + } + } else { + for (uint64_t g = 0; g < query_provenance[u].size(); g++) { + if (data_provenance[u_p][v_f][query_provenance[u][g]].size() != 0) { + // pass the provenance information from the parent node to its child node. + if (node_provenance[v].find(query_provenance[u][g]) == + node_provenance[v].end()) { + node_provenance[v].insert(std::pair>( + query_provenance[u][g], + data_provenance[u_p][v_f][query_provenance[u][g]])); + // std::cout <<"*********" << pro[query_provenance[u][g]][0] << std::endl; + } else { + std::vector tmp_v = data_provenance[u_p][v_f][query_provenance[u][g]]; + for (unsigned int &m : tmp_v) { + std::vector::iterator q_t = + find(node_provenance[v][query_provenance[u][g]].begin(), + node_provenance[v][query_provenance[u][g]].end(), + m); + if (q_t == node_provenance[v][query_provenance[u][g]].end()) { + node_provenance[v][query_provenance[u][g]].push_back(m); + } + } + } + } + } + } + } + } + } + } + + if (tmp.first->second.empty()) { + // set it as invalid + // std::cout <<" Erase it. "<< std::endl; + P_Candidates[u].erase(v_f); + + frontiers[j] = INVALID_VERTEX_ID; + for (ui k = 0; k < tree[u_p].children_count; ++k) { + V_ID u_c = tree[u_p].children[k]; + if (visited_query[u_c]) { + // std::cout <<"u_c is " << u_c <<", v_f is " << v_f; + P_Candidates[u_c].erase(v_f); + } + } + } + } + + if (find_u && node_provenance.size() != 0) { + data_provenance.insert( + std::pair>>>( + u, node_provenance)); + } + + if (candidates_count_index[u] == 0) { + std::cout << "Node: " << u << " Fail" << std::endl; + // std::cout <<"Pro: " << std::endl; + return false; + } + // flag reset. + for (ui j = 0; j < candidates_count_index[u]; ++j) { + V_ID v = candidates_index[u][j]; + data_visited[v] = 0; // reset the data_visited for the data graph. + } + } + + for (ui j = 0; j < candidates_count[u]; ++j) { + V_ID v = candidates[u][j]; + data_visited_2[v] = 0; + } + } + // Temp print + + if (DEBUG) { + std::cout << " ### Reverse Refinement" << std::endl; + } + + std::cout << "Refinement->" << std::endl; + + for (int i = query_count - 1; i >= 0; i--) { + // std::cout <<"Current u->"; + ui u = order[i]; + // std::cout < ######## P->" << P_Candidates[u].size() << std::endl; + } + + int all_cc = 0; + std::unordered_map> resss; + local_optimization(resss, + query_count, + order_index, + intersetion, + order, + tree, + P_Candidates, + data_provenance, + data_graph, + query_graph); + + all_cc = resss.size(); + all_cc = all_cc + 1 - 1; + // int ress = 0; + + // Enumeration + // cuTS enumeration: + // (1) + + // std::cout <<"#### all function -> ####" << std::endl << std::endl; + ui **candidates_2 = new ui *[query_count]; + int64_t **parent_offset = new int64_t *[query_count]; + int64_t *candidates_l = new int64_t[query_count]; + ui *parent_l = new ui[query_count]; + // bool* visited_d = new bool[candidates_max_count]; + + for (ui i = 0; i < query_count; ++i) { + candidates_2[i] = new ui[Q_LIMIT]; + parent_offset[i] = new int64_t[Q_LIMIT]; + parent_l[i] = 0; + candidates_l[i] = 0; + } + // Enumeration: + // (1) Process the query graph and find out all the pattern edges information. + + std::vector> pattern_e; + // std::vector>>> data_e; + std::vector pattern_candidates; + for (ui i = 0; i < query_count - 1; ++i) { + std::vector tmp_e; + V_ID u = order[i]; + ui u_nbrs_count; + int tmp_cc = candidates_count[u]; + /*unordered_map> frontiers_1 = P_Candidates[u]; +std::cout <second.size(); + }*/ + + V_ID *u_nbrs = query_graph->getVertexNeighbors(u, u_nbrs_count); + for (ui j = 0; j < u_nbrs_count; ++j) { + V_ID u_nbr = u_nbrs[j]; + // If this node is not their parent nodes and has less index(which means haven't visited yet). + if (order_index[u_nbr] > order_index[u]) { + // Recordes the pattern edges information. + tmp_e.push_back(u_nbr); + } + } + pattern_e.push_back(tmp_e); + pattern_candidates.push_back(tmp_cc); + // std::cout <<"Node->" << u << ", Candidates:" << tmp_cc << ",order->" << i << std::endl; + } + + V_ID u_2 = order[query_count - 1]; + // ui u_nbrs_count; + ui tmp_cc = candidates_count[u_2]; + + /*unordered_map> frontiers_1 = P_Candidates[u_2]; + for (auto it_1 = frontiers_1.cbegin(); it_1 != frontiers_1.cend(); it_1 ++) { + tmp_cc += it_1->second.size(); + }*/ + + pattern_candidates.push_back(tmp_cc); + + // (2) Process the data graph to find out all the candidates for the data graph. + + // std::cout <<"end 1" << std::endl; + + // (3) + // This is all. Partition the two subgraph by calculating their cartesian && the candidates + // of edges. + // Partition the query graph into two subgroup + + int cartesian[100]; + ui r = order[0]; + ui final_group; + + // (4) Enumerate local result + + ui *order_1 = new ui[100]; + ui *order_2 = new ui[100]; + ui order_count_1 = 0; + ui order_count_2 = 0; + + std::cout << "in" << std::endl; + + bool split = false; + + if (split == false) { + cartesian_s(cartesian, + order, + order_index, + tree, + pattern_candidates, + r, + pattern_e, + final_group, + order_1, + order_2, + order_count_1, + order_count_2, + query_count); + } else { + std::string myText; + + // Read from the text file + std::ifstream MyReadFile("fiedler"); + + // Use a while loop together with the getline() function to read the file line by line + // int oo = 0; + getline(MyReadFile, myText); + + // char type; + V_ID id; + L_ID class_id; + while (MyReadFile >> id) { + MyReadFile >> class_id; + + std::cout << id << "," << class_id << std::endl; + } + + // Close the file + MyReadFile.close(); + return false; + // Read the partition result. + } + + // Here is the section for the coding. + // + + bool inside = false; + bool outside = false; + ui counnt = 0; + ui *order_1_d = new ui[query_count]; + ui *order_2_d = new ui[query_count]; + ui order_1_d_count = order_count_1; + ui order_2_d_count = order_count_2; + + for (ui i = 0; i < order_count_1; i++) { + order_1_d[i] = order_1[i]; + } + + for (ui i = 0; i < order_count_2; i++) { + order_2_d[i] = order_2[i]; + } + + CheckRepartiton1( + query_graph, order_1, order_2, order_count_1, order_count_2, inside, outside, counnt); + + // if (order_count_1 > 1 ) { + counnt = 0; + inside = false; + outside = false; + CheckRepartiton2( + query_graph, order_1, order_2, order_count_1, order_count_2, inside, outside, counnt); + bool nodo = false; + if (order_count_1 <= 1 || order_count_2 <= 1) { + // nodo = true; + } + if (order_count_1 <= 2 || order_count_2 <= 2) { + // nodo = true; + std::cout << "Should not do it" << std::endl; + order_count_1 = order_1_d_count; + order_count_2 = order_2_d_count; + for (ui i = 0; i < order_count_1; i++) { + order_1[i] = order_1_d[i]; + } + for (ui i = 0; i < order_count_2; i++) { + order_2[i] = order_2_d[i]; + } + } + + static V_ID *visit_local_2 = new V_ID[data_count]; + + static bool *visited = new bool[data_count]; + std::fill(visited, visited + data_count, false); + Index_To_Candidate(candidates_index, + candidates_count_index, + P_Candidates, + query_count, + data_count, + order, + visited); + + for (ui j = 0; j < query_count; j++) { + std::cout << "u->" << order[j] << "Candidates_count->" << candidates_count_index[order[j]] + << std::endl; + } + + for (ui i = 1; i < order_count_1; i++) { + for (ui j = i + 1; j < order_count_1; j++) { + if (candidates_count_index[order_1[i]] > 1.5 * candidates_count_index[order_1[j]]) { + ui tmp10 = order_1[i]; + order_1[i] = order_1[j]; + order_1[j] = tmp10; + } + } + } + + for (ui i = 1; i < order_count_2; i++) { + for (ui j = i + 1; j < order_count_2; j++) { + if (candidates_count_index[order_2[i]] > 1.5 * candidates_count_index[order_2[j]]) { + ui tmp10 = order_2[i]; + order_2[i] = order_2[j]; + order_2[j] = tmp10; + } + } + } + + std::cout << "out" << std::endl; + + // std::cout <<"Test 2" << std::endl; + bool *p_3_1 = new bool[query_count]; + + bool *p_3_2 = new bool[query_count]; + + for (ui i = 0; i < query_count; i++) { + p_3_1[i] = false; + p_3_2[i] = false; + } + + // (5.1) Load edges information,std::cout <<"Load edge->" << std::endl; + for (ui i = 1; i < order_count_1; i++) { + for (ui j = 1; j < order_count_2; j++) { + // std::cout <<"Tmp:" << tmp << std::endl; + // if (order_1[i] != order_2[j]) { + if (order_index[order_2[j]] > order_index[order_1[i]]) { + ui tmp = order_index[order_1[i]]; + if (std::find(pattern_e[tmp].begin(), pattern_e[tmp].end(), order_2[j]) != + pattern_e[tmp].end()) { + p_3_1[order_1[i]] = true; + p_3_2[order_2[j]] = true; + } + } else if (order_index[order_2[j]] < order_index[order_1[i]]) { + ui tmp = order_index[order_2[j]]; + if (std::find(pattern_e[tmp].begin(), pattern_e[tmp].end(), order_1[i]) != + pattern_e[tmp].end()) { + p_3_1[order_1[i]] = true; + p_3_2[order_2[j]] = true; + } + } + + // if( order_1[i] != order_2[j] and query_graph->getVertexLabel(order_1[i]) == + // query_graph->getVertexLabel(order_2[j])) { + // p_3_1[order_1[i]] = true; + // p_3_2[order_2[j]] = true; + // } + //} + } + } + + // if (1 == 2) { + // ui move = 0; + // for (ui i = 1; i < order_count_1 - move; i++) { + // if (p_3_1[order_1[i]] == false) { + // bool doit = true; + // ui nb_count; + // ui *nb = query_graph->getVertexNeighbors(order_1[i], nb_count); + + // for (ui j = 0; j < nb_count; j++) { + // bool inorder = false; + // for (ui k = 0; k < order_count_1; k++) { + // if (order_1[k] == nb[j]) { + // inorder = true; + // } + // } + // if (nodo == false) { + // if (candidates_count_index[order_1[i]] < 1.5 * candidates_count_index[nb[j]] && + // inorder) { + // doit = false; + // p_3_1[order_1[i]] = true; + // } + // } else { + // if (candidates_count_index[order_1[i]] < 1.2 * candidates_count_index[nb[j]] && + // inorder) { + // doit = false; + // p_3_1[order_1[i]] = true; + // } + // } + // } + + // if (doit) { + // ui tmp2 = order_1[i]; + // move += 1; + // for (ui j = i; j < order_count_1 - 1; j++) { + // ui tmp = order_1[j]; + // order_1[j] = order_1[j + 1]; + // order_1[j + 1] = tmp; + // } + // if (order_1[i] != tmp2) { + // i -= 1; + // } + // } + // } + // } + + // move = 0; + // for (ui i = 1; i < order_count_2 - move; i++) { + // if (p_3_2[order_2[i]] == false) { + // bool doit = true; + + // std::cout << std::endl; + // ui nb_count; + // ui *nb = query_graph->getVertexNeighbors(order_2[i], nb_count); + + // for (ui j = 0; j < nb_count; j++) { + // bool inorder = false; + // for (ui k = 0; k < order_count_2; k++) { + // if (order_2[k] == nb[j]) { + // inorder = true; + // } + // } + // if (nodo == false) { + // if (candidates_count_index[order_2[i]] < 1.5 * candidates_count_index[nb[j]] && + // inorder) { + // doit = false; + // p_3_2[order_2[i]] = true; + // } + // } else { + // if (candidates_count_index[order_2[i]] < 1.2 * candidates_count_index[nb[j]] && + // inorder) { + // doit = false; + // p_3_2[order_2[i]] = true; + // } + // } + // } + + // if (doit) { + // ui tmp2 = order_2[i]; + // move += 1; + // for (ui j = i; j < order_count_2 - 1; j++) { + // ui tmp = order_2[j]; + // order_2[j] = order_2[j + 1]; + // order_2[j + 1] = tmp; + // } + // if (order_2[i] != tmp2) { + // i -= 1; + // } + // } + // } + // } + // } + + static V_ID **res_1 = new V_ID *[query_count]; + static V_ID **res_2 = new V_ID *[query_count]; + static V_ID **res_1_n = new V_ID *[query_count]; + static V_ID **res_2_n = new V_ID *[query_count]; + + static int64_t **parent_offset_1 = new int64_t *[query_count]; + static int64_t **children_offset_1 = new int64_t *[query_count]; + static int64_t **children_offset_2 = new int64_t *[query_count]; + static int64_t **parent_offset_2 = new int64_t *[query_count]; + + static int64_t **parent_offset_1_n = new int64_t *[query_count]; + static int64_t **parent_offset_2_n = new int64_t *[query_count]; + + int64_t *candidates_l_1 = new int64_t[query_count]; + int64_t *candidates_l_2 = new int64_t[query_count]; + + int64_t *candidates_l_1_n = new int64_t[query_count]; + int64_t *candidates_l_2_n = new int64_t[query_count]; + + for (ui i = 0; i < query_count; ++i) { + res_1[i] = new V_ID[Q_LIMIT]; + res_2[i] = new V_ID[Q_LIMIT]; + res_1_n[i] = new V_ID[Q_LIMIT]; + res_2_n[i] = new V_ID[Q_LIMIT]; + + parent_offset_1[i] = new int64_t[Q_LIMIT]; + parent_offset_2[i] = new int64_t[Q_LIMIT]; + + parent_offset_1_n[i] = new int64_t[Q_LIMIT]; + parent_offset_2_n[i] = new int64_t[Q_LIMIT]; + + children_offset_1[i] = new int64_t[Q_LIMIT]; + children_offset_2[i] = new int64_t[Q_LIMIT]; + + candidates_l_1[i] = 0; + candidates_l_2[i] = 0; + + candidates_l_1_n[i] = 0; + candidates_l_2_n[i] = 0; + } + + // int NTHREADS = 1, nthreads; + int64_t total_result = 0; + // double sum[NTHREADS]; + double timer_start1 = omp_get_wtime(); + // omp_set_num_threads(NTHREADS); + + std::vector order_index_1(query_count); + std::vector order_index_2(query_count); + + // std::cout <<"Group original->"; + for (ui i = 0; i < query_count; i++) { + ui query_vertex = order[i]; + std::cout << query_vertex << ","; + order_index_1[query_vertex] = 99999; + order_index_2[query_vertex] = 99999; + } + + std::cout << "Group 1->" << std::endl; + + for (ui i = 0; i < order_count_1; i++) { + ui query_vertex = order_1[i]; + std::cout << query_vertex << ","; + order_index_1[query_vertex] = i; + } + + std::cout << std::endl; + + std::cout << "Group 2->" << std::endl; + for (ui i = 0; i < order_count_2; i++) { + ui query_vertex = order_2[i]; + std::cout << query_vertex << ","; + order_index_2[query_vertex] = i; + } + + std::cout << std::endl; + + // static bool *visit_all = new bool[data_count*data_count]; + // std::fill(visit_all, visit_all+data_count*data_count, false); + /* + for(ui i=0; i < data_count; i++) { + ui data_count_1; + V_ID *data_nb = data_graph->getVertexNeighbors(i, data_count_1); + for(ui j=0; j < data_count; j++) { + visit_all[i*data_count + j] = true; + } + }*/ + + // Morphism of partition 1. + static bool morphism_1[32]; + + for (ui i = 0; i < order_count_1; i++) { + morphism_1[i] = false; + for (ui j = 0; j < order_count_1; j++) { + if (i != j && + query_graph->getVertexLabel(order_1[i]) == query_graph->getVertexLabel(order_1[j])) { + morphism_1[i] = true; + break; + } + } + } + + // Morphism of partition 2. + /* + static bool morphism_2[32]; + for (ui i = 0; i < order_count_2; i ++) { + morphism_2[i] = false; + for (ui j = 0; j < order_count_2; j ++) { + if (i!=j and query_graph->getVertexLabel(order_2[i]) == + query_graph->getVertexLabel(order_2[j])) { morphism_2[i] = true; break; + } + } + } + */ + + std::cout << "Middle->" << std::endl; + // Connection and offset. + // static V_ID *visit_local_2 = new V_ID[data_count]; + + static V_ID *connection = new V_ID[query_count * query_count]; + + static V_ID *offset = new V_ID[query_count]; + + for (ui i = 0; i < query_count; ++i) { + offset[i] = 0; + if (i != 0) { + offset[i] = offset[i - 1]; + } + V_ID u = order[i]; + // V_ID u_p = tree[u].parent; + ui u_nbrs_count; + V_ID *u_nbrs = query_graph->getVertexNeighbors(u, u_nbrs_count); + for (ui j = 0; j < u_nbrs_count; ++j) { + V_ID u_nbr = u_nbrs[j]; + // If this node is not their parent nodes and has less index(which means haven't visited yet). + if (order_index[u_nbr] < order_index[u]) { + connection[offset[i]] = order_index[u_nbr]; + offset[i] += 1; + } + /*if (order_index[u_nbr] > order_index[u]) { + connection[offset[i]] = order_index[u_nbr]; + offset[i] += 1; + }*/ + } + } + + static V_ID *connection_1 = new V_ID[query_count * query_count]; + static V_ID *offset_1 = new V_ID[query_count]; + for (ui i = 0; i < order_count_1; ++i) { + offset_1[i] = 0; + if (i != 0) { + offset_1[i] = offset_1[i - 1]; + } + + V_ID u = order_1[i]; + // V_ID u_p = tree[u].parent; + ui u_nbrs_count; + V_ID *u_nbrs = query_graph->getVertexNeighbors(u, u_nbrs_count); + for (ui j = 0; j < u_nbrs_count; ++j) { + V_ID u_nbr = u_nbrs[j]; + // If this node is not their parent nodes and has less index(which means haven't visited yet). + if (order_index_1[u_nbr] < order_index_1[u]) { + connection_1[offset_1[i]] = order_index_1[u_nbr]; + offset_1[i] += 1; + } + /*if (order_index[u_nbr] > order_index[u]) { + connection[offset[i]] = order_index[u_nbr]; + offset[i] += 1; + }*/ + } + } + + static V_ID *connection_2 = new V_ID[query_count * query_count]; + static V_ID *offset_2 = new V_ID[query_count]; + for (ui i = 0; i < order_count_2; ++i) { + offset_2[i] = 0; + if (i != 0) { + offset_2[i] = offset_2[i - 1]; + } + V_ID u = order_2[i]; + // V_ID u_p = tree[u].parent; + ui u_nbrs_count; + V_ID *u_nbrs = query_graph->getVertexNeighbors(u, u_nbrs_count); + for (ui j = 0; j < u_nbrs_count; ++j) { + V_ID u_nbr = u_nbrs[j]; + // If this node is not their parent nodes and has less index(which means haven't visited yet). + if (order_index_2[u_nbr] < order_index_2[u]) { + connection_2[offset_2[i]] = order_index_2[u_nbr]; + offset_2[i] += 1; + } + } + } + + int64_t res_all_1 = 0; + ui u = order_1[0]; + static V_ID res_r_1[32]; + + std::cout << "Before" << std::endl; + + auto f_4 = std::chrono::steady_clock::now(); + + // Local Enumeration 1 + u = order_1[0]; + // total_1 = 0; + // int current_order = 0; + std::vector local_4; + // std::vector<>isll + + ui u_1_0 = order_1[0]; + for (ui i = 0; i < candidates_count[u_1_0]; i++) { + res_1[0][i] = candidates[u_1_0][i]; + res_all_1 += 1; + } + candidates_l_1[0] = res_all_1; + + // (5.1) Load edges information,std::cout <<"Load edge->" << std::endl; + // std::cout <<"Test 2" << std::endl; + std::vector> p_e; + + for (ui i = 1; i < order_count_1; i++) { + for (ui j = 1; j < order_count_2; j++) { + // std::cout <<"Tmp:" << tmp << std::endl; + // if (order_1[i] != order_2[j]) { + if (order_index[order_2[j]] > order_index[order_1[i]]) { + ui tmp = order_index[order_1[i]]; + if (std::find(pattern_e[tmp].begin(), pattern_e[tmp].end(), order_2[j]) != + pattern_e[tmp].end()) { + p_e.emplace_back(i, j); + } + } else if (order_index[order_2[j]] < order_index[order_1[i]]) { + ui tmp = order_index[order_2[j]]; + if (std::find(pattern_e[tmp].begin(), pattern_e[tmp].end(), order_1[i]) != + pattern_e[tmp].end()) { + p_e.emplace_back(i, j); + } + } + //} + } + } + // std::cout << "This" << p_e.size() << std::endl; + + // (5.2) Load isomorphism information. + // . . . + std::vector> i_e; + + for (ui i = 1; i < order_count_1; i++) { + for (ui j = 1; j < order_count_2; j++) { + if (order_1[i] != order_2[j] && + query_graph->getVertexLabel(order_1[i]) == query_graph->getVertexLabel(order_2[j])) { + i_e.emplace_back(i, j); + } + } + } + + L_ID *l_1 = new L_ID[order_count_1]; + L_ID *l_2 = new L_ID[order_count_2]; + bool *mor = new bool[order_count_2]; + + V_ID *con_2 = new V_ID[order_count_2]; + + V_ID *con_2_2 = new V_ID[order_count_1]; + + for (ui i = 0; i < order_count_1; i++) { + l_1[i] = query_graph->getVertexLabel(order_1[i]); + con_2_2[i] = 0; + } + + for (ui i = 0; i < order_count_2; i++) { + con_2[i] = 0; + mor[i] = false; + l_2[i] = query_graph->getVertexLabel(order_2[i]); + for (ui j = 0; j < i; j++) { + if (l_2[i] == l_2[j]) { + mor[i] = true; + mor[j] = true; + } + } + } + + std::vector q_all; + ui p_1 = 0; + ui q_1 = 0; + for (auto &t : p_e) { + ui p = t.first; + ui q = t.second; + con_2[q] += 1; + con_2_2[p] += 1; + + if (p > p_1) { + p_1 = p; + } + if (q > q_1) { + q_1 = q; + } + } + + for (auto &t : i_e) { + // std::cout <<"p_1 and i_e[t].first" << p_1 << " " << i_e[t].first << std::endl; + if (t.first > p_1) { + p_1 = t.first; + } + if (t.second > q_1) { + q_1 = t.second; + } + } + + ui p_1_1 = p_1 + 1; + ui q_1_1 = q_1 + 1; + + static bool morphism[32]; + + for (ui i = 0; i < query_count; i++) { + morphism[i] = false; + for (ui j = 0; j < query_count; j++) { + if (i != j && + query_graph->getVertexLabel(order[i]) == query_graph->getVertexLabel(order[j])) { + morphism[i] = true; + break; + } + } + } + + auto f_5 = std::chrono::steady_clock::now(); + + if (order_count_2 <= 1) { + ui bfs_order_1 = 1; + + std::cout << "Enumerate 1" << std::endl; + enumeration_bfs2(visited, + res_all_1, + candidates_index, + candidates_count_index, + order, + tree, + res_r_1, + bfs_order_1, + query_count, + query_graph, + data_graph, + order_index, + connection, + offset, + res_1, + parent_offset_1, + candidates_l_1, + visit_local_2, + candidates_max_count, + morphism); + + int64_t embedding = exploreGraphQLStyle(data_graph, + query_graph, + candidates_index, + candidates_count_index, + order, + 10000000, + res_all_1); + + f_5 = std::chrono::steady_clock::now(); + + std::cout << embedding << "," << 0 << "," + << std::chrono::duration_cast(f_5 - f_4).count() / + 1000000000.0 + << "," + << std::chrono::duration_cast(f_5 - f_4).count() / + 1000000000.0 + << "," << res_all_1 << std::endl; + + // freopen("output.txt", "a", stdout); + // std::cout <(f_5 - f_4).count()/1000000000.0 << + // "," <(f_5 - f_4).count()/1000000000.0 << + // "," << res_all_1 << std::endl; fclose(stdout); + return true; + + } else { + ui bfs_order_1 = 1; + + std::cout << "Enumerate 1" << std::endl; + enumeration_bfs2(visited, + res_all_1, + candidates_index, + candidates_count_index, + order_1, + tree, + res_r_1, + bfs_order_1, + order_count_1, + query_graph, + data_graph, + order_index_1, + connection_1, + offset_1, + res_1, + parent_offset_1, + candidates_l_1, + visit_local_2, + candidates_max_count, + morphism_1); + + f_5 = std::chrono::steady_clock::now(); + } + std::cout << "Done with enumeration 1" << std::endl; + + auto f_6 = std::chrono::steady_clock::now(); + + static V_ID *visit_local_4 = new V_ID[data_count * query_count]; + std::fill(visit_local_4, visit_local_4 + data_count * query_count, 0); + + // (5) Parallel combination. + // In this section, we are using the parallel method. + + static int *res_s = new int[Q_LIMIT]; + + static bool **valid_1 = new bool *[query_count]; + static bool **valid_2 = new bool *[query_count]; + + for (ui i = 0; i < query_count; i++) { + valid_1[i] = new bool[candidates_l_1[order_count_1 - 1]]; + valid_2[i] = new bool[candidates_l_2[order_count_2 - 1]]; + std::fill(valid_1[i], valid_1[i] + candidates_l_1[order_count_1 - 1], false); + std::fill(valid_2[i], valid_2[i] + candidates_l_2[order_count_2 - 1], false); + } + + std::cout << "Test" << std::endl; + for (ui i = 0; i < candidates_l_1[order_count_1 - 1]; i++) { + valid_1[order_count_1 - 1][i] = true; + } + + for (ui i = 0; i < candidates_l_2[order_count_2 - 1]; i++) { + valid_2[order_count_2 - 1][i] = true; + } + + reverse_cuts(res_1, + parent_offset_1, + children_offset_1, + candidates_l_1, + order_count_1, + res_1_n, + parent_offset_1_n, + candidates_l_1_n, + valid_1); + reverse_cuts(res_2, + parent_offset_2, + children_offset_2, + candidates_l_2, + order_count_2, + res_2_n, + parent_offset_2_n, + candidates_l_2_n, + valid_2); + + // Print Children information:: + for (ui i = 0; i < query_count; i++) { + ui tmp_u = order[i]; + std::cout << tmp_u << ",children:["; + for (ui j = 0; j < tree[tmp_u].children_count; j++) { + std::cout << "," << tree[tmp_u].children[j]; + } + ui u_nbrs_count_1; + V_ID *u_nbrs_1 = query_graph->getVertexNeighbors(tmp_u, u_nbrs_count_1); + + std::cout << "]" << std::endl; + std::cout << "Neighborhood:["; + for (ui j = 0; j < u_nbrs_count_1; j++) { + std::cout << "," << u_nbrs_1[j]; + } + std::cout << "]" << std::endl; + } + + V_ID **res_1_r = new V_ID *[candidates_l_1[order_count_1 - 1]]; + V_ID **res_2_r = new V_ID *[candidates_l_2[order_count_2 - 1]]; + + for (int64_t i = 0; i < candidates_l_1[order_count_1 - 1]; i++) { + res_1_r[i] = new V_ID[order_count_1]; + for (ui j = 0; j < order_count_1; j++) { + res_1_r[i][j] = 0; + } + } + + std::cout << "Initial Test" << std::endl; + + for (int64_t i = 0; i < candidates_l_1[order_count_1 - 1]; i++) { + res_1_r[i][order_count_1 - 1] = res_1[order_count_1 - 1][i]; + int64_t t = i; + for (int j = order_count_1 - 2; j >= 0; j--) { + t = parent_offset_1[j + 1][t]; + res_1_r[i][j] = res_1[j][t]; + } + } + std::cout << "End point 1:" << std::endl; + for (int64_t i = 0; i < candidates_l_2[order_count_2 - 1]; i++) { + res_2_r[i] = new V_ID[order_count_2]; + } + for (int64_t i = 0; i < candidates_l_2[order_count_2 - 1]; i++) { + res_2_r[i][order_count_2 - 1] = res_2[order_count_2 - 1][i]; + int64_t t = i; + for (int j = order_count_2 - 2; j >= 0; j--) { + t = parent_offset_2[j + 1][t]; + res_2_r[i][j] = res_2[j][t]; + } + } + std::cout << std::endl << "Test all" << std::endl; + + bool *visit_q_1 = new bool[order_count_1]; + + std::fill(visit_q_1, visit_q_1 + order_count_1, false); + + p_1 = p_1_1 - 1; + std::cout << order_count_1 - p_1 << std::endl; + std::vector p_all; + for (ui i = 0; i < order_count_2; i++) { + if (con_2[i] > 0) { + q_all.push_back(i); + } + } + + for (ui i = 0; i < order_count_1; i++) { + if (con_2_2[i] > 0) { + p_all.push_back(i); + } + } + + int64_t call_count = 0; + + for (int64_t d_1 = 0; d_1 < candidates_l_1[order_count_1 - 1]; d_1++) { + res_s[d_1] = 0; + } + + std::cout << "I->" << candidates_l_1[order_count_1 - 1] << std::endl; + std::cout << "J->" << candidates_l_2[order_count_2 - 1] << std::endl; + + double timer_middle = omp_get_wtime(); + int64_t total_sum = 0; + V_ID res_1_1[32]; + + double timer_local_enumeration = omp_get_wtime(); + + bool *visited_j = new bool[candidates_l_2[order_count_2 - 1]]; + for (int64_t k = 0; k < candidates_l_2[order_count_2 - 1]; k++) { + visited_j[k] = false; + } + + if (candidates_l_1[order_count_1 - 1] < candidates_l_2[order_count_2 - 1]) { + for (int64_t d_1 = 0; d_1 < candidates_l_1[0]; d_1++) { + int64_t start_1, end_1; + ui order_s = 0; + find_children_range(children_offset_1, candidates_l_1, p_1_1, order_s, start_1, end_1, d_1); + // V_ID r_1,r_2; + int64_t d_2; + for (d_2 = 0; d_2 < candidates_l_2[0]; d_2++) { + if (res_1[0][d_1] == res_2[0][d_2]) { + break; + } + } + // std::cout <<"d_1" << std::endl; + for (int64_t i = start_1; i < end_1; i++) { + res_1_1[p_1] = res_1[p_1][i]; + int64_t j2 = i; + for (int ii = p_1; ii > 0; ii--) { + j2 = parent_offset_1[ii][j2]; + res_1_1[ii - 1] = res_1[ii - 1][j2]; + } + ui p2, q2; + for (auto &t : i_e) { + p2 = t.first; + visited[res_1_1[p2]] = true; + } + for (auto &t : p_e) { + p2 = t.first; + q2 = t.second; + int64_t tt = data_count * q2; + std::unordered_map> *v_nl = data_graph->getVertexNL(res_1_1[p2]); + std::vector &tmp = (*v_nl)[l_2[q2]]; + for (unsigned int m : tmp) { + visit_local_4[tt + m] += 1; + } + } + int64_t total = 0; + bool all_exist = false; + int64_t start_2[32], end_2[32]; + ui t, q, q_add; + t = 0; + int64_t call_count_2 = 0; + if (t < q_all.size()) { + q = q_all[t]; + q_add = q + 1; + int64_t tmp = data_count * q; + // Find the range of children + find_children_range( + children_offset_2, candidates_l_2, q_add, order_s, start_2[t], end_2[t], d_2); + if (q_add == order_count_2) { + std::cout << con_2[q] << "," << q << std::endl; + for (int64_t j = start_2[t]; j < end_2[t]; j++) { + V_ID *res_t_2 = res_2_r[j]; + call_count_2 += 1; + if (visit_local_4[tmp + res_t_2[q]] >= con_2[q]) { + all_exist = true; + for (auto &t_2 : i_e) { + ui q_3 = t_2.second; + if (visited[res_t_2[q_3]]) { + all_exist = false; + break; + } + } + total += all_exist; + } + } + } else { + // std::cout << "t" << std::endl; + for (int64_t j = start_2[t]; j < end_2[t]; j++) { + call_count_2++; + if (visit_local_4[tmp + res_2[q][j]] == con_2[q]) { + total += edge_next(res_2_r, + visited_j, + visit_local_4, + data_count, + con_2, + visited, + children_offset_2, + candidates_l_2, + q_add, + j, + order_count_2, + i_e, + q_all, + t, + start_2, + end_2, + res_2, + q_1, + call_count_2); + } + } + } + } else { + // bool all_exist; + find_children_range( + children_offset_2, candidates_l_2, order_count_2, order_s, start_2[t], end_2[t], d_2); + for (int64_t j = start_2[t]; j < end_2[t]; j++) { + V_ID *res_t_2 = res_2_r[j]; + call_count_2 += 1; + all_exist = true; + for (auto &t_2 : i_e) { + call_count_2 += 1; + ui q_3 = t_2.second; + if (visited[res_t_2[q_3]]) { + all_exist = false; + break; + } + } + total += all_exist; + } + } + // #pragma omp end parallel for simd + if (p_1_1 == order_count_1) { + total_sum += total; + call_count += call_count_2; + } else { + int64_t start_1_1; + int64_t end_1_1; + int64_t pp = i; + find_children_range( + children_offset_1, candidates_l_1, order_count_1, p_1, start_1_1, end_1_1, pp); + // std::cout < " << tt << std::endl; + } + + for (auto &t2 : p_e) { + p2 = t2.first; + q2 = t2.second; + int64_t tt = data_count * q2; + std::unordered_map> *v_nl = data_graph->getVertexNL(res_1_1[p2]); + std::vector &tmp = (*v_nl)[l_2[q2]]; + for (unsigned int m : tmp) { + visit_local_4[tt + m] = 0; + } + } + + for (auto &t2 : i_e) { + p2 = t2.first; + visited[res_1_1[p2]] = false; + } + } + } + } else { + // Fix comment 1 + ui tmp_p_q = p_1_1; + p_1_1 = q_1_1; + q_1_1 = tmp_p_q; + + tmp_p_q = p_1; + p_1 = q_1; + q_1 = tmp_p_q; + + for (auto &t : i_e) { + ui tmp11 = t.first; + t.first = t.second; + t.second = tmp11; + } + for (auto &t : p_e) { + ui tmp11 = t.first; + t.first = t.second; + t.second = tmp11; + } + + for (int64_t d_1 = 0; d_1 < candidates_l_2[0]; d_1++) { + int64_t start_1, end_1; + ui order_s = 0; + find_children_range(children_offset_2, candidates_l_2, p_1_1, order_s, start_1, end_1, d_1); + // V_ID r_1,r_2; + int64_t d_2; + for (d_2 = 0; d_2 < candidates_l_1[0]; d_2++) { + if (res_2[0][d_1] == res_1[0][d_2]) { + break; + } + } + + // std::cout <<"d_1" << std::endl; + for (int64_t i = start_1; i < end_1; i++) { + res_1_1[p_1] = res_2[p_1][i]; + + for (int ii = p_1; ii > 0; ii--) { + int64_t j = i; + j = parent_offset_2[ii][j]; + res_1_1[ii - 1] = res_2[ii - 1][j]; + } + ui p2, q2; + for (auto &t : i_e) { + p2 = t.first; + visited[res_1_1[p2]] = true; + } + for (auto &t : p_e) { + p2 = t.first; + q2 = t.second; + int64_t tt = data_count * q2; + std::unordered_map> *v_nl = data_graph->getVertexNL(res_1_1[p2]); + std::vector &tmp = (*v_nl)[l_1[q2]]; + for (unsigned int m : tmp) { + visit_local_4[tt + m] += 1; + } + } + // Fix comment 2 + int64_t total = 0; + bool all_exist = false; + int64_t start_2[32], end_2[32]; + ui t, q, q_add; + t = 0; + int64_t call_count_2 = 0; + if (t < p_all.size()) { + q = p_all[t]; + q_add = q + 1; + int64_t tmp = data_count * q; + find_children_range( + children_offset_1, candidates_l_1, q_add, order_s, start_2[t], end_2[t], d_2); + if (q_add == order_count_1) { + std::cout << con_2_2[q] << "," << q << std::endl; + for (int64_t j = start_2[t]; j < end_2[t]; j++) { + V_ID *res_t_2 = res_1_r[j]; + call_count_2 += 1; + if (visit_local_4[tmp + res_t_2[q]] >= con_2_2[q]) { + all_exist = true; + for (auto &t_2 : i_e) { + ui q_3 = t_2.second; + if (visited[res_t_2[q_3]]) { + all_exist = false; + break; + } + } + total += all_exist; + } + } + } else { + // std::cout <<"all-->" << std::endl; + for (int64_t j = start_2[t]; j < end_2[t]; j++) { + call_count_2++; + if (visit_local_4[tmp + res_1[q][j]] == con_2_2[q]) { + total += edge_next(res_1_r, + visited_j, + visit_local_4, + data_count, + con_2_2, + visited, + children_offset_1, + candidates_l_1, + q_add, + j, + order_count_1, + i_e, + p_all, + t, + start_2, + end_2, + res_1, + q_1, + call_count_2); + } + } + } + } else { + // + find_children_range( + children_offset_1, candidates_l_1, order_count_1, order_s, start_2[t], end_2[t], d_2); + for (int64_t j = start_2[t]; j < end_2[t]; j++) { + V_ID *res_t_2 = res_1_r[j]; + call_count_2 += 1; + all_exist = true; + for (auto &t_2 : i_e) { + call_count_2 += 1; + ui q_3 = t_2.second; + if (visited[res_t_2[q_3]]) { + all_exist = false; + break; + } + } + total += all_exist; + } + } + // #pragma omp end parallel for simd + if (p_1_1 == order_count_2) { + total_sum += total; + call_count += call_count_2; + } else { + int64_t start_1_1; + int64_t end_1_1; + int64_t pp = i; + find_children_range( + children_offset_2, candidates_l_2, order_count_2, p_1, start_1_1, end_1_1, pp); + // std::cout < " << tt << std::endl; + } + + for (t = 0; t < p_e.size(); t++) { + p2 = p_e[t].first; + q2 = p_e[t].second; + int64_t tt = data_count * q2; + std::unordered_map> *v_nl = data_graph->getVertexNL(res_1_1[p2]); + std::vector &tmp = (*v_nl)[l_1[q2]]; + for (unsigned int m : tmp) { + visit_local_4[tt + m] = 0; + } + } + + for (t = 0; t < i_e.size(); t++) { + p2 = i_e[t].first; + visited[res_1_1[p2]] = false; + } + } + } + } + // std::cout <<"Total 2:" << total_2 << std::endl; + std::cout << "Data count" << data_count << std::endl; + total_result = total_sum; + + double timer_end = omp_get_wtime(); + double timer_took = + timer_end - timer_middle + + std::chrono::duration_cast(f_5 - f_4).count() / 1000000000.0 + + std::chrono::duration_cast(f_6 - f_5).count() / + 1000000000.0; // timer_start1; + // double timer_took3 = timer_took; + + std::cout << "call count->" << call_count << ", per call nanoseconds->" + << timer_took * 1000000000.0 / call_count << std::endl; + std::cout << "total results:" << total_result << ",Time:" << timer_took << ",local enumeration-->" + << (timer_local_enumeration - timer_start1) * 100 / timer_took << std::endl; + + // (6) Global node based-enumeration result comparasion + // std::cout <<"Start enumeration" << endl; + u = order[0]; + + static V_ID **res = new V_ID *[query_count]; + static V_ID **cuTS = new V_ID *[query_count]; + + for (ui i = 0; i < query_count; ++i) { + res[i] = new V_ID[candidates_max_count]; + cuTS[i] = new V_ID[candidates_max_count]; + } + + int64_t res_all = 0; + + // int64_t total = 0; + + std::fill(visited, visited + data_count, false); + + double timer_start = omp_get_wtime(); + auto first = std::chrono::steady_clock::now(); + + auto f_2 = first; + auto f_3 = first; + auto f_10 = first; + // traditional enumeration: + + f_10 = std::chrono::steady_clock::now(); + + if (tree[u].children_count == 0) { + int length = P_Candidates[u].size(); + for (int i = 0; i < length; i++) { + res_all += 1; + } + } else { + // ui current_order = 0; + ui bfs_order = 0; + query_count -= 1; + // int64_t single_res; + std::cout << "Start-->" << std::endl; + int64_t bfs_count = 0; + + for (const auto &it4 : P_Candidates[u]) { + res_all += 1; + // [current_order] = it4->first; + candidates_2[bfs_order][bfs_count++] = it4.first; + visited[it4.first] = true; + visited[it4.first] = false; + } + + f_2 = std::chrono::steady_clock::now(); + + candidates_l[bfs_order++] = bfs_count; + query_count += 1; + f_3 = std::chrono::steady_clock::now(); + } + + // auto second =std::chrono::steady_clock::now(); + std::cout << "P_1 Time->" + << std::chrono::duration_cast(f_5 - f_4).count() / + 1000000000.0 + << std::endl; + std::cout << "P_2 Time->" + << std::chrono::duration_cast(f_6 - f_5).count() / + 1000000000.0 + << std::endl; + std::cout << "DFS Time->" + << std::chrono::duration_cast(f_2 - first).count() / + 1000000000.0 + << std::endl; + + timer_end = omp_get_wtime(); + std::cout << "BFS->" + << std::chrono::duration_cast(f_3 - f_2).count() / + 1000000000.0 + << std::endl; + double last_r = timer_took; + timer_took = timer_end - timer_start; + std::cout << "Node total results:" << candidates_l[query_count - 1] - total_result + << ",Time:" << timer_took << ", ratio->" + << timer_took * 100 / (timer_took + timer_start1 - timer_all_s) << " %, Accelera rate->" + << timer_took / last_r << std::endl; + + // freopen("output.txt", "a", stdout); + + // std::cout <(f_3 - f_2).count()/1000000000.0 + // << "," + // << call_count << std::endl; + + // fclose(stdout); + + return true; + // NTE Tree: +} +// +// +// +// NLF data_graph, query_graph, ID of the vertex node. total count. +// + +void ZFComputeNLF(Graph *data_graph, Graph *query_graph, V_ID i, ui &count, ui *tmp) { + // std::cout <<"Test 0" << std::endl; + L_ID label = query_graph->getVertexLabel(i); + ui degree = query_graph->getVertexDegree(i); + // std::cout <<"Test 00" << std::endl; + std::unordered_map *query_nlf = query_graph->getVertexNLF(i); + + // data vertex count; + ui data_v_count; + ui *data_v = data_graph->getVerticesByLabel(label, data_v_count); + count = 0; + // std::cout <<"Test 1" << std::endl; + for (ui j = 0; j < data_v_count; ++j) { + ui v = data_v[j]; + if (data_graph->getVertexDegree(v) >= degree) { + // NFL check + std::unordered_map *data_nlf = data_graph->getVertexNLF(v); + + if (data_nlf->size() >= query_nlf->size()) { + bool valid = true; + + // Label, count in the (nlf) + for (auto item : *query_nlf) { + auto element = data_nlf->find(item.first); + if (element == data_nlf->end() || element->second < item.second) { + valid = false; + break; + } + } + + if (valid) { + if (tmp != nullptr) { + tmp[count] = v; // + } + count += 1; // Recored count of the number + } + } + } + } +} diff --git a/src/graph/subgraph_provenance/subgraph.h b/src/graph/subgraph_provenance/subgraph.h new file mode 100644 index 00000000000..ee16377958d --- /dev/null +++ b/src/graph/subgraph_provenance/subgraph.h @@ -0,0 +1,42 @@ +// Copyright [2022] +#ifndef SUBGRAPH_H +#define SUBGRAPH_H + +#include + +#include "graph.h" +#include "trees.h" + +bool CECIFunction(Graph *data_graph, + Graph *query_graph, + ui **&candidates, + ui *&candidates_count, + ui *&order, + ui *&provenance, + TreeNode *&tree, + std::vector>> &P_Candidates, + std::vector>> &P_Provenance); + +// static void FilterProvenance(std::unordered_map *u_nlf, +// ui u_l, +// ui u_d, +// V_ID &u, +// V_ID &v, +// std::unordered_map &Provenance, +// Graph *data_graph, +// std::vector &data_visited, +// ui **&candidates, +// ui *&candidates_count); +// static void Insertion(V_ID u, +// V_ID &tmp_first, +// V_ID &tmp_second, +// std::unordered_map>> &intersetion); + +// static V_ID ParentNode(V_ID first, V_ID second, TreeNode *&tree); + +// static void Initial(Graph *data_graph, Graph *query_graph, ui &candidates, ui +// *&candidates_count); static V_ID InitialStartVertex(Graph *data_graph, Graph *query_graph); + +void ZFComputeNLF(Graph *data_graph, Graph *query_graph, V_ID i, ui &count, ui *tmp = nullptr); + +#endif // SUBGRAPH_H diff --git a/src/graph/subgraph_provenance/trees.h b/src/graph/subgraph_provenance/trees.h new file mode 100644 index 00000000000..978a994a338 --- /dev/null +++ b/src/graph/subgraph_provenance/trees.h @@ -0,0 +1,88 @@ +// Copyright [2022] +#ifndef CECI_TYPES_H +#define CECI_TYPES_H + +#include +#include + +using ui = unsigned int; // u is the query node. + +using V_ID = ui; +using L_ID = ui; + +class TreeNode { + public: + V_ID id; + V_ID parent; + ui level; + // ui under_level_count; + ui children_count; + // V_ID* under_level; + V_ID* children; + + // This field tracks the nodes that should participate in the intersection to generate the + // candidates for node "id". A.k.a, the nodes that are visited earlier than node "id", and should + // be intersected to generate the candidates for "node id". Note, this field excludes the "parent + // node" of node "id". + ui bn_count; + V_ID* bn; + + ui fn_count; + V_ID* fn; + size_t estimated_embeddings_num; + + public: + TreeNode() { + id = 0; + // under_level = nullptr; + bn = nullptr; + fn = nullptr; + children = nullptr; + parent = 99999; + level = 0; + // under_level_count = 0; + children_count = 0; + bn_count = 0; + fn_count = 0; + estimated_embeddings_num = 0; + } + + ~TreeNode() { + // delete[] under_level; + delete[] bn; + delete[] fn; + delete[] children; + } + + void initialize(const ui size) { + // under_level = new V_ID[size]; + bn = new V_ID[size]; + fn = new V_ID[size]; + children = new V_ID[size]; + } +}; + +class Edges { + public: + ui* offset; + ui* edge; + ui v_count; + ui e_count; + ui max_degree; + + public: + Edges() { + offset = nullptr; + edge = nullptr; + v_count = 0; + e_count = 0; + max_degree = 0; + } + + ~Edges() { + delete[] offset; + delete[] edge; + } +}; + +#endif // CECI_TYPES_H From 6abc99b80e792ad3e4cf02b41cf16692f7fb8474 Mon Sep 17 00:00:00 2001 From: peter-rich Date: Sat, 8 Oct 2022 07:32:25 +0800 Subject: [PATCH 6/9] Run subgraph algorithm --- src/graph/executor/algo/IsomorExecutor.cpp | 37 +++++++++++++++++++--- src/graph/subgraph_provenance/subgraph.cpp | 2 +- 2 files changed, 34 insertions(+), 5 deletions(-) diff --git a/src/graph/executor/algo/IsomorExecutor.cpp b/src/graph/executor/algo/IsomorExecutor.cpp index 07cdd8b7b42..8973d124b56 100644 --- a/src/graph/executor/algo/IsomorExecutor.cpp +++ b/src/graph/executor/algo/IsomorExecutor.cpp @@ -111,13 +111,42 @@ folly::Future IsomorExecutor::execute() { Graph* query_graph = new Graph(); query_graph->loadGraphFromExecutor(v_count, l_count, e_count, offset, neighbors, labels); - delete offset; - delete neighbors; - delete labels; + ui** candidates = nullptr; + ui* candidates_count = nullptr; + + TreeNode* ceci_tree = nullptr; + ui* ceci_order = nullptr; + ui* provenance = nullptr; + + std::vector>> + P_Candidates; // Parent, first branch, second branch. + std::vector>> P_Provenance; + // std::cout"Provenance Function: " << std::endl:endl; + + bool result = CECIFunction(data_graph, + query_graph, + candidates, + candidates_count, + ceci_order, + provenance, + ceci_tree, + P_Candidates, + P_Provenance); + delete data_graph; + delete query_graph; + delete[] ceci_order; + delete[] provenance; + delete[] candidates_count; + delete[] candidates; + delete ceci_tree; + + delete[] offset; + delete[] neighbors; + delete[] labels; ResultBuilder builder; // Set result in the ds and set the new column name for the (isomor matching 's) result. - return finish(ResultBuilder().value(Value(std::move(ds))).build()); + return finish(ResultBuilder().value(Value(std::move(result))).build()); } } // namespace graph } // namespace nebula diff --git a/src/graph/subgraph_provenance/subgraph.cpp b/src/graph/subgraph_provenance/subgraph.cpp index e1aa75acf98..12178252413 100644 --- a/src/graph/subgraph_provenance/subgraph.cpp +++ b/src/graph/subgraph_provenance/subgraph.cpp @@ -3365,7 +3365,7 @@ std::cout < Date: Sat, 15 Oct 2022 06:50:42 +0800 Subject: [PATCH 7/9] new --- src/graph/CMakeLists.txt | 2 +- src/graph/executor/CMakeLists.txt | 5 +- src/graph/executor/algo/IsomorExecutor.cpp | 4 +- .../executor/subgraph_provenance/.gitignore | 3 ++ .../subgraph_provenance/README.md | 0 .../subgraph_provenance/bitsetoperation.cpp | 0 .../subgraph_provenance/bitsetoperation.h | 0 src/graph/executor/subgraph_provenance/ceci | Bin 0 -> 177560 bytes .../subgraph_provenance/ceci.cpp | 0 .../computesetintersection.cpp | 0 .../computesetintersection.h | 0 .../subgraph_provenance/config.h | 0 .../subgraph_provenance/graph.cpp | 0 .../subgraph_provenance/graph.h | 0 .../subgraph_provenance/run.bash | 0 .../subgraph_provenance/subgraph.cpp | 48 +++++++++--------- .../subgraph_provenance/subgraph.h | 0 .../subgraph_provenance/trees.h | 0 src/graph/subgraph_provenance/CMakeLists.txt | 10 ---- 19 files changed, 35 insertions(+), 37 deletions(-) create mode 100644 src/graph/executor/subgraph_provenance/.gitignore rename src/graph/{ => executor}/subgraph_provenance/README.md (100%) rename src/graph/{ => executor}/subgraph_provenance/bitsetoperation.cpp (100%) rename src/graph/{ => executor}/subgraph_provenance/bitsetoperation.h (100%) create mode 100755 src/graph/executor/subgraph_provenance/ceci rename src/graph/{ => executor}/subgraph_provenance/ceci.cpp (100%) rename src/graph/{ => executor}/subgraph_provenance/computesetintersection.cpp (100%) rename src/graph/{ => executor}/subgraph_provenance/computesetintersection.h (100%) rename src/graph/{ => executor}/subgraph_provenance/config.h (100%) rename src/graph/{ => executor}/subgraph_provenance/graph.cpp (100%) rename src/graph/{ => executor}/subgraph_provenance/graph.h (100%) rename src/graph/{ => executor}/subgraph_provenance/run.bash (100%) rename src/graph/{ => executor}/subgraph_provenance/subgraph.cpp (99%) rename src/graph/{ => executor}/subgraph_provenance/subgraph.h (100%) rename src/graph/{ => executor}/subgraph_provenance/trees.h (100%) delete mode 100644 src/graph/subgraph_provenance/CMakeLists.txt diff --git a/src/graph/CMakeLists.txt b/src/graph/CMakeLists.txt index aadbdcb54d6..3761480298d 100644 --- a/src/graph/CMakeLists.txt +++ b/src/graph/CMakeLists.txt @@ -2,6 +2,7 @@ # # This source code is licensed under Apache 2.0 License. + nebula_add_subdirectory(context) nebula_add_subdirectory(executor) nebula_add_subdirectory(optimizer) @@ -14,4 +15,3 @@ nebula_add_subdirectory(util) nebula_add_subdirectory(validator) nebula_add_subdirectory(visitor) nebula_add_subdirectory(gc) -nebula_add_subdirectory(subgraph_provenance) \ No newline at end of file diff --git a/src/graph/executor/CMakeLists.txt b/src/graph/executor/CMakeLists.txt index 25da101a494..ecc261bdb15 100644 --- a/src/graph/executor/CMakeLists.txt +++ b/src/graph/executor/CMakeLists.txt @@ -5,6 +5,9 @@ nebula_add_library( executor_obj OBJECT Executor.cpp + subgraph_provenance/ceci.cpp + subgraph_provenance/graph.cpp + subgraph_provenance/subgraph.cpp StorageAccessExecutor.cpp logic/LoopExecutor.cpp logic/PassThroughExecutor.cpp @@ -90,4 +93,4 @@ nebula_add_library( mutate/UpdateExecutor.cpp ) -nebula_add_subdirectory(test) + diff --git a/src/graph/executor/algo/IsomorExecutor.cpp b/src/graph/executor/algo/IsomorExecutor.cpp index 8973d124b56..e6e6d24bd08 100644 --- a/src/graph/executor/algo/IsomorExecutor.cpp +++ b/src/graph/executor/algo/IsomorExecutor.cpp @@ -7,9 +7,9 @@ #include #include +#include "graph/executor/subgraph_provenance/graph.h" +#include "graph/executor/subgraph_provenance/subgraph.h" #include "graph/planner/plan/Algo.h" -#include "graph/subgraph_provenance/graph.h" -#include "graph/subgraph_provenance/subgraph.h" namespace nebula { namespace graph { diff --git a/src/graph/executor/subgraph_provenance/.gitignore b/src/graph/executor/subgraph_provenance/.gitignore new file mode 100644 index 00000000000..6a3c664fb85 --- /dev/null +++ b/src/graph/executor/subgraph_provenance/.gitignore @@ -0,0 +1,3 @@ +*.o +dataset/* +test/* diff --git a/src/graph/subgraph_provenance/README.md b/src/graph/executor/subgraph_provenance/README.md similarity index 100% rename from src/graph/subgraph_provenance/README.md rename to src/graph/executor/subgraph_provenance/README.md diff --git a/src/graph/subgraph_provenance/bitsetoperation.cpp b/src/graph/executor/subgraph_provenance/bitsetoperation.cpp similarity index 100% rename from src/graph/subgraph_provenance/bitsetoperation.cpp rename to src/graph/executor/subgraph_provenance/bitsetoperation.cpp diff --git a/src/graph/subgraph_provenance/bitsetoperation.h b/src/graph/executor/subgraph_provenance/bitsetoperation.h similarity index 100% rename from src/graph/subgraph_provenance/bitsetoperation.h rename to src/graph/executor/subgraph_provenance/bitsetoperation.h diff --git a/src/graph/executor/subgraph_provenance/ceci b/src/graph/executor/subgraph_provenance/ceci new file mode 100755 index 0000000000000000000000000000000000000000..5556ba2f1f16947118d94dd0d8fc957631e5de63 GIT binary patch literal 177560 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 A8~^|S literal 0 HcmV?d00001 diff --git a/src/graph/subgraph_provenance/ceci.cpp b/src/graph/executor/subgraph_provenance/ceci.cpp similarity index 100% rename from src/graph/subgraph_provenance/ceci.cpp rename to src/graph/executor/subgraph_provenance/ceci.cpp diff --git a/src/graph/subgraph_provenance/computesetintersection.cpp b/src/graph/executor/subgraph_provenance/computesetintersection.cpp similarity index 100% rename from src/graph/subgraph_provenance/computesetintersection.cpp rename to src/graph/executor/subgraph_provenance/computesetintersection.cpp diff --git a/src/graph/subgraph_provenance/computesetintersection.h b/src/graph/executor/subgraph_provenance/computesetintersection.h similarity index 100% rename from src/graph/subgraph_provenance/computesetintersection.h rename to src/graph/executor/subgraph_provenance/computesetintersection.h diff --git a/src/graph/subgraph_provenance/config.h b/src/graph/executor/subgraph_provenance/config.h similarity index 100% rename from src/graph/subgraph_provenance/config.h rename to src/graph/executor/subgraph_provenance/config.h diff --git a/src/graph/subgraph_provenance/graph.cpp b/src/graph/executor/subgraph_provenance/graph.cpp similarity index 100% rename from src/graph/subgraph_provenance/graph.cpp rename to src/graph/executor/subgraph_provenance/graph.cpp diff --git a/src/graph/subgraph_provenance/graph.h b/src/graph/executor/subgraph_provenance/graph.h similarity index 100% rename from src/graph/subgraph_provenance/graph.h rename to src/graph/executor/subgraph_provenance/graph.h diff --git a/src/graph/subgraph_provenance/run.bash b/src/graph/executor/subgraph_provenance/run.bash similarity index 100% rename from src/graph/subgraph_provenance/run.bash rename to src/graph/executor/subgraph_provenance/run.bash diff --git a/src/graph/subgraph_provenance/subgraph.cpp b/src/graph/executor/subgraph_provenance/subgraph.cpp similarity index 99% rename from src/graph/subgraph_provenance/subgraph.cpp rename to src/graph/executor/subgraph_provenance/subgraph.cpp index 12178252413..423b65ee43f 100644 --- a/src/graph/subgraph_provenance/subgraph.cpp +++ b/src/graph/executor/subgraph_provenance/subgraph.cpp @@ -2,7 +2,6 @@ #include "subgraph.h" #include -#include #include #include @@ -2457,7 +2456,7 @@ bool CECIFunction(Graph *data_graph, // Initial the CECI Index. // In this case, - double timer_all_s = omp_get_wtime(); + // double timer_all_s = omp_get_wtime(); // std::cout <<"a" << std::endl; V_ID start_vertex = InitialStartVertex(data_graph, query_graph); @@ -3597,7 +3596,7 @@ std::cout < order_index_1(query_count); @@ -4084,11 +4083,11 @@ std::cout <" << candidates_l_1[order_count_1 - 1] << std::endl; std::cout << "J->" << candidates_l_2[order_count_2 - 1] << std::endl; - double timer_middle = omp_get_wtime(); + // double timer_middle = omp_get_wtime(); int64_t total_sum = 0; V_ID res_1_1[32]; - double timer_local_enumeration = omp_get_wtime(); + // double timer_local_enumeration = omp_get_wtime(); bool *visited_j = new bool[candidates_l_2[order_count_2 - 1]]; for (int64_t k = 0; k < candidates_l_2[order_count_2 - 1]; k++) { @@ -4418,18 +4417,20 @@ std::cout <(f_5 - f_4).count() / 1000000000.0 + - std::chrono::duration_cast(f_6 - f_5).count() / - 1000000000.0; // timer_start1; + // double timer_end = omp_get_wtime(); + // double timer_took = + // timer_end - timer_middle + + // std::chrono::duration_cast(f_5 - f_4).count() / 1000000000.0 + + // std::chrono::duration_cast(f_6 - f_5).count() / + // 1000000000.0; // timer_start1; // double timer_took3 = timer_took; - std::cout << "call count->" << call_count << ", per call nanoseconds->" - << timer_took * 1000000000.0 / call_count << std::endl; - std::cout << "total results:" << total_result << ",Time:" << timer_took << ",local enumeration-->" - << (timer_local_enumeration - timer_start1) * 100 / timer_took << std::endl; + std::cout << "call count->" << call_count << ", per call nanoseconds->" << std::endl; + //<< timer_took * 1000000000.0 / call_count << std::endl; + std::cout + << "total results:" << total_result + << std::endl; // ",Time:" << timer_took << ",local enumeration-->" + // << (timer_local_enumeration - timer_start1) * 100 / timer_took << std::endl; // (6) Global node based-enumeration result comparasion // std::cout <<"Start enumeration" << endl; @@ -4449,7 +4450,7 @@ std::cout <" << std::chrono::duration_cast(f_3 - f_2).count() / 1000000000.0 << std::endl; - double last_r = timer_took; - timer_took = timer_end - timer_start; - std::cout << "Node total results:" << candidates_l[query_count - 1] - total_result - << ",Time:" << timer_took << ", ratio->" - << timer_took * 100 / (timer_took + timer_start1 - timer_all_s) << " %, Accelera rate->" - << timer_took / last_r << std::endl; + // double last_r = timer_took; + // timer_took = timer_end - timer_start; + // std::cout << "Node total results:" << candidates_l[query_count - 1] - total_result + // << ",Time:" << timer_took << ", ratio->" + // << timer_took * 100 / (timer_took + timer_start1 - timer_all_s) << " %, Accelera + // rate->" + // << timer_took / last_r << std::endl; // freopen("output.txt", "a", stdout); diff --git a/src/graph/subgraph_provenance/subgraph.h b/src/graph/executor/subgraph_provenance/subgraph.h similarity index 100% rename from src/graph/subgraph_provenance/subgraph.h rename to src/graph/executor/subgraph_provenance/subgraph.h diff --git a/src/graph/subgraph_provenance/trees.h b/src/graph/executor/subgraph_provenance/trees.h similarity index 100% rename from src/graph/subgraph_provenance/trees.h rename to src/graph/executor/subgraph_provenance/trees.h diff --git a/src/graph/subgraph_provenance/CMakeLists.txt b/src/graph/subgraph_provenance/CMakeLists.txt deleted file mode 100644 index 85777ea48f7..00000000000 --- a/src/graph/subgraph_provenance/CMakeLists.txt +++ /dev/null @@ -1,10 +0,0 @@ -# Copyright (c) 2020 vesoft inc. All rights reserved. -# -# This source code is licensed under Apache 2.0 License. - -nebula_add_library( - executor_provenance OBJECT - ceci.cpp - graph.cpp - subgraph.cpp -) From d1b754425b330abdcf141699d57f81c109d19f10 Mon Sep 17 00:00:00 2001 From: peter-rich Date: Sat, 15 Oct 2022 11:38:28 +0800 Subject: [PATCH 8/9] add comment fo offset and neighborhood --- src/graph/executor/algo/IsomorExecutor.cpp | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/graph/executor/algo/IsomorExecutor.cpp b/src/graph/executor/algo/IsomorExecutor.cpp index e6e6d24bd08..7dcca70d47f 100644 --- a/src/graph/executor/algo/IsomorExecutor.cpp +++ b/src/graph/executor/algo/IsomorExecutor.cpp @@ -29,7 +29,10 @@ folly::Future IsomorExecutor::execute() { unsigned int v_count = iterDV->size(); unsigned int l_count = iterDV->size(); unsigned int e_count = iterDE->size(); + // To store the prefix sum of the degree of each vertex. unsigned int* offset = new unsigned int[v_count + 2]; + // Array of the neighborhood can be initialized by 2 dimension of the matrix, + // However, here we use 2*edge count as we have in edge and out edges. unsigned int* neighbors = new unsigned int[e_count * 2]; unsigned int* labels = new unsigned int[l_count]; // load data vertices id and tags @@ -42,6 +45,7 @@ folly::Future IsomorExecutor::execute() { labels[v_id] = l_id; // Tag Id iterDV->next(); } + // load edges degree while (iterDE->valid()) { auto s = iterDE->getEdgeProp("*", kSrc); @@ -49,8 +53,10 @@ folly::Future IsomorExecutor::execute() { offset[src]++; iterDE->next(); } + + // caldulate the start position of each vertex in the neighborhood array for (unsigned int i = 0; i < v_count; i++) { - offset[i + 1] = offset[i]; + offset[i + 1] += offset[i]; } // load data edges @@ -89,8 +95,10 @@ folly::Future IsomorExecutor::execute() { offset[src]++; iterDE->next(); } + + // caldulate the start position of each vertex in the neighborhood array for (unsigned int i = 0; i < v_count; i++) { - offset[i + 1] = offset[i]; + offset[i + 1] += offset[i]; } // load query edges From 654d4890359965c0fd71982093af9d1f036645a5 Mon Sep 17 00:00:00 2001 From: peter-rich Date: Thu, 27 Oct 2022 11:25:06 +0800 Subject: [PATCH 9/9] change degree --- src/graph/executor/algo/IsomorExecutor.cpp | 29 +++++++++++++++++++--- 1 file changed, 25 insertions(+), 4 deletions(-) diff --git a/src/graph/executor/algo/IsomorExecutor.cpp b/src/graph/executor/algo/IsomorExecutor.cpp index 7dcca70d47f..e9803c8df23 100644 --- a/src/graph/executor/algo/IsomorExecutor.cpp +++ b/src/graph/executor/algo/IsomorExecutor.cpp @@ -29,12 +29,28 @@ folly::Future IsomorExecutor::execute() { unsigned int v_count = iterDV->size(); unsigned int l_count = iterDV->size(); unsigned int e_count = iterDE->size(); - // To store the prefix sum of the degree of each vertex. - unsigned int* offset = new unsigned int[v_count + 2]; + // Example: + // Vetices 3: 0, 1, 2, 3 + // Edges: + // 0 1 + // + // To store the degree of each vertex + unsigned int* degree = new unsigned int[v_count]; + + // To store the starting position of each vertex in neighborhood array. + + unsigned int* offset = new unsigned int[v_count + 1]; + // Array of the neighborhood can be initialized by 2 dimension of the matrix, // However, here we use 2*edge count as we have in edge and out edges. unsigned int* neighbors = new unsigned int[e_count * 2]; unsigned int* labels = new unsigned int[l_count]; + + // Initialize the degree for data graph + for (unsigned int i = 0; i < v_count; i++) { + degree[i] = 0; + } + // load data vertices id and tags while (iterDV->valid()) { const auto vertex = iterDV->getColumn(nebula::kVid); // check if v is a vertex @@ -50,13 +66,13 @@ folly::Future IsomorExecutor::execute() { while (iterDE->valid()) { auto s = iterDE->getEdgeProp("*", kSrc); unsigned int src = s.getInt(); - offset[src]++; + degree[src]++; iterDE->next(); } // caldulate the start position of each vertex in the neighborhood array for (unsigned int i = 0; i < v_count; i++) { - offset[i + 1] += offset[i]; + offset[i + 1] += degree[i] + offset[i]; } // load data edges @@ -88,6 +104,11 @@ folly::Future IsomorExecutor::execute() { iterQV->next(); } + // Initialize the degree for query graph + for (unsigned int i = 0; i < v_count; i++) { + degree[i] = 0; + } + // load query edges degree while (iterQE->valid()) { auto s = iterQE->getEdgeProp("*", kSrc);