co2114 2026.1.1__py3-none-any.whl → 2026.1.3__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- co2114/agent/environment.py +1 -1
- co2114/constraints/csp/__init__.py +291 -124
- co2114/constraints/csp/util.py +67 -28
- co2114/constraints/sudoku.py +81 -3
- co2114/optimisation/__init__.py +1 -1
- co2114/optimisation/adversarial.py +387 -0
- co2114/optimisation/planning.py +125 -41
- co2114/optimisation/things.py +9 -0
- {co2114-2026.1.1.dist-info → co2114-2026.1.3.dist-info}/METADATA +1 -1
- {co2114-2026.1.1.dist-info → co2114-2026.1.3.dist-info}/RECORD +13 -13
- {co2114-2026.1.1.dist-info → co2114-2026.1.3.dist-info}/WHEEL +1 -1
- co2114/optimisation/minimax.py +0 -185
- {co2114-2026.1.1.dist-info → co2114-2026.1.3.dist-info}/licenses/LICENSE +0 -0
- {co2114-2026.1.1.dist-info → co2114-2026.1.3.dist-info}/top_level.txt +0 -0
co2114/agent/environment.py
CHANGED
|
@@ -66,7 +66,7 @@ class BaseEnvironment:
|
|
|
66
66
|
|
|
67
67
|
for i in range(steps):
|
|
68
68
|
self.__counter += 1
|
|
69
|
-
if self.is_done: # print
|
|
69
|
+
if self.is_done: # print termination message and exit
|
|
70
70
|
print(f"{self}: Simulation complete after {i} of {steps} iterations.")
|
|
71
71
|
return
|
|
72
72
|
self.step() # else iterate one step
|
|
@@ -1,194 +1,230 @@
|
|
|
1
1
|
import numpy as np
|
|
2
2
|
import random
|
|
3
3
|
from ...agent.things import Thing, Agent
|
|
4
|
+
from ...agent.environment import Environment
|
|
4
5
|
from collections.abc import Callable, Iterable
|
|
5
6
|
from copy import deepcopy
|
|
6
7
|
|
|
7
|
-
from
|
|
8
|
+
from typing import Literal, override, TypeVar, Collection, Generic, Iterator
|
|
9
|
+
|
|
10
|
+
from .util import aslist
|
|
8
11
|
|
|
9
12
|
__all__ = [
|
|
10
13
|
'util',
|
|
11
14
|
'CSPAgent',
|
|
12
15
|
'ConstraintSatisfactionProblem',
|
|
16
|
+
'CSPRunnerEnvironment',
|
|
13
17
|
'Variable',
|
|
14
18
|
'Factor']
|
|
15
19
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
def program(self, percept):
|
|
21
|
-
pass
|
|
22
|
-
|
|
23
|
-
def solve(self):
|
|
24
|
-
raise NotImplementedError
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
class ConstraintSatisfactionProblem:
|
|
28
|
-
def __init__(self, variables, constraints):
|
|
29
|
-
self.__variables = variables
|
|
30
|
-
self.__constraints = constraints
|
|
31
|
-
|
|
32
|
-
@property
|
|
33
|
-
def variables(self):
|
|
34
|
-
""" List of variables in the CSP.
|
|
35
|
-
|
|
36
|
-
:return: List of variables.
|
|
37
|
-
"""
|
|
38
|
-
return self.__variables
|
|
39
|
-
|
|
40
|
-
@property
|
|
41
|
-
def domains(self):
|
|
42
|
-
return {variable: variable.domain for variable in self.variables}
|
|
20
|
+
# === CLASSES === #
|
|
21
|
+
Type = TypeVar("Type")
|
|
22
|
+
Domain = Collection[Type]
|
|
43
23
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
return self.__constraints
|
|
47
|
-
|
|
48
|
-
@property
|
|
49
|
-
def is_consistent(self):
|
|
50
|
-
return all(constraint.is_satisfied for constraint in self.constraints)
|
|
24
|
+
class __Variable(Thing, Generic[Type]):
|
|
25
|
+
""" Abstract Base Class for Variables in a CSP.
|
|
51
26
|
|
|
27
|
+
Defines operations on variables to allow arithmetic and comparison
|
|
28
|
+
"""
|
|
52
29
|
@property
|
|
53
|
-
def
|
|
54
|
-
|
|
55
|
-
and self.is_consistent
|
|
56
|
-
|
|
57
|
-
@property
|
|
58
|
-
def arcs(self):
|
|
59
|
-
return [arc for constraint in self.constraints if constraint.is_binary
|
|
60
|
-
for arc in constraint.arcs]
|
|
61
|
-
|
|
62
|
-
def __repr__(self):
|
|
63
|
-
return str({v.name: v for v in self.variables})
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
class __Variable(Thing):
|
|
67
|
-
@property
|
|
68
|
-
def value(self):
|
|
30
|
+
def value(self) -> Type:
|
|
31
|
+
""" The value assigned to the variable (read only) """
|
|
69
32
|
return self.__value
|
|
70
33
|
|
|
71
34
|
@property
|
|
72
|
-
def is_assigned(self):
|
|
35
|
+
def is_assigned(self) -> bool:
|
|
36
|
+
""" Whether the variable has been assigned a value. """
|
|
73
37
|
return hasattr(self, '__value') and self.__value is not None
|
|
74
38
|
|
|
75
|
-
|
|
39
|
+
@override
|
|
40
|
+
def __eq__(self, x:Type) -> bool:
|
|
41
|
+
""" Equality comparison operator override. """
|
|
76
42
|
return (x == self.value)
|
|
77
43
|
|
|
78
|
-
|
|
44
|
+
@override
|
|
45
|
+
def __ne__(self, x:Type) -> bool:
|
|
46
|
+
""" Inequality comparison operator override. """
|
|
79
47
|
return (x != self.value)
|
|
80
48
|
|
|
81
|
-
|
|
49
|
+
@override
|
|
50
|
+
def __lt__(self, x:Type) -> bool:
|
|
51
|
+
""" Less-than comparison operator override.
|
|
52
|
+
|
|
53
|
+
Defaults to True if variable is unassigned.
|
|
54
|
+
"""
|
|
82
55
|
return self.value < x if self.is_assigned else True
|
|
83
56
|
|
|
84
|
-
|
|
57
|
+
@override
|
|
58
|
+
def __gt__(self, x:Type) -> bool:
|
|
59
|
+
""" Greater-than comparison operator override.
|
|
60
|
+
|
|
61
|
+
Defaults to True if variable is unassigned.
|
|
62
|
+
"""
|
|
85
63
|
return self.value > x if self.is_assigned else True
|
|
86
64
|
|
|
87
|
-
|
|
65
|
+
@override
|
|
66
|
+
def __le__(self, x:Type) -> bool:
|
|
67
|
+
""" Less-than-or-equal comparison operator override. """
|
|
88
68
|
return self.__eq__(x) or self.__lt__(x)
|
|
89
69
|
|
|
90
|
-
|
|
70
|
+
@override
|
|
71
|
+
def __ge__(self, x:Type) -> bool:
|
|
72
|
+
""" Greater-than-or-equal comparison operator override. """
|
|
91
73
|
return self.__eq__(x) or self.__gt__(x)
|
|
92
74
|
|
|
93
|
-
|
|
75
|
+
@override
|
|
76
|
+
def __add__(self, x:Type) -> Type | "__Variable":
|
|
77
|
+
""" Addition operator override. """
|
|
94
78
|
return self.value + x if self.is_assigned else self
|
|
95
79
|
|
|
96
|
-
|
|
80
|
+
@override
|
|
81
|
+
def __sub__(self, x:Type) -> Type | "__Variable":
|
|
82
|
+
""" Subtraction operator override. """
|
|
97
83
|
return self.value - x if self.is_assigned else self
|
|
98
84
|
|
|
99
|
-
|
|
85
|
+
@override
|
|
86
|
+
def __mul__(self, x:Type) -> Type | "__Variable":
|
|
87
|
+
""" Multiplication operator override. """
|
|
100
88
|
return self.value * x if self.is_assigned else self
|
|
101
89
|
|
|
102
|
-
|
|
90
|
+
@override
|
|
91
|
+
def __truediv__(self, x:Type) -> Type | "__Variable":
|
|
92
|
+
""" True division operator override. """
|
|
103
93
|
return self.value / x if self.is_assigned else self
|
|
104
94
|
|
|
105
|
-
|
|
95
|
+
@override
|
|
96
|
+
def __rtruediv__(self, x:Type) -> Type | "__Variable":
|
|
97
|
+
""" Right-hand true division operator override. """
|
|
106
98
|
return x / self.value if self.is_assigned else self
|
|
107
99
|
|
|
108
|
-
|
|
100
|
+
@override
|
|
101
|
+
def __mod__(self, x:Type) -> Type | "__Variable":
|
|
102
|
+
""" Modulo operator override. """
|
|
109
103
|
return self.value % x if self.is_assigned else self
|
|
110
104
|
|
|
111
|
-
|
|
105
|
+
@override
|
|
106
|
+
def __or__(self, x:Type) -> Type | "__Variable":
|
|
107
|
+
""" Or operator override. """
|
|
112
108
|
return self.value | x if self.is_assigned else self
|
|
113
109
|
|
|
114
|
-
|
|
110
|
+
@override
|
|
111
|
+
def __and__(self, x:Type) -> Type | "__Variable":
|
|
112
|
+
""" And operator override. """
|
|
115
113
|
return self.value & x if self.is_assigned else self
|
|
116
114
|
|
|
117
|
-
|
|
115
|
+
@override
|
|
116
|
+
def __pow__(self, x:Type) -> Type | "__Variable":
|
|
117
|
+
""" Power operator override. """
|
|
118
118
|
return self.value ** x if self.is_assigned else self
|
|
119
119
|
|
|
120
|
-
|
|
120
|
+
@override
|
|
121
|
+
def __neg__(self) -> Type | "__Variable":
|
|
122
|
+
""" Negation operator override. """
|
|
121
123
|
return -self.value if self.is_assigned else self
|
|
122
124
|
|
|
123
|
-
|
|
124
|
-
|
|
125
|
+
@override
|
|
126
|
+
def __truediv__(self, x:Type) -> Type | "__Variable":
|
|
127
|
+
""" True division operator override. """
|
|
128
|
+
return self.value / x if self.is_assigned else self
|
|
125
129
|
|
|
126
|
-
|
|
127
|
-
|
|
130
|
+
@override
|
|
131
|
+
def __floordiv__(self, x:Type) -> Type | "__Variable":
|
|
132
|
+
""" Floor division operator override. """
|
|
133
|
+
return self.value // x if self.is_assigned else self
|
|
128
134
|
|
|
129
|
-
|
|
135
|
+
@override
|
|
136
|
+
def __abs__(self) -> Type | "__Variable":
|
|
137
|
+
""" Absolute value operator override. """
|
|
130
138
|
return abs(self.value) if self.is_assigned else self
|
|
131
139
|
|
|
132
|
-
|
|
133
|
-
class
|
|
134
|
-
def __init__(self, domain, name=None):
|
|
140
|
+
class Variable(__Variable, Generic[Type]):
|
|
141
|
+
""" Variable class for Constraint Satisfaction Problems. """
|
|
142
|
+
def __init__(self, domain:Domain[Type], name:str | None = None):
|
|
143
|
+
""" Initialise a variable with a domain and optional name.
|
|
144
|
+
|
|
145
|
+
:param domain: Collection of possible values for the variable.
|
|
146
|
+
:param name: Optional name for the variable.
|
|
147
|
+
"""
|
|
135
148
|
self.__domain = deepcopy(domain) # domain belongs only to one variable
|
|
136
|
-
self.__value = None
|
|
149
|
+
self.__value:Type | None = None
|
|
137
150
|
self.name = name
|
|
138
|
-
self.__hash = random.random()
|
|
151
|
+
self.__hash = random.random() # unique hash for variable identity
|
|
139
152
|
|
|
140
|
-
|
|
153
|
+
@override
|
|
154
|
+
def __hash__(self ) -> int:
|
|
155
|
+
""" Returns a unique hash for the variable. """
|
|
141
156
|
return hash(self.__hash)
|
|
142
157
|
|
|
143
158
|
@property
|
|
144
|
-
def is_assigned(self):
|
|
159
|
+
def is_assigned(self) -> bool:
|
|
160
|
+
""" Determines whether variable is assigned a value. """
|
|
145
161
|
return self.__value is not None
|
|
146
162
|
|
|
147
163
|
@property
|
|
148
|
-
def value(self):
|
|
164
|
+
def value(self) -> Type | None:
|
|
165
|
+
""" Returns the value assigned to the variable """
|
|
149
166
|
return self.__value
|
|
150
167
|
|
|
151
168
|
@value.setter
|
|
152
|
-
def value(self, x):
|
|
169
|
+
def value(self, x: Type | None) -> None:
|
|
170
|
+
""" Set value of variable to x if in domain, else raise ValueError.
|
|
171
|
+
|
|
172
|
+
:param x: Value to assign to variable.
|
|
173
|
+
:raises ValueError: If x is not in variable domain.
|
|
174
|
+
"""
|
|
153
175
|
if x in self.domain or x is None:
|
|
154
176
|
self.__value = x
|
|
155
177
|
else:
|
|
156
178
|
raise ValueError(f"{self.name}: {x} not in domain {self.domain}")
|
|
157
179
|
|
|
158
180
|
@property
|
|
159
|
-
def domain(self):
|
|
181
|
+
def domain(self) -> Domain[Type]:
|
|
182
|
+
""" Returns the domain of the variable. (read only)"""
|
|
160
183
|
return self.__domain
|
|
161
184
|
|
|
162
|
-
def __repr__(self):
|
|
185
|
+
def __repr__(self) -> str:
|
|
186
|
+
""" String representation of the variable. Returns name and either value if assigned or ?. """
|
|
163
187
|
return f"{self.name}({str(self.value) if self.is_assigned else '?'})"
|
|
164
188
|
|
|
165
189
|
|
|
166
190
|
class Factor(Thing):
|
|
167
|
-
|
|
191
|
+
""" Wrapper class for constraints in a CSP. """
|
|
192
|
+
def __init__(self,
|
|
193
|
+
constraint:Callable[..., bool],
|
|
194
|
+
variables:Iterable[Variable] | Variable):
|
|
195
|
+
""" Initialise a Factor with a constraint function and variables.
|
|
196
|
+
|
|
197
|
+
:param constraint: A callable function representing the constraint.
|
|
198
|
+
:param variables: An Variable (or iterable collection of Variable instances) involved in the constraint.
|
|
199
|
+
"""
|
|
168
200
|
assert isinstance(constraint, Callable), "constraint must be callable"
|
|
169
201
|
self.__function = constraint
|
|
170
202
|
if not isinstance(variables, Iterable):
|
|
171
203
|
variables = [variables]
|
|
172
|
-
self.__variables =
|
|
204
|
+
self.__variables:list[Variable] = aslist(variables)
|
|
173
205
|
if self.__xnary < 1:
|
|
174
206
|
raise ValueError(f"{self}: number of variables must be >1")
|
|
175
207
|
|
|
176
|
-
def __call__(self, *args, **kwargs):
|
|
208
|
+
def __call__(self, *args, **kwargs) -> bool:
|
|
209
|
+
""" Implements ability to call that will evaluate the constraint function. """
|
|
177
210
|
return self.__function(*args, **kwargs)
|
|
178
211
|
|
|
179
|
-
def __iter__(self):
|
|
212
|
+
def __iter__(self) -> Iterator[Variable]:
|
|
213
|
+
""" Implements iteration over variables in the factor. """
|
|
180
214
|
return iter(self.__variables)
|
|
181
215
|
|
|
182
|
-
def __contains__(self, variable):
|
|
183
|
-
"""
|
|
184
|
-
|
|
185
|
-
|
|
216
|
+
def __contains__(self, variable: Variable) -> bool:
|
|
217
|
+
""" Overrides in keyword behaviour because variable equality
|
|
218
|
+
is checked by value rather than hash
|
|
219
|
+
|
|
220
|
+
:param variable: Variable to check for membership in the factor.
|
|
221
|
+
:return: True if variable is in the factor, else False.
|
|
186
222
|
"""
|
|
187
223
|
return any(variable is _variable for _variable in self)
|
|
188
224
|
|
|
189
|
-
|
|
190
225
|
@property
|
|
191
|
-
def is_satisfied(self):
|
|
226
|
+
def is_satisfied(self) -> bool:
|
|
227
|
+
""" Determines whether the constraint is satisfied. """
|
|
192
228
|
if all(v.is_assigned for v in self.__variables):
|
|
193
229
|
return self(*self.__variables)
|
|
194
230
|
elif self.is_global:
|
|
@@ -196,67 +232,198 @@ class Factor(Thing):
|
|
|
196
232
|
else:
|
|
197
233
|
return True
|
|
198
234
|
|
|
199
|
-
|
|
235
|
+
@override
|
|
236
|
+
def __repr__(self) -> str:
|
|
237
|
+
""" String representation of the factor. """
|
|
200
238
|
return str(tuple([str(v.name) for v in self.variables]))
|
|
201
239
|
|
|
202
240
|
@property
|
|
203
|
-
def __xnary(self):
|
|
241
|
+
def __xnary(self) -> int:
|
|
242
|
+
""" Determines xnary of the factor. """
|
|
204
243
|
return len(self.variables)
|
|
205
244
|
|
|
206
245
|
@property
|
|
207
|
-
def is_unary(self):
|
|
246
|
+
def is_unary(self) -> bool:
|
|
247
|
+
""" Is the factor unary? """
|
|
208
248
|
return self.__xnary == 1
|
|
209
249
|
|
|
210
250
|
@property
|
|
211
|
-
def is_binary(self):
|
|
251
|
+
def is_binary(self) -> bool:
|
|
252
|
+
""" Is the factor binary? """
|
|
212
253
|
return self.__xnary == 2
|
|
213
254
|
|
|
214
255
|
@property
|
|
215
|
-
def is_global(self):
|
|
256
|
+
def is_global(self) -> bool:
|
|
257
|
+
""" Is the factor global? """
|
|
216
258
|
return self.__xnary > 2
|
|
217
259
|
|
|
218
260
|
@property
|
|
219
|
-
def arcs(self):
|
|
261
|
+
def arcs(self) -> tuple["Arc", "Arc"]:
|
|
262
|
+
""" Returns the arcs for binary factors.
|
|
263
|
+
|
|
264
|
+
:return: Tuple of arcs (Factor, Variable, Variable), e.g. (self, A, B) and (self, B, A)
|
|
265
|
+
:raises TypeError: If factor is not binary.
|
|
266
|
+
"""
|
|
220
267
|
if not self.is_binary:
|
|
221
268
|
raise TypeError(f"{self}: constraint is not binary")
|
|
222
269
|
L,R = self.variables
|
|
223
|
-
return
|
|
270
|
+
return ((self, L, R), (self, R, L))
|
|
224
271
|
|
|
225
272
|
@property
|
|
226
|
-
def variables(self):
|
|
273
|
+
def variables(self) -> Collection[Variable]:
|
|
274
|
+
""" Returns the variables involved in the factor. (read only) """
|
|
227
275
|
return self.__variables
|
|
228
276
|
|
|
277
|
+
Arc = tuple[Factor, Variable, Variable] # type alias for arcs in binary factors
|
|
229
278
|
|
|
230
279
|
class Constraint(Factor):
|
|
280
|
+
""" Class alias for Factor to represent constraints in a CSP. """
|
|
231
281
|
pass
|
|
232
282
|
|
|
283
|
+
class ConstraintSatisfactionProblem:
|
|
284
|
+
""" Constraint Satisfaction Problem base class.
|
|
285
|
+
|
|
286
|
+
Defined by variables (and their domains) and constraints.
|
|
287
|
+
|
|
288
|
+
Has attributes
|
|
289
|
+
- variables: Collection of variables in the CSP.
|
|
290
|
+
- domains: Dictionary of variable domains.
|
|
291
|
+
- constraints: Collection of constraints (Factors) in the CSP.
|
|
292
|
+
|
|
293
|
+
Has utility checks including
|
|
294
|
+
- is_consistent: Whether all constraints are satisfied.
|
|
295
|
+
- is_complete: Whether all variables are assigned and all constraints are satisfied.
|
|
296
|
+
- arcs: List of arcs in binary constraints.
|
|
297
|
+
"""
|
|
298
|
+
def __init__(self,
|
|
299
|
+
variables:Collection[Variable],
|
|
300
|
+
constraints:Collection[Factor]) -> None:
|
|
301
|
+
"""
|
|
302
|
+
Constructor for CSP
|
|
303
|
+
|
|
304
|
+
:param variables: Collection of variables in the CSP.
|
|
305
|
+
:param constraints: Collection of constraints (Factors) in the CSP.
|
|
306
|
+
"""
|
|
307
|
+
self.__variables = variables
|
|
308
|
+
self.__constraints = constraints
|
|
233
309
|
|
|
234
|
-
|
|
235
|
-
|
|
310
|
+
@property
|
|
311
|
+
def variables(self) -> Collection[Variable]:
|
|
312
|
+
""" Read only list of variables in the CSP.
|
|
313
|
+
|
|
314
|
+
Read only.
|
|
315
|
+
|
|
316
|
+
:return: List of variables.
|
|
317
|
+
"""
|
|
318
|
+
return self.__variables
|
|
319
|
+
|
|
320
|
+
@property
|
|
321
|
+
def domains(self) -> dict[Variable, Domain]:
|
|
322
|
+
""" Read only dictionary of variable domains.
|
|
323
|
+
|
|
324
|
+
:return: Dictionary mapping variables to their domains.
|
|
325
|
+
"""
|
|
326
|
+
return {variable: variable.domain for variable in self.variables}
|
|
236
327
|
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
328
|
+
@property
|
|
329
|
+
def constraints(self) -> Collection[Factor]:
|
|
330
|
+
""" Read only list of constraints in the CSP."""
|
|
331
|
+
return self.__constraints
|
|
332
|
+
|
|
333
|
+
@property
|
|
334
|
+
def is_consistent(self) -> bool:
|
|
335
|
+
""" Determines whether all constraints are satisfied for current variable assignments. """
|
|
336
|
+
return all(constraint.is_satisfied for constraint in self.constraints)
|
|
337
|
+
|
|
338
|
+
@property
|
|
339
|
+
def is_complete(self) -> bool:
|
|
340
|
+
""" Determines whether all variables are assigned and all constraints are satisfied. """
|
|
341
|
+
return all(variable.is_assigned for variable in self.variables) \
|
|
342
|
+
and self.is_consistent
|
|
343
|
+
|
|
344
|
+
@property
|
|
345
|
+
def arcs(self) -> list[Arc]:
|
|
346
|
+
""" Returns list of arcs in binary constraints. """
|
|
347
|
+
return [arc for constraint in self.constraints if constraint.is_binary
|
|
348
|
+
for arc in constraint.arcs]
|
|
349
|
+
|
|
350
|
+
def __repr__(self) -> str:
|
|
351
|
+
""" String representation of the CSP. """
|
|
352
|
+
return str({v.name: v for v in self.variables})
|
|
353
|
+
|
|
241
354
|
|
|
242
|
-
#
|
|
243
|
-
# def csp(self):
|
|
244
|
-
# return self.__csp
|
|
355
|
+
CSP = ConstraintSatisfactionProblem # type alias
|
|
245
356
|
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
357
|
+
class CSPAgent(Agent):
|
|
358
|
+
""" ConstraintSatisfaction Problem Agent base class. """
|
|
359
|
+
def __repr__(self) -> str:
|
|
360
|
+
return "🤖"
|
|
361
|
+
|
|
362
|
+
@override
|
|
363
|
+
def program(self, percept:CSP) -> tuple[Literal["solve"], CSP]:
|
|
364
|
+
""" Constraint Satisfaction Problem agent program. """
|
|
365
|
+
return ("solve", percept)
|
|
249
366
|
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
367
|
+
def solve(self, csp: CSP) -> CSP:
|
|
368
|
+
""" Actuator for solving CSP problems """
|
|
369
|
+
raise NotImplementedError("Need to implement solve method in subclass")
|
|
253
370
|
|
|
254
|
-
# @property
|
|
255
|
-
# def domains(self):
|
|
256
|
-
# return self.csp.domains
|
|
257
371
|
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
372
|
+
## Environment ##
|
|
373
|
+
class CSPRunnerEnvironment(Environment):
|
|
374
|
+
""" Environment for running CSP agents. """
|
|
375
|
+
def __init__(self, csp:CSP, solution=None, *args, **kwargs):
|
|
376
|
+
super().__init__(*args, **kwargs)
|
|
377
|
+
self.csp = csp
|
|
378
|
+
self.solution = solution
|
|
379
|
+
self.__done = False
|
|
262
380
|
|
|
381
|
+
@property
|
|
382
|
+
def is_done(self) -> bool:
|
|
383
|
+
""" Check if CSP is solved. """
|
|
384
|
+
return self.__done
|
|
385
|
+
|
|
386
|
+
@override
|
|
387
|
+
def percept(self, agent:CSPAgent) -> CSP:
|
|
388
|
+
""" Provides agent with the CSP as percept. """
|
|
389
|
+
return self.csp
|
|
390
|
+
|
|
391
|
+
@override
|
|
392
|
+
def execute_action(self,
|
|
393
|
+
agent:CSPAgent,
|
|
394
|
+
action:tuple[Literal["solve"], CSP]):
|
|
395
|
+
""" Execute action for CSP agent.
|
|
396
|
+
|
|
397
|
+
:param agent: The CSP agent executing the action.
|
|
398
|
+
:param action: The action to execute, expected to be a tuple ("solve", csp).
|
|
399
|
+
"""
|
|
400
|
+
command, csp = action
|
|
401
|
+
if command == "solve":
|
|
402
|
+
solution = agent.solve(csp)
|
|
403
|
+
else:
|
|
404
|
+
raise ValueError(f"{agent}: unknown command {command}")
|
|
405
|
+
|
|
406
|
+
if solution:
|
|
407
|
+
print(f"final solution:\n{solution}")
|
|
408
|
+
if self.solution is not None:
|
|
409
|
+
print(f"correct solution:\n{self.solution}")
|
|
410
|
+
else:
|
|
411
|
+
print("no solution found")
|
|
412
|
+
self.__done = True
|
|
413
|
+
|
|
414
|
+
|
|
415
|
+
@override
|
|
416
|
+
def run(self, steps:int=1, pause_for_user:bool=False) -> None:
|
|
417
|
+
""" Run the CSP environment for a number of steps."""
|
|
418
|
+
if pause_for_user:
|
|
419
|
+
input("Press enter to start simulation")
|
|
420
|
+
|
|
421
|
+
print(f"{self}: Running for {steps} iterations.")
|
|
422
|
+
for i in range(steps):
|
|
423
|
+
if self.is_done:
|
|
424
|
+
if steps:
|
|
425
|
+
print(f"{self}: Simulation complete after {i} of {steps} iterations.")
|
|
426
|
+
return
|
|
427
|
+
self.step()
|
|
428
|
+
if steps:
|
|
429
|
+
print(f"{self}: Simulation complete after {steps} of {steps} iterations.")
|