mirror of
https://github.com/allaunthefox/Research-Stack.git
synced 2026-07-31 03:05:21 +00:00
474 lines
20 KiB
Python
474 lines
20 KiB
Python
"""
|
||
Adaptive Merkle Jack - Self-reconfiguring structure using Research Stack mathematics.
|
||
|
||
This model uses FAMM frustration physics, manifold-generalized Bernoulli equations,
|
||
and String-Star Manifold concepts to dynamically adapt geometry under load.
|
||
|
||
Key mathematical frameworks applied:
|
||
- FAMM (Frustration physics): Stress redistribution via frustration minimization
|
||
- Manifold-generalized Bernoulli: Optimal load distribution on curved manifolds
|
||
- String-Star Manifold: Adaptive geometry with information conservation
|
||
- Scale Space: Multi-scale adaptation across different load regimes
|
||
"""
|
||
|
||
import json
|
||
import numpy as np
|
||
import math
|
||
from typing import List, Tuple, Dict, Any, Optional
|
||
from dataclasses import dataclass
|
||
|
||
@dataclass
|
||
class LoadState:
|
||
"""Current load state of the structure."""
|
||
load_type: str # 'compression', 'tension', 'shear', 'torsion', 'mixed'
|
||
magnitude: float
|
||
direction: Tuple[float, float, float]
|
||
stress_distribution: Dict[Tuple[int, int], float]
|
||
max_stress: float
|
||
safety_factor: float
|
||
|
||
@dataclass
|
||
class AdaptationParameters:
|
||
"""Parameters for geometry adaptation."""
|
||
branch_angle_sensitivity: float = 0.01 # rad/MPa (reduced)
|
||
tubule_radius_sensitivity: float = 0.0001 # m/MPa (reduced)
|
||
famm_frustration_threshold: float = 0.5 # Frustration threshold
|
||
bernoulli_curvature_weight: float = 0.3 # Weight for manifold curvature
|
||
scale_space_sigma: float = 0.01 # Scale space smoothing parameter (reduced)
|
||
|
||
class AdaptiveMerkleJack:
|
||
"""Self-adapting Merkle Jack structure using Research Stack mathematics."""
|
||
|
||
def __init__(self, geometry_file: str):
|
||
"""Load initial geometry and initialize adaptive parameters."""
|
||
with open(geometry_file, 'r') as f:
|
||
data = json.load(f)
|
||
|
||
self.nodes = data['nodes']
|
||
self.edges = data['edges']
|
||
self.params = data['parameters']
|
||
|
||
# Build node lookup
|
||
self.node_map = {n['id']: n for n in self.nodes}
|
||
|
||
# Material properties
|
||
self.youngs_modulus = 200e9 # Pa
|
||
self.yield_strength = 250e6 # Pa
|
||
|
||
# Convert to meters
|
||
self.tubule_radius = self.params['tubule_radius'] / 1000.0
|
||
self.cross_sectional_area = math.pi * self.tubule_radius**2
|
||
|
||
# Adaptive state
|
||
self.current_state = LoadState('none', 0, (0, 0, 1), {}, 0, float('inf'))
|
||
self.adapt_params = AdaptationParameters()
|
||
|
||
# FAMM frustration tensor (tracks stress frustration across edges)
|
||
self.frustration_tensor = {}
|
||
|
||
# Manifold curvature at each node (for Bernoulli adaptation)
|
||
self.manifold_curvature = {}
|
||
|
||
# Scale space representation (multi-scale geometry)
|
||
self.scale_space = {}
|
||
|
||
def calculate_famm_frustration(self, edge_stresses: Dict[Tuple[int, int], float]) -> Dict[Tuple[int, int], float]:
|
||
"""
|
||
Calculate FAMM frustration for each edge.
|
||
|
||
FAMM frustration measures the mismatch between local stress and
|
||
optimal stress distribution. High frustration indicates need for adaptation.
|
||
|
||
F = |σ_local - σ_optimal| / σ_optimal
|
||
"""
|
||
if not edge_stresses:
|
||
return {}
|
||
|
||
# Optimal stress is uniform distribution (FAMM principle)
|
||
mean_stress = np.mean(list(edge_stresses.values()))
|
||
optimal_stress = mean_stress
|
||
|
||
frustration = {}
|
||
for edge, stress in edge_stresses.items():
|
||
if optimal_stress > 0:
|
||
frustration[edge] = abs(stress - optimal_stress) / optimal_stress
|
||
else:
|
||
frustration[edge] = 0.0
|
||
|
||
return frustration
|
||
|
||
def calculate_manifold_curvature(self, node_id: int) -> float:
|
||
"""
|
||
Calculate manifold curvature at a node using String-Star Manifold.
|
||
|
||
Curvature κ = ∇²φ where φ is the potential field from neighboring nodes.
|
||
Higher curvature indicates geometric singularity requiring adaptation.
|
||
"""
|
||
node = self.node_map[node_id]
|
||
|
||
if node['parent'] is None:
|
||
return 0.0 # Root has no curvature
|
||
|
||
# Get neighboring nodes
|
||
neighbors = [n for n in self.nodes if n['parent'] == node_id]
|
||
parent = self.node_map[node['parent']]
|
||
|
||
if not neighbors:
|
||
return 0.0
|
||
|
||
# Calculate curvature from position differences
|
||
positions = []
|
||
for neighbor in neighbors:
|
||
dx = neighbor['x'] - node['x']
|
||
dy = neighbor['y'] - node['y']
|
||
dz = neighbor['z'] - node['z']
|
||
positions.append(np.array([dx, dy, dz]))
|
||
|
||
# Parent direction
|
||
p_dx = node['x'] - parent['x']
|
||
p_dy = node['y'] - parent['y']
|
||
p_dz = node['z'] - parent['z']
|
||
parent_dir = np.array([p_dx, p_dy, p_dz])
|
||
|
||
# Curvature as deviation from parent direction
|
||
if positions:
|
||
avg_child_dir = np.mean(positions, axis=0)
|
||
parent_norm = np.linalg.norm(parent_dir)
|
||
child_norm = np.linalg.norm(avg_child_dir)
|
||
|
||
if parent_norm > 0 and child_norm > 0:
|
||
cos_angle = np.dot(parent_dir, avg_child_dir) / (parent_norm * child_norm)
|
||
curvature = math.acos(min(1.0, max(-1.0, cos_angle)))
|
||
return curvature
|
||
|
||
return 0.0
|
||
|
||
def apply_manifold_bernoulli(self, edge: Tuple[int, int], load_direction: Tuple[float, float, float]) -> float:
|
||
"""
|
||
Apply manifold-generalized Bernoulli equation for load distribution.
|
||
|
||
On a curved manifold, pressure distribution follows:
|
||
P + ½ρv² + ρgh + ∫κ ds = constant
|
||
|
||
This determines optimal load sharing based on manifold curvature.
|
||
"""
|
||
p_id, c_id = edge
|
||
parent = self.node_map[p_id]
|
||
child = self.node_map[c_id]
|
||
|
||
# Calculate edge direction
|
||
dx = child['x'] - parent['x']
|
||
dy = child['y'] - parent['y']
|
||
dz = child['z'] - parent['z']
|
||
edge_dir = np.array([dx, dy, dz])
|
||
edge_dir = edge_dir / np.linalg.norm(edge_dir)
|
||
|
||
# Load direction
|
||
load_dir = np.array(load_direction)
|
||
load_dir = load_dir / np.linalg.norm(load_dir)
|
||
|
||
# Manifold curvature at child node
|
||
curvature = self.manifold_curvature.get(c_id, 0.0)
|
||
|
||
# Bernoulli adjustment: edges aligned with load and low curvature get more load
|
||
alignment = abs(np.dot(edge_dir, load_dir))
|
||
curvature_penalty = self.adapt_params.bernoulli_curvature_weight * curvature
|
||
|
||
# Bernoulli factor (higher = better load capacity)
|
||
bernoulli_factor = alignment * (1.0 - curvature_penalty)
|
||
|
||
return bernoulli_factor
|
||
|
||
def scale_space_adaptation(self, stress_level: float) -> Tuple[float, float]:
|
||
"""
|
||
Apply Scale Space theory for multi-scale adaptation.
|
||
|
||
Scale Space: Geometry evolves across scales σ to find optimal configuration.
|
||
|
||
Returns: (angle_adjustment, radius_adjustment)
|
||
"""
|
||
# Scale space parameter controls adaptation magnitude
|
||
sigma = self.adapt_params.scale_space_sigma
|
||
|
||
# Stress level normalized to yield strength
|
||
normalized_stress = stress_level / self.yield_strength
|
||
|
||
# Scale space evolution: adaptation scales with stress level
|
||
angle_adj = self.adapt_params.branch_angle_sensitivity * normalized_stress * sigma
|
||
radius_adj = self.adapt_params.tubule_radius_sensitivity * normalized_stress * sigma
|
||
|
||
return angle_adj, radius_adj
|
||
|
||
def adapt_branch_angles(self, frustration: Dict[Tuple[int, int], float]) -> Dict[int, List[float]]:
|
||
"""
|
||
Adapt branch angles using FAMM frustration minimization.
|
||
|
||
Edges with high frustration have their angles adjusted to redistribute stress.
|
||
"""
|
||
new_angles = list(self.params['branch_angles'])
|
||
angle_changes = {}
|
||
|
||
for edge, f_val in frustration.items():
|
||
if f_val > self.adapt_params.famm_frustration_threshold:
|
||
p_id, c_id = edge
|
||
child_depth = self.node_map[c_id]['depth']
|
||
|
||
if child_depth < len(new_angles):
|
||
# Adjust angle to reduce frustration
|
||
# FAMM: move toward optimal configuration
|
||
current_angle = new_angles[child_depth]
|
||
|
||
# Frustration-driven adjustment
|
||
angle_change = self.adapt_params.branch_angle_sensitivity * f_val
|
||
|
||
# Adapt toward more vertical (better for compression)
|
||
if self.current_state.load_type in ['compression', 'tension']:
|
||
new_angles[child_depth] = max(5.0, current_angle - angle_change)
|
||
# Adapt toward more horizontal (better for shear/torsion)
|
||
else:
|
||
new_angles[child_depth] = min(60.0, current_angle + angle_change)
|
||
|
||
angle_changes[edge] = [current_angle, new_angles[child_depth]]
|
||
|
||
return new_angles, angle_changes
|
||
|
||
def adapt_tubule_radii(self, stress_distribution: Dict[Tuple[int, int], float]) -> Dict[Tuple[int, int], float]:
|
||
"""
|
||
Adapt tubule radii based on stress distribution.
|
||
|
||
Edges under high stress get increased radius (material redistribution).
|
||
"""
|
||
radius_changes = {}
|
||
|
||
max_stress = max(stress_distribution.values()) if stress_distribution else 0
|
||
if max_stress == 0:
|
||
return radius_changes
|
||
|
||
for edge, stress in stress_distribution.items():
|
||
# Normalize stress
|
||
normalized_stress = stress / max_stress
|
||
|
||
# Radius adaptation proportional to stress
|
||
if normalized_stress > 0.7: # Only adapt highly stressed edges
|
||
radius_increase = self.adapt_params.tubule_radius_sensitivity * normalized_stress
|
||
radius_changes[edge] = radius_increase
|
||
|
||
return radius_changes
|
||
|
||
def reconfigure_geometry(self, load_state: LoadState) -> Dict[str, Any]:
|
||
"""
|
||
Reconfigure geometry based on current load state.
|
||
|
||
Uses FAMM frustration, manifold Bernoulli, and Scale Space to adapt.
|
||
"""
|
||
self.current_state = load_state
|
||
|
||
# Step 1: Calculate FAMM frustration
|
||
frustration = self.calculate_famm_frustration(load_state.stress_distribution)
|
||
self.frustration_tensor = frustration
|
||
|
||
# Step 2: Calculate manifold curvature for all nodes
|
||
for node in self.nodes:
|
||
self.manifold_curvature[node['id']] = self.calculate_manifold_curvature(node['id'])
|
||
|
||
# Step 3: Scale space adaptation parameters
|
||
angle_adj, radius_adj = self.scale_space_adaptation(load_state.max_stress)
|
||
|
||
# Step 4: Adapt branch angles using FAMM
|
||
new_angles, angle_changes = self.adapt_branch_angles(frustration)
|
||
|
||
# Step 5: Adapt tubule radii based on stress
|
||
radius_changes = self.adapt_tubule_radii(load_state.stress_distribution)
|
||
|
||
# Step 6: Calculate Bernoulli load redistribution factors
|
||
bernoulli_factors = {}
|
||
for edge in self.edges:
|
||
bernoulli_factors[tuple(edge)] = self.apply_manifold_bernoulli(edge, load_state.direction)
|
||
|
||
adaptation_result = {
|
||
'new_branch_angles': new_angles,
|
||
'angle_changes': angle_changes,
|
||
'radius_changes': radius_changes,
|
||
'frustration_tensor': frustration,
|
||
'bernoulli_factors': bernoulli_factors,
|
||
'manifold_curvature': self.manifold_curvature,
|
||
'scale_space_adjustment': (angle_adj, radius_adj)
|
||
}
|
||
|
||
return adaptation_result
|
||
|
||
def predict_adapted_stress(self, adaptation: Dict[str, Any], load_state: LoadState) -> Dict[Tuple[int, int], float]:
|
||
"""
|
||
Predict stress distribution after adaptation.
|
||
|
||
Uses Bernoulli factors to redistribute load based on new geometry.
|
||
"""
|
||
adapted_stresses = {}
|
||
|
||
# Base stress distribution
|
||
base_stresses = load_state.stress_distribution
|
||
|
||
# Apply Bernoulli redistribution
|
||
total_bernoulli = sum(adaptation['bernoulli_factors'].values())
|
||
|
||
for edge, base_stress in base_stresses.items():
|
||
bernoulli_factor = adaptation['bernoulli_factors'][edge]
|
||
|
||
# Redistribute based on Bernoulli factor
|
||
if total_bernoulli > 0:
|
||
redistribution_factor = bernoulli_factor / total_bernoulli
|
||
adapted_stresses[edge] = base_stress * redistribution_factor
|
||
else:
|
||
adapted_stresses[edge] = base_stress
|
||
|
||
# Apply radius changes (stress ∝ 1/r²)
|
||
for edge, radius_change in adaptation['radius_changes'].items():
|
||
if edge in adapted_stresses:
|
||
new_radius = self.tubule_radius + radius_change
|
||
area_ratio = (self.tubule_radius / new_radius) ** 2
|
||
adapted_stresses[edge] *= area_ratio
|
||
|
||
return adapted_stresses
|
||
|
||
def evaluate_adaptation(self, adapted_stress: Dict[Tuple[int, int], float]) -> Dict[str, Any]:
|
||
"""
|
||
Evaluate the effectiveness of adaptation.
|
||
"""
|
||
if not adapted_stress:
|
||
return {'improvement': 0, 'new_max_stress': 0, 'new_safety_factor': float('inf')}
|
||
|
||
new_max_stress = max(adapted_stress.values())
|
||
old_max_stress = self.current_state.max_stress
|
||
|
||
improvement = (old_max_stress - new_max_stress) / old_max_stress if old_max_stress > 0 else 0
|
||
new_safety_factor = self.yield_strength / new_max_stress if new_max_stress > 0 else float('inf')
|
||
|
||
return {
|
||
'improvement': improvement,
|
||
'new_max_stress': new_max_stress,
|
||
'new_safety_factor': new_safety_factor,
|
||
'stress_reduction': old_max_stress - new_max_stress
|
||
}
|
||
|
||
def run_adaptive_simulation(self, load_type: str, magnitude: float, direction: Tuple[float, float, float]) -> Dict[str, Any]:
|
||
"""
|
||
Run full adaptive simulation for a given load condition.
|
||
"""
|
||
# Create load state (simplified - use stress from previous test)
|
||
load_state = LoadState(load_type, magnitude, direction, {}, magnitude * 1e6, 1.0)
|
||
|
||
# For simulation, create a mock stress distribution based on load type
|
||
mock_stresses = {}
|
||
for edge in self.edges:
|
||
if load_type == 'tension':
|
||
# Root edges take more load
|
||
if edge[0] == 0:
|
||
mock_stresses[tuple(edge)] = magnitude / self.cross_sectional_area
|
||
else:
|
||
mock_stresses[tuple(edge)] = magnitude * 0.5 / self.cross_sectional_area
|
||
elif load_type == 'torsion':
|
||
# Outer edges take more torsional load
|
||
p_id, c_id = edge
|
||
child = self.node_map[c_id]
|
||
r = math.sqrt(child['x']**2 + child['y']**2) / 1000.0
|
||
mock_stresses[tuple(edge)] = magnitude * r / self.cross_sectional_area
|
||
else:
|
||
mock_stresses[tuple(edge)] = magnitude * 0.1 / self.cross_sectional_area
|
||
|
||
load_state.stress_distribution = mock_stresses
|
||
load_state.max_stress = max(mock_stresses.values()) if mock_stresses else 0
|
||
load_state.safety_factor = self.yield_strength / load_state.max_stress
|
||
|
||
print(f"\n{'='*70}")
|
||
print(f"ADAPTIVE SIMULATION: {load_type.upper()}")
|
||
print(f"Magnitude: {magnitude} N")
|
||
print(f"Initial Max Stress: {load_state.max_stress/1e6:.2f} MPa")
|
||
print(f"Initial Safety Factor: {load_state.safety_factor:.2f}")
|
||
print(f"{'='*70}")
|
||
|
||
# Reconfigure geometry
|
||
adaptation = self.reconfigure_geometry(load_state)
|
||
|
||
print(f"\nAdaptation Summary:")
|
||
print(f" Branch angles changed: {len(adaptation['angle_changes'])}")
|
||
print(f" Radii adapted: {len(adaptation['radius_changes'])}")
|
||
print(f" Max frustration: {max(adaptation['frustration_tensor'].values()) if adaptation['frustration_tensor'] else 0:.3f}")
|
||
print(f" Scale space adjustment: ({adaptation['scale_space_adjustment'][0]:.4f} rad, {adaptation['scale_space_adjustment'][1]:.6f} m)")
|
||
|
||
# Predict adapted stress
|
||
adapted_stress = self.predict_adapted_stress(adaptation, load_state)
|
||
|
||
# Evaluate adaptation
|
||
evaluation = self.evaluate_adaptation(adapted_stress)
|
||
|
||
print(f"\nAdaptation Results:")
|
||
print(f" Stress reduction: {evaluation['stress_reduction']/1e6:.2f} MPa")
|
||
print(f" Improvement: {evaluation['improvement']*100:.1f}%")
|
||
print(f" New Max Stress: {evaluation['new_max_stress']/1e6:.2f} MPa")
|
||
print(f" New Safety Factor: {evaluation['new_safety_factor']:.2f}")
|
||
|
||
if evaluation['new_safety_factor'] >= 1.5:
|
||
print(f" ✅ ADAPTATION SUCCESSFUL")
|
||
elif evaluation['new_safety_factor'] >= 1.0:
|
||
print(f" ⚠️ ADAPTATION PARTIAL (margin < 1.5)")
|
||
else:
|
||
print(f" ❌ ADAPTATION INSUFFICIENT")
|
||
|
||
return {
|
||
'load_type': load_type,
|
||
'magnitude': magnitude,
|
||
'initial_state': {
|
||
'max_stress': load_state.max_stress,
|
||
'safety_factor': load_state.safety_factor
|
||
},
|
||
'adaptation': adaptation,
|
||
'adapted_stress': adapted_stress,
|
||
'evaluation': evaluation
|
||
}
|
||
|
||
if __name__ == "__main__":
|
||
geometry_file = "/home/allaun/Documents/Research Stack/5-Applications/text-to-cad/models/merkle_jack.json"
|
||
|
||
print("Initializing Adaptive Merkle Jack...")
|
||
adaptive_jack = AdaptiveMerkleJack(geometry_file)
|
||
|
||
print(f"Loaded {len(adaptive_jack.nodes)} nodes and {len(adaptive_jack.edges)} edges")
|
||
print("Using FAMM frustration physics, manifold-generalized Bernoulli, and Scale Space adaptation")
|
||
|
||
# Test adaptive simulation for critical load conditions
|
||
test_conditions = [
|
||
('tension', 5000.0, (0, 0, 1)), # Uplift - failed in static test
|
||
('torsion', 500.0, (0, 0, 1)), # Twist - catastrophic in static test
|
||
('shear', 3000.0, (0, 1, 0)), # Wind - safe but test adaptation
|
||
('compression', 10000.0, (0, 0, -1)), # Heavy compression - test adaptation
|
||
]
|
||
|
||
results = []
|
||
for load_type, magnitude, direction in test_conditions:
|
||
result = adaptive_jack.run_adaptive_simulation(load_type, magnitude, direction)
|
||
results.append(result)
|
||
|
||
print(f"\n{'='*70}")
|
||
print("ADAPTIVE SIMULATION SUMMARY")
|
||
print(f"{'='*70}")
|
||
|
||
for result in results:
|
||
print(f"\n{result['load_type'].upper()}:")
|
||
print(f" Initial SF: {result['initial_state']['safety_factor']:.2f}")
|
||
print(f" Final SF: {result['evaluation']['new_safety_factor']:.2f}")
|
||
print(f" Improvement: {result['evaluation']['improvement']*100:.1f}%")
|
||
print(f" Status: {'✅ PASS' if result['evaluation']['new_safety_factor'] >= 1.5 else '⚠️ MARGINAL' if result['evaluation']['new_safety_factor'] >= 1.0 else '❌ FAIL'}")
|
||
|
||
# Count passes
|
||
passes = sum(1 for r in results if r['evaluation']['new_safety_factor'] >= 1.5)
|
||
marginals = sum(1 for r in results if 1.0 <= r['evaluation']['new_safety_factor'] < 1.5)
|
||
fails = sum(1 for r in results if r['evaluation']['new_safety_factor'] < 1.0)
|
||
|
||
print(f"\nFinal Results: {passes} PASS, {marginals} MARGINAL, {fails} FAIL out of {len(results)} tests")
|
||
|
||
if fails == 0:
|
||
print("✅ Adaptive model achieves safety under all tested conditions")
|
||
elif passes + marginals >= len(results) * 0.8:
|
||
print("⚠️ Adaptive model achieves acceptable safety under most conditions")
|
||
else:
|
||
print("❌ Adaptive model requires further optimization")
|