Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Round environment #48

Merged
merged 21 commits into from
Nov 14, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
dfb9dc1
update draw walls function
vagechirkov Nov 4, 2022
ac9e493
add reflection_from_circular_wall function and tests
vagechirkov Nov 7, 2022
d08a7e3
reflect agent from the circular border
vagechirkov Nov 7, 2022
3a073dd
reflect resource from the circular border
vagechirkov Nov 7, 2022
e61ecca
minor comment
vagechirkov Nov 7, 2022
9b3e07f
ignore videos
vagechirkov Nov 7, 2022
c40ff66
run test on PR too
vagechirkov Nov 7, 2022
4fde313
test docker-image workflow
vagechirkov Nov 8, 2022
465e8bb
split GitHub action Docker CI workflow in two workflows
vagechirkov Nov 8, 2022
5f486f6
Merge branch 'develop' into round-environment
vagechirkov Nov 10, 2022
b990a13
update agent's position only ones per update
vagechirkov Nov 10, 2022
768941d
relocate resource base on its velocity
vagechirkov Nov 10, 2022
12a35dc
fix new agents generation via slider in playground
vagechirkov Nov 10, 2022
377b298
make the default patch_radius larger
vagechirkov Nov 14, 2022
b74d604
Revert "make the default patch_radius larger"
vagechirkov Nov 14, 2022
fdd57fd
refactor imports
vagechirkov Nov 14, 2022
d3359aa
fix the bug when moving agents with the mouse (use list instead of tu…
vagechirkov Nov 14, 2022
8e02862
use velocity instead of radius for relocation of the agent
vagechirkov Nov 14, 2022
887199b
relocate agents when their positions are manually changed
vagechirkov Nov 14, 2022
edf6d39
relocate resource when the positions are manually changed
vagechirkov Nov 14, 2022
09f67da
Mocking submodule variables to keep
mezdahun Nov 14, 2022
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions .github/workflows/docker-image-push.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
name: Docker Image CI

on:
push:
branches:
- develop

jobs:

build:

runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v2
- name: Docker Login
env:
DOCKER_USER: ${{secrets.DOCKER_USER}}
DOCKER_PASSWORD: ${{secrets.DOCKER_PASSWORD}}
run: |
docker login -u $DOCKER_USER -p $DOCKER_PASSWORD
- name: Build the Docker image
run: docker build . --file Dockerfile --tag ${{secrets.DOCKER_USER}}/scioip34abm:latest
- name: Push Docker image to DockerHub
run: docker push ${{secrets.DOCKER_USER}}/scioip34abm:latest
19 changes: 5 additions & 14 deletions .github/workflows/docker-image.yml
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
name: Docker Image CI

on:
push:
branches: [ develop ]
pull_request:
branches: [ develop ]
branches:
- develop

jobs:

Expand All @@ -13,14 +12,6 @@ jobs:
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v2
- name: Docker Login
env:
DOCKER_USER: ${{secrets.DOCKER_USER}}
DOCKER_PASSWORD: ${{secrets.DOCKER_PASSWORD}}
run: |
docker login -u $DOCKER_USER -p $DOCKER_PASSWORD
- name: Build the Docker image
run: docker build . --file Dockerfile --tag ${{secrets.DOCKER_USER}}/scioip34abm:latest
- name: Push Docker image to DockerHub
run: docker push ${{secrets.DOCKER_USER}}/scioip34abm:latest
- uses: actions/checkout@v2
- name: Build the Docker image
run: docker build . --file Dockerfile
2 changes: 1 addition & 1 deletion .github/workflows/python-package.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
name: Python package

on: [push]
on: [push, pull_request]

jobs:
build:
Expand Down
5 changes: 4 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -245,4 +245,7 @@ cython_debug/
.idea

# ignore all copied env files
*_copy.env
*_copy.env

# ignore videos
*.mp4
69 changes: 57 additions & 12 deletions abm/projects/cooperative_signaling/cs_agent/cs_agent.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import numpy as np

from abm.agent import supcalc
from abm.projects.cooperative_signaling.cs_agent.cs_supcalc import \
reflection_from_circular_wall, random_walk, F_reloc_LR, phototaxis
from abm.agent.agent import Agent
from abm.contrib import colors
from abm.projects.cooperative_signaling.cs_agent.cs_supcalc import random_walk, \
F_reloc_LR, phototaxis


class CSAgent(Agent):
Expand Down Expand Up @@ -57,11 +57,11 @@ def update(self, agents):
else:
if self.meter > 0:
theta, taxis_dir = phototaxis(
self.meter,
self.prev_meter,
self.theta_prev,
self.taxis_dir,
self.phototaxis_theta_step)
self.meter,
self.prev_meter,
self.theta_prev,
self.taxis_dir,
self.phototaxis_theta_step)
self.taxis_dir = taxis_dir
vel = (2 - self.velocity)
self.agent_type = "mars_miner"
Expand All @@ -84,12 +84,15 @@ def update(self, agents):
self.velocity += vel
# self.prove_velocity() # possibly bounding velocity of agent

# updating agent's position
self.position[0] += self.velocity * np.cos(self.orientation)
self.position[1] -= self.velocity * np.sin(self.orientation)
# new agent's position
new_pos = (
self.position[0] + self.velocity * np.cos(self.orientation),
self.position[1] - self.velocity * np.sin(self.orientation)
)

# boundary conditions if applicable
self.reflect_from_walls()
# update the agent's position with constraints (reflection from the
# walls) or with the new position
self.position = list(self.reflect_from_walls(new_pos))
else:
# self.agent_type = "signalling"
print(self.meter)
Expand Down Expand Up @@ -286,3 +289,45 @@ def prove_velocity(self, velocity_limit=1):
if np.abs(self.velocity) > velocity_limit:
# stopping agent if too fast during exploration
self.velocity = 1

def reflect_from_walls(self, new_pos=()):
"""
Reflecting agent from the circle arena border.
"""
# x coordinate - x of the center point of the circle
x = new_pos[0] + self.radius
c_x = (self.WIDTH / 2 + self.window_pad)
dx = x - c_x
# y coordinate - y of the center point of the circle
y = new_pos[1] + self.radius
c_y = (self.HEIGHT / 2 + self.window_pad)
dy = y - c_y
# radius of the environment
e_r = self.HEIGHT / 2

# return if the agent has not reached the boarder
if np.linalg.norm([dx, dy]) + self.radius < e_r:
return new_pos

# reflect the agent from the boarder
self.orientation = reflection_from_circular_wall(
dx, dy, self.orientation)

# make orientation between 0 and 2pi
self.prove_orientation()

# relocate the agent back inside the circle
new_pos = (
self.position[0] + self.velocity * np.cos(self.orientation),
self.position[1] - self.velocity * np.sin(self.orientation)
)
# check if the agent is still outside the circle
diff = [new_pos[0] - c_x, new_pos[1] - c_y]
if np.linalg.norm(diff) + self.radius >= e_r:
# if yes, relocate it again
dist = np.linalg.norm(diff) + self.radius - e_r
new_pos = (
self.position[0] + dist * np.cos(self.orientation),
self.position[1] - dist * np.sin(self.orientation)
)
return new_pos
37 changes: 37 additions & 0 deletions abm/projects/cooperative_signaling/cs_agent/cs_supcalc.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import numpy as np
import pygame

from abm.contrib import movement_params

Expand Down Expand Up @@ -38,6 +39,42 @@ def F_reloc_LR(vel_now, V_now, v_desired=None, theta_max=None):
return (v_desired - vel_now), theta



def reflection_from_circular_wall(dx, dy, orientation):
"""
Calculating the reflection of the agent from the circle arena border.
SEE: https://stackoverflow.com/questions/54543170/angle-reflexion-for-bouncing-ball-in-a-circle

:param dx: x coordinate of the agent minus center of the circle
:param dy: y coordinate of the agent minus center of the circle
:param orientation: orientation of the agent
:return: new orientation of the agent
"""
# normal vector of the circle
c_norm = (pygame.math.Vector2(dx, dy)).normalize()
# incident vector: the current direction vector of the bouncing agent
vec_i = pygame.math.Vector2(np.cos(orientation), np.sin(orientation))
# orientation inside the circle
i_orientation = np.pi + np.arctan2(vec_i[1], vec_i[0])

# reflection vector: outgoing direction vector of the bouncing agent
vec_r = vec_i - 2 * c_norm.dot(vec_i) * c_norm
# np.degrees(self.orientation)
new_orientation = np.pi + np.arctan2(vec_r[1], vec_r[0])

# make sure that the new orientation points inside the circle and not too
# flat to the border
if np.abs(new_orientation - i_orientation) > np.pi / 4:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I understand this fixes the behavior, but I am wondering why it is necessary.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would guess it has something to do with the curvature of the boundary and the step size per update 😅

new_orientation = i_orientation
# make sure that the change of the orientation is not too big; this prevents
# the agent from "jumping" over the border and other wierd behavior when the
# agent changes its orientation too ofter
elif np.abs(new_orientation - orientation) > np.pi / 4:
new_orientation = i_orientation

return new_orientation


def phototaxis(meter, prev_meter, prev_theta, taxis_dir,
phototaxis_theta_step):
"""
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import numpy as np
import pytest
from unittest import mock

from abm.projects.cooperative_signaling.cs_agent import cs_supcalc

Expand All @@ -9,11 +10,44 @@ def test_random_walk():
# set random seed
np.random.seed(42)

dvel, dtheta = cs_supcalc.random_walk()

# passing values to override abm.movementparams values read from .env file
dvel, dtheta = cs_supcalc.random_walk(desired_vel=1, exp_theta_min=-0.3, exp_theta_max=0.3)
assert dvel == 1.0
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

for me random walk test fails can you please check?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah it is because the env parameters, we might want to mock this so that a new local env file won't influence the test results

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is strange because all the tests in the workflow passed 🤔

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you replace the .env file (i.e. you are after running the playground tool) the tests won't pass. Or do they pass for you even with a new .env file?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I never had any problems with tests before or after running the playground 😅🤔

assert dtheta == -0.0752759286915825

# test case: not passing min theta, read from module fixed var
with mock.patch('abm.contrib.movement_params.exp_theta_min', -0.5):
dvel, dtheta = cs_supcalc.random_walk(desired_vel=1, exp_theta_min=None, exp_theta_max=0.3)
assert dvel == 1.0
assert dtheta == 0.26057144512793295

# test case: not passing velocity, read from module fixed var
with mock.patch('abm.contrib.movement_params.exp_vel_max', 3):
dvel, dtheta = cs_supcalc.random_walk(desired_vel=None, exp_theta_min=-0.3, exp_theta_max=0.3)
assert dvel == 3
assert dtheta == 0.13919636508684308


def test_reflection_from_circular_wall():
"""Test reflection_from_circular_wall()"""

new_orientation = cs_supcalc.reflection_from_circular_wall(
0, 1, np.pi / 2)
assert new_orientation == np.pi * 3 / 2

new_orientation = cs_supcalc.reflection_from_circular_wall(
1, 0, np.pi)
assert new_orientation == np.pi * 2

# test very flat reflection angle
orient = np.pi + np.pi / 6
vec_i = [np.cos(orient), np.sin(orient)]
# orientation inside the circle
i_orientation = np.pi + np.arctan2(vec_i[1], vec_i[0])
new_orientation = cs_supcalc.reflection_from_circular_wall(
0, 1, orient)
assert new_orientation == i_orientation


@pytest.mark.parametrize(
"meter, prev_meter, prev_theta, taxis_dir, new_theta, new_taxis_dir",
Expand Down
89 changes: 39 additions & 50 deletions abm/projects/cooperative_signaling/cs_environment/cs_resource.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@

from abm.contrib import colors
from abm.environment.rescource import Rescource
from abm.projects.cooperative_signaling.cs_agent.cs_supcalc import random_walk
from abm.projects.cooperative_signaling.cs_agent.cs_supcalc import random_walk, \
reflection_from_circular_wall


class CSResource(Rescource):
Expand Down Expand Up @@ -44,59 +45,45 @@ def prove_orientation(self):
self.orientation = self.orientation - 2 * np.pi

def reflect_from_walls(self):
"""reflecting agent from environment boundaries according to a desired
x, y coordinate. If this is over any
boundaries of the environment, the agents position and orientation will
be changed such that the agent is
reflected from these boundaries."""

# Boundary conditions according to center of agent (simple)
"""
Reflecting resource from the circle arena border. Analogous to CSAgent
reflect_from_walls() method.
"""
# x coordinate - x of the center point of the circle
x = self.position[0] + self.radius
c_x = (self.WIDTH / 2 + self.window_pad)
dx = x - c_x
# y coordinate - y of the center point of the circle
y = self.position[1] + self.radius

# Reflection from left wall
if x < self.boundaries_x[0]:
self.position[0] = self.boundaries_x[0] - self.radius

if np.pi / 2 <= self.orientation < np.pi:
self.orientation -= np.pi / 2
elif np.pi <= self.orientation <= 3 * np.pi / 2:
self.orientation += np.pi / 2
self.prove_orientation() # bounding orientation into 0 and 2pi

# Reflection from right wall
if x > self.boundaries_x[1]:

self.position[0] = self.boundaries_x[1] - self.radius - 1

if 3 * np.pi / 2 <= self.orientation < 2 * np.pi:
self.orientation -= np.pi / 2
elif 0 <= self.orientation <= np.pi / 2:
self.orientation += np.pi / 2
self.prove_orientation() # bounding orientation into 0 and 2pi

# Reflection from upper wall
if y < self.boundaries_y[0]:
self.position[1] = self.boundaries_y[0] - self.radius

if np.pi / 2 <= self.orientation <= np.pi:
self.orientation += np.pi / 2
elif 0 <= self.orientation < np.pi / 2:
self.orientation -= np.pi / 2
self.prove_orientation() # bounding orientation into 0 and 2pi

# Reflection from lower wall
if y > self.boundaries_y[1]:
self.position[1] = self.boundaries_y[1] - self.radius - 1
if 3 * np.pi / 2 <= self.orientation <= 2 * np.pi:
self.orientation += np.pi / 2
elif np.pi <= self.orientation < 3 * np.pi / 2:
self.orientation -= np.pi / 2
self.prove_orientation() # bounding orientation into 0 and 2pi

c_y = (self.HEIGHT / 2 + self.window_pad)
dy = y - c_y
# radius of the environment
e_r = self.HEIGHT / 2

# return if the resource has not reached the boarder
if np.linalg.norm([dx, dy]) + self.radius < e_r:
return

# reflect the resource from the boarder
self.orientation = reflection_from_circular_wall(
dx, dy, self.orientation)

# make orientation between 0 and 2pi
self.prove_orientation()

# relocate the resource back inside the circle
relocation = self.velocity
self.position[0] += relocation * np.cos(self.orientation)
self.position[1] -= relocation * np.sin(self.orientation)
self.center = (
self.position[0] + self.radius, self.position[1] + self.radius)

# check if the resource is still outside the circle
diff = [self.center[0] - c_x, self.center[1] - c_y]
if np.linalg.norm(diff) + self.radius >= e_r:
# if yes, relocate it again at the center
self.center = (c_x, c_y)

def update(self):

# applying random movement on resource patch
Expand Down Expand Up @@ -130,6 +117,8 @@ def draw_update(self):
self.mask = pygame.mask.from_surface(self.image)
if self.is_clicked or self.show_stats:
font = pygame.font.Font(None, 18)
text = font.render(f"{self.resc_left:.2f}, Q{self.unit_per_timestep:.2f}", True, colors.BLACK)
text = font.render(
f"{self.resc_left:.2f}, Q{self.unit_per_timestep:.2f}", True,
colors.BLACK)
self.image.blit(text, (0, 0))
text_rect = text.get_rect(center=self.rect.center)
Loading