def force_as_dxdy(pixel_a: Pixel_xy, pixel_b: Pixel_xy, screen_distance_unit, repulsive): """ Compute the force between pixel_a pixel and pixel_b and return it as a velocity: direction * force. """ direction: Velocity = normalize_dxdy(( pixel_a - pixel_b) if repulsive else (pixel_b - pixel_a)) d = max(1, pixel_a.distance_to(pixel_b)) #, wrap=False)) if repulsive: dist = max(1, pixel_a.distance_to(pixel_b) / screen_distance_unit) #, wrap=False) rep_coefficient = gui_get(REP_COEFF) rep_exponent = gui_get(REP_EXPONENT) force = direction * ( (10**rep_coefficient) / 10) * dist**rep_exponent return force else: # attraction dist = max(1, max(d, screen_distance_unit) / screen_distance_unit) att_exponent = gui_get(ATT_EXPONENT) force = direction * dist**att_exponent # If the link is too short, push away instead of attracting. if d < screen_distance_unit: force = force * (-1) att_coefficient = gui_get(ATT_COEFF) final_force = force * 10**(att_coefficient - 1) return final_force
def force_as_dxdy(pixel_a: Pixel_xy, pixel_b: Pixel_xy, screen_distance_unit, repulsive): """ Compute the force between pixel_a pixel and pixel_b and return it as a velocity: direction * force. """ direction: Velocity = normalize_dxdy(( pixel_a - pixel_b) if repulsive else (pixel_b - pixel_a)) d = pixel_a.distance_to(pixel_b, wrap=False) if repulsive: dist = max( 1, pixel_a.distance_to(pixel_b, wrap=False) / screen_distance_unit) rep_coefficient = SimEngine.gui_get('rep_coef') rep_exponent = SimEngine.gui_get('rep_exponent') force = direction * (10**rep_coefficient) / 10 * dist**rep_exponent return force else: # attraction dist = max(1, max(d, screen_distance_unit) / screen_distance_unit) att_exponent = SimEngine.gui_get('att_exponent') force = direction * dist**att_exponent # If the link is too short, push away instead of attracting. if d < screen_distance_unit: force = force * (-1) att_coefficient = SimEngine.gui_get('att_coef') return 10**(att_coefficient - 1) * force
def vertical_linked_nodes(LinkType, x, y_top, length=None): node_1 = Braess_Node(Pixel_xy((x, y_top))) if length is None: length = Braess_World.dist_unit if LinkType == Braess_Cord: length /= 2 node_2 = Braess_Node(Pixel_xy((x, y_top + length))) link_construct = LinkType(node_1, node_2) return link_construct
def draw_label(self): offset = Block.patch_text_offset if isinstance(self, Patch) else Block.agent_text_offset text_center = Pixel_xy((self.rect.x + offset, self.rect.y + offset)) line_color = None if offset == 0 else \ Color('white') if isinstance(self, Patch) and self.color == Color('black') else self.color obj_center = self.rect.center gui.draw_label(self.label, text_center, obj_center, line_color)
def pixel_xy_to_patch(pixel_xy: Pixel_xy) -> Patch: """ Get the patch RowCol for this pixel """ row_col: RowCol = pixel_xy.pixel_to_row_col() patch = World.patches_array[row_col.row, row_col.col] return patch
def move_agent(self, delta: Velocity): Agent.some_agent_changed = True (capped_x, capped_y) = delta.cap_abs_value(1) # Note that x and y have been defined to be getters for center pixels (x, y). new_center_pixel = Pixel_xy((self.x + capped_x, self.y + capped_y)) self.move_to_xy(new_center_pixel)
def extend_linked_nodes(self, LinkType): node_1 = self.node_2 length = Braess_World.dist_unit if LinkType == Braess_Cord: length /= 2 node_2 = Braess_Node(Pixel_xy((node_1.x, node_1.y + length))) link_construct = LinkType(node_1, node_2) return link_construct
def __init__(self, node_1: Braess_Node, node_2: Braess_Node, **kwargs): super().__init__(node_1, node_2, **kwargs) # This is the resting length. All springs have this as their default length. self.resting_length = Braess_World.dist_unit if not (isinstance(self, Braess_Bar) or isinstance(self, Braess_Cord)): self.node_2.move_to_xy(Pixel_xy((self.node_2.x, self.node_1.y + self.resting_length))) self.color = Color('green') self.width = 2
def create_random_agent(self, color=None, shape_name='netlogo_figure', scale=1.4): """ Create an Agent placed randomly on the screen. Set it to face the screen's center pixel. """ agent = self.agent_class(color=color, shape_name=shape_name, scale=scale) agent.move_to_xy(Pixel_xy.random_pixel()) agent.face_xy(center_pixel()) return agent
def __init__(self, center_pixel: Pixel_xy, color=Color('black')): super().__init__() self.center_pixel = center_pixel self.rect = Rect((0, 0), (gui.PATCH_SIZE, gui.PATCH_SIZE)) # noinspection PyTypeChecker sum_pixel: Pixel_xy = center_pixel + Pixel_xy((1, 1)) self.rect.center = sum_pixel self.image = Surface((self.rect.w, self.rect.h)) self.color = self.base_color = color self.label = None
def __init__(self, node_1: Braess_Node, node_2: Braess_Node, **kwargs): # The link must be directed because we depend on the order of the nodes. # Typically node_1 is fixed and node_2 moves. super().__init__(node_1, node_2, directed=True, **kwargs) # This is the resting length. All springs have this as their default length. self.resting_length = Braess_World.dist_unit if not (isinstance(self, Braess_Bar) or isinstance(self, Braess_Cord)): self.node_2.move_to_xy(Pixel_xy((self.node_2.x, self.node_1.y + self.resting_length))) self.color = Color('green') self.width = 2
def mouse_click(self, xy: Tuple[int, int]): """ Toggle clicked patch's aliveness. """ patch = self.pixel_tuple_to_patch(xy) if len(patch.agents) == 1: node = choice(list(patch.agents)) else: patches = patch.neighbors_24() nodes = {node for patch in patches for node in patch.agents} node = nodes.pop() if nodes else Pixel_xy(xy).closest_block( World.agents) if node: node.highlighted = not node.highlighted
def mouse_click(self, xy: Tuple[int, int]): """ Select closest node. """ patch = self.pixel_tuple_to_patch(xy) if len(patch.agents) == 1: node = sample(patch.agents, 1)[0] else: patches = patch.neighbors_24() nodes = {node for patch in patches for node in patch.agents} node = nodes.pop() if nodes else Pixel_xy(xy).closest_block( World.agents) if node: node.selected = not node.selected
def draw_label(self): text = gui.FONT.render(self.label, True, Color('black'), Color('white')) offset = Block.patch_text_offset if isinstance( self, Patch) else Block.agent_text_offset text_center = Pixel_xy((self.rect.x + offset, self.rect.y + offset)) # gui.SCREEN.blit(text, text_center) gui.blit(text, text_center) line_color = Color('white') if isinstance( self, Patch) and self.color == Color('black') else self.color # self.draw_line(line_color=line_color, start_pixel=self.rect.center, end_pixel=text_center) gui.draw_line(start_pixel=self.rect.center, end_pixel=text_center, line_color=line_color)
def create_random_agents(self, n, shape_name='netlogo_figure', color=None, scale=1.4): """ Create n Agents placed randomly on the screen. They are all facing the screen's center pixel. """ for _ in range(n): agent = self.agent_class(color=color, shape_name=shape_name, scale=scale) agent.move_to_xy(Pixel_xy.random_pixel()) agent.face_xy(center_pixel())
def __init__(self, center_pixel: Pixel_xy, color=Color('black')): super().__init__() self.center_pixel = center_pixel self.rect = Rect((0, 0), (gui.PATCH_SIZE, gui.PATCH_SIZE)) # noinspection PyTypeChecker sum_pixel: Pixel_xy = center_pixel + Pixel_xy((1, 1)) assert isinstance(sum_pixel, Pixel_xy) self.rect.center = sum_pixel # .as_tuple() self.image = Surface((self.rect.w, self.rect.h)) self.color = self.base_color = color self.label = None self.font = Font(None, int(1.5 * gui.BLOCK_SPACING())) self.agent_text_offset = int(1.5 * gui.PATCH_SIZE) self.patch_text_offset = -int(1.0 * gui.PATCH_SIZE)
def compute_velocity(self, screen_distance_unit, velocity_adjustment): repulsive_force: Velocity = Velocity((0, 0)) for node in (World.agents - {self}): repulsive_force += self.force_as_dxdy(self.center_pixel, node.center_pixel, screen_distance_unit, repulsive=True) # Also consider repulsive force from walls. repulsive_wall_force: Velocity = Velocity((0, 0)) horizontal_walls = [ Pixel_xy((0, 0)), Pixel_xy((SCREEN_PIXEL_WIDTH(), 0)) ] x_pixel = Pixel_xy((self.center_pixel.x, 0)) for h_wall_pixel in horizontal_walls: repulsive_wall_force += self.force_as_dxdy(x_pixel, h_wall_pixel, screen_distance_unit, repulsive=True) vertical_walls = [ Pixel_xy((0, 0)), Pixel_xy((0, SCREEN_PIXEL_HEIGHT())) ] y_pixel = Pixel_xy((0, self.center_pixel.y)) for v_wall_pixel in vertical_walls: repulsive_wall_force += self.force_as_dxdy(y_pixel, v_wall_pixel, screen_distance_unit, repulsive=True) attractive_force: Velocity = Velocity((0, 0)) for node in (World.agents - {self}): if link_exists(self, node): attractive_force += self.force_as_dxdy(self.center_pixel, node.center_pixel, screen_distance_unit, repulsive=False) # noinspection PyTypeChecker net_force: Velocity = repulsive_force + repulsive_wall_force + attractive_force normalized_force: Velocity = net_force / max( [net_force.x, net_force.y, velocity_adjustment]) normalized_force *= 10 if gui_get(PRINT_FORCE_VALUES): print(f'{self}. \n' f'rep-force {tuple(repulsive_force.round(2))}; \n' f'rep-wall-force {tuple(repulsive_wall_force.round(2))}; \n' f'att-force {tuple(attractive_force.round(2))}; \n' f'net-force {tuple(net_force.round(2))}; \n' f'normalized_force {tuple(normalized_force.round(2))}; \n\n') return normalized_force
def adjust_distances(self, velocity_adjustment): #get from gui what the base distance units dist_unit = SimEngine.gui_get(('dist_unit')) #define how many distance units exist per screen screen_distance_unit = sqrt(SCREEN_PIXEL_WIDTH()**2 + SCREEN_PIXEL_HEIGHT()**2) / dist_unit #define initial Veolocity as (0,0), forces act in the x and y directions repulsive_force: Velocity = Velocity((0, 0)) #for all other agents excluding this instance for agent in (World.agents - {self}): #add their respective influence of each other element towards this element, calculate using #this center pixel, elements center pixel and using defined units (optional repulsove flag) repulsive_force += self.force_as_dxdy(self.center_pixel, agent.center_pixel, screen_distance_unit, repulsive=True) # Also consider repulsive force from walls. repulsive_wall_force: Velocity = Velocity((0, 0)) horizontal_walls = [ Pixel_xy((0, 0)), Pixel_xy((SCREEN_PIXEL_WIDTH(), 0)) ] x_pixel = Pixel_xy((self.center_pixel.x, 0)) for h_wall_pixel in horizontal_walls: repulsive_wall_force += self.force_as_dxdy(x_pixel, h_wall_pixel, screen_distance_unit, repulsive=True) vertical_walls = [ Pixel_xy((0, 0)), Pixel_xy((0, SCREEN_PIXEL_HEIGHT())) ] y_pixel = Pixel_xy((0, self.center_pixel.y)) for v_wall_pixel in vertical_walls: repulsive_wall_force += self.force_as_dxdy(y_pixel, v_wall_pixel, screen_distance_unit, repulsive=True) #calculate the attractive force generated by all the nodes connected by a link attractive_force: Velocity = Velocity((0, 0)) for agent in (World.agents - {self}): if link_exists(self, agent): attractive_force += self.force_as_dxdy(self.center_pixel, agent.center_pixel, screen_distance_unit, repulsive=False) #find the final force, find out what velocity adjustment is net_force = repulsive_force + repulsive_wall_force + attractive_force normalized_force: Velocity = net_force / max( [net_force.x, net_force.y, velocity_adjustment]) normalized_force *= 10 #print force values if selected if SimEngine.gui_get('Print force values'): print(f'{self}. \n' f'rep-force {tuple(repulsive_force.round(2))}; \n' f'rep-wall-force {tuple(repulsive_wall_force.round(2))}; \n' f'att-force {tuple(attractive_force.round(2))}; \n' f'net-force {tuple(net_force.round(2))}; \n' f'normalized_force {tuple(normalized_force.round(2))}; \n\n') #set the velocity of this object self.set_velocity(normalized_force) #take a step self.forward()
def set_center_pixel(self, xy: Pixel_xy): self.center_pixel: Pixel_xy = xy.wrap() # Set the center point of this agent's rectangle. self.rect.center = (self.center_pixel - Agent.half_patch_pixel).round()
def adjust_distances(self, velocity_adjustment): dist_unit = 8 screen_distance_unit = sqrt(SCREEN_PIXEL_WIDTH()**2 + SCREEN_PIXEL_HEIGHT()**2) / dist_unit repulsive_force: Velocity = Velocity((0, 0)) for agent in (World.agents - {self}): repulsive_force += self.force_as_dxdy(self.center_pixel, agent.center_pixel, screen_distance_unit, repulsive=True) # Also consider repulsive force from walls. repulsive_wall_force: Velocity = Velocity((0, 0)) horizontal_walls = [ Pixel_xy((0, 0)), Pixel_xy((SCREEN_PIXEL_WIDTH(), 0)) ] x_pixel = Pixel_xy((self.center_pixel.x, 0)) for h_wall_pixel in horizontal_walls: repulsive_wall_force += self.force_as_dxdy(x_pixel, h_wall_pixel, screen_distance_unit, repulsive=True) vertical_walls = [ Pixel_xy((0, 0)), Pixel_xy((0, SCREEN_PIXEL_HEIGHT())) ] y_pixel = Pixel_xy((0, self.center_pixel.y)) for v_wall_pixel in vertical_walls: repulsive_wall_force += self.force_as_dxdy(y_pixel, v_wall_pixel, screen_distance_unit, repulsive=True) attractive_force: Velocity = Velocity((0, 0)) for agent in (World.agents - {self}): if link_exists(self, agent): attractive_force += self.force_as_dxdy(self.center_pixel, agent.center_pixel, screen_distance_unit, repulsive=False) net_force = repulsive_force + repulsive_wall_force + attractive_force normalized_force: Velocity = net_force / max( [net_force.x, net_force.y, velocity_adjustment]) normalized_force *= 10 if SimEngine.gui_get('Print force values'): print(f'{self}. \n' f'rep-force {tuple(repulsive_force.round(2))}; \n' f'rep-wall-force {tuple(repulsive_wall_force.round(2))}; \n' f'att-force {tuple(attractive_force.round(2))}; \n' f'net-force {tuple(net_force.round(2))}; \n' f'normalized_force {tuple(normalized_force.round(2))}; \n\n') self.set_velocity(normalized_force) self.forward()
def pixel_tuple_to_patch(self, xy: Tuple[int, int]): """ Get the patch RowCol for this pixel """ return self.pixel_xy_to_patch(Pixel_xy(xy))
def setup_a(self): """ Set up for the drop weight animation. """ # Move out by a small amount so that the two lines can be seen. step = 5 if SimEngine.gui_get('Pause?') else 0 self.top_spring.move_by_dxdy((step, 0)) self.top_spring.set_target_by_dxdy((self.x_offset-step, 0)) self.weight_cord.set_target_by_dxdy((0, Braess_World.cord_slack)) center_bar_node = self.bottom_spring.node_2 # Construct the full bar. bar_y = center_bar_node.y left_bar_node = Braess_Node(Pixel_xy( (self.x, bar_y) ) ) right_bar_node = Braess_Node(Pixel_xy( (self.x, bar_y) ) ) # By convention, the position of node_1 determines node_2's position. # In this case, since we will be pulling the bar up by its ends, # make the ends the controlling nodes. self.bar_right = Braess_Bar(right_bar_node, center_bar_node) self.bar_left = Braess_Bar(left_bar_node, center_bar_node) # Attach the bottom spring to the left end of the bar. self.bottom_spring.node_2 = left_bar_node left_bar_node.move_by_dxdy(Velocity((-step, 0))) right_bar_node.move_by_dxdy(Velocity((step, 0))) left_bar_node.set_target_by_dxdy(Velocity((-self.x_offset+step, Braess_World.cord_slack))) right_bar_node.set_target_by_dxdy(Velocity((self.x_offset-step, Braess_World.cord_slack))) # Attach the top cord to the right end of the bar rather than the center. self.top_cord.node_2 = right_bar_node # The left cord is a new element in state 2. It is offset to the left. x_coord = self.x cord_length = self.bottom_spring.node_1.y - Braess_World.top self.left_cord = Braess_Link.vertical_linked_nodes(Braess_Cord, x_coord, Braess_World.top, length=cord_length) # The left cord is offset to the left. self.left_cord.move_by_dxdy((-step, 0)) self.left_cord.node_1.set_target_by_dxdy(Velocity((-self.x_offset + step, 0))) self.left_cord.node_2.set_target_by_dxdy(Velocity((-self.x_offset + step, Braess_World.cord_slack))) self.left_cord.color = Color('yellow2') self.top_cord.color = Color('yellow2') # Make the left_cord's bottom node the top node of the bottom spring. World.agents.remove(self.bottom_spring.node_1) self.bottom_spring.node_1 = self.left_cord.node_2 # Add the new cord and bars to the adjustable links. self.adjustable_links.extend([self.left_cord, self.bar_right, self.bar_left]) Agent.key_step_done = False # ## Done with the setup for the animation. ## # SimEngine.gui_set(Braess_World.CUT_CORD, enabled=False) SimEngine.gui_set(GO_ONCE, enabled=True) SimEngine.gui_set(GOSTOP, enabled=True) if SimEngine.gui_get('Slow?'): SimEngine.fps = 15 if not SimEngine.gui_get('Pause?'): gui.WINDOW['GoStop'].click() Braess_World.state = 'a'
def __init__(self): center_pixel = Pixel_xy( (uniform(0, SCREEN_PIXEL_WIDTH()), uniform(0, SCREEN_PIXEL_HEIGHT()))) color = utils.color_random_variation(Color('yellow')) super().__init__(center_pixel=center_pixel, color=color, scale=1)
def adjust_nodes(self): """ Move node_2 up or down to match node_1. """ if self.node_1.y != self.node_2.y: self.node_2.move_to_xy(Pixel_xy((self.node_2.x, self.node_1.y)))