Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added GUI-beta and BuildPhenotype --> BuildCPPN #589

Closed
wants to merge 13 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@

<img align="right" width="150" height="150" src="./docs/source/logo_light.png">

# Revolve2
Expand Down Expand Up @@ -49,4 +50,9 @@ robot_state_end = scene_state_end.get_modular_robot_simulation_state(robot)
xy_displacement = fitness_functions.xy_displacement(
robot_state_begin, robot_state_end
)
```
```

## Furthermore you can beta test the new GUI by running:
`cd gui/viewer/`

`python main_window.py`
3 changes: 0 additions & 3 deletions codetools/mypy/mypy.ini
Original file line number Diff line number Diff line change
Expand Up @@ -45,9 +45,6 @@ ignore_missing_imports = True
[mypy-scipy.*]
ignore_missing_imports = True

[mypy-sklearn.*]
ignore_missing_imports = True

[mypy-cairo.*]
ignore_missing_imports = True

Expand Down
2 changes: 1 addition & 1 deletion docs/source/physical_robot_core_setup/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -177,4 +177,4 @@ There are two examples (https://github.com/ci-group/revolve2/tree/master/example

**5a_physical_robot_remote**: Control a physical modular robot by running its brain locally on your computer and streaming movement instructions to the physical modular robot.

**5b_compare_simulated_and_physical_robot**: Create a physical robot with a simulated twin. You will use two separate scripts, one for the simulated robot and one for the physical robot. With this duplicate you can check whether you have built the physical robot correctly, by comparing it to its simulated counterpart.
**5b_compare_simulated_and_physical_robot**: Create a physical robot with a simulated twin. You will use two separate scripts, one for the simulated robot and one for the physical robot. With this duplicate you can check whether you have built the physical robot correctly, by comparing it to its simulated counterpart.
2 changes: 0 additions & 2 deletions examples/3_experiment_foundations/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,3 @@ Here you will learn about a few things that are important in almost all experime
- In `3b_evaluate_single_robot` we will see how you can evaluate robots in ane experiment.
- Since evaluating one robot is not very interesting in `3c_evaluate_multiple_isolated_robots` you will see how this evaluation can be done for a population of robots.
- Alternatively if you want multiple robots interacting with each other look into example `3d_evaluate_multiple_interacting_robots`.


1 change: 1 addition & 0 deletions examples/4_example_experiment_setups/README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@

## 4) Lets look into more complex experiment setups
Most projects require some more complex setups to make them interesting research.
In this collection of examples we will show how such setups can be made and how you can incorporate databases for easy data collection.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,13 @@
"""Functions for selecting individuals from populations in EA algorithms."""

from ._multiple_unique import multiple_unique
from ._pareto_frontier import pareto_frontier
from ._topn import topn
from ._tournament import tournament
from revolve2.experimentation.optimization.ea.selection._multiple_unique import (
multiple_unique,
)
from revolve2.experimentation.optimization.ea.selection._pareto_frontier import (
pareto_frontier,
)
from revolve2.experimentation.optimization.ea.selection._topn import topn
from revolve2.experimentation.optimization.ea.selection._tournament import tournament
from revolve2.experimentation.optimization.ea.selection._roulette import roulette

__all__ = ["multiple_unique", "pareto_frontier", "topn", "tournament"]
__all__ = ["multiple_unique", "pareto_frontier", "topn", "tournament", "roulette"]
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import random
from typing import TypeVar

from ._argsort import argsort
from ._supports_lt import SupportsLt

Genotype = TypeVar("Genotype")
Fitness = TypeVar("Fitness", bound=SupportsLt)


def roulette(n: int, genotypes: list[Genotype], fitnesses: list[Fitness]) -> list[int]:
"""
Perform roulette wheel selection to choose n genotypes probabilistically based on fitness.

:param n: The number of genotypes to select.
:param genotypes: The genotypes. Ignored, but kept for compatibility with other selection functions.
:param fitnesses: Fitnesses of the genotypes.
:returns: Indices of the selected genotypes.
"""
assert len(fitnesses) >= n, "Number of selections cannot exceed population size"

# Normalize fitness values to ensure all are positive
min_fitness = min(fitnesses)
if min_fitness < 0:
fitnesses = [
f - min_fitness for f in fitnesses
] # Shift all values to be positive

total_fitness = sum(fitnesses)
assert (
total_fitness > 0
), "Total fitness must be greater than zero for roulette selection"

# Compute selection probabilities
probabilities = [f / total_fitness for f in fitnesses]

# Perform roulette wheel selection
selected_indices = random.choices(range(len(fitnesses)), weights=probabilities, k=n)

return selected_indices
10 changes: 10 additions & 0 deletions gui/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@

## 1) Using the GUI.

This GUI is a very new addition to revolve, its goal is to bundle up some of the functionalities of our robot evolution system and allow for easy parametrization of evolutionary runs.

- To use the GUI simply navigate to the "gui/viewer/" folder
- Then run the **main_window.py** file from your terminal: python main_window.py


Note: This is still very much under development, a lot of the planned features do not do anything yet (this will be indicated by the GUI). created by Áron Ferencz, if you have any issues or ideas you can reach me at the following email address: [email protected]
7 changes: 7 additions & 0 deletions gui/backend_example/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
This is the `robot_bodybrain_ea` example, but with added saving of results to a database.

Definitely first look at the `4c_robot_bodybrain_ea` and `4b_simple_ea_xor_database` examples.
Many explanation comments are omitted here.

To visualize the evolved robots, use `rerun.py` with the pickled genotype you got from evolution.
Running `plot.py` allows you to plot the robots fitness metrics over each generation.
47 changes: 47 additions & 0 deletions gui/backend_example/_multineat_params.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import multineat


def get_multineat_params() -> multineat.Parameters:
"""
Get Multineat Parameters.

:returns: The parameters.
"""
multineat_params = multineat.Parameters()

multineat_params.MutateRemLinkProb = 0.02
multineat_params.RecurrentProb = 0.0
multineat_params.OverallMutationRate = 0.15
multineat_params.MutateAddLinkProb = 0.08
multineat_params.MutateAddNeuronProb = 0.01
multineat_params.MutateWeightsProb = 0.90
multineat_params.MaxWeight = 8.0
multineat_params.WeightMutationMaxPower = 0.2
multineat_params.WeightReplacementMaxPower = 1.0
multineat_params.MutateActivationAProb = 0.0
multineat_params.ActivationAMutationMaxPower = 0.5
multineat_params.MinActivationA = 0.05
multineat_params.MaxActivationA = 6.0

multineat_params.MutateNeuronActivationTypeProb = 0.03

multineat_params.MutateOutputActivationFunction = False

multineat_params.ActivationFunction_SignedSigmoid_Prob = 0.0
multineat_params.ActivationFunction_UnsignedSigmoid_Prob = 0.0
multineat_params.ActivationFunction_Tanh_Prob = 1.0
multineat_params.ActivationFunction_TanhCubic_Prob = 0.0
multineat_params.ActivationFunction_SignedStep_Prob = 1.0
multineat_params.ActivationFunction_UnsignedStep_Prob = 0.0
multineat_params.ActivationFunction_SignedGauss_Prob = 1.0
multineat_params.ActivationFunction_UnsignedGauss_Prob = 0.0
multineat_params.ActivationFunction_Abs_Prob = 0.0
multineat_params.ActivationFunction_SignedSine_Prob = 1.0
multineat_params.ActivationFunction_UnsignedSine_Prob = 0.0
multineat_params.ActivationFunction_Linear_Prob = 1.0

multineat_params.MutateNeuronTraitsProb = 0.0
multineat_params.MutateLinkTraitsProb = 0.0

multineat_params.AllowLoops = False
return multineat_params
8 changes: 8 additions & 0 deletions gui/backend_example/config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
DATABASE_FILE = "database.sqlite"
GENERATIONAL = True
STEADY_STATE = False
NUM_GENERATIONS = 10
NUM_REPETITIONS = 1
NUM_SIMULATORS = 16
OFFSPRING_SIZE = 100
POPULATION_SIZE = 100
4 changes: 4 additions & 0 deletions gui/backend_example/config_simulation_parameters.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
STANDARD_CONTROL_FREQUENCY = 20
STANDARD_SAMPLING_FREQUENCY = 5
STANDARD_SIMULATION_TIME = 30
STANDARD_SIMULATION_TIMESTEP = 0.001
10 changes: 10 additions & 0 deletions gui/backend_example/database_components/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
"""A collection of components used in the Database."""

from ._base import Base
from ._experiment import Experiment
from ._generation import Generation
from ._genotype import Genotype
from ._individual import Individual
from ._population import Population

__all__ = ["Base", "Experiment", "Generation", "Genotype", "Individual", "Population"]
9 changes: 9 additions & 0 deletions gui/backend_example/database_components/_base.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
"""Base class."""

import sqlalchemy.orm as orm


class Base(orm.MappedAsDataclass, orm.DeclarativeBase):
"""Base class for all SQLAlchemy models in this example."""

pass
16 changes: 16 additions & 0 deletions gui/backend_example/database_components/_experiment.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
"""Experiment class."""

import sqlalchemy.orm as orm

from revolve2.experimentation.database import HasId

from ._base import Base


class Experiment(Base, HasId):
"""Experiment description."""

__tablename__ = "experiment"

# The seed for the rng.
rng_seed: orm.Mapped[int] = orm.mapped_column(nullable=False)
26 changes: 26 additions & 0 deletions gui/backend_example/database_components/_generation.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
"""Generation class."""

import sqlalchemy
import sqlalchemy.orm as orm

from revolve2.experimentation.database import HasId

from ._base import Base
from ._experiment import Experiment
from ._population import Population


class Generation(Base, HasId):
"""A single finished iteration of CMA-ES."""

__tablename__ = "generation"

experiment_id: orm.Mapped[int] = orm.mapped_column(
sqlalchemy.ForeignKey("experiment.id"), nullable=False, init=False
)
experiment: orm.Mapped[Experiment] = orm.relationship()
generation_index: orm.Mapped[int] = orm.mapped_column(nullable=False)
population_id: orm.Mapped[int] = orm.mapped_column(
sqlalchemy.ForeignKey("population.id"), nullable=False, init=False
)
population: orm.Mapped[Population] = orm.relationship()
90 changes: 90 additions & 0 deletions gui/backend_example/database_components/_genotype.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
"""Genotype class."""

from __future__ import annotations

import multineat
import numpy as np

from revolve2.experimentation.database import HasId
from revolve2.modular_robot import ModularRobot
from revolve2.standards.genotypes.cppnwin.modular_robot import BrainGenotypeCpgOrm
from revolve2.standards.genotypes.cppnwin.modular_robot.v2 import BodyGenotypeOrmV2

from ._base import Base


class Genotype(Base, HasId, BodyGenotypeOrmV2, BrainGenotypeCpgOrm):
"""SQLAlchemy model for a genotype for a modular robot body and brain."""

__tablename__ = "genotype"

@classmethod
def random(
cls,
innov_db_body: multineat.InnovationDatabase,
innov_db_brain: multineat.InnovationDatabase,
rng: np.random.Generator,
) -> Genotype:
"""
Create a random genotype.

:param innov_db_body: Multineat innovation database for the body. See Multineat library.
:param innov_db_brain: Multineat innovation database for the brain. See Multineat library.
:param rng: Random number generator.
:returns: The created genotype.
"""
body = cls.random_body(innov_db_body, rng)
brain = cls.random_brain(innov_db_brain, rng)

return Genotype(body=body.body, brain=brain.brain)

def mutate(
self,
innov_db_body: multineat.InnovationDatabase,
innov_db_brain: multineat.InnovationDatabase,
rng: np.random.Generator,
) -> Genotype:
"""
Mutate this genotype.

This genotype will not be changed; a mutated copy will be returned.

:param innov_db_body: Multineat innovation database for the body. See Multineat library.
:param innov_db_brain: Multineat innovation database for the brain. See Multineat library.
:param rng: Random number generator.
:returns: A mutated copy of the provided genotype.
"""
body = self.mutate_body(innov_db_body, rng)
brain = self.mutate_brain(innov_db_brain, rng)

return Genotype(body=body.body, brain=brain.brain)

@classmethod
def crossover(
cls,
parent1: Genotype,
parent2: Genotype,
rng: np.random.Generator,
) -> Genotype:
"""
Perform crossover between two genotypes.

:param parent1: The first genotype.
:param parent2: The second genotype.
:param rng: Random number generator.
:returns: A newly created genotype.
"""
body = cls.crossover_body(parent1, parent2, rng)
brain = cls.crossover_brain(parent1, parent2, rng)

return Genotype(body=body.body, brain=brain.brain)

def develop(self) -> ModularRobot:
"""
Develop the genotype into a modular robot.

:returns: The created robot.
"""
body = self.develop_body()
brain = self.develop_brain(body=body)
return ModularRobot(body=body, brain=brain)
17 changes: 17 additions & 0 deletions gui/backend_example/database_components/_individual.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
"""Individual class."""

from dataclasses import dataclass

from revolve2.experimentation.optimization.ea import Individual as GenericIndividual

from ._base import Base
from ._genotype import Genotype


@dataclass
class Individual(
Base, GenericIndividual[Genotype], population_table="population", kw_only=True
):
"""An individual in a population."""

__tablename__ = "individual"
12 changes: 12 additions & 0 deletions gui/backend_example/database_components/_population.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
"""Population class."""

from revolve2.experimentation.optimization.ea import Population as GenericPopulation

from ._base import Base
from ._individual import Individual


class Population(Base, GenericPopulation[Individual], kw_only=True):
"""A population of individuals."""

__tablename__ = "population"
Loading