co2114 2026.1.1__tar.gz → 2026.1.2__tar.gz
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-2026.1.1 → co2114-2026.1.2}/PKG-INFO +1 -1
- {co2114-2026.1.1 → co2114-2026.1.2}/co2114/agent/environment.py +1 -1
- {co2114-2026.1.1 → co2114-2026.1.2}/co2114/optimisation/planning.py +125 -41
- {co2114-2026.1.1 → co2114-2026.1.2}/co2114/optimisation/things.py +9 -0
- {co2114-2026.1.1 → co2114-2026.1.2}/co2114.egg-info/PKG-INFO +1 -1
- {co2114-2026.1.1 → co2114-2026.1.2}/setup.py +1 -1
- {co2114-2026.1.1 → co2114-2026.1.2}/LICENSE +0 -0
- {co2114-2026.1.1 → co2114-2026.1.2}/README.md +0 -0
- {co2114-2026.1.1 → co2114-2026.1.2}/co2114/__init__.py +0 -0
- {co2114-2026.1.1 → co2114-2026.1.2}/co2114/agent/__init__.py +0 -0
- {co2114-2026.1.1 → co2114-2026.1.2}/co2114/agent/things.py +0 -0
- {co2114-2026.1.1 → co2114-2026.1.2}/co2114/constraints/__init__.py +0 -0
- {co2114-2026.1.1 → co2114-2026.1.2}/co2114/constraints/csp/__init__.py +0 -0
- {co2114-2026.1.1 → co2114-2026.1.2}/co2114/constraints/csp/util.py +0 -0
- {co2114-2026.1.1 → co2114-2026.1.2}/co2114/constraints/magic.py +0 -0
- {co2114-2026.1.1 → co2114-2026.1.2}/co2114/constraints/sudoku.py +0 -0
- {co2114-2026.1.1 → co2114-2026.1.2}/co2114/engine.py +0 -0
- {co2114-2026.1.1 → co2114-2026.1.2}/co2114/optimisation/__init__.py +0 -0
- {co2114-2026.1.1 → co2114-2026.1.2}/co2114/optimisation/minimax.py +0 -0
- {co2114-2026.1.1 → co2114-2026.1.2}/co2114/reasoning/__init__.py +0 -0
- {co2114-2026.1.1 → co2114-2026.1.2}/co2114/reasoning/cluedo.py +0 -0
- {co2114-2026.1.1 → co2114-2026.1.2}/co2114/reasoning/inference.py +0 -0
- {co2114-2026.1.1 → co2114-2026.1.2}/co2114/reasoning/logic.py +0 -0
- {co2114-2026.1.1 → co2114-2026.1.2}/co2114/search/__init__.py +0 -0
- {co2114-2026.1.1 → co2114-2026.1.2}/co2114/search/graph.py +0 -0
- {co2114-2026.1.1 → co2114-2026.1.2}/co2114/search/maze.py +0 -0
- {co2114-2026.1.1 → co2114-2026.1.2}/co2114/search/things.py +0 -0
- {co2114-2026.1.1 → co2114-2026.1.2}/co2114/search/util.py +0 -0
- {co2114-2026.1.1 → co2114-2026.1.2}/co2114/util/__init__.py +0 -0
- {co2114-2026.1.1 → co2114-2026.1.2}/co2114/util/colours.py +0 -0
- {co2114-2026.1.1 → co2114-2026.1.2}/co2114/util/fonts.py +0 -0
- {co2114-2026.1.1 → co2114-2026.1.2}/co2114.egg-info/SOURCES.txt +0 -0
- {co2114-2026.1.1 → co2114-2026.1.2}/co2114.egg-info/dependency_links.txt +0 -0
- {co2114-2026.1.1 → co2114-2026.1.2}/co2114.egg-info/requires.txt +0 -0
- {co2114-2026.1.1 → co2114-2026.1.2}/co2114.egg-info/top_level.txt +0 -0
- {co2114-2026.1.1 → co2114-2026.1.2}/setup.cfg +0 -0
|
@@ -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
|
|
@@ -4,7 +4,12 @@ from ..agent.environment import GraphicEnvironment
|
|
|
4
4
|
from ..search.util import manhattan
|
|
5
5
|
import random
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
from typing import override
|
|
8
|
+
|
|
9
|
+
Location = tuple[int, int]
|
|
10
|
+
State = None | dict[str, dict[House | Hospital, Location] | dict[str, int]]
|
|
11
|
+
Numeric = int | float
|
|
12
|
+
PRESET_STATES: dict[str, dict[str, list[Location] | int]] = {
|
|
8
13
|
"empty": None,
|
|
9
14
|
'0': {
|
|
10
15
|
"hospitals": [(2, 4)],
|
|
@@ -41,35 +46,99 @@ PRESET_STATES = {
|
|
|
41
46
|
|
|
42
47
|
|
|
43
48
|
class HospitalOptimiser(Optimiser, UtilityBasedAgent):
|
|
44
|
-
|
|
49
|
+
""" Hospital Optimiser Agent"""
|
|
50
|
+
def explore(self, state:State) -> None:
|
|
51
|
+
""" Move hospitals to new locations in state
|
|
52
|
+
|
|
53
|
+
:param state: new state with hospital locations
|
|
54
|
+
"""
|
|
45
55
|
if not state: return
|
|
46
56
|
print(f"{self}: exploring state\n {state['hospitals']}")
|
|
47
57
|
for hospital, loc in state["hospitals"].items():
|
|
48
58
|
hospital.location = loc
|
|
49
59
|
|
|
50
|
-
|
|
51
|
-
|
|
60
|
+
@override
|
|
61
|
+
def utility(self, state:State) -> Numeric:
|
|
62
|
+
""" Calculate utility of possible state by calculating distance
|
|
63
|
+
of each hospital to houses
|
|
64
|
+
|
|
65
|
+
Returns negative total distance to be minimised.
|
|
66
|
+
|
|
67
|
+
:param state: current state with hospital and house locations
|
|
68
|
+
:return: negative total distance
|
|
69
|
+
"""
|
|
52
70
|
obj = 0
|
|
53
|
-
houses,
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
71
|
+
houses: dict[House, Location] = state["houses"] # type: ignore
|
|
72
|
+
hospitals: dict[Hospital, Location] = state["hospitals"] # type: ignore
|
|
73
|
+
|
|
74
|
+
for house in houses: # iterate over houses
|
|
75
|
+
dist_to_nearest_hospital = infinity # very big
|
|
76
|
+
|
|
77
|
+
for hospital in hospitals: # iterate over hospitals
|
|
78
|
+
house_loc = houses[house]
|
|
79
|
+
hospital_loc = hospitals[hospital]
|
|
80
|
+
|
|
81
|
+
dist = manhattan(house_loc, hospital_loc)
|
|
82
|
+
|
|
83
|
+
# calculate closest distance
|
|
84
|
+
if dist < dist_to_nearest_hospital:
|
|
85
|
+
dist_to_nearest_hospital = dist
|
|
86
|
+
|
|
87
|
+
obj += dist_to_nearest_hospital # add distance for this house
|
|
63
88
|
return -obj
|
|
64
89
|
|
|
65
90
|
|
|
66
91
|
class HospitalPlacement(GraphicEnvironment):
|
|
67
|
-
|
|
92
|
+
""" Hospital Placement Environment
|
|
93
|
+
|
|
94
|
+
Allows placement of hospitals and houses on a grid
|
|
95
|
+
|
|
96
|
+
Has a state consisting of hospital and house locations and bounds of the environment
|
|
97
|
+
"""
|
|
98
|
+
def __init__(self,
|
|
99
|
+
init:dict[str, list[Location] | int] | None = None,
|
|
100
|
+
*args,
|
|
101
|
+
**kwargs) -> None:
|
|
102
|
+
""" Constroctor for HospitalPlacement environment
|
|
103
|
+
|
|
104
|
+
:param init: initial state dictionary with hospital and house locations and height/width of environment
|
|
105
|
+
:param args: additional args for GraphicEnvironment
|
|
106
|
+
:param kwargs: additional kwargs for GraphicEnvironment
|
|
107
|
+
"""
|
|
68
108
|
super().__init__(*args, **kwargs)
|
|
69
109
|
self.initialise_state(init)
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def initialise_state(self,
|
|
113
|
+
state_dict: dict[str, list[Location] | int]) -> None:
|
|
114
|
+
""" Initialise environment state from state dictionary
|
|
115
|
+
|
|
116
|
+
:param state_dict: state dictionary with hospital and house locations and height/width of environment
|
|
117
|
+
"""
|
|
118
|
+
if state_dict is None: return # empty state
|
|
119
|
+
|
|
120
|
+
if "height" in state_dict:
|
|
121
|
+
self.height = state_dict["height"]
|
|
122
|
+
|
|
123
|
+
if "width" in state_dict:
|
|
124
|
+
self.width = state_dict["width"]
|
|
125
|
+
|
|
126
|
+
self.size = self.width, self.height
|
|
127
|
+
|
|
128
|
+
if "hospitals" in state_dict:
|
|
129
|
+
for loc in state_dict["hospitals"]:
|
|
130
|
+
self.add_thing(Hospital(), location=loc)
|
|
131
|
+
|
|
132
|
+
for loc in state_dict["houses"]:
|
|
133
|
+
self.add_thing(House(), location=loc)
|
|
134
|
+
|
|
70
135
|
|
|
71
136
|
@property
|
|
72
|
-
def state(self):
|
|
137
|
+
def state(self) -> State:
|
|
138
|
+
""" Attribute returning current environment state
|
|
139
|
+
|
|
140
|
+
:return: current state with hospital and house locations and bounds of environment
|
|
141
|
+
"""
|
|
73
142
|
return {
|
|
74
143
|
"hospitals": {
|
|
75
144
|
thing: thing.location
|
|
@@ -85,7 +154,12 @@ class HospitalPlacement(GraphicEnvironment):
|
|
|
85
154
|
}
|
|
86
155
|
|
|
87
156
|
@property
|
|
88
|
-
def neighbours(self):
|
|
157
|
+
def neighbours(self) -> list[State]:
|
|
158
|
+
""" Generate neighbouring states by moving each hospital
|
|
159
|
+
in each direction by one unit if possible.
|
|
160
|
+
|
|
161
|
+
:return: list of neighbouring states
|
|
162
|
+
"""
|
|
89
163
|
neighbours = []
|
|
90
164
|
for i, hospital in enumerate(self.state["hospitals"]):
|
|
91
165
|
location = hospital.location
|
|
@@ -97,17 +171,31 @@ class HospitalPlacement(GraphicEnvironment):
|
|
|
97
171
|
neighbours.append(candidate)
|
|
98
172
|
return neighbours
|
|
99
173
|
|
|
100
|
-
def is_inbounds(self, location):
|
|
174
|
+
def is_inbounds(self, location:Location) -> bool:
|
|
175
|
+
""" Checks if location is in bounds and unoccupied
|
|
176
|
+
|
|
177
|
+
:return bool: True if location is in bounds and unoccupied
|
|
178
|
+
"""
|
|
101
179
|
if not super().is_inbounds(location):
|
|
102
180
|
return False
|
|
103
181
|
return len(self.things_at(location)) == 0
|
|
104
182
|
|
|
105
|
-
|
|
183
|
+
@override
|
|
184
|
+
def add_agent(self, agent:Agent) -> None:
|
|
185
|
+
""" Add agent to environment, overrides GraphicEnvironment method as agent has no location.
|
|
186
|
+
|
|
187
|
+
:param agent: agent to add
|
|
188
|
+
"""
|
|
106
189
|
if not isinstance(agent, Agent):
|
|
107
190
|
raise TypeError(f"{self}: {agent} is not an Agent.")
|
|
108
191
|
self.agents.add(agent)
|
|
109
192
|
|
|
110
|
-
|
|
193
|
+
@override
|
|
194
|
+
def add_thing_randomly(self, thing:things.Thing) -> None:
|
|
195
|
+
""" Add thing to random unoccupied location in environment
|
|
196
|
+
|
|
197
|
+
:param thing: thing to add
|
|
198
|
+
"""
|
|
111
199
|
x = random.randint(self.x_start, self.x_end-1)
|
|
112
200
|
y = random.randint(self.y_start, self.y_end-1)
|
|
113
201
|
lim, count = 10, 0
|
|
@@ -120,35 +208,31 @@ class HospitalPlacement(GraphicEnvironment):
|
|
|
120
208
|
y = random.randint(self.y_start, self.y_end-1)
|
|
121
209
|
self.add_thing(thing, (x,y))
|
|
122
210
|
|
|
123
|
-
|
|
124
|
-
def initialise_state(self, state_dict):
|
|
125
|
-
if state_dict is None:
|
|
126
|
-
return
|
|
127
|
-
if "height" in state_dict:
|
|
128
|
-
self.height = state_dict["height"]
|
|
129
|
-
if "width" in state_dict:
|
|
130
|
-
self.width = state_dict["width"]
|
|
131
|
-
self.size = self.width, self.height
|
|
132
|
-
if "hospitals" in state_dict:
|
|
133
|
-
for loc in state_dict["hospitals"]:
|
|
134
|
-
self.add_thing(Hospital(), location=loc)
|
|
135
|
-
for loc in state_dict["houses"]:
|
|
136
|
-
self.add_thing(House(), location=loc)
|
|
137
|
-
|
|
138
|
-
|
|
139
211
|
@property
|
|
140
|
-
def is_done(self):
|
|
212
|
+
def is_done(self) -> bool:
|
|
213
|
+
""" Definition of when environment is done """
|
|
141
214
|
if len(self.agents) == 0: return True # if there are no agents
|
|
142
215
|
return hasattr(self, "success") and self.success
|
|
143
216
|
|
|
144
|
-
|
|
217
|
+
@override
|
|
218
|
+
def percept(self, agent:Agent) -> tuple[State, list[State]]:
|
|
219
|
+
""" Percept for agent in environment
|
|
220
|
+
|
|
221
|
+
:return tuple: current state and neighbouring states
|
|
222
|
+
"""
|
|
145
223
|
return self.state, self.neighbours
|
|
146
224
|
|
|
147
|
-
|
|
148
|
-
|
|
225
|
+
@override
|
|
226
|
+
def execute_action(self,
|
|
227
|
+
agent:HospitalOptimiser,action:tuple[str,State]) -> None:
|
|
228
|
+
""" Execute an action
|
|
229
|
+
|
|
230
|
+
:param agent: agent performing action
|
|
231
|
+
:param action: action to execute, tuple of command and state. possible commands are "done" and "explore"
|
|
232
|
+
"""
|
|
149
233
|
command, state = action
|
|
150
234
|
match command:
|
|
151
|
-
case "done":
|
|
235
|
+
case "done": # optimisation complete
|
|
152
236
|
if state:
|
|
153
237
|
agent.explore(state)
|
|
154
238
|
self.success = True
|
|
@@ -2,17 +2,26 @@ from ..search import things
|
|
|
2
2
|
|
|
3
3
|
from ..util.fonts import platform
|
|
4
4
|
|
|
5
|
+
from typing import override
|
|
6
|
+
|
|
7
|
+
# Re-export relevant classes from things module
|
|
5
8
|
Agent = things.Agent
|
|
6
9
|
UtilityBasedAgent = things.UtilityBasedAgent
|
|
7
10
|
|
|
8
11
|
class Hospital(things.Thing):
|
|
12
|
+
@override
|
|
9
13
|
def __repr__(self):
|
|
14
|
+
""" String representation of a Hospital. """
|
|
10
15
|
return "🏥" if platform != "darwin" else "+"
|
|
11
16
|
|
|
12
17
|
class House(things.Thing):
|
|
18
|
+
@override
|
|
13
19
|
def __repr__(self):
|
|
20
|
+
""" String representation of a House. """
|
|
14
21
|
return "🏠" if platform != "darwin" else "^"
|
|
15
22
|
|
|
16
23
|
class Optimiser(things.Agent):
|
|
24
|
+
@override
|
|
17
25
|
def __repr__(self):
|
|
26
|
+
""" String representation of an Optimiser. """
|
|
18
27
|
return "📈"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|