Skip to content

Commit eb36196

Browse files
committed
Merge branch 'main' into solve-while-model
2 parents 7cae08b + 77ee330 commit eb36196

11 files changed

Lines changed: 138 additions & 10 deletions

File tree

pycona/active_algorithms/genacq.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ def learn(self, instance: ProblemInstance, oracle: Oracle = UserOracle(), verbos
6868
if self.env.verbose >= 1:
6969
print(f"\nLearned {self.env.metrics.cl} constraints in "
7070
f"{self.env.metrics.total_queries} queries.")
71+
self.env.instance.bias = []
7172
return self.env.instance
7273

7374
self.env.metrics.increase_generation_time(gen_end - gen_start)
@@ -108,7 +109,7 @@ def generalize(self, r, c):
108109
for var in scope_vars:
109110
var_types = []
110111
for type_group in self._types:
111-
if var.name in type_group:
112+
if var in type_group:
112113
var_types.append(type_group)
113114
type_sequences.append(var_types)
114115

@@ -139,6 +140,7 @@ def generalize(self, r, c):
139140
all_type_sequences.sort(key=lambda seq: len(set().union(*seq)))
140141

141142
while len(all_type_sequences) > 0 and gq_counter < self._qg_max:
143+
142144
Y = all_type_sequences.pop(0)
143145

144146
# Instead of getting constraints from bias, generate them for this type sequence

pycona/active_algorithms/growacq.py

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -51,14 +51,23 @@ def learn(self, instance: ProblemInstance, oracle: Oracle = UserOracle(), verbos
5151
self.inner_algorithm.env = copy.copy(self.env)
5252

5353
Y = []
54+
init_bias = list(self.env.instance.bias)
55+
init_bias_provided = len(init_bias) > 0
5456

5557
n_vars = len(X)
5658
for x in X:
57-
# we 'grow' the inner bias by adding one extra variable at a time
59+
# we 'grow' the considered part of the problem by adding one extra variable at a time
5860
Y.append(x)
59-
# add the constraints involving x and other added variables
60-
if len(self.env.instance.bias) == 0:
61+
# Add candidate constraints for the new frontier.
62+
# - If an initial bias is provided, only reveal newly visible constraints
63+
# - Otherwise, generate constraints incrementally from the language
64+
if init_bias_provided:
65+
visible_now = set(get_con_subset(init_bias, Y))
66+
self.env.instance.bias = list(visible_now)
67+
init_bias = set(init_bias) - visible_now
68+
else:
6169
self.env.instance.construct_bias_for_vars(x, Y)
70+
6271
if verbose >= 3:
6372
print(f"Added variable {x} in GrowAcq")
6473
print("size of B in growacq: ", len(self.env.instance.bias))

pycona/benchmarks/__init__.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,6 @@
66
from .nurse_rostering import construct_nurse_rostering
77
from .zebra import construct_zebra_problem
88
from .nqueens import construct_nqueens_problem
9-
from .golomb import construct_golomb
9+
from .golomb import construct_golomb
10+
from .latin_squares import construct_latin_squares
11+
from .greater_than_sudoku import construct_gtsudoku
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
import cpmpy as cp
2+
from cpmpy.transformations.normalize import toplevel_list
3+
from ..answering_queries.constraint_oracle import ConstraintOracle
4+
from ..problem_instance import ProblemInstance, absvar
5+
6+
7+
def construct_gtsudoku(block_size_row=2, block_size_col=2, grid_size=4):
8+
"""
9+
:return: a ProblemInstance object, along with a constraint-based oracle
10+
"""
11+
12+
# Create a dictionary with the parameters
13+
parameters = {"block_size_row": block_size_row, "block_size_col": block_size_col, "grid_size": grid_size}
14+
15+
# Variables
16+
grid = cp.intvar(1, grid_size, shape=(grid_size, grid_size), name="grid")
17+
18+
model = cp.Model()
19+
20+
# Constraints on rows and columns
21+
for row in grid:
22+
model += cp.AllDifferent(row).decompose()
23+
24+
for col in grid.T: # numpy's Transpose
25+
model += cp.AllDifferent(col).decompose()
26+
27+
# Constraints on blocks
28+
for i in range(0, grid_size, block_size_row):
29+
for j in range(0, grid_size, block_size_col):
30+
model += cp.AllDifferent(grid[i:i + block_size_row, j:j + block_size_col]).decompose() # python's indexing
31+
32+
true_horizontal_gt = [
33+
(0, 0, 0, 1),
34+
(1, 1, 1, 2),
35+
(2, 2, 2, 3),
36+
(3, 3, 3, 4),
37+
(4, 4, 4, 5),
38+
]
39+
40+
for r1, c1, r2, c2 in true_horizontal_gt:
41+
if r2 < grid_size and c2 < grid_size:
42+
model += (grid[r1, c1] > grid[r2, c2])
43+
44+
true_vertical_gt = [
45+
(0, 2, 1, 2),
46+
(1, 3, 2, 3),
47+
(2, 4, 3, 4),
48+
(3, 5, 4, 5),
49+
(4, 6, 5, 6),
50+
]
51+
52+
for r1, c1, r2, c2 in true_vertical_gt:
53+
if r1 < grid_size and r2 < grid_size and c1 < grid_size and c2 < grid_size:
54+
model += (grid[r1, c1] > grid[r2, c2])
55+
56+
57+
C_T = list(set(toplevel_list(model.constraints)))
58+
59+
# Create the language:
60+
AV = absvar(2) # create abstract vars - as many as maximum arity
61+
62+
# create abstract relations using the abstract vars
63+
lang = [AV[0] == AV[1], AV[0] != AV[1], AV[0] < AV[1], AV[0] > AV[1], AV[0] >= AV[1], AV[0] <= AV[1]]
64+
65+
instance = ProblemInstance(variables=grid, params=parameters, language=lang, name=f"sudoku_{block_size_row}_{block_size_col}_{grid_size}")
66+
67+
oracle = ConstraintOracle(C_T)
68+
69+
return instance, oracle

pycona/benchmarks/latin_squares.py

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import cpmpy as cp
2+
from cpmpy.transformations.normalize import toplevel_list
3+
from ..answering_queries.constraint_oracle import ConstraintOracle
4+
from ..problem_instance import ProblemInstance, absvar
5+
6+
7+
def construct_latin_squares(grid_size=4):
8+
"""
9+
:return: a ProblemInstance object, along with a constraint-based oracle
10+
"""
11+
12+
# Create a dictionary with the parameters
13+
parameters = {"grid_size": grid_size}
14+
15+
# Variables
16+
grid = cp.intvar(1, grid_size, shape=(grid_size, grid_size), name="grid")
17+
18+
model = cp.Model()
19+
20+
# Constraints on rows and columns
21+
for row in grid:
22+
model += cp.AllDifferent(row).decompose()
23+
24+
for col in grid.T: # numpy's Transpose
25+
model += cp.AllDifferent(col).decompose()
26+
27+
C_T = list(set(toplevel_list(model.constraints)))
28+
29+
# Create the language:
30+
AV = absvar(2) # create abstract vars - as many as maximum arity
31+
32+
# create abstract relations using the abstract vars
33+
lang = [AV[0] == AV[1], AV[0] != AV[1], AV[0] < AV[1], AV[0] > AV[1], AV[0] >= AV[1], AV[0] <= AV[1]]
34+
35+
instance = ProblemInstance(variables=grid, params=parameters, language=lang, name=f"latin_squares_{grid_size}")
36+
37+
oracle = ConstraintOracle(C_T)
38+
39+
return instance, oracle

pycona/benchmarks/sudoku.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ def construct_sudoku(block_size_row=2, block_size_col=2, grid_size=4):
3737
# create abstract relations using the abstract vars
3838
lang = [AV[0] == AV[1], AV[0] != AV[1], AV[0] < AV[1], AV[0] > AV[1], AV[0] >= AV[1], AV[0] <= AV[1]]
3939

40-
instance = ProblemInstance(variables=grid, params=parameters, language=lang, name="sudoku")
40+
instance = ProblemInstance(variables=grid, params=parameters, language=lang, name=f"sudoku_{block_size_row}_{block_size_col}_{grid_size}")
4141

4242
oracle = ConstraintOracle(C_T)
4343

pycona/ca_environment/active_ca.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,10 @@ def init_state(self, instance, oracle, verbose, metrics=None):
5050
self.find_scope.ca = self
5151
self.findc.ca = self
5252

53+
# Reset optional query-generator internal state between independent runs
54+
if hasattr(self.qgen, "reset_partial") and callable(self.qgen.reset_partial):
55+
self.qgen.reset_partial()
56+
5357
def run_query_generation(self, Y=None):
5458
""" Run the query generation process. """
5559
Y = self.qgen.generate(Y)

pycona/find_scope/findscope.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ def run(self, Y):
2626
:return: The scope of the partial example.
2727
"""
2828
assert self.ca is not None
29-
scope = self._find_scope(set(), Y, do_ask=False)
29+
scope = self._find_scope(set(), list(Y), do_ask=False)
3030
return scope
3131

3232
def _find_scope(self, R, Y, do_ask):

pycona/find_scope/findscope2.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ def _find_scope(self, R, Y):
7878

7979
# Create Y1, Y2 -------------------------
8080
proba = self.ca.bias_proba if hasattr(self.ca, 'bias_proba') else []
81-
Y1, Y2 = self.split_func(Y=Y, R=R, kappaB=kappaBRY, P_c=proba)
81+
Y1, Y2 = self.split_func(Y=Y, R=R, kappaB=kappaBRY, P_c=proba, time_limit=self.time_limit)
8282

8383
S1 = set()
8484
S2 = set()

pycona/find_scope/findscope_obj.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,13 +31,16 @@ def split_proba(Y, R, kappaB, P_c, **kwargs):
3131
if len(kappaB) > 10000:
3232
return split_half(Y)
3333

34+
time_limit = kwargs.get('time_limit', 1)
35+
3436
hashY = [hash(y) for y in Y]
3537
hashR = [hash(r) for r in R]
3638

3739
x = cp.boolvar(shape=(len(Y),))
3840

3941
model = cp.Model()
4042

43+
4144
Y1_size = sum(x)
4245

4346
model += Y1_size <= (len(Y) + 1) // 2
@@ -65,7 +68,7 @@ def split_proba(Y, R, kappaB, P_c, **kwargs):
6568

6669
s.maximize(objective)
6770

68-
flag = s.solve(time_limit=1)
71+
flag = s.solve(time_limit=time_limit)
6972

7073
if not flag:
7174
restore_scope_values(x, values)

0 commit comments

Comments
 (0)