I do not know why my simulation is so slow. I need to optimize my simulation and improve the way I use variables

This is a python program that does some sort of simulation,, I am looking for any type of optimization while keeping the same p[i] form, I have tried Pypy and I got an around 3x performance gain over python. Any suggestions are welcomed

import random
from time import perf_counter
infected, non_infected = 1, 99999
infectation_chance, infection_days, death_chance = 1/100, 2/1000, 2/100
population, population_list = infected + non_infected, non_infected * [0] + infected * [1]
place = 10
p = {i: [] for i in range(1,place + 1)}
day = 1
simulation_duration = 3
while 0 < simulation_duration:
    print(f"Working on day {day}..")
    time1 = perf_counter()
    for person in population_list:
        p[random.randint(1, place)].append(person)
    time2 = perf_counter()
    i = 0
    while i < place:
        tl = []
        i += 1
        for crowd in p[i]:
            if crowd == 0:
                 if (random.random() < infectation_chance * str(p[i]).count("1")) - (infectation_chance/100 * str(p[i]).count("0")):
                     tl.append(1)
                 else:
                    tl.append(0)
            if crowd == 1:
                tl.append(1)
        p[i] = tl

    i = 0
    population_list = []
    while i < place:
        i += 1
        population_list.append(p[i])
    simulation_duration -= 1
    day += 1
    print(f"Total time: {perf_counter() - time1} nInfection time: {perf_counter() - time2} nPlacing time: {time2-time1}")
print(str(population_list).count("1"), str(population_list).count("0"))

Even tho I received lots of help I still need more optimization.Any type of optimization as far as it doesn’t change the results are welcomed.Since this is fully compatible with pypy I am using pypy, I can also use python if it means better performance. Current setup:

import random
from time import perf_counter
infected, non_infected = 1000, 999000
infectation_chancea, death_chance, recovery_chance ,reinfectation_chance, incubation_time = 100/6, 1 - 0.03, 1 - 0.899, 1 - 2/100, 1
death_chance, recovery_chance = death_chance / incubation_time, recovery_chance/incubation_time
population, population_list = infected + non_infected, non_infected * [0] + infected * [1]
place = 2
day = 1
simulation_duration = 100000000
with open("results.txt", "w") as results:
    results.seek(0)
    results.write("")
    results.close()
with open("results.txt", "a+") as results:
    print("Starting... nPlease wait for results")
    while infected > 0  and simulation_duration > 0:
        population = population_list.count(0) + population_list.count(-1) + population_list.count(1)
        healthy = population_list.count(0) + population_list.count(-1)
        recovered = population_list.count(-1)
        infected = population_list.count(1)
        died = population_list.count(2)
        p = {i: [] for i in range(1,place + 1)}
        results.write(f"Day {day}: Infected: {infected} Healthy: {healthy} Recovered: {recovered} Alive: {population} Died: {died} n")
        time1 = perf_counter()
        for person in population_list:
            p[random.randint(1, place)].append(person)
        time2 = perf_counter()
        i = 0
        while i < place:
            i += 1
            pi_infected = p[i].count(1)
            infectation_chance =  1 - pi_infected / float((float(len(p[i])) / infectation_chancea))
            for j, crowd in enumerate(p[i]):
                if crowd == 2:
                    pass
                elif crowd == 0:
                    if random.random() > infectation_chance):
                        p[i][j] = 1

                elif crowd == 1:
                    if random.random() > death_chance):
                        p[i][j] = 2

                    elif random.random() > recovery_chance):
                        p[i][j] = -1

                elif crowd == -1:
                    if random.random() > reinfectation_chance):
                        p[i][j] = 1

        i = 0
        population_list = []
        while i < place:
            i += 1
            population_list.extend(p[i])
        simulation_duration -= 1
        day += 1
print("Simulation finishsed, check 'results.txt' for the results)

Answer

here is a corrected version in pure python, commented because there were some bugs. your major time loss was counting infected/non-infected inside the for loop, though the result is always the same. it could be optimized again with numpy if you wanna use a bigger population

import random
from time import perf_counter
infected, non_infected = 1, 99999
infectation_chance, infection_days, death_chance = 1/100, 2/1000, 2/100
population, population_list = infected + non_infected, non_infected * [0] + infected * [1]
place = 10
day = 1
simulation_duration = 3
while 0 < simulation_duration:
    # p must be reset here or it will grow indefinitely
    p = {i: [] for i in range(1,place + 1)}
    print(f"Working on day {day}..")
    time1 = perf_counter()
    for person in population_list:
        p[random.randint(1, place)].append(person)
    time2 = perf_counter()
    i = 0
    while i < place:
        i += 1
        # if you need to, count infected/non-infected here
        # not in your for loop where it has always the same value
        # and don't cast to str, what's the point?
        # pi_infected = p[i].count(1)
        # pi_sane = p[i].count(0)
        for j, crowd in enumerate(p[i]):
            if crowd == 0:
                # your formula was broken (a-b is always true)
                # i used a generic one
                if random.random()>(1-infectation_chance):
                    # change your list in place:
                    # no need for temp list, save lots of cycles
                    p[i][j] = 1

    i = 0
    population_list = []
    while i < place:
        i += 1
        # it's extend, not append here or your population list
        # will have a length of #place
        population_list.extend(p[i])
    simulation_duration -= 1
    day += 1
    print(f"Total time: {perf_counter() - time1} nInfection time: {perf_counter() - time2} nPlacing time: {time2-time1}")
print(population_list.count(1), population_list.count(0))

numpy version

import random
import numpy as np
from time import perf_counter
infected, non_infected = 1, 99999
infectation_chance, infection_days, death_chance = 1/100, 2/1000, 2/100

place = 10
population = infected + non_infected
group_size = population//place

population_list=np.zeros((population))
population_list[:infected]=1

day = 1
simulation_duration = 3

while 0 < simulation_duration:
    print(f"Working on day {day}..")
    time1 = perf_counter()
    # shuffle is not recursive so we need to flatten population_list
    population_list=population_list.flatten()
    np.random.shuffle(population_list)
    population_list=population_list.reshape((place, group_size))
    time2 = perf_counter()
    
    # we need to rebuild the pure python code with no loops
    # first we create randoms for all pop
    randoms = np.random.rand(population).reshape((place, group_size))
    
    # list of infected by group: a list of all p[i].count(1)
    nb_infected = np.count_nonzero(population_list, axis=1).reshape((place,1))
    # compute (1-infectation_chance**p[i].count(1)) for all pop
    infection_map=np.full((place, group_size), 1-infectation_chance)**nb_infected
    # if randoms>infection_map and population_list==0
    new_infected = np.bitwise_and(randoms>infection_map, population_list==0)
    # then set to 1 in place
    population_list[new_infected] = 1
                    
    simulation_duration -= 1
    day += 1
    print(f"Total time: {perf_counter() - time1} nInfection time: {perf_counter() - time2} nPlacing time: {time2-time1}")
total_infected=np.count_nonzero(population_list)
print(total_infected, population-total_infected)