-
Notifications
You must be signed in to change notification settings - Fork 0
/
arc_consistency.py
163 lines (134 loc) · 5.45 KB
/
arc_consistency.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
# MIT License
#
# Copyright (c) 2019 Tobias Klinke
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
""" Implementation of arc consistency algorithm """
from collections import namedtuple
import copy
from typing import Any, Dict, Iterable, List, Set
from csp import CSP
# An arc in a constraint network
Arc = namedtuple("Arc", ["constraint", "variable"])
class Agenda:
""" An agenda for arc consistency algorithm """
def __init__(self) -> None:
self._arcs = set()
def add_arc(self, arc: Arc) -> None:
""" Add new arc """
self._arcs.add(arc)
def next_arc(self) -> Arc:
"""
Remove and return next arc according to selection strategy
"""
return self._arcs.pop()
@property
def is_empty(self) -> bool:
""" True, if frontier is empty (no more arcs left) """
return not self._arcs
def __len__(self) -> int:
return len(self._arcs)
debug = True
def debug_print(x: Any):
if debug:
print(x)
def arc_consistency(csp: CSP) -> Dict[str, Set[Any]]:
""" Try to solve CSP by arc consistency algorithm """
# The possible values for each variable
domains = get_initial_domains(csp)
# The constraint network
arcs = list(get_initial_arcs(csp))
# initialize agenda
agenda = Agenda()
for initial_arc in arcs:
agenda.add_arc(initial_arc)
while not agenda.is_empty:
current_arc = agenda.next_arc()
debug_print(current_arc)
debug_print("{} arcs left".format(len(agenda)))
new_domain = make_consistent(current_arc, domains)
old_domain = domains[current_arc.variable]
if new_domain != old_domain:
debug_print("new_domain for {0}: {1}"
.format(current_arc.variable, new_domain))
for invalid_arc in invalidated_arcs(current_arc, arcs):
agenda.add_arc(invalid_arc)
domains[current_arc.variable] = new_domain
return domains
def get_initial_domains(csp: CSP) -> Dict[str, Set[Any]]:
""" Get initial domains for all variables """
# deep copy because specification of CSP could have reused domains
# and we are going to modify the domains
return copy.deepcopy(csp.variables)
def get_initial_arcs(csp: CSP) -> Iterable[Arc]:
""" One arc from each constraint to all of its variables """
for constraint in csp.constraints:
for var in constraint.variables:
yield Arc(constraint=constraint,
variable=var)
def make_consistent(arc: Arc, domains: Dict[str, Set[Any]]) -> Set[Any]:
"""
Make an arc consistent.
Return new domain for `arc.variable`.
"""
other_vars = [var for var in arc.constraint.variables
if var != arc.variable]
current_domain = domains[arc.variable]
other_domains = {var: dom for (var, dom) in domains.items()
if var in other_vars}
new_domain = set()
for value in current_domain:
for other_values in domain_product(other_domains):
all_values = other_values
all_values[arc.variable] = value
if arc.constraint.check(all_values):
new_domain.add(value)
break
return new_domain
Assignment = Dict[str, Any]
def domain_product(domains: Dict[str, Set[Any]]) -> Iterable[Assignment]:
"""
Generate all possible combinations of values in domains.
Yield an assignmnent (variable: value) from cartesian product of all
domains.
"""
def get_assignment(domains: Dict[str, Set[Any]]) -> Iterable[Assignment]:
# termination
if not domains:
yield {}
else:
# Reduce by one variable and get all assingnment for remaining
# variables
remaining_domains = domains.copy()
var, domain = remaining_domains.popitem()
for remaining_assignment in get_assignment(remaining_domains):
for value in domain:
remaining_assignment[var] = value
yield remaining_assignment
return get_assignment(domains)
def invalidated_arcs(current_arc: Arc, arcs: List[Arc]) -> Iterable[Arc]:
"""
Generate all arcs that have been invalidated by making
`current_arc` consistent.
"""
current_var = current_arc.variable
return [arc for arc in arcs
if arc.constraint != current_arc.constraint
and current_var in arc.constraint.variables
and arc.variable != current_var]