Files
neuroevolution/mathema/core/morphology.py
2026-02-21 10:58:05 +01:00

178 lines
5.9 KiB
Python

import uuid
import logging
from typing import Any, Callable, Dict, List, Union
log = logging.getLogger(__name__)
MorphologyType = Union[str, Callable[[str], List[Dict[str, Any]]]]
def generate_id() -> str:
return uuid.uuid4().hex
def get_InitSensor(morphology: MorphologyType):
"""
Return the initial sensor configuration for a given morphology.
This helper selects a minimal starting set of sensors used to bootstrap a
new agent's morphology. It resolves the full sensor list for the provided
morphology and returns a list containing only the first sensor entry.
Raises:
ValueError: If the resolved morphology provides no sensors.
"""
sensors = get_Sensors(morphology)
if not sensors:
log.error("Morphology has no sensors.")
raise ValueError("Morphology has no sensors.")
return [sensors[0]]
def get_InitActuator(morphology: MorphologyType):
"""
Return the initial actuator configuration for a given morphology.
This helper selects a minimal starting set of actuators used to bootstrap
a new agent's morphology. It resolves the full actuator list for the
provided morphology and returns a list containing only the first actuator
entry.
Raises:
ValueError: If the resolved morphology provides no actuators.
"""
actuators = get_Actuators(morphology)
if not actuators:
log.error("Morphology has no actuators.")
raise ValueError("Morphology has no actuators.")
return [actuators[0]]
def get_Sensors(morphology: MorphologyType) -> List[Dict[str, Any]]:
"""
Resolve and return the list of sensor specifications for a morphology.
The morphology may be provided as:
- a callable that accepts a kind string ("sensors" or "actuators"),
- a registered string key mapping to a known morphology implementation,
- or a module-like object exposing a callable 'xor_mimic' function.
Args:
morphology: Morphology selector (callable, string key, or module-like).
Returns:
A list of sensor specification dictionaries, each describing a sensor
actor (e.g., name, vector length, and associated scape).
"""
fn = _resolve_morphology(morphology)
return fn("sensors")
def get_Actuators(morphology: MorphologyType) -> List[Dict[str, Any]]:
"""
Resolve and return the list of actuator specifications for a morphology.
The morphology may be provided as:
- a callable that accepts a kind string ("sensors" or "actuators"),
- a registered string key mapping to a known morphology implementation,
- or a module-like object exposing a callable 'xor_mimic' function.
Args:
morphology: Morphology selector (callable, string key, or module-like).
Returns:
A list of actuator specification dictionaries, each describing an
actuator actor (e.g., name, vector length, and associated scape).
"""
fn = _resolve_morphology(morphology)
return fn("actuators")
def _resolve_morphology(morphology: MorphologyType) -> Callable[[str], List[Dict[str, Any]]]:
"""
Resolve a morphology selector into a callable that can produce sensor or actuator specs.
This function normalizes different morphology representations into a
single callable interface: fn(kind) -> List[Dict[str, Any]]. Supported
inputs are:
- A callable: returned as-is.
- A string key: looked up in a registry of known morphologies.
- A module-like object: if it exposes a callable attribute 'xor_mimic',
that callable is used.
Args:
morphology: Morphology selector (callable, string key, or module-like).
Returns:
A callable that accepts "sensors" or "actuators" and returns the
corresponding specification list.
Raises:
ValueError: If a string key is provided but not registered.
TypeError: If morphology cannot be resolved to a valid callable.
"""
if callable(morphology):
return morphology
if isinstance(morphology, str):
reg = {
"car_racing_features": car_racing_features
}
if morphology in reg:
return reg[morphology]
log.error(f"Unknown morphology name: {morphology}")
raise ValueError(f"Unknown morphology name: {morphology}")
try:
if hasattr(morphology, "xor_mimic") and callable(getattr(morphology, "xor_mimic")):
return getattr(morphology, "xor_mimic")
except Exception:
pass
log.error("morphology must be a callable, a module with 'xor_mimic', or a registered string key")
raise TypeError("morphology must be a callable, a module with 'xor_mimic', or a registered string key")
def car_racing_features(kind: str) -> List[Dict[str, Any]]:
"""
Provide a feature-based CarRacing morphology specification.
This morphology exposes:
- One sensor ("car_GetFeatures") producing a fixed-length feature vector
derived from a look-ahead horizon plus additional scalar features.
- One actuator ("car_ApplyAction") consuming a 3-element action vector.
Args:
kind: Either "sensors" or "actuators".
Returns:
A list containing a single specification dictionary for the requested kind.
Raises:
ValueError: If kind is not "sensors" or "actuators".
"""
LOOK_AHEAD = 10
feature_len = LOOK_AHEAD + 6
if kind == "sensors":
return [
{
"name": "car_GetFeatures",
"vector_length": feature_len,
"scape": "car_racing"
}
]
elif kind == "actuators":
return [
{
"name": "car_ApplyAction",
"vector_length": 3,
"scape": "car_racing"
}
]
else:
log.error(f"car_racing_features: unsupported kind '{kind}', expected 'sensors' or 'actuators'")
raise ValueError("car_racing_features: kind must be 'sensors' or 'actuators'")