add car racing sim and first geno to phenotype mapper experiment.
This commit is contained in:
8
.idea/.gitignore
generated
vendored
Normal file
8
.idea/.gitignore
generated
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
# Default ignored files
|
||||
/shelf/
|
||||
/workspace.xml
|
||||
# Editor-based HTTP Client requests
|
||||
/httpRequests/
|
||||
# Datasource local storage ignored files
|
||||
/dataSources/
|
||||
/dataSources.local.xml
|
||||
6
.idea/inspectionProfiles/profiles_settings.xml
generated
Normal file
6
.idea/inspectionProfiles/profiles_settings.xml
generated
Normal file
@@ -0,0 +1,6 @@
|
||||
<component name="InspectionProjectProfileManager">
|
||||
<settings>
|
||||
<option name="USE_PROJECT_PROFILE" value="false" />
|
||||
<version value="1.0" />
|
||||
</settings>
|
||||
</component>
|
||||
7
.idea/misc.xml
generated
Normal file
7
.idea/misc.xml
generated
Normal file
@@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="Black">
|
||||
<option name="sdkName" value="Python 3.12 (ba)" />
|
||||
</component>
|
||||
<component name="ProjectRootManager" version="2" project-jdk-name="Python 3.12 (neuroevolution)" project-jdk-type="Python SDK" />
|
||||
</project>
|
||||
8
.idea/modules.xml
generated
Normal file
8
.idea/modules.xml
generated
Normal file
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ProjectModuleManager">
|
||||
<modules>
|
||||
<module fileurl="file://$PROJECT_DIR$/.idea/neuroevolution.iml" filepath="$PROJECT_DIR$/.idea/neuroevolution.iml" />
|
||||
</modules>
|
||||
</component>
|
||||
</project>
|
||||
14
.idea/neuroevolution.iml
generated
Normal file
14
.idea/neuroevolution.iml
generated
Normal file
@@ -0,0 +1,14 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module type="PYTHON_MODULE" version="4">
|
||||
<component name="NewModuleRootManager">
|
||||
<content url="file://$MODULE_DIR$">
|
||||
<excludeFolder url="file://$MODULE_DIR$/.venv" />
|
||||
</content>
|
||||
<orderEntry type="jdk" jdkName="Python 3.12 (neuroevolution)" jdkType="Python SDK" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
</component>
|
||||
<component name="PyDocumentationSettings">
|
||||
<option name="format" value="PLAIN" />
|
||||
<option name="myDocStringFormat" value="Plain" />
|
||||
</component>
|
||||
</module>
|
||||
6
.idea/vcs.xml
generated
Normal file
6
.idea/vcs.xml
generated
Normal file
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="VcsDirectoryMappings">
|
||||
<mapping directory="" vcs="Git" />
|
||||
</component>
|
||||
</project>
|
||||
12
experiments/car_racing_test/run.py
Normal file
12
experiments/car_racing_test/run.py
Normal file
@@ -0,0 +1,12 @@
|
||||
import gymnasium as gym
|
||||
|
||||
env = gym.make("CarRacing-v3", render_mode="human")
|
||||
obs, info = env.reset()
|
||||
|
||||
for _ in range(1000):
|
||||
action = env.action_space.sample()
|
||||
_, reward, terminated, truncated, _ = env.step(action)
|
||||
if terminated or truncated:
|
||||
obs, info = env.reset()
|
||||
|
||||
env.close()
|
||||
@@ -6,7 +6,7 @@ from typing import List, Dict, Tuple
|
||||
|
||||
# ---- Hilfsfunktionen ----
|
||||
def generate_id():
|
||||
"""Generiert eine eindeutige ID basierend auf Zufallszahlen."""
|
||||
"""generate random number as id. TODO: this should be uuidv4 instead of float"""
|
||||
return random.random()
|
||||
|
||||
|
||||
Binary file not shown.
258
experiments/phenotype_genotype_map/genotype.json
Normal file
258
experiments/phenotype_genotype_map/genotype.json
Normal file
@@ -0,0 +1,258 @@
|
||||
{
|
||||
"cortex": {
|
||||
"id": 0.38985127736117664,
|
||||
"sensor_ids": [
|
||||
0.5481697495393968
|
||||
],
|
||||
"actuator_ids": [
|
||||
0.300862433948894
|
||||
],
|
||||
"neuron_ids": [
|
||||
0.7892266733003815,
|
||||
0.08993126065168999,
|
||||
0.7353679413901013,
|
||||
0.7986671199471203,
|
||||
0.2619274118215478,
|
||||
0.1878355172703,
|
||||
0.5460586524022121,
|
||||
0.0325156080072021
|
||||
]
|
||||
},
|
||||
"sensor": {
|
||||
"id": 0.5481697495393968,
|
||||
"name": "rng",
|
||||
"vector_length": 2,
|
||||
"cx_id": 0.37250552293263184,
|
||||
"fanout_ids": [
|
||||
0.7892266733003815,
|
||||
0.08993126065168999,
|
||||
0.7353679413901013,
|
||||
0.7986671199471203
|
||||
]
|
||||
},
|
||||
"actuator": {
|
||||
"id": 0.300862433948894,
|
||||
"name": "pts",
|
||||
"vector_length": 1,
|
||||
"cx_id": 0.37250552293263184,
|
||||
"fanin_ids": [
|
||||
0.0325156080072021
|
||||
]
|
||||
},
|
||||
"neurons": [
|
||||
{
|
||||
"id": 0.7892266733003815,
|
||||
"layer_index": 0,
|
||||
"cx_id": 0.37250552293263184,
|
||||
"activation_function": "tanh",
|
||||
"input_weights": [
|
||||
{
|
||||
"input_id": 0.5481697495393968,
|
||||
"weights": [
|
||||
-0.2010596737648036,
|
||||
-0.017559575650012982
|
||||
]
|
||||
}
|
||||
],
|
||||
"output_ids": [
|
||||
0.11787440750922895,
|
||||
0.9473188607259506,
|
||||
0.9045741659912035
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 0.08993126065168999,
|
||||
"layer_index": 0,
|
||||
"cx_id": 0.37250552293263184,
|
||||
"activation_function": "tanh",
|
||||
"input_weights": [
|
||||
{
|
||||
"input_id": 0.5481697495393968,
|
||||
"weights": [
|
||||
0.49425647147649876,
|
||||
0.09556856915703738
|
||||
]
|
||||
}
|
||||
],
|
||||
"output_ids": [
|
||||
0.11787440750922895,
|
||||
0.9473188607259506,
|
||||
0.9045741659912035
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 0.7353679413901013,
|
||||
"layer_index": 0,
|
||||
"cx_id": 0.37250552293263184,
|
||||
"activation_function": "tanh",
|
||||
"input_weights": [
|
||||
{
|
||||
"input_id": 0.5481697495393968,
|
||||
"weights": [
|
||||
-0.3432015569352376,
|
||||
-0.15663876804924903
|
||||
]
|
||||
}
|
||||
],
|
||||
"output_ids": [
|
||||
0.11787440750922895,
|
||||
0.9473188607259506,
|
||||
0.9045741659912035
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 0.7986671199471203,
|
||||
"layer_index": 0,
|
||||
"cx_id": 0.37250552293263184,
|
||||
"activation_function": "tanh",
|
||||
"input_weights": [
|
||||
{
|
||||
"input_id": 0.5481697495393968,
|
||||
"weights": [
|
||||
0.44235413103542676,
|
||||
-0.3014661028473905
|
||||
]
|
||||
}
|
||||
],
|
||||
"output_ids": [
|
||||
0.11787440750922895,
|
||||
0.9473188607259506,
|
||||
0.9045741659912035
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 0.2619274118215478,
|
||||
"layer_index": 1,
|
||||
"cx_id": 0.37250552293263184,
|
||||
"activation_function": "tanh",
|
||||
"input_weights": [
|
||||
{
|
||||
"input_id": 0.7892266733003815,
|
||||
"weights": [
|
||||
0.4815260544600901
|
||||
]
|
||||
},
|
||||
{
|
||||
"input_id": 0.08993126065168999,
|
||||
"weights": [
|
||||
-0.34742595611872107
|
||||
]
|
||||
},
|
||||
{
|
||||
"input_id": 0.7353679413901013,
|
||||
"weights": [
|
||||
0.1955465593022997
|
||||
]
|
||||
},
|
||||
{
|
||||
"input_id": 0.7986671199471203,
|
||||
"weights": [
|
||||
0.1420046463445398
|
||||
]
|
||||
}
|
||||
],
|
||||
"output_ids": [
|
||||
0.804289241732289
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 0.1878355172703,
|
||||
"layer_index": 1,
|
||||
"cx_id": 0.37250552293263184,
|
||||
"activation_function": "tanh",
|
||||
"input_weights": [
|
||||
{
|
||||
"input_id": 0.7892266733003815,
|
||||
"weights": [
|
||||
-0.26492297905129614
|
||||
]
|
||||
},
|
||||
{
|
||||
"input_id": 0.08993126065168999,
|
||||
"weights": [
|
||||
-0.483071194536965
|
||||
]
|
||||
},
|
||||
{
|
||||
"input_id": 0.7353679413901013,
|
||||
"weights": [
|
||||
0.0016581996680702371
|
||||
]
|
||||
},
|
||||
{
|
||||
"input_id": 0.7986671199471203,
|
||||
"weights": [
|
||||
0.47010344086613354
|
||||
]
|
||||
}
|
||||
],
|
||||
"output_ids": [
|
||||
0.804289241732289
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 0.5460586524022121,
|
||||
"layer_index": 1,
|
||||
"cx_id": 0.37250552293263184,
|
||||
"activation_function": "tanh",
|
||||
"input_weights": [
|
||||
{
|
||||
"input_id": 0.7892266733003815,
|
||||
"weights": [
|
||||
0.22583100100162312
|
||||
]
|
||||
},
|
||||
{
|
||||
"input_id": 0.08993126065168999,
|
||||
"weights": [
|
||||
0.22079487426614341
|
||||
]
|
||||
},
|
||||
{
|
||||
"input_id": 0.7353679413901013,
|
||||
"weights": [
|
||||
0.3514718848950448
|
||||
]
|
||||
},
|
||||
{
|
||||
"input_id": 0.7986671199471203,
|
||||
"weights": [
|
||||
0.03653443587296967
|
||||
]
|
||||
}
|
||||
],
|
||||
"output_ids": [
|
||||
0.804289241732289
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 0.0325156080072021,
|
||||
"layer_index": 2,
|
||||
"cx_id": 0.37250552293263184,
|
||||
"activation_function": "tanh",
|
||||
"input_weights": [
|
||||
{
|
||||
"input_id": 0.2619274118215478,
|
||||
"weights": [
|
||||
0.4397546856440344
|
||||
]
|
||||
},
|
||||
{
|
||||
"input_id": 0.1878355172703,
|
||||
"weights": [
|
||||
0.024524621225712195
|
||||
]
|
||||
},
|
||||
{
|
||||
"input_id": 0.5460586524022121,
|
||||
"weights": [
|
||||
0.19908558570374346
|
||||
]
|
||||
}
|
||||
],
|
||||
"output_ids": [
|
||||
0.300862433948894
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
258
experiments/phenotype_genotype_map/genotype_updated.json
Normal file
258
experiments/phenotype_genotype_map/genotype_updated.json
Normal file
@@ -0,0 +1,258 @@
|
||||
{
|
||||
"cortex": {
|
||||
"id": 0.38985127736117664,
|
||||
"sensor_ids": [
|
||||
0.5481697495393968
|
||||
],
|
||||
"actuator_ids": [
|
||||
0.300862433948894
|
||||
],
|
||||
"neuron_ids": [
|
||||
0.7892266733003815,
|
||||
0.08993126065168999,
|
||||
0.7353679413901013,
|
||||
0.7986671199471203,
|
||||
0.2619274118215478,
|
||||
0.1878355172703,
|
||||
0.5460586524022121,
|
||||
0.0325156080072021
|
||||
]
|
||||
},
|
||||
"sensor": {
|
||||
"id": 0.5481697495393968,
|
||||
"name": "rng",
|
||||
"vector_length": 2,
|
||||
"cx_id": 0.37250552293263184,
|
||||
"fanout_ids": [
|
||||
0.7892266733003815,
|
||||
0.08993126065168999,
|
||||
0.7353679413901013,
|
||||
0.7986671199471203
|
||||
]
|
||||
},
|
||||
"actuator": {
|
||||
"id": 0.300862433948894,
|
||||
"name": "pts",
|
||||
"vector_length": 1,
|
||||
"cx_id": 0.37250552293263184,
|
||||
"fanin_ids": [
|
||||
0.0325156080072021
|
||||
]
|
||||
},
|
||||
"neurons": [
|
||||
{
|
||||
"id": 0.7892266733003815,
|
||||
"layer_index": 0,
|
||||
"cx_id": 0.37250552293263184,
|
||||
"activation_function": "tanh",
|
||||
"input_weights": [
|
||||
{
|
||||
"input_id": 0.5481697495393968,
|
||||
"weights": [
|
||||
-0.2010596737648036,
|
||||
-0.017559575650012982
|
||||
]
|
||||
}
|
||||
],
|
||||
"output_ids": [
|
||||
0.11787440750922895,
|
||||
0.9473188607259506,
|
||||
0.9045741659912035
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 0.08993126065168999,
|
||||
"layer_index": 0,
|
||||
"cx_id": 0.37250552293263184,
|
||||
"activation_function": "tanh",
|
||||
"input_weights": [
|
||||
{
|
||||
"input_id": 0.5481697495393968,
|
||||
"weights": [
|
||||
0.49425647147649876,
|
||||
0.09556856915703738
|
||||
]
|
||||
}
|
||||
],
|
||||
"output_ids": [
|
||||
0.11787440750922895,
|
||||
0.9473188607259506,
|
||||
0.9045741659912035
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 0.7353679413901013,
|
||||
"layer_index": 0,
|
||||
"cx_id": 0.37250552293263184,
|
||||
"activation_function": "tanh",
|
||||
"input_weights": [
|
||||
{
|
||||
"input_id": 0.5481697495393968,
|
||||
"weights": [
|
||||
-0.3432015569352376,
|
||||
-0.15663876804924903
|
||||
]
|
||||
}
|
||||
],
|
||||
"output_ids": [
|
||||
0.11787440750922895,
|
||||
0.9473188607259506,
|
||||
0.9045741659912035
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 0.7986671199471203,
|
||||
"layer_index": 0,
|
||||
"cx_id": 0.37250552293263184,
|
||||
"activation_function": "tanh",
|
||||
"input_weights": [
|
||||
{
|
||||
"input_id": 0.5481697495393968,
|
||||
"weights": [
|
||||
0.44235413103542676,
|
||||
-0.3014661028473905
|
||||
]
|
||||
}
|
||||
],
|
||||
"output_ids": [
|
||||
0.11787440750922895,
|
||||
0.9473188607259506,
|
||||
0.9045741659912035
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 0.2619274118215478,
|
||||
"layer_index": 1,
|
||||
"cx_id": 0.37250552293263184,
|
||||
"activation_function": "tanh",
|
||||
"input_weights": [
|
||||
{
|
||||
"input_id": 0.7892266733003815,
|
||||
"weights": [
|
||||
0.4815260544600901
|
||||
]
|
||||
},
|
||||
{
|
||||
"input_id": 0.08993126065168999,
|
||||
"weights": [
|
||||
-0.34742595611872107
|
||||
]
|
||||
},
|
||||
{
|
||||
"input_id": 0.7353679413901013,
|
||||
"weights": [
|
||||
0.1955465593022997
|
||||
]
|
||||
},
|
||||
{
|
||||
"input_id": 0.7986671199471203,
|
||||
"weights": [
|
||||
0.1420046463445398
|
||||
]
|
||||
}
|
||||
],
|
||||
"output_ids": [
|
||||
0.804289241732289
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 0.1878355172703,
|
||||
"layer_index": 1,
|
||||
"cx_id": 0.37250552293263184,
|
||||
"activation_function": "tanh",
|
||||
"input_weights": [
|
||||
{
|
||||
"input_id": 0.7892266733003815,
|
||||
"weights": [
|
||||
-0.26492297905129614
|
||||
]
|
||||
},
|
||||
{
|
||||
"input_id": 0.08993126065168999,
|
||||
"weights": [
|
||||
-0.483071194536965
|
||||
]
|
||||
},
|
||||
{
|
||||
"input_id": 0.7353679413901013,
|
||||
"weights": [
|
||||
0.0016581996680702371
|
||||
]
|
||||
},
|
||||
{
|
||||
"input_id": 0.7986671199471203,
|
||||
"weights": [
|
||||
0.47010344086613354
|
||||
]
|
||||
}
|
||||
],
|
||||
"output_ids": [
|
||||
0.804289241732289
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 0.5460586524022121,
|
||||
"layer_index": 1,
|
||||
"cx_id": 0.37250552293263184,
|
||||
"activation_function": "tanh",
|
||||
"input_weights": [
|
||||
{
|
||||
"input_id": 0.7892266733003815,
|
||||
"weights": [
|
||||
0.22583100100162312
|
||||
]
|
||||
},
|
||||
{
|
||||
"input_id": 0.08993126065168999,
|
||||
"weights": [
|
||||
0.22079487426614341
|
||||
]
|
||||
},
|
||||
{
|
||||
"input_id": 0.7353679413901013,
|
||||
"weights": [
|
||||
0.3514718848950448
|
||||
]
|
||||
},
|
||||
{
|
||||
"input_id": 0.7986671199471203,
|
||||
"weights": [
|
||||
0.03653443587296967
|
||||
]
|
||||
}
|
||||
],
|
||||
"output_ids": [
|
||||
0.804289241732289
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 0.0325156080072021,
|
||||
"layer_index": 2,
|
||||
"cx_id": 0.37250552293263184,
|
||||
"activation_function": "tanh",
|
||||
"input_weights": [
|
||||
{
|
||||
"input_id": 0.2619274118215478,
|
||||
"weights": [
|
||||
0.4397546856440344
|
||||
]
|
||||
},
|
||||
{
|
||||
"input_id": 0.1878355172703,
|
||||
"weights": [
|
||||
0.024524621225712195
|
||||
]
|
||||
},
|
||||
{
|
||||
"input_id": 0.5460586524022121,
|
||||
"weights": [
|
||||
0.19908558570374346
|
||||
]
|
||||
}
|
||||
],
|
||||
"output_ids": [
|
||||
0.300862433948894
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
410
experiments/phenotype_genotype_map/pehno_geno_map.py
Normal file
410
experiments/phenotype_genotype_map/pehno_geno_map.py
Normal file
@@ -0,0 +1,410 @@
|
||||
import asyncio
|
||||
import json
|
||||
import math
|
||||
import random
|
||||
from collections import defaultdict
|
||||
from typing import Dict, List, Tuple, Any
|
||||
|
||||
|
||||
# utils
|
||||
def tanh(x: float) -> float:
|
||||
return math.tanh(x)
|
||||
|
||||
|
||||
def rng_vector(vl: int) -> List[float]:
|
||||
# wie sensor:rng(VL)
|
||||
return [random.random() for _ in range(vl)]
|
||||
|
||||
|
||||
# actor base class
|
||||
class Actor:
|
||||
"""
|
||||
base class for all our actors: sensors, actuators, neurons, cortex.
|
||||
"""
|
||||
def __init__(self, name: str):
|
||||
self.name = name
|
||||
self.inbox: asyncio.Queue = asyncio.Queue()
|
||||
|
||||
async def send(self, msg: Tuple[Any, ...]):
|
||||
"""
|
||||
|
||||
"""
|
||||
await self.inbox.put(msg)
|
||||
|
||||
async def run(self):
|
||||
"""
|
||||
this needs to be overridden in subclasses.
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
# sensor
|
||||
class Sensor(Actor):
|
||||
def __init__(self, sid: Any, cx_pid: "Actor", name: str, vl: int, fanout_pids: List["Actor"]):
|
||||
super().__init__(f"Sensor-{sid}")
|
||||
self.sid = sid
|
||||
self.cx_pid = cx_pid
|
||||
self.sname = name
|
||||
self.vl = vl
|
||||
self.fanout = fanout_pids
|
||||
|
||||
async def run(self):
|
||||
while True:
|
||||
msg = await self.inbox.get()
|
||||
tag = msg[0]
|
||||
if tag == "sync": # {Cx, sync}
|
||||
# if there is a sync command, we sense from the environment
|
||||
# and put the result in the inbox of the neurons connected to sensor.
|
||||
print("sensing...")
|
||||
# choose sensor funtion
|
||||
if self.sname == "rng":
|
||||
vec = rng_vector(self.vl)
|
||||
else:
|
||||
# place for own sensors (this will be replaced by scapes down the road)
|
||||
vec = [0.0] * self.vl
|
||||
# forward an alle Fanouts
|
||||
for pid in self.fanout:
|
||||
await pid.send(("forward", self.sid, vec))
|
||||
elif tag == "terminate":
|
||||
# terminate.
|
||||
return
|
||||
|
||||
|
||||
# neuron
|
||||
class Neuron(Actor):
|
||||
def __init__(self, nid: Any, cx_pid: "Actor", af_name: str,
|
||||
input_idps: List[Tuple[Any, List[float]]], # [(input_id, weights)] (+ optional bias am Ende)
|
||||
output_pids: List["Actor"]):
|
||||
super().__init__(f"Neuron-{nid}")
|
||||
self.nid = nid
|
||||
self.cx_pid = cx_pid
|
||||
self.af = tanh if af_name == "tanh" else tanh
|
||||
# input-id → (weights, received_flag, last_value_vector_or_scalar)
|
||||
self.inputs: Dict[Any, Dict[str, Any]] = {}
|
||||
self.order: List[Any] = [] # for deterministic order
|
||||
for entry in input_idps:
|
||||
self.order.append(entry[0])
|
||||
self.inputs[entry[0]] = {"weights": entry[1], "got": False, "val": None}
|
||||
self.bias: float = 0.0 # optional bias
|
||||
self.outputs = output_pids
|
||||
|
||||
async def run(self):
|
||||
while True:
|
||||
msg = await self.inbox.get()
|
||||
tag = msg[0]
|
||||
if tag == "forward":
|
||||
# ("forward", from_id, value_vec_or_scalar)
|
||||
_tag, from_id, data = msg
|
||||
if from_id not in self.inputs:
|
||||
# unexpected source - we continue and ignore it
|
||||
continue
|
||||
# we mark that we have values from this source to keep sequence
|
||||
self.inputs[from_id]["got"] = True
|
||||
self.inputs[from_id]["val"] = data
|
||||
|
||||
# when we have a signal from all sources: calculate dot product.
|
||||
if all(self.inputs[i]["got"] for i in self.order):
|
||||
acc = 0.0
|
||||
for i in self.order:
|
||||
w = self.inputs[i]["weights"]
|
||||
v = self.inputs[i]["val"]
|
||||
# first layer: v is vector -> dotproduct
|
||||
if isinstance(v, list):
|
||||
acc += sum(wj * vj for wj, vj in zip(w, v))
|
||||
else:
|
||||
# from layer 1: scalar -> 1 weight
|
||||
acc += w[0] * float(v)
|
||||
out = self.af(acc + self.bias)
|
||||
# an alle Outputs als Vektor (wie Erlang: [Output])
|
||||
for pid in self.outputs:
|
||||
await pid.send(("forward", self.nid, [out]))
|
||||
# reset "got"-flags so the next cycle can start
|
||||
for i in self.order:
|
||||
self.inputs[i]["got"] = False
|
||||
self.inputs[i]["val"] = None
|
||||
|
||||
elif tag == "get_backup":
|
||||
# ("get_backup", reply_to)
|
||||
_tag, reply_to = msg
|
||||
# we create (nid, [(input_id, weights), ..., ('bias', bias)])
|
||||
idps = [(i, self.inputs[i]["weights"]) for i in self.order]
|
||||
idps.append(("bias", self.bias))
|
||||
# send back to cortex
|
||||
await reply_to.send(("backup_from_neuron", self.nid, idps))
|
||||
|
||||
elif tag == "terminate":
|
||||
return
|
||||
|
||||
|
||||
# ---------- Actuator ----------
|
||||
class Actuator(Actor):
|
||||
def __init__(self, aid: Any, cx_pid: "Actor", name: str, fanin_ids: List[Any], expect_count: int):
|
||||
super().__init__(f"Actuator-{aid}")
|
||||
self.aid = aid
|
||||
self.cx_pid = cx_pid
|
||||
self.aname = name
|
||||
self.fanin_ids = fanin_ids[:] # order matters!
|
||||
self.expect = expect_count
|
||||
self.received: Dict[Any, List[float]] = {}
|
||||
|
||||
async def run(self):
|
||||
while True:
|
||||
msg = await self.inbox.get()
|
||||
tag = msg[0]
|
||||
if tag == "forward":
|
||||
_tag, from_id, vec = msg
|
||||
self.received[from_id] = vec
|
||||
if len(self.received) == self.expect:
|
||||
# collect in order of fanin_ids
|
||||
result: List[float] = []
|
||||
for fid in self.fanin_ids:
|
||||
result.extend(self.received[fid])
|
||||
# actuator functions (here: pts → print)
|
||||
if self.aname == "pts":
|
||||
print(f"actuator:pts(Result): {result}")
|
||||
# send sync message to cortex
|
||||
await self.cx_pid.send(("sync_from_actuator", self.aid))
|
||||
self.received.clear()
|
||||
elif tag == "terminate":
|
||||
return
|
||||
|
||||
|
||||
# cortex with report queue
|
||||
class Cortex(Actor):
|
||||
def __init__(self, cid: Any, sensor_pids: List["Actor"], actuator_pids: List["Actor"],
|
||||
neuron_pids: List["Actor"], total_steps: int, report_queue: asyncio.Queue):
|
||||
super().__init__(f"Cortex-{cid}")
|
||||
self.cid = cid
|
||||
self.sensors = sensor_pids
|
||||
self.actuators = actuator_pids
|
||||
self.neurons = neuron_pids
|
||||
self.total_steps = total_steps
|
||||
self.awaiting_sync: set = set()
|
||||
self.actuator_ids: List[Any] = []
|
||||
self.report_queue = report_queue
|
||||
|
||||
async def run(self):
|
||||
# start: trigger all sensors
|
||||
for s in self.sensors:
|
||||
await s.send(("sync",))
|
||||
|
||||
while True:
|
||||
msg = await self.inbox.get()
|
||||
tag = msg[0]
|
||||
|
||||
if tag == "register_actuators":
|
||||
_, actuator_ids = msg
|
||||
self.actuator_ids = list(actuator_ids)
|
||||
self.awaiting_sync = set(self.actuator_ids)
|
||||
|
||||
elif tag == "sync_from_actuator":
|
||||
_, aid = msg
|
||||
if aid in self.awaiting_sync:
|
||||
self.awaiting_sync.remove(aid)
|
||||
|
||||
if not self.awaiting_sync:
|
||||
self.total_steps -= 1
|
||||
if self.total_steps <= 0:
|
||||
# collect backup
|
||||
weights = []
|
||||
for n in self.neurons:
|
||||
await n.send(("get_backup", self))
|
||||
remaining = len(self.neurons)
|
||||
while remaining > 0:
|
||||
bmsg = await self.inbox.get()
|
||||
if bmsg[0] == "backup_from_neuron":
|
||||
_, nid, idps = bmsg
|
||||
weights.append((nid, idps))
|
||||
remaining -= 1
|
||||
|
||||
# report result to exoself
|
||||
await self.report_queue.put(("completed_backup", weights))
|
||||
|
||||
# terminate
|
||||
for s in self.sensors:
|
||||
await s.send(("terminate",))
|
||||
for a in self.actuators:
|
||||
await a.send(("terminate",))
|
||||
for n in self.neurons:
|
||||
await n.send(("terminate",))
|
||||
return
|
||||
|
||||
# Nächster Zyklus
|
||||
self.awaiting_sync = set(self.actuator_ids)
|
||||
for s in self.sensors:
|
||||
await s.send(("sync",))
|
||||
|
||||
elif tag == "completed_backup":
|
||||
# placeholder
|
||||
pass
|
||||
|
||||
|
||||
# exoself
|
||||
class Exoself:
|
||||
def __init__(self, genotype: Dict[str, Any], total_steps: int = 10):
|
||||
self.g = genotype
|
||||
self.total_steps = total_steps
|
||||
self.cx_actor: Cortex = None
|
||||
self.pid_by_id: Dict[Any, Actor] = {}
|
||||
self.report_queue: asyncio.Queue = asyncio.Queue()
|
||||
|
||||
# --- helpers for arrays of sensors and actuators ---
|
||||
@staticmethod
|
||||
def _as_list(maybe_list_or_item):
|
||||
if maybe_list_or_item is None:
|
||||
return []
|
||||
if isinstance(maybe_list_or_item, list):
|
||||
return maybe_list_or_item
|
||||
return [maybe_list_or_item]
|
||||
|
||||
def _get_sensors_json(self) -> List[Dict[str, Any]]:
|
||||
if "sensors" in self.g and isinstance(self.g["sensors"], list):
|
||||
return self.g["sensors"]
|
||||
elif "sensor" in self.g and isinstance(self.g["sensor"], dict):
|
||||
return [self.g["sensor"]]
|
||||
else:
|
||||
return []
|
||||
|
||||
def _get_actuators_json(self) -> List[Dict[str, Any]]:
|
||||
if "actuators" in self.g and isinstance(self.g["actuators"], list):
|
||||
return self.g["actuators"]
|
||||
elif "actuator" in self.g and isinstance(self.g["actuator"], dict):
|
||||
return [self.g["actuator"]]
|
||||
else:
|
||||
return []
|
||||
|
||||
def _build_pid_map(self):
|
||||
cx = self.g["cortex"]
|
||||
self.cx_actor = Cortex(cx["id"], [], [], [], self.total_steps, self.report_queue)
|
||||
self.pid_by_id[cx["id"]] = self.cx_actor
|
||||
|
||||
# placeholder for all known ids
|
||||
for s in self._get_sensors_json():
|
||||
self.pid_by_id[s["id"]] = None
|
||||
for a in self._get_actuators_json():
|
||||
self.pid_by_id[a["id"]] = None
|
||||
for n in self.g["neurons"]:
|
||||
self.pid_by_id[n["id"]] = None
|
||||
|
||||
def _link_and_spawn(self):
|
||||
# order neurons by layers
|
||||
layers = defaultdict(list)
|
||||
for n in self.g["neurons"]:
|
||||
layers[n["layer_index"]].append(n)
|
||||
ordered_layers = [layers[i] for i in sorted(layers)]
|
||||
if not ordered_layers:
|
||||
raise ValueError("Keine Neuronen im Genotyp gefunden.")
|
||||
|
||||
# build actuators
|
||||
actuators_json = self._get_actuators_json()
|
||||
actuator_pid_by_id: Dict[Any, Actuator] = {}
|
||||
|
||||
# for last layer: we need list of neuron-ids (for matching)
|
||||
last_layer = ordered_layers[-1]
|
||||
last_layer_ids = {n["id"] for n in last_layer}
|
||||
|
||||
for a in actuators_json:
|
||||
fanin_ids = self._as_list(a.get("fanin_ids", []))
|
||||
# Safety: if genotype is empty at this place, take neurons of last layer
|
||||
if not fanin_ids:
|
||||
fanin_ids = list(last_layer_ids)
|
||||
actuator = Actuator(a["id"], self.cx_actor, a["name"], fanin_ids, expect_count=len(fanin_ids))
|
||||
self.pid_by_id[a["id"]] = actuator
|
||||
actuator_pid_by_id[a["id"]] = actuator
|
||||
|
||||
actuator_actors = list(actuator_pid_by_id.values())
|
||||
|
||||
# build the neurons
|
||||
neuron_pid_by_id: Dict[Any, Neuron] = {}
|
||||
for layer in ordered_layers:
|
||||
for n in layer:
|
||||
input_idps = [(iw["input_id"], iw["weights"]) for iw in n["input_weights"]]
|
||||
neuron = Neuron(n["id"], self.cx_actor, n["activation_function"], input_idps, [])
|
||||
neuron_pid_by_id[n["id"]] = neuron
|
||||
self.pid_by_id[n["id"]] = neuron
|
||||
|
||||
# set next layer as output for non output neurons
|
||||
for li, layer in enumerate(ordered_layers[:-1]):
|
||||
next_layer = ordered_layers[li + 1]
|
||||
next_pids = [neuron_pid_by_id[nx["id"]] for nx in next_layer]
|
||||
for n in layer:
|
||||
neuron_pid_by_id[n["id"]].outputs = next_pids
|
||||
|
||||
# outputs for output layer: actuators
|
||||
for n in ordered_layers[-1]:
|
||||
outs: List[Actor] = []
|
||||
nid = n["id"]
|
||||
for a in actuators_json:
|
||||
fanin_ids = set(self._as_list(a.get("fanin_ids", [])))
|
||||
# default: if fanin empty, all last layer feed this actuator
|
||||
if not fanin_ids or nid in fanin_ids:
|
||||
outs.append(actuator_pid_by_id[a["id"]])
|
||||
neuron_pid_by_id[nid].outputs = outs
|
||||
|
||||
# build sensors
|
||||
sensors_json = self._get_sensors_json()
|
||||
sensor_actors: List[Sensor] = []
|
||||
first_layer = ordered_layers[0]
|
||||
first_layer_pid_by_id = {n["id"]: neuron_pid_by_id[n["id"]] for n in first_layer}
|
||||
|
||||
for s in sensors_json:
|
||||
# if the genotype contains fanout_ids we use exactly these
|
||||
fanout_ids = self._as_list(s.get("fanout_ids", []))
|
||||
if fanout_ids:
|
||||
fanout_pids = [first_layer_pid_by_id[fid] for fid in fanout_ids if fid in first_layer_pid_by_id]
|
||||
# if fanout_ids point to non-first-layer (unclean genotype), fallback:
|
||||
if not fanout_pids:
|
||||
fanout_pids = list(first_layer_pid_by_id.values())
|
||||
else:
|
||||
# default: all neurons in first layer
|
||||
fanout_pids = list(first_layer_pid_by_id.values())
|
||||
|
||||
sensor = Sensor(s["id"], self.cx_actor, s["name"], s["vector_length"], fanout_pids)
|
||||
self.pid_by_id[s["id"]] = sensor
|
||||
sensor_actors.append(sensor)
|
||||
|
||||
# fill cortex
|
||||
self.cx_actor.sensors = sensor_actors
|
||||
self.cx_actor.actuators = actuator_actors
|
||||
self.cx_actor.neurons = [neuron_pid_by_id[n["id"]] for n in self.g["neurons"]]
|
||||
|
||||
async def run(self) -> List[Tuple[Any, List[Tuple[Any, List[float]]]]]:
|
||||
self._build_pid_map()
|
||||
self._link_and_spawn()
|
||||
|
||||
# start tasks
|
||||
tasks = [asyncio.create_task(self.cx_actor.run())]
|
||||
for v in self.pid_by_id.values():
|
||||
if isinstance(v, Actor) and v is not self.cx_actor:
|
||||
tasks.append(asyncio.create_task(v.run()))
|
||||
|
||||
# register all actuator ids
|
||||
actuator_ids = [a["id"] for a in self._get_actuators_json()]
|
||||
await self.cx_actor.send(("register_actuators", actuator_ids))
|
||||
|
||||
# wait for result in report queue
|
||||
tag, weights = await self.report_queue.get()
|
||||
assert tag == "completed_backup"
|
||||
|
||||
# cleanup
|
||||
for t in tasks:
|
||||
if not t.done():
|
||||
t.cancel()
|
||||
return weights
|
||||
|
||||
@staticmethod
|
||||
def update_genotype_with_backup(geno: Dict[str, Any],
|
||||
backup: List[Tuple[Any, List[Tuple[Any, List[float]]]]]) -> Dict[str, Any]:
|
||||
by_id = {n["id"]: n for n in geno["neurons"]}
|
||||
for nid, idps in backup:
|
||||
if nid not in by_id:
|
||||
continue
|
||||
new_iw = []
|
||||
for item in idps:
|
||||
if isinstance(item[0], str) and item[0] == "bias":
|
||||
continue
|
||||
input_id, weights = item
|
||||
new_iw.append({"input_id": input_id, "weights": weights})
|
||||
by_id[nid]["input_weights"] = new_iw
|
||||
return geno
|
||||
|
||||
19
experiments/phenotype_genotype_map/run.py
Normal file
19
experiments/phenotype_genotype_map/run.py
Normal file
@@ -0,0 +1,19 @@
|
||||
import asyncio
|
||||
import json
|
||||
from pehno_geno_map import Exoself
|
||||
|
||||
# load genotype from json
|
||||
with open("genotype.json") as f:
|
||||
geno = json.load(f)
|
||||
|
||||
# map phenotype and run for 5 cycles
|
||||
ex = Exoself(geno, total_steps=5)
|
||||
# get backup
|
||||
backup = asyncio.run(ex.run())
|
||||
|
||||
print("Backup erhalten (gekürzt):", [(nid, len(idps)) for nid, idps in backup])
|
||||
|
||||
# update genotype and safe
|
||||
u_geno = Exoself.update_genotype_with_backup(geno, backup)
|
||||
with open("genotype_updated.json", "w") as f:
|
||||
json.dump(u_geno, f, indent=2)
|
||||
@@ -58,6 +58,7 @@ class Sensor(threading.Thread):
|
||||
except queue.Empty:
|
||||
continue
|
||||
|
||||
|
||||
class Actuator(threading.Thread):
|
||||
def __init__(self, actuator_queue):
|
||||
super().__init__()
|
||||
@@ -80,6 +81,7 @@ class Actuator(threading.Thread):
|
||||
except queue.Empty:
|
||||
continue
|
||||
|
||||
|
||||
class Cortex(threading.Thread):
|
||||
def __init__(self, sensor_queue, neuron_queue, actuator_queue):
|
||||
super().__init__()
|
||||
@@ -102,6 +104,7 @@ class Cortex(threading.Thread):
|
||||
print("Unknown command. Please use 'sense_think_act' or 'terminate'.")
|
||||
print("Cortex terminated.")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sensor_queue = queue.Queue()
|
||||
neuron_queue = queue.Queue()
|
||||
4
requirements.txt
Normal file
4
requirements.txt
Normal file
@@ -0,0 +1,4 @@
|
||||
gymnasium==1.2.1
|
||||
numpy>=1.23
|
||||
Box2D
|
||||
pygame
|
||||
Reference in New Issue
Block a user