From 164af166aaeb036fc2d240e1125f4e33380b2f54 Mon Sep 17 00:00:00 2001 From: Dimos Tsouros Date: Mon, 21 Oct 2024 12:37:31 +0200 Subject: [PATCH 01/13] model.solve() not constraints.solve() --- pycona/active_algorithms/gquacq.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pycona/active_algorithms/gquacq.py b/pycona/active_algorithms/gquacq.py index 6e22c15..2517c9d 100644 --- a/pycona/active_algorithms/gquacq.py +++ b/pycona/active_algorithms/gquacq.py @@ -1,4 +1,5 @@ import time +import cpmpy as cp import networkx as nx from cpmpy.transformations.get_variables import get_variables @@ -117,7 +118,7 @@ def mineAsk(self, r): # if potentially generalizing leads to unsat, continue to next new_CL = self.env.instance.cl.copy() new_CL += B - if new_CL.solve() and self.env.ask_generalization_query(r, B): + if cp.Model(new_CL).solve() and self.env.ask_generalization_query(r, B): gen_flag = True self.env.add_to_cl(B) else: From 1771df3dba0f9a7f18af36d0ee176f59f9f279a4 Mon Sep 17 00:00:00 2001 From: Dimos Tsouros Date: Mon, 21 Oct 2024 12:37:44 +0200 Subject: [PATCH 02/13] actually we give a relation --- pycona/ca_environment/active_ca.py | 1 - 1 file changed, 1 deletion(-) diff --git a/pycona/ca_environment/active_ca.py b/pycona/ca_environment/active_ca.py index a240d7b..c5846be 100644 --- a/pycona/ca_environment/active_ca.py +++ b/pycona/ca_environment/active_ca.py @@ -194,7 +194,6 @@ def ask_generalization_query(self, c, C): :param C: A list of constraints to which the generalization is applied. :return: The oracle's answer to the generalization query (True/False). """ - assert isinstance(c, Expression), "Generalization queries first input needs to be a constraint" assert isinstance(C, list), "Generalization queries second input needs to be a list of constraints" assert all(isinstance(c1, Expression) for c1 in C), "Generalization queries second input needs to be " \ "a list of constraints" From e1646c24387eda5f5fbed7368ef851591715824b Mon Sep 17 00:00:00 2001 From: Dimos Tsouros Date: Mon, 21 Oct 2024 12:37:58 +0200 Subject: [PATCH 03/13] only check if in a set, not list ... --- pycona/answering_queries/constraint_oracle.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pycona/answering_queries/constraint_oracle.py b/pycona/answering_queries/constraint_oracle.py index dde5441..3f02a6c 100644 --- a/pycona/answering_queries/constraint_oracle.py +++ b/pycona/answering_queries/constraint_oracle.py @@ -58,7 +58,7 @@ def answer_recommendation_query(self, c): :return: A boolean indicating if the recommended constraint is in the set of constraints. """ # Check if the recommended constraint is in the set of constraints - return c in self.constraints + return c in set(self.constraints) def answer_generalization_query(self, C): """ From ef96afddc04c71762aba72962e6cbe6b24d79837 Mon Sep 17 00:00:00 2001 From: Dimos Tsouros Date: Wed, 23 Oct 2024 23:14:59 +0200 Subject: [PATCH 04/13] Update constraint_oracle.py --- pycona/answering_queries/constraint_oracle.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/pycona/answering_queries/constraint_oracle.py b/pycona/answering_queries/constraint_oracle.py index 3f02a6c..77842c4 100644 --- a/pycona/answering_queries/constraint_oracle.py +++ b/pycona/answering_queries/constraint_oracle.py @@ -46,7 +46,6 @@ def answer_membership_query(self, Y): # Need the oracle to answer based only on the constraints with a scope that is a subset of Y suboracle = get_con_subset(self.constraints, Y) - # Check if at least one constraint is violated or not return all([check_value(c) for c in suboracle]) @@ -58,7 +57,7 @@ def answer_recommendation_query(self, c): :return: A boolean indicating if the recommended constraint is in the set of constraints. """ # Check if the recommended constraint is in the set of constraints - return c in set(self.constraints) + return c in self.constraints def answer_generalization_query(self, C): """ From 1d0a7375f66d8f8f745b6b89d2cbbfc9c2527693 Mon Sep 17 00:00:00 2001 From: Dimos Tsouros Date: Wed, 30 Oct 2024 14:46:37 +0100 Subject: [PATCH 05/13] try to import networkx --- pycona/active_algorithms/gquacq.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/pycona/active_algorithms/gquacq.py b/pycona/active_algorithms/gquacq.py index 2517c9d..2f3837f 100644 --- a/pycona/active_algorithms/gquacq.py +++ b/pycona/active_algorithms/gquacq.py @@ -1,7 +1,6 @@ import time import cpmpy as cp -import networkx as nx from cpmpy.transformations.get_variables import get_variables from .algorithm_core import AlgorithmCAInteractive @@ -91,6 +90,11 @@ def mineAsk(self, r): :param r: The index of a relation in gamma. :return: List of learned constraints. """ + try: + import networkx as nx + except ImportError: + raise ImportError("To use the predictAsk function of PQuAcq, networkx needs to be installed") + gq_counter = 0 C = [c for c in self.env.instance.cl if get_relation(c, self.env.instance.language) == r] From 7da5cfdd4bfac2245756f1817018c7fb321d21d8 Mon Sep 17 00:00:00 2001 From: Dimos Tsouros Date: Wed, 30 Oct 2024 15:03:04 +0100 Subject: [PATCH 06/13] Update exam_timetabling.py --- pycona/benchmarks/exam_timetabling.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/pycona/benchmarks/exam_timetabling.py b/pycona/benchmarks/exam_timetabling.py index ba617be..229e8fe 100644 --- a/pycona/benchmarks/exam_timetabling.py +++ b/pycona/benchmarks/exam_timetabling.py @@ -16,8 +16,9 @@ def construct_examtt_simple(nsemesters=9, courses_per_semester=6, slots_per_day= total_courses = nsemesters * courses_per_semester total_slots = slots_per_day * days_for_exams - parameter_vars = ['nsemesters', 'courses_per_semester', 'slots_per_day', 'days_for_exams'] - parameters = {var_name: locals()[var_name] for var_name in parameter_vars} + parameters = {'nsemesters': nsemesters, 'courses_per_semester': courses_per_semester, + 'slots_per_day': slots_per_day, 'days_for_exams': days_for_exams} + # Variables courses = cp.intvar(1, total_slots, shape=(nsemesters, courses_per_semester), name="courses") From bec7c209d7315af0aa1af798d1572616fec5728a Mon Sep 17 00:00:00 2001 From: Dimos Tsouros Date: Wed, 30 Oct 2024 15:20:19 +0100 Subject: [PATCH 07/13] only check if in a set, not list ... again --- pycona/answering_queries/constraint_oracle.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pycona/answering_queries/constraint_oracle.py b/pycona/answering_queries/constraint_oracle.py index 77842c4..507fdb0 100644 --- a/pycona/answering_queries/constraint_oracle.py +++ b/pycona/answering_queries/constraint_oracle.py @@ -57,7 +57,7 @@ def answer_recommendation_query(self, c): :return: A boolean indicating if the recommended constraint is in the set of constraints. """ # Check if the recommended constraint is in the set of constraints - return c in self.constraints + return c in set(self.constraints) def answer_generalization_query(self, C): """ From 83f3bdfac5607fc00ffd3c3e0e51d23931bbd8d0 Mon Sep 17 00:00:00 2001 From: Dimos Tsouros Date: Wed, 30 Oct 2024 16:07:40 +0100 Subject: [PATCH 08/13] Update requirements.txt --- requirements.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 1af028c..482abbb 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,2 +1,3 @@ cpmpy>=0.9 -scikit-learn \ No newline at end of file +scikit-learn +networkx \ No newline at end of file From daea911240abeff17e389690f2e1c8196f70a9b0 Mon Sep 17 00:00:00 2001 From: Dimos Tsouros Date: Sun, 3 Nov 2024 16:35:36 +0100 Subject: [PATCH 09/13] fix constraint checking in recommendation queries --- pycona/answering_queries/constraint_oracle.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/pycona/answering_queries/constraint_oracle.py b/pycona/answering_queries/constraint_oracle.py index 507fdb0..cda12dd 100644 --- a/pycona/answering_queries/constraint_oracle.py +++ b/pycona/answering_queries/constraint_oracle.py @@ -1,3 +1,4 @@ +import cpmpy as cp from cpmpy.transformations.normalize import toplevel_list from .oracle import Oracle @@ -56,8 +57,10 @@ def answer_recommendation_query(self, c): :param c: The recommended constraint. :return: A boolean indicating if the recommended constraint is in the set of constraints. """ - # Check if the recommended constraint is in the set of constraints - return c in set(self.constraints) + # Check if the recommended constraint is in the set of constraints or implied by them + m = cp.Model(self.constraints) + m += ~c + return not m.solve() def answer_generalization_query(self, C): """ From 7e9d430b453bc9d0e6aa7446eeaa05047880d6a4 Mon Sep 17 00:00:00 2001 From: Dimos Tsouros Date: Tue, 26 Nov 2024 11:19:09 +0100 Subject: [PATCH 10/13] dont print --- pycona/benchmarks/sudoku.py | 1 - 1 file changed, 1 deletion(-) diff --git a/pycona/benchmarks/sudoku.py b/pycona/benchmarks/sudoku.py index 17b4e67..9c4f3e8 100644 --- a/pycona/benchmarks/sudoku.py +++ b/pycona/benchmarks/sudoku.py @@ -41,5 +41,4 @@ def construct_sudoku(block_size_row, block_size_col, grid_size): oracle = ConstraintOracle(C_T) - print(len(C_T)) return instance, oracle From 2ce9385fa76ba9fcf6ae1b76fc6f1a8a33e4f915 Mon Sep 17 00:00:00 2001 From: Senne Berden Date: Wed, 27 Nov 2024 18:59:15 +0100 Subject: [PATCH 11/13] Removed some unused variables and updated documentation --- pycona/answering_queries/constraint_oracle.py | 3 ++- pycona/benchmarks/exam_timetabling.py | 3 --- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/pycona/answering_queries/constraint_oracle.py b/pycona/answering_queries/constraint_oracle.py index cda12dd..31f5e49 100644 --- a/pycona/answering_queries/constraint_oracle.py +++ b/pycona/answering_queries/constraint_oracle.py @@ -52,7 +52,8 @@ def answer_membership_query(self, Y): def answer_recommendation_query(self, c): """ - Answer a recommendation query by checking if the recommended constraint is part of the constraints. + Answer a recommendation query by checking if the recommended constraint is part of the target set of + constraints, or logically implied by the constraints in the target set of constraints. :param c: The recommended constraint. :return: A boolean indicating if the recommended constraint is in the set of constraints. diff --git a/pycona/benchmarks/exam_timetabling.py b/pycona/benchmarks/exam_timetabling.py index 229e8fe..07cff1f 100644 --- a/pycona/benchmarks/exam_timetabling.py +++ b/pycona/benchmarks/exam_timetabling.py @@ -12,8 +12,6 @@ def construct_examtt_simple(nsemesters=9, courses_per_semester=6, slots_per_day= """ :return: a ProblemInstance object, along with a constraint-based oracle """ - - total_courses = nsemesters * courses_per_semester total_slots = slots_per_day * days_for_exams parameters = {'nsemesters': nsemesters, 'courses_per_semester': courses_per_semester, @@ -32,7 +30,6 @@ def construct_examtt_simple(nsemesters=9, courses_per_semester=6, slots_per_day= C_T = list(model.constraints) if model.solve(): - solution = courses.value() courses.clear() else: print("no solution") From e0a107cff53057d08be802f52ae1589c4cb745df Mon Sep 17 00:00:00 2001 From: Dimos Tsouros Date: Wed, 27 Nov 2024 20:09:58 +0100 Subject: [PATCH 12/13] separate better all conditions from asking a query + do not ask if does not add a constraint --- pycona/active_algorithms/gquacq.py | 27 ++++++++++++++++----------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/pycona/active_algorithms/gquacq.py b/pycona/active_algorithms/gquacq.py index 2f3837f..99e1361 100644 --- a/pycona/active_algorithms/gquacq.py +++ b/pycona/active_algorithms/gquacq.py @@ -115,19 +115,24 @@ def mineAsk(self, r): gen_flag = False B = [c for c in self.env.instance.bias if get_relation(c, self.env.instance.language) == r and frozenset(get_scope(c)).issubset(Y)] + D = [tuple([v.name for v in get_scope(c)]) for c in B] # missing edges that can be completed (exist in B) - # if already a subset of it was negative, or cannot be completed to a clique, continue to next - if not any(Y2.issubset(Y) for Y2 in self._negativeQ) and can_be_clique(G.subgraph(Y), D): - # if potentially generalizing leads to unsat, continue to next - new_CL = self.env.instance.cl.copy() - new_CL += B - if cp.Model(new_CL).solve() and self.env.ask_generalization_query(r, B): - gen_flag = True - self.env.add_to_cl(B) - else: - gq_counter += 1 - self._negativeQ.append(Y) + # If one of the following conditions is true, continue to next: + # already a subset of it was negative, cannot be completed to a clique, does not add any constraint or + # potentially generalizing leads to UNSAT + new_CL = self.env.instance.cl.copy() + new_CL += B + if any(Y2.issubset(Y) for Y2 in self._negativeQ) or not can_be_clique(G.subgraph(Y), D) or \ + len(B) > 0 or cp.Model(new_CL).solve(): + continue + + if self.env.ask_generalization_query(self.env.instance.language[r], B): + gen_flag = True + self.env.add_to_cl(B) + else: + gq_counter += 1 + self._negativeQ.append(Y) if not gen_flag: communities = nx.community.greedy_modularity_communities(G.subgraph(Y)) From 1a98b125983f9ce9e95fec235a6abef7daae0623 Mon Sep 17 00:00:00 2001 From: Dimos Tsouros Date: Wed, 27 Nov 2024 20:10:10 +0100 Subject: [PATCH 13/13] indeed, need to assert there --- pycona/ca_environment/active_ca.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pycona/ca_environment/active_ca.py b/pycona/ca_environment/active_ca.py index c5846be..637267d 100644 --- a/pycona/ca_environment/active_ca.py +++ b/pycona/ca_environment/active_ca.py @@ -194,6 +194,8 @@ def ask_generalization_query(self, c, C): :param C: A list of constraints to which the generalization is applied. :return: The oracle's answer to the generalization query (True/False). """ + assert c in set(self.instance.language), "Generalization queries first input needs to be an expression from " \ + "the language" assert isinstance(C, list), "Generalization queries second input needs to be a list of constraints" assert all(isinstance(c1, Expression) for c1 in C), "Generalization queries second input needs to be " \ "a list of constraints"