Source code for model

#!/usr/bin/python

#### Imports

import agentframework as af

import random

import numpy as np # for getting sum of values in the environment matrix easily

import matplotlib
matplotlib.use('TkAgg')
import matplotlib.pyplot as plt     #plt.set_cmap('YlGn') ## CAUSES tk CHECKBUTTON TO NOT WORK!!!!!
import matplotlib.animation as anim

import tkinter as tk

### global variable to control animation start/stop
carry_on = True

#### functions for running the model and GUI: moving, eating, mating, aging, dying

[docs]def update_labels(frame_for_display,num_agents_for_display): """To be used within the update() function to update the display of frame number and number of sheep. Arguments: frame_for_display (int): frame number num_agents_for_display (agents): number of sheep alive in the scene Requires global: run_button (tkinter button) n_label (tkinter label) frame_label (tkinter label) """ n_text = "{} sheep grazing".format(num_agents_for_display) if num_agents_for_display == 0: n_text = "All dead!" run_button.config(text='Restart', bg='#268B49') n_label.configure(text=n_text) frame_text = "Frame {}".format(frame_for_display) frame_label.configure(text=frame_text)
[docs]def update(frame_number, max_age, optimised_movement, breed, min_age_for_preg, preg_duration, agents, environment): """To be used by FuncAnimation from matplotlib to update the simulation. Arguments passed through by the run() function which calls this update() function. Argument: frame_number (int): provided by FuncAnimate() max_age (int) optimised_movement (bool) breed (bool) min_age_for_preg (int) preg_duration (int) agents (list of Agent classes) environment (list of list of numbers) Global: carry_on (bool) """ if frame_number == 0: # frame 0 occurs twice, and first call does not draw, hence exclude return global carry_on # update the frame number and number of sheep displayed in the GUI update_labels(frame_number-1,len(agents)) ##### print number of sheep to console # print('Frame: ', frame_number-1) # print('Number of Sheep: ', len(agents)) # re-plot environment at the start of every update to show developments during last update fig.clear() plt.imshow(environment, cmap='YlGn', vmin=0, vmax=250) plt.xlim(0,300) plt.ylim(300,0) # 300 to 0 to get the correct orientation plt.axis('off') # simulation stopping conditions. Exit function using 'return'. if len(agents) == 0: print('All dead!') carry_on = False return elif np.array(environment).sum() == 0: print("All grass eaten!") carry_on = False return # shuffle agents - DISABLED - Since no systematic errors can be seen # and also the shuffling causes the order at which sheep are drawn to # the screen to change, meaning that if two sheep are overlapping, # they flicker with sheep 1 being drawn first, and 2 second, # but the next frame might be the opposite. # this could have been solved by drawing the sheep based on IDs # but this unnecessarily complicates the code. #random.shuffle(agents) # make an empty list to populate with sheep that die this round, if any the_dead = [] # run through each sheep and perform actions and plot it on the environment for agent in agents: # set colour based on sex c = ('black' if agent.get_sex() == 'm' else 'white') # set size relative to maximum age (oldest = biggest) s = ((agent.get_age()+1)/(max_age+1))*100 if agent.is_dead(max_age): # check if this sheep is at max age, and let it die peacefully if so. the_dead.append(agent) plt.scatter(agent.get_x(),agent.get_y(),s=s,c=c,marker='1') # Plot him as a skeleton else: # if still alive, let him live and PERFORM ACTIONS # plot it (first round, plots initial position) plt.scatter(agent.get_x(),agent.get_y(),s=s,c=c,marker='*') # change sick_enabled to True to activate throwing up agent.eat(max_grass_per_turn=20, sick_enabled=False) # perform actions agent.move(optimised=optimised_movement) if breed: agent.mate(preg_duration,min_age_for_preg) #agent.share_with_neighbours(20) # un-comment to activate sharing with neighbours. # age it agent.increment_age() # remove all who died in this round in one go for dead_agent in the_dead: agents.remove(dead_agent)
[docs]def gen_function(): """To be used by FuncAnimation from matplotlib to progress the simulation. Maximum number of frames chosen to be 2,000. Global: carry_on (bool): if false, gen function does not increase the frame number """ a = 0 global carry_on while carry_on: # maximum number of frames for a given simulation set at 2,000 if a < 2000: yield a # Returns and awaits next call. a += 1
[docs]def run(): """Reads in parameters from silders and checkboxes and initiates the animation to be plotted. Used by run_stop_toggle to initiate simulation on buttonpress. """ # make the button reusable by resetting carry_on to True every run global carry_on carry_on = True # re-import the clean environment for every run of the simulation environment = af.import_environment() # read parameters from the GUI widgets optimised_movement = (graze_var.get() == 'Optimum') # convert str to bool seed = seed_var.get() breed = babies_var.get() max_age = max_age_slider.get() min_age_for_preg = min_preg_age_slider.get() preg_duration = preg_duration_slider.get() num_agents = n_slider.get() # print parameters to console print( '\n#### Run started with parameters:\n\ {} agents\n\ {} max age\n\ Grazing: {}\n\ Seeding: {}\n\ {} breeding\n\ {} minimum age for pregnancy\n\ {} duration of pregnancy\n'\ .format( num_agents, max_age, ('Optimum' if optimised_movement else 'Random'), seed, ('Enabled' if breed else 'Disabled'), min_age_for_preg, preg_duration ) ) # create initial list of agents # if seed chosen seed to always create the same agents if seed != 'Off': random.seed(int(seed)) agents = [] for _ in range(num_agents): agents.append(af.Agent(environment,agents)) # run animation, passing the parameters initiated here to the update() function through 'fargs' animation = anim.FuncAnimation( fig, update, # arguments for update(): fargs=(max_age, optimised_movement, breed, min_age_for_preg, preg_duration, agents, environment), frames=gen_function, repeat=False ) # and place it into the GUI anim_placeholder.draw()
[docs]def start_kill_toggle(): """Function to be used by the run_button to start, kill, and restart the simulation. Implements the following functionality: 1. If button is pressed for the first time, the simulation is run() and the button text changes to "Kill" and its colour to normal. 2. If the now "Kill" button is pressed, simulation is stopped and text changed to "Restart" and to green colour. 3. If the now "Restart" button is pressed, the simulation is re-run with new parameters from the GUI, and text changed to "Kill" and colour to normal. 4. 2 and 3 are then repeated. Functionality for returning this button to "Restart" if all sheep die is implemented in update_labels(). """ current_text = run_button.config("text")[-1] if current_text == 'Start' or current_text == 'Restart': run() run_button.config(text='Kill', bg='red3') elif current_text == 'Kill': global carry_on carry_on = False run_button.config(text='Restart', bg='#268B49')
#### TKINTER GUI SETUP ############################################################## # setup of root, canvas, and frames root = tk.Tk() root.title('Sheep Sim - Amir') canvas = tk.Canvas(root, height=510, width=670) canvas.pack() left_frame = tk.Frame(root, bd=5) left_frame.place(relx=0.15, rely=0.05, relwidth=0.25, relheight=0.9, anchor='n') right_frame = tk.Frame(root,bd=5) right_frame.place(relx=0.64, rely=0.02, relwidth=0.68, relheight=0.96, anchor='n') # fill options panel (lef_frame) ########## # title main_title = tk.Label(left_frame, text="Sheep Sim (°ꈊ°)") main_title.place(relx=0,rely=-0.02,relwidth=1,relheight=0.08) main_title.config(font=("Poor Richard", 16)) # line separator_line0 = tk.Frame(left_frame,bg='grey') separator_line0.place(relx=0.05,rely=0.08,relheight=0.001,relwidth=0.9) # number of agents slide n_slider = tk.Scale(left_frame, from_=1, to=120, orient=tk.HORIZONTAL,label='Number of Agents') n_slider.place(relx=0.05,rely=0.085,relwidth=0.9, relheight=0.18) # max age slider max_age_slider = tk.Scale(left_frame, from_=1, to=120, orient=tk.HORIZONTAL,label='Life Expectancy') max_age_slider.place(relx=0.05,rely=0.22,relwidth=0.9, relheight=0.18) # seed dropdown label seed_label = tk.Label(left_frame, text="Spawn Seed:") seed_label.place(relx=0.075,rely=0.37,relwidth=0.41,relheight=0.06) # seed dropdown seed_var = tk.StringVar(root) seed_dropdown = tk.OptionMenu(left_frame, seed_var, 'Off', '5995', '0', '10', '100') seed_dropdown.place(relx=0.52,rely=0.37,relwidth=0.41,relheight=0.06) # optimised grazing dropdown label seed_label = tk.Label(left_frame, text="Grazing:") seed_label.place(relx=0.05,rely=0.45,relwidth=0.3,relheight=0.06) # optimised grazing dropdown graze_var = tk.StringVar(root) optimised_dropdown = tk.OptionMenu(left_frame, graze_var, 'Optimum', 'Random') optimised_dropdown.place(relx=0.36,rely=0.45,relwidth=0.57,relheight=0.06) # line separator_line = tk.Frame(left_frame,bg='grey') separator_line.place(relx=0.05,rely=0.54,relheight=0.001,relwidth=0.9) # reproduction checkbox babies_var = tk.IntVar(root) optimised_chck = tk.Checkbutton(left_frame, variable = babies_var, text='Reproduction') optimised_chck.place(relx=-0.01,rely=0.55,relwidth=0.9,relheight=0.06) # min age for pregnancy slider min_preg_age_slider = tk.Scale(left_frame, from_=0, to=50, orient=tk.HORIZONTAL,label='Fertility Age') min_preg_age_slider.place(relx=0.05,rely=0.62,relwidth=0.9,relheight=0.18) # pregnancy duration slider preg_duration_slider = tk.Scale(left_frame, from_=1, to=50, orient=tk.HORIZONTAL,label='Pregnancy Duration') preg_duration_slider.place(relx=0.05,rely=0.75,relwidth=0.9,relheight=0.17) # set defaults
[docs]def set_defaults(): """Requires the GUI widgets to exist before it is called.""" n_slider.set(60) max_age_slider.set(60) graze_var.set('Optimum') seed_var.set('5995') babies_var.set(1) min_preg_age_slider.set(20) preg_duration_slider.set(10)
set_defaults() # initiate to set the defaults upon first loading the GUI # add reset to defaults button defaults_button = tk.Button(left_frame, text="Defaults", command=set_defaults) defaults_button.place(relx=0.07, rely=0.93, relwidth=0.37, relheight=0.07) # add run button run_button = tk.Button(left_frame, text="Start", font=100, fg='white', bg='#268B49', command=start_kill_toggle) run_button.place(relx=0.50, rely=0.92, relwidth=0.42, relheight=0.09) # fill plotting panel (right_frame) ########## fig = plt.figure(figsize=(15, 15)) fig.set_facecolor('#F0F0F0') fig.clear() # plot environment to fill empty space/give a sense of the simulation pre-run environment = af.import_environment() plt.imshow(environment, cmap='YlGn', vmin=0, vmax=250) plt.axis('off') anim_placeholder = matplotlib.backends.backend_tkagg.FigureCanvasTkAgg(fig, master=right_frame) anim_placeholder._tkcanvas.place(relx=0,rely=0,relwidth=1,relheight=1) # show number of agents and frame number beneath the animation window # these will be updated by the update_labels() function within the update() function n_label = tk.Label(right_frame,text="",anchor=tk.W) n_label.place(relx=0.12,rely=0.9,relwidth=0.5,relheight=0.05) frame_label = tk.Label(right_frame, text="",anchor=tk.E) frame_label.place(relx=0.4,rely=0.9,relwidth=0.5,relheight=0.05) # start gui #root.mainloop()