Кодирования для моделирования популяции животных


(Это все доступно в репозитории GitHub, если это проще: https://github.com/paulnicholsen27/SpeciesSimulator)

Я представляю это как пример кода для возможной работы, так что я просто ищу любые крупные красные флаги или простые способы украсьте его немного.

Мне дали файл YAML различных животных и среды обитания-это один из каждого:

species:
  - name: kangaroo 
    attributes:
      monthly_food_consumption: 3 #arbitrary unit
      monthly_water_consumption: 4 #arbitrary unit
      life_span: 30 #years 
      minimum_breeding_age: 5 #years
      maximum_breeding_age: 20 #years
      gestation_period: 9 #months
      minimum_temperature: 30 #F
      maximum_temperature: 110 #F
  - name: bear
    attributes:
      monthly_food_consumption: 4
      monthly_water_consumption: 4
      life_span: 50
      minimum_breeding_age: 10
      maximum_breeding_age: 35
      gestation_period: 12
      minimum_temperature: 0
      maximum_temperature: 95
habitats:
    - name: plains
      monthly_food: 100 #arbitrary unit
      monthly_water: 150 #arbitrary unit
      average_temperature:
        summer: 85 #F
        spring: 60 #F
        fall: 50 #F
        winter: 30 #F

Инструкции

SpeciesSimulator

Это был вызов кодирование мне дали, которые я повеселился и узнал немного.

Данные параметры сделали это довольно невозможно для кенгуру, чтобы выжить, но никто не любит кенгуру в любом случае.

Задача: создать приложение, которое импортирует предоставляемых в YAML файл конфигурации (который содержит управления, видов и параметров среды обитания), а затем имитирует ход времени для каждого вида в каждой среде обитания.

Результат: в конце каждого запуска нужно указать следующие данные-

 1. Species
 2. Habitat:
  A. Average Population: x
  B. Max Population: x
  C. Mortality Rate: x%  #overall death percentage
  D. Causes of Death:
      - x% starvation
      - x% age
      - x% cold_weather
      - x% hot_weather

Правила/Примечания:

  • Течением времени:
    • Ход времени должен быть в месяцах. Животные только едят/пьют/приятель/умирают с интервалом 1 месяц и среды обитания обновить только их питание/температура воды/на 1 месяца.
    • Годы значения в конфиге нужно контролировать, сколько лет моделирование должно выполняться.
    • Значение итераций в конфиге должны контролировать, сколько раз вы выполните полноценный симулятор от начала до конца. При выполнении нескольких итераций, финальная статистика должна представлять статистика из всех итераций в сочетании.
  • Вид:
    • Каждый месяц отдельные животные должны потреблять пищу и воду, возраст, и выдержать температурный режим
    • Вам нужно всего лишь запустить один вид одновременно внутри среды обитания (не нужно бежать бок о бок вида)
    • Предположим, животные не моногамны
    • Нет необходимости отслеживать отношения родитель/потомок
  • Смерть (причины):
    • Голодание: 3 последовательных полных месяцев без еды
    • Жажда: 1 полный месяц без еды
    • Старости: возраст life_span
    • Экстремальная температура: 1 полный месяц выше или ниже порогового значения для вида
  • Разведение:
    • Когда вид начинает в новом месте обитания оно должно начинаться с точности 1 мужчина и 1 женщина
    • Когда женщина рожает, секс потомства должны быть выбраны 50:50
    • Размножение контролируется
      • доступные женщины (не беременные и в разведение возраст)
      • благоприятной среды обитания
      • там должно быть больше еды/воды, имеющиеся в настоящее время в среде обитания, чем требуется для поддержания текущего населения
      • Однако, даже когда нет достаточно еды, позволяют селекция происходит по ставке 0.5%.
      • срок гестации (количество месяцев женской беременности до родов)
  • Среда обитания:
    • Среда обитания должна обновить свои продукты питания/вода каждый месяц.
    • Сезоны/Температуры
      • Использовать в этом сезоне/месяц сопоставления 12,1,2=3,4,5 Зима=Весна 6,7,8=лето 9,10,11=осень
      • Температура должна быть обновлена каждый новый месяц и должна колебаться выше/ниже в среднем на 5 градусов, с 0.5% шанс иметь до 15 градусов колебания.

И вот мой код:

import random
import yaml


def yaml_parser(yaml):
    with open('config.txt') as f:
        data = f.read()
        output = yaml.load(data)
    return output


def dice_roller(percentage):
    '''
        given the probability of an event occuring, returns True if
        event is successful
    '''
    chance = random.random()
    percentage = percentage / 100.0
    if chance <= percentage:
        return True
    else:
        return False


class Animal:
    def __init__(self, species, monthly_food_consumption, monthly_water_consumption,
                 life_span, minimum_breeding_age, maximum_breeding_age, gestation_period,
                 minimum_temperature, maximum_temperature, gender=None):
        self.species = species
        self.monthly_food_consumption = monthly_food_consumption
        self.monthly_water_consumption = monthly_water_consumption
        self.life_span_years = life_span
        self.minimum_breeding_age_years = minimum_breeding_age
        self.maximum_breeding_age_years = maximum_breeding_age
        self.life_span = self.life_span_years * 12  # all time units converted to months
        self.minimum_breeding_age = self.minimum_breeding_age_years * 12
        self.maximum_breeding_age = self.maximum_breeding_age_years * 12
        self.gestation_period = gestation_period
        self.minimum_temperature = minimum_temperature
        self.maximum_temperature = maximum_temperature
        self.age = 0
        self.living = True
        if gender:  # 1 is female, 2 is male
            self.gender = gender
        else:
            self.gender = random.randint(1, 2)
        self.pregnant = {'pregnant': False, 'months': 0}
        self.cause_of_death = None
        self.months_without_water = 0
        self.months_without_food = 0
        self.months_of_extreme_temperature = 0
        self.fertility_rate = 80  # TODO: this value is made up for now


class Habitat:
    def __init__(self, name, monthly_food, monthly_water, summer_temp, spring_temp,
                 fall_temp, winter_temp):
        self.name = name
        self.monthly_food = monthly_food
        self.monthly_water = monthly_water
        self.summer_temp = summer_temp
        self.spring_temp = spring_temp
        self.fall_temp = fall_temp
        self.winter_temp = winter_temp
        self.food_supply = 0
        self.water_supply = 0
        self.population = []
        self.population_record = []

    def set_temperature(self, season):
        '''
        sets temperature which fluctuates by up to 5 degrees, with a
        1/200 chance of fluctuating by up to 15 degrees
        '''
        multiplier = 1
        if dice_roller(.5):
            multiplier = 3
        fluctuation = random.randint(-5, 5) * multiplier
        if season == 'summer':
            self.temperature = self.summer_temp + fluctuation
        elif season == 'fall':
            self.temperature = self.fall_temp + fluctuation
        elif season == 'winter':
            self.temperature = self.winter_temp + fluctuation
        elif season == 'spring':
            self.temperature = self.spring_temp + fluctuation

    def refresh_food_and_water(self):
        self.food_supply += self.monthly_food
        self.water_supply += self.monthly_water

    def consume_food_and_water(self):
        '''
            for each living animal in population: if food and water supply is
            adequate, decreases supply by animal's consumption.  Otherwise,
            increases months without food/water by one
        '''
        for animal in self.population:
            if animal.living:
                if self.food_supply >= animal.monthly_food_consumption:
                    self.food_supply -= animal.monthly_food_consumption
                    animal.months_without_food = 0
                else:
                    animal.months_without_food += 1
                if self.water_supply >= animal.monthly_water_consumption:
                    self.water_supply -= animal.monthly_water_consumption
                    animal.months_without_water = 0
                else:
                    animal.months_without_water += 1

    def age_animals(self):
        '''
            increments age of each living animal by one month, along with
            months pregnant if applicable
        '''
        for animal in self.population:
            if animal.living:
                animal.age += 1
                if animal.pregnant['pregnant']:
                    animal.pregnant['months'] += 1

    def breed_animals(self):
        babies = []
        male_available = False
        for animal in self.population:
            if animal.gender == 2 and animal.age >= animal.minimum_breeding_age:
                #check for at least one male of age
                male_available = True
                break
        for animal in self.population:
            if animal.gender == 1 and animal.living:
                if animal.pregnant['pregnant'] and (animal.pregnant['months'] >= animal.gestation_period):
                    animal.pregnant = {'pregnant': False, 'months': 0}
                    new_animal = Animal(
                        animal.species,
                        animal.monthly_food_consumption,
                        animal.monthly_water_consumption,
                        animal.life_span_years,
                        animal.minimum_breeding_age_years,
                        animal.maximum_breeding_age_years,
                        animal.gestation_period,
                        animal.minimum_temperature,
                        animal.maximum_temperature
                    )
                    babies.append(new_animal)
                elif (not animal.pregnant['pregnant'] and
                        animal.minimum_breeding_age <= animal.age < animal.maximum_breeding_age):
                    fertility = animal.fertility_rate
                    if (self.food_supply < animal.monthly_food_consumption or
                            self.water_supply < animal.monthly_water_consumption):
                        fertility *= .005  # reduces fertility rate if insuff. resources
                    if dice_roller(fertility):
                        animal.pregnant['pregnant'] = True
        self.population += babies

    def kill_the_weak(self):
        '''
        sets living to False if any fatal conditions are met and stores
        cause of death.  Also tracks remaining living population.
        '''
        living_count = 0
        for animal in self.population:
            if animal.living:
                living_count += 1
                if animal.age > animal.life_span:
                    animal.living = False
                    animal.cause_of_death = 'age'
                elif animal.months_without_water > 1:
                    animal.living = False
                    animal.cause_of_death = 'thirst'
                elif animal.months_without_food > 3:
                    animal.living = False
                    animal.cause_of_death = 'starvation'
                elif self.temperature > animal.maximum_temperature:
                    animal.months_of_extreme_temperature += 1
                    if animal.months_of_extreme_temperature > 1:
                        animal.living = False
                        animal.cause_of_death = 'hot_weather'
                elif self.temperature < animal.minimum_temperature:
                    animal.months_of_extreme_temperature += 1
                    if animal.months_of_extreme_temperature > 1:
                        animal.living = False
                        animal.cause_of_death = 'cold_weather'
                else:
                    animal.months_of_extreme_temperature = 0

        self.population_record.append(living_count)


def current_season(month):
    '''
        given month number, returns season
    '''
    month_of_year = month % 12
    if month_of_year == 0:
        month_of_year = 12
    seasons = {
        1: 'winter',
        2: 'winter',
        3: 'spring',
        4: 'spring',
        5: 'spring',
        6: 'summer',
        7: 'summer',
        8: 'summer',
        9: 'fall',
        10: 'fall',
        11: 'fall',
        12: 'winter'
    }

    season = seasons[month_of_year]
    return season


def monthly_tasks(month, environment):
    season = current_season(month)
    environment.refresh_food_and_water()
    environment.set_temperature(season)
    environment.kill_the_weak()
    environment.consume_food_and_water()
    environment.breed_animals()
    environment.age_animals()


def percentage_converter(part, whole):
    '''
    converts to a percentage to two decimal places
    '''
    percentage = round(part/float(whole) * 100.0, 2)
    return percentage


def results_generator(species, habitat, iteration_results, months, iterations):
    '''
        iteration_results should consist of a list of completed habitats, returns dictionary of results
    '''
    animal_type = species['name']
    habitat_type = habitat.name
    total_population = 0
    max_population = max([max(environment.population_record) for environment in iteration_results])
    for environment in iteration_results:
        total_population += sum(environment.population_record)
    average_population = total_population / (months * iterations)

    number_of_dead = 0
    death_by_age = 0
    death_by_starvation = 0
    death_by_thirst = 0
    death_by_cold = 0
    death_by_heat = 0
    total_animals = 0
    for environment in iteration_results:
        total_animals += len(environment.population)
        for animal in environment.population:
            if not animal.living:
                number_of_dead += 1
                if animal.cause_of_death == 'age':
                    death_by_age += 1
                elif animal.cause_of_death == 'starvation':
                    death_by_starvation += 1
                elif animal.cause_of_death == 'thirst':
                    death_by_thirst += 1
                elif animal.cause_of_death == 'cold_weather':
                    death_by_cold += 1
                elif animal.cause_of_death == 'hot_weather':
                    death_by_heat += 1
        for cause_of_death in ([death_by_heat, death_by_cold, death_by_thirst,
                                death_by_starvation, death_by_age]):
            cause_of_death = percentage_converter(cause_of_death, number_of_dead)

    mortality_rate = str(round(number_of_dead / float(total_animals) * 100, 2)) + '%'
    causes_of_death = {'age': death_by_age,
                       'starvation': death_by_starvation,
                       'thirst': death_by_thirst,
                       'hot_weather': death_by_heat,
                       'cold_weather': death_by_cold
                       }
    for cause, count in causes_of_death.iteritems():
        causes_of_death[cause] = str(percentage_converter(count, number_of_dead)) + '%'
    results = {habitat_type: {
        'Average Population': average_population,
        'Max Population': max_population,
        'Mortality Rate': mortality_rate,
        'Cause of Death': causes_of_death}
    }
    return results


def simulation_runner():
    '''
    Main function of the simulator, calls functions to parse and run data
    '''
    data = yaml_parser(yaml)
    months = data['years'] * 12
    iterations = data['iterations']
    results = {}
    results['Conditions'] = "Simulation ran for {0} iterations at {1} years per iteration".format(iterations, data['years'])
    for species in data['species']:
        name = species['name']
        animal_results = []
        habitat_population_tracker = []  # will keep track of populations over iterations
        for habitat in data['habitats']:
            iteration_results = []
            for i, iteration in enumerate(range(iterations)):
                environment = Habitat(habitat['name'],
                                      habitat['monthly_food'],
                                      habitat['monthly_water'],
                                      habitat['average_temperature']['summer'],
                                      habitat['average_temperature']['spring'],
                                      habitat['average_temperature']['fall'],
                                      habitat['average_temperature']['winter'],
                                      )
                for gender_code in [1, 2]:  # create initial male and female
                    new_animal = Animal(species['name'],
                                        species['attributes']['monthly_food_consumption'],
                                        species['attributes']['monthly_water_consumption'],
                                        species['attributes']['life_span'],
                                        species['attributes']['minimum_breeding_age'],
                                        species['attributes']['maximum_breeding_age'],
                                        species['attributes']['gestation_period'],
                                        species['attributes']['minimum_temperature'],
                                        species['attributes']['maximum_temperature'],
                                        gender=gender_code
                                        )
                    environment.population.append(new_animal)
                for month in range(months):
                    monthly_tasks(month, environment)
                iteration_results.append(environment)
            animal_results.append(results_generator(species, environment, iteration_results, months, iterations))
        results[name] = animal_results
    return yaml.dump(results, default_flow_style=False)

if __name__ == '__main__':
    print simulation_runner()


Комментарии
3 ответа

Некоторые общие замечания, которые возникают после проверки кода

Именования

Во-первых, вы могли бы использовать немного лучше имен для функций и методов. Хотя нет серебряной пули относительно именования, обычно вы хотите поставить глагол, который объясняет, какие функции делают. Небольшие примеры

percentage_converter Это легче понять, если вы называете это convert_percentage_to_decimal

results_generator звучит лучше, как generate_results

monthly_tasks звучит как перечисление. run_monthly_tasks или execute_monthly_tasks дать представление, что функция имеет определенное влияние на вашу программу

Последнее и не менее важное, выбор полов, как 1 и 2 могло быть и лучше. Почему не male и female? В противном случае вам придется компенсировать с комментариями в каждой части кода

Что лучше?

if animal.gender == 2

Или

if animal.is_female

Документация

Документации в код является произвольным, некоторые функции содержат docstring, некоторые-нет. Специально __init__ метод классов можно использовать более подробное описание, акцентировать внимание на многие параметры, которые они получают.

Другой пример, посмотрим, как эта функция может улучшить с лучшими комментарии (я выбрал один с строкой документации)

def current_season(month):
'''
given month number, returns season
'''

Что если мы поменяли его на что-то подобное?

def get_season(month):
'''
Returns the season of the year, depending on the month provided

:param month: month of the year in numeric format (1-12)
:returns: string with season of the year
'''

Мне даже не надо смотреть на код, чтобы понять, что происходит внутри. Счастье :)

Лучшие практики

Обычно мы хотим избежать чрезвычайно длинные функции, или многие операторов, поскольку поток трудно следовать этому пути

В этом коде функции, такие как simulation_runner с петель вложены в несколько уровней, трудно отслеживать, где мы стоим ровно и что каждый цикл отвечает

Либо мы можем уменьшить количество петель меняется логика, или мы можем извлечь из этого цикла в список осмысленностей

Очень простой пример, по коду вы используете эту структуру много:

for animal in self.population:
if animal.living:

Вы можете уменьшить уровень вложенности введение функция, возвращающая животных:

def _living_animals():
for animal in self.population:
if animal.living:
yield animal

Затем использовать его шпарили (и доходность помогает с потреблением памяти!)

for animal in self._living_animals()

Попробовать и проверить, какие другие циклы в коде, вы можете использовать этот шаблон, вы можете найти парочку

Исключения

Ваш код не обрабатывает любые исключения, и это что-то хитрое дело с файлами.

Вы должны понимать, что иногда файлы не будут иметь безупречный формат, и, по крайней мере, вы хотите, чтобы посмотреть в словарь ключей доступа на simulate_runner функция.

Шалостей программирования

Некоторые части кода можно значительно улучшить, используя силу питона, голые примеров

if chance <= percentage:
return True
else:
return False

Могут быть преобразованы в:

return chance <= percentage

Тернарный оператор также помогает на задания

multiplier = 1
if dice_roller(.5):
multiplier = 3

Будет выглядеть так

multiplier = 3 if dice_roller(.5) else 1

Метод current_season создает словарь каждый раз, когда вы проверить в этом сезоне. Одно улучшение можно создать этот словарь только один раз за пределами функции. Второй создает его, используя списочные словарь

Тестирование

Вы включали тестирование на это задание?

Было бы очень хорошее дополнение ;)

6
ответ дан 7 марта 2018 в 09:03 Источник Поделиться

В дополнение к ответу @А. Роме, я бы хотел указать на этот код:

def breed_animals(self):
...
self.population += babies

def consume_food_and_water(self):
...
for animal in self.population:
...
animal.months_without_food = 0

Вы поддерживаете population в очереди , а не как неупорядоченный набор. Все новые животные идут в конце списка, и питание назначается с начала до конца. Это означает, что вы непропорционально убивать младенцев.

В то время как это может быть правильным, в моделировании смысла, это не будет явной - то есть, я не вижу ничего, что указывает, что вы предназначены , чтобы убивать детей.

Поэтому я хотел бы предложить, что вы либо (1) сделать ваши намерения явного, или (2) рандомизации распределения продовольствия.

3
ответ дан 8 марта 2018 в 07:03 Источник Поделиться

несколько советов о животном

Вместо того, чтобы определить поведение старение для каждого животного HabitatЯ бы реферат что Animal класс

Что-то вроде этого:

class Animal:
def __init__(self, species, monthly_food_consumption, monthly_water_consumption,
life_span, minimum_breeding_age, maximum_breeding_age, gestation_period,
minimum_temperature, maximum_temperature):
self.species = species
self.monthly_food_consumption = monthly_food_consumption
self.monthly_water_consumption = monthly_water_consumption
self.life_span_years = life_span
self.minimum_breeding_age_years = minimum_breeding_age
self.maximum_breeding_age_years = maximum_breeding_age
self.life_span = self.life_span_years * 12 # all time units converted to months
self.minimum_breeding_age = self.minimum_breeding_age_years * 12
self.maximum_breeding_age = self.maximum_breeding_age_years * 12
self.gestation_period = gestation_period
self.minimum_temperature = minimum_temperature
self.maximum_temperature = maximum_temperature
self.age = 0
self.living = True
self.pregnant = None
self.cause_of_death = None
self.months_without_water = 0
self.months_without_food = 0
self.months_of_extreme_temperature = 0
self.fertility_rate = 80

def get_older(self):
self.age += 1
if self.age > self.life_span:
self.living = False
self.cause_of_death = 'old age'
if self.pregnant:
self.pregnant += 1

def eat(self, reserve):
succes = reserve >= self.monthly_food_consumption
if success:
reserve -= self.monthly_food_consumption
self.months_without_food = 0
else:
self.months_without_food += 1
if self.months_without_food > 3:
self.living = False
self.cause_of_death = 'hunger'
return reserve

в Habitat

деф age_animals(самовыдвижение):
для животного в себе.living_animals():
животного.get_older()

дефа Селф.consume_food():

reserve = self.food_supply
for animal in self.living_animals():
reserve = animal.eat(reserve)

и так далее

Фабрика

Вместо того, чтобы передать атрибуты по умолчанию для видов для каждого экземпляра, вы можете работать с фабрикой (заимствовано из этого так ответа)

def animal_factory(species, monthly_food_consumption, monthly_water_consumption,
life_span, minimum_breeding_age, maximum_breeding_age, gestation_period,
minimum_temperature, maximum_temperature):
def __init__(self, gender=None):
genders = ['male', 'female']
if gender not in genders:
gender = genders[random.randint(0, 1)]

setattr(self, 'gender', gender)
Animal.__init__(self, species, monthly_food_consumption, monthly_water_consumption,
life_span, minimum_breeding_age, maximum_breeding_age, gestation_period,
minimum_temperature, maximum_temperature)
return type(species, (Animal,), {'__init__': __init__})

Что вы можете использовать его в качестве

Kangoroo = animal_factory('kangaroo', 
monthly_food_consumption=3,
monthly_water_consumption=4,
life_span=30,
minimum_breeding_age=5,
maximum_breeding_age=20,
gestation_period=9,
minimum_temperature=30,
maximum_temperature=100,
)

и использовать animal = Kangoroo() чтобы сделать новый кенгуру

Беременных

вместо кодирования pregnant состояние животного в качестве dictвы можете просто использовать intи использовать None для животных, которые не беременны

тогда строка 160 изменениями от if animal.pregnant['pregnant'] and (animal.pregnant['months'] >= animal.gestation_period): для if animal.pregnant and animal.pregnant > animal.gestation_period):

2
ответ дан 8 марта 2018 в 09:03 Источник Поделиться