#!/usr/bin/env python3 """ Mathematical validation script for the Felix Framework. This script validates mathematical properties derived in the formal documentation against the implementation, ensuring theoretical consistency and correctness. Validates: 1. Parametric helix equations and properties 2. Geometric invariants (curvature, torsion) 3. Agent distribution functions 4. Communication complexity bounds 5. Attention focusing mechanism properties Mathematical references: - docs/mathematical_model.md: Formal mathematical specifications - docs/hypothesis_mathematics.md: Statistical formulations and proofs """ import math import numpy as np from typing import List, Tuple import time from src.core.helix_geometry import HelixGeometry from src.agents.agent import generate_spawn_times, create_openscad_agents from src.communication import CentralPost class MathematicalValidator: """Validates mathematical properties against implementation.""" def __init__(self, tolerance: float = 1e-10): """Initialize validator with numerical tolerance.""" self.tolerance = tolerance self.helix = HelixGeometry(33.0, 0.001, 33.0, 33) # OpenSCAD parameters self.validation_results = {} def validate_parametric_equations(self) -> bool: """ Validate parametric helix equations from mathematical_model.md Section 1.2. Tests: 1. Boundary conditions: r(0) and r(1) 2. Radius tapering function R(t) 3. Angular progression θ(t) 4. Height function z(t) = Ht """ print("=== Validating Parametric Equations ===") # Test boundary conditions pos_0 = self.helix.get_position(0.0) pos_1 = self.helix.get_position(1.0) # At t=0: should be at (R_bottom, 0, 0) expected_0 = (self.helix.bottom_radius, 0.0, 0.0) diff_0 = max(abs(a - b) for a, b in zip(pos_0, expected_0)) # At t=1: should be at (R_top, 0, height) after n full turns # After 33 turns, angle = 33 * 2π, so cos(33*2π) = cos(0) = 1 expected_1 = (self.helix.top_radius, 0.0, self.helix.height) diff_1 = max(abs(a - b) for a, b in zip(pos_1, expected_1)) boundary_valid = diff_0 < self.tolerance and diff_1 < self.tolerance print(f"Boundary conditions: {'✅' if boundary_valid else '❌'}") print(f" t=0: {pos_0} vs {expected_0}, diff={diff_0:.2e}") print(f" t=1: {pos_1} vs {expected_1}, diff={diff_1:.2e}") # Test radius function R(t) = R_bottom * (R_top/R_bottom)^t radius_valid = True for t in [0.0, 0.25, 0.5, 0.75, 1.0]: z = self.helix.height * t actual_radius = self.helix.get_radius(z) expected_radius = self.helix.bottom_radius * pow( self.helix.top_radius / self.helix.bottom_radius, t ) diff = abs(actual_radius - expected_radius) if diff > self.tolerance: radius_valid = False print(f" R({t:.2f}): {actual_radius:.6f} vs {expected_radius:.6f}, diff={diff:.2e}") print(f"Radius function: {'✅' if radius_valid else '❌'}") # Test monotonicity of R(t) - should be strictly increasing monotonic_valid = True prev_radius = 0.0 for t in np.linspace(0, 1, 100): z = self.helix.height * t radius = self.helix.get_radius(z) if radius <= prev_radius and t > 0: monotonic_valid = False break prev_radius = radius print(f"Radius monotonicity: {'✅' if monotonic_valid else '❌'}") self.validation_results['parametric_equations'] = { 'boundary_conditions': boundary_valid, 'radius_function': radius_valid, 'monotonicity': monotonic_valid, 'overall': boundary_valid and radius_valid and monotonic_valid } return boundary_valid and radius_valid and monotonic_valid def validate_geometric_properties(self) -> bool: """ Validate geometric properties from mathematical_model.md Section 3. Tests: 1. Arc length approximation convergence 2. Tangent vector properties 3. Smoothness (differentiability) """ print("\n=== Validating Geometric Properties ===") # Test arc length approximation convergence # Should converge as number of segments increases segments_list = [100, 500, 1000, 2000] arc_lengths = [] for segments in segments_list: length = self.helix.approximate_arc_length(segments=segments) arc_lengths.append(length) # Check convergence: difference between consecutive approximations should decrease convergence_valid = True for i in range(1, len(arc_lengths)): diff_curr = abs(arc_lengths[i] - arc_lengths[i-1]) if i > 1: diff_prev = abs(arc_lengths[i-1] - arc_lengths[i-2]) if diff_curr >= diff_prev: # Should be decreasing convergence_valid = False print(f" Arc length ({segments_list[i]} segments): {arc_lengths[i]:.3f}, " f"diff from previous: {diff_curr:.3f}") print(f"Arc length convergence: {'✅' if convergence_valid else '❌'}") # Test tangent vector properties tangent_valid = True for t in [0.1, 0.3, 0.5, 0.7, 0.9]: tangent = self.helix.get_tangent_vector(t) # Tangent vector should be normalized (length ≈ 1) length = math.sqrt(sum(component**2 for component in tangent)) if abs(length - 1.0) > self.tolerance: tangent_valid = False print(f" Tangent at t={t}: length={length:.6f}") print(f"Tangent vector normalization: {'✅' if tangent_valid else '❌'}") # Test smoothness by checking continuity of positions smoothness_valid = True epsilon = 1e-6 for t in np.linspace(0.1, 0.9, 20): pos1 = self.helix.get_position(t - epsilon) pos2 = self.helix.get_position(t + epsilon) # Distance should be small for small epsilon distance = math.sqrt(sum((a - b)**2 for a, b in zip(pos1, pos2))) expected_distance = 2 * epsilon * self.helix.approximate_arc_length(segments=1000) if distance > 10 * expected_distance: # Allow some tolerance smoothness_valid = False print(f"Smoothness (continuity): {'✅' if smoothness_valid else '❌'}") self.validation_results['geometric_properties'] = { 'arc_length_convergence': convergence_valid, 'tangent_normalization': tangent_valid, 'smoothness': smoothness_valid, 'overall': convergence_valid and tangent_valid and smoothness_valid } return convergence_valid and tangent_valid and smoothness_valid def validate_agent_distribution(self) -> bool: """ Validate agent distribution functions from mathematical_model.md Section 4. Tests: 1. Uniform spawn time distribution U(0,1) 2. Agent density evolution properties 3. Statistical properties of large samples """ print("\n=== Validating Agent Distribution ===") # Test spawn time distribution N = 10000 spawn_times = generate_spawn_times(N, seed=12345) # Test uniformity using Kolmogorov-Smirnov test approximation sorted_times = sorted(spawn_times) max_deviation = 0.0 for i, t in enumerate(sorted_times): empirical_cdf = (i + 1) / N theoretical_cdf = t # For U(0,1), CDF(t) = t deviation = abs(empirical_cdf - theoretical_cdf) max_deviation = max(max_deviation, deviation) # For U(0,1), critical value at α=0.05 is approximately 1.36/√N critical_value = 1.36 / math.sqrt(N) uniformity_valid = max_deviation < critical_value print(f"Spawn time uniformity: {'✅' if uniformity_valid else '❌'}") print(f" Max KS deviation: {max_deviation:.6f}, critical: {critical_value:.6f}") # Test mean and variance mean_time = np.mean(spawn_times) var_time = np.var(spawn_times) # For U(0,1): mean = 0.5, variance = 1/12 ≈ 0.0833 mean_valid = abs(mean_time - 0.5) < 3 * math.sqrt(1/12/N) # 3-sigma test var_valid = abs(var_time - 1/12) < 0.01 # Allow reasonable tolerance print(f"Mean (expected 0.5): {mean_time:.6f}, valid: {'✅' if mean_valid else '❌'}") print(f"Variance (expected 0.0833): {var_time:.6f}, valid: {'✅' if var_valid else '❌'}") # Test reproducibility with same seed spawn_times_2 = generate_spawn_times(N, seed=12345) reproducibility_valid = spawn_times == spawn_times_2 print(f"Reproducibility with same seed: {'✅' if reproducibility_valid else '❌'}") self.validation_results['agent_distribution'] = { 'uniformity': uniformity_valid, 'mean': mean_valid, 'variance': var_valid, 'reproducibility': reproducibility_valid, 'overall': uniformity_valid and mean_valid and var_valid and reproducibility_valid } return uniformity_valid and mean_valid and var_valid and reproducibility_valid def validate_attention_focusing(self) -> bool: """ Validate attention focusing mechanism from hypothesis_mathematics.md Section H3. Tests: 1. Attention density A(t) = k / (2πR(t)) 2. Monotonic increase: dA/dt > 0 3. Exponential growth toward narrow end """ print("\n=== Validating Attention Focusing Mechanism ===") # Calculate attention density at different positions attention_densities = [] t_values = np.linspace(0.1, 0.9, 20) for t in t_values: z = self.helix.height * t radius = self.helix.get_radius(z) # Attention density A(t) = 1 / (2π * R(t)) attention = 1.0 / (2 * math.pi * radius) attention_densities.append(attention) # Test monotonic decrease (attention decreases as radius increases toward top) monotonic_decrease = True for i in range(1, len(attention_densities)): if attention_densities[i] >= attention_densities[i-1]: monotonic_decrease = False print(f" Non-monotonic at i={i}: A[{i-1}]={attention_densities[i-1]:.6f}, " f"A[{i}]={attention_densities[i]:.6f}") break print(f"Attention density monotonic decrease (correct): {'✅' if monotonic_decrease else '❌'}") # Test exponential decay rate (attention decreases exponentially) # dA/dt should be negative and proportional to -A(t) * ln(R_top/R_bottom) ln_ratio = math.log(self.helix.top_radius / self.helix.bottom_radius) exponential_decay_valid = True for i in range(1, len(attention_densities) - 1): # Numerical derivative dt = t_values[i+1] - t_values[i-1] dA_dt = (attention_densities[i+1] - attention_densities[i-1]) / dt # Expected: dA/dt = -A(t) * ln_ratio (negative because attention decreases) expected_derivative = -attention_densities[i] * ln_ratio relative_error = abs(dA_dt - expected_derivative) / abs(expected_derivative) if expected_derivative != 0 else 1 if relative_error > 0.15: # Allow 15% numerical error for derivative approximation exponential_decay_valid = False if i < 5: # Print first few for debugging print(f" t={t_values[i]:.2f}: dA/dt={dA_dt:.3f}, " f"expected={expected_derivative:.3f}, error={relative_error:.2%}") print(f"Exponential decay rate: {'✅' if exponential_decay_valid else '❌'}") # Test focusing factor: ratio of attention at bottom vs top (bottom has higher attention) focusing_ratio = attention_densities[0] / attention_densities[-1] if attention_densities[-1] > 0 else 0 expected_ratio = (self.helix.top_radius / self.helix.bottom_radius) # R_top/R_bottom ratio focusing_valid = focusing_ratio > 100 # Should show significant focusing at bottom print(f"Attention at bottom (t=0.1): {attention_densities[0]:.6f}") print(f"Attention at top (t=0.9): {attention_densities[-1]:.6f}") print(f"Focusing ratio (A_bottom/A_top): {focusing_ratio:.1f}") print(f"Significant focusing (>100x): {'✅' if focusing_valid else '❌'}") self.validation_results['attention_focusing'] = { 'monotonic_decrease': monotonic_decrease, 'exponential_decay': exponential_decay_valid, 'significant_focusing': focusing_valid, 'overall': monotonic_decrease and exponential_decay_valid and focusing_valid } return monotonic_decrease and exponential_decay_valid and focusing_valid def validate_communication_complexity(self) -> bool: """ Validate communication complexity from hypothesis_mathematics.md Section H2. Tests: 1. O(N) scaling for spoke-based system 2. Message count bounds 3. Maximum communication distance """ print("\n=== Validating Communication Complexity ===") # Test scaling with different agent counts agent_counts = [10, 20, 50, 100] message_counts = [] processing_times = [] for N in agent_counts: central_post = CentralPost(max_agents=N, enable_metrics=True) agents = create_openscad_agents(self.helix, number_of_nodes=N, random_seed=42069) # Register agents and time the operation start_time = time.perf_counter() for agent in agents: central_post.register_agent(agent) registration_time = time.perf_counter() - start_time processing_times.append(registration_time) message_counts.append(N) # Each agent creates one connection # Test O(N) scaling - processing time should be roughly linear linear_scaling_valid = True for i in range(1, len(agent_counts)): ratio_agents = agent_counts[i] / agent_counts[i-1] ratio_time = processing_times[i] / processing_times[i-1] # Allow factor of 3 deviation from linear scaling if ratio_time > 3 * ratio_agents or ratio_time < ratio_agents / 3: linear_scaling_valid = False print(f" N={agent_counts[i]}: time={processing_times[i]:.6f}s, " f"scaling ratio={ratio_time:.2f} (expected ≈{ratio_agents:.2f})") print(f"Linear scaling O(N): {'✅' if linear_scaling_valid else '❌'}") # Test maximum communication distance max_spoke_distance = self.helix.top_radius actual_distances = [] for t in np.linspace(0, 1, 100): pos = self.helix.get_position(t) # Distance from agent to central axis at same height distance = math.sqrt(pos[0]**2 + pos[1]**2) # Should equal R(t) actual_distances.append(distance) max_actual_distance = max(actual_distances) distance_bound_valid = abs(max_actual_distance - max_spoke_distance) < self.tolerance print(f"Maximum spoke distance: {max_actual_distance:.6f} " f"(expected {max_spoke_distance:.6f})") print(f"Distance bound valid: {'✅' if distance_bound_valid else '❌'}") # Test message complexity O(N) vs theoretical O(N²) mesh complexity_advantage = True for N in agent_counts: spoke_messages = N # Each agent to central post mesh_messages = N * (N - 1) // 2 # All pairs efficiency_ratio = mesh_messages / spoke_messages expected_ratio = (N - 1) / 2 if abs(efficiency_ratio - expected_ratio) > 0.1: complexity_advantage = False print(f" N={N}: spoke={spoke_messages}, mesh={mesh_messages}, " f"advantage={efficiency_ratio:.1f}x") print(f"Message complexity advantage: {'✅' if complexity_advantage else '❌'}") self.validation_results['communication_complexity'] = { 'linear_scaling': linear_scaling_valid, 'distance_bounds': distance_bound_valid, 'complexity_advantage': complexity_advantage, 'overall': linear_scaling_valid and distance_bound_valid and complexity_advantage } return linear_scaling_valid and distance_bound_valid and complexity_advantage def validate_numerical_precision(self) -> bool: """ Validate numerical precision and stability. Tests: 1. Consistency with OpenSCAD validation 2. Numerical stability across parameter ranges 3. Error accumulation in iterative calculations """ print("\n=== Validating Numerical Precision ===") # Test against OpenSCAD validation (should be < 1e-12) from validate_openscad import validate_implementation openscad_valid = validate_implementation() print(f"OpenSCAD validation: {'✅' if openscad_valid else '❌'}") # Test numerical stability at extreme parameter values stability_valid = True # Test very small t values small_t_values = [1e-10, 1e-8, 1e-6, 1e-4] for t in small_t_values: try: pos = self.helix.get_position(t) # Should be close to (R_bottom, 0, 0) if not all(math.isfinite(x) for x in pos): stability_valid = False except Exception: stability_valid = False # Test values very close to 1 large_t_values = [1 - 1e-10, 1 - 1e-8, 1 - 1e-6, 1 - 1e-4] for t in large_t_values: try: pos = self.helix.get_position(t) if not all(math.isfinite(x) for x in pos): stability_valid = False except Exception: stability_valid = False print(f"Numerical stability: {'✅' if stability_valid else '❌'}") # Test error accumulation in arc length calculation # Compare high-precision vs standard precision arc_length_high = self.helix.approximate_arc_length(segments=10000) arc_length_std = self.helix.approximate_arc_length(segments=1000) relative_error = abs(arc_length_high - arc_length_std) / arc_length_high error_acceptable = relative_error < 0.01 # 1% tolerance for numerical approximation print(f"Arc length error: {relative_error:.4%} (high vs standard precision)") print(f"Error accumulation acceptable: {'✅' if error_acceptable else '❌'}") self.validation_results['numerical_precision'] = { 'openscad_validation': openscad_valid, 'numerical_stability': stability_valid, 'error_accumulation': error_acceptable, 'overall': openscad_valid and stability_valid and error_acceptable } return openscad_valid and stability_valid and error_acceptable def run_full_validation(self) -> bool: """Run complete mathematical validation suite.""" print("=" * 60) print("FELIX FRAMEWORK MATHEMATICAL VALIDATION") print("=" * 60) validators = [ ('Parametric Equations', self.validate_parametric_equations), ('Geometric Properties', self.validate_geometric_properties), ('Agent Distribution', self.validate_agent_distribution), ('Attention Focusing', self.validate_attention_focusing), ('Communication Complexity', self.validate_communication_complexity), ('Numerical Precision', self.validate_numerical_precision), ] all_valid = True results_summary = [] for name, validator in validators: try: result = validator() results_summary.append((name, result)) if not result: all_valid = False except Exception as e: print(f"ERROR in {name}: {e}") results_summary.append((name, False)) all_valid = False # Print summary print("\n" + "=" * 60) print("VALIDATION SUMMARY") print("=" * 60) for name, result in results_summary: status = "✅ PASSED" if result else "❌ FAILED" print(f"{name:<25}: {status}") overall_status = "✅ ALL VALIDATIONS PASSED" if all_valid else "❌ SOME VALIDATIONS FAILED" print(f"\nOverall Result: {overall_status}") if all_valid: print("\n🎉 Mathematical implementation is validated!") print(" Ready for hypothesis testing and research publication.") else: print("\n⚠️ Mathematical validation failures detected.") print(" Review implementation before proceeding with experiments.") return all_valid if __name__ == "__main__": validator = MathematicalValidator(tolerance=1e-10) success = validator.run_full_validation() # Save validation results for research documentation import json # Convert numpy booleans to regular booleans for JSON serialization def convert_numpy_types(obj): if hasattr(obj, 'item'): return obj.item() elif isinstance(obj, dict): return {k: convert_numpy_types(v) for k, v in obj.items()} elif isinstance(obj, list): return [convert_numpy_types(v) for v in obj] return obj serializable_results = convert_numpy_types(validator.validation_results) with open("mathematical_validation_results.json", "w") as f: json.dump(serializable_results, f, indent=2) print(f"\nValidation results saved to: mathematical_validation_results.json") exit(0 if success else 1)