Spaces:
Paused
Paused
File size: 7,218 Bytes
5e0b58b | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 | """Typed adapters from runtime subsystem statistics to dashboard models.
This module provides type-safe conversion functions that transform raw subsystem
statistics into the immutable data contracts expected by the dashboard.
All adapters include error handling and graceful fallbacks.
"""
from __future__ import annotations
from typing import Protocol
from .models import HomeostasisMetrics, SpikeMetrics, StructuralMetrics
# ============================================================================
# Protocols (Type Contracts)
# ============================================================================
class HomeostasisStatsLike(Protocol):
"""Fields consumed from the homeostasis engine stats snapshot."""
enabled: bool
updates: int
target_rate_hz: float
mean_rate_hz: float
mean_rate_error_hz: float
mean_threshold_adaptation: float
target_energy: float
mean_energy: float
mean_energy_error: float
active_neurons: int
class StructuralStatsLike(Protocol):
"""Fields consumed from the structural plasticity engine stats snapshot."""
neuron_count: int
synapse_count: int
new_neurons: int
pruned_neurons: int
new_synapses: int
pruned_synapses: int
growth_budget: float
used_budget: float
structural_changes: int
class SpikeStatsLike(Protocol):
"""Fields consumed from the spike recording stats snapshot."""
total_spikes: int
active_neurons: int
mean_firing_rate_hz: float
burst_index: float
synchrony: float
spike_count_last_tick: int
# ============================================================================
# Adapter Functions
# ============================================================================
def homeostasis_metrics(stats: HomeostasisStatsLike | None) -> HomeostasisMetrics:
"""Convert regulator statistics into the immutable dashboard contract.
Args:
stats: Statistics snapshot from the homeostasis engine, or None.
Returns:
HomeostasisMetrics object with safe defaults for missing values.
"""
if stats is None:
return HomeostasisMetrics(
enabled=False,
target_rate_hz=0.0,
actual_rate_hz=0.0,
rate_error_hz=0.0,
mean_rate_hz=0.0,
mean_rate_error_hz=0.0,
mean_threshold_adaptation=0.0,
target_energy=0.0,
mean_energy=0.0,
mean_energy_error=0.0,
active_neurons=0,
updates=0,
)
return HomeostasisMetrics(
enabled=bool(stats.enabled),
target_rate_hz=float(stats.target_rate_hz),
actual_rate_hz=float(stats.mean_rate_hz),
rate_error_hz=float(stats.mean_rate_error_hz),
mean_rate_hz=float(stats.mean_rate_hz),
mean_rate_error_hz=float(stats.mean_rate_error_hz),
mean_threshold_adaptation=float(stats.mean_threshold_adaptation),
target_energy=float(stats.target_energy),
mean_energy=float(stats.mean_energy),
mean_energy_error=float(stats.mean_energy_error),
active_neurons=int(stats.active_neurons),
updates=int(stats.updates),
)
def structural_metrics(stats: StructuralStatsLike | None) -> StructuralMetrics:
"""Convert structural plasticity statistics into dashboard model.
Args:
stats: Statistics snapshot from the structural plasticity engine, or None.
Returns:
StructuralMetrics object with safe defaults for missing values.
"""
if stats is None:
return StructuralMetrics(
neuron_count=0,
synapse_count=0,
new_neurons=0,
pruned_neurons=0,
new_synapses=0,
pruned_synapses=0,
growth_budget=0.0,
used_budget=0.0,
structural_changes=0,
)
return StructuralMetrics(
neuron_count=int(stats.neuron_count),
synapse_count=int(stats.synapse_count),
new_neurons=int(stats.new_neurons),
pruned_neurons=int(stats.pruned_neurons),
new_synapses=int(stats.new_synapses),
pruned_synapses=int(stats.pruned_synapses),
growth_budget=float(stats.growth_budget),
used_budget=float(stats.used_budget),
structural_changes=int(stats.structural_changes),
)
def spike_metrics(stats: SpikeStatsLike | None) -> SpikeMetrics:
"""Convert spike recording statistics into dashboard model.
Args:
stats: Statistics snapshot from the spike recorder, or None.
Returns:
SpikeMetrics object with safe defaults for missing values.
"""
if stats is None:
return SpikeMetrics(
total_spikes=0,
active_neurons=0,
mean_firing_rate_hz=0.0,
burst_index=0.0,
synchrony=0.0,
spike_count_last_tick=0,
)
return SpikeMetrics(
total_spikes=int(stats.total_spikes),
active_neurons=int(stats.active_neurons),
mean_firing_rate_hz=float(stats.mean_firing_rate_hz),
burst_index=float(stats.burst_index),
synchrony=float(stats.synchrony),
spike_count_last_tick=int(stats.spike_count_last_tick),
)
# ============================================================================
# Aggregated Adapter
# ============================================================================
def aggregate_metrics(
homeostasis: HomeostasisStatsLike | None = None,
structural: StructuralStatsLike | None = None,
spikes: SpikeStatsLike | None = None,
) -> dict[str, HomeostasisMetrics | StructuralMetrics | SpikeMetrics]:
"""Aggregate all metrics into a single dictionary.
Useful for batch conversion when the dashboard needs a full snapshot.
Args:
homeostasis: Homeostasis engine statistics.
structural: Structural plasticity engine statistics.
spikes: Spike recorder statistics.
Returns:
Dictionary with 'homeostasis', 'structural', and 'spikes' keys.
"""
return {
"homeostasis": homeostasis_metrics(homeostasis),
"structural": structural_metrics(structural),
"spikes": spike_metrics(spikes),
}
# ============================================================================
# Validation Helpers
# ============================================================================
def sanitize_float(value: float | None, default: float = 0.0) -> float:
"""Sanitize a float value, replacing None with default."""
if value is None:
return default
try:
return float(value)
except (TypeError, ValueError):
return default
def sanitize_int(value: int | None, default: int = 0) -> int:
"""Sanitize an integer value, replacing None with default."""
if value is None:
return default
try:
return int(value)
except (TypeError, ValueError):
return default
def sanitize_bool(value: bool | None, default: bool = False) -> bool:
"""Sanitize a boolean value, replacing None with default."""
if value is None:
return default
try:
return bool(value)
except (TypeError, ValueError):
return default
|