Skip to content

Commit

Permalink
0.0.0 alpha1 (#43)
Browse files Browse the repository at this point in the history
* Initial commit with basic version of EA loop (#2)

Contains the basic loop, but very rough. Everything needs cleanup and API improvements.

    Isaac Gym simulation environment.
    Modular robot framework that can be used to create robots for the simulation environment.
    An optimization framework for evolutionary processes.
    A database system to store results and provide recovery in case of failure.

* Expose sim params in isaac gym local runner

* For isaac runner, add a bunch of todos. Make initial pos and rot configurable.

* Review apis (#10)

* For modular robots: remove slot, rotation is now directly part of Module

* Remove back from BRICK module, because that's where it's attached so it can't be filled

* Add brain 'cpg_random', which randomly initialized cpg weights

* Remove old file

* Rework an error message in ea selection multiple_unique

* Added TODOs to database that transactions still need to be implmeented

* Remove some more old BACK from BRICK

Co-authored-by: Aart Stuurman <[email protected]>

* Add some missing packages to setup.py

* Correct values in to_urdf

* Fix bug in ea were population and new individuals were swapped in a function call

* Add headless for isaac runner

* Store individuals seperate from generation (#19)

* Added individual class to ea

* Individuals stored seperately from generations. generations now use references

* Individual members now individually serialized

* Create Serializable class. Use it to serialize Genotype and Evaluation nicer

* Add isaac as a prereq for isaac env

* Individuals now also list parents

Co-authored-by: Aart Stuurman <[email protected]>

* Improve cpg performance (#20)

Co-authored-by: Aart Stuurman <[email protected]>

* In EA, rename evaluation to fitness. (#24)

Co-authored-by: Aart Stuurman <[email protected]>

* 22 simulation history (#25)

* Can set update frequency.

* Implement sampling frequency

* Add getting path from AnyView

* Fixed bug in DictView where dict 'contains' was not properly programmed.

* EA optimizer now makes space in the database were the evaluator can do its work.

* Save individual rng objects for each generation

* Make physics State serializable

Co-authored-by: Aart Stuurman <[email protected]>

* Emergency fix: fix incorrect assertion in EA

* 21 analysis - intermediate merge (#29)

* Add analyzer to ea optimization

* Fix bug in ea analyzer where Generations did not always create Generation correctly.

* Improve serialization of cppnneat genotypes

* Add plot script for ea fitness

* Add modular robot rerunner. Update ea plotter with max min avg

* add prepare_sim to isaac runner so gpu can be used

Co-authored-by: Aart Stuurman <[email protected]>

* Fix py.typed. Fix many mypy errors - intermediate merge for #3 (#30)

Co-authored-by: Aart Stuurman <[email protected]>

* 28 improve database performance (#32)

* Completely reworked db interface and inner workings. Db implementations can now be significantly faster.

* Removed all existing db implementations

* Added SQLite implementation

* Updated all existing code to use the new db interface

Co-authored-by: Aart Stuurman <[email protected]>

* 15 isaac leak (#34)

* Isaac now runs in a subprocess every batch

Co-authored-by: Aart Stuurman <[email protected]>

* Removed asyncinit dependency (#35)

Co-authored-by: Aart Stuurman <[email protected]>

* Add aabb calculation for actor. Can be used to know how far to put robots above ground. (#37)

Co-authored-by: Aart Stuurman <[email protected]>

* 27 robot friction (#39)

* Add static friction to robot rigid bodies

* Set friction paremeters to be arbitrarily chosen values that look good

* Remove some old imports

Co-authored-by: Aart Stuurman <[email protected]>

* for cppnneat brain genotype set output func to signed sign (#41)

Co-authored-by: Aart Stuurman <[email protected]>

* Add modular robot size measurement and number of bricks to modular robot (#42)

Co-authored-by: Aart Stuurman <[email protected]>

* Add support for multiple runs to plt_ea_fitness script.

Co-authored-by: Aart Stuurman <[email protected]>
  • Loading branch information
surgura and Aart Stuurman authored Jan 6, 2022
1 parent 08e224f commit a6901d3
Show file tree
Hide file tree
Showing 91 changed files with 4,270 additions and 8 deletions.
19 changes: 11 additions & 8 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ parts/
sdist/
var/
wheels/
pip-wheel-metadata/
share/python-wheels/
*.egg-info/
.installed.cfg
Expand Down Expand Up @@ -50,6 +49,7 @@ coverage.xml
*.py,cover
.hypothesis/
.pytest_cache/
cover/

# Translations
*.mo
Expand All @@ -72,6 +72,7 @@ instance/
docs/_build/

# PyBuilder
.pybuilder/
target/

# Jupyter Notebook
Expand All @@ -82,7 +83,9 @@ profile_default/
ipython_config.py

# pyenv
.python-version
# For a library or package, you might want to ignore these files since the code is
# intended to run in multiple environments; otherwise, check them in:
# .python-version

# pipenv
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
Expand All @@ -103,12 +106,6 @@ celerybeat.pid

# Environments
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/

# Spyder project settings
.spyderproject
Expand All @@ -127,3 +124,9 @@ dmypy.json

# Pyre type checker
.pyre/

# pytype static type analyzer
.pytype/

# Cython debug symbols
cython_debug/
Empty file added core/revolve2/core/__init__.py
Empty file.
Empty file.
40 changes: 40 additions & 0 deletions core/revolve2/core/analysis/modular_robot_rerunner.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
"""
Rerun(watch) a modular robot in isaac gym.
"""

from pyrr import Quaternion, Vector3
from revolve2.core.modular_robot import ModularRobot
from revolve2.core.physics.control import ActorController
from revolve2.core.physics.env import ActorControl, Batch, Environment, PosedActor
from revolve2.envs.isaacgym import LocalRunner


class ModularRobotRerunner:
_controller: ActorController

async def rerun(self, robot: ModularRobot, control_frequency: float) -> None:
batch = Batch(
simulation_time=1000000,
sampling_frequency=0.0001,
control_frequency=control_frequency,
control=self._control,
)

actor, self._controller = robot.make_actor_and_controller()

env = Environment()
env.actors.append(PosedActor(actor, Vector3([0.0, 0.0, 0.1]), Quaternion()))
batch.environments.append(env)

runner = LocalRunner(LocalRunner.SimParams())
await runner.run_batch(batch)

def _control(self, dt: float, control: ActorControl) -> None:
self._controller.step(dt)
control.set_dof_targets(0, 0, self._controller.get_dof_targets())


if __name__ == "__main__":
print(
"This file cannot be ran as a script. Import it and use the contained classes instead."
)
95 changes: 95 additions & 0 deletions core/revolve2/core/analysis/plot_ea_fitness.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
"""
Plot average, min, and max fitness over generations, using the results of the evolutionary optimizer.
Assumes fitness is a float and database is files.
"""

import argparse
from statistics import mean

import matplotlib.pyplot as plt
from revolve2.core.database.sqlite import Database
from revolve2.core.optimization.ea import Analyzer as EaAnalyzer
from revolve2.core.optimization.ea.analyzer import Generation


async def main() -> None:
parser = argparse.ArgumentParser()
parser.add_argument(
"databases",
nargs="+",
help="The databases to make plots for. If more then one database(multiple runs of the same experiment) is provided, their respective plots are averaged into one single plot.",
)
args = parser.parse_args()

max_fitnesses = []
min_fitnesses = []
mean_fitnesses = []

for db_file in args.databases:
db = await Database.create(db_file)
with db.begin_transaction() as txn:
analyzer = EaAnalyzer(txn, db.root)

max_fitness = [
max(
[
analyzer.individuals[individual].fitness
for individual in generation
],
)
for generation in analyzer.generations
]

min_fitness = [
min(
[
analyzer.individuals[individual].fitness
for individual in generation
],
)
for generation in analyzer.generations
]

mean_fitness = [
mean(
[
analyzer.individuals[individual].fitness
for individual in generation
],
)
for generation in analyzer.generations
]
max_fitnesses.append(max_fitness)
min_fitnesses.append(min_fitness)
mean_fitnesses.append(mean_fitness)

assert all(
len(max_fitnesses[0]) == len(x) for x in max_fitnesses
), "Not all databases have an equal amount of generations."

mean_max_fitness = [mean(x) for x in list(map(list, zip(*max_fitnesses)))]
mean_min_fitness = [mean(x) for x in list(map(list, zip(*min_fitnesses)))]
mean_avg_fitness = [mean(x) for x in list(map(list, zip(*mean_fitnesses)))]

x = [i for i in range(len(mean_max_fitness))]

fig, ax = plt.subplots()
ax.plot(
x,
mean_max_fitness,
)
ax.plot(
x,
mean_min_fitness,
)
ax.plot(
x,
mean_avg_fitness,
)
plt.show()


if __name__ == "__main__":
import asyncio

asyncio.run(main())
8 changes: 8 additions & 0 deletions core/revolve2/core/database/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
from .database import Database
from .database_error import DatabaseError
from .list import List
from .node import Node
from .object import Object
from .static_data import StaticData, is_static_data
from .transaction import Transaction
from .uninitialized import Uninitialized
21 changes: 21 additions & 0 deletions core/revolve2/core/database/database.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
from abc import ABC, abstractmethod

from .node import Node
from .transaction import Transaction


class Database(ABC):
@abstractmethod
def begin_transaction(self) -> Transaction:
"""
Begin a transaction context.
"""
pass

@property
@abstractmethod
def root(self) -> Node:
"""
Get the root node of the database.
"""
pass
2 changes: 2 additions & 0 deletions core/revolve2/core/database/database_error.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
class DatabaseError(Exception):
pass
71 changes: 71 additions & 0 deletions core/revolve2/core/database/list.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
from typing import Optional

from .database_error import DatabaseError
from .list_impl import ListImpl
from .node import Node
from .transaction import Transaction


class List:
_impl: Optional[ListImpl]

def __init__(self, impl: ListImpl = None):
self._impl = impl

@property
def is_stub(self) -> bool:
"""
If list is not yet linked to the database but a stub created by the user.
"""
return self._impl is None

def get_or_append(self, txn: Transaction, index: int) -> Node:
"""
Get the item at the given index, or if the list is one short,
append to the list and return the new node.
If the list is more than one too short or the index is not the last index in the list,
DatabaseError is thrown.
"""
if self.is_stub:
raise DatabaseError(
"List not usable yet. It is a stub created by the user that has not yet been linked with the database."
)

return self._impl.get_or_append(txn, index)

def append(self, txn: Transaction) -> Node:
"""
Append a new node to the list and return it.
"""
if self.is_stub:
raise DatabaseError(
"List not usable yet. It is a stub created by the user that has not yet been linked with the database."
)

return self._impl.append(txn)

def get(self, txn: Transaction, index: int) -> Node:
"""
Get the item at the given index.
:raises: DatabaseError if out of bounds
"""
if self.is_stub:
raise DatabaseError(
"List not usable yet. It is a stub created by the user that has not yet been linked with the database."
)

return self._impl.get(txn, index)

def len(self, txn: Transaction) -> int:
"""
Get the length of the list.
"""
if self.is_stub:
raise DatabaseError(
"List not usable yet. It is a stub created by the user that has not yet been linked with the database."
)

return self._impl.len(txn)

def _set_impl(self, impl: ListImpl) -> None:
self._impl = impl
22 changes: 22 additions & 0 deletions core/revolve2/core/database/list_impl.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
from abc import ABC, abstractmethod

from .node import Node
from .transaction import Transaction


class ListImpl(ABC):
@abstractmethod
def get_or_append(self, txn: Transaction, index: int) -> Node:
pass

@abstractmethod
def append(self, txn: Transaction) -> Node:
pass

@abstractmethod
def get(self, txn: Transaction, index: int) -> Node:
pass

@abstractmethod
def len(self, txn: Transaction) -> int:
pass
50 changes: 50 additions & 0 deletions core/revolve2/core/database/node.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
from typing import Optional, Union

from .database_error import DatabaseError
from .node_impl import NodeImpl
from .object import Object
from .transaction import Transaction
from .uninitialized import Uninitialized


class Node:
"""
Represents a node in a database.
Can be either an object or unitialized.
"""

_impl: Optional[NodeImpl]

def __init__(self, impl: NodeImpl = None):
self._impl = impl

@property
def is_stub(self) -> bool:
"""
If node is not yet linked to the database but a stub created by the user.
"""
return self._impl is None

def get_object(self, txn: Transaction) -> Union[Object, Uninitialized]:
"""
Read the underlying object from the database.
"""
if self.is_stub:
raise DatabaseError()

return self._impl.get_object(txn)

def set_object(self, txn: Transaction, object: Object) -> None:
"""
Set the underlying object in the database.
If object is not uninitialized, raises DatabaseError.
"""
if self.is_stub:
raise DatabaseError(
"Node not usable yet. It is a stub created by the user that has not yet been linked with the database."
)

return self._impl.set_object(txn, object)

def _set_impl(self, impl: NodeImpl) -> None:
self._impl = impl
16 changes: 16 additions & 0 deletions core/revolve2/core/database/node_impl.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
from abc import ABC, abstractmethod
from typing import Union

from .object import Object
from .transaction import Transaction
from .uninitialized import Uninitialized


class NodeImpl(ABC):
@abstractmethod
def get_object(self, txn: Transaction) -> Union[Object, Uninitialized]:
pass

@abstractmethod
def set_object(self, txn: Transaction, object: Object) -> None:
pass
Loading

0 comments on commit a6901d3

Please sign in to comment.