From 76b4f0ea55a6b90e2f99a37a9c8eaa805c0afe23 Mon Sep 17 00:00:00 2001 From: dfeen87 <158860247+dfeen87@users.noreply.github.com> Date: Tue, 21 Jul 2026 00:06:59 +0000 Subject: [PATCH] Consolidate and unify the governed runtime in U.F.O. framework Perform a Full Integration Pass to integrate and unify the U.F.O., L.D.E., membrane geometry, routing, stability, workloads, diagnostics, and visualization subsystems. Features: - Deterministic seeding logic in main simulation engines (SingleAgentEngine, MultiAgentEngine, MultiClusterEngine). - Strict invariant validation inside GovernorConfig and BehavioralString, raising ValidationError or GeometryValidationError on corrupt geometry or invalid parameters. - Standardized 120 DPI, 100-sample configurable resolution for reproducible spatial visualization panels. - Workload preprocessing structural validation. - End-of-tick verification for all 6 key stability metrics. - Flake8 and Mypy compliance across the entire package. Co-authored-by: google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com> --- .coverage | Bin 53248 -> 69632 bytes radial_membrane_ai/governor.py | 20 +++++ radial_membrane_ai/membrane.py | 20 +++++ radial_membrane_ai/tests/test_model.py | 34 ++++++++ radial_membrane_ai/tests/test_ufo_engine.py | 42 ++++++++++ radial_membrane_ai/tests/test_workloads.py | 33 ++++++++ radial_membrane_ai/ufo_engine/multi_agent.py | 79 +++++++++++++++++- .../ufo_engine/multi_cluster.py | 79 +++++++++++++++++- radial_membrane_ai/ufo_engine/single_agent.py | 68 ++++++++++++++- .../visualization/visualizer.py | 14 +++- 10 files changed, 377 insertions(+), 12 deletions(-) diff --git a/.coverage b/.coverage index 577b46159bab918e9e6828239a55fbb53ecbccda..3b1fb028c5080fd3517b1c93f87a936fe7a484be 100644 GIT binary patch literal 69632 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 EAE{1(M*si- delta 412 zcmYjM&ntsr9Dknod7jm1`_4q<$Gg^q4Y`y33zR|*FGX67VJOtz9mQpO4i40uIn9cq z2nPqT3wqURv%}^hitOF2m&aV3KK1#2etpBcG`ve9GPcAVX|LUa5Mtm$a0h!3f_`wt zt88|V+>8fwH|wATDSY#xj;D1yCR{GKS(`-A&>yE!eymksEVOcyqWETf3yRV;yIfID zZ9#Ak`=G)Aw20Jd;4_+svQDD#IjzaM None: + self.validate() + + def validate(self) -> None: + """Enforces strict invariants on Governor Configuration.""" + weights = [ + ("w_d", self.w_d), ("w_a", self.w_a), ("w_l", self.w_l), + ("w_c", self.w_c), ("w_T", self.w_T), ("w_K", self.w_K), + ("lyapunov_alpha", self.lyapunov_alpha), ("lyapunov_beta", self.lyapunov_beta), + ("lyapunov_gamma", self.lyapunov_gamma) + ] + for w_name, w_val in weights: + if w_val < 0.0: + raise ValidationError(f"Weight {w_name} must be non-negative, got {w_val}.") + if self.learning_rate <= 0.0: + raise ValidationError(f"learning_rate must be strictly positive, got {self.learning_rate}.") + if self.suppression_weight < 0.0: + raise ValidationError(f"suppression_weight must be non-negative, got {self.suppression_weight}.") + """ Configuration coefficients for the local cost function and stability control. diff --git a/radial_membrane_ai/membrane.py b/radial_membrane_ai/membrane.py index 2c60bcf..6e02145 100644 --- a/radial_membrane_ai/membrane.py +++ b/radial_membrane_ai/membrane.py @@ -11,6 +11,7 @@ from dataclasses import dataclass import numpy as np from typing import TYPE_CHECKING +from radial_membrane_ai.exceptions import ValidationError, GeometryValidationError if TYPE_CHECKING: from radial_membrane_ai.facet import FacetVector @@ -18,6 +19,25 @@ @dataclass class BehavioralString: + def __post_init__(self) -> None: + self.validate() + + def validate(self) -> None: + """Enforces strict invariants on BehavioralString.""" + if self.radius < 0.0: + raise GeometryValidationError(f"Radius cannot be negative, got {self.radius}.") + if self.tension < 0.0: + raise GeometryValidationError(f"Tension cannot be negative, got {self.tension}.") + if self.stiffness < 0.0: + raise GeometryValidationError(f"Stiffness cannot be negative, got {self.stiffness}.") + if self.activation < 0.0: + raise ValidationError(f"Activation cannot be negative, got {self.activation}.") + if self.cost < 0.0: + raise ValidationError(f"Cost cannot be negative, got {self.cost}.") + if self.theta < -1e-5 or self.theta > 2.0 * math.pi + 1e-5: + raise GeometryValidationError(f"Angle theta must be in [0, 2pi], got {self.theta}.") + if not (1 <= self.index <= 12): + raise ValidationError(f"Index must be between 1 and 12, got {self.index}.") """ A controllable dimension of behavior on the radial identity membrane. diff --git a/radial_membrane_ai/tests/test_model.py b/radial_membrane_ai/tests/test_model.py index 7f798c2..f05d9ff 100644 --- a/radial_membrane_ai/tests/test_model.py +++ b/radial_membrane_ai/tests/test_model.py @@ -58,6 +58,29 @@ def test_membrane_initialization() -> None: with pytest.raises(ValueError): membrane.get_quadrant_activation("invalid_quadrant") + # Test BehavioralString strict validation + from radial_membrane_ai.exceptions import ValidationError, GeometryValidationError + with pytest.raises(GeometryValidationError, match="Radius cannot be negative"): + BehavioralString("err", 1, 0.0, 0.5, -1.0, 0.0, 0.0, 0.0) + + with pytest.raises(GeometryValidationError, match="Tension cannot be negative"): + BehavioralString("err", 1, 0.0, 0.5, 1.0, 0.0, -0.1, 0.0) + + with pytest.raises(GeometryValidationError, match="Stiffness cannot be negative"): + BehavioralString("err", 1, 0.0, 0.5, 1.0, 0.0, 0.0, -0.5) + + with pytest.raises(ValidationError, match="Activation cannot be negative"): + BehavioralString("err", 1, 0.0, -0.5, 1.0, 0.0, 0.0, 0.0) + + with pytest.raises(ValidationError, match="Cost cannot be negative"): + BehavioralString("err", 1, 0.0, 0.5, 1.0, -2.0, 0.0, 0.0) + + with pytest.raises(GeometryValidationError, match="Angle theta must be in"): + BehavioralString("err", 1, -1.0, 0.5, 1.0, 0.0, 0.0, 0.0) + + with pytest.raises(ValidationError, match="Index must be between"): + BehavioralString("err", 15, 0.0, 0.5, 1.0, 0.0, 0.0, 0.0) + def test_membrane_activation_updates_and_field() -> None: """ @@ -228,6 +251,17 @@ def test_governor_costs_and_stability() -> None: gov.energy_history = [1.0, 2.0, 3.0, 4.0, 5.0] assert not gov.is_stable() # growing and unstable + # Test GovernorConfig validation + from radial_membrane_ai.exceptions import ValidationError + with pytest.raises(ValidationError, match="Weight w_d must be non-negative"): + GovernorConfig(w_d=-0.1) + + with pytest.raises(ValidationError, match="learning_rate must be strictly positive"): + GovernorConfig(learning_rate=0.0) + + with pytest.raises(ValidationError, match="suppression_weight must be non-negative"): + GovernorConfig(suppression_weight=-1.5) + def test_boundary_geometry() -> None: """ diff --git a/radial_membrane_ai/tests/test_ufo_engine.py b/radial_membrane_ai/tests/test_ufo_engine.py index 321280a..b70f2d7 100644 --- a/radial_membrane_ai/tests/test_ufo_engine.py +++ b/radial_membrane_ai/tests/test_ufo_engine.py @@ -4,6 +4,7 @@ """ from __future__ import annotations +import pytest import numpy as np from radial_membrane_ai.ufo_engine import ( @@ -219,3 +220,44 @@ def test_serialization_and_exports(tmp_path) -> None: } export_simulation_results_to_json(dummy_results, str(results_file)) assert results_file.exists() + + +def test_stability_metrics_post_tick_violations() -> None: + """ + Tests that post-tick verification on stability metrics (Lyapunov energy, + tension, stiffness, curvature, deviation) correctly raises GovernanceError on violations. + """ + from radial_membrane_ai.exceptions import GovernanceError + + # 1. Test Lyapunov energy violation + engine_sa = SingleAgentEngine() + engine_sa.membrane.strings[0].tension = 20.0 # Exceeds hard_energy_limit of 15.0 through computed energy + with pytest.raises(GovernanceError, match="Lyapunov energy.*exceeded hard limit"): + engine_sa.tick(task_value=0.5, excitation=np.ones(12) * 0.5) + + # 2. Test Tension violation + engine_sa2 = SingleAgentEngine() + if hasattr(engine_sa2.membrane, "temporal_state") and engine_sa2.membrane.temporal_state: + engine_sa2.membrane.temporal_state.accumulated_tension = 100.0 # Exceeds hard_tension_limit of 10.0 + with pytest.raises(GovernanceError, match="Temporal tension.*exceeded hard limit"): + engine_sa2.tick(task_value=0.5, excitation=np.ones(12) * 0.5) + + # 3. Test Stiffness violation + engine_sa3 = SingleAgentEngine() + # Artificially modify a string stiffness to exceed 2.0 + engine_sa3.membrane.strings[0].stiffness = 5.0 + with pytest.raises(GovernanceError, match="Stiffness.*exceeded hard limit"): + engine_sa3.tick(task_value=0.5, excitation=np.ones(12) * 0.5) + + # 4. Test Curvature violation + engine_sa4 = SingleAgentEngine() + setattr(engine_sa4.boundary, "curvature", lambda theta, **kwargs: 100.0) + with pytest.raises(GovernanceError, match="Boundary curvature.*exceeded hard limit"): + engine_sa4.tick(task_value=0.5, excitation=np.ones(12) * 0.5) + + # 5. Test Radius Deviation violation + engine_sa5 = SingleAgentEngine() + setattr(engine_sa5.boundary, "update_boundary", lambda *args, **kwargs: None) + engine_sa5.boundary.radius_deviation[1] = 100.0 + with pytest.raises(GovernanceError, match="Radius deviation.*exceeded hard limit"): + engine_sa5.tick(task_value=0.5, excitation=np.ones(12) * 0.5) diff --git a/radial_membrane_ai/tests/test_workloads.py b/radial_membrane_ai/tests/test_workloads.py index 4120296..3221630 100644 --- a/radial_membrane_ai/tests/test_workloads.py +++ b/radial_membrane_ai/tests/test_workloads.py @@ -606,3 +606,36 @@ def mc_custom_apply(eng: Any, step: Any, tgt: Any) -> None: setattr(engine_mc_any, "_apply_actions", mc_custom_apply) _ = engine_mc.run(mc_workload) + + +def test_invalid_workload_validation_error() -> None: + """ + Tests that a workload with a non-existent agent/cluster ID causes + WorkloadConfigurationError to be raised when executed. + """ + from radial_membrane_ai.exceptions import WorkloadConfigurationError + + # We define a workload targeting a non-existent entity and verify validation catches it. + invalid_step = WorkloadStep( + agent_actions={ + "bad_action": Action.change_regime("non_existent_agent_99", "STOCHASTIC") + } + ) + invalid_workload = Workload( + name="Invalid Workload Test", + description="Should fail validation", + target=SimulationTarget.MULTI_AGENT, + regime_expectation=KernelRegimeType.BALANCED, + stability_expectation=StabilityBand.GREEN, + coherence_expectation=0.5, + envelope_expectation=PolicyEnvelope(), + steps=[invalid_step] + ) + + engine = WorkloadEngine() + with pytest.raises(WorkloadConfigurationError, match="references invalid entity"): + engine.run(invalid_workload) + + # Test run_trace also runs the same validation + with pytest.raises(WorkloadConfigurationError, match="references invalid entity"): + engine.run_trace(invalid_workload) diff --git a/radial_membrane_ai/ufo_engine/multi_agent.py b/radial_membrane_ai/ufo_engine/multi_agent.py index 9c8088a..687f46b 100644 --- a/radial_membrane_ai/ufo_engine/multi_agent.py +++ b/radial_membrane_ai/ufo_engine/multi_agent.py @@ -60,7 +60,8 @@ def __init__( n_agents: int = 3, cost_weights: CostWeights | None = None, band_config: StabilityBandConfig | None = None, - custom_agents: List[UFOAgent] | None = None + custom_agents: List[UFOAgent] | None = None, + seed: int = 0 ) -> None: """ Initializes the Multi-Agent Engine. @@ -105,7 +106,7 @@ def __init__( self.residual_history: List[float] = [] # Call deterministic environment seeding - set_deterministic_env() + set_deterministic_env(seed) # Kernel Regime Expansion Layer components self.regime_manager = RegimeManager() @@ -711,9 +712,28 @@ def tick(self, task_value: float, excitation: np.ndarray) -> str: ) # Post-tick invariant assertions and near-violation warning logs + hard_energy_limit = 15.0 hard_tension_limit = 10.0 - latest_tension = self.temporal_state.accumulated_tension if self.temporal_state is not None else 0.0 + hard_stiffness_limit = 2.0 + hard_curvature_limit = 50.0 + hard_deviation_limit = 5.0 + + # Check Lyapunov energy + latest_energy = self.h_hol_history[-1] if self.h_hol_history else 0.0 + if latest_energy > hard_energy_limit: + from radial_membrane_ai.exceptions import GovernanceError + raise GovernanceError( + f"Governed bound violated: Multi-agent energy ({latest_energy:.4f}) " + f"exceeded hard limit ({hard_energy_limit})." + ) + elif latest_energy >= 0.95 * hard_energy_limit: + self.interventions.append( + f"Near-violation warning: Multi-agent energy ({latest_energy:.4f}) " + f"is within 5% of hard limit ({hard_energy_limit})." + ) + # Check Tension + latest_tension = self.temporal_state.accumulated_tension if self.temporal_state is not None else 0.0 if latest_tension > hard_tension_limit: from radial_membrane_ai.exceptions import GovernanceError raise GovernanceError( @@ -726,6 +746,59 @@ def tick(self, task_value: float, excitation: np.ndarray) -> str: f"is within 5% of hard limit ({hard_tension_limit})." ) + # Check Stiffness, Curvature, Deviation across active agents + active_agents = [a for a in self.agents if a.shard.state != ShardState.QUARANTINED] + if active_agents: + max_stiffness = float(max(s.stiffness for a in active_agents for s in a.membrane.strings)) + if max_stiffness > hard_stiffness_limit: + from radial_membrane_ai.exceptions import GovernanceError + raise GovernanceError( + f"Governed bound violated: Stiffness ({max_stiffness:.4f}) " + f"exceeded hard limit ({hard_stiffness_limit})." + ) + elif max_stiffness >= 0.95 * hard_stiffness_limit: + self.interventions.append( + f"Near-violation warning: Stiffness ({max_stiffness:.4f}) " + f"is within 5% of hard limit ({hard_stiffness_limit})." + ) + + max_curvature = float(max( + abs(a.boundary.curvature(s.theta)) for a in active_agents for s in a.membrane.strings + )) + if max_curvature > hard_curvature_limit: + from radial_membrane_ai.exceptions import GovernanceError + raise GovernanceError( + f"Governed bound violated: Boundary curvature ({max_curvature:.4f}) " + f"exceeded hard limit ({hard_curvature_limit})." + ) + elif max_curvature >= 0.95 * hard_curvature_limit: + self.interventions.append( + f"Near-violation warning: Boundary curvature ({max_curvature:.4f}) " + f"is within 5% of hard limit ({hard_curvature_limit})." + ) + + max_dev = float(max(abs(dev) for a in active_agents for dev in a.boundary.radius_deviation.values())) + if max_dev > hard_deviation_limit: + from radial_membrane_ai.exceptions import GovernanceError + raise GovernanceError( + f"Governed bound violated: Radius deviation ({max_dev:.4f}) " + f"exceeded hard limit ({hard_deviation_limit})." + ) + elif max_dev >= 0.95 * hard_deviation_limit: + self.interventions.append( + f"Near-violation warning: Radius deviation ({max_dev:.4f}) " + f"is within 5% of hard limit ({hard_deviation_limit})." + ) + + # Check Coherence + latest_coh = self.c_mesh_history[-1] if self.c_mesh_history else 1.0 + if latest_coh < 0.0: + from radial_membrane_ai.exceptions import GovernanceError + raise GovernanceError( + f"Governed bound violated: Coherence ({latest_coh:.4f}) " + f"fell below lower limit (0.0)." + ) + finally: # Restore agent boundaries get_radius scale for agent in self.agents: diff --git a/radial_membrane_ai/ufo_engine/multi_cluster.py b/radial_membrane_ai/ufo_engine/multi_cluster.py index c3fd029..8dce943 100644 --- a/radial_membrane_ai/ufo_engine/multi_cluster.py +++ b/radial_membrane_ai/ufo_engine/multi_cluster.py @@ -40,7 +40,7 @@ class MultiClusterEngine: hierarchical Holistic Governor, supporting governed behavioral regimes. """ - def __init__(self) -> None: + def __init__(self, seed: int = 0) -> None: """ Initializes the Multi-Cluster Simulation Engine. """ @@ -60,7 +60,7 @@ def __init__(self) -> None: self.interventions: List[str] = [] # Call deterministic environment seeding - set_deterministic_env() + set_deterministic_env(seed) # Kernel Regime Expansion Layer components self.regime_manager = RegimeManager() @@ -770,9 +770,28 @@ def tick(self, task_value: float, default_excitation: np.ndarray) -> str: ) # Post-tick invariant assertions and near-violation warning logs + hard_energy_limit = 15.0 hard_tension_limit = 10.0 - latest_tension = self.temporal_state.accumulated_tension if self.temporal_state is not None else 0.0 + hard_stiffness_limit = 2.0 + hard_curvature_limit = 50.0 + hard_deviation_limit = 5.0 + + # Check Lyapunov energy + latest_energy = self.h_global_history[-1] if self.h_global_history else 0.0 + if latest_energy > hard_energy_limit: + from radial_membrane_ai.exceptions import GovernanceError + raise GovernanceError( + f"Governed bound violated: Multi-cluster energy ({latest_energy:.4f}) " + f"exceeded hard limit ({hard_energy_limit})." + ) + elif latest_energy >= 0.95 * hard_energy_limit: + self.interventions.append( + f"Near-violation warning: Multi-cluster energy ({latest_energy:.4f}) " + f"is within 5% of hard limit ({hard_energy_limit})." + ) + # Check Tension + latest_tension = self.temporal_state.accumulated_tension if self.temporal_state is not None else 0.0 if latest_tension > hard_tension_limit: from radial_membrane_ai.exceptions import GovernanceError raise GovernanceError( @@ -785,6 +804,60 @@ def tick(self, task_value: float, default_excitation: np.ndarray) -> str: f"is within 5% of hard limit ({hard_tension_limit})." ) + # Check active clusters and agents within them + active_clusters = [c for c in self.clusters.values() if getattr(c, "quarantine_timer", 0) == 0] + active_agents = [a for c in active_clusters for a in c.agents if a.shard.state != ShardState.QUARANTINED] + if active_agents: + max_stiffness = float(max(s.stiffness for a in active_agents for s in a.membrane.strings)) + if max_stiffness > hard_stiffness_limit: + from radial_membrane_ai.exceptions import GovernanceError + raise GovernanceError( + f"Governed bound violated: Stiffness ({max_stiffness:.4f}) " + f"exceeded hard limit ({hard_stiffness_limit})." + ) + elif max_stiffness >= 0.95 * hard_stiffness_limit: + self.interventions.append( + f"Near-violation warning: Stiffness ({max_stiffness:.4f}) " + f"is within 5% of hard limit ({hard_stiffness_limit})." + ) + + max_curvature = float(max( + abs(a.boundary.curvature(s.theta)) for a in active_agents for s in a.membrane.strings + )) + if max_curvature > hard_curvature_limit: + from radial_membrane_ai.exceptions import GovernanceError + raise GovernanceError( + f"Governed bound violated: Boundary curvature ({max_curvature:.4f}) " + f"exceeded hard limit ({hard_curvature_limit})." + ) + elif max_curvature >= 0.95 * hard_curvature_limit: + self.interventions.append( + f"Near-violation warning: Boundary curvature ({max_curvature:.4f}) " + f"is within 5% of hard limit ({hard_curvature_limit})." + ) + + max_dev = float(max(abs(dev) for a in active_agents for dev in a.boundary.radius_deviation.values())) + if max_dev > hard_deviation_limit: + from radial_membrane_ai.exceptions import GovernanceError + raise GovernanceError( + f"Governed bound violated: Radius deviation ({max_dev:.4f}) " + f"exceeded hard limit ({hard_deviation_limit})." + ) + elif max_dev >= 0.95 * hard_deviation_limit: + self.interventions.append( + f"Near-violation warning: Radius deviation ({max_dev:.4f}) " + f"is within 5% of hard limit ({hard_deviation_limit})." + ) + + # Check Coherence + latest_coh = self.c_global_history[-1] if self.c_global_history else 1.0 + if latest_coh < 0.0: + from radial_membrane_ai.exceptions import GovernanceError + raise GovernanceError( + f"Governed bound violated: Coherence ({latest_coh:.4f}) " + f"fell below lower limit (0.0)." + ) + finally: for cluster in self.clusters.values(): for agent in cluster.agents: diff --git a/radial_membrane_ai/ufo_engine/single_agent.py b/radial_membrane_ai/ufo_engine/single_agent.py index e9dd3ae..9742423 100644 --- a/radial_membrane_ai/ufo_engine/single_agent.py +++ b/radial_membrane_ai/ufo_engine/single_agent.py @@ -64,7 +64,8 @@ def __init__( band_config: StabilityBandConfig | None = None, r_max: float = 2.0, r_growth_rate: float = 0.3, - r_relaxation: float = 0.2 + r_relaxation: float = 0.2, + seed: int = 0 ) -> None: """ Initializes the single-agent engine. @@ -97,7 +98,7 @@ def __init__( self.residual_history: List[float] = [] # Call deterministic environment seeding - set_deterministic_env() + set_deterministic_env(seed) # Kernel Regime Expansion Layer components self.regime_manager = RegimeManager() @@ -595,6 +596,9 @@ def tick( # Post-tick invariant assertions and near-violation warning logs hard_energy_limit = 15.0 hard_tension_limit = 10.0 + hard_stiffness_limit = 2.0 + hard_curvature_limit = 50.0 + hard_deviation_limit = 5.0 # Check Lyapunov energy latest_energy = self.v_history[-1] if self.v_history else 0.0 @@ -624,6 +628,66 @@ def tick( f"is within 5% of hard limit ({hard_tension_limit})." ) + # Check Stiffness + max_stiffness = ( + float(max(s.stiffness for s in self.membrane.strings)) + if self.membrane.strings else 0.0 + ) + if max_stiffness > hard_stiffness_limit: + from radial_membrane_ai.exceptions import GovernanceError + raise GovernanceError( + f"Governed bound violated: Stiffness ({max_stiffness:.4f}) " + f"exceeded hard limit ({hard_stiffness_limit})." + ) + elif max_stiffness >= 0.95 * hard_stiffness_limit: + self.interventions.append( + f"Near-violation warning: Stiffness ({max_stiffness:.4f}) " + f"is within 5% of hard limit ({hard_stiffness_limit})." + ) + + # Check Channel Coherence + min_coherence = self.compute_local_coherence() + if min_coherence < 0.0: + from radial_membrane_ai.exceptions import GovernanceError + raise GovernanceError( + f"Governed bound violated: Channel coherence ({min_coherence:.4f}) " + f"fell below lower limit (0.0)." + ) + + # Check Boundary Curvature + max_curvature = ( + float(max(abs(self.boundary.curvature(s.theta)) for s in self.membrane.strings)) + if self.membrane.strings else 0.0 + ) + if max_curvature > hard_curvature_limit: + from radial_membrane_ai.exceptions import GovernanceError + raise GovernanceError( + f"Governed bound violated: Boundary curvature ({max_curvature:.4f}) " + f"exceeded hard limit ({hard_curvature_limit})." + ) + elif max_curvature >= 0.95 * hard_curvature_limit: + self.interventions.append( + f"Near-violation warning: Boundary curvature ({max_curvature:.4f}) " + f"is within 5% of hard limit ({hard_curvature_limit})." + ) + + # Check Radius Deviation + max_dev = ( + float(max(abs(dev) for dev in self.boundary.radius_deviation.values())) + if self.boundary.radius_deviation else 0.0 + ) + if max_dev > hard_deviation_limit: + from radial_membrane_ai.exceptions import GovernanceError + raise GovernanceError( + f"Governed bound violated: Radius deviation ({max_dev:.4f}) " + f"exceeded hard limit ({hard_deviation_limit})." + ) + elif max_dev >= 0.95 * hard_deviation_limit: + self.interventions.append( + f"Near-violation warning: Radius deviation ({max_dev:.4f}) " + f"is within 5% of hard limit ({hard_deviation_limit})." + ) + self._record_state() finally: diff --git a/radial_membrane_ai/visualization/visualizer.py b/radial_membrane_ai/visualization/visualizer.py index 097f313..8ced2eb 100644 --- a/radial_membrane_ai/visualization/visualizer.py +++ b/radial_membrane_ai/visualization/visualizer.py @@ -20,11 +20,15 @@ from radial_membrane_ai.workloads.engine import WorkloadTrace -def extract_geom_and_vchannel(membrane: Any, boundary: Any) -> Tuple[MembraneGeometrySnapshot, VChannelSnapshot]: +def extract_geom_and_vchannel( + membrane: Any, + boundary: Any, + samples: int = 100 +) -> Tuple[MembraneGeometrySnapshot, VChannelSnapshot]: """Helper to extract geometry and V-channel snapshots from any membrane & boundary.""" - # Sample 100 angles for boundary coordinates + # Sample configured number of angles for boundary coordinates boundary_coords = [] - for theta in np.linspace(0, 2 * math.pi, 100, endpoint=False): + for theta in np.linspace(0, 2 * math.pi, samples, endpoint=False): r = boundary.get_radius(theta) if hasattr(boundary, 'get_radius') else 1.0 boundary_coords.append((r * math.cos(theta), r * math.sin(theta))) @@ -85,6 +89,8 @@ class MeshVisualizer: Renders diagnostic dashboard frames and timelines across agents, clusters, and the global mesh. """ + def __init__(self, samples_resolution: int = 100) -> None: + self.samples_resolution = samples_resolution def render_agent_state(self, agent_state: Any) -> VisualizationFrame: """Renders single agent state snapshot at a single timestep.""" @@ -99,7 +105,7 @@ def render_agent_state(self, agent_state: Any) -> VisualizationFrame: if not (membrane and boundary): raise ValueError("State does not have valid membrane or boundary attributes.") - geom, vch = extract_geom_and_vchannel(membrane, boundary) + geom, vch = extract_geom_and_vchannel(membrane, boundary, samples=self.samples_resolution) # Base metrics curvature = float(max(geom.curvature_map)) if geom.curvature_map else 0.0