Growing the agents
A lot of this stuff hinges on having the right initial data. We cant run the agents at this moment. I Added the stuff here that was already in workshop 4, but like I said, it will not run.
Personally, I believe that we have to store the eventual data of every script in a csv file. This script needs a program of requirements.
Load Libraries
import os
import topogenesis as tg
import pyvista as pv
import trimesh as tm
import pandas as pd
import numpy as np
import functions
np.random.seed(0)
Define Stencil
# creating neighborhood definition
stencil = tg.create_stencil("von_neumann", 1, 1)
# setting the center to zero
stencil.set_index([0,0,0], 0)
print(stencil)
Define the environment
# loading the lattice from csv
lattice_path = os.path.relpath('../data/meshes/useable_lattice.csv')
avail_lattice = tg.lattice_from_csv(lattice_path)
init_avail_lattice = tg.to_lattice(np.copy(avail_lattice), avail_lattice)
Load program of requirements
#loading the relative relations excell
relative_rels = pd.read_excel('../data/relationships/relative_relations.xlsx')
#relative_rels = relative_rels.iloc[:2,:3]
#loading the relative preferences excell
relative_prefs = pd.read_excel('../data/relationships/relative_preferences.xlsx')
functions.normalize_excel(relative_prefs)
#relative_prefs = relative_prefs.head(2)
# read agent size
agent_sizes = pd.read_excel('../data/relationships/agent_size.xlsx')
#agent_sizes = agent_sizes.head(2)
program_prefs = relative_prefs.drop(["space_name", "street_sight"], 1)
program_rels = relative_rels.drop(["Unnamed: 0"], 1)
Load the value fields
# loading the lattice from csv
fields = {}
for f in program_prefs.columns:
lattice_path = os.path.relpath('../data/fields/' + f + '.csv')
fields[f] = tg.lattice_from_csv(lattice_path) * avail_lattice
Initialize the agent's seeds
agent_pref_fields=[]
for i in range(0,len(program_prefs)):
int_d=1
for f in program_prefs:
t=fields[f]**program_prefs[f][i]
int_d=int_d*t
agent_pref_fields.append(int_d)
Initialize the agents
# initialize the occupation lattice
occ_lattice = avail_lattice * 0 - 1
# Finding the index of the available voxels in avail_lattice
avail_flat = avail_lattice.flatten()
avail_index = np.array(np.where(avail_lattice == 1)).T
# Randomly choosing three available voxels
# agn_num = len(program_prefs)
# np.random.seed()
# select_id = np.random.choice(len(avail_index), agn_num)
# agn_origins = avail_index[select_id]
agn_num = len(program_prefs)
agn_origins=[]
for i in range(0,agn_num):
best_index = np.array(np.where(agent_pref_fields[i] == agent_pref_fields[i].max())).T
best_index=best_index[0]
agn_origins.append(best_index)
agn_origins=np.stack(agn_origins)
# adding the origins to the agents locations
agn_locs = []
agn_vals = []
# for each agent origin ...
for a_id, a_origin in enumerate(agn_origins):
# add the origin to the list of agent locations
agn_locs.append([a_origin])
# set the origin in availablity lattice as 0 (UNavailable)
avail_lattice[tuple(a_origin)] = 0
# set the origin in occupation lattice as the agent id (a_id)
occ_lattice[tuple(a_origin)] = a_id
#initializing the agents' values
for a_id, a_prefs in program_prefs.iterrows():
agn_loc = agn_locs[a_id]
a_eval = 1
for f in program_prefs.columns:
# find the raw value of free neighbours...
val = fields[f][agn_loc[0][0], agn_loc[0][1], agn_loc[0][2]]
# raise the the raw value to the power of preference weight of the agent
a_weighted_vals = val ** a_prefs[f]
# multiply them to the previous weighted values
a_eval *= a_weighted_vals
agn_vals.append([a_eval])
Initialize the distance field
for a_id, a_prefs in program_prefs.iterrows():
# create a lattice with only the agent locations enabled
agn_lattice = functions.enabling_loc_in_lattice(agn_locs[a_id], avail_lattice)
# calculate distance field to the agent locations through breath-first traversal
distance_lattice = functions.breath_first_traversal(agn_lattice, avail_lattice)
# invert the field
fields[a_id] = 1 - functions.min_max_scaler(distance_lattice)
Show the agents
p = pv.Plotter(notebook=True)
# Set the grid dimensions: shape + 1 because we want to inject our values on the CELL data
grid = pv.UniformGrid()
grid.dimensions = np.array(occ_lattice.shape) + 1
# The bottom left corner of the data set
grid.origin = occ_lattice.minbound - occ_lattice.unit * 0.5
# These are the cell sizes along each axis
grid.spacing = occ_lattice.unit
# adding the boundingbox wireframe
p.add_mesh(grid.outline(), color="grey", label="Domain")
# adding axes
p.add_axes()
p.show_bounds(grid="back", location="back", color="#777777")
# Add the data values to the cell data
grid.cell_arrays["Agents"] = occ_lattice.flatten(order="F").astype(int) # Flatten the array!
# filtering the voxels
threshed = grid.threshold([-0.1, agn_num - 0.9])
# adding the voxels
p.add_mesh(threshed, show_edges=True, opacity=1.0, show_scalar_bar=False)
# adding the availability lattice
#init_avail_lattice.fast_vis(p)
p.show(use_ipyvtk=True)
Run the simulation
# make a deep copy of occupation lattice
cur_occ_lattice = tg.to_lattice(np.copy(occ_lattice), occ_lattice)
# initialzing the list of frames
frames = [cur_occ_lattice]
# setting the time variable to 0
t = 0
n_frames = 30
# main feedback loop of the simulation (for each time step ...)
while t<n_frames:
# for each agent ...
for a_id, a_prefs in program_prefs.iterrows():
# retrieve the list of the locations of the current agent
a_locs = agn_locs[a_id]
a_vals = agn_vals[a_id]
#get max building depth for the current agentS
max_depth = 3#agent_sizes.loc[a_id, 'voxel_depth']
max_height = 2
# initialize the list of free neighbours
free_neighs = []
agent_lattice = functions.enabling_loc_in_lattice(a_locs, avail_lattice)
# for each location of the agent
for loc in a_locs:
# retrieve the list of neighbours of the agent based on the stencil
neighs = avail_lattice.find_neighbours_masked(stencil, loc = loc)
#BUILDING DEPTH AND BUILDING HEIGHT LOGIC
#calculate index that the agent should avoid to achieve max building depth
index_to_avoid_building_depth = functions.index_to_avoid_for_building_depth(agent_lattice, max_depth)
#calculate index that the agent should avoid to achieve max building height
index_to_avoid_building_height = functions.index_to_avoid_for_building_height(agent_lattice, max_height)
# for each neighbour ...
for n in neighs:
# compute 3D index of neighbour
neigh_3d_id = np.unravel_index(n, avail_lattice.shape)
# if the neighbour is available and avoidance logic to achieve max building depth
if avail_lattice[neigh_3d_id] and functions.array_element_not_in_2d_array(neigh_3d_id, index_to_avoid_building_depth) and functions.array_element_not_in_2d_array(neigh_3d_id, index_to_avoid_building_height):
# add the neighbour to the list of free neighbours
free_neighs.append(neigh_3d_id)
# check if found any free neighbour
if len(free_neighs)>0:
# convert free neighbours to a numpy array
fns = np.array(free_neighs)
# find the value of neighbours
# init the agent value array
a_eval = np.ones(len(fns))
# for each field...
for f in program_prefs.columns:
# find the raw value of free neighbours...
vals = fields[f][fns[:,0], fns[:,1], fns[:,2]]
# raise the the raw value to the power of preference weight of the agent
a_weighted_vals = vals ** a_prefs[f]
# multiply them to the previous weighted values
a_eval *= a_weighted_vals
#post-processing
functions.squareness(square_weight=0.2, free_neighs=free_neighs, a_eval=a_eval)
#evaluating agent connectiveness
for r in range(program_rels.columns.size):
vals = fields[r][fns[:,0], fns[:,1], fns[:,2]]
a_weighted_vals = vals ** program_rels.iloc[a_id][r]
a_eval *= a_weighted_vals
# if the agents have reached their max size -> remove the worst voxel
if len(a_locs) >= agent_sizes.loc[a_id, 'voxel_total'] and np.max(a_eval) > np.min(a_vals):
smallest_voxel_val_id = np.argmin(a_vals)
voxel_to_be_replaced_id = tuple(a_locs[smallest_voxel_val_id])
#remove the worst voxel
agn_locs[a_id].pop(smallest_voxel_val_id)
agn_vals[a_id].pop(smallest_voxel_val_id)
#voxel that is removed will be available and unoccupied
avail_lattice[voxel_to_be_replaced_id] = 1
occ_lattice[voxel_to_be_replaced_id] = -1
# re-evaluate the agent size (since the worst voxel may have been removed in the last condition check)
# if it is smaller then max size -> add best voxel
if len(a_locs) < agent_sizes.loc[a_id, 'voxel_total']:
# select the neighbour with highest evaluation
selected_int = np.argmax(a_eval)
# find 3D integer index of selected neighbour
selected_neigh_3d_id = free_neighs[selected_int]
# find the location of the newly selected neighbour
selected_neigh_loc = np.array(selected_neigh_3d_id).flatten()
# add the newly selected neighbour location to agent locations
agn_locs[a_id].append(selected_neigh_loc)
# add the newly best value selected
agn_vals[a_id].append(a_eval[selected_int])
# set the newly selected neighbour as UNavailable (0) in the availability lattice
avail_lattice[selected_neigh_3d_id] = 0
# set the newly selected neighbour as OCCUPIED by current agent
# (-1 means not-occupied so a_id)
occ_lattice[selected_neigh_3d_id] = a_id
# updating the distance field for agent connectiveness
# lattice with only the current agent location enabled
agn_lattice = functions.enabling_loc_in_lattice(agn_locs[a_id], avail_lattice)
# calculate new distance lattice
distance_lattice = functions.breath_first_traversal(agn_lattice, avail_lattice)
# invert the distance
fields[a_id] = 1 - functions.min_max_scaler(distance_lattice)
# constructing the new lattice
new_occ_lattice = tg.to_lattice(np.copy(occ_lattice), occ_lattice)
# adding the new lattice to the list of frames
frames.append(new_occ_lattice)
# adding one to the time counter
t += 1
Visualize the result
p = pv.Plotter(notebook=True)
base_lattice = frames[0]
# Set the grid dimensions: shape + 1 because we want to inject our values on the CELL data
grid = pv.UniformGrid()
grid.dimensions = np.array(base_lattice.shape) + 1
# The bottom left corner of the data set
grid.origin = base_lattice.minbound - base_lattice.unit * 0.5
# These are the cell sizes along each axis
grid.spacing = base_lattice.unit
# adding the boundingbox wireframe
p.add_mesh(grid.outline(), color="grey", label="Domain")
# adding the availability lattice
#init_avail_lattice.fast_vis(p)
# adding axes
p.add_axes()
p.show_bounds(grid="back", location="back", color="#aaaaaa")
def create_mesh(value):
f = int(value)
lattice = frames[f]
# Add the data values to the cell data
grid.cell_arrays["Agents"] = lattice.flatten(order="F").astype(int) # Flatten the array!
# filtering the voxels
threshed = grid.threshold([-0.1, agn_num - 0.9])
# adding the voxels
p.add_mesh(threshed, name='sphere', show_edges=True, opacity=1.0, show_scalar_bar=False)
return
p.add_slider_widget(create_mesh, [0, n_frames], title='Time', value=0, event_type="always", style="classic")
p.show(use_ipyvtk=True)
Save frames to csv
for i, lattice in enumerate(frames):
csv_path = os.path.relpath('../data/abm_animation/abm_f_'+ f'{i:03}' + '.csv')
lattice.to_csv(csv_path)