Search code examples
pythonarraysfor-loopwhile-loopagent-based-modeling

Looping through, and updating, array values until a condition is met, or until 100 loops have been completed?


Working in Python 3.7 on a Jupyter Notebook. I'm working on a project that will loop through a 2D Numpy Array ("board", if you will) check for all instances of the number 1. When it finds the number 1, I need it to check the values to the left, right, above it, and below it. If any of the values next to it is a 2, then that element itself becomes a 2.

After looping through the entire array, I will need the code to check if the board has changed at all from the start of that single loop. If it hasn't changed, then the simulation (the loop) should end. If it has changed, however, then the simulation should run again. However, the simulation should not loop for more than 100 turns.

Here is the set-up leading up to my problem-cell:

import numpy as np
import matplotlib.pyplot as plt
%matplotlib inline
import numpy.random as rand
import time
from IPython.display import display, clear_output

def set_board(height=5,width=10,density=.75):
    city = np.zeros((height,width),dtype='int64')
    for i in range(height):
        for j in range(width):
            if rand.random() <= density:
                city[i,j] = 1
    return city

def VisMap(Map=set_board(),Colors=plt.cm.RdYlGn):
    MapVis = plt.imshow(Map, Colors)
    return MapVis

def onBoard(i, j, array):
    if (i >= 0 and i < array.shape[0]-1 
        and j >= 0 and j < array.shape[1]-1):
        return True
    return False

def getNeighborValues(i, j, board):
    neighborhood_indices = [(i-1,j),(i,j-1),(i+1,j),(i,j+1)]
    neighborhood_values = []
    for index in neighborhood_indices:
        if onBoard(index[0],index[1],board) == True:
            neighborhood_values.append(board[index[0],index[1]])
        pass
    return neighborhood_values

def startRumor(board):
    height, width = board.shape 
    height_quarters = int(height/4)
    width_quarters = int(width/4)
    starting_middle_height_index = height_quarters
    ending_middle_height_index = 3*height_quarters
    starting_middle_width_index = width_quarters
    ending_middle_width_index = 3*width_quarters
    found_starting_point = False 
    if np.all(board[starting_middle_height_index:ending_middle_height_index, starting_middle_width_index:ending_middle_width_index] == 0):
        i = rand.randint(starting_middle_height_index, ending_middle_height_index)
        j = rand.randint(starting_middle_width_index, ending_middle_width_index)
        board[i,j] = 2
        found_starting_point = True
    while not found_starting_point:
        i = rand.randint(starting_middle_height_index, ending_middle_height_index)
        j = rand.randint(starting_middle_width_index, ending_middle_width_index)
        if board[i,j] == 1:
            found_starting_point = True
            board[i, j] = 2

And here is the cell I'm having an issue with (specifically beginning with Step 5):

#Step 1: Initializing my board
StartingBoard = set_board(100,200,.4)

#Step 2: Visualizing my board
PreRumorVis = VisMap(StartingBoard)

#Step 3: Starting the rumor
startRumor(StartingBoard)

#Step 4: Visualizing the board after the rumor has started
PostRumorVis = VisMap(StartingBoard)

#Step 5: Create a copy of the city, and turn anything 
#with a 2 around it into a 2, and keep doing that for 100 
#loops. Or, if the city at the end of the loop is equal to 
#the one from the beginning of the loop, break it. If there 
#is some change, though, set the new city to now be the 
#updated copy, and loop through again. (In this case, it 
#probably should loop all 100 times).

City_Copy = StartingBoard.copy()
New_City = City_Copy.copy()
iterations = 0
for num in range(100):
    for i in range(City_Copy.shape[0]):
        for j in range(City_Copy.shape[1]):
            if City_Copy[i,j] == 1:
                if 2 in getNeighborValues(i,j, City_Copy):
                    New_City[i,j] = 2
                else:
                    New_City[i,j] = 1
    if np.array_equal(New_City, City_Copy) == True:
        break
    else:
        City_Copy = New_City.copy()
        iterations += 1

print("This rumor has been around for", iterations, "days.")            
New_City

Edit: I found I was missing the copy function at first, thanks to one of the commenters. However, I'm still getting about 18 days, when it should be 100 (or very close). Wondering if I should be opening with a for loop or a while loop. The problem might be somewhere with setting the variables equal to copies.... It's a little confusing to me. It all makes sense logically, but there's a screw loose somewhere.


Solution

  • In Python, Assignment statements do not copy objects, instead they create bindings between a target and an object. When we use = operator the user thinks that this creates a new object, Well, it doesn’t. It only creates a new variable that shares the reference of the original object.

    Example:-

    >>> a=np.array([[0,1],[0,2]])
    >>> b=a
    >>> np.array_equal(b,a)
    True
    >>> b[0][1]=1
    >>> b
    array([[0, 1],
           [0, 2]])
    >>> a
    array([[0, 1],
           [0, 2]])
    >>> np.array_equal(b,a)
    True
    

    This happens due to the fact of shallow copying. Shallow copying is only limited to compound objects such as lists. To avoid this go for deep copying.

    >>> import copy
    >>> a=np.array([[0,1],[0,2]])
    >>> b=copy.deepcopy(a) 
    >>> np.array_equal(b,a)
    True
    >>>b[0][0]=1
    >>> np.array_equal(b,a)
    False
    

    Solution:-

    Since you have assigned New_City=City_Copy whatever changes in New_City is done it is reflected in City_Copy. Thus they are both equal and the loop breaks the first time itself.So loop is not incremented. Try to use deepcopy to solve this.

    Reference:-

    1. Shallow copy vs Deep copy