Source code for blossom.simulation.parameter_io

"""
Load information from parameter files and construct world and
organism objects at the initial timestep.
"""

import glob
import configparser
import yaml
import json
import numpy as np
import copy

from .utils import cast_to_list
from .world import World
from .organism import Organism
from . import default_fields


[docs] def load_world(fn=None, init_dict={}): """ Load world from either parameter file or dictionary and construct initial World object. Parameters ---------- fn : str Input filename of parameter file. init_dict : dict Dictionary containing world parameters. Returns ------- world : World World object constructed from the parameter file. """ if fn: return load_world_from_param_file(fn) else: return load_world_from_dict(init_dict)
[docs] def load_world_from_dict(init_dict): world_init_dict = {} for (field, default) in default_fields.world_fields.items(): world_init_dict[field] = init_dict.get(field, default) # Check world size parameters, set dimensionality value accordingly if type(world_init_dict['world_size']) is int: world_init_dict['world_size'] = [world_init_dict['world_size']] else: assert type(world_init_dict['world_size']) is list assert len(world_init_dict['world_size']) <= 2 world_init_dict['dimensionality'] = len(world_init_dict['world_size']) for i in world_init_dict['world_size']: assert type(i) is int # Check that field list sizes are consistent with each other field_lengths = [] fields = [] for field in ['water', 'food', 'obstacles']: if world_init_dict[field] is not None: field_lengths.append(len(world_init_dict[field])) fields.append(field) if len(field_lengths) >= 2: assert field_lengths[0] == field_lengths[1] if len(field_lengths) == 3: assert field_lengths[1] == field_lengths[2] # Check that field list sizes are consistent with the world size if len(fields) >= 1: field = fields[0] assert world_init_dict['world_size'][0] == len(world_init_dict[field]) if len(field) == 2: assert world_init_dict['world_size'][1] \ == len(world_init_dict[field][0]) return World(world_init_dict)
[docs] def load_world_from_param_file(fn): """ Load world parameter file and construct initial World object. Parameters ---------- fn : str Input filename of parameter file. Returns ------- world : World World object constructed from the parameter file. """ env_file = glob.glob(fn) if len(env_file) == 0: raise IndexError('There is no environment configuration file in the ' + 'current directory.') if len(env_file) > 1: print('There are multiple environment configuration files in the ' + 'current directory. There should only be one environment ' + 'configuration file. Selecting the first provided..') env_file = env_file[0] world_init_dict = {} # Load from config file config_world = configparser.ConfigParser() config_world.read(env_file) # environment_filename: str, or 'None' environment_filename = config_world.get('Overall Parameters', 'environment_filename') if environment_filename != 'None': with open(environment_filename, 'r') as f: initial_environment_dict = json.load(f) world_init_dict['water'] = initial_environment_dict['water'] world_init_dict['food'] = initial_environment_dict['food'] world_init_dict['obstacles'] = initial_environment_dict['obstacles'] if type(world_init_dict['water'][0]) is list: world_init_dict['dimensionality'] = 2 world_init_dict['world_size'] = [len(world_init_dict['water']), len(world_init_dict['water'][0])] else: world_init_dict['dimensionality'] = 1 world_init_dict['world_size'] = [len(world_init_dict['water'])] else: # world_size: space delimited ints, or 'None' # example: world_size = 10 10 world_size = config_world.get('Overall Parameters', 'world_size') if world_size == 'None': # this is probably harder to do, # since we'd have to allow for infinite bounds... world_size = None return -1 else: world_size = [int(L) for L in world_size.split()] world_init_dict['world_size'] = world_size world_init_dict['dimensionality'] = len(world_size) if len(world_size) == 2: blank_vals = [[0 for x in range(world_size[1])] for x in range(world_size[0])] elif len(world_size) == 1: blank_vals = [0 for x in range(world_size)[0]] else: raise ValueError world_init_dict['water'] = copy.deepcopy(blank_vals) world_init_dict['food'] = copy.deepcopy(blank_vals) world_init_dict['obstacles'] = copy.deepcopy(blank_vals) return World(world_init_dict)
[docs] def load_species(fns=None, init_dicts=[{}], init_world=World({}), custom_module_fns=[]): """ Load organisms from available species parameter files or dictionaries. Parameters ---------- fns : list of str Input filenames of species parameter files. Different species get different species parameter files, from which the individual organisms are initialized. init_dicts : list of dict Parameter dicts for each species. init_world : World Initial World instance for this Universe. custom_module_fns : list of str List of external Python scripts containing custom organism behaviors. :mod:`blossom` will search for methods within each filename included here. Returns ------- population_dict : dict of Organisms A dict of Organism objects constructed from the parameter file. """ if fns: return load_species_from_param_files(fns, init_world, custom_module_fns) else: return load_species_from_dict(init_dicts, init_world, custom_module_fns)
[docs] def create_organisms(species_init_dict, init_world=World({}), location_callback=None, seed=None): ''' Make organism list from an species_init_dict either provided directly or scraped from parameter file. All organisms are from a single species. ''' rng = np.random.default_rng(seed) organism_list = [] list_field_keys = [] for key in species_init_dict.keys(): if type(species_init_dict[key]) is list: if key != 'custom_module_fns': list_field_keys.append(key) # Generate all organisms initial_population = species_init_dict['population_size'] for i in range(initial_population): organism_init_dict = { key: val for key, val in species_init_dict.items() if not key == 'population_size' } for key in list_field_keys: organism_init_dict[key] = organism_init_dict[key][i] if 'initial_locations' in species_init_dict: location = organism_init_dict['initial_locations'] elif location_callback: location = location_callback(init_world.world_size) elif 'location_callback' in species_init_dict: location = (species_init_dict['location_callback'] (init_world.world_size)) else: # Vary organism location randomly location = [] for i in range(init_world.dimensionality): location.append(rng.integers(0, init_world.world_size[i])) organism_init_dict['location'] = location # Add organism to organism list organism_list.append(Organism(organism_init_dict, seed=seed)) return organism_list
[docs] def load_species_from_dict(init_dicts, init_world, custom_module_fns=None, seed=None): """ Create a list of organisms loaded from Python dicts. Parameters ---------- init_dicts : list of dict Input species dictionaries from which the individual organisms are initialized. Each dictionary is for a different species. init_world : World Initial World instance for this Universe. custom_module_fns : list of str List of external Python scripts containing custom organism behaviors. :mod:`blossom` will search for methods within each filename included here. seed : int, Generator, optional Random seed for the simulation Returns ------- population_dict : dict of Organisms A dict of Organism objects constructed from the parameter file. """ init_dicts = cast_to_list(init_dicts) population_dict = {} for init_dict in init_dicts: species_init_dict = {} # Set up defaults based on species parameters for (field, default) in default_fields.species_fields.items(): species_init_dict[field] = init_dict.get(field, default) # Set up custom fields provided in initialization dictionary init_keys = set(init_dict.keys()) default_keys = set(default_fields.species_fields.keys()) for custom_field in (init_keys - default_keys): species_init_dict[custom_field] = init_dict[custom_field] assert 'population_size' in init_dict population_size = init_dict['population_size'] assert type(population_size) is int species_init_dict['population_size'] = population_size assert type(species_init_dict['species_name']) == str for field in ['movement_type', 'reproduction_type', 'drinking_type', 'eating_type', 'action_type']: assert (species_init_dict[field] is None or type(species_init_dict[field]) is str) for field in ['dna_length']: assert type(species_init_dict[field]) is int if species_init_dict['drinking_type']: assert ( species_init_dict['water_capacity'] is not None and species_init_dict['water_initial'] is not None and species_init_dict['water_metabolism'] is not None and species_init_dict['water_intake'] is not None and species_init_dict['max_time_without_water'] is not None ) if species_init_dict['eating_type']: assert ( species_init_dict['food_capacity'] is not None and species_init_dict['food_initial'] is not None and species_init_dict['food_metabolism'] is not None and species_init_dict['food_intake'] is not None and species_init_dict['max_time_without_food'] is not None ) for field in ['max_age', 'max_time_without_food', 'max_time_without_water', 'mutation_rate', 'food_capacity', 'food_initial', 'food_metabolism', 'food_intake', 'water_capacity', 'water_initial', 'water_metabolism', 'water_intake']: if type(species_init_dict[field]) is list: assert (len(species_init_dict[field]) == population_size) elif type(species_init_dict[field]) in [int, float]: species_init_dict[field] = ([species_init_dict[field]] * population_size) else: assert species_init_dict[field] is None if not species_init_dict['custom_module_fns']: # Track custom method file paths species_init_dict['custom_module_fns'] = custom_module_fns if 'initial_locations' in init_dict: assert len(init_dict['initial_locations']) == population_size for location in init_dict['initial_locations']: assert len(location) == init_world.dimensionality species_init_dict['initial_locations'] \ = init_dict['initial_locations'] species_organism_list = create_organisms(species_init_dict, init_world, seed=seed) # Populate population dict with relevant bulk stats and organism lists population_dict[species_init_dict['species_name']] = { 'statistics': { 'total': population_size, 'alive': population_size, 'dead': 0 }, 'organisms': species_organism_list } return population_dict
[docs] def load_species_from_param_files(fns, init_world, custom_module_fns=None, seed=None): """ Load all available species parameter files. Parameters ---------- fns : list of str Input filenames of species parameter files. Different species get different species parameter files, from which the individual organisms are initialized. init_world : World Initial World instance for this Universe. custom_module_fns : list of str List of external Python scripts containing custom organism behaviors. :mod:`blossom` will search for methods within each filename included here. seed : int, Generator, optional Random seed for the simulation Returns ------- population_dict : dict of Organisms A dict of Organism objects constructed from the parameter file. """ # Find organism filenames, can be a list of patterns fns = cast_to_list(fns) org_files = [fn for pattern in fns for fn in glob.glob(pattern)] # Initialize list of dictionaries to hold all organism parameters # Each dictionary contains parameters for a single species population_dict = {} for i, org_file in enumerate(org_files): # Initialize temporary dict to store species parameters species_init_dict = {} # Parameters that must be str or None param_str = ['species_name', 'action_type', 'movement_type', 'reproduction_type', 'drinking_type', 'eating_type'] # Parameters that must be int param_int = ['population_size', 'dna_length'] # Parameters that must be int or 'None' or 'inf' param_int_none_inf = ['max_age', 'max_time_without_food', 'max_time_without_water', 'mutation_rate', 'food_capacity', 'food_initial', 'food_metabolism', 'food_intake', 'water_capacity', 'water_initial', 'water_metabolism', 'water_intake'] # Parameters that must be float param_float = ['proportion_m', 'proportion_f', 'proportion_a', 'proportion_h'] # Parameters that must be bool param_bool = ['can_mutate'] # Load from config file config_org = configparser.ConfigParser() config_org.read(org_file) # Cycle through all parameters in the config file, # converting them to proper types as specified above for section in config_org.sections(): for (key, val) in config_org.items(section): if key in param_str: pass elif key in param_int: val = int(val) elif key in param_int_none_inf: if val != 'None' and type(val) not in [int, float]: val = float(val) elif key in param_float: val = float(val) elif key in param_bool: val = (val == 'True') else: print(key, val) raise NameError('Key is not a valid parameter') # ensure 'None' parameters are set to None if val == 'None': val = None species_init_dict[key] = val # Track custom method file paths species_init_dict['custom_module_fns'] = custom_module_fns species_organism_list = create_organisms(species_init_dict, init_world, seed=seed) # Populate population dict with relevant bulk stats and organism lists population_dict[species_init_dict['species_name']] = { 'statistics': { 'total': species_init_dict['population_size'], 'alive': species_init_dict['population_size'], 'dead': 0 }, 'organisms': species_organism_list } return population_dict
[docs] def parse_config_number(x): """ If config number is the string 'inf', use ``np.inf``. """ if x == 'inf': x = np.inf return x
[docs] def load_from_config(fn, seed=None): """ Create initial population and world from .yml configuration file. """ with open(fn, 'r') as f: cfg = yaml.load(f, Loader=yaml.FullLoader) initial_seed = cfg.get('seed', seed) if initial_seed is None: initial_seed = np.random.default_rng().integers(2**32) rng = np.random.default_rng(initial_seed) config_params = { 'initial_seed': initial_seed, 'rng': rng } # Load world world_cfg = cfg['world'] size = world_cfg['size'] world_init_dict = {'world_size': size} if 'water' in world_cfg: peak = world_cfg['water']['peak'] world_init_dict['water'] = np.full(size, parse_config_number(peak)) else: world_init_dict['water'] = np.full(size, np.inf) if 'food' in world_cfg: peak = world_cfg['food']['peak'] if peak == 'inf': peak = np.inf world_init_dict['food'] = np.full(size, peak) else: world_init_dict['food'] = np.full(size, np.inf) if 'obstacles' in world_cfg: peak = world_cfg['obstacles']['peak'] if peak == 'inf': peak = np.inf world_init_dict['obstacles'] = np.full(size, peak) else: world_init_dict['obstacles'] = np.full(size, 0) world = load_world_from_dict(world_init_dict) # Load species species_cfgs = cfg['species'] species_init_dicts=[] for species_cfg in species_cfgs: species_init_dict = { 'population_size': species_cfg['population'], 'species_name': species_cfg['name'], 'max_age': parse_config_number(species_cfg['max_age']), 'custom_module_fns': species_cfg['linked_modules'] } # Action action = species_cfg['action'] if isinstance(action, str): species_init_dict['action_type'] = action elif isinstance(action, dict): species_init_dict['action_type'] = action['type'] # Movement movement = species_cfg.get('movement') if isinstance(movement, str): species_init_dict['movement_type'] = movement elif isinstance(movement, dict): species_init_dict['movement_type'] = movement['type'] # Reproduction reproduction = species_cfg.get('reproduction') if isinstance(reproduction, str): species_init_dict['reproduction_type'] = reproduction elif isinstance(reproduction, dict): species_init_dict['reproduction_type'] = reproduction['type'] # Drinking drinking = species_cfg.get('drinking') if drinking is not None: species_init_dict.update({ 'drinking_type': drinking['type'], 'water_capacity': drinking['capacity'], 'water_initial': drinking['initial'], 'water_metabolism': drinking['metabolism'], 'water_intake': drinking['intake'], 'max_time_without_water': drinking['days_without'] }) # Eating eating = species_cfg.get('eating') if eating is not None: species_init_dict.update({ 'eating_type': eating['type'], 'food_capacity': eating['capacity'], 'food_initial': eating['initial'], 'food_metabolism': eating['metabolism'], 'food_intake': eating['intake'], 'max_time_without_food': eating['days_without'] }) species_init_dicts.append(species_init_dict) population_dict = load_species_from_dict(species_init_dicts, world, seed=rng) return population_dict, world, config_params