diff --git a/DSSE/__init__.py b/DSSE/__init__.py index 54afd702..29548770 100644 --- a/DSSE/__init__.py +++ b/DSSE/__init__.py @@ -1,9 +1,11 @@ """Core module of the project. Contains the main logic of the application.""" from .environment.env import DroneSwarmSearch +from .environment.coverage_env import CoverageDroneSwarmSearch from .environment.constants import Actions __all__ = [ "DroneSwarmSearch", + "CoverageDroneSwarmSearch", "Actions" ] diff --git a/DSSE/environment/coverage_env.py b/DSSE/environment/coverage_env.py index fa1e755d..e17ce942 100644 --- a/DSSE/environment/coverage_env.py +++ b/DSSE/environment/coverage_env.py @@ -1,6 +1,6 @@ from gymnasium.spaces import Discrete from .env_base import DroneSwarmSearchBase -from .constants import Actions +from .constants import Actions, Reward # TODO: Match env_base to conv_env -> If using particle sim, redo __init__ and reset. @@ -8,21 +8,30 @@ class CoverageDroneSwarmSearch(DroneSwarmSearchBase): metadata = { "name": "DroneSwarmSearchCPP", } + reward_scheme = Reward( + default=0, + leave_grid=-100, + exceed_timestep=-100, + drones_collision=-100, + search_cell=0, + search_and_find=10000, + ) def __init__( self, grid_size=7, render_mode="ansi", - render_grid=False, + render_grid=True, render_gradient=True, - vector=(-0.5, -0.5), - disperse_constant=10, + vector=(3.1, 3.2), + dispersion_inc=0.1, + dispersion_start=0.5, timestep_limit=100, disaster_position=(0, 0), drone_amount=1, drone_speed=10, drone_probability_of_detection=0.9, - pre_render_time=0, + pre_render_time=10, ) -> None: super().__init__( grid_size, @@ -30,7 +39,8 @@ def __init__( render_grid, render_gradient, vector, - disperse_constant, + dispersion_inc, + dispersion_start, timestep_limit, disaster_position, drone_amount, @@ -44,16 +54,21 @@ def __init__( self.all_states = { (x, y) for x in range(self.grid_size) for y in range(self.grid_size) } + self.repeated_coverage = 0 + self.cumm_pos = 0 def reset(self, seed=None, options=None): - obs, infos = super().reset(seed=seed, options=options) + obs, _ = super().reset(seed=seed, options=options) self.seen_states = {pos for pos in self.agents_positions.values()} self.not_seen_states = self.all_states - self.seen_states + infos = self.compute_infos(False) + self.cumm_pos = 0 + self.repeated_coverage = 0 return obs, infos def create_observations(self): observations = {} - self.probability_matrix.step(self.drone.speed) + # self.probability_matrix.step(self.drone.speed) probability_matrix = self.probability_matrix.get_matrix() for agent in self.agents: @@ -73,12 +88,12 @@ def step(self, actions: dict[str, int]) -> tuple: if not self._was_reset: raise ValueError("Please reset the env before interacting with it") - # TODO: Define the reward_scheme terminations = {a: False for a in self.agents} - rewards = {a: 0 for a in self.agents} + rewards = {a: self.reward_scheme.default for a in self.agents} truncations = {a: False for a in self.agents} - prob_matrix = self.probability_matrix.get_matrix() + self.timestep += 1 + prob_matrix = self.probability_matrix.get_matrix() for agent in self.agents: if agent not in actions: raise ValueError("Missing action for " + agent) @@ -87,30 +102,39 @@ def step(self, actions: dict[str, int]) -> tuple: if drone_action not in self.action_space(agent): raise ValueError("Invalid action for " + agent) - drone_x, drone_y = self.agents_positions[agent] - if drone_action != Actions.SEARCH.value: - new_position = self.move_drone((drone_x, drone_y), drone_action) - if not self.is_valid_position(new_position): - rewards[agent] = self.reward_scheme["out_of_bounds"] - else: - self.agents_positions[agent] = new_position - rewards[agent] = ( - prob_matrix[drone_y][drone_x] * 10000 - if prob_matrix[drone_y][drone_x] * 100 > 1 - else -100 - ) - self.seen_states.add(self.agents_positions[agent]) - self.not_seen_states.remove(self.agents_positions[agent]) - - # Check truncation conditions (overwrites termination conditions) if self.timestep >= self.timestep_limit: - rewards[agent] = self.reward_scheme["exceed_timestep"] + rewards[agent] = self.reward_scheme.exceed_timestep truncations[agent] = True + continue + + drone_x, drone_y = self.agents_positions[agent] + new_position = self.move_drone((drone_x, drone_y), drone_action) + if not self.is_valid_position(new_position): + rewards[agent] = self.reward_scheme.leave_grid + continue + + self.agents_positions[agent] = new_position + new_x, new_y = new_position + if new_position in self.not_seen_states: + reward_poc = 1 / (self.timestep) * prob_matrix[new_y, new_x] * 1_000 + rewards[agent] = self.reward_scheme.search_cell + reward_poc + self.seen_states.add(new_position) + self.not_seen_states.remove(new_position) + # Probability of sucess (POS) = POC * POD + self.cumm_pos += prob_matrix[new_y, new_x] * self.pod + else: + self.repeated_coverage += 1 - self.timestep += 1 # Get dummy infos is_completed = len(self.not_seen_states) == 0 - infos = {drone: {"completed": is_completed} for drone in self.agents} + self.render() + if is_completed: + # TODO: Proper define reward for completing the search (R_done) + rewards = { + drone: self.reward_scheme.search_and_find for drone in self.agents + } + terminations = {drone: True for drone in self.agents} + infos = self.compute_infos(is_completed) self.compute_drone_collision(terminations, rewards, truncations) # Get observations @@ -120,5 +144,16 @@ def step(self, actions: dict[str, int]) -> tuple: self.agents = [] return observations, rewards, terminations, truncations, infos + def compute_infos(self, is_completed: bool) -> dict[str, dict]: + # TODO: Is this the best way to inform the coverage rate, Cum_pos and repetitions? + coverage_rate = len(self.seen_states) / len(self.all_states) + infos = { + "is_completed": is_completed, + "coverage_rate": coverage_rate, + "repeated_coverage": self.repeated_coverage / len(self.all_states), + "acumulated_pos": self.cumm_pos, + } + return {drone: infos for drone in self.agents} + def action_space(self, agent): return Discrete(8) diff --git a/DSSE/environment/env_base.py b/DSSE/environment/env_base.py index 2558e4a4..9da407f2 100644 --- a/DSSE/environment/env_base.py +++ b/DSSE/environment/env_base.py @@ -42,8 +42,8 @@ def __init__( self.drone = DroneData( amount=drone_amount, speed=drone_speed, - probability_of_detection=drone_probability_of_detection, ) + self.pod = drone_probability_of_detection # Error Checking if self.drone.amount > self.grid_size * self.grid_size: @@ -95,7 +95,6 @@ def calculate_simulation_time_step( """ return cell_size / (drone_max_speed - wind_resistance) # in seconds - @abstractmethod def render(self): self.pygame_renderer.render_map() self.pygame_renderer.render_entities(self.agents_positions.values()) @@ -209,7 +208,7 @@ def compute_drone_collision(self, terminations, rewards, truncations): ): truncations[drone_1_id] = True terminations[drone_1_id] = True - rewards[drone_1_id] = self.reward_scheme["drones_collision"] + rewards[drone_1_id] = self.reward_scheme.drones_collision def move_drone(self, position, action): """ diff --git a/DSSE/environment/particle_sim.py b/DSSE/environment/particle_sim.py new file mode 100644 index 00000000..6938778b --- /dev/null +++ b/DSSE/environment/particle_sim.py @@ -0,0 +1,16 @@ +from datetime import datetime, timedelta +from opendrift.models.oceandrift import OceanDrift + +def open_drift(lat, lon, time, number, radius, duration, outfile): + o = OceanDrift() + o.add_readers_from_list(['https://tds.hycom.org/thredds/dodsC/GLBy0.08/expt_93.0/uv3z']) + o.seed_elements(lat=lat, lon=lon, time=time, number=number, radius=radius) + + # Running the model + o.run(duration=duration, outfile=outfile) + + lat_final = o.elements.lat + lon_final = o.elements.lon + + # Return final positions as list of tuples (latitude, longitude) + return list(zip(lat_final, lon_final)) \ No newline at end of file diff --git a/basic_coverage.py b/basic_coverage.py new file mode 100644 index 00000000..a86db24e --- /dev/null +++ b/basic_coverage.py @@ -0,0 +1,23 @@ +from DSSE import CoverageDroneSwarmSearch + +env = CoverageDroneSwarmSearch( + grid_size=40, + drone_amount=3, + dispersion_inc=0.1, + vector=(3.2, 3.1), + render_mode="human", +) + +opt = { + "drones_positions": [(0, 10), (10, 10), (20, 10)], +} +obs, info = env.reset(options=opt) + +step = 0 +while env.agents: + step += 1 + actions = {agent: env.action_space(agent).sample() for agent in env.agents} + observations, rewards, terminations, truncations, infos = env.step(actions) + print(f"Step: {step}") + +print(infos["drone0"]) \ No newline at end of file