diff --git a/src/agents/query_engine/MettaParserActions.cc b/src/agents/query_engine/MettaParserActions.cc index bb751f83..1823b0ab 100644 --- a/src/agents/query_engine/MettaParserActions.cc +++ b/src/agents/query_engine/MettaParserActions.cc @@ -32,19 +32,21 @@ MettaParserActions::~MettaParserActions() {} void MettaParserActions::symbol(const string& name) { ParserActions::symbol(name); - if ((name == MettaMapping::AND_QUERY_OPERATOR) || (name == MettaMapping::OR_QUERY_OPERATOR) || - (name == MettaMapping::CHAIN_QUERY_OPERATOR)) { + if ((name == MettaMapping::AND_QUERY_OPERATOR) || (name == MettaMapping::ANDNOT_QUERY_OPERATOR) || + (name == MettaMapping::OR_QUERY_OPERATOR) || (name == MettaMapping::CHAIN_QUERY_OPERATOR)) { if (name == MettaMapping::AND_QUERY_OPERATOR) { this->current_expression_type = AND; + } else if (name == MettaMapping::ANDNOT_QUERY_OPERATOR) { + this->current_expression_type = ANDNOT; } else if (name == MettaMapping::OR_QUERY_OPERATOR) { this->current_expression_type = OR; } else { this->current_expression_type = CHAIN; } } else { - if ((this->current_expression_type == AND) || (this->current_expression_type == OR) || - (this->current_expression_type == CHAIN)) { - RAISE_ERROR("Invalid query expression: AND/OR/CHAIN can't operate symbols."); + if ((this->current_expression_type == AND) || (this->current_expression_type == ANDNOT) || + (this->current_expression_type == OR) || (this->current_expression_type == CHAIN)) { + RAISE_ERROR("Invalid query expression: AND/ANDNOT/OR/CHAIN can't operate symbols."); return; } this->current_expression_size++; @@ -54,8 +56,9 @@ void MettaParserActions::symbol(const string& name) { void MettaParserActions::variable(const string& value) { ParserActions::variable(value); - if ((this->current_expression_type == AND) || (this->current_expression_type == OR)) { - RAISE_ERROR("Invalid query expression: AND/OR can't operate variables."); + if ((this->current_expression_type == AND) || (this->current_expression_type == ANDNOT) || + (this->current_expression_type == OR)) { + RAISE_ERROR("Invalid query expression: AND/ANDNOT/OR can't operate variables."); return; } if (this->current_expression_type == LINK) { @@ -67,8 +70,9 @@ void MettaParserActions::variable(const string& value) { void MettaParserActions::literal(const string& value) { ParserActions::literal(value); - if ((this->current_expression_type == AND) || (this->current_expression_type == OR)) { - RAISE_ERROR("Invalid query expression: AND/OR can't operate literals."); + if ((this->current_expression_type == AND) || (this->current_expression_type == ANDNOT) || + (this->current_expression_type == OR)) { + RAISE_ERROR("Invalid query expression: AND/ANDNOT/OR can't operate literals."); return; } this->current_expression_size++; @@ -77,8 +81,9 @@ void MettaParserActions::literal(const string& value) { void MettaParserActions::literal(int value) { ParserActions::literal(value); - if ((this->current_expression_type == AND) || (this->current_expression_type == OR)) { - RAISE_ERROR("Invalid query expression: AND/OR can't operate literals."); + if ((this->current_expression_type == AND) || (this->current_expression_type == ANDNOT) || + (this->current_expression_type == OR)) { + RAISE_ERROR("Invalid query expression: AND/ANDNOT/OR can't operate literals."); return; } this->current_expression_size++; @@ -88,9 +93,9 @@ void MettaParserActions::literal(int value) { void MettaParserActions::literal(float value) { ParserActions::literal(value); - if ((this->current_expression_type == AND) || (this->current_expression_type == OR) || - (this->current_expression_type == CHAIN)) { - RAISE_ERROR("Invalid query expression: AND/OR/CHAIN can't operate float literals."); + if ((this->current_expression_type == AND) || (this->current_expression_type == ANDNOT) || + (this->current_expression_type == OR) || (this->current_expression_type == CHAIN)) { + RAISE_ERROR("Invalid query expression: AND/ANDNOT/OR/CHAIN can't operate float literals."); return; } this->current_expression_size++; @@ -155,10 +160,11 @@ void MettaParserActions::expression_end(bool toplevel, const string& metta_expre this->proxy->parameters.get(BaseQueryProxy::USE_LINK_TEMPLATE_CACHE)); this->element_stack.push(new_link_template); } - } else if ((this->current_expression_type == AND) || (this->current_expression_type == OR)) { + } else if ((this->current_expression_type == AND) || (this->current_expression_type == ANDNOT) || + (this->current_expression_type == OR)) { if (this->element_stack.size() >= 2) { shared_ptr new_operator; - // When using MeTTa to define a query, AND and OR operations always take 2 arguments. + // When using MeTTa to define a query, AND, ANDNOT and OR operations always take 2 arguments. array, 2> clauses; vector> link_templates; for (int i = 1; i >= 0; i--) { @@ -172,7 +178,8 @@ void MettaParserActions::expression_end(bool toplevel, const string& metta_expre if (this->element_stack.top()->is_operator) { clauses[i] = this->element_stack.top(); } else { - RAISE_ERROR("All AND clauses are supposed to be LinkTemplate or Operator"); + RAISE_ERROR( + "All AND/ANDNOT/OR clauses are supposed to be LinkTemplate or Operator"); return; } } @@ -180,7 +187,10 @@ void MettaParserActions::expression_end(bool toplevel, const string& metta_expre } if (this->current_expression_type == AND) { LOG_DEBUG("Pushing AND"); - new_operator = make_shared>(clauses, link_templates); + new_operator = make_shared>(clauses, link_templates, false); + } else if (this->current_expression_type == ANDNOT) { + LOG_DEBUG("Pushing ANDNOT"); + new_operator = make_shared>(clauses, link_templates, true); } else { LOG_DEBUG("Pushing OR"); new_operator = make_shared>(clauses, link_templates); diff --git a/src/agents/query_engine/MettaParserActions.h b/src/agents/query_engine/MettaParserActions.h index 41d6653e..29b320b1 100644 --- a/src/agents/query_engine/MettaParserActions.h +++ b/src/agents/query_engine/MettaParserActions.h @@ -13,7 +13,7 @@ using namespace query_element; namespace query_engine { -enum ExpressionType { LINK, LINK_TEMPLATE, AND, OR, CHAIN }; +enum ExpressionType { LINK, LINK_TEMPLATE, AND, ANDNOT, OR, CHAIN }; /** * diff --git a/src/agents/query_engine/PatternMatchingQueryProcessor.cc b/src/agents/query_engine/PatternMatchingQueryProcessor.cc index d92ff0c1..0b187e66 100644 --- a/src/agents/query_engine/PatternMatchingQueryProcessor.cc +++ b/src/agents/query_engine/PatternMatchingQueryProcessor.cc @@ -30,6 +30,7 @@ using namespace metta; using namespace attention_broker; string PatternMatchingQueryProcessor::AND = "AND"; +string PatternMatchingQueryProcessor::ANDNOT = "ANDNOT"; string PatternMatchingQueryProcessor::OR = "OR"; string PatternMatchingQueryProcessor::CHAIN = "CHAIN"; @@ -297,7 +298,7 @@ shared_ptr PatternMatchingQueryProcessor::setup_query_tree( cursor += 3; } else if ((query_tokens[cursor] == LinkSchema::UNTYPED_VARIABLE) || (query_tokens[cursor] == LinkSchema::ATOM) || (query_tokens[cursor] == AND) || - (query_tokens[cursor] == OR)) { + (query_tokens[cursor] == ANDNOT) || (query_tokens[cursor] == OR)) { cursor += 2; } else if (query_tokens[cursor] == CHAIN) { cursor += 4; @@ -327,7 +328,12 @@ shared_ptr PatternMatchingQueryProcessor::setup_query_tree( } else if (query_tokens[cursor] == LinkSchema::LINK_TEMPLATE) { element_stack.push(build_link_template(proxy, cursor, element_stack)); } else if (query_tokens[cursor] == AND) { - element_stack.push(build_and(proxy, cursor, element_stack)); + element_stack.push(build_and(proxy, false, cursor, element_stack)); + if (proxy->parameters.get(BaseQueryProxy::UNIQUE_ASSIGNMENT_FLAG)) { + element_stack.push(build_unique_assignment_filter(proxy, cursor, element_stack)); + } + } else if (query_tokens[cursor] == ANDNOT) { + element_stack.push(build_and(proxy, true, cursor, element_stack)); if (proxy->parameters.get(BaseQueryProxy::UNIQUE_ASSIGNMENT_FLAG)) { element_stack.push(build_unique_assignment_filter(proxy, cursor, element_stack)); } @@ -381,7 +387,7 @@ shared_ptr PatternMatchingQueryProcessor::build_link_template( return link_template; } -#define BUILD_AND(N) \ +#define BUILD_AND(N, AND_NOT_FLAG) \ { \ vector> link_templates; \ array, N> clauses; \ @@ -400,11 +406,12 @@ shared_ptr PatternMatchingQueryProcessor::build_link_template( } \ element_stack.pop(); \ } \ - return make_shared>(clauses, link_templates); \ + return make_shared>(clauses, link_templates, AND_NOT_FLAG); \ } shared_ptr PatternMatchingQueryProcessor::build_and( shared_ptr proxy, + bool and_not_flag, unsigned int cursor, stack>& element_stack) { const vector query_tokens = proxy->get_query_tokens(); @@ -414,16 +421,16 @@ shared_ptr PatternMatchingQueryProcessor::build_and( } // clang-format off switch (num_clauses) { - case 1: BUILD_AND(1) - case 2: BUILD_AND(2) - case 3: BUILD_AND(3) - case 4: BUILD_AND(4) - case 5: BUILD_AND(5) - case 6: BUILD_AND(6) - case 7: BUILD_AND(7) - case 8: BUILD_AND(8) - case 9: BUILD_AND(9) - case 10: BUILD_AND(10) + case 1: BUILD_AND(1, and_not_flag) + case 2: BUILD_AND(2, and_not_flag) + case 3: BUILD_AND(3, and_not_flag) + case 4: BUILD_AND(4, and_not_flag) + case 5: BUILD_AND(5, and_not_flag) + case 6: BUILD_AND(6, and_not_flag) + case 7: BUILD_AND(7, and_not_flag) + case 8: BUILD_AND(8, and_not_flag) + case 9: BUILD_AND(9, and_not_flag) + case 10: BUILD_AND(10, and_not_flag) // clang-format on default: { RAISE_ERROR("PATTERN_MATCHING_QUERY message: max supported num_clauses for AND: 10"); diff --git a/src/agents/query_engine/PatternMatchingQueryProcessor.h b/src/agents/query_engine/PatternMatchingQueryProcessor.h index a56e6f55..493c8c14 100644 --- a/src/agents/query_engine/PatternMatchingQueryProcessor.h +++ b/src/agents/query_engine/PatternMatchingQueryProcessor.h @@ -64,6 +64,7 @@ class PatternMatchingQueryProcessor : public BusCommandProcessor { stack>& element_stack); shared_ptr build_and(shared_ptr proxy, + bool and_not_flag, unsigned int cursor, stack>& element_stack); @@ -91,6 +92,7 @@ class PatternMatchingQueryProcessor : public BusCommandProcessor { shared_ptr proxy; shared_ptr atomdb; static string AND; + static string ANDNOT; static string OR; static string CHAIN; }; diff --git a/src/agents/query_engine/query_element/And.h b/src/agents/query_engine/query_element/And.h index 57644a46..bd192f61 100644 --- a/src/agents/query_engine/query_element/And.h +++ b/src/agents/query_engine/query_element/And.h @@ -28,11 +28,15 @@ class And : public Operator { * @param clauses Array with N clauses (each clause is supposed to be a Source or an Operator). * @param link_templates Vector with all the query elements in clauses which are LinkTemplate * objects. This is stored in the And object just to make shure they don't get released before + * @param not_operator_flag A flag indicating if this AND operator should act like a AND_NOT + * operator instead. An AND_NOT operator is like an AND operator but it assumes a NOT attached + * to its last clause. For instance AND_NOT(A, B, C) is true if A AND B AND NOT C is true. * the And operation ends. */ And(const array, N>& clauses, - const vector>& link_templates = {}) - : Operator(clauses) { + const vector>& link_templates = {}, + bool not_operator_flag = false) + : Operator(clauses), not_operator_flag(not_operator_flag) { initialize(clauses); this->link_templates = link_templates; } @@ -42,12 +46,14 @@ class And : public Operator { */ ~And() { graceful_shutdown(); + LOG_LOCAL_DEBUG("AND operator destructor. Deleting query answers..."); for (size_t i = 0; i < N; i++) { for (auto answer : this->query_answer[i]) { delete answer; } this->query_answer[i].clear(); } + LOG_LOCAL_DEBUG("AND operator destructor. Deleting query answers... DONE"); } // -------------------------------------------------------------------------------------------- @@ -59,7 +65,9 @@ class And : public Operator { } virtual void graceful_shutdown() { + LOG_LOCAL_DEBUG("And::graceful_shutdown() BEGIN"); if (Operator::is_flow_finished()) { + LOG_LOCAL_DEBUG("And::graceful_shutdown() early END"); return; } Operator::graceful_shutdown(); @@ -68,6 +76,7 @@ class And : public Operator { delete this->operator_thread; this->operator_thread = NULL; } + LOG_LOCAL_DEBUG("And::graceful_shutdown() END"); } // -------------------------------------------------------------------------------------------- @@ -124,6 +133,8 @@ class And : public Operator { thread* operator_thread; unsigned int query_answer_count; vector> link_templates; + bool not_operator_flag; + unsigned int num_and_clauses; void initialize(const array, N>& clauses) { this->operator_thread = NULL; @@ -133,7 +144,13 @@ class And : public Operator { } this->no_more_answers_to_arrive = false; this->query_answer_count = 0; - this->id = "And("; + if (this->not_operator_flag) { + this->id = "AndNot("; + this->num_and_clauses = N - 1; + } else { + this->id = "And("; + this->num_and_clauses = N; + } for (unsigned int i = 0; i < N; i++) { this->id += clauses[i]->id; if (i != (N - 1)) { @@ -145,13 +162,20 @@ class And : public Operator { } bool ready_to_process_candidate() { - for (unsigned int i = 0; i < N; i++) { + for (unsigned int i = 0; i < this->num_and_clauses; i++) { if ((!this->all_answers_arrived[i]) && (this->query_answer[i].size() <= (this->next_input_to_process[i] + 1))) { return false; } } - return true; + if (this->not_operator_flag) { + // If running an AndNot operator, all answers of the NOT clause must arrive + // BEFORE any combination is eavaluated because we must be sure that any potentially + // approved combination aren't returned in the NOT clause. + return this->all_answers_arrived[this->num_and_clauses]; + } else { + return true; + } } void ingest_newly_arrived_answers() { @@ -184,13 +208,28 @@ class And : public Operator { void operate_candidate(const CandidateRecord& candidate) { QueryAnswer* new_query_answer = QueryAnswer::copy(candidate.answer[0]); - for (unsigned int i = 1; i < N; i++) { + for (unsigned int i = 1; i < this->num_and_clauses; i++) { if (!new_query_answer->merge(candidate.answer[i])) { delete new_query_answer; return; } } + if (this->not_operator_flag) { + if (this->query_answer[this->num_and_clauses].size() == 0) { + LOG_DEBUG("NOT clause didn't match. Disregarding it."); + } else { + for (auto answer : this->query_answer[this->num_and_clauses]) { + LOG_DEBUG(new_query_answer->to_string() + " AND NOT " + answer->to_string()); + if (new_query_answer->assignment.is_compatible(answer->assignment)) { + LOG_DEBUG("Discarding query answer"); + delete new_query_answer; + return; + } + } + } + } this->query_answer_count++; + LOG_DEBUG("Reporting answer: " + new_query_answer->to_string()); this->output_buffer->add_query_answer(new_query_answer); } @@ -198,13 +237,13 @@ class And : public Operator { if (this->border.size() > 0) { return false; } else { - for (unsigned int i = 0; i < N; i++) { + for (unsigned int i = 0; i < this->num_and_clauses; i++) { if ((this->next_input_to_process[i] == this->query_answer[i].size()) && (this->all_answers_arrived[i])) { return true; } } - for (unsigned int i = 0; i < N; i++) { + for (unsigned int i = 0; i < this->num_and_clauses; i++) { if (this->next_input_to_process[i] < this->query_answer[i].size()) { return false; } @@ -217,10 +256,12 @@ class And : public Operator { CandidateRecord candidate; unsigned int index_in_queue; bool abort_candidate; - for (unsigned int new_candidate_count = 0; new_candidate_count < N; new_candidate_count++) { + for (unsigned int new_candidate_count = 0; new_candidate_count < this->num_and_clauses; + new_candidate_count++) { abort_candidate = false; candidate.fitness = 1.0; - for (unsigned int answer_queue_index = 0; answer_queue_index < N; answer_queue_index++) { + for (unsigned int answer_queue_index = 0; answer_queue_index < this->num_and_clauses; + answer_queue_index++) { index_in_queue = last_used_candidate.index[answer_queue_index]; if (answer_queue_index == new_candidate_count) { index_in_queue++; @@ -241,6 +282,10 @@ class And : public Operator { if (abort_candidate) { continue; } + if (this->not_operator_flag) { + candidate.answer[this->num_and_clauses] = NULL; + candidate.index[this->num_and_clauses] = 0; + } if (visited.find(candidate) == visited.end()) { this->border.push(candidate); this->visited.insert(candidate); @@ -277,7 +322,7 @@ class And : public Operator { // processed_all_input() is double-checked on purpose to avoid race condition processed_all_input()) { this->output_buffer->query_answers_finished(); - LOG_INFO(this->id << " processed " << this->query_answer_count << " answers."); + LOG_INFO(this->id << " reported " << this->query_answer_count << " answers."); } STOP_WATCH_STOP(and_operator); Utils::sleep(); @@ -291,12 +336,16 @@ class And : public Operator { } CandidateRecord candidate; double fitness = 1.0; - for (unsigned int i = 0; i < N; i++) { + for (unsigned int i = 0; i < this->num_and_clauses; i++) { candidate.answer[i] = this->query_answer[i][this->next_input_to_process[i]], candidate.index[i] = this->next_input_to_process[i]; this->next_input_to_process[i]++; fitness *= candidate.answer[i]->importance; } + if (this->not_operator_flag) { + candidate.answer[this->num_and_clauses] = NULL; + candidate.index[this->num_and_clauses] = 0; + } candidate.fitness = fitness; this->border.push(candidate); this->visited.insert(candidate); diff --git a/src/metta/MettaMapping.cc b/src/metta/MettaMapping.cc index fb811177..adb5cd04 100644 --- a/src/metta/MettaMapping.cc +++ b/src/metta/MettaMapping.cc @@ -5,5 +5,6 @@ using namespace commons; string MettaMapping::EXPRESSION_LINK_TYPE = "Expression"; string MettaMapping::SYMBOL_NODE_TYPE = "Symbol"; string MettaMapping::AND_QUERY_OPERATOR = "and"; +string MettaMapping::ANDNOT_QUERY_OPERATOR = "andnot"; string MettaMapping::OR_QUERY_OPERATOR = "or"; string MettaMapping::CHAIN_QUERY_OPERATOR = "chain"; diff --git a/src/metta/MettaMapping.h b/src/metta/MettaMapping.h index 831760e1..ce2b125b 100644 --- a/src/metta/MettaMapping.h +++ b/src/metta/MettaMapping.h @@ -18,6 +18,7 @@ class MettaMapping { static string EXPRESSION_LINK_TYPE; static string SYMBOL_NODE_TYPE; static string AND_QUERY_OPERATOR; + static string ANDNOT_QUERY_OPERATOR; static string OR_QUERY_OPERATOR; static string CHAIN_QUERY_OPERATOR; }; diff --git a/src/tests/cpp/and_operator_test.cc b/src/tests/cpp/and_operator_test.cc index 1553e5ca..77485966 100644 --- a/src/tests/cpp/and_operator_test.cc +++ b/src/tests/cpp/and_operator_test.cc @@ -16,13 +16,14 @@ using namespace query_element; class TestSource : public Source { public: TestSource(unsigned int count) { this->id = "TestSource_" + std::to_string(count); } + TestSource() { this->id = "TestSource_" + Utils::random_string(30); } ~TestSource() {} void add(const char* handle, double importance, - const array& labels, - const array& values, + const vector& labels, + const vector& values, bool sleep_flag = true) { QueryAnswer* query_answer = new QueryAnswer(handle, importance); for (unsigned int i = 0; i < labels.size(); i++) { @@ -39,7 +40,8 @@ class TestSource : public Source { class TestSink : public Sink { public: - TestSink(shared_ptr precedent) : Sink(precedent, "TestSink(" + precedent->id + ")") {} + TestSink(shared_ptr precedent) + : Sink(precedent, "TestSink(" + precedent->id + "," + Utils::random_string(30) + ")") {} ~TestSink() {} bool empty() { return this->input_buffer->is_query_answers_empty(); } bool finished() { return this->input_buffer->is_query_answers_finished(); } @@ -51,7 +53,7 @@ void check_query_answer(string tag, double importance, unsigned int handles_size, const array& handles) { - cout << "check_query_answer(" + tag + ")" << endl; + LOG_INFO("check_query_answer(" + tag + "); " + query_answer->to_string()); EXPECT_TRUE(double_equals(query_answer->importance, importance)); EXPECT_EQ(query_answer->get_handles_size(), 2); set set_handles; @@ -63,8 +65,8 @@ void check_query_answer(string tag, } TEST(AndOperator, basics) { - auto source1 = make_shared(10); - auto source2 = make_shared(20); + auto source1 = make_shared(); + auto source2 = make_shared(); auto and_operator = make_shared>(array, 2>({source1, source2})); TestSink sink(and_operator); QueryAnswer* query_answer; @@ -237,8 +239,8 @@ TEST(AndOperator, operation_logic) { } TEST(AndOperator, empty_source) { - auto source1 = make_shared(100); - auto source2 = make_shared(200); + auto source1 = make_shared(); + auto source2 = make_shared(); auto and_operator = make_shared>(array, 2>({source1, source2})); TestSink sink(and_operator); Utils::sleep(1000); @@ -256,3 +258,229 @@ TEST(AndOperator, empty_source) { EXPECT_TRUE(sink.empty()); EXPECT_TRUE(sink.finished()); } + +TEST(AndOperator, not_operator_1) { + array, 3> source; + source[0] = make_shared(); + source[1] = make_shared(); + source[2] = make_shared(); + vector> dummy; + auto and_operator = make_shared>( + array, 3>({source[0], source[1], source[2]}), dummy, true); + TestSink sink(and_operator); + QueryAnswer* query_answer; + + source[0]->add("S0_1", 0.9, {"v1"}, {"1"}); + source[0]->add("S0_2", 0.8, {"v1"}, {"2"}); + source[0]->add("S0_3", 0.7, {"v1"}, {"3"}); + source[1]->add("S1_1", 0.3, {"v2"}, {"1"}); + source[1]->add("S1_2", 0.2, {"v2"}, {"2"}); + source[1]->add("S1_3", 0.1, {"v2"}, {"3"}); + + source[2]->add("S2_1", 1.0, {"v1"}, {"2"}); + unsigned int expected_count = 6; + + Utils::sleep(SLEEP_DURATION); + source[0]->query_answers_finished(); + source[1]->query_answers_finished(); + source[2]->query_answers_finished(); + + unsigned int count = 0; + while (count < expected_count) { + if (sink.empty()) { + Utils::sleep(); + continue; + } + if (sink.finished() && sink.empty()) { + break; + } + EXPECT_FALSE((query_answer = dynamic_cast(sink.pop())) == NULL); + LOG_INFO("query_answer: " + query_answer->to_string()); + EXPECT_EQ(query_answer->get_handles_vector().size(), 2); + for (string handle : query_answer->get_handles_vector()) { + EXPECT_NE(handle, string("S0_2")); + } + count++; + } + EXPECT_EQ(count, expected_count); + Utils::sleep(SLEEP_DURATION); + EXPECT_TRUE(sink.empty()); + EXPECT_TRUE(sink.finished()); +} + +TEST(AndOperator, not_operator_2) { + array, 3> source; + source[0] = make_shared(); + source[1] = make_shared(); + source[2] = make_shared(); + vector> dummy; + auto and_operator = make_shared>( + array, 3>({source[0], source[1], source[2]}), dummy, true); + TestSink sink(and_operator); + QueryAnswer* query_answer; + + source[0]->add("S0_1", 0.9, {"v1"}, {"1"}); + source[0]->add("S0_2", 0.8, {"v1"}, {"2"}); + source[0]->add("S0_3", 0.7, {"v1"}, {"3"}); + source[1]->add("S1_1", 0.3, {"v2"}, {"1"}); + source[1]->add("S1_2", 0.2, {"v2"}, {"2"}); + source[1]->add("S1_3", 0.1, {"v2"}, {"3"}); + + source[2]->add("S2_1", 1.0, {"v1", "v2"}, {"2", "3"}); + unsigned int expected_count = 8; + + Utils::sleep(SLEEP_DURATION); + source[0]->query_answers_finished(); + source[1]->query_answers_finished(); + source[2]->query_answers_finished(); + + unsigned int count = 0; + while (count < expected_count) { + if (sink.empty()) { + Utils::sleep(); + continue; + } + if (sink.finished() && sink.empty()) { + break; + } + EXPECT_FALSE((query_answer = dynamic_cast(sink.pop())) == NULL); + LOG_INFO("query_answer: " + query_answer->to_string()); + EXPECT_EQ(query_answer->get_handles_vector().size(), 2); + auto handles = query_answer->get_handles_vector(); + EXPECT_FALSE((handles[0] == "S0_2") && (handles[1] == "S1_3")); + count++; + } + EXPECT_EQ(count, expected_count); + Utils::sleep(SLEEP_DURATION); + EXPECT_TRUE(sink.empty()); + EXPECT_TRUE(sink.finished()); +} + +TEST(AndOperator, not_operator_3) { + array, 3> source; + source[0] = make_shared(); + source[1] = make_shared(); + source[2] = make_shared(); + vector> dummy; + auto and_operator = make_shared>( + array, 3>({source[0], source[1], source[2]}), dummy, true); + TestSink sink(and_operator); + QueryAnswer* query_answer; + + source[0]->add("S0_1", 0.9, {"v1"}, {"1"}); + source[0]->add("S0_2", 0.8, {"v1"}, {"2"}); + source[0]->add("S0_3", 0.7, {"v1"}, {"3"}); + source[1]->add("S1_1", 0.3, {"v2"}, {"1"}); + source[1]->add("S1_2", 0.2, {"v2"}, {"2"}); + source[1]->add("S1_3", 0.1, {"v2"}, {"3"}); + + source[2]->add("S2_1", 1.0, {"v3"}, {"0"}); + unsigned int expected_count = 0; + + Utils::sleep(SLEEP_DURATION); + source[0]->query_answers_finished(); + source[1]->query_answers_finished(); + source[2]->query_answers_finished(); + + while (!(sink.empty() && sink.finished())) { + if (sink.empty()) { + Utils::sleep(); + continue; + } + FAIL(); + } +} + +TEST(AndOperator, not_operator_4) { + array, 3> source; + source[0] = make_shared(); + source[1] = make_shared(); + source[2] = make_shared(); + vector> dummy; + auto and_operator = make_shared>( + array, 3>({source[0], source[1], source[2]}), dummy, true); + TestSink sink(and_operator); + QueryAnswer* query_answer; + + source[0]->add("S0_1", 0.9, {"v1"}, {"1"}); + source[0]->add("S0_2", 0.8, {"v1"}, {"2"}); + source[0]->add("S0_3", 0.7, {"v1"}, {"3"}); + source[1]->add("S1_1", 0.3, {"v2"}, {"1"}); + source[1]->add("S1_2", 0.2, {"v2"}, {"2"}); + source[1]->add("S1_3", 0.1, {"v2"}, {"3"}); + + source[2]->add("S2_1", 1.0, {"v1"}, {"2"}); + source[2]->add("S2_2", 1.0, {"v2"}, {"3"}); + unsigned int expected_count = 4; + + Utils::sleep(SLEEP_DURATION); + source[0]->query_answers_finished(); + source[1]->query_answers_finished(); + source[2]->query_answers_finished(); + + unsigned int count = 0; + while (count < expected_count) { + if (sink.empty()) { + Utils::sleep(); + continue; + } + if (sink.finished() && sink.empty()) { + break; + } + EXPECT_FALSE((query_answer = dynamic_cast(sink.pop())) == NULL); + LOG_INFO("query_answer: " + query_answer->to_string()); + EXPECT_EQ(query_answer->get_handles_vector().size(), 2); + auto handles = query_answer->get_handles_vector(); + EXPECT_FALSE((handles[0] == "S0_2") || (handles[1] == "S1_3")); + count++; + } + EXPECT_EQ(count, expected_count); + Utils::sleep(SLEEP_DURATION); + EXPECT_TRUE(sink.empty()); + EXPECT_TRUE(sink.finished()); +} + +TEST(AndOperator, not_operator_5) { + array, 3> source; + source[0] = make_shared(); + source[1] = make_shared(); + source[2] = make_shared(); + vector> dummy; + auto and_operator = make_shared>( + array, 3>({source[0], source[1], source[2]}), dummy, true); + TestSink sink(and_operator); + QueryAnswer* query_answer; + + source[0]->add("S0_1", 0.9, {"v1"}, {"1"}); + source[0]->add("S0_2", 0.8, {"v1"}, {"2"}); + source[0]->add("S0_3", 0.7, {"v1"}, {"3"}); + source[1]->add("S1_1", 0.3, {"v2"}, {"1"}); + source[1]->add("S1_2", 0.2, {"v2"}, {"2"}); + source[1]->add("S1_3", 0.1, {"v2"}, {"3"}); + + unsigned int expected_count = 9; + + Utils::sleep(SLEEP_DURATION); + source[0]->query_answers_finished(); + source[1]->query_answers_finished(); + source[2]->query_answers_finished(); + + unsigned int count = 0; + while (count < expected_count) { + if (sink.empty()) { + Utils::sleep(); + continue; + } + if (sink.finished() && sink.empty()) { + break; + } + EXPECT_FALSE((query_answer = dynamic_cast(sink.pop())) == NULL); + LOG_INFO("query_answer: " + query_answer->to_string()); + EXPECT_EQ(query_answer->get_handles_vector().size(), 2); + count++; + } + EXPECT_EQ(count, expected_count); + Utils::sleep(SLEEP_DURATION); + EXPECT_TRUE(sink.empty()); + EXPECT_TRUE(sink.finished()); +} diff --git a/src/tests/cpp/pattern_matching_query_test.cc b/src/tests/cpp/pattern_matching_query_test.cc index 23cb491f..af86ca68 100644 --- a/src/tests/cpp/pattern_matching_query_test.cc +++ b/src/tests/cpp/pattern_matching_query_test.cc @@ -500,6 +500,37 @@ TEST(PatternMatchingQuery, queries) { string q14m = ""; int q14_expected_count = 2; + vector q15 = { + "ANDNOT", "2", + "LINK_TEMPLATE", "Expression", "3", + "NODE", "Symbol", "Similarity", + "VARIABLE", "v1", + "NODE", "Symbol", "\"human\"", + "LINK_TEMPLATE", "Expression", "3", + "NODE", "Symbol", "Inheritance", + "VARIABLE", "v1", + "NODE", "Symbol", "\"plant\"" + }; + string q15m = "(andnot (Similarity $v1 \"human\") (Inheritance $v1 \"plant\"))"; + int q15_expected_count = 2; + + vector q16 = { + "ANDNOT", "2", + "LINK_TEMPLATE", "Expression", "3", + "NODE", "Symbol", "Similarity", + "VARIABLE", "v1", + "NODE", "Symbol", "\"human\"", + "CHAIN", "0", "1", "2", + "VARIABLE", "v1", + "NODE", "Symbol", "\"animal\"", + "LINK_TEMPLATE", "Expression", "3", + "NODE", "Symbol", "Inheritance", + "VARIABLE", "vc1", + "VARIABLE", "vc2", + }; + string q16m = "(andnot (Similarity $v1 \"human\") (chain 0 1 2 $v1 \"animal\" (Inheritance $vc1 $vc2)))"; + int q16_expected_count = 1; + // Regular queries check_query("q1", q1, q1m, q1_expected_count, client_bus, "PatternMatchingQuery.queries", false, false, false, false, false); check_query("q2", q2, q2m, q2_expected_count, client_bus, "PatternMatchingQuery.queries", false, false, false, false, false); @@ -516,6 +547,8 @@ TEST(PatternMatchingQuery, queries) { check_query("q12", q12, q12m, q12_expected_count, client_bus, "PatternMatchingQuery.queries", false, true, false, false, false); check_query("q13", q13, q13m, q13_expected_count, client_bus, "PatternMatchingQuery.queries", false, true, false, false, false); check_query("q14", q14, q14m, q14_expected_count, client_bus, "PatternMatchingQuery.queries", false, true, false, false, false); + check_query("q15", q15, q15m, q15_expected_count, client_bus, "PatternMatchingQuery.queries", false, false, false, false, false); + check_query("q16", q16, q16m, q16_expected_count, client_bus, "PatternMatchingQuery.queries", false, false, false, false, false); // Importance filtering // XXX AttentionBroker is being revised so its dynamics is a bit unpredictable right now