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)