from math import cos, degrees, sin
from matplotlib.patches import Ellipse
from numpy import array
from pNeuma_simulator import params
[docs]
class Particle:
"""A class representing a two-dimensional particle.
This class models a particle with position, velocity, and other attributes
relevant to its motion and interactions.
Attributes:
ID (int): The unique identifier of the particle.
mode (str): Type of particle, e.g., "Car" or "Moto".
theta (float): Angle of the particle.
a0 (float): The desired direction.
ttc (float): Time to collision.
f_a (list): Distance to collision.
image (object): Clone of the particle.
leader (Particle): The leading particle.
rad (numpy.ndarray): The radius matrix.
gap (float): The gap between particles.
tau (float): Adaptation time.
lam (float): Lambda parameter.
v0 (float): Desired velocity.
s0 (float): Jam spacing.
pos (numpy.ndarray): The position of the particle.
vel (numpy.ndarray): The velocity of the particle.
theta (float): The angle of the particle's velocity.
interactions (list): List of interactions with other particles.
"""
def __init__(self, x, y, speed, theta, mode="Car", ID=None, styles=None):
"""Initialize the particle's position, velocity, mode and ID.
Any key-value pairs passed in the styles dictionary will be passed
as arguments to Matplotlib's Ellipse patch constructor.
Args:
x (float): The x-coordinate of the particle's position.
y (float): The y-coordinate of the particle's position.
speed (float): Magnitude of the particle's velocity.
theta (float): The angle of the particle in radians.
mode (str, optional): The mode of the particle. Defaults to "Car".
ID (int, optional): The unique identifier of the particle. Defaults to None.
styles (dict, optional): A dictionary of styles for Matplotlib's Ellipse patch. Defaults to None.
"""
self.ID = ID
self.mode = mode
self.theta = theta
self.speed = speed
self.a0 = 0
self.ttc = None
self.f_a = None
self.image = None
self.leader = None
self.rad = None
self.gap = None
self.tau = None
self.lam = None
self.v0 = None
self.s0 = None
self.pos = array((x, y))
self.vel = array((speed * cos(theta), speed * sin(theta)))
self.interactions = []
if self.mode == "Car":
self.l = params.car_l # half length
self.w = params.car_w # half width
self.a = params.car_a # noise amplitude
self.b = params.car_b # relaxation time
else:
self.l = params.moto_l # half length
self.w = params.moto_w # half width
self.a = params.moto_a # noise amplitude
self.b = params.moto_b # relaxation time
self.styles = styles
if not self.styles:
# Default ellipse styles
self.styles = {"ec": "k", "fill": False}
# For convenience, map the components of the particle's position and
# velocity vector onto the attributes x, y, vx and vy.
@property
def x(self):
return self.pos[0]
@x.setter
def x(self, value):
self.pos[0] = value
@property
def y(self):
return self.pos[1]
@y.setter
def y(self, value):
self.pos[1] = value
@property
def vx(self):
return self.vel[0]
@property
def vy(self):
return self.vel[1]
[docs]
def draw(self, ax):
"""Add this Particle's Ellipse patch to the Matplotlib Axes ax."""
ellipse = Ellipse(xy=self.pos, width=2 * self.l, height=2 * self.w, angle=degrees(self.theta), **self.styles)
ax.add_patch(ellipse)
return ellipse
[docs]
def advance(self, dt, new_V, new_theta):
"""Advance the particle's position according to its velocity."""
self.theta = new_theta
self.speed = new_V
self.vel = array((new_V * cos(new_theta), new_V * sin(new_theta)))
self.pos = self.pos + self.vel * dt
# apply periodic boundary conditions
if self.pos[0] > params.L / 2:
self.pos[0] -= params.L
[docs]
def encode(self, t):
my_dict = self.__dict__
my_dict.pop("ID", None)
my_dict.pop("mode", None)
my_dict.pop("image", None)
my_dict.pop("leader", None)
my_dict.pop("vel", None)
my_dict.pop("tau", None)
my_dict.pop("gap", None)
my_dict.pop("rad", None)
my_dict.pop("f_a", None)
my_dict.pop("a0", None)
my_dict.pop("l", None)
my_dict.pop("w", None)
my_dict.pop("a", None)
my_dict.pop("b", None)
my_dict.pop("styles", None)
my_dict.pop("interactions", None)
if t > 0:
my_dict.pop("lam", None)
my_dict.pop("v0", None)
my_dict.pop("s0", None)
return my_dict
def __deepcopy__(self, memodict={}):
# https://stackoverflow.com/questions/24756712
copy_object = Particle(self.x, self.y, self.speed, self.theta, self.mode, self.ID, self.styles)
copy_object.ttc = self.ttc
copy_object.lam = self.lam
copy_object.v0 = self.v0
copy_object.s0 = self.s0
return copy_object
def __getitem__(self, key):
"""Get the value of a specific attribute of the particle."""
return getattr(self, key)