fcp2021 / simulation.py
simulation.py
Raw
		
#!/usr/bin/env python3


# Imported required librairies

import numpy as np
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation
'''
REDUNDANT SCRIPT. This file has been replaced by Project_Classes. 
Left intact such that commits can be seen
'''


class Simulation:		

    '''
    Simulation of CoVid19 using a 2D Array 

    This model involves 6 different states of the virus:

    0 = susceptible 		(S)
    1 = infected (asymptomatic) (A)
    2 = infected (symptomatic)	(I)		
    3 = recovered		(R)	
    4 = hospitalised		(H)
    5 = dead			(D)
    6 = incubation		(P)
	(period before symptoms)		
    

    A possible configuration of the 2D Array:


	0 2 0 0 	
	0 0 0 0 					
	0 2 0 1 					
	0 0 0 0 

    Which is equivalent to:

	S I S S
	S S S S
	S I S A
	S S S S
    
    Each number represents a person and its value corresponds to its state
    As the simulation is run, the variable "days" will increase and the matrix will be updated

	
	
    Separate matrices are used for overlaying individual data
	
	Example
	==========
    
    '''

    
    # Creating status codes to store in the numpy array representing the state (from Oscar's code) added special states.
    # This will be used in the function below rgb_matrix(self) to show in the animation colourmap
    SUSCEPTIBLE = 0
    INFECTED_A = 1
    INFECTED_S = 2
    RECOVERED = 3
    HOSPITALISED = 4
    DEAD = 5
    INCUBATION = 6
    
    STATUSES = {
        'susceptible': SUSCEPTIBLE,
        'infected_a': INFECTED_A,
    	'infected_s': INFECTED_S,
        'recovered': RECOVERED,
    	'hospitalised': HOSPITALISED,
        'dead': DEAD,
        'incubation': INCUBATION,
    }

    COLOURMAP = {
        'susceptible': 'green',
        'infected_a': 'yellow',
    	'infected_s': 'orange',
        'recovered': 'blue',
    	'hospitalised': 'pink',
        'dead': 'black',
        'incubation': 'grey',
    }

    COLOURMAP_RGB = {
        'green': (0, 255, 0),
        'yellow': (255, 255, 0),
        'orange': (255, 128, 0),
        'blue': (0, 0, 255),
        'pink': (255, 192, 203),	# Colour of hope in hospital
        'black': (0, 0, 0),
        'grey': (128, 128, 128),
    }
    
# Classes created for LineAnimation specifically 
# This is because the function counting the data for each status is used to create excel files
# Therefore, it does not have the same keys' name as the original STATUSES dict

    LINEANIMATION_DATA = {
        
        "simInfected_asymptomatic.csv": 0,
        "simInfected_symptomatic.csv": 0, 
        "simInfected.csv": 0, 
        "simRecovered.csv": 0,
        "simHospitalised.csv": 0,
        "simDead.csv": 0, 
        "simNew_cases.csv": 0,
        "simCumulative_infected.csv": 0,
    }
    
    LINEANIMATION_COLOURMAP = {
        
        "simInfected_asymptomatic.csv": 'yellow',
        "simInfected_symptomatic.csv": 'orange', 
        "simInfected.csv": 'red', 
        "simRecovered.csv": 'blue',
        "simHospitalised.csv": 'pink',
        "simDead.csv": 'black', 
        "simNew_cases.csv": 'purple',
        "simCumulative_infected.csv": 'green',
    }
    

    def __init__(self, size, introduced_cases_asymp, introduced_cases_symp, average_interactions_asymp, max_range_asymp, average_interactions_symp, max_range_symp, days = False, lockdown = False, average_interactions_asymp_lockdown = 0.1, average_interactions_symp_lockdown = 0.1, max_range_asymp_lockdown = 0.1, max_range_symp_lockdown = 0.1):
        self.day = 0 # This is not used and can be deleted 

        self.size = size
        self.dead = 0
        self.recovered = 0
        self.infected_asymp = 0
        self.infected_symp = 0
        self.infected = 0
        self.infection_array = None
        self.new_cases = introduced_cases_asymp + introduced_cases_symp
        self.hospitalised = 0
        self.lockdown = False
        self.average_incubation = 6
        self.average_infection = 7
        self.average_hospital = 10
        self.cum_infected = introduced_cases_asymp + introduced_cases_symp #NB there is a bug if one or the other = 3... Remove this issue in init array
        self.introduced_cases_asymp = introduced_cases_asymp
        self.introduced_cases_symp = introduced_cases_symp
        self.average_interactions_symp = average_interactions_symp
        self.max_range_symp = max_range_symp
        self.average_interactions_asymp = average_interactions_asymp
        self.max_range_asymp = max_range_asymp
        self.average_interactions_symp_lockdown = average_interactions_symp_lockdown
        self.max_range_symp_lockdown = max_range_symp_lockdown
        self.average_interactions_asymp_lockdown = average_interactions_asymp_lockdown
        self.max_range_asymp_lockdown = max_range_asymp_lockdown
        self.average_interactions_symp_current = average_interactions_symp
        self.max_range_symp_current = max_range_symp
        self.average_interactions_asymp_current = average_interactions_asymp
        self.max_range_asymp_current = max_range_asymp
        self.incubation_countdown = None
        self.hospital_countdown = None
        self.death_countdown = None
        self.matm1 = None                   # This variable name can/needs to be changed to match better it's function. As mat0 no longer exists
        self.ages = self.age_array_create("U", 0, 100)
        self.fraction_0_to_40_death_rate = 0.0001
        self.fraction_40_to_50_death_rate = 0.004
        self.fraction_50_to_60_death_rate = 0.013
        self.fraction_60_to_70_death_rate = 0.036
        self.fraction_70_to_80_death_rate = 0.08
        self.fraction_80_to_death_rate = 0.148
        self.fraction_0_to_40_hospital_rate = 0.0005
        self.fraction_40_to_50_hospital_rate = 0.005
        self.fraction_50_to_60_hospital_rate = 0.006
        self.fraction_60_to_70_hospital_rate = 0.008
        self.fraction_70_to_80_hospital_rate = 0.08
        self.fraction_80_to_hospital_rate = 0.16
        self.fraction_0_to_40_symptom_rate = 0.1
        self.fraction_40_to_50_symptom_rate = 0.2
        self.fraction_50_to_60_symptom_rate = 0.2
        self.fraction_60_to_70_symptom_rate = 0.5
        self.fraction_70_to_80_symptom_rate = 0.6
        self.fraction_80_to_symptom_rate = 0.8
        self.will_die = self.will_die_mat()
        self.will_hospital = self.will_hospital_mat()
        self.will_symptoms = self.will_symptoms_mat()
        self.pause = False

        self.statistics_list = [[self.infected, 'simInfected.csv'], [self.infected_symp, 'simInfected_symptomatic.csv'], [self.infected_asymp, 'simInfected_asymptomatic.csv'], [self.recovered, 'simRecovered.csv'], [self.new_cases, 'simNew_cases.csv'], [self.cum_infected, 'simCumulative_infected.csv'], [self.hospitalised, 'simHospitalised.csv'], [self.dead, 'simDead.csv']]
        self.days = days
        self.state = np.zeros((self.size, self.size), int) # self.state is the equivalent of mat0.
        self.state[:, :] = self.SUSCEPTIBLE                # It allows the simulation object to posses a matrix straight away
                                                           # Therefore the animation can run from the start (i.e., with no introduced covid cases)
                                                           # It is also easier to make the animation evolve with time (i.e., with the function day_run())
        

    def init_array(self):
        '''
        Create the initial array  with n people infected and prossess work arrays
        
        Parameters
        ==========
        self : simulation grid
        n : int
            Number of matrix points assigned to value symp
        symp : int
            1 or 2 depending on whether introduced covid cases are asymptomatic or symptomatic

        Returns
        ==========
        Numpy array of zeros r by c with n entries of 2 in random locations
        
    	Example 
    	==========
    	Introduce 1 symptomatic case in a 3 by 3 matrix
    
    	0 0 0
    	0 0 0
     	0 0 0
    	
    	>>> m = 3
    	>>> row = np.random.randint(0,int(self.size))
    	2
    	>>> column = np.random.randint(0,int(self.size))
    	1
    	
    	Therefore the matrix is now:
    
    	0 0 0
    	0 0 2
    	0 0 0
        '''
        self.incubation_countdown = self.countdown_time(self.average_incubation)
        self.hospital_countdown = self.countdown_time(self.average_infection)
        self.death_countdown = self.countdown_time(self.average_hospital)

        self.will_die = self.will_die_mat()
        self.will_hospital = self.will_hospital_mat()
        self.will_symptoms = self.will_symptoms_mat()
        n = self.introduced_cases_asymp
        # x = np.zeros((self.size,self.size), dtype=np.uint8)
        while n > 0:
            row = np.random.randint(0,int(self.size))		# random.randint(): returns random number in the specified range
            column = np.random.randint(0,int(self.size))
            if self.state[row,column] == 0:				# If the coordinates obtained point towards a Susceptible state, that person becomes Infected asymptomatic (A)	
                self.state[row,column] = 1				
                n -= 1						# Number of initial infected - 1
        m = self.introduced_cases_symp      
        while m > 0:
            row = np.random.randint(0,int(self.size))		# Same procedure for Symptomatic cases
            column = np.random.randint(0,int(self.size))
            if self.state[row,column] == 0:
                self.state[row,column] = 2
                m -= 1
        return self.state
        
    
    def recalculate_arrays(self):
        '''
        For use when changing factors during running, this will recalucalulate arrays. 
        Do not use unless necessary as short term statistical errors could occur due to progression route.

        Returns
        -------
        None.

        '''
        self.incubation_countdown = self.countdown_time(self.average_incubation)
        self.hospital_countdown = self.countdown_time(self.average_infection)
        self.death_countdown = self.countdown_time(self.average_hospital)

        self.will_die = self.will_die_mat()
        self.will_hospital = self.will_hospital_mat()
        self.will_symptoms = self.will_symptoms_mat()
    
    def get_rgb_matrix(self):
        """Function returns a RGB matrix from people state in the sample
        Taken from Oscar Benjamin simulation.py script
        "This represents the state as an RGB (colour) matrix using the
        coloursceheme set in the class variables COLOURMAP and COLOURMAP_RGB."
        The final matrix will be used to animate the simulation using matplotlib's imshow
        
        """
        
        rgb_matrix = np.zeros((self.size, self.size, 3), int)
        for status, statusnum in self.STATUSES.items():
            colour_name = self.COLOURMAP[status]
            colour_rgb = self.COLOURMAP_RGB[colour_name]
            rgb_matrix[self.state == statusnum] = colour_rgb
        return rgb_matrix


    
    def check_array(self, arr, status):
        '''
        Checks the array for all instances where an entry = status and returns coordinates as a matrix

        Parameters
        ==========
        arr : numpy array
            Numpy array being used to simulate.
        status : int
            status to search the array for (ie. 1 to locate all asymptomatic cases).

        Returns
        ==========
        array
            Contains the array coordinates of all values as row entries in the array.
	
    	Example
    	==========
    	Find all the infection (symptomatic) cases in the following matrix (Mi):
    
    	0 2 0 0 	
    	0 0 2 0 					
    	0 2 2 0 					
    	0 0 0 0 
    
    	Which is equivalent to 
    
    	>>> Mi = np.array([[0, 2, 0, 0], [0, 0, 2, 0], [0, 2, 2, 0], [0, 0, 0, 0]])
    	
    	>>> Coord_Status = list(np.where(arr == int(2)))
    	[array([0, 1, 2, 2], dtype=int64), array([1, 2, 1, 2], dtype=int64)]
    	
    	>>> X_Coord_Status = np.array(Coord_Status[0])
    	[0 1 2 2]
    	>>> Y_Coord_Status = np.array(Coord_Status[1])
    	[1 2 1 2]
    	>>> Result_Coord = (np.vstack((X_Coord_Status, Y_Coord_Status))).transpose()
    	[[0 1]
     	 [1 2]
     	 [2 1]
     	 [2 2]]

        '''
        Coord_Status = list(np.where(arr == int(status)))		# Find a special value in the array (i.e., the status number)
									# Two 1D arrays are combined in a list, they represent the x and y coordinates respectively of the status wanted (see Example above)
        X_Coord_Status = np.array(Coord_Status[0])				
        Y_Coord_Status = np.array(Coord_Status[1])
        Result_Coord = (np.vstack((X_Coord_Status, Y_Coord_Status))).transpose()	# The 2 arrays are then stacked and we take their transpose to obtain a 'n' by 2 matrix of all the coordinates of the status searched for
        return Result_Coord								# with n = number of person with the searched status
    

    def interaction(self, arr, symptoms, average_interactions, max_range): #note we are keeping this to asymp and symp seperately, dont forget to combine later also consider breaking function up to help with things like shielding

        '''
        Creates a matrix of mapping coordinates for infections from points modelled to be infectious with the max range as well as 
        distribution of how many variables defined in the class variables
        

        FOR REFERENCE LATER must be decided whether asymp/symp diffences are decided by chance of developing covid or through 
        interaction, if later separate class variables

        Parameters
        ==========
        arr : numpy array
            Numpy array being used to simulate.
        symptoms: int
            status to search the array for (ie. 1 to locate all asymptomatic cases).
        Returns
        ==========
        np.array
            n by 4 array detailing all simulated interactions NB. the outcome of interactions is not covered in this function
            and if shielding is appluies this list will be modulated by that function
        '''
    
        a = 2 #change for a local var in class				# M: What does this variable represents
        infected_loc = self.check_array(arr, symptoms)			# So this function is here to spread the virus to other susceptible persons ?
        arr = list(arr)							# Does it works well (Like not infecting same person again and giving coordinates in the limited range of the matrix ?
        OP = np.array([[0, 0, 0, 0]])
        dist = np.random.poisson(average_interactions, infected_loc.shape[0])
        v = 0
        for i in infected_loc:
            #implement form of distribution and indent with for loop to get how many they infect (note resolve issue with expanding array to fit all interactions)
            interactions_individ = dist[v]
            while interactions_individ > 0:
                new_loc = [(j + np.random.randint(-max_range, max_range+1)) for j in i]
                entry = np.hstack((i, new_loc))
                OP = np.vstack((OP,entry))
                interactions_individ -= 1
            v += 1
        OP = OP[1:]
        a = np.where(OP < 0)
        b = np.where(OP >= int(self.size)) #open to optimisation, only need to search last two columns
        out_of_range_OP = np.sort(np.unique(np.hstack((a[0],b[0]))))
        return np.delete(OP, out_of_range_OP, 0)
            #something to remove repeats and self interactions - can do later NB. if this replacxes rather than removes the stats option is increased in accuracy
        return OP[1:]
    
    def transmission(self, arr, interactions1, interactions2):
        '''
        Turns those 'in contact' with infected people to incubation period
        Parameters
        ==========
        arr : np array
            main 'people matrix'.
        interactions1 : np array
            interactions by asymptomatic cases
        interactions2 : np array
            interactions by symptomatic cases
        Returns
        ==========
        np.array
            main array with cases being set to incubation based on recipient end of transmissions
        '''
		

        interactions = np.vstack((interactions1, interactions2))
        self.infection_array = interactions
        locations = interactions[0:,2:]
        for i in locations:
            if arr[int(i[0]),int(i[1])] == 0:
                arr[int(i[0]),int(i[1])] = 6
        return arr

    
    def age_array_create(self, dist, mu_low=None, sigma_high=None):

        ''' 
        creates a reference array of ages of sample
        CREATE ABSOLUTE LIMITS (0 and 120) ALSO INTEGER AGES for normal?
        Parameters
        ==========
        dist : str
            Distribution type.  N - Normal
                                U - Uniform
        mu_low : float
            if N is chosen this is the lower bound (inclusive)
            if U is chosen this is the mean value.
        sigma_high : float
            if N is chosen this is the high value (exclusive)
            if U is chosen this is the standard deviation.

        Returns
        ==========
        np array
            array with random ages following chosen distriubution.

        '''
        if str(dist) == "U":
            if sigma_high == None:
                return np.random.randint(0, 99, size=(self.size, self.size))
            else:
                return np.random.randint(mu_low, sigma_high, size=(self.size, self.size))
        if str(dist) == "N":
            if sigma_high == None:
                return np.random.normal(mu_low, sigma_high, size=(self.size, self.size))
            else:
                return np.random.normal(mu_low, sigma_high, size=(self.size, self.size)) # get this to be int (also consider getting rid of)  

    def ratio_mat(self, a, b, c, d):
        '''
        Creates a matrix with c and d in a ratio of a:b

        Parameters
        ==========
        a : int
            first value of ratio ~a/(a+b) of the matrix is of value c.
        b : int
            second value of ratio ~b/(a+b) of the matix is of value d.
        c : int (/bool)
            status that ratio represents.
        d : int  (/bool)
            second status (alternative outcome).

        Returns
        ==========
        np array
        matrix of .

        '''
        arr = np.random.randint(-a,b, size=(self.size, self.size))
        return np.where(arr < 0, c, d)
    
    def day_pass(self,mat_new):
        '''
        shifts all days in memory back by one, deleting the last day

        Parameters
        ==========
        mat_new : np array
            matrix being worked on currently.

        Returns
        ==========
        None.

        '''

        self.matm1 = self.state # The mat0 variable was changed to state and initialised in the __init__ function as a np.zeros(width, height) array
        self.state = mat_new    # No changes to this line except modifying self.mat0 to self.state
        self.day += 1           # M: not sure what this is used for so left intact


         
    def countdown_time(self, lamda):
        '''
        Gives an array of a posion distribution lamda to be used as n by n timers to trigger one
        state to another

        Parameters
        ==========
        lamda : float
            rate in poission distribution.

        Returns
        ==========
        np array
            An array of a posion distribution lamda to be used as n by n timers to trigger one 
            state to another.

        '''
        array = np.random.poisson(lam=lamda, size=(self.size,self.size))
        array = np.where(array == 0, 2, array)
        array = np.where(array == 1, 2, array)
        return array
    
    def inc_to_inf(self, mat):
        '''
        Controls transmission from incubated cases to asymptomatic cases, then symptomatic cases or recovered.

        Parameters
        ==========
        mat : np.array
            current day array.

        Returns
        ==========
        np.array
            array where countdown matrix has been used to change incubating cases to infected cases (symp) or recovered.
            NB this also programmes asymptomatic infection 3 days before outcome is decided

        '''
        if  isinstance(self.matm1, np.ndarray): # can remove this indent if protectec in system
            self.incubation_countdown = np.where(self.matm1 == 1, (self.incubation_countdown-1),self.incubation_countdown)
            self.incubation_countdown = np.where(self.matm1 == 6, (self.incubation_countdown-1),self.incubation_countdown)
            arr1 = np.where(mat == 6, 1, mat) #asymp in place of inc
            workspace1 = np.where((self.incubation_countdown <= 3), arr1, mat) 
            arr2 = np.where(mat == 1, self.will_symptoms, mat)
            workspace2 = np.where((self.incubation_countdown == 0), arr2, workspace1)
            return workspace2
            
            #self.percent_asymptomatic
        else:
            return mat
        
    def inf_to_hosp(self, mat):
        '''
        Controls transmission from infected cases to hospitalised cases or recovered.

        Parameters
        ==========
        mat : np.array
            current day array.

        Returns
        ==========
        np.array
            array where countdown matrix has been used to change infected cases to hospitalised cases or recovered.

        '''
        if  isinstance(self.matm1, np.ndarray): # can remove this indent if protectec in system
            self.hospital_countdown = np.where(self.matm1 == 2, (self.hospital_countdown-1),self.hospital_countdown)
            arr1 = np.where(mat == 2, self.will_hospital, mat) #asymp in place of inc
            workspace = np.where((self.hospital_countdown == 0), arr1, mat) 
            return workspace
        else:
            return mat    
        
        
    def hosp_to_die(self, mat):
        '''
        Controls transmission from hospitalised cases to dead cases or recovered.

        Parameters
        ==========
        mat : np.array
            current day array.

        Returns
        ==========
        np.array
            array where countdown matrix has been used to change hospitalised cases to dead cases or recovered.
            

        '''
        if  isinstance(self.matm1, np.ndarray): # can remove this indent if protectec in system
            self.death_countdown = np.where(self.matm1 == 2, (self.death_countdown-1),self.death_countdown)
            arr1 = np.where(mat == 4, self.will_die, mat) #asymp in place of inc
            workspace = np.where((self.hospital_countdown == 0), arr1, mat) 
            return workspace
        else:
            return mat  
        
    def will_die_mat(self):
        
        '''
        Creates a matrix of those expected to die. NB use this to ensure that these people will 
        be symptomatic and hositalised (underestimate those calculations accordingly)
        
        Data source = https://www.statista.com/chart/20860/coronavirus-fatality-rate-by-age/ - this 
        data is early and cannot be fully trusted, consider updating this if better data availiable
        Returns
        ==========
        arr : np array
            array of 3s and 5s corresponding to deaths and recoverys corresonding to the workspace
            size. Thiese also correspond to expected rates acording to the age matrix.

        '''
        
        arr = np.ones((self.size,self.size), np.uint8)
        arr = np.where(((self.ages >= 0)), self.ratio_mat(self.dec_to_rat(self.fraction_0_to_40_death_rate)[0], (self.dec_to_rat(self.fraction_0_to_40_death_rate))[1], 5, 3),arr)
        arr = np.where(((self.ages >= 40)), self.ratio_mat(self.dec_to_rat(self.fraction_40_to_50_death_rate)[0], (self.dec_to_rat(self.fraction_40_to_50_death_rate))[1], 5, 3),arr)
        arr = np.where(((self.ages >= 50)), self.ratio_mat(self.dec_to_rat(self.fraction_50_to_60_death_rate)[0], (self.dec_to_rat(self.fraction_50_to_60_death_rate))[1], 5, 3),arr)
        arr = np.where(((self.ages >= 60)), self.ratio_mat(self.dec_to_rat(self.fraction_60_to_70_death_rate)[0], (self.dec_to_rat(self.fraction_60_to_70_death_rate))[1], 5, 3),arr)
        arr = np.where(((self.ages >= 70)), self.ratio_mat(self.dec_to_rat(self.fraction_70_to_80_death_rate)[0], (self.dec_to_rat(self.fraction_70_to_80_death_rate))[1], 5, 3),arr)
        arr = np.where((self.ages >= 80), self.ratio_mat(self.dec_to_rat(self.fraction_80_to_death_rate)[0], (self.dec_to_rat(self.fraction_80_to_death_rate))[1], 5, 3),arr)
        return arr
    
    def will_hospital_mat(self):               
        '''
        Creates a matrix of those expected to be hsopitalised. NB this automatically includes those who will die
        therefore ratios are an underestimate. ###Change this to work backwards at a later date for better stats when symptomatic stats can be found
        
        Data source = have not found
        Returns
        ==========
        arr : np array
            array of 4s and 3s corresponding to hospitalisations and recoverys corresonding to the workspace
            size. Thiese also correspond to expected rates acording to the age matrix.

        '''
        arr = np.ones((self.size,self.size), np.uint8)
        arr = np.where(((self.ages >= 0)), self.ratio_mat(self.dec_to_rat(self.fraction_0_to_40_hospital_rate)[0], (self.dec_to_rat(self.fraction_0_to_40_hospital_rate))[1], 4, 3),arr)
        arr = np.where(((self.ages >= 40)), self.ratio_mat(self.dec_to_rat(self.fraction_40_to_50_hospital_rate)[0], (self.dec_to_rat(self.fraction_40_to_50_hospital_rate))[1], 4, 3),arr)
        arr = np.where(((self.ages >= 50)), self.ratio_mat(self.dec_to_rat(self.fraction_50_to_60_hospital_rate)[0], (self.dec_to_rat(self.fraction_50_to_60_hospital_rate))[1], 4, 3),arr)
        arr = np.where(((self.ages >= 60)), self.ratio_mat(self.dec_to_rat(self.fraction_60_to_70_hospital_rate)[0], (self.dec_to_rat(self.fraction_60_to_70_hospital_rate))[1], 4, 3),arr)
        arr = np.where(((self.ages >= 70)), self.ratio_mat(self.dec_to_rat(self.fraction_70_to_80_hospital_rate)[0], (self.dec_to_rat(self.fraction_70_to_80_hospital_rate))[1], 4, 3),arr)
        arr = np.where((self.ages >= 80), self.ratio_mat(self.dec_to_rat(self.fraction_80_to_hospital_rate)[0], (self.dec_to_rat(self.fraction_80_to_hospital_rate))[1], 4, 3),arr)
        arr = np.where((self.will_die == 5), 4, arr)
        return arr
    
    def will_symptoms_mat(self):
        '''
        Creates a matrix of those expected to become symptomatic. NB this automatically includes those who will die
        therefore ratios are an underestimate. ###Change this to work backwards at a later date for better stats when symptomatic stats can be found
        
        Data source = have not found
        Returns
        ==========
        arr : np array
            array of 2s and 3s corresponding to symptomatic cases and recoverys in an array the workspace
            size. These also correspond to expected rates acording to the age matrix.

        '''
        arr = np.ones((self.size,self.size), np.uint8)
        arr = np.where(((self.ages >= 0)), self.ratio_mat(self.dec_to_rat(self.fraction_0_to_40_symptom_rate)[0], (self.dec_to_rat(self.fraction_0_to_40_symptom_rate))[1], 2, 3),arr)
        arr = np.where(((self.ages >= 40)), self.ratio_mat(self.dec_to_rat(self.fraction_40_to_50_symptom_rate)[0], (self.dec_to_rat(self.fraction_40_to_50_symptom_rate))[1], 2, 3),arr)
        arr = np.where(((self.ages >= 50)), self.ratio_mat(self.dec_to_rat(self.fraction_50_to_60_symptom_rate)[0], (self.dec_to_rat(self.fraction_50_to_60_symptom_rate))[1], 2, 3),arr)
        arr = np.where(((self.ages >= 60)), self.ratio_mat(self.dec_to_rat(self.fraction_60_to_70_symptom_rate)[0], (self.dec_to_rat(self.fraction_60_to_70_symptom_rate))[1], 2, 3),arr)
        arr = np.where(((self.ages >= 70)), self.ratio_mat(self.dec_to_rat(self.fraction_70_to_80_symptom_rate)[0], (self.dec_to_rat(self.fraction_70_to_80_symptom_rate))[1], 2, 3),arr)
        arr = np.where((self.ages >= 80), self.ratio_mat(self.dec_to_rat(self.fraction_80_to_symptom_rate)[0], (self.dec_to_rat(self.fraction_80_to_symptom_rate))[1], 2, 3),arr)
        arr = np.where((self.will_hospital == 4), 2, arr)
        return arr

    def day_run(self):
        '''
        Compressed fixed actions that happen in a 'day'

        Returns
        ==========
        None.

        '''

        # Again all this section was 'modified' :
        # self.mat0 was changed to self.state
        self.day_checks()
        self.state = self.transmission(self.state,self.interaction(self.state, 1, self.average_interactions_asymp_current,self.max_range_asymp_current),self.interaction(self.state, 2, self.average_interactions_symp_current,self.max_range_symp_current))#take 0.5 values out and replace with variables asymp and symp transmission
        self.state = self.hosp_to_die(self.state)
        self.state = self.inf_to_hosp(self.state)
        self.state = self.inc_to_inf(self.state)
        self.writing_stats()
        self.day_checks
        self.day_pass(self.state)
        return self.state

    def day_run_pause_included(self):
            while self.pause == True:
                pass
            self.day_run                

    def main(self):
        '''
        NB unfinished and rudementary, first attempt to have a running simulation
    
        Returns
        ==========
        None.
    
        '''

        self.state = self.init_array()
        self.stats_init()
        n = self.days
        if n == False:
            while (((self.count(self.state, 2)) != 0) or (self.count(self.state, 1) != 0) or (self.count(self.state,4) != 0)):
                print(self.state)
                #print(self.infection_array)
                self.day_run()
        else:
            while n > 0:
                print(self.state)
                #print(self.infection_array)
                self.day_run()
                n -= 1
    

    def count(self, mat, value):
        '''
        counts number of value in matrix mat

        Parameters
        ----------
        mat : np.array
            workspace array.
        value : int
            state value.

        Returns
        -------
        int
            count of array value

        '''
        arr = mat == value
        return np.count_nonzero(arr)
    
    def statistics(self):
	
        '''
        Processes the statsitics of the matrix adding the values to csv files

        Returns
        -------
        None.

        '''
	
        Stats ={"simInfected_asymptomatic.csv": self.infected_asymp,
                "simInfected_symptomatic.csv": self.infected_symp, 
                "simInfected.csv": self.infected, 
                "simRecovered.csv": self.recovered,
                "simHospitalised.csv": self.hospitalised,
                "simDead.csv": self.dead, 
                "simNew_cases.csv": self.new_cases,
                "simCumulative_infected.csv": self.cum_infected,
                }
	
	# Can normally be deleted
	# List = [[self.infected, 'simInfected.csv'], [self.infected_symp, 'simInfected_symptomatic.csv'], [self.infected_asymp, 'simInfected_asymptomatic.csv'], [self.recovered, 'simRecovered.csv'], [self.new_cases, 'simNew_cases.csv'], [self.cum_infected, 'simCumulative_infected.csv'], [self.hospitalised, 'simHospitalised.csv'], [self.dead, 'simDead.csv']]
	
	# Also, updated the way the Stats dict was receiving the data of each status (infected, recovered, etc...)
        self.infected_asymp = self.count(self.state, 1)
        self.infected_symp = self.count(self.state, 2)
        self.infected = self.infected_symp + self.infected_asymp
        self.recovered = self.count(self.state, 3)
        self.hospitalised = self.count(self.state,4)
        self.new_dead = self.count(self.state,5) - self.dead
        self.dead = self.count(self.state,5)
        self.new_cases = self.infected + self.recovered +self.dead + self.hospitalised - self.cum_infected
        self.cum_infected = self.infected + self.recovered + self.hospitalised + self.dead
        
        Stats["simInfected_asymptomatic.csv"] = self.infected_asymp
        Stats["simInfected_symptomatic.csv"] = self.infected_symp
        Stats["simInfected.csv"] = self.infected
        Stats["simRecovered.csv"] = self.recovered
        Stats["simHospitalised.csv"] = self.hospitalised
        Stats["simDead.csv"] = self.dead
        Stats["simNew_cases.csv"] = self.new_cases
        Stats["simCumulative_infected.csv"] = self.cum_infected

        return Stats

    def writing_stats(self):
        for file, stat in self.statistics().items():
            self.csv_stat(stat, file)
        
   
    def csv_stat(self, stat, file):
        '''
        appends the statistic to the csv file of past statistics

        Parameters
        ----------
        stat : int/float
            data to be input to file
        file : str
            File description

        Returns
        -------
        None.

        '''
        with open(file, 'a') as csv:

            csv.write(',\n' + str(stat))

        
    def dec_to_rat(self, dec):
        ratA = int(10000*dec)
        ratB = 10000 - ratA
        return [ratA, ratB]
        
    def stats_init(self):
        '''
        Creates csv files to paste statistics in

        Returns
        -------
        None.

        '''
        for i in self.statistics_list:
            with open(i[1], 'w') as csv:
                csv.write(str(i[0]))

                
    def isolation_age(self):
        return 0

    def day_checks(self):
        if self.lockdown == True:
            self.average_interactions_symp_current = self.average_interactions_symp_lockdown
            self.max_range_symp_current = self.max_range_symp_lockdown
            self.average_interactions_asymp_current = self.average_interactions_asymp_lockdown
            self.max_range_asymp_current = self.max_range_asymp_lockdown
        elif self.lockdown == False:
            self.average_interactions_symp_current = self.average_interactions_symp
            self.max_range_symp_current = self.max_range_symp
            self.average_interactions_asymp_current = self.average_interactions_asymp
            self.max_range_asymp_current = self.max_range_asymp      
            
if __name__ == '__main__':
    simulation = Simulation(100,2,2,0.2,20,1.1,3,100)
    simulation.main()