Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 10 additions & 5 deletions pycona/active_algorithms/gquacq.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ class GQuAcq(AlgorithmCAInteractive):

def __init__(self, ca_env: ActiveCAEnv = None, qg_max=10):
"""
Initialize the PQuAcq algorithm with an optional constraint acquisition environment.
Initialize the GQuAcq algorithm with an optional constraint acquisition environment.

:param ca_env: An instance of ActiveCAEnv, default is None.
: param GQmax: maximum number of generalization queries
Expand All @@ -30,16 +30,21 @@ def __init__(self, ca_env: ActiveCAEnv = None, qg_max=10):
self._negativeQ = []
self._qg_max = qg_max

def learn(self, instance: ProblemInstance, oracle: Oracle = UserOracle(), verbose=0, metrics: Metrics = None):
def learn(self, instance: ProblemInstance, oracle: Oracle = UserOracle(), verbose=0, X=None, metrics: Metrics = None):
"""
Learn constraints using the QuAcq algorithm by generating queries and analyzing the results.
Learn constraints using the GQuAcq algorithm by generating queries and analyzing the results.

:param instance: the problem instance to acquire the constraints for
:param oracle: An instance of Oracle, default is to use the user as the oracle.
:param verbose: Verbosity level, default is 0.
:param metrics: statistics logger during learning
:param X: The set of variables to consider, default is None.
:return: the learned instance
"""
if X is None:
X = instance.X
assert isinstance(X, list) and set(X).issubset(set(instance.X)), "When using .learn(), set parameter X must be a list of variables"

self.env.init_state(instance, oracle, verbose, metrics)

if len(self.env.instance.bias) == 0:
Expand All @@ -52,8 +57,8 @@ def learn(self, instance: ProblemInstance, oracle: Oracle = UserOracle(), verbos
print("Number of Queries: ", self.env.metrics.membership_queries_count)

gen_start = time.time()
Y = self.env.run_query_generation()
gen_end = time.time()
Y = self.env.run_query_generation(X)
gen_end = time.time()

if len(Y) == 0:
# if no query can be generated it means we have (prematurely) converged to the target network -----
Expand Down
22 changes: 14 additions & 8 deletions pycona/active_algorithms/growacq.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,39 +26,45 @@ def __init__(self, ca_env: ActiveCAEnv = None, inner_algorithm: AlgorithmCAInter
super().__init__(env)
self.inner_algorithm = inner_algorithm if inner_algorithm is not None else MQuAcq2(ca_env)

def learn(self, instance: ProblemInstance, oracle: Oracle = UserOracle(), verbose=0, metrics: Metrics = None):
def learn(self, instance: ProblemInstance, oracle: Oracle = UserOracle(), verbose=0, X=None, metrics: Metrics = None):
"""
Learn constraints by incrementally adding variables and using the inner algorithm to learn constraints
for each added variable.

:param instance: the problem instance to acquire the constraints for
:param oracle: An instance of Oracle, default is to use the user as the oracle.
:param verbose: Verbosity level, default is 0.
:param X: The set of variables to consider, default is None.
:param metrics: statistics logger during learning
:return: the learned instance
"""
if X is None:
X = instance.X
assert isinstance(X, list) and set(X).issubset(set(instance.X)), "When using .learn(), set parameter X must be a list of variables"

self.env.init_state(instance, oracle, verbose, metrics)

if verbose >= 1:
print(f"Running growacq with {self.inner_algorithm} as inner algorithm")

self.inner_algorithm.env = copy.copy(self.env)

self.env.instance.X = []
Y = []

n_vars = len(self.env.instance.variables.flat)
for x in self.env.instance.variables.flat:
n_vars = len(X)
for x in X:
# we 'grow' the inner bias by adding one extra variable at a time
self.env.instance.X.append(x)
Y.append(x)
# add the constraints involving x and other added variables
self.env.instance.construct_bias_for_var(x)
if len(self.env.instance.bias) == 0:
self.env.instance.construct_bias_for_var(x, Y)
if verbose >= 3:
print(f"Added variable {x} in GrowAcq")
print("size of B in growacq: ", len(self.env.instance.bias))

if verbose >= 2:
print(f"\nGrowAcq: calling inner_algorithm for {len(self.env.instance.X)}/{n_vars} variables")
self.env.instance = self.inner_algorithm.learn(self.env.instance, oracle, verbose=verbose, metrics=self.env.metrics)
print(f"\nGrowAcq: calling inner_algorithm for {len(Y)}/{n_vars} variables")
self.env.instance = self.inner_algorithm.learn(self.env.instance, oracle, verbose=verbose, X=Y, metrics=self.env.metrics)

if verbose >= 3:
print("C_L: ", len(self.env.instance.cl))
Expand Down
9 changes: 7 additions & 2 deletions pycona/active_algorithms/mquacq.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,16 +21,21 @@ def __init__(self, ca_env: ActiveCAEnv = None):
"""
super().__init__(ca_env)

def learn(self, instance: ProblemInstance, oracle: Oracle = UserOracle(), verbose=0, metrics: Metrics = None):
def learn(self, instance: ProblemInstance, oracle: Oracle = UserOracle(), verbose=0, X=None, metrics: Metrics = None):
"""
Learn constraints using the modified QuAcq algorithm by generating queries and analyzing the results.

:param instance: the problem instance to acquire the constraints for
:param oracle: An instance of Oracle, default is to use the user as the oracle.
:param verbose: Verbosity level, default is 0.
:param metrics: statistics logger during learning
:param X: The set of variables to consider, default is None.
:return: the learned instance
"""
if X is None:
X = instance.X
assert isinstance(X, list) and set(X).issubset(set(instance.X)), "When using .learn(), set parameter X must be a list of variables"

self.env.init_state(instance, oracle, verbose, metrics)

if len(self.env.instance.bias) == 0:
Expand All @@ -47,7 +52,7 @@ def learn(self, instance: ProblemInstance, oracle: Oracle = UserOracle(), verbos

# generate e in D^X accepted by C_l and rejected by B
gen_start = time.time()
Y = self.env.run_query_generation()
Y = self.env.run_query_generation(X)
gen_end = time.time()

if len(Y) == 0:
Expand Down
9 changes: 7 additions & 2 deletions pycona/active_algorithms/mquacq2.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,16 +31,21 @@ def __init__(self, ca_env: ActiveCAEnv = None, *, perform_analyzeAndLearn: bool
self.cl_neighbours = None
self.hashX = None

def learn(self, instance: ProblemInstance, oracle: Oracle = UserOracle(), verbose=0, metrics: Metrics = None):
def learn(self, instance: ProblemInstance, oracle: Oracle = UserOracle(), verbose=0, X=None, metrics: Metrics = None):
"""
Learn constraints using the modified QuAcq algorithm by generating queries and analyzing the results.

:param instance: the problem instance to acquire the constraints for
:param oracle: An instance of Oracle, default is to use the user as the oracle.
:param verbose: Verbosity level, default is 0.
:param metrics: statistics logger during learning
:param X: The set of variables to consider, default is None.
:return: the learned instance
"""
if X is None:
X = instance.X
assert isinstance(X, list) and set(X).issubset(set(instance.X)), "When using .learn(), set parameter X must be a list of variables"

self.env.init_state(instance, oracle, verbose, metrics)

# Hash the variables
Expand All @@ -52,7 +57,7 @@ def learn(self, instance: ProblemInstance, oracle: Oracle = UserOracle(), verbos

while True:
gen_start = time.time()
Y = self.env.run_query_generation()
Y = self.env.run_query_generation(X)
gen_end = time.time()
self.env.metrics.increase_generation_time(gen_end - gen_start)

Expand Down
9 changes: 7 additions & 2 deletions pycona/active_algorithms/pquacq.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,16 +25,21 @@ def __init__(self, ca_env: ActiveCAEnv = None):
"""
super().__init__(ca_env)

def learn(self, instance: ProblemInstance, oracle: Oracle = UserOracle(), verbose=0, metrics: Metrics = None):
def learn(self, instance: ProblemInstance, oracle: Oracle = UserOracle(), verbose=0, X=None, metrics: Metrics = None):
"""
Learn constraints using the QuAcq algorithm by generating queries and analyzing the results.

:param instance: the problem instance to acquire the constraints for
:param oracle: An instance of Oracle, default is to use the user as the oracle.
:param verbose: Verbosity level, default is 0.
:param metrics: statistics logger during learning
:param X: The set of variables to consider, default is None.
:return: the learned instance
"""
if X is None:
X = instance.X
assert isinstance(X, list) and set(X).issubset(set(instance.X)), "When using .learn(), set parameter X must be a list of variables"

self.env.init_state(instance, oracle, verbose, metrics)

if len(self.env.instance.bias) == 0:
Expand All @@ -47,7 +52,7 @@ def learn(self, instance: ProblemInstance, oracle: Oracle = UserOracle(), verbos
print("Number of Queries: ", self.env.metrics.membership_queries_count)

gen_start = time.time()
Y = self.env.run_query_generation()
Y = self.env.run_query_generation(X)
gen_end = time.time()

if len(Y) == 0:
Expand Down
9 changes: 7 additions & 2 deletions pycona/active_algorithms/quacq.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,16 +21,21 @@ def __init__(self, ca_env: ActiveCAEnv = None):
"""
super().__init__(ca_env)

def learn(self, instance: ProblemInstance, oracle: Oracle = UserOracle(), verbose=0, metrics: Metrics = None):
def learn(self, instance: ProblemInstance, oracle: Oracle = UserOracle(), verbose=0, X=None, metrics: Metrics = None):
"""
Learn constraints using the QuAcq algorithm by generating queries and analyzing the results.

:param instance: the problem instance to acquire the constraints for
:param oracle: An instance of Oracle, default is to use the user as the oracle.
:param verbose: Verbosity level, default is 0.
:param metrics: statistics logger during learning
:param X: The set of variables to consider, default is None.
:return: the learned instance
"""
if X is None:
X = instance.X
assert isinstance(X, list) and set(X).issubset(set(instance.X)), "When using .learn(), set parameter X must be a list of variables"

self.env.init_state(instance, oracle, verbose, metrics)

if len(self.env.instance.bias) == 0:
Expand All @@ -43,7 +48,7 @@ def learn(self, instance: ProblemInstance, oracle: Oracle = UserOracle(), verbos
print("Number of Queries: ", self.env.metrics.membership_queries_count)

gen_start = time.time()
Y = self.env.run_query_generation()
Y = self.env.run_query_generation(X)
gen_end = time.time()

if len(Y) == 0:
Expand Down
4 changes: 2 additions & 2 deletions pycona/benchmarks/exam_timetabling.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

from ..answering_queries.constraint_oracle import ConstraintOracle
from ..problem_instance import ProblemInstance, absvar

from cpmpy.transformations.normalize import toplevel_list

def day_of_exam(course, slots_per_day):
return course // slots_per_day
Expand All @@ -27,7 +27,7 @@ def construct_examtt_simple(nsemesters=9, courses_per_semester=6, slots_per_day=
for row in courses:
model += cp.AllDifferent(day_of_exam(row, slots_per_day)).decompose()

C_T = list(model.constraints)
C_T = list(set(toplevel_list(model.constraints)))

if model.solve():
courses.clear()
Expand Down
4 changes: 2 additions & 2 deletions pycona/benchmarks/job_shop_scheduling.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import cpmpy as cp
import numpy as np
from cpmpy.expressions.utils import all_pairs

from cpmpy.transformations.normalize import toplevel_list
from ..answering_queries.constraint_oracle import ConstraintOracle
from ..problem_instance import ProblemInstance, absvar

Expand Down Expand Up @@ -56,7 +56,7 @@ def construct_job_shop_scheduling_problem(n_jobs, machines, horizon, seed=0):
for (j1, t1), (j2, t2) in all_pairs(zip(*tasks_on_mach)):
m += (end[j1, t1] <= start[j2, t2]) | (end[j2, t2] <= start[j1, t1])

C_T = list(model.constraints)
C_T = list(set(toplevel_list(model.constraints)))

max_duration = max(duration)

Expand Down
4 changes: 2 additions & 2 deletions pycona/benchmarks/jsudoku.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import cpmpy as cp

from cpmpy.transformations.normalize import toplevel_list
from ..answering_queries.constraint_oracle import ConstraintOracle
from ..problem_instance import ProblemInstance, absvar

Expand Down Expand Up @@ -49,6 +49,6 @@ def construct_jsudoku():

instance = ProblemInstance(variables=grid, params=parameters, language=lang, name="jsudoku")

oracle = ConstraintOracle(C_T)
oracle = ConstraintOracle(list(set(toplevel_list(C_T))))

return instance, oracle
4 changes: 2 additions & 2 deletions pycona/benchmarks/murder.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@

import cpmpy as cp

from cpmpy.transformations.normalize import toplevel_list
from ..answering_queries.constraint_oracle import ConstraintOracle
from ..problem_instance import ProblemInstance, absvar

Expand Down Expand Up @@ -45,6 +45,6 @@ def construct_murder_problem():

instance = ProblemInstance(variables=grid, language=lang, name="murder")

oracle = ConstraintOracle(C_T)
oracle = ConstraintOracle(list(set(toplevel_list(C_T))))

return instance, oracle
4 changes: 2 additions & 2 deletions pycona/benchmarks/nurse_rostering.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import cpmpy as cp

from cpmpy.transformations.normalize import toplevel_list
from ..answering_queries.constraint_oracle import ConstraintOracle
from ..problem_instance import ProblemInstance, absvar

Expand Down Expand Up @@ -30,7 +30,7 @@ def construct_nurse_rostering(shifts_per_day=3, num_days=5, num_nurses=8, nurses
if not model.solve():
raise Exception("The problem has no solution")

C_T = list(model.constraints)
C_T = list(set(toplevel_list(model.constraints)))

# Create the language:
AV = absvar(2) # create abstract vars - as many as maximum arity
Expand Down
4 changes: 2 additions & 2 deletions pycona/benchmarks/sudoku.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import cpmpy as cp

from cpmpy.transformations.normalize import toplevel_list
from ..answering_queries.constraint_oracle import ConstraintOracle
from ..problem_instance import ProblemInstance, absvar

Expand Down Expand Up @@ -29,7 +29,7 @@ def construct_sudoku(block_size_row, block_size_col, grid_size):
for j in range(0, grid_size, block_size_col):
model += cp.AllDifferent(grid[i:i + block_size_row, j:j + block_size_col]).decompose() # python's indexing

C_T = list(model.constraints)
C_T = list(set(toplevel_list(model.constraints)))

# Create the language:
AV = absvar(2) # create abstract vars - as many as maximum arity
Expand Down
4 changes: 2 additions & 2 deletions pycona/ca_environment/acive_ca_proba.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,12 +55,12 @@ def init_state(self, instance, oracle, verbose, metrics=None):
else:
self._bias_proba = {c: 0.01 for c in self.instance.bias}

def run_query_generation(self):
def run_query_generation(self, X=None):
""" Run the query generation process. """
if self.training_frequency > 0 and len(set(self.datasetY)) == 2:
self._train_classifier()
self._predict_bias_proba()
return super().run_query_generation()
return super().run_query_generation(X)

def run_find_scope(self, Y):
""" Run the find scope process. """
Expand Down
4 changes: 2 additions & 2 deletions pycona/ca_environment/active_ca.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,9 +50,9 @@ def init_state(self, instance, oracle, verbose, metrics=None):
self.find_scope.ca = self
self.findc.ca = self

def run_query_generation(self):
def run_query_generation(self, Y=None):
""" Run the query generation process. """
Y = self.qgen.generate()
Y = self.qgen.generate(Y)
return Y

def run_find_scope(self, Y):
Expand Down
8 changes: 6 additions & 2 deletions pycona/problem_instance/problem_instance.py
Original file line number Diff line number Diff line change
Expand Up @@ -241,15 +241,19 @@ def construct_bias(self):

self.bias = all_cons

def construct_bias_for_var(self, v1):
def construct_bias_for_var(self, v1, X=None):
"""
Construct the bias (candidate constraints) for a specific variable.

:param v1: The variable for which to construct the bias.
:param X: The set of variables to consider, default is None.
"""
if X is None:
X = self.X
assert isinstance(X, list) and set(X).issubset(set(self.X)), "When using .construct_bias_for_var(), set parameter X must be a list of variables. Instead, got: " + str(X)

all_cons = []
X = list(set(self.X) - {v1})
X = list(set(X) - {v1})

for relation in self.language:
abs_vars = get_scope(relation)
Expand Down
Loading
Loading