Summary of Code

def array_city(x, p1, p2):

This function makes an x by x grid of 0s, 1s, and 2s by using np.random.choice.  The idea of this function is the foundation of the project and was not difficult to think of.  Before learning about np.random.choice, I created a grid and iterated through each row and each column using two for loops and assigned each position a random integer between 0 and 2.  The hard part was trying to control the percentage of zeros, ones and twos.  This function was lengthy and took almost the entire first week to create.  np.random.choice allowed me to do so in one line.

def get_neighbors(row,col,grid):

After creating the controlled grid, I, along with other Schelling model students, had no idea where to go from there.  Professor Granger pointed us in the right direction by telling us to write get_neighbors which passes a row and column in a specific grid and returns the coordinates of each neighbors.

Initially, I wrote the function specifically for middle spaces, which returned all surrounding 8 coordinates.  Upon testing, I realized that all along the boarder are special edge cases that need to be treated differently.  Thus, I added additional if statements to fix that.

My next function was going to return the value of each coordinate that get_neighbors returned, but I decided to combine the two and have get_neighbors return the value instead of the coordinates of each position.  Testing this function was quite easy.  All I did was make a controlled 3x3 grid, and made assert tests for each space.  I chose a 3x3 grid because it contains every special case that I wrote my get_neighbors function for.  

def sat_per(row,col,grid):

Coming up with the idea for this function was fairly instinctive.  I knew that if something was already satisfied, I did not need to move it.  Oppositely, I need to move everything that is not satisfied.  Thus, I needed a function to compute the satisfaction of a specific space.  Writing this function was easy as well.  I initialized three variables (zeros, ones, twos), iterated through each value in get_neighbors, and added 1 to the corresponding variable.  At the end, I divided each by the length of get_neighbors, and returned the corresponding percent.

def sat(satisfaction,grid): def unsat(satisfaction,grid): def empty(grid):

This function was not difficult to come up with either.  I knew I needed the coordinates of all the satisfied, unsatisfied, and empties  I initialized 3 empty lists, ran through each row and column of a specific grid, and appended each coordinate to the corresponding list depending on its satisfaction.  Initially I made just one function for all three lists, but I realized while writing my later functions, that separating them would be easier.  I even went as far as making a function for satisfied ones, another for satisfied twos, and two more for unsatisfied spaces.  Then I realized that that would force me to work on 1s and 2s individually instead of at the same time.  I went from one function, to three, to five, then back down to three.

def should_move(grid,row,col,satisfaction,new_value):

I was stuck after completing the last function, so Professor Granger helped me with the idea of this one.  This function simply passes a row and column in a specific grid, and when assigned a new value, will return True if satisfied and False if unsatisfied.

def do_move(grid,row,col,satisfaction):

This function is simple.  If should_move returns True, then implement the move.  Figuring out the moving logic was without a doubt the hardest part of this project.  The way I wrote it is that it takes a random coordinate in empty, and if should_move returns True, then the empty coordinate is assigned the new value and the old coordinate is replaced with a 0.  I had to make sure that I maintained the original amount of reds and blues, rather than just reassigning new values.

My empty function returned the coordinates of all empty spaces in an ordered sequence, so when I passed do_move on the list of empties, it did not work.  By adding np.random.shuffle, do_move works on random empty spaces instead of in an ordered fashion.

def move_all(satisfaction,grid):

This function simply runs do_move on each position in the grid.

def sat_all(grid,satisfaction):

This function simply runs move_all until all positions are satisfied.

def visualize(grid):

This function takes a grid and runs through each position, assigning each value a specific color.


There are numerous controls that effect the grid and its satisfied output, such as size, percent of each population, and satisfaction value. The effect that some of these controls have on how difficult it is to satisfy the grid are straight forward. For example, satisfying a smaller grid should be easier than satisfying a larger grid one. Also, a higher satisfaction percent should be more difficult to achieve than a smaller percent. Some factors are not so straight forward, and those are the questions that I would like to tackle.

First, I would like to study how the difference between blue and red percentages will effect the grid. I could see this going either way, as one color would be easier to satisfy because of how many there are, and the other would be much more difficult. To do so, I created a 50x50 grid with a constant 10% empty, and 40% blue and 50% red. Then I ran sat_all and counted how many times it looped. Then I did the same thing, but increased the percentage of red by 10% and decreased the amount of blue by 10% each time. I found that as the difference increased, the number of loops increased as well. Thus, dominance of one population makes the grid more difficult to satisfy.

Second, I would like to study how grid size effects the satisfaction ability. I'm inclined to predict that a bigger grid means more loops. However, this also means more empty spaces that are open for moving into, which, from previous testing, has concluded that the grid is easier to satisfy. To test this, I started with a 10 by 10 grid, computed how many loops it ran, then increased the size by 10 and repeated. I found that as I increased the grid size with constant reds, blues, and satisfaction percent, satisfaction became exponentially more difficult to achieve.

Visualizations of these observations are in the "Schelling Graphs" notebook

What's Next?

I have two topics I still would like to investigate in the future. The first is changing the movement rules. Currently, I pick random empties and assign them values based on satisfaction. To make the model more realistic, I could set rules for movement. For example, only adjacent movement is allowed, blues and reds can swap, blocks can only move certain distances. The second issue came up when I ran my program with different satisfaction values. The difference between high and low satisfaction values is very apparent, and can be seen at the bottom of my “Schelling Graphs” notebook. Both gathered the reds and blues at the top of the grids. I think this is a result of how I iterated through my grids. Maybe if I change that, the colonies will collect at different locations of the grids.


The most important coding lesson that I learned from this project is to break my ideas up into small functions that do small jobs. Initially, I wrote much too large functions that became too complicated to test and debug. By breaking down these functions, I was able to delve deeper into the logic of my code and better understand step by step what I was trying to achieve.