HiveBrain v1.2.0
Get Started
← Back to all entries
patternpythonMinor

Multigeneration evolution simulator, graphing phenotypic change

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
evolutiongraphingphenotypicmultigenerationsimulatorchange

Problem

I created an evolution simulator. It takes random chance and applies it to phenotypes of species. This was very much for fun, and I would love any input on:

  • Readability of code



  • Efficiency of generation generating



  • Better ways to create dynamic GUI elements



  • Future mod-ability



  • My usage of classes, something I'm historically not great at



  • Any tips on how to improve how the program looks as well as behaves



Also, please feel completely free to just run the program for fun! It is (I hope) cool to see how natural disasters will affect certain phenotypes in a population and how changing the chances of things like mutations and natural disasters affects the population as a whole! I had a lot of fun playing around with the different outcomes.

A quick overview of the buttons in the GUI:

  • Quit: quits the program



  • Export profile: Saves all the current settings to a file that you can access later, using...



  • Load profile: loads a presaved .profile file



  • NUM_ORG: original number of organisms in the population



  • OPT_OFF_NUM: optimal number of offspring



  • NAT_DIS_FREQ: Frequency of natural disasters, use number between 0 and 100



  • GEN_FREQ: how fast the generations reproduce, in seconds



  • POP_LIM: upper limit of the population (between 1000 and 9999)



  • FREQ_MUT: likelihood of a mutation occurring in an organism



  • MAX_MUT: maximum number of mutations in the population



  • GEN_NUM: Number of generations (it works pretty quickly, but results may vary)



  • EXECUTE MAIN: Runs the main function, generation a population list



  • GRAPH: generates the graph based off of the settings above the button



  • Checkboxes: allows you to control what is graphed. For example, unchecking the first box removes the "heat-resistant" organisms from the graph



  • Show Natural Disaster Lines?: Draws a line straight down from a natural disaster to show what generation it occurred at



Resultant graph:

Program in action:

```
#--------------------------------------

Solution

For python an official styleguide exists, PEP 8. It recommends:

  • 4 spaces per indentation level (there are a few instances where you have 5)



  • descriptive variable names (no single letter) in lower_case



  • in argument lists, a space after a comma



  • exactly 2 blank lines before a function and class definition



  • 80 characters max per line



  • whitespace around operators



There are automatic tools to check where your code violates these recommendation pep8, which is commonly installed when you install python.

I would use collections.namedtuple for your IntVars. This allows you to get rid of quite a lot of duplication:

from collections import namedtuple
...
Enum = namedtuple("Enum", "no oon ndf gf pl fm mm pop natcheck")
variables = Enum(*(IntVar() for _ in range(9)))
...
def graph():
    ...
    approved_list = ['null']
    approved_list += [characteristics[i+1] for i, var in enumerate(variables) if var.get() == 1]


Now you can do variables.no to get the variable no. You will have to change every appearance of the variables, though.

Your Checkbutton code can be replaced now with something like:

buttons = []
for row, var in enumerate(variables):
    if row <= 6:
        text = "<---Graph"
    elif row == 7:
        text = "Graph pop"
    elif row == 8:
        text ="Show natural disaster lines?"
    buttons.append(Checkbutton(main, text=text, variable=var))
    if row <= 7:
        buttons[-1].grid(row=row, column=2, sticky=W)
    else:
        buttons[-1].grid(row=row, column=2, sticky=W, columnspan=2)
    buttons[-1].toggle()


And other instances, where you have special behaviour for your different variables

range starts by default at 0, so there is no need to manually set it (unless you want to use step, without using a keyword, like range(3, step=2)).

You should avoid unnecessary globals, whenever you can. They are hard to keep track off, even worse when they are actually modified by functions. In all of your cases you could just make them parameters of the function instead of relying on global.

In class element_input, the button b is not assigned to self.b and will therefore probably not persist (or will at least not be accessible).

In python it is normal to iterate over the elements of a list, not its indices:

with open(profilename, 'w') as out_file:
    for element_name in gui_element_names:
        out_file.write("%s\n" % c[element_name])


Use lists instead of dicts here:

char_effect = {1:'-', 2:'+',3:'+',4:'+',5:'-',6:'+',7:'-',8:'+'}
char_effect = ['-', '+', '+', '+', '-', '+', '-', '+']


The only difference: index - 1
Or even better, make a disaster class (and an entity class):

import random
from itertools import count

ADVANTAGES = "heat-resistant cold-resistant energy-efficient fast slow big small attractive".split()

class Disaster():
    def __init__(self, name, immunities):
        self.name = name
        self.immunities = immunities

    def survivors(self, population):
        return [entity for entity in population if entity.characteristic in self.immunities]

class Entity():
    ID = count()

    def __init__(self, characteristic):
        self.characteristic = characteristic
        self.id = next(Entity.ID)

population = (Entity(random.choice(ADVANTAGES)) for _ in range(100))
landslide = Disaster("landslide", ["fast", "big", "small"])
population = landslide.survivors(population)

Code Snippets

from collections import namedtuple
...
Enum = namedtuple("Enum", "no oon ndf gf pl fm mm pop natcheck")
variables = Enum(*(IntVar() for _ in range(9)))
...
def graph():
    ...
    approved_list = ['null']
    approved_list += [characteristics[i+1] for i, var in enumerate(variables) if var.get() == 1]
buttons = []
for row, var in enumerate(variables):
    if row <= 6:
        text = "<---Graph"
    elif row == 7:
        text = "Graph pop"
    elif row == 8:
        text ="Show natural disaster lines?"
    buttons.append(Checkbutton(main, text=text, variable=var))
    if row <= 7:
        buttons[-1].grid(row=row, column=2, sticky=W)
    else:
        buttons[-1].grid(row=row, column=2, sticky=W, columnspan=2)
    buttons[-1].toggle()
with open(profilename, 'w') as out_file:
    for element_name in gui_element_names:
        out_file.write("%s\n" % c[element_name])
char_effect = {1:'-', 2:'+',3:'+',4:'+',5:'-',6:'+',7:'-',8:'+'}
char_effect = ['-', '+', '+', '+', '-', '+', '-', '+']
import random
from itertools import count

ADVANTAGES = "heat-resistant cold-resistant energy-efficient fast slow big small attractive".split()

class Disaster():
    def __init__(self, name, immunities):
        self.name = name
        self.immunities = immunities

    def survivors(self, population):
        return [entity for entity in population if entity.characteristic in self.immunities]

class Entity():
    ID = count()

    def __init__(self, characteristic):
        self.characteristic = characteristic
        self.id = next(Entity.ID)

population = (Entity(random.choice(ADVANTAGES)) for _ in range(100))
landslide = Disaster("landslide", ["fast", "big", "small"])
population = landslide.survivors(population)

Context

StackExchange Code Review Q#139053, answer score: 4

Revisions (0)

No revisions yet.