gabse 0.1.1__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.
- gabse-0.1.1/LICENSE +21 -0
- gabse-0.1.1/MANIFEST.in +6 -0
- gabse-0.1.1/PKG-INFO +27 -0
- gabse-0.1.1/README.md +10 -0
- gabse-0.1.1/gabse/__init__.py +29 -0
- gabse-0.1.1/gabse/agent.py +131 -0
- gabse-0.1.1/gabse/context.py +86 -0
- gabse-0.1.1/gabse/data.py +72 -0
- gabse-0.1.1/gabse/engine.py +41 -0
- gabse-0.1.1/gabse/schedule.py +131 -0
- gabse-0.1.1/gabse.egg-info/PKG-INFO +27 -0
- gabse-0.1.1/gabse.egg-info/SOURCES.txt +16 -0
- gabse-0.1.1/gabse.egg-info/dependency_links.txt +1 -0
- gabse-0.1.1/gabse.egg-info/requires.txt +3 -0
- gabse-0.1.1/gabse.egg-info/top_level.txt +1 -0
- gabse-0.1.1/pyproject.toml +36 -0
- gabse-0.1.1/setup.cfg +16 -0
gabse-0.1.1/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Carl Toller Melén
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
gabse-0.1.1/MANIFEST.in
ADDED
gabse-0.1.1/PKG-INFO
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: gabse
|
|
3
|
+
Version: 0.1.1
|
|
4
|
+
Summary: The GABSE (Generic Agent-Based Simulation for Engineering) framework
|
|
5
|
+
Author: Carl Toller Melén
|
|
6
|
+
Author-email: Carl Toller Melén <carl@tollermelen.se>
|
|
7
|
+
Maintainer-email: Carl Toller Melén <carl@tollermelen.se>
|
|
8
|
+
License-Expression: MIT
|
|
9
|
+
Project-URL: Homepage, https://github.com/tm-carl/GABSE
|
|
10
|
+
Requires-Python: >=3.11
|
|
11
|
+
Description-Content-Type: text/markdown
|
|
12
|
+
License-File: LICENSE
|
|
13
|
+
Requires-Dist: numpy>=2.3.4
|
|
14
|
+
Requires-Dist: scipy>=1.16.3
|
|
15
|
+
Requires-Dist: sortedcontainers>=2.4.0
|
|
16
|
+
Dynamic: license-file
|
|
17
|
+
|
|
18
|
+
# GABSE
|
|
19
|
+
This is the GABSE (Generic Agent-Based Simulation for Engineering) framework. It provides classes and methods to create and manage simulations involving agents, their actions, sensors, and the simulation context. It is based on agent-based modeling technique and is developed with the intention of being lightweight, scalable, and flexible. This package provides the engine, action scheduling, generic agent functionality, and sensory data collection and management tools.
|
|
20
|
+
|
|
21
|
+
EXAMPLES:
|
|
22
|
+
- Zombie apocalypse (both 2D and 3D)
|
|
23
|
+
|
|
24
|
+
REQUIRED packages:
|
|
25
|
+
- sortedcontainers
|
|
26
|
+
- numpy
|
|
27
|
+
- copy
|
gabse-0.1.1/README.md
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
# GABSE
|
|
2
|
+
This is the GABSE (Generic Agent-Based Simulation for Engineering) framework. It provides classes and methods to create and manage simulations involving agents, their actions, sensors, and the simulation context. It is based on agent-based modeling technique and is developed with the intention of being lightweight, scalable, and flexible. This package provides the engine, action scheduling, generic agent functionality, and sensory data collection and management tools.
|
|
3
|
+
|
|
4
|
+
EXAMPLES:
|
|
5
|
+
- Zombie apocalypse (both 2D and 3D)
|
|
6
|
+
|
|
7
|
+
REQUIRED packages:
|
|
8
|
+
- sortedcontainers
|
|
9
|
+
- numpy
|
|
10
|
+
- copy
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Copyright (C), 2025, Carl Toller Melén
|
|
3
|
+
|
|
4
|
+
This is the GABSE (Generic Agent-Based Simulation for Engineering) framework.
|
|
5
|
+
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
# version number
|
|
9
|
+
__name__ = "gabse"
|
|
10
|
+
__author__ = "Carl Toller Melén"
|
|
11
|
+
__version__ = "0.1.1"
|
|
12
|
+
|
|
13
|
+
from gabse import engine
|
|
14
|
+
from gabse.agent import Agent
|
|
15
|
+
from gabse.data import Sensor, DataCollector
|
|
16
|
+
from gabse.schedule import Action, Schedule
|
|
17
|
+
from gabse.engine import Engine
|
|
18
|
+
|
|
19
|
+
from gabse.context import Context
|
|
20
|
+
|
|
21
|
+
__all__ = [
|
|
22
|
+
"Engine",
|
|
23
|
+
"Action",
|
|
24
|
+
"Schedule",
|
|
25
|
+
"Agent",
|
|
26
|
+
"Sensor",
|
|
27
|
+
"Context",
|
|
28
|
+
"DataCollector",
|
|
29
|
+
]
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
"""
|
|
2
|
+
@author: Carl Toller Melén
|
|
3
|
+
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
# %%
|
|
7
|
+
# Import required packages
|
|
8
|
+
import numpy as np
|
|
9
|
+
from scipy.spatial import cKDTree as _cKDTree
|
|
10
|
+
|
|
11
|
+
# %%
|
|
12
|
+
# Agent class for representing entities in the simulation
|
|
13
|
+
|
|
14
|
+
class Agent:
|
|
15
|
+
# Static variable to keep track of agent IDs
|
|
16
|
+
_id_counter = 0
|
|
17
|
+
|
|
18
|
+
# Initialize agent with unique ID, position, engine reference, and empty sensor
|
|
19
|
+
def __init__(self, engine, position=None):
|
|
20
|
+
Agent._id_counter += 1
|
|
21
|
+
self.id = Agent._id_counter
|
|
22
|
+
if position is None:
|
|
23
|
+
position = [0, 0, 0]
|
|
24
|
+
self.engine = engine
|
|
25
|
+
self.position = np.array(position)
|
|
26
|
+
self.neighbours = list()
|
|
27
|
+
self.sensor = ""
|
|
28
|
+
|
|
29
|
+
# Find nearest neighbours based on Euclidean distance
|
|
30
|
+
def find_neighbours(self, agents, noOfNeighbours):
|
|
31
|
+
"""
|
|
32
|
+
agents: iterable of agents to consider (can exclude self prior to call)
|
|
33
|
+
returns: list of nearest agents, or single agent if noOfNeighbours == 1
|
|
34
|
+
"""
|
|
35
|
+
if not agents:
|
|
36
|
+
return [] if noOfNeighbours != 1 else None
|
|
37
|
+
|
|
38
|
+
n = len(agents)
|
|
39
|
+
k = min(noOfNeighbours, n)
|
|
40
|
+
|
|
41
|
+
# Try KDTree for large n or repeated queries
|
|
42
|
+
try:
|
|
43
|
+
pos = np.vstack([a.get_position() for a in agents])
|
|
44
|
+
tree = _cKDTree(pos)
|
|
45
|
+
dists, idxs = tree.query(self.get_position(), k=k)
|
|
46
|
+
if k == 1:
|
|
47
|
+
return agents[int(idxs)]
|
|
48
|
+
if np.isscalar(idxs):
|
|
49
|
+
idxs = [int(idxs)]
|
|
50
|
+
else:
|
|
51
|
+
idxs = [int(i) for i in np.atleast_1d(idxs)]
|
|
52
|
+
return [agents[i] for i in idxs]
|
|
53
|
+
except Exception:
|
|
54
|
+
# Get self position
|
|
55
|
+
self_pos = self.get_position()
|
|
56
|
+
|
|
57
|
+
# stack positions (shape: (n, dim)) and compute squared distances
|
|
58
|
+
pos = self.engine.context.get_positions_array()
|
|
59
|
+
|
|
60
|
+
if pos.size == 0:
|
|
61
|
+
return [] if noOfNeighbours != 1 else None
|
|
62
|
+
|
|
63
|
+
# compute squared Euclidean distances
|
|
64
|
+
d2 = np.sum((pos - self_pos) ** 2, axis=1)
|
|
65
|
+
|
|
66
|
+
# Return based if only one neighbours requested
|
|
67
|
+
if k == 1:
|
|
68
|
+
return agents[int(np.argmin(d2))]
|
|
69
|
+
if k < n:
|
|
70
|
+
idx_k = np.argpartition(d2, k - 1)[:k]
|
|
71
|
+
idx_sorted = idx_k[np.argsort(d2[idx_k])]
|
|
72
|
+
else:
|
|
73
|
+
idx_sorted = np.argsort(d2)
|
|
74
|
+
|
|
75
|
+
return [agents[i] for i in idx_sorted[:k]]
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
# Check if agent is out of bounds and change the position so that it is within bounds
|
|
79
|
+
def check_out_of_bounds(self):
|
|
80
|
+
bounds = np.array(self.engine.context.get_dimensions())
|
|
81
|
+
|
|
82
|
+
minValues = bounds[0:3]
|
|
83
|
+
maxValues = bounds[3:]
|
|
84
|
+
|
|
85
|
+
return np.clip(self.position, minValues, maxValues)
|
|
86
|
+
|
|
87
|
+
#Move agent to a specific position
|
|
88
|
+
def move_position(self, position):
|
|
89
|
+
self.position = position
|
|
90
|
+
self.position = self.check_out_of_bounds()
|
|
91
|
+
# print(self.position)
|
|
92
|
+
|
|
93
|
+
try:
|
|
94
|
+
self.engine.context.mark_dirty()
|
|
95
|
+
except Exception:
|
|
96
|
+
pass
|
|
97
|
+
|
|
98
|
+
def move_vector(self, vector):
|
|
99
|
+
self.position += vector
|
|
100
|
+
self.position = self.check_out_of_bounds()
|
|
101
|
+
# print(self.position)
|
|
102
|
+
|
|
103
|
+
try:
|
|
104
|
+
self.engine.context.mark_dirty()
|
|
105
|
+
except Exception:
|
|
106
|
+
pass
|
|
107
|
+
|
|
108
|
+
# Calculate Euclidean distance between two agents
|
|
109
|
+
@staticmethod
|
|
110
|
+
def get_distance(p1, p2):
|
|
111
|
+
return np.linalg.norm(p1.get_position() - p2.get_position())
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def find_shortest_path(self, network):
|
|
115
|
+
pass
|
|
116
|
+
|
|
117
|
+
# Add sensor to agent
|
|
118
|
+
def add_sensor(self, sensor):
|
|
119
|
+
self.sensor = sensor
|
|
120
|
+
|
|
121
|
+
# Getters and Setters
|
|
122
|
+
def get_sensor(self):
|
|
123
|
+
return self.sensor
|
|
124
|
+
|
|
125
|
+
def get_position(self):
|
|
126
|
+
return self.position
|
|
127
|
+
|
|
128
|
+
def set_position(self, position):
|
|
129
|
+
self.position = position
|
|
130
|
+
|
|
131
|
+
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
"""
|
|
2
|
+
@author: Carl Toller Melén
|
|
3
|
+
|
|
4
|
+
"""
|
|
5
|
+
# %%
|
|
6
|
+
# Import required packages
|
|
7
|
+
import numpy as np
|
|
8
|
+
|
|
9
|
+
# %%
|
|
10
|
+
# Context class for managing agents within a defined space
|
|
11
|
+
|
|
12
|
+
class Context:
|
|
13
|
+
# Initializes the context with dimensions and empty agent list
|
|
14
|
+
def __init__(self, dimensions):
|
|
15
|
+
self._positions_cache = None
|
|
16
|
+
self.dimensions = np.array(dimensions)
|
|
17
|
+
self.agents = list()
|
|
18
|
+
self._dirty = True
|
|
19
|
+
|
|
20
|
+
# Adds a new agent to the list
|
|
21
|
+
def add_agent(self, agent):
|
|
22
|
+
self.agents.append(agent)
|
|
23
|
+
self._dirty = True
|
|
24
|
+
|
|
25
|
+
# Removes an agent from the list
|
|
26
|
+
def remove_agent(self, agent):
|
|
27
|
+
# Finds the right agent in the list and removes it
|
|
28
|
+
self.agents.remove(agent)
|
|
29
|
+
self._dirty = True
|
|
30
|
+
|
|
31
|
+
def mark_dirty(self):
|
|
32
|
+
self._dirty = True
|
|
33
|
+
|
|
34
|
+
# Checks if an object is of a specific class name
|
|
35
|
+
@staticmethod
|
|
36
|
+
def check_class(obj, name):
|
|
37
|
+
if obj.__class__.__name__ == name:
|
|
38
|
+
return True
|
|
39
|
+
else:
|
|
40
|
+
return False
|
|
41
|
+
|
|
42
|
+
# Getters
|
|
43
|
+
def get_agents(self):
|
|
44
|
+
return self.agents
|
|
45
|
+
|
|
46
|
+
def get_agents_by_class(self, class_name):
|
|
47
|
+
return [agent for agent in self.agents if self.check_class(agent, class_name)]
|
|
48
|
+
|
|
49
|
+
def get_dimensions(self):
|
|
50
|
+
return self.dimensions
|
|
51
|
+
|
|
52
|
+
def get_positions_array(self):
|
|
53
|
+
"""
|
|
54
|
+
Returns a (n, dim) numpy array of agent positions.
|
|
55
|
+
Cached until an agent calls context.mark_dirty().
|
|
56
|
+
"""
|
|
57
|
+
if self._dirty:
|
|
58
|
+
if not self.agents:
|
|
59
|
+
self._positions_cache = np.empty((0, self.dimensions.size // 2))
|
|
60
|
+
else:
|
|
61
|
+
# list comprehension into vstack once per rebuild
|
|
62
|
+
self._positions_cache = np.vstack([a.get_position() for a in self.agents])
|
|
63
|
+
self._dirty = False
|
|
64
|
+
return self._positions_cache
|
|
65
|
+
|
|
66
|
+
def get_agent_count(self, classes=None):
|
|
67
|
+
entry = dict()
|
|
68
|
+
|
|
69
|
+
# if no classes provided, return total count for each agent type
|
|
70
|
+
if not classes:
|
|
71
|
+
unique_classes = set(obj.__class__.__name__ for obj in self.agents)
|
|
72
|
+
for cls in unique_classes:
|
|
73
|
+
a = sum(self.check_class(obj, cls) for obj in self.agents)
|
|
74
|
+
entry[cls] = a
|
|
75
|
+
return entry
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
for arg in classes:
|
|
79
|
+
# print(arg)
|
|
80
|
+
a = sum(self.check_class(obj, arg) for obj in self.agents)
|
|
81
|
+
# print(a)
|
|
82
|
+
entry[arg] = a
|
|
83
|
+
|
|
84
|
+
#print(entry)
|
|
85
|
+
return entry
|
|
86
|
+
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
"""
|
|
2
|
+
@author: Carl Toller Melén
|
|
3
|
+
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
# %%
|
|
7
|
+
# Import required packages
|
|
8
|
+
import numpy as np
|
|
9
|
+
import copy
|
|
10
|
+
|
|
11
|
+
# %%
|
|
12
|
+
# Sensor class for logging agent data over time
|
|
13
|
+
|
|
14
|
+
class Sensor:
|
|
15
|
+
# Initializes the sensor with engine reference, parent agent, empty logger, and frequency
|
|
16
|
+
def __init__(self, engine, parent, frequency):
|
|
17
|
+
self.engine = engine
|
|
18
|
+
self.parent = parent
|
|
19
|
+
self.logger = list()
|
|
20
|
+
self.frequency = frequency
|
|
21
|
+
|
|
22
|
+
# Logs data entries based on specified getters
|
|
23
|
+
def entry(self, *getters):
|
|
24
|
+
entry = {"tick": self.engine.get_tick()}
|
|
25
|
+
|
|
26
|
+
for arg in getters:
|
|
27
|
+
# print(g)
|
|
28
|
+
method = getattr(self.parent, "get_" + arg)
|
|
29
|
+
if not callable(method):
|
|
30
|
+
continue
|
|
31
|
+
|
|
32
|
+
data = method()
|
|
33
|
+
#check if data is numpy array and convert to list
|
|
34
|
+
if isinstance(data, np.ndarray):
|
|
35
|
+
data = data.tolist() # to avoid reference issues with mutable data types
|
|
36
|
+
else:
|
|
37
|
+
try:
|
|
38
|
+
data = copy.copy(data) # to avoid reference issues with mutable data types
|
|
39
|
+
except Exception:
|
|
40
|
+
pass
|
|
41
|
+
|
|
42
|
+
entry[arg] = data
|
|
43
|
+
|
|
44
|
+
self.logger.append(entry)
|
|
45
|
+
# print(self.engine.getTick())
|
|
46
|
+
|
|
47
|
+
# Getters
|
|
48
|
+
def get_frequency(self):
|
|
49
|
+
return self.frequency
|
|
50
|
+
|
|
51
|
+
def get_logger(self):
|
|
52
|
+
return self.logger
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
# %%
|
|
56
|
+
# Data Collector class for gathering and exporting data from agents' and context sensors
|
|
57
|
+
|
|
58
|
+
class DataCollector:
|
|
59
|
+
def __init__(self, engine):
|
|
60
|
+
self.engine = engine
|
|
61
|
+
self.repo = dict()
|
|
62
|
+
|
|
63
|
+
# Collects data from all agents' sensors and stores it in the repository
|
|
64
|
+
def collect_data(self):
|
|
65
|
+
for agt in self.engine.context.get_agents():
|
|
66
|
+
self.repo[f"{agt.__class__.__name__} {agt.id}"] = agt.get_sensor().get_logger()
|
|
67
|
+
|
|
68
|
+
# print(self.repo)
|
|
69
|
+
|
|
70
|
+
# Exports the collected data repository
|
|
71
|
+
def export_data(self):
|
|
72
|
+
return self.repo
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
"""
|
|
2
|
+
@author: Carl Toller Melén
|
|
3
|
+
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
# %%
|
|
7
|
+
# Import required packages
|
|
8
|
+
from gabse.schedule import Schedule
|
|
9
|
+
from gabse.context import Context
|
|
10
|
+
|
|
11
|
+
# %%
|
|
12
|
+
# Engine class for managing the simulation
|
|
13
|
+
|
|
14
|
+
class Engine:
|
|
15
|
+
def __init__(self, modelTime, dimensions, context=None):
|
|
16
|
+
self.tick = 0.0
|
|
17
|
+
self.modelTime = modelTime
|
|
18
|
+
self.schedule = Schedule(self.tick)
|
|
19
|
+
|
|
20
|
+
# Initialize context, allowing for custom context to be passed
|
|
21
|
+
if context is None:
|
|
22
|
+
self.context = Context(dimensions)
|
|
23
|
+
else:
|
|
24
|
+
self.context = context
|
|
25
|
+
|
|
26
|
+
def run(self):
|
|
27
|
+
while self.tick <= self.modelTime and self.schedule.get_size() > 0:
|
|
28
|
+
self.tick = self.schedule.step()
|
|
29
|
+
# print(self.tick)
|
|
30
|
+
|
|
31
|
+
# print("RUN COMPLETED!")
|
|
32
|
+
|
|
33
|
+
def abort(self):
|
|
34
|
+
self.schedule.clear_schedule()
|
|
35
|
+
print(f"Stopped at: {self.tick}")
|
|
36
|
+
|
|
37
|
+
def get_tick(self):
|
|
38
|
+
return self.tick
|
|
39
|
+
|
|
40
|
+
def get_context(self):
|
|
41
|
+
return self.context
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
"""
|
|
2
|
+
@author: Carl Toller Melén
|
|
3
|
+
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
# %%
|
|
7
|
+
# Import required packages
|
|
8
|
+
from sortedcontainers import SortedList
|
|
9
|
+
|
|
10
|
+
# %%
|
|
11
|
+
# Action class for representing scheduled actions
|
|
12
|
+
|
|
13
|
+
class Action:
|
|
14
|
+
"""
|
|
15
|
+
tick: float
|
|
16
|
+
The simulation tick at which the action is scheduled to occur.
|
|
17
|
+
agent: Agent
|
|
18
|
+
The agent that will perform the action.
|
|
19
|
+
method: str
|
|
20
|
+
The name of the method to be called on the agent.
|
|
21
|
+
args: list, optional
|
|
22
|
+
The arguments to be passed to the method. Can be None, empty list, or "" if no arguments are needed.
|
|
23
|
+
priority: int, optional
|
|
24
|
+
The priority of the action (lower values indicate higher priority). Default is 0.
|
|
25
|
+
interval: float, optional
|
|
26
|
+
The interval for recurring actions. If greater than 0, the action will be rescheduled
|
|
27
|
+
after execution. Default is 0.
|
|
28
|
+
"""
|
|
29
|
+
def __init__(self, tick, agent, method, args=None, priority=0, interval=0):
|
|
30
|
+
self.tick = float(tick)
|
|
31
|
+
self.agent = agent
|
|
32
|
+
self.method = method
|
|
33
|
+
self.args = args
|
|
34
|
+
self.priority = int(priority)
|
|
35
|
+
self.interval = float(interval)
|
|
36
|
+
|
|
37
|
+
def __str__(self):
|
|
38
|
+
return f"Action entry:\ntick: {self.tick}, agent: {self.agent}, method: {self.method}, arguments: {self.args}, priority: {self.priority}, interval: {self.interval}"
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
# %%
|
|
42
|
+
# Schedule class for managing and executing scheduled actions
|
|
43
|
+
class Schedule:
|
|
44
|
+
|
|
45
|
+
# Creates an empty schedule (list) and tick timer, set to zero
|
|
46
|
+
# List is sorted based on tick value of actions and priority
|
|
47
|
+
def __init__(self, tick):
|
|
48
|
+
self.schedule = SortedList(key=lambda a: (a.tick, a.priority))
|
|
49
|
+
self.tick = tick
|
|
50
|
+
|
|
51
|
+
# Schedule method for adding an action in schedule
|
|
52
|
+
def schedule_action(self, action: Action):
|
|
53
|
+
self.schedule.add(action)
|
|
54
|
+
|
|
55
|
+
# Method for stepping forward in simulation
|
|
56
|
+
def step(self):
|
|
57
|
+
# If schedule is empty, return current tick
|
|
58
|
+
if not self.schedule:
|
|
59
|
+
return self.tick
|
|
60
|
+
|
|
61
|
+
# Checks if previous actions exist and, if so, removes them
|
|
62
|
+
while self.schedule[0].tick < self.tick:
|
|
63
|
+
self.schedule.pop(0)
|
|
64
|
+
|
|
65
|
+
# If schedule is empty after removing past actions, return current tick
|
|
66
|
+
if not self.schedule:
|
|
67
|
+
return self.tick
|
|
68
|
+
|
|
69
|
+
# Load the first action in schedule
|
|
70
|
+
action = self.schedule[0]
|
|
71
|
+
|
|
72
|
+
# Step to next action tick
|
|
73
|
+
self.tick = action.tick
|
|
74
|
+
|
|
75
|
+
# Calls action agent method
|
|
76
|
+
method = getattr(action.agent, action.method)
|
|
77
|
+
|
|
78
|
+
# print(f"{action.agent} at {self.tick}")
|
|
79
|
+
# print(action.method)
|
|
80
|
+
# print(args)
|
|
81
|
+
|
|
82
|
+
# Check and call
|
|
83
|
+
if callable(method):
|
|
84
|
+
if action.args is None or len(action.args) == 0 or action.args == "":
|
|
85
|
+
method()
|
|
86
|
+
else:
|
|
87
|
+
method(*action.args)
|
|
88
|
+
# print(result) # Output: 1, 2, 3
|
|
89
|
+
else:
|
|
90
|
+
print("Method not found or not callable.")
|
|
91
|
+
|
|
92
|
+
# Checks if the action is recurring and, if so, schedules next instance
|
|
93
|
+
if action.interval > 0.0:
|
|
94
|
+
nextAction = Action(
|
|
95
|
+
tick=action.tick + action.interval,
|
|
96
|
+
agent=action.agent,
|
|
97
|
+
method=action.method,
|
|
98
|
+
args=action.args,
|
|
99
|
+
interval=action.interval
|
|
100
|
+
)
|
|
101
|
+
self.schedule_action(nextAction)
|
|
102
|
+
|
|
103
|
+
# Remove the executed action from the schedule
|
|
104
|
+
self.schedule.pop(0)
|
|
105
|
+
|
|
106
|
+
#Return current tick
|
|
107
|
+
return self.tick
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
# Filter out all actions related to the target agent
|
|
111
|
+
def remove_agent_from_list(self, target):
|
|
112
|
+
self.schedule = SortedList(
|
|
113
|
+
[action for action in self.schedule if action.agent != target],
|
|
114
|
+
key=lambda action: (action.tick, action.priority)
|
|
115
|
+
)
|
|
116
|
+
|
|
117
|
+
def get_schedule(self):
|
|
118
|
+
return self.schedule
|
|
119
|
+
|
|
120
|
+
def print_schedule(self):
|
|
121
|
+
for action in self.schedule:
|
|
122
|
+
print(action)
|
|
123
|
+
|
|
124
|
+
def get_tick(self):
|
|
125
|
+
return self.tick
|
|
126
|
+
|
|
127
|
+
def get_size(self):
|
|
128
|
+
return len(self.schedule)
|
|
129
|
+
|
|
130
|
+
def clear_schedule(self):
|
|
131
|
+
self.schedule.clear()
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: gabse
|
|
3
|
+
Version: 0.1.1
|
|
4
|
+
Summary: The GABSE (Generic Agent-Based Simulation for Engineering) framework
|
|
5
|
+
Author: Carl Toller Melén
|
|
6
|
+
Author-email: Carl Toller Melén <carl@tollermelen.se>
|
|
7
|
+
Maintainer-email: Carl Toller Melén <carl@tollermelen.se>
|
|
8
|
+
License-Expression: MIT
|
|
9
|
+
Project-URL: Homepage, https://github.com/tm-carl/GABSE
|
|
10
|
+
Requires-Python: >=3.11
|
|
11
|
+
Description-Content-Type: text/markdown
|
|
12
|
+
License-File: LICENSE
|
|
13
|
+
Requires-Dist: numpy>=2.3.4
|
|
14
|
+
Requires-Dist: scipy>=1.16.3
|
|
15
|
+
Requires-Dist: sortedcontainers>=2.4.0
|
|
16
|
+
Dynamic: license-file
|
|
17
|
+
|
|
18
|
+
# GABSE
|
|
19
|
+
This is the GABSE (Generic Agent-Based Simulation for Engineering) framework. It provides classes and methods to create and manage simulations involving agents, their actions, sensors, and the simulation context. It is based on agent-based modeling technique and is developed with the intention of being lightweight, scalable, and flexible. This package provides the engine, action scheduling, generic agent functionality, and sensory data collection and management tools.
|
|
20
|
+
|
|
21
|
+
EXAMPLES:
|
|
22
|
+
- Zombie apocalypse (both 2D and 3D)
|
|
23
|
+
|
|
24
|
+
REQUIRED packages:
|
|
25
|
+
- sortedcontainers
|
|
26
|
+
- numpy
|
|
27
|
+
- copy
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
LICENSE
|
|
2
|
+
MANIFEST.in
|
|
3
|
+
README.md
|
|
4
|
+
pyproject.toml
|
|
5
|
+
setup.cfg
|
|
6
|
+
gabse/__init__.py
|
|
7
|
+
gabse/agent.py
|
|
8
|
+
gabse/context.py
|
|
9
|
+
gabse/data.py
|
|
10
|
+
gabse/engine.py
|
|
11
|
+
gabse/schedule.py
|
|
12
|
+
gabse.egg-info/PKG-INFO
|
|
13
|
+
gabse.egg-info/SOURCES.txt
|
|
14
|
+
gabse.egg-info/dependency_links.txt
|
|
15
|
+
gabse.egg-info/requires.txt
|
|
16
|
+
gabse.egg-info/top_level.txt
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
gabse
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# toml
|
|
2
|
+
[build-system]
|
|
3
|
+
requires = ["setuptools>=64.0"]
|
|
4
|
+
build-backend = "setuptools.build_meta"
|
|
5
|
+
|
|
6
|
+
[project]
|
|
7
|
+
name = "gabse"
|
|
8
|
+
version = "0.1.1"
|
|
9
|
+
description = "The GABSE (Generic Agent-Based Simulation for Engineering) framework"
|
|
10
|
+
readme = "README.md"
|
|
11
|
+
requires-python = ">=3.11"
|
|
12
|
+
license = "MIT"
|
|
13
|
+
authors = [
|
|
14
|
+
{ name = "Carl Toller Melén", email = "carl@tollermelen.se" },
|
|
15
|
+
]
|
|
16
|
+
maintainers = [
|
|
17
|
+
{ name = "Carl Toller Melén", email = "carl@tollermelen.se" },
|
|
18
|
+
]
|
|
19
|
+
|
|
20
|
+
dependencies = [
|
|
21
|
+
"numpy>=2.3.4",
|
|
22
|
+
"scipy>=1.16.3",
|
|
23
|
+
"sortedcontainers>=2.4.0",
|
|
24
|
+
]
|
|
25
|
+
|
|
26
|
+
[project.urls]
|
|
27
|
+
Homepage = "https://github.com/tm-carl/GABSE"
|
|
28
|
+
#Documentation = "https://eriktoller.github.io/andfn/n"
|
|
29
|
+
#Repository = "https://github.com/eriktoller/andfn"
|
|
30
|
+
#Issues = "https://github.com/eriktoller/andfn/issues"
|
|
31
|
+
|
|
32
|
+
[tool.setuptools.packages.find]
|
|
33
|
+
include = ["gabse"]
|
|
34
|
+
|
|
35
|
+
[tool.setuptools.dynamic]
|
|
36
|
+
version = { attr = "gabse.__version__" }
|
gabse-0.1.1/setup.cfg
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
[metadata]
|
|
2
|
+
name = gabse
|
|
3
|
+
version = 0.1.1
|
|
4
|
+
description = The GABSE (Generic Agent-Based Simulation for Engineering) framework
|
|
5
|
+
author = Carl Toller Melén
|
|
6
|
+
author_email = carl@tollermelen.se
|
|
7
|
+
license = MIT
|
|
8
|
+
|
|
9
|
+
[options]
|
|
10
|
+
packages = find:
|
|
11
|
+
python_requires = >=3.11
|
|
12
|
+
|
|
13
|
+
[egg_info]
|
|
14
|
+
tag_build =
|
|
15
|
+
tag_date = 0
|
|
16
|
+
|