MHRN-Space / src /playground /models.py
ThomasHeisig's picture
Sync GitHub main af692537c27175c8410bf57a747be0caf23718e2 (part 8)
9d98328 verified
Raw History Blame Contribute Delete
70.2 kB
"""Core data contracts for the non-canonical MHRN playground."""
from __future__ import annotations
import math
import re
from dataclasses import dataclass
from typing import Mapping, TypeAlias
from .closed_loop import CLOSED_LOOP_PRESETS
Coordinate: TypeAlias = tuple[float, ...]
Edge: TypeAlias = tuple[int, int]
ActionInputMap: TypeAlias = str | tuple[tuple[float, ...], ...]
_NAME_RE = re.compile(r"[^A-Za-z0-9_.-]+")
@dataclass(frozen=True, slots=True)
class Topology:
"""Neutral topology returned by every playground topology generator."""
name: str
dimensions: int
edges: tuple[Edge, ...]
coordinates: tuple[Coordinate, ...]
metadata: Mapping[str, object]
@dataclass(frozen=True, slots=True)
class PlaygroundConfig:
"""Validated bounded configuration for one exploratory playground session."""
name: str = "playground_session"
neuron_model: str = "izhikevich_rs"
synapse_model: str = "static"
plasticity_rule: str = "none"
topology: str = "mhrn_5d"
stimulus: str = "deterministic"
readout: str = "population_vector"
n_neurons: int = 128
edge_budget: int = 1024
ticks: int = 256
seed: int = 12345
dimensions: int = 5
dt_ms: float = 1.0
weight: float = 4.0
weight_decay: float = 0.0
weight_max_clamp: float = 100.0
delay_ticks: int = 1
stimulus_current: float = 8.0
stimulus_rate_hz: float = 20.0
radius: float = 0.35
k_neighbors: int = 16
rewiring_probability: float = 0.15
modules: int = 2
ensemble_runs: int = 1
persist: bool = False
pan_enabled: bool = False
pan_dimensions: int = 5
pan_closed_loop: bool = True
pan_feedback_gain: float = 0.05
pan_health_decay: float = 0.001
pan_apoptosis_threshold: float = 0.1
pan_aging_threshold: float = 0.3
pan_bias_current: float = 10.0
clock_mode: str = "continuous"
clock_base_hz: float = 100.0
clock_event_batch_ms: float = 10.0
neuron_backend: str = "cpu"
execution_mode: str = "TICK_ONLY"
execution_initial_mode: str = "EVENT_ONLY"
execution_theta_high: float = 0.30
execution_theta_low: float = 0.05
execution_hysteresis: float = 0.02
execution_min_dwell: int = 100
execution_activity_window: int = 100
execution_transition_mode: str = "clean"
execution_sync_on_switch: bool = True
execution_log_transitions: bool = True
execution_log_state_hash: bool = True
growth_enabled: bool = False
growth_neurogenesis: bool = True
growth_synaptogenesis: bool = True
growth_path_formation: bool = True
growth_pruning: bool = True
growth_activity_threshold: float = 0.25
growth_coactivation_threshold: int = 2
growth_information_threshold: float = 0.25
growth_prune_threshold: float = 0.05
growth_max_synapses_per_neuron: int = 128
growth_max_new_synapses_per_barrier: int = 8
cuda_budget_mb: int = 2048
offload_enabled: bool = False
offload_snapshot_interval: int = 1000
hardware_profile_name: str = "reference_cpu"
thalamic_gating_enabled: bool = False
thalamic_relay_threshold: float = 0.0
thalamic_attention_gain: float = 1.15
thalamic_inhibition_gain: float = 0.35
cortical_layers_enabled: bool = False
cortical_layer_count: int = 6
cortical_plasticity: bool = True
cortical_learning_rate: float = 0.01
behavior_learning_enabled: bool = False
behavior_action_count: int = 4
behavior_learning_rate: float = 0.2
behavior_epsilon: float = 0.2
behavior_target_action: int = 0
behavior_target_mode: str = "cycle"
behavior_min_activity: float = 0.01
behavior_episode_ticks: int = 16
behavior_bias_current: float = 3.0
geometry_lambda_a: float = 0.5
geometry_lambda_b: float = 0.5
geometry_sigma: float = 0.1
geometry_p0: float = 0.3
geometry_mode: str = "mixed_additive"
geometry_delay_velocity: float = 0.25
neural_io_enabled: bool = False
neural_io_input_channels: int = 16
neural_io_output_channels: int = 16
neural_io_input_codec: str = "population_latency_v1"
neural_io_output_decoder: str = "population_rate_v1"
neural_io_input_payload: object = 0.5
neural_io_window_ticks: int = 16
neural_io_input_current: float = 25.0
neural_io_input_role: str = "GATEWAY_AFFERENT"
neural_io_output_role: str = "GATEWAY_EFFERENT"
neural_io_phase: str = "QUERY"
neural_io_correlation_id: str = ""
neural_io_modality: str = "digital"
neural_io_source_id: str = "playground.input"
closed_loop_preset: str = "custom"
input_topology: str = "uniform"
input_channels: int = 16
input_channel_map: tuple[tuple[int, ...], ...] = ()
input_amplitude_per_channel: tuple[float, ...] = ()
input_frequency_per_channel: tuple[float, ...] = ()
input_phase_per_channel: tuple[float, ...] = ()
input_noise_sigma: float = 0.0
target_cue_channel: int = 0
reward_cue_channel: int = 0
action_feedback_channel: int = 0
posture_score_channel: int = 2
reward_event_channel: int = 3
posture_current_scale: float = 25.0
reward_event_scale: float = 25.0
posture_reward_enabled: bool = False
posture_weight_upright: float = 0.4
posture_weight_height: float = 0.3
posture_weight_stability: float = 0.2
posture_weight_symmetry: float = 0.1
posture_target_height: float = 1.0
posture_tilt_max: float = 1.0
posture_velocity_max: float = 5.0
trigger_good_score: float = 0.85
trigger_good_duration: int = 10
trigger_good_reward: float = 1.0
trigger_warning_score: float = 0.4
trigger_warning_reward: float = -0.3
trigger_falling_rate: float = -0.05
trigger_falling_reward: float = -1.0
trigger_collapse_score: float = 0.1
trigger_collapse_reward: float = -5.0
trigger_recovery_bonus: float = 2.0
reward_continuous_alpha: float = 0.1
episode_termination_enabled: bool = True
episode_max_ticks: int = 256
episode_reset_on_collapse: bool = True
pan_feedback_delay: int = 0
pan_feedback_source: str = "population"
pan_feedback_target: str = "all"
pan_feedback_nonlinearity: str = "linear"
pan_feedback_threshold: float = 0.0
pan_feedback_saturation: float = 100.0
action_loop_enabled: bool = False
action_loop_delay: int = 1
action_persistence: int = 1
action_to_input_map: ActionInputMap = "auto"
action_space_size: int = 4
action_coupling_strength: float = 0.0
action_noise: float = 0.0
freeze_actions: bool = False
frozen_action_sequence: tuple[int, ...] = ()
reward_signal_enabled: bool = False
reward_magnitude: float = 1.0
reward_delay_ticks: int = 0
reward_shaping: str = "sparse"
reward_baseline: float = 0.0
reward_decay: float = 0.0
reward_channel: int = 0
freeze_rewards: bool = False
frozen_reward_sequence: tuple[float, ...] = ()
parity_reference_source: str = "CPU_PYTHON_PLAYGROUND"
parity_reference_commit: str = ""
target_cue_control: str = "aligned"
target_encoding: str = "none"
target_persistence: int = 1
target_cue_current: float = 0.0
target_shuffle: bool = False
target_predictability: str = "deterministic"
credit_window: int = 64
eligibility_trace_tau: float = 200.0
credit_assignment: str = "none"
td_lambda: float = 0.9
gamma_discount: float = 0.95
neuron_threshold_variance: float = 0.0
neuron_tau_m_variance: float = 0.0
inhibitory_fraction: float = 0.0
gaba_strength: float = 1.0
e_i_ratio: float = 1.0
delay_distribution: str = "fixed"
delay_mean_ticks: float = 1.0
refractory_variance: float = 0.0
adaptation_strength: float = 0.0
adaptation_tau: float = 200.0
oscillation_enabled: bool = False
oscillation_frequency: float = 8.0
geometry_input_coupling: bool = False
geometry_input_sigma: float = 0.2
sandbox_enabled: bool = False
sandbox_physics: str = "stick_figure"
sandbox_action_coupling: str = "direct"
sandbox_sensor_noise: float = 0.05
@classmethod
def from_mapping(cls, payload: Mapping[str, object]) -> "PlaygroundConfig":
"""Build a config from untrusted API/CLI input and validate all bounds."""
defaults = cls()
raw_payload = dict(payload)
requested_preset = raw_payload.get("closed_loop_preset", "custom")
if not isinstance(requested_preset, str):
raise ValueError("closed_loop_preset must be a string")
requested_preset = requested_preset.strip() or "custom"
def resolve_preset(name: str, seen: set[str]) -> dict[str, object]:
if name == "custom":
return {}
if name in seen:
raise ValueError("closed_loop preset inheritance cycle")
item = CLOSED_LOOP_PRESETS.get(name)
if item is None:
raise ValueError(f"unknown closed_loop_preset: {name}")
raw_settings = item.get("settings", {})
if not isinstance(raw_settings, Mapping):
raise ValueError(f"invalid settings for preset: {name}")
settings = dict(raw_settings)
parent = settings.pop("closed_loop_preset", None)
merged: dict[str, object] = {}
if isinstance(parent, str):
merged.update(resolve_preset(parent, seen | {name}))
merged.update(settings)
return merged
if requested_preset != "custom":
preset_payload = resolve_preset(requested_preset, set())
preset_payload.update(raw_payload)
payload = preset_payload
else:
payload = raw_payload
def text(name: str, default: str) -> str:
value = payload.get(name, default)
if not isinstance(value, str):
raise ValueError(f"{name} must be a string")
value = value.strip()
if not value:
raise ValueError(f"{name} must not be empty")
return value
def integer(name: str, default: int) -> int:
value = payload.get(name, default)
if isinstance(value, bool) or not isinstance(value, (int, float)):
raise ValueError(f"{name} must be an integer")
converted = int(value)
if float(value) != float(converted):
raise ValueError(f"{name} must be an integer")
return converted
def number(name: str, default: float) -> float:
value = payload.get(name, default)
if isinstance(value, bool) or not isinstance(value, (int, float)):
raise ValueError(f"{name} must be numeric")
return float(value)
def numbers(name: str, default: tuple[float, ...]) -> tuple[float, ...]:
value = payload.get(name, default)
if not isinstance(value, (list, tuple)):
raise ValueError(f"{name} must be an array")
result: list[float] = []
for item in value:
if isinstance(item, bool) or not isinstance(item, (int, float)):
raise ValueError(f"{name} values must be numeric")
result.append(float(item))
return tuple(result)
def integers(name: str, default: tuple[int, ...]) -> tuple[int, ...]:
value = payload.get(name, default)
if not isinstance(value, (list, tuple)):
raise ValueError(f"{name} must be an array")
result: list[int] = []
for item in value:
if isinstance(item, bool) or not isinstance(item, (int, float)):
raise ValueError(f"{name} values must be integers")
converted = int(item)
if float(item) != float(converted):
raise ValueError(f"{name} values must be integers")
result.append(converted)
return tuple(result)
def optional_text(name: str, default: str) -> str:
value = payload.get(name, default)
if not isinstance(value, str):
raise ValueError(f"{name} must be a string")
return value.strip()
def integer_groups(
name: str,
default: tuple[tuple[int, ...], ...],
) -> tuple[tuple[int, ...], ...]:
value = payload.get(name, default)
if not isinstance(value, (list, tuple)):
raise ValueError(f"{name} must be an array of arrays")
groups: list[tuple[int, ...]] = []
for group in value:
if not isinstance(group, (list, tuple)):
raise ValueError(f"{name} must be an array of arrays")
converted: list[int] = []
for item in group:
if isinstance(item, bool) or not isinstance(item, (int, float)):
raise ValueError(
f"{name} values must be integer neuron indices"
)
integer_value = int(item)
if float(item) != float(integer_value):
raise ValueError(
f"{name} values must be integer neuron indices"
)
converted.append(integer_value)
groups.append(tuple(converted))
return tuple(groups)
def action_map(name: str, default: ActionInputMap) -> ActionInputMap:
value = payload.get(name, default)
if isinstance(value, str):
normalized = value.strip().lower()
if normalized not in {"auto", "spatial"}:
raise ValueError(f"{name} must be auto, spatial, or an array")
return normalized
if not isinstance(value, (list, tuple)):
raise ValueError(f"{name} must be auto, spatial, or an array")
rows: list[tuple[float, ...]] = []
for row in value:
if not isinstance(row, (list, tuple)):
raise ValueError(f"{name} array rows must be arrays")
converted: list[float] = []
for item in row:
if isinstance(item, bool) or not isinstance(item, (int, float)):
raise ValueError(f"{name} values must be numeric")
converted.append(float(item))
rows.append(tuple(converted))
return tuple(rows)
raw_name = text("name", defaults.name)
safe_name = _NAME_RE.sub("_", raw_name).strip("._-")[:80] or defaults.name
config = cls(
name=safe_name,
neuron_model=text("neuron_model", defaults.neuron_model),
synapse_model=text("synapse_model", defaults.synapse_model),
plasticity_rule=text("plasticity_rule", defaults.plasticity_rule),
topology=text("topology", defaults.topology),
stimulus=text("stimulus", defaults.stimulus),
readout=text("readout", defaults.readout),
n_neurons=integer("n_neurons", defaults.n_neurons),
edge_budget=integer("edge_budget", defaults.edge_budget),
ticks=integer("ticks", defaults.ticks),
seed=integer("seed", defaults.seed),
dimensions=integer("dimensions", defaults.dimensions),
dt_ms=number("dt_ms", defaults.dt_ms),
weight=number("weight", defaults.weight),
weight_decay=number("weight_decay", defaults.weight_decay),
weight_max_clamp=number("weight_max_clamp", defaults.weight_max_clamp),
delay_ticks=integer("delay_ticks", defaults.delay_ticks),
stimulus_current=number("stimulus_current", defaults.stimulus_current),
stimulus_rate_hz=number("stimulus_rate_hz", defaults.stimulus_rate_hz),
radius=number("radius", defaults.radius),
k_neighbors=integer("k_neighbors", defaults.k_neighbors),
rewiring_probability=number(
"rewiring_probability", defaults.rewiring_probability
),
modules=integer("modules", defaults.modules),
ensemble_runs=integer("ensemble_runs", defaults.ensemble_runs),
persist=bool(payload.get("persist", defaults.persist)),
pan_enabled=bool(payload.get("pan_enabled", defaults.pan_enabled)),
pan_dimensions=integer("pan_dimensions", defaults.pan_dimensions),
pan_closed_loop=bool(
payload.get("pan_closed_loop", defaults.pan_closed_loop)
),
pan_feedback_gain=number("pan_feedback_gain", defaults.pan_feedback_gain),
pan_health_decay=number("pan_health_decay", defaults.pan_health_decay),
pan_apoptosis_threshold=number(
"pan_apoptosis_threshold", defaults.pan_apoptosis_threshold
),
pan_aging_threshold=number(
"pan_aging_threshold", defaults.pan_aging_threshold
),
pan_bias_current=number("pan_bias_current", defaults.pan_bias_current),
clock_mode=text("clock_mode", defaults.clock_mode),
clock_base_hz=number("clock_base_hz", defaults.clock_base_hz),
clock_event_batch_ms=number(
"clock_event_batch_ms", defaults.clock_event_batch_ms
),
neuron_backend=text("neuron_backend", defaults.neuron_backend).lower(),
execution_mode=text("execution_mode", defaults.execution_mode).upper(),
execution_initial_mode=text(
"execution_initial_mode", defaults.execution_initial_mode
).upper(),
execution_theta_high=number(
"execution_theta_high", defaults.execution_theta_high
),
execution_theta_low=number(
"execution_theta_low", defaults.execution_theta_low
),
execution_hysteresis=number(
"execution_hysteresis", defaults.execution_hysteresis
),
execution_min_dwell=integer(
"execution_min_dwell", defaults.execution_min_dwell
),
execution_activity_window=integer(
"execution_activity_window", defaults.execution_activity_window
),
execution_transition_mode=text(
"execution_transition_mode", defaults.execution_transition_mode
).lower(),
execution_sync_on_switch=bool(
payload.get(
"execution_sync_on_switch", defaults.execution_sync_on_switch
)
),
execution_log_transitions=bool(
payload.get(
"execution_log_transitions", defaults.execution_log_transitions
)
),
execution_log_state_hash=bool(
payload.get(
"execution_log_state_hash", defaults.execution_log_state_hash
)
),
growth_enabled=bool(payload.get("growth_enabled", defaults.growth_enabled)),
growth_neurogenesis=bool(
payload.get("growth_neurogenesis", defaults.growth_neurogenesis)
),
growth_synaptogenesis=bool(
payload.get("growth_synaptogenesis", defaults.growth_synaptogenesis)
),
growth_path_formation=bool(
payload.get("growth_path_formation", defaults.growth_path_formation)
),
growth_pruning=bool(payload.get("growth_pruning", defaults.growth_pruning)),
growth_activity_threshold=number(
"growth_activity_threshold", defaults.growth_activity_threshold
),
growth_coactivation_threshold=integer(
"growth_coactivation_threshold", defaults.growth_coactivation_threshold
),
growth_information_threshold=number(
"growth_information_threshold", defaults.growth_information_threshold
),
growth_prune_threshold=number(
"growth_prune_threshold", defaults.growth_prune_threshold
),
growth_max_synapses_per_neuron=integer(
"growth_max_synapses_per_neuron",
defaults.growth_max_synapses_per_neuron,
),
growth_max_new_synapses_per_barrier=integer(
"growth_max_new_synapses_per_barrier",
defaults.growth_max_new_synapses_per_barrier,
),
cuda_budget_mb=integer("cuda_budget_mb", defaults.cuda_budget_mb),
offload_enabled=bool(
payload.get("offload_enabled", defaults.offload_enabled)
),
offload_snapshot_interval=integer(
"offload_snapshot_interval", defaults.offload_snapshot_interval
),
hardware_profile_name=text(
"hardware_profile_name", defaults.hardware_profile_name
),
thalamic_gating_enabled=bool(
payload.get("thalamic_gating_enabled", defaults.thalamic_gating_enabled)
),
thalamic_relay_threshold=number(
"thalamic_relay_threshold", defaults.thalamic_relay_threshold
),
thalamic_attention_gain=number(
"thalamic_attention_gain", defaults.thalamic_attention_gain
),
thalamic_inhibition_gain=number(
"thalamic_inhibition_gain", defaults.thalamic_inhibition_gain
),
cortical_layers_enabled=bool(
payload.get("cortical_layers_enabled", defaults.cortical_layers_enabled)
),
cortical_layer_count=integer(
"cortical_layer_count", defaults.cortical_layer_count
),
cortical_plasticity=bool(
payload.get("cortical_plasticity", defaults.cortical_plasticity)
),
cortical_learning_rate=number(
"cortical_learning_rate", defaults.cortical_learning_rate
),
behavior_learning_enabled=bool(
payload.get(
"behavior_learning_enabled", defaults.behavior_learning_enabled
)
),
behavior_action_count=integer(
"behavior_action_count", defaults.behavior_action_count
),
behavior_learning_rate=number(
"behavior_learning_rate", defaults.behavior_learning_rate
),
behavior_epsilon=number("behavior_epsilon", defaults.behavior_epsilon),
behavior_target_action=integer(
"behavior_target_action", defaults.behavior_target_action
),
behavior_target_mode=text(
"behavior_target_mode", defaults.behavior_target_mode
).lower(),
behavior_min_activity=number(
"behavior_min_activity", defaults.behavior_min_activity
),
behavior_episode_ticks=integer(
"behavior_episode_ticks", defaults.behavior_episode_ticks
),
behavior_bias_current=number(
"behavior_bias_current", defaults.behavior_bias_current
),
geometry_lambda_a=number("geometry_lambda_a", defaults.geometry_lambda_a),
geometry_lambda_b=number("geometry_lambda_b", defaults.geometry_lambda_b),
geometry_sigma=number("geometry_sigma", defaults.geometry_sigma),
geometry_p0=number("geometry_p0", defaults.geometry_p0),
geometry_mode=text("geometry_mode", defaults.geometry_mode),
geometry_delay_velocity=number(
"geometry_delay_velocity", defaults.geometry_delay_velocity
),
neural_io_enabled=bool(
payload.get("neural_io_enabled", defaults.neural_io_enabled)
),
neural_io_input_channels=integer(
"neural_io_input_channels", defaults.neural_io_input_channels
),
neural_io_output_channels=integer(
"neural_io_output_channels", defaults.neural_io_output_channels
),
neural_io_input_codec=text(
"neural_io_input_codec", defaults.neural_io_input_codec
),
neural_io_output_decoder=text(
"neural_io_output_decoder", defaults.neural_io_output_decoder
),
neural_io_input_payload=payload.get(
"neural_io_input_payload", defaults.neural_io_input_payload
),
neural_io_window_ticks=integer(
"neural_io_window_ticks", defaults.neural_io_window_ticks
),
neural_io_input_current=number(
"neural_io_input_current", defaults.neural_io_input_current
),
neural_io_input_role=text(
"neural_io_input_role", defaults.neural_io_input_role
),
neural_io_output_role=text(
"neural_io_output_role", defaults.neural_io_output_role
),
neural_io_phase=text("neural_io_phase", defaults.neural_io_phase),
neural_io_correlation_id=text(
"neural_io_correlation_id",
defaults.neural_io_correlation_id or "auto",
),
neural_io_modality=text("neural_io_modality", defaults.neural_io_modality),
neural_io_source_id=text(
"neural_io_source_id", defaults.neural_io_source_id
),
closed_loop_preset=requested_preset,
input_topology=text("input_topology", defaults.input_topology).lower(),
input_channels=integer("input_channels", defaults.input_channels),
input_channel_map=integer_groups(
"input_channel_map", defaults.input_channel_map
),
input_amplitude_per_channel=numbers(
"input_amplitude_per_channel",
defaults.input_amplitude_per_channel,
),
input_frequency_per_channel=numbers(
"input_frequency_per_channel",
defaults.input_frequency_per_channel,
),
input_phase_per_channel=numbers(
"input_phase_per_channel",
defaults.input_phase_per_channel,
),
input_noise_sigma=number("input_noise_sigma", defaults.input_noise_sigma),
target_cue_channel=integer(
"target_cue_channel", defaults.target_cue_channel
),
reward_cue_channel=integer(
"reward_cue_channel", defaults.reward_cue_channel
),
action_feedback_channel=integer(
"action_feedback_channel", defaults.action_feedback_channel
),
posture_score_channel=integer(
"posture_score_channel", defaults.posture_score_channel
),
reward_event_channel=integer(
"reward_event_channel", defaults.reward_event_channel
),
posture_current_scale=number(
"posture_current_scale", defaults.posture_current_scale
),
reward_event_scale=number(
"reward_event_scale", defaults.reward_event_scale
),
posture_reward_enabled=bool(
payload.get("posture_reward_enabled", defaults.posture_reward_enabled)
),
posture_weight_upright=number(
"posture_weight_upright", defaults.posture_weight_upright
),
posture_weight_height=number(
"posture_weight_height", defaults.posture_weight_height
),
posture_weight_stability=number(
"posture_weight_stability", defaults.posture_weight_stability
),
posture_weight_symmetry=number(
"posture_weight_symmetry", defaults.posture_weight_symmetry
),
posture_target_height=number(
"posture_target_height", defaults.posture_target_height
),
posture_tilt_max=number("posture_tilt_max", defaults.posture_tilt_max),
posture_velocity_max=number(
"posture_velocity_max", defaults.posture_velocity_max
),
trigger_good_score=number(
"trigger_good_score", defaults.trigger_good_score
),
trigger_good_duration=integer(
"trigger_good_duration", defaults.trigger_good_duration
),
trigger_good_reward=number(
"trigger_good_reward", defaults.trigger_good_reward
),
trigger_warning_score=number(
"trigger_warning_score", defaults.trigger_warning_score
),
trigger_warning_reward=number(
"trigger_warning_reward", defaults.trigger_warning_reward
),
trigger_falling_rate=number(
"trigger_falling_rate", defaults.trigger_falling_rate
),
trigger_falling_reward=number(
"trigger_falling_reward", defaults.trigger_falling_reward
),
trigger_collapse_score=number(
"trigger_collapse_score", defaults.trigger_collapse_score
),
trigger_collapse_reward=number(
"trigger_collapse_reward", defaults.trigger_collapse_reward
),
trigger_recovery_bonus=number(
"trigger_recovery_bonus", defaults.trigger_recovery_bonus
),
reward_continuous_alpha=number(
"reward_continuous_alpha", defaults.reward_continuous_alpha
),
episode_termination_enabled=bool(
payload.get(
"episode_termination_enabled", defaults.episode_termination_enabled
)
),
episode_max_ticks=integer("episode_max_ticks", defaults.episode_max_ticks),
episode_reset_on_collapse=bool(
payload.get(
"episode_reset_on_collapse", defaults.episode_reset_on_collapse
)
),
pan_feedback_delay=integer(
"pan_feedback_delay", defaults.pan_feedback_delay
),
pan_feedback_source=text(
"pan_feedback_source", defaults.pan_feedback_source
).lower(),
pan_feedback_target=text(
"pan_feedback_target", defaults.pan_feedback_target
).lower(),
pan_feedback_nonlinearity=text(
"pan_feedback_nonlinearity", defaults.pan_feedback_nonlinearity
).lower(),
pan_feedback_threshold=number(
"pan_feedback_threshold", defaults.pan_feedback_threshold
),
pan_feedback_saturation=number(
"pan_feedback_saturation", defaults.pan_feedback_saturation
),
action_loop_enabled=bool(
payload.get("action_loop_enabled", defaults.action_loop_enabled)
),
action_loop_delay=integer("action_loop_delay", defaults.action_loop_delay),
action_persistence=integer(
"action_persistence", defaults.action_persistence
),
action_to_input_map=action_map(
"action_to_input_map", defaults.action_to_input_map
),
action_space_size=integer("action_space_size", defaults.action_space_size),
action_coupling_strength=number(
"action_coupling_strength", defaults.action_coupling_strength
),
action_noise=number("action_noise", defaults.action_noise),
freeze_actions=bool(payload.get("freeze_actions", defaults.freeze_actions)),
frozen_action_sequence=integers(
"frozen_action_sequence", defaults.frozen_action_sequence
),
reward_signal_enabled=bool(
payload.get(
"reward_signal_enabled",
defaults.reward_signal_enabled,
)
),
reward_magnitude=number("reward_magnitude", defaults.reward_magnitude),
reward_delay_ticks=integer(
"reward_delay_ticks", defaults.reward_delay_ticks
),
reward_shaping=text("reward_shaping", defaults.reward_shaping).lower(),
reward_baseline=number("reward_baseline", defaults.reward_baseline),
reward_decay=number("reward_decay", defaults.reward_decay),
reward_channel=integer(
"reward_channel",
integer("reward_cue_channel", defaults.reward_channel),
),
freeze_rewards=bool(payload.get("freeze_rewards", defaults.freeze_rewards)),
frozen_reward_sequence=numbers(
"frozen_reward_sequence", defaults.frozen_reward_sequence
),
parity_reference_source=optional_text(
"parity_reference_source", defaults.parity_reference_source
),
parity_reference_commit=optional_text(
"parity_reference_commit", defaults.parity_reference_commit
),
target_cue_control=text(
"target_cue_control", defaults.target_cue_control
).lower(),
target_encoding=text("target_encoding", defaults.target_encoding).lower(),
target_persistence=integer(
"target_persistence", defaults.target_persistence
),
target_cue_current=number(
"target_cue_current", defaults.target_cue_current
),
target_shuffle=bool(payload.get("target_shuffle", defaults.target_shuffle)),
target_predictability=text(
"target_predictability", defaults.target_predictability
).lower(),
credit_window=integer("credit_window", defaults.credit_window),
eligibility_trace_tau=number(
"eligibility_trace_tau", defaults.eligibility_trace_tau
),
credit_assignment=text(
"credit_assignment", defaults.credit_assignment
).lower(),
td_lambda=number("td_lambda", defaults.td_lambda),
gamma_discount=number("gamma_discount", defaults.gamma_discount),
neuron_threshold_variance=number(
"neuron_threshold_variance",
defaults.neuron_threshold_variance,
),
neuron_tau_m_variance=number(
"neuron_tau_m_variance", defaults.neuron_tau_m_variance
),
inhibitory_fraction=number(
"inhibitory_fraction", defaults.inhibitory_fraction
),
gaba_strength=number("gaba_strength", defaults.gaba_strength),
e_i_ratio=number("e_i_ratio", defaults.e_i_ratio),
delay_distribution=text(
"delay_distribution", defaults.delay_distribution
).lower(),
delay_mean_ticks=number("delay_mean_ticks", defaults.delay_mean_ticks),
refractory_variance=number(
"refractory_variance", defaults.refractory_variance
),
adaptation_strength=number(
"adaptation_strength", defaults.adaptation_strength
),
adaptation_tau=number("adaptation_tau", defaults.adaptation_tau),
oscillation_enabled=bool(
payload.get(
"oscillation_enabled",
defaults.oscillation_enabled,
)
),
oscillation_frequency=number(
"oscillation_frequency", defaults.oscillation_frequency
),
geometry_input_coupling=bool(
payload.get(
"geometry_input_coupling",
defaults.geometry_input_coupling,
)
),
geometry_input_sigma=number(
"geometry_input_sigma", defaults.geometry_input_sigma
),
sandbox_enabled=bool(
payload.get("sandbox_enabled", defaults.sandbox_enabled)
),
sandbox_physics=text("sandbox_physics", defaults.sandbox_physics).lower(),
sandbox_action_coupling=text(
"sandbox_action_coupling",
defaults.sandbox_action_coupling,
).lower(),
sandbox_sensor_noise=number(
"sandbox_sensor_noise", defaults.sandbox_sensor_noise
),
)
config.validate()
return config
def validate(self) -> None:
if not 2 <= self.n_neurons <= 1024:
raise ValueError("n_neurons must be between 2 and 1024")
max_edges = min(20_000, self.n_neurons * (self.n_neurons - 1))
if not self.n_neurons <= self.edge_budget <= max_edges:
raise ValueError(f"edge_budget must be between n_neurons and {max_edges}")
if not 1 <= self.ticks <= 2048:
raise ValueError("ticks must be between 1 and 2048")
if not 1 <= self.dimensions <= 32:
raise ValueError("dimensions must be between 1 and 32")
if not 0.05 <= self.dt_ms <= 5.0:
raise ValueError("dt_ms must be between 0.05 and 5.0")
if not 0.0 <= self.weight <= 100.0:
raise ValueError("weight must be between 0 and 100")
if not 0.0 <= self.weight_decay <= 1.0:
raise ValueError("weight_decay must be between 0 and 1")
if not 0.0 < self.weight_max_clamp <= 100.0:
raise ValueError("weight_max_clamp must be > 0 and <= 100")
if not 1 <= self.delay_ticks <= 64:
raise ValueError("delay_ticks must be between 1 and 64")
if not 0.0 <= self.stimulus_current <= 500.0:
raise ValueError("stimulus_current must be between 0 and 500")
if not 0.0 <= self.stimulus_rate_hz <= 1000.0:
raise ValueError("stimulus_rate_hz must be between 0 and 1000")
if not 0.001 <= self.radius <= 2.0:
raise ValueError("radius must be between 0.001 and 2.0")
if not 1 <= self.k_neighbors <= min(64, self.n_neurons - 1):
raise ValueError("k_neighbors outside allowed range")
if not 0.0 <= self.rewiring_probability <= 1.0:
raise ValueError("rewiring_probability must be between 0 and 1")
if not 1 <= self.modules <= min(32, self.n_neurons):
raise ValueError("modules outside allowed range")
if not 1 <= self.ensemble_runs <= 8:
raise ValueError("ensemble_runs must be between 1 and 8")
if not 5 <= self.pan_dimensions <= 32:
raise ValueError("pan_dimensions must be between 5 and 32")
if not 0.0 <= self.pan_feedback_gain <= 5.0:
raise ValueError("pan_feedback_gain must be between 0 and 5")
if not 0.0 <= self.pan_health_decay <= 1.0:
raise ValueError("pan_health_decay must be between 0 and 1")
if not 0.0 <= self.pan_apoptosis_threshold <= 1.0:
raise ValueError("pan_apoptosis_threshold must be between 0 and 1")
if not 0.0 <= self.pan_aging_threshold <= 1.0:
raise ValueError("pan_aging_threshold must be between 0 and 1")
if not 0.0 <= self.pan_bias_current <= 500.0:
raise ValueError("pan_bias_current must be between 0 and 500")
if self.pan_aging_threshold < self.pan_apoptosis_threshold:
raise ValueError("pan_aging_threshold must be >= pan_apoptosis_threshold")
if self.clock_mode not in {"continuous", "dual"}:
raise ValueError("clock_mode must be continuous or dual")
if not 1.0 <= self.clock_base_hz <= 10_000.0:
raise ValueError("clock_base_hz must be between 1 and 10000")
if not self.dt_ms <= self.clock_event_batch_ms <= 1000.0:
raise ValueError("clock_event_batch_ms must be between dt_ms and 1000")
if self.neuron_backend == "cuda_pan" and self.neuron_model != "pan_adex_5d":
raise ValueError("cuda_pan requires pan_adex_5d")
if self.neuron_backend not in {"cpu", "cuda_membrane", "cuda_pan"}:
raise ValueError("neuron_backend must be cpu, cuda_membrane or cuda_pan")
if self.neuron_backend != "cpu" and self.neuron_model not in {
"lif",
"adex",
"pan_adex_5d",
}:
raise ValueError("CUDA membrane backend supports LIF/AdEx/PAN-AdEx only")
if self.execution_mode not in {"EVENT_ONLY", "TICK_ONLY", "HYBRID_AUTO"}:
raise ValueError("unsupported execution_mode")
if self.execution_initial_mode not in {"EVENT_ONLY", "TICK_ONLY"}:
raise ValueError("execution_initial_mode must be EVENT_ONLY or TICK_ONLY")
if not 0.0 <= self.execution_theta_low <= self.execution_theta_high <= 1.0:
raise ValueError("execution thresholds must satisfy 0 <= low <= high <= 1")
if not 0.0 <= self.execution_hysteresis <= 0.5:
raise ValueError("execution_hysteresis must be between 0 and 0.5")
if not 0 <= self.execution_min_dwell <= 1_000_000:
raise ValueError("execution_min_dwell must be between 0 and 1000000")
if not 1 <= self.execution_activity_window <= 1_000_000:
raise ValueError("execution_activity_window must be between 1 and 1000000")
if self.execution_transition_mode not in {"clean", "fast", "debug"}:
raise ValueError("unsupported execution_transition_mode")
if not 0.0 <= self.growth_activity_threshold <= 1.0:
raise ValueError("growth_activity_threshold must be between 0 and 1")
if not 1 <= self.growth_coactivation_threshold <= 1000:
raise ValueError("growth_coactivation_threshold must be between 1 and 1000")
if not 0.0 <= self.growth_information_threshold <= 1.0:
raise ValueError("growth_information_threshold must be between 0 and 1")
if not 0.0 <= self.growth_prune_threshold <= 100.0:
raise ValueError("growth_prune_threshold must be between 0 and 100")
if not 1 <= self.growth_max_synapses_per_neuron <= 512:
raise ValueError("growth_max_synapses_per_neuron must be between 1 and 512")
if not 1 <= self.growth_max_new_synapses_per_barrier <= 256:
raise ValueError(
"growth_max_new_synapses_per_barrier must be between 1 and 256"
)
if not 128 <= self.cuda_budget_mb <= 16_384:
raise ValueError("cuda_budget_mb must be between 128 and 16384")
if not 1 <= self.offload_snapshot_interval <= 1_000_000:
raise ValueError("offload_snapshot_interval must be between 1 and 1000000")
if self.growth_enabled and self.clock_mode != "dual":
raise ValueError("growth_enabled requires clock_mode=dual")
if self.hardware_profile_name not in {
"reference_cpu",
"cuda_8gb_balanced_plan",
}:
raise ValueError("unsupported hardware_profile_name")
if not 0.0 <= self.thalamic_relay_threshold <= 1.0:
raise ValueError("thalamic_relay_threshold must be between 0 and 1")
if not 0.0 <= self.thalamic_attention_gain <= 4.0:
raise ValueError("thalamic_attention_gain must be between 0 and 4")
if not 0.0 <= self.thalamic_inhibition_gain <= 1.0:
raise ValueError("thalamic_inhibition_gain must be between 0 and 1")
if not 2 <= self.cortical_layer_count <= 12:
raise ValueError("cortical_layer_count must be between 2 and 12")
if not 0.0 <= self.cortical_learning_rate <= 1.0:
raise ValueError("cortical_learning_rate must be between 0 and 1")
if not 1 <= self.behavior_action_count <= 16:
raise ValueError("behavior_action_count must be between 1 and 16")
if not 0.0 < self.behavior_learning_rate <= 1.0:
raise ValueError("behavior_learning_rate must be > 0 and <= 1")
if not 0.0 <= self.behavior_epsilon <= 1.0:
raise ValueError("behavior_epsilon must be between 0 and 1")
if not 0 <= self.behavior_target_action < self.behavior_action_count:
raise ValueError("behavior_target_action outside action range")
if self.behavior_target_mode not in {"fixed", "cycle"}:
raise ValueError("behavior_target_mode must be fixed or cycle")
if not 0.0 <= self.behavior_min_activity <= 1.0:
raise ValueError("behavior_min_activity must be between 0 and 1")
if not 1 <= self.behavior_episode_ticks <= 1_000_000:
raise ValueError("behavior_episode_ticks must be between 1 and 1000000")
if not 0.0 <= self.behavior_bias_current <= 100.0:
raise ValueError("behavior_bias_current must be between 0 and 100")
if not 0.0 <= self.geometry_lambda_a <= 10.0:
raise ValueError("geometry_lambda_a must be between 0 and 10")
if not 0.0 <= self.geometry_lambda_b <= 10.0:
raise ValueError("geometry_lambda_b must be between 0 and 10")
if not 0.001 <= self.geometry_sigma <= 2.0:
raise ValueError("geometry_sigma must be between 0.001 and 2")
if not 0.0 <= self.geometry_p0 <= 1.0:
raise ValueError("geometry_p0 must be between 0 and 1")
if self.geometry_mode not in {"mixed_additive", "shortcut_union"}:
raise ValueError("geometry_mode must be mixed_additive or shortcut_union")
if not 0.001 <= self.geometry_delay_velocity <= 10.0:
raise ValueError("geometry_delay_velocity must be between 0.001 and 10")
if (
self.closed_loop_preset != "custom"
and self.closed_loop_preset not in CLOSED_LOOP_PRESETS
):
raise ValueError("unsupported closed_loop_preset")
if self.input_topology not in {
"uniform",
"channel_partitioned",
"spatial_gradient",
"random_per_neuron",
}:
raise ValueError("unsupported input_topology")
if not 1 <= self.input_channels <= 64:
raise ValueError("input_channels must be between 1 and 64")
if len(self.input_channel_map) > self.input_channels:
raise ValueError("input_channel_map has more groups than input_channels")
for group in self.input_channel_map:
if any(index < 0 or index >= self.n_neurons for index in group):
raise ValueError("input_channel_map contains an invalid neuron index")
for name, values, low, high in (
(
"input_amplitude_per_channel",
self.input_amplitude_per_channel,
0.0,
500.0,
),
(
"input_frequency_per_channel",
self.input_frequency_per_channel,
1.0,
200.0,
),
(
"input_phase_per_channel",
self.input_phase_per_channel,
0.0,
2.0 * math.pi,
),
):
if len(values) > self.input_channels:
raise ValueError(f"{name} has more values than input_channels")
if any(value < low or value > high for value in values):
raise ValueError(f"{name} values outside allowed range")
if not 0.0 <= self.input_noise_sigma <= 1.0:
raise ValueError("input_noise_sigma must be between 0 and 1")
for name, channel in (
("target_cue_channel", self.target_cue_channel),
("reward_cue_channel", self.reward_cue_channel),
("reward_channel", self.reward_channel),
("action_feedback_channel", self.action_feedback_channel),
("posture_score_channel", self.posture_score_channel),
("reward_event_channel", self.reward_event_channel),
):
if not 0 <= channel < self.input_channels:
raise ValueError(f"{name} outside input channel range")
if not 0 <= self.pan_feedback_delay <= 64:
raise ValueError("pan_feedback_delay must be between 0 and 64")
if self.pan_feedback_source not in {
"population",
"layer",
"subset",
"hypervector",
}:
raise ValueError("unsupported pan_feedback_source")
if self.pan_feedback_target not in {"all", "layer", "random_subset"}:
raise ValueError("unsupported pan_feedback_target")
if self.pan_feedback_nonlinearity not in {"linear", "tanh", "sign", "clip"}:
raise ValueError("unsupported pan_feedback_nonlinearity")
if not 0.0 <= self.pan_feedback_threshold <= 1.0:
raise ValueError("pan_feedback_threshold must be between 0 and 1")
if not 0.0 <= self.pan_feedback_saturation <= 100.0:
raise ValueError("pan_feedback_saturation must be between 0 and 100")
if not 1 <= self.action_loop_delay <= 64:
raise ValueError("action_loop_delay must be between 1 and 64")
if not 1 <= self.action_persistence <= 128:
raise ValueError("action_persistence must be between 1 and 128")
if not 1 <= self.action_space_size <= 32:
raise ValueError("action_space_size must be between 1 and 32")
if (
self.behavior_target_mode == "fixed"
and self.behavior_target_action >= self.action_space_size
):
raise ValueError("behavior_target_action outside closed-loop action range")
if not 0.0 <= self.action_coupling_strength <= 10.0:
raise ValueError("action_coupling_strength must be between 0 and 10")
if not 0.0 <= self.action_noise <= 1.0:
raise ValueError("action_noise must be between 0 and 1")
required_episodes = self.ticks // self.behavior_episode_ticks
if self.freeze_actions:
if len(self.frozen_action_sequence) < required_episodes:
raise ValueError(
"frozen_action_sequence must cover every completed episode"
)
if any(
action < 0 or action >= self.action_space_size
for action in self.frozen_action_sequence
):
raise ValueError("frozen_action_sequence contains invalid action")
if self.freeze_rewards:
if not self.reward_signal_enabled:
raise ValueError("freeze_rewards requires reward_signal_enabled")
if len(self.frozen_reward_sequence) < required_episodes:
raise ValueError(
"frozen_reward_sequence must cover every completed episode"
)
if any(
not math.isfinite(reward) or abs(reward) > 100.0
for reward in self.frozen_reward_sequence
):
raise ValueError(
"frozen_reward_sequence values must be finite and <= 100 abs"
)
if self.freeze_actions or self.freeze_rewards:
if self.parity_reference_source != "CPU_PYTHON_PLAYGROUND":
raise ValueError(
"parity_reference_source must be CPU_PYTHON_PLAYGROUND"
)
if re.fullmatch(r"[0-9a-fA-F]{7,40}", self.parity_reference_commit) is None:
raise ValueError(
"parity_reference_commit must be a 7-40 character git SHA"
)
if not isinstance(self.action_to_input_map, str):
if len(self.action_to_input_map) > self.action_space_size:
raise ValueError("action_to_input_map has more rows than actions")
for row in self.action_to_input_map:
if len(row) > self.input_channels:
raise ValueError("action_to_input_map row exceeds input_channels")
if not 0.0 <= self.reward_magnitude <= 10.0:
raise ValueError("reward_magnitude must be between 0 and 10")
if not 0 <= self.reward_delay_ticks <= 64:
raise ValueError("reward_delay_ticks must be between 0 and 64")
if self.reward_shaping not in {"sparse", "dense", "potential_based"}:
raise ValueError("unsupported reward_shaping")
if not -1.0 <= self.reward_baseline <= 1.0:
raise ValueError("reward_baseline must be between -1 and 1")
if not 0.0 <= self.reward_decay <= 1.0:
raise ValueError("reward_decay must be between 0 and 1")
if self.target_cue_control not in {"aligned", "randomized", "absent"}:
raise ValueError("unsupported target_cue_control")
if self.target_encoding not in {
"none",
"one_hot",
"rate",
"population_latency",
}:
raise ValueError("unsupported target_encoding")
if not 1 <= self.target_persistence <= 256:
raise ValueError("target_persistence must be between 1 and 256")
if not 0.0 <= self.target_cue_current <= 500.0:
raise ValueError("target_cue_current must be between 0 and 500")
if self.target_predictability not in {
"deterministic",
"stochastic",
"adversarial",
}:
raise ValueError("unsupported target_predictability")
if not 1 <= self.credit_window <= 512:
raise ValueError("credit_window must be between 1 and 512")
if not 1.0 <= self.eligibility_trace_tau <= 1000.0:
raise ValueError("eligibility_trace_tau must be between 1 and 1000 ms")
if self.credit_assignment not in {"none", "trace", "reward_modulated_stdp"}:
raise ValueError("unsupported credit_assignment")
if not 0.0 <= self.td_lambda <= 1.0:
raise ValueError("td_lambda must be between 0 and 1")
if not 0.0 <= self.gamma_discount <= 1.0:
raise ValueError("gamma_discount must be between 0 and 1")
for name, value in (
("neuron_threshold_variance", self.neuron_threshold_variance),
("neuron_tau_m_variance", self.neuron_tau_m_variance),
("inhibitory_fraction", self.inhibitory_fraction),
("refractory_variance", self.refractory_variance),
):
if not 0.0 <= value <= 1.0:
raise ValueError(f"{name} must be between 0 and 1")
if not 0.0 <= self.gaba_strength <= 10.0:
raise ValueError("gaba_strength must be between 0 and 10")
if not 0.0 <= self.e_i_ratio <= 10.0:
raise ValueError("e_i_ratio must be between 0 and 10")
if self.delay_distribution not in {"fixed", "uniform", "lognormal", "gamma"}:
raise ValueError("unsupported delay_distribution")
if not 1.0 <= self.delay_mean_ticks <= 32.0:
raise ValueError("delay_mean_ticks must be between 1 and 32")
if not 0.0 <= self.adaptation_strength <= 10.0:
raise ValueError("adaptation_strength must be between 0 and 10")
if not 10.0 <= self.adaptation_tau <= 1000.0:
raise ValueError("adaptation_tau must be between 10 and 1000 ms")
if not 0.5 <= self.oscillation_frequency <= 100.0:
raise ValueError("oscillation_frequency must be between 0.5 and 100 Hz")
if not 0.001 <= self.geometry_input_sigma <= 2.0:
raise ValueError("geometry_input_sigma must be between 0.001 and 2")
if not 0.0 <= self.sandbox_sensor_noise <= 1.0:
raise ValueError("sandbox_sensor_noise must be between 0 and 1")
if self.sandbox_physics != "stick_figure":
raise ValueError("sandbox_physics must be stick_figure")
if self.sandbox_action_coupling != "direct":
raise ValueError("sandbox_action_coupling must be direct")
if self.neural_io_enabled:
if not 1 <= self.neural_io_input_channels <= 256:
raise ValueError("neural_io_input_channels must be between 1 and 256")
if not 1 <= self.neural_io_output_channels <= 256:
raise ValueError("neural_io_output_channels must be between 1 and 256")
if (
self.neural_io_input_channels + self.neural_io_output_channels
> self.n_neurons
):
raise ValueError("neural I/O populations must not overlap")
if not 1 <= self.neural_io_window_ticks <= self.ticks:
raise ValueError(
"neural_io_window_ticks must be between 1 and session ticks"
)
if not 0.0 <= self.neural_io_input_current <= 500.0:
raise ValueError("neural_io_input_current must be between 0 and 500")
if self.neural_io_input_role not in {
"AFFERENT",
"GATEWAY_AFFERENT",
}:
raise ValueError(
"neural_io_input_role must be AFFERENT or GATEWAY_AFFERENT"
)
if self.neural_io_output_role not in {
"EFFERENT",
"GATEWAY_EFFERENT",
}:
raise ValueError(
"neural_io_output_role must be EFFERENT or GATEWAY_EFFERENT"
)
if self.neural_io_phase not in {
"IDLE",
"QUERY",
"WAIT",
"RESPONSE",
"TIMEOUT",
}:
raise ValueError("unsupported neural_io_phase")
def to_dict(self) -> dict[str, object]:
return {
"name": self.name,
"neuron_model": self.neuron_model,
"synapse_model": self.synapse_model,
"plasticity_rule": self.plasticity_rule,
"topology": self.topology,
"stimulus": self.stimulus,
"readout": self.readout,
"n_neurons": self.n_neurons,
"edge_budget": self.edge_budget,
"ticks": self.ticks,
"seed": self.seed,
"dimensions": self.dimensions,
"dt_ms": self.dt_ms,
"weight": self.weight,
"weight_decay": self.weight_decay,
"weight_max_clamp": self.weight_max_clamp,
"delay_ticks": self.delay_ticks,
"stimulus_current": self.stimulus_current,
"stimulus_rate_hz": self.stimulus_rate_hz,
"radius": self.radius,
"k_neighbors": self.k_neighbors,
"rewiring_probability": self.rewiring_probability,
"modules": self.modules,
"ensemble_runs": self.ensemble_runs,
"persist": self.persist,
"pan_enabled": self.pan_enabled,
"pan_dimensions": self.pan_dimensions,
"pan_closed_loop": self.pan_closed_loop,
"pan_feedback_gain": self.pan_feedback_gain,
"pan_health_decay": self.pan_health_decay,
"pan_apoptosis_threshold": self.pan_apoptosis_threshold,
"pan_aging_threshold": self.pan_aging_threshold,
"pan_bias_current": self.pan_bias_current,
"clock_mode": self.clock_mode,
"clock_base_hz": self.clock_base_hz,
"clock_event_batch_ms": self.clock_event_batch_ms,
"neuron_backend": self.neuron_backend,
"execution_mode": self.execution_mode,
"execution_initial_mode": self.execution_initial_mode,
"execution_theta_high": self.execution_theta_high,
"execution_theta_low": self.execution_theta_low,
"execution_hysteresis": self.execution_hysteresis,
"execution_min_dwell": self.execution_min_dwell,
"execution_activity_window": self.execution_activity_window,
"execution_transition_mode": self.execution_transition_mode,
"execution_sync_on_switch": self.execution_sync_on_switch,
"execution_log_transitions": self.execution_log_transitions,
"execution_log_state_hash": self.execution_log_state_hash,
"growth_enabled": self.growth_enabled,
"growth_neurogenesis": self.growth_neurogenesis,
"growth_synaptogenesis": self.growth_synaptogenesis,
"growth_path_formation": self.growth_path_formation,
"growth_pruning": self.growth_pruning,
"growth_activity_threshold": self.growth_activity_threshold,
"growth_coactivation_threshold": self.growth_coactivation_threshold,
"growth_information_threshold": self.growth_information_threshold,
"growth_prune_threshold": self.growth_prune_threshold,
"growth_max_synapses_per_neuron": self.growth_max_synapses_per_neuron,
"growth_max_new_synapses_per_barrier": (
self.growth_max_new_synapses_per_barrier
),
"cuda_budget_mb": self.cuda_budget_mb,
"offload_enabled": self.offload_enabled,
"offload_snapshot_interval": self.offload_snapshot_interval,
"hardware_profile_name": self.hardware_profile_name,
"thalamic_gating_enabled": self.thalamic_gating_enabled,
"thalamic_relay_threshold": self.thalamic_relay_threshold,
"thalamic_attention_gain": self.thalamic_attention_gain,
"thalamic_inhibition_gain": self.thalamic_inhibition_gain,
"cortical_layers_enabled": self.cortical_layers_enabled,
"cortical_layer_count": self.cortical_layer_count,
"cortical_plasticity": self.cortical_plasticity,
"cortical_learning_rate": self.cortical_learning_rate,
"behavior_learning_enabled": self.behavior_learning_enabled,
"behavior_action_count": self.behavior_action_count,
"behavior_learning_rate": self.behavior_learning_rate,
"behavior_epsilon": self.behavior_epsilon,
"behavior_target_action": self.behavior_target_action,
"behavior_target_mode": self.behavior_target_mode,
"behavior_min_activity": self.behavior_min_activity,
"behavior_episode_ticks": self.behavior_episode_ticks,
"behavior_bias_current": self.behavior_bias_current,
"geometry_lambda_a": self.geometry_lambda_a,
"geometry_lambda_b": self.geometry_lambda_b,
"geometry_sigma": self.geometry_sigma,
"geometry_p0": self.geometry_p0,
"geometry_mode": self.geometry_mode,
"geometry_delay_velocity": self.geometry_delay_velocity,
"neural_io_enabled": self.neural_io_enabled,
"neural_io_input_channels": self.neural_io_input_channels,
"neural_io_output_channels": self.neural_io_output_channels,
"neural_io_input_codec": self.neural_io_input_codec,
"neural_io_output_decoder": self.neural_io_output_decoder,
"neural_io_input_payload_present": (
self.neural_io_input_payload is not None
),
"neural_io_window_ticks": self.neural_io_window_ticks,
"neural_io_input_current": self.neural_io_input_current,
"neural_io_input_role": self.neural_io_input_role,
"neural_io_output_role": self.neural_io_output_role,
"neural_io_phase": self.neural_io_phase,
"neural_io_correlation_id": self.neural_io_correlation_id,
"neural_io_modality": self.neural_io_modality,
"neural_io_source_id": self.neural_io_source_id,
"closed_loop_preset": self.closed_loop_preset,
"input_topology": self.input_topology,
"input_channels": self.input_channels,
"input_channel_map": [list(group) for group in self.input_channel_map],
"input_amplitude_per_channel": list(self.input_amplitude_per_channel),
"input_frequency_per_channel": list(self.input_frequency_per_channel),
"input_phase_per_channel": list(self.input_phase_per_channel),
"input_noise_sigma": self.input_noise_sigma,
"target_cue_channel": self.target_cue_channel,
"reward_cue_channel": self.reward_cue_channel,
"action_feedback_channel": self.action_feedback_channel,
"posture_score_channel": self.posture_score_channel,
"reward_event_channel": self.reward_event_channel,
"posture_current_scale": self.posture_current_scale,
"reward_event_scale": self.reward_event_scale,
"posture_reward_enabled": self.posture_reward_enabled,
"posture_weight_upright": self.posture_weight_upright,
"posture_weight_height": self.posture_weight_height,
"posture_weight_stability": self.posture_weight_stability,
"posture_weight_symmetry": self.posture_weight_symmetry,
"posture_target_height": self.posture_target_height,
"posture_tilt_max": self.posture_tilt_max,
"posture_velocity_max": self.posture_velocity_max,
"trigger_good_score": self.trigger_good_score,
"trigger_good_duration": self.trigger_good_duration,
"trigger_good_reward": self.trigger_good_reward,
"trigger_warning_score": self.trigger_warning_score,
"trigger_warning_reward": self.trigger_warning_reward,
"trigger_falling_rate": self.trigger_falling_rate,
"trigger_falling_reward": self.trigger_falling_reward,
"trigger_collapse_score": self.trigger_collapse_score,
"trigger_collapse_reward": self.trigger_collapse_reward,
"trigger_recovery_bonus": self.trigger_recovery_bonus,
"reward_continuous_alpha": self.reward_continuous_alpha,
"episode_termination_enabled": self.episode_termination_enabled,
"episode_max_ticks": self.episode_max_ticks,
"episode_reset_on_collapse": self.episode_reset_on_collapse,
"pan_feedback_delay": self.pan_feedback_delay,
"pan_feedback_source": self.pan_feedback_source,
"pan_feedback_target": self.pan_feedback_target,
"pan_feedback_nonlinearity": self.pan_feedback_nonlinearity,
"pan_feedback_threshold": self.pan_feedback_threshold,
"pan_feedback_saturation": self.pan_feedback_saturation,
"action_loop_enabled": self.action_loop_enabled,
"action_loop_delay": self.action_loop_delay,
"action_persistence": self.action_persistence,
"action_to_input_map": (
self.action_to_input_map
if isinstance(self.action_to_input_map, str)
else [list(row) for row in self.action_to_input_map]
),
"action_space_size": self.action_space_size,
"action_coupling_strength": self.action_coupling_strength,
"action_noise": self.action_noise,
"freeze_actions": self.freeze_actions,
"frozen_action_sequence": list(self.frozen_action_sequence),
"reward_signal_enabled": self.reward_signal_enabled,
"reward_magnitude": self.reward_magnitude,
"reward_delay_ticks": self.reward_delay_ticks,
"reward_shaping": self.reward_shaping,
"reward_baseline": self.reward_baseline,
"reward_decay": self.reward_decay,
"reward_channel": self.reward_channel,
"freeze_rewards": self.freeze_rewards,
"frozen_reward_sequence": list(self.frozen_reward_sequence),
"parity_reference_source": self.parity_reference_source,
"parity_reference_commit": self.parity_reference_commit,
"target_cue_control": self.target_cue_control,
"target_encoding": self.target_encoding,
"target_persistence": self.target_persistence,
"target_cue_current": self.target_cue_current,
"target_shuffle": self.target_shuffle,
"target_predictability": self.target_predictability,
"credit_window": self.credit_window,
"eligibility_trace_tau": self.eligibility_trace_tau,
"credit_assignment": self.credit_assignment,
"td_lambda": self.td_lambda,
"gamma_discount": self.gamma_discount,
"neuron_threshold_variance": self.neuron_threshold_variance,
"neuron_tau_m_variance": self.neuron_tau_m_variance,
"inhibitory_fraction": self.inhibitory_fraction,
"gaba_strength": self.gaba_strength,
"e_i_ratio": self.e_i_ratio,
"delay_distribution": self.delay_distribution,
"delay_mean_ticks": self.delay_mean_ticks,
"refractory_variance": self.refractory_variance,
"adaptation_strength": self.adaptation_strength,
"adaptation_tau": self.adaptation_tau,
"oscillation_enabled": self.oscillation_enabled,
"oscillation_frequency": self.oscillation_frequency,
"geometry_input_coupling": self.geometry_input_coupling,
"geometry_input_sigma": self.geometry_input_sigma,
"sandbox_enabled": self.sandbox_enabled,
"sandbox_physics": self.sandbox_physics,
"sandbox_action_coupling": self.sandbox_action_coupling,
"sandbox_sensor_noise": self.sandbox_sensor_noise,
}
def to_runtime_dict(self) -> dict[str, object]:
"""Return transient config including the exact I/O payload.
This mapping is for in-process re-execution only and must never be
persisted or returned as a session result.
"""
payload = self.to_dict()
payload["neural_io_input_payload"] = self.neural_io_input_payload
return payload