Module crowd-control.classes.lattice
Expand source code
import numpy as np
import random 
from numpy import ndarray
from classes.cell import Cell 
from helpers import get_neighbor_coords, get_value_array, get_distance_array
class Lattice:
    """
    A Lattice is a 2D discrete grid of cells objects, initialized with empty cells.
    Attributes:
        len_x (int): Number of rows.
        len_y (int): Number of columns.
        cells (ndarray[Cell]): 2D array of cell objects
        n_cells (int): Number of cells in the lattice
    """
    def __init__(self, len_x, len_y) -> None:
        """
        Initializes empty Lattice object.
        Args:
            len_x (int): Number of rows.
            len_y (int): Number of columns.
        """
        self.len_x = len_x
        self.len_y = len_y
        self.cells = self.initialize_grid()
        self.n_cells = len_x * len_y
    def initialize_grid(self) -> ndarray[Cell]:
        """
        Create empty cell objects and return as an 2D array.
        """
        cells = [[] for _ in range(self.len_x)]
        for x in range(self.len_x):
            for y in range(self.len_y):
                cell = Cell(x, y, y, self.len_y - y)
                cells[x].append(cell)
        return np.array(cells)
    
    def load_neighbours(self):
        """
        Assign neighbors to all cell objects.
        """
        for cell in self.cells.flatten():
            neighbor_coords = get_neighbor_coords(cell.x, cell.y, self.len_x, self.len_y)
            for coords in neighbor_coords:
                neighbor = self.cells[coords]
                cell.add_neighbor(neighbor)
    
    def get_random_cell(self) -> Cell:
        """
        Select a random cell on the lattice and return it.
        """
        random_x = random.randint(0, self.len_x - 1)
        random_y = random.randint(0, self.len_y - 1)
        return self.cells[random_x, random_y]
    def get_random_empty_edge_cell(self, y, x, p) -> Cell | None:
        """
        Return unpopulated cell on edge around same row number. 
        Return None if no empty cell found.
        Used for periodic boundary conditions.
        Args:
            y (int): Column number of edge
            x (int): Row number of cell
            p (float): Probability of moving straight.
        """
        x_list = []
        for i in range(x-1, x+2):
            x_list.append(i) if i >= 0 and i < self.len_x else None
        # try to target cell on same row
        if self.cells[x, y].is_empty() and random.random() <= p:
            return self.cells[x, y]
        # otherwise pick a random one
        random.shuffle(x_list)
        for x_n in x_list:
            if self.cells[x_n, y].is_empty():
                return self.cells[x_n, y] 
        # no empty cell was found
        return None    
        
    def get_populated_cells(self) -> ndarray[Cell]:
        """
        Return all populated cell objects.
        """
        return self.cells[get_value_array(self.cells) != 0]
    def populate_corridor(self, N):
        """
        Randomly populate lattice with left-moving and right-moving agents.
        Args:
            N (int): Number of cells to populate.
        """
        assert N <= self.cells.size, 'Number of people is larger than number of cells'
        
        for _ in range(N):
            value = 1 if random.random() < 0.5 else -1
            self.populate_random_cell(value)
    def populate_random_cell(self, value):
        """
        Populate a random cell and return it.
        Args:
            value (int): Decides if cell is populated by right-mover or left-mover.
        """
        assert isinstance(value, int) and value in [-1, 1], 'value must be +/- 1 integer'
        cell = self.get_random_cell()
        while not self.populate_cell(cell, value):
            cell = self.get_random_cell()
        return cell
    
    def populate_cell(self, cell, value):
        """
        Populate cell with a left or right-moving individual if it is empty.
        Return True if successful, else False.
        Args:
            cell (Cell): Cell to populate.
            value (int): Decides if cell is populated by right-mover or left-mover.
        """
        assert isinstance(value, int) and value in [-1, 1]
        if not cell.is_empty():
            return False
        
        cell.populate(value)
        return True
    
    def calculate_lane_formation(self):
        """
        Calculates the objective function for lane formation.
        Returns phi, a number between zero and one.
        """
        phi = 0
        N = len(self.get_populated_cells())
        value_array = get_value_array(self.cells)
        for row in range(self.len_x):
            counter_left = np.count_nonzero(value_array[row] == -1)
            counter_right = np.count_nonzero(value_array[row] == 1)
            if counter_left > 1 or counter_right > 1:
                phi += ((counter_left - counter_right)**2/(counter_left + counter_right))/N
        return phi
    def __str__(self) -> str:
        return f'{get_distance_array(self.cells, 1)}'
Classes
class Lattice (len_x, len_y)- 
A Lattice is a 2D discrete grid of cells objects, initialized with empty cells.
Attributes
len_x:int- Number of rows.
 len_y:int- Number of columns.
 cells:ndarray[Cell]- 2D array of cell objects
 n_cells:int- Number of cells in the lattice
 
Initializes empty Lattice object.
Args
len_x:int- Number of rows.
 len_y:int- Number of columns.
 
Expand source code
class Lattice: """ A Lattice is a 2D discrete grid of cells objects, initialized with empty cells. Attributes: len_x (int): Number of rows. len_y (int): Number of columns. cells (ndarray[Cell]): 2D array of cell objects n_cells (int): Number of cells in the lattice """ def __init__(self, len_x, len_y) -> None: """ Initializes empty Lattice object. Args: len_x (int): Number of rows. len_y (int): Number of columns. """ self.len_x = len_x self.len_y = len_y self.cells = self.initialize_grid() self.n_cells = len_x * len_y def initialize_grid(self) -> ndarray[Cell]: """ Create empty cell objects and return as an 2D array. """ cells = [[] for _ in range(self.len_x)] for x in range(self.len_x): for y in range(self.len_y): cell = Cell(x, y, y, self.len_y - y) cells[x].append(cell) return np.array(cells) def load_neighbours(self): """ Assign neighbors to all cell objects. """ for cell in self.cells.flatten(): neighbor_coords = get_neighbor_coords(cell.x, cell.y, self.len_x, self.len_y) for coords in neighbor_coords: neighbor = self.cells[coords] cell.add_neighbor(neighbor) def get_random_cell(self) -> Cell: """ Select a random cell on the lattice and return it. """ random_x = random.randint(0, self.len_x - 1) random_y = random.randint(0, self.len_y - 1) return self.cells[random_x, random_y] def get_random_empty_edge_cell(self, y, x, p) -> Cell | None: """ Return unpopulated cell on edge around same row number. Return None if no empty cell found. Used for periodic boundary conditions. Args: y (int): Column number of edge x (int): Row number of cell p (float): Probability of moving straight. """ x_list = [] for i in range(x-1, x+2): x_list.append(i) if i >= 0 and i < self.len_x else None # try to target cell on same row if self.cells[x, y].is_empty() and random.random() <= p: return self.cells[x, y] # otherwise pick a random one random.shuffle(x_list) for x_n in x_list: if self.cells[x_n, y].is_empty(): return self.cells[x_n, y] # no empty cell was found return None def get_populated_cells(self) -> ndarray[Cell]: """ Return all populated cell objects. """ return self.cells[get_value_array(self.cells) != 0] def populate_corridor(self, N): """ Randomly populate lattice with left-moving and right-moving agents. Args: N (int): Number of cells to populate. """ assert N <= self.cells.size, 'Number of people is larger than number of cells' for _ in range(N): value = 1 if random.random() < 0.5 else -1 self.populate_random_cell(value) def populate_random_cell(self, value): """ Populate a random cell and return it. Args: value (int): Decides if cell is populated by right-mover or left-mover. """ assert isinstance(value, int) and value in [-1, 1], 'value must be +/- 1 integer' cell = self.get_random_cell() while not self.populate_cell(cell, value): cell = self.get_random_cell() return cell def populate_cell(self, cell, value): """ Populate cell with a left or right-moving individual if it is empty. Return True if successful, else False. Args: cell (Cell): Cell to populate. value (int): Decides if cell is populated by right-mover or left-mover. """ assert isinstance(value, int) and value in [-1, 1] if not cell.is_empty(): return False cell.populate(value) return True def calculate_lane_formation(self): """ Calculates the objective function for lane formation. Returns phi, a number between zero and one. """ phi = 0 N = len(self.get_populated_cells()) value_array = get_value_array(self.cells) for row in range(self.len_x): counter_left = np.count_nonzero(value_array[row] == -1) counter_right = np.count_nonzero(value_array[row] == 1) if counter_left > 1 or counter_right > 1: phi += ((counter_left - counter_right)**2/(counter_left + counter_right))/N return phi def __str__(self) -> str: return f'{get_distance_array(self.cells, 1)}'Methods
def calculate_lane_formation(self)- 
Calculates the objective function for lane formation. Returns phi, a number between zero and one.
Expand source code
def calculate_lane_formation(self): """ Calculates the objective function for lane formation. Returns phi, a number between zero and one. """ phi = 0 N = len(self.get_populated_cells()) value_array = get_value_array(self.cells) for row in range(self.len_x): counter_left = np.count_nonzero(value_array[row] == -1) counter_right = np.count_nonzero(value_array[row] == 1) if counter_left > 1 or counter_right > 1: phi += ((counter_left - counter_right)**2/(counter_left + counter_right))/N return phi def get_populated_cells(self) ‑> numpy.ndarray[classes.cell.Cell]- 
Return all populated cell objects.
Expand source code
def get_populated_cells(self) -> ndarray[Cell]: """ Return all populated cell objects. """ return self.cells[get_value_array(self.cells) != 0] def get_random_cell(self) ‑> classes.cell.Cell- 
Select a random cell on the lattice and return it.
Expand source code
def get_random_cell(self) -> Cell: """ Select a random cell on the lattice and return it. """ random_x = random.randint(0, self.len_x - 1) random_y = random.randint(0, self.len_y - 1) return self.cells[random_x, random_y] def get_random_empty_edge_cell(self, y, x, p) ‑> classes.cell.Cell | None- 
Return unpopulated cell on edge around same row number. Return None if no empty cell found. Used for periodic boundary conditions.
Args
y:int- Column number of edge
 x:int- Row number of cell
 p:float- Probability of moving straight.
 
Expand source code
def get_random_empty_edge_cell(self, y, x, p) -> Cell | None: """ Return unpopulated cell on edge around same row number. Return None if no empty cell found. Used for periodic boundary conditions. Args: y (int): Column number of edge x (int): Row number of cell p (float): Probability of moving straight. """ x_list = [] for i in range(x-1, x+2): x_list.append(i) if i >= 0 and i < self.len_x else None # try to target cell on same row if self.cells[x, y].is_empty() and random.random() <= p: return self.cells[x, y] # otherwise pick a random one random.shuffle(x_list) for x_n in x_list: if self.cells[x_n, y].is_empty(): return self.cells[x_n, y] # no empty cell was found return None def initialize_grid(self) ‑> numpy.ndarray[classes.cell.Cell]- 
Create empty cell objects and return as an 2D array.
Expand source code
def initialize_grid(self) -> ndarray[Cell]: """ Create empty cell objects and return as an 2D array. """ cells = [[] for _ in range(self.len_x)] for x in range(self.len_x): for y in range(self.len_y): cell = Cell(x, y, y, self.len_y - y) cells[x].append(cell) return np.array(cells) def load_neighbours(self)- 
Assign neighbors to all cell objects.
Expand source code
def load_neighbours(self): """ Assign neighbors to all cell objects. """ for cell in self.cells.flatten(): neighbor_coords = get_neighbor_coords(cell.x, cell.y, self.len_x, self.len_y) for coords in neighbor_coords: neighbor = self.cells[coords] cell.add_neighbor(neighbor) def populate_cell(self, cell, value)- 
Populate cell with a left or right-moving individual if it is empty. Return True if successful, else False.
Args
cell:Cell- Cell to populate.
 value:int- Decides if cell is populated by right-mover or left-mover.
 
Expand source code
def populate_cell(self, cell, value): """ Populate cell with a left or right-moving individual if it is empty. Return True if successful, else False. Args: cell (Cell): Cell to populate. value (int): Decides if cell is populated by right-mover or left-mover. """ assert isinstance(value, int) and value in [-1, 1] if not cell.is_empty(): return False cell.populate(value) return True def populate_corridor(self, N)- 
Randomly populate lattice with left-moving and right-moving agents.
Args
N:int- Number of cells to populate.
 
Expand source code
def populate_corridor(self, N): """ Randomly populate lattice with left-moving and right-moving agents. Args: N (int): Number of cells to populate. """ assert N <= self.cells.size, 'Number of people is larger than number of cells' for _ in range(N): value = 1 if random.random() < 0.5 else -1 self.populate_random_cell(value) def populate_random_cell(self, value)- 
Populate a random cell and return it.
Args
value:int- Decides if cell is populated by right-mover or left-mover.
 
Expand source code
def populate_random_cell(self, value): """ Populate a random cell and return it. Args: value (int): Decides if cell is populated by right-mover or left-mover. """ assert isinstance(value, int) and value in [-1, 1], 'value must be +/- 1 integer' cell = self.get_random_cell() while not self.populate_cell(cell, value): cell = self.get_random_cell() return cell