Skip to content

Commit

Permalink
Merge pull request #10 from blossom-evolution/redo_io
Browse files Browse the repository at this point in the history
Redo IO, move to config files and dashboard
  • Loading branch information
bbrzycki authored Apr 22, 2024
2 parents e8ff9cf + 4b684e4 commit e863f95
Show file tree
Hide file tree
Showing 34 changed files with 1,126 additions and 534 deletions.
35 changes: 35 additions & 0 deletions .readthedocs.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# Read the Docs configuration file for Sphinx projects
# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details

# Required
version: 2

# Set the OS, Python version and other tools you might need
build:
os: ubuntu-22.04
tools:
python: "3.11"
# You can also specify other tool versions:
# nodejs: "20"
# rust: "1.70"
# golang: "1.20"

# Build documentation in the "docs/" directory with Sphinx
sphinx:
configuration: docs/source/conf.py
# You can configure Sphinx to use a different builder, for instance use the dirhtml builder for simpler URLs
# builder: "dirhtml"
# Fail on all warnings to avoid broken references
fail_on_warning: true

# Optionally build your docs in additional formats such as PDF and ePub
formats:
- pdf
# - epub

# Optional but recommended, declare the Python requirements required
# to build your documentation
# See https://docs.readthedocs.io/en/stable/guides/reproducible-builds.html
python:
install:
- requirements: requirements.txt
60 changes: 59 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,63 @@
# blossom [![PyPI version](https://badge.fury.io/py/blossom.svg)](https://badge.fury.io/py/blossom) [![Build Status](https://travis-ci.org/blossom-evolution/blossom.svg?branch=master)](https://travis-ci.org/blossom-evolution/blossom) [![Documentation Status](https://readthedocs.org/projects/blossom/badge/?version=latest)](https://blossom.readthedocs.io/en/latest/?badge=latest)

Blossom is a Python package for simulating the evolution of organisms. Blossom supports both one and two dimensional worlds.
Blossom is a Python package for simulating the dynamics of organism populations. Blossom supports both one and two dimensional worlds.

## Installation

You can use pip to install the latest version of the package automatically:

```
pip install blossom
```

Alternately, execute:

```
git clone [email protected]:blossom-evolution/blossom.git
python setup.py install
```

## Basic Usage

To start a simulation project, create a new directory to house all custom
scripts, including a configuration file `config.yml`. In this config file, you
specify species parameters, including starting population, max age, and action
methods. Some action methods are built-in (for movement, eating, drinking, and
reproduction), but you may use custom methods defined in external Python
scripts, which are imported at runtime by Blossom via `linked_modules`.

World parameters are also specified in the config file, including dimensions
and the distribution of water and food. You may also set limits on the number
of timesteps and organisms, in order to control the simulation in case of
runaway populations.

With your project directory set up, you may run the simulation using the
included command-line interface (CLI):

```
blossom run
```

Note that for reproducibility, you can set the random seed either in the config
file or at the CLI. For additional options, run `blossom run -h`.


## Dashboard

Blossom provides a dashboard that runs in your local browser, tracking the
progress of your population runs.

Initiate the dashboard, run

```
blossom dashboard TRACK_DIR [-p PORT]
```
where `TRACK_DIR` is the simulation project directory. You can then view the
dashboard at `localhost:PORT`.

![dashboard screenshot](media/blossom-dashboard.png)


### GIF of basic simulation

![example simulation](media/combined.gif)
2 changes: 1 addition & 1 deletion blossom/_version.py
Original file line number Diff line number Diff line change
@@ -1 +1 @@
__version__ = '1.4.0'
__version__ = '2.0.0'
5 changes: 4 additions & 1 deletion blossom/blossom_exe.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@
import click

from ._version import __version__
from .visualization import dashboard
from .simulation import universe
from .visualization import dashboard, render


CONTEXT_SETTINGS = dict(help_option_names=['-h', '--help'])
Expand All @@ -17,7 +18,9 @@ def cli(ctx):
pass


cli.add_command(universe.run_universe)
cli.add_command(dashboard.dashboard)
cli.add_command(render.make_gif)


if __name__ == '__main__':
Expand Down
2 changes: 1 addition & 1 deletion blossom/simulation/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,6 @@
from . import world_generator
from . import utils
from .population_funcs import (
hash_by_id, hash_by_position, organism_filter, organism_list_copy,
hash_by_id, hash_by_location, organism_filter, organism_list_copy,
get_organism_list, get_population_dict
)
113 changes: 86 additions & 27 deletions blossom/simulation/dataset_io.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,27 +5,33 @@

import copy
import json
import pickle
from pathlib import Path
import numpy as np

from .world import World
from .organism import Organism


def load_universe(fn):
def load_universe(fn, seed=None):
"""
Load dataset file from JSON.
Parameters
----------
fn : str
Input filename of saved universe dataset.
Input filename of saved universe dataset
seed : int, Generator, optional
Random seed for the simulation
Returns
-------
population_dict : dict
A dict of Organism objects reconstructed from the saved dataset.
A dict of Organism objects reconstructed from the saved dataset
world : World
World object reconstructed from the saved dataset.
World object reconstructed from the saved dataset
seed : int, Generator
Numpy random number generator from last timestep
"""
with open(fn, 'r') as f:
universe_dict = json.load(f)
Expand All @@ -42,49 +48,102 @@ def load_universe(fn):
for organism_dict in population_dict_json[species]['organisms']
]

return population_dict, world
seed_fn = Path(fn).with_suffix('.seed')
if seed is None and seed_fn.is_file():
seed = universe_dict['info']['initial_seed']
with open(seed_fn, 'rb') as f:
rng = pickle.load(f)
else:
if seed is None:
seed = np.random.default_rng().integers(2**32)
rng = np.random.default_rng(seed)
config_params = {
'initial_seed': seed,
'rng': rng
}

return population_dict, world, config_params


def save_universe(population_dict, world, fn):
def save_universe(universe):
"""
Save population_dict and world to file in JSON format.
Parameters
----------
population_dict : dict
Dict of Organisms to write to file.
world : World
World attributes to write to file.
fn : str
Output filename of saved universe dataset.
universe : Universe
Universe containing organism
"""
padded_time = str(universe.current_time).zfill(universe.pad_zeros)
data_fn = (
universe.run_data_dir / f'{universe.project_dir.name}.{padded_time}.json'
)
log_fn = (
universe.run_logs_dir / f'{universe.project_dir.name}.{padded_time}.log'
)

population_dict_json = {}
for species in population_dict:
for species in universe.population_dict:
population_dict_json[species] = {}
population_dict_json[species]['statistics'] = population_dict[species]['statistics']
population_dict_json[species]['statistics'] = universe.population_dict[species]['statistics']
population_dict_json[species]['organisms'] = [
organism.to_dict()
for organism in population_dict[species]['organisms']
for organism in universe.population_dict[species]['organisms']
]
universe_dict = {
'population': population_dict_json,
'world': world.to_dict()
'world': universe.world.to_dict(),
'info': {
'initial_seed': universe.initial_seed
}
}
with open(data_fn, 'w') as f:
json.dump(universe_dict, f, indent=2, cls=NPEncoder)

with open(fn, 'w') as f:
json.dump(universe_dict, f, indent=2)


def save_universe_logs(population_dict, world, fn, elapsed_time):
log_dict = {
'species': {
species: population_dict[species]['statistics']
for species in population_dict
species: universe.population_dict[species]['statistics']
for species in universe.population_dict
},
'world': {
'timestep': world.current_time,
'elapsed_time': elapsed_time
'timestep': universe.world.current_time,
'elapsed_time': universe.elapsed_time
},
'info': {
'initial_seed': universe.initial_seed,
'size': data_fn.stat().st_size
}
}
with open(fn, 'w') as f:
json.dump(log_dict, f, indent=2)
with open(log_fn, 'w') as f:
json.dump(log_dict, f, indent=2, cls=NPEncoder)

# Save seed information for last completed timestep
last_padded_time = str(universe.current_time-1).zfill(universe.pad_zeros)
last_seed_fn = (
universe.run_data_dir / f'{universe.project_dir.name}.{last_padded_time}.seed'
)
last_seed_fn.unlink(missing_ok=True)
seed_fn = (
universe.run_data_dir / f'{universe.project_dir.name}.{padded_time}.seed'
)
with open(seed_fn, 'wb') as f:
pickle.dump(universe.rng, f)


class NPEncoder(json.JSONEncoder):
"""
Class to help serialize numpy types to json.
"""
def default(self, obj):
if isinstance(obj, np.integer):
return int(obj)
elif isinstance(obj, np.bool_):
return bool(obj)
elif isinstance(obj, np.floating):
if np.isnan(obj):
return None
return float(obj)
elif isinstance(obj, np.ndarray):
return obj.tolist()
else:
return super().default(obj)
2 changes: 1 addition & 1 deletion blossom/simulation/default_fields.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
'age_at_death': None,
'cause_of_death': None,
'last_action': None,
'position': [0],
'location': [0],
'sex': None,
'ancestry': [],
'water_current': None,
Expand Down
Loading

0 comments on commit e863f95

Please sign in to comment.