Skip to content

Step 7 Shafts and corridors

Pseudocode

import packages such as kmeans, numpy, networkx...
import lattice

create stencil

# calculate cluster centers

for each agent:
    agent_locations = np.where
    number_clusters = len(agent_locations / 100 +1)
    kmeans = kmeans(nnumber_clusters)
    cluster_centers.append()

# make shafts

again, kmeans = kmeans(n_clusters=5)
for cluster:
    shaft_lattice = 1

# make adjacency matrix

for all voxels:
    voxel_neighbours = avail_latt.find_neighbours_masked(stencil)
    for each neighbour:
        if avail_latt[neighbour] == 1:
            adjacency_list.append()

Make connectivity graph

# corridor growth

for all clusters:
    slice horizontally
    for each shaft:
        find shortest path to cluster with connectivity graph

for each level:
    find the shafts
    for each shaft:
        construct destination and source
    for each destination:
        find shortest path to a source(shaft) with connectivity graph



</div>
</div>
</div>
<div class="cell border-box-sizing text_cell rendered" markdown="1">
<div class="inner_cell" markdown="1">
<div class="text_cell_render border-box-sizing rendered_html" markdown="1">
#### Load required libraries

</div>
</div>
</div>
<div class="cell border-box-sizing code_cell rendered" markdown="1">
<div class="input">

```python
import os
import topogenesis as tg
import pyvista as pv
import trimesh as tm
import numpy as np
import networkx as nx
import pandas as pd
import scipy as sp
from scipy.sparse import csr_matrix
import pickle
from sklearn.cluster import KMeans
np.random.seed(0)
import copy

Loading the lattice

# extra import function
def lattice_from_csv(file_path):
    # read metadata
    meta_df = pd.read_csv(file_path, nrows=3)

    shape = np.array(meta_df['shape'])
    unit = np.array(meta_df['unit'])
    minbound = np.array(meta_df['minbound'])

    # read lattice
    lattice_df = pd.read_csv(file_path, skiprows=5)

    # create the buffer
    buffer = np.array(lattice_df['value']).reshape(shape)

    # create the lattice
    l = tg.to_lattice(buffer, minbound=minbound, unit=unit)

    return l
last_frame = lattice_from_csv('../Data/dynamic output/abm_animation/abm_f_1500.csv')
# loading the lattice from csv
lattice_path = os.path.relpath('../Data/dynamic output/voxelized_envelope_cut.csv')
avail_lattice = tg.lattice_from_csv(lattice_path)
init_avail_lattice = tg.to_lattice(np.copy(avail_lattice), avail_lattice)
# creating neighborhood definition
stencil = tg.create_stencil("von_neumann", 1, 1)
# setting the center to zero
stencil.set_index([0,0,0], 0)

# creating neighborhood definition
stencil_flat = tg.create_stencil("von_neumann", 1, 1)
# setting the center to zero
stencil_flat.set_index([0,0,0], 0)
stencil_flat.set_index([0,0,1], 0)
stencil_flat.set_index([0,0,-1], 0)
print(stencil_flat)
avail_index_bool = np.array((last_frame >= 0).tolist()) # making a new availability boolean lattice

#lattice creation
avail_lattice_bool = tg.to_lattice(avail_index_bool.reshape(avail_lattice.shape),avail_lattice)
# convert mesh to pv_mesh
def tri_to_pv(tri_mesh):
    faces = np.pad(tri_mesh.faces, ((0, 0),(1,0)), 'constant', constant_values=3)
    pv_mesh = pv.PolyData(tri_mesh.vertices, faces)
    return pv_mesh

# Visualize the mesh using pyvista plotter

# initiating the plotter
p = pv.Plotter(notebook=True)

# fast visualization of the lattice
avail_lattice_bool.fast_vis(p)

# plotting
p.window_size = 1000, 1000
# p.screenshot("corridors_1")    
p.show(use_ipyvtk=True)

Calculate cluster centers

a = np.array(np.where(last_frame == 1)).T
print(a)
print(len(a))
nclusters = int(len(a) / 100 + 1)
#kmeans_model = KMeans(n_clusters= nclusters, random_state=0).fit(a)
#print(kmeans_model)
cluster_centers = []
for func_id in range(0, int(np.max(last_frame)) + 1):
    if not func_id in [1]: #taking out a bug in one of the agents
        function_locs = np.array(np.where(last_frame == func_id)).T

        # for every multiple of 100 voxels a cluster center is created and at least 1 per agent
        nclusters = int(len(function_locs) / 100 + 1)

        kmeans_model = KMeans(n_clusters= nclusters, random_state=0).fit(function_locs)
        cluster_centers.append(np.round(kmeans_model.cluster_centers_).astype(np.int8))

# making sure it's no longer a nested cluster center list
cluster_center_list = []
for i in range(len(cluster_centers)):
    cluster_center_list.append(cluster_centers[i][0])

#creating a lattice that shows all cluster centers
cluster_center_lattice = avail_lattice_bool * 0
for cluster_center in cluster_center_list:
    cluster_center_lattice[cluster_center[0], cluster_center[1], cluster_center[2]] = 1

Visualizing cluster

p = pv.Plotter(notebook=True)

base_lattice = cluster_center_lattice

# 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 avilability lattice
avail_lattice_bool.fast_vis(p)

# Add the data values to the cell data
grid.cell_arrays["Agents"] = base_lattice.flatten(order="F").astype(int)  # Flatten the array!
# filtering the voxels
threshed = grid.threshold([0.9, 1.1])
# adding the voxels
p.add_mesh(threshed, name='sphere', show_edges=True, opacity=1.0, show_scalar_bar=False, color = [1,1,0])

p.window_size = 1000, 1000
p.screenshot("clusters")    
p.show(use_ipyvtk=True)

Make shafts

kmeans_model = KMeans(n_clusters=5, random_state=0).fit(cluster_center_list) # select how many shafts
cluster_centers_total = np.round(kmeans_model.cluster_centers_).astype(np.int8)

# init shaft lattice
shft_lattice = avail_lattice_bool * 0
# setting the shafts to 1
for cl_cen in cluster_centers_total:
    shft_lattice[cl_cen[0],cl_cen[1],:] = 1

Visualize

p = pv.Plotter(notebook=True)

base_lattice = shft_lattice * avail_lattice_bool

# 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 avilability lattice
avail_lattice_bool.fast_vis(p)

# Add the data values to the cell data
grid.cell_arrays["Agents"] = base_lattice.flatten(order="F").astype(int)  # Flatten the array!
# filtering the voxels
threshed = grid.threshold([0.9, 1.1])
# adding the voxels
p.add_mesh(threshed, name='sphere', show_edges=True, opacity=1.0, show_scalar_bar=False)

p.window_size = 1000, 1000
p.screenshot("shafts")    
p.show(use_ipyvtk=True)

Make adjacency matrix

avail_lattice_flat = avail_lattice_bool.flatten()

# find the number of all voxels
vox_count = avail_lattice_bool.size 

# initialize the adjacency matrix
adj_list = []

# Finding the index of the available voxels in avail_lattice
avail_index = np.array(np.where(avail_lattice_bool == 1)).T

# fill the adjacency matrix using the list of all neighbours
for vox_loc in avail_index:
    # find the 1D id
    vox_id = np.ravel_multi_index(vox_loc, avail_lattice_bool.shape)

    # retrieve the list of neighbours of the voxel based on the stencil
    vox_neighs = init_avail_lattice.find_neighbours_masked(stencil, loc = vox_loc)

   # iterating over the neighbours
    for neigh in vox_neighs:
        if avail_lattice_flat[neigh] == 1:
            adj_list.append([1.0, vox_id, neigh])

#list to array
adj_array = np.array(adj_list).T

#array to sparce matrix
adj_matrix_sparse =  csr_matrix((adj_array[0], (adj_array[1], adj_array[2])), shape=(vox_count,vox_count))

#sparce matrix to nx connectivity graph
g = nx.from_scipy_sparse_matrix(adj_matrix_sparse)

Corridor growth

occ_ind = cluster_center_list

# initialize corridor lattice
cor_lattice = shft_lattice * 0
cor_flat = cor_lattice.flatten()
# for each voxel that needs to have access to shafts
for a_vox in occ_ind:

    # slice the corridor lattice horizontally
    cor_floor = shft_lattice[:,:,a_vox[2]]
    # find the vertical shaft voxel indices
    shaft_vox_inds = np.array(np.where(cor_floor > 0)).T
    paths = []
    path_lens = []
    for shft_ind in shaft_vox_inds:
        # construct the destination address
        dst_vox = np.array([shft_ind[0],shft_ind[1],a_vox[2]])
        # construct 1-dimensional indices
        src_ind = np.ravel_multi_index(a_vox, shft_lattice.shape)
        dst_ind = np.ravel_multi_index(dst_vox, shft_lattice.shape)        
        # find the shortest path
        if nx.algorithms.shortest_paths.generic.has_path(g, src_ind, dst_ind):
            path = nx.algorithms.shortest_paths.astar.astar_path(g, src_ind, dst_ind)
            paths.append(path)
            path_lens.append(len(path))

    # find the number of shortest path
    for shortest_path_index in np.array(path_lens).argsort()[:1]: #select how many paths to connect to closest shafts
        cor_flat[paths[shortest_path_index]] = 1

#set the floor level the shafts need to be connected
connected_floor_levels = [2]

for level in connected_floor_levels:
    cur_floor_level = shft_lattice[:,:,level] #finding floor level
    shaft_vox_inds = np.array(np.where(cur_floor_level > 0)).T #finding shafts
    dst_ind_list = []
    source_ind_list = []
    for shft_ind in shaft_vox_inds:
        # construct the destination address
        dst_vox = np.array([shft_ind[0],shft_ind[1],level])
        index = np.ravel_multi_index(dst_vox, shft_lattice.shape) #finding the 1D index
        #setting the indices as both src and dst
        dst_ind_list.append(index) 
        source_ind_list.append(index)

    for dst_ind in dst_ind_list:
        paths = []
        path_lens = []
        for source_ind in source_ind_list: #checking if path to itself
            if dst_ind == source_ind:
                continue
            if nx.algorithms.shortest_paths.generic.has_path(g, source_ind, dst_ind):
                path = nx.algorithms.shortest_paths.astar.astar_path(g, source_ind, dst_ind) #finding the closest paths
                paths.append(path)
                path_lens.append(len(path))

        # find the 2 shortest paths
        for shortest_path_index in np.array(path_lens).argsort()[:2]: # selecting how many paths to connect to closests shafts
            cor_flat[paths[shortest_path_index]] = 1       

# reshape the flat lattice
cor_lattice = cor_flat.reshape(cor_lattice.shape)

Visualize

p = pv.Plotter(notebook=True)

base_lattice = (shft_lattice + cor_lattice) * avail_lattice_bool

# 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 avilability lattice
avail_lattice_bool.fast_vis(p)

# Add the data values to the cell data
grid.cell_arrays["Agents"] = base_lattice.flatten(order="F").astype(int)  # Flatten the array!
# filtering the voxels
threshed = grid.threshold([0.9, 2.1])
# adding the voxels
p.add_mesh(threshed, name='sphere', show_edges=True, opacity=1.0, show_scalar_bar=False)

p.window_size = 1000, 1000
p.screenshot("corridors")    
p.show(use_ipyvtk=True)
shafts_and_corridors_lattice = avail_lattice_bool * 0
shafts_and_corridors_lattice[np.where(cor_lattice == 1)] = 2
shafts_and_corridors_lattice[np.where(shft_lattice == 1)] = 1
shafts_and_corridors_lattice *= avail_lattice_bool
csv_path = os.path.relpath('../data/dynamic output/shafts_and_corridors_lattice.csv')
shafts_and_corridors_lattice.to_csv(csv_path)

csv_path = os.path.relpath('../data/dynamic output/avail_lattice_bool1.csv')
avail_lattice_bool.to_csv(csv_path)

Credits

__author__ = "Shervin Azadi and Pirouz Nourian"
__license__ = "MIT"
__version__ = "1.0"
__url__ = "https://github.com/shervinazadi/spatial_computing_workshops"
__summary__ = "Spatial Computing Design Studio Workshop on MCDA and Path Finding for Generative Spatial Relations"