-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathbranching_scheme.py
168 lines (141 loc) · 6.02 KB
/
branching_scheme.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
"""This code is a translation of the original code in java.
Original code: https://github.com/minicp/minicp
Online course: https://www.edx.org/learn/computer-programming/universite-catholique-de-louvain-constraint-programming
/*
* mini-cp is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License v3
* as published by the Free Software Foundation.
*
* mini-cp is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY.
* See the GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with mini-cp. If not, see http://www.gnu.org/licenses/lgpl-3.0.en.html
*
* Copyright (c) 2018. by Laurent Michel, Pierre Schaus, Pascal Van Hentenryck
*/
Factory for search procedures.
A typical custom search on an array of variable {q} is illustrated next
def callback() -> "list[Callable[[], None]]":
idx = -1; // index of the first variable that is not fixed
for k in range(len(q)):
if(q[k].size() > 1):
idx=k
break
if(idx == -1):
return []
else:
qi = q[idx];
v = qi.min();
left = lambda: factory.equal(qi, v);
right = lambda: factory.notEqual(qi, v);
return [left, right];
search = factory.make_dfs(cp, callback)
See factory make_dfs(Solver, Callable)
"""
from typing import Callable, TypeVar
from mini_cp.cp import factory
from mini_cp.engine import int_var, search
_T = TypeVar("_T")
_N = TypeVar("_N")
# Constant that should be returned to notify the
# solver that there are no branches
# to create any more and that the current state should
# be considered as a solution
# See factory make_dfs(Solver, Callable)
_EMPTY = []
def select_min(
x: "list[_T]", p: "Callable[[_T], bool]", f: "Callable[[_T], _N]"
) -> "_T":
"""Minimum selector.
Example of usage.
xs = select_min(x, lambda xi: xi.size() > 1, lambda xi: xi.size())
'x': the array on which the minimum value is searched
'p': the callback that filters the element eligible for selection
'f': the evaluation function that returns a value to be compared when
applied on an element on x
'_T': the type of the elements in x, for instance {IntVar}
'_N': the type on which the minimum is computed, for instance {Integer}
Returns the minimum element in x that satisfies the callback p or None
if no element satisfies the callback.
"""
sel = None
for xi in x:
if p(xi):
if (sel is None) or (f(xi) - f(sel) < 0):
sel = xi
return sel
def first_fail(x: "list[int_var.IntVar]") -> "Callable[[], list[Callable[[], None]]]":
"""First-Fail strategy.
It selects the first variable with a domain larger than one.
Then it creates two branches. The left branch
assigning the variable to its minimum value.
The right branch removing this minimum value from the domain.
'x': the list of variables on which the first fail strategy is applied.
Returns a first-fail branching strategy.
See factory make_dfs(Solver, Callable)
"""
def call_back():
xs = select_min(x, lambda xi: xi.size() > 1, lambda xi: xi.size())
if xs is None:
return _EMPTY
else:
v = xs.min()
left = lambda: xs.get_solver().post(factory.equal(xs, v))
right = lambda: xs.get_solver().post(factory.not_equal(xs, v=v))
return [left, right]
return call_back
def And(
choices: "list[Callable[[], list[Callable[[], None]]]]",
) -> "Callable[[], list[Callable[[], None]]]":
"""Sequential Search combinator that linearly
considers a list of branching generator.
One branching of this list is executed
when all the previous ones are exhausted (they return an empty array).
'choices': the branching schemes considered sequentially in the sequential
by path in the search tree
Returns a branching scheme implementing the sequential search.
See Sequencer
"""
return search.Sequencer(choices)
def limited_discrepancy(
branching: "Callable[[], list[Callable[[], None]]]", max_discrepancy: "int"
) -> "Callable[[], list[Callable[[], None]]]":
"""Limited Discrepancy Search combinator
that limits the number of right decisions
'branching': a branching scheme
'max_discrepancy': a discrepancy limit (non-negative number)
Returns a branch scheme that cuts off any path accumulating a
discrepancy beyond the limit max_discrepancy.
See LimitedDiscrepancyBranching
"""
return search.LimitedDiscrepancyBranching(branching, max_discrepancy)
def last_conflict(
variable_selector: "Callable[[], int_var.IntVar]",
value_selector: "Callable[[int_var.IntVar], int]",
) -> "Callable[[], list[Callable[[], None]]]":
"""Last conflict heuristic
Attempts to branch first on the last variable that caused an Inconsistency
Lecoutre, C., Saïs, L., Tabary, S., Vidal, V. (2009).
Reasoning from last conflict (s) in constraint programming.
Artificial Intelligence, 173(18), 1592-1614.
'variable_selector': returns the next variable to fix
'value_selector': given a variable, returns the value to which it
must be assigned on the left branch (and excluded on the right)
"""
raise NotImplementedError("last_conflict")
def conflict_ordering_search(
variable_selector: "Callable[[], int_var.IntVar]",
value_selector: "Callable[[int_var.IntVar], int]",
) -> "Callable[[], list[Callable[[], None]]]":
"""Conflict Ordering Search
Gay, S., Hartert, R., Lecoutre, C., Schaus, P. (2015).
Conflict ordering search for scheduling problems.
In International conference on principles and practice of constraint programming (pp. 140-148).
Springer.
'variable_selector': returns the next variable to fix
'value_selector': given a variable, returns the value to which it
must be assigned on the left branch (and excluded on the right)
"""
raise NotImplementedError("conflict_ordering_search")