""" Material-Bounded Merkle Jack - Realistic structure using Research Stack mathematics. This model uses FAMM frustration physics, manifold-generalized Bernoulli equations, and String-Star Manifold concepts to find OPTIMAL geometry, not impossible dynamic adaptation. Key differences from adaptive model: - No instantaneous geometry changes (physically impossible) - Elastic deformation only (Hooke's Law) - Yield strength enforcement - Fatigue life analysis - Manufacturing feasibility constraints - Optimization-based design instead of adaptation Mathematical Frameworks: - FAMM: Minimize frustration in optimal geometry design - Manifold-generalized Bernoulli: Optimal load distribution in design phase - String-Star Manifold: Curvature-aware geometry optimization - Scale Space: Multi-scale optimization for manufacturing """ import json import numpy as np import math from typing import List, Tuple, Dict, Any, Optional from dataclasses import dataclass @dataclass class MaterialProperties: """Real material properties with physical constraints. SLS Nylon PA12 (Selective Laser Sintering): - Lower modulus and strength than steel - Anisotropic due to layer orientation - Porosity affects properties """ youngs_modulus: float = 1.7e9 # Pa (SLS PA12 nylon) yield_strength: float = 48e6 # Pa (SLS PA12) ultimate_strength: float = 52e6 # Pa (SLS PA12) shear_modulus: float = 0.6e9 # Pa (SLS PA12) poisson_ratio: float = 0.4 density: float = 930 # kg/m³ (SLS PA12) fatigue_strength_coefficient: float = 0.3 # S-N curve coefficient (lower for polymers) fatigue_exponent: float = -0.12 # S-N curve exponent porosity: float = 0.03 # 3% porosity typical for SLS anisotropy_factor: float = 0.8 # Strength reduction in weak direction @dataclass class ManufacturingConstraints: """Manufacturing feasibility constraints for SLS.""" min_tubule_radius: float = 0.8e-3 # 0.8 mm minimum (SLS powder size limit) max_tubule_radius: float = 10.0e-3 # 10 mm maximum (SLS build volume) min_branch_angle: float = 45.0 # degrees (SLS overhang limit ~45°) max_branch_angle: float = 60.0 # degrees min_feature_size: float = 0.6e-3 # 0.6 mm (SLS powder size ~60µm) max_aspect_ratio: float = 5.0 # length/radius ratio (SLS support limited) layer_thickness: float = 0.1e-3 # 0.1 mm SLS layer thickness surface_roughness: float = 15e-6 # 15 µm Ra (SLS typical) min_wall_thickness: float = 0.8e-3 # 0.8 mm (SLS minimum) build_direction: str = "vertical" # SLS build orientation @dataclass class LoadCondition: """Expected load condition for design optimization.""" name: str load_type: str # 'compression', 'tension', 'shear', 'torsion' magnitude: float # N or N·m direction: Tuple[float, float, float] probability: float # Probability of occurrence (0-1) cycles: int # Expected load cycles for fatigue class MaterialBoundedMerkleJack: """Material-bounded Merkle Jack with realistic physics constraints.""" def __init__(self, geometry_file: str): """Load initial geometry.""" 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.material = MaterialProperties() # Manufacturing constraints self.manufacturing = ManufacturingConstraints() # Convert to meters self.tubule_radius = self.params['tubule_radius'] / 1000.0 self.cross_sectional_area = math.pi * self.tubule_radius**2 # Elastic deformation state self.elastic_deformation = {} self.residual_stress = {} # Fatigue damage accumulation self.fatigue_damage = {} def calculate_elastic_deformation(self, stress: float, edge_length: float) -> float: """ Calculate elastic deformation using Hooke's Law. σ = E * ε → ε = σ / E ΔL = ε * L = (σ / E) * L """ strain = stress / self.material.youngs_modulus deformation = strain * edge_length return deformation def check_yield_criterion(self, stress: float) -> bool: """Check if stress exceeds yield strength.""" return stress <= self.material.yield_strength def calculate_von_mises_stress(self, axial_stress: float, shear_stress: float = 0) -> float: """Calculate Von Mises stress.""" return math.sqrt(axial_stress**2 + 3 * shear_stress**2) def calculate_fatigue_life(self, stress_amplitude: float, mean_stress: float = 0) -> int: """ Calculate fatigue life using S-N curve with Goodman correction. N = (σ_e / σ_a)^(1/b) with Goodman mean stress correction """ # Endurance limit (typical for steel: 0.5 * ultimate strength) endurance_limit = self.material.fatigue_strength_coefficient * self.material.ultimate_strength # Goodman mean stress correction stress_ratio = mean_stress / self.material.ultimate_strength if self.material.ultimate_strength > 0 else 0 effective_amplitude = stress_amplitude / (1 - stress_ratio) # S-N curve: N = (σ_e / σ_a)^(1/b) if effective_amplitude > 0 and effective_amplitude < endurance_limit: cycles = int((endurance_limit / effective_amplitude) ** (1 / abs(self.material.fatigue_exponent))) else: cycles = 0 # Immediate failure return cycles def check_manufacturing_feasibility(self, radius: float, angle_deg: float) -> Tuple[bool, List[str]]: """Check if geometry is manufacturable.""" issues = [] # Check radius constraints if radius < self.manufacturing.min_tubule_radius: issues.append(f"Radius {radius*1000:.2f} mm below minimum {self.manufacturing.min_tubule_radius*1000:.2f} mm") if radius > self.manufacturing.max_tubule_radius: issues.append(f"Radius {radius*1000:.2f} mm above maximum {self.manufacturing.max_tubule_radius*1000:.2f} mm") # Check angle constraints if angle_deg < self.manufacturing.min_branch_angle: issues.append(f"Branch angle {angle_deg:.1f}° below minimum {self.manufacturing.min_branch_angle:.1f}°") if angle_deg > self.manufacturing.max_branch_angle: issues.append(f"Branch angle {angle_deg:.1f}° above maximum {self.manufacturing.max_branch_angle:.1f}°") # Check aspect ratio edge_lengths = [self.calculate_edge_length(edge) for edge in self.edges] for length in edge_lengths: aspect_ratio = length / radius if radius > 0 else float('inf') if aspect_ratio > self.manufacturing.max_aspect_ratio: issues.append(f"Aspect ratio {aspect_ratio:.1f} exceeds maximum {self.manufacturing.max_aspect_ratio}") return len(issues) == 0, issues def calculate_edge_length(self, edge: Tuple[int, int]) -> float: """Calculate edge length in meters.""" p_id, c_id = edge parent = self.node_map[p_id] child = self.node_map[c_id] dx = (child['x'] - parent['x']) / 1000.0 dy = (child['y'] - parent['y']) / 1000.0 dz = (child['z'] - parent['z']) / 1000.0 return math.sqrt(dx**2 + dy**2 + dz**2) def optimize_geometry_for_loads(self, load_conditions: List[LoadCondition]) -> Dict[str, Any]: """ Optimize geometry for multiple load conditions using FAMM and manifold math. This finds the BEST INITIAL geometry, not dynamic adaptation. """ print(f"\n{'='*70}") print(f"OPTIMIZING GEOMETRY FOR {len(load_conditions)} LOAD CONDITIONS") print(f"{'='*70}") # Current geometry evaluation current_evaluation = self.evaluate_all_loads(load_conditions) print(f"\nCurrent Geometry Performance:") print(f" Max stress: {current_evaluation['max_stress']/1e6:.2f} MPa") print(f" Safety factor: {current_evaluation['min_safety_factor']:.2f}") print(f" Fatigue life: {min(current_evaluation['fatigue_lives']) if current_evaluation['fatigue_lives'] else 'N/A'} cycles") print(f" Manufacturing feasible: {current_evaluation['manufacturing_feasible']}") if not current_evaluation['manufacturing_feasible']: print(f" Manufacturing issues: {len(current_evaluation['manufacturing_issues'])}") for issue in current_evaluation['manufacturing_issues'][:3]: print(f" - {issue}") # Optimization using FAMM frustration minimization # Instead of changing geometry dynamically, we find optimal static geometry optimal_angles = self.optimize_branch_angles_famm(load_conditions) optimal_radius = self.optimize_tubule_radius_manifold(load_conditions) print(f"\nOptimized Geometry Parameters:") print(f" Branch angles: {[f'{a:.1f}°' for a in optimal_angles]}") print(f" Tubule radius: {optimal_radius*1000:.2f} mm") # Evaluate optimized geometry old_params = self.params['branch_angles'][:] old_radius = self.tubule_radius self.params['branch_angles'] = optimal_angles self.tubule_radius = optimal_radius self.cross_sectional_area = math.pi * optimal_radius**2 optimized_evaluation = self.evaluate_all_loads(load_conditions) print(f"\nOptimized Geometry Performance:") print(f" Max stress: {optimized_evaluation['max_stress']/1e6:.2f} MPa") print(f" Safety factor: {optimized_evaluation['min_safety_factor']:.2f}") print(f" Fatigue life: {min(optimized_evaluation['fatigue_lives']) if optimized_evaluation['fatigue_lives'] else 'N/A'} cycles") print(f" Manufacturing feasible: {optimized_evaluation['manufacturing_feasible']}") if not optimized_evaluation['manufacturing_feasible']: print(f" Manufacturing issues: {len(optimized_evaluation['manufacturing_issues'])}") # Calculate improvement stress_reduction = (current_evaluation['max_stress'] - optimized_evaluation['max_stress']) / current_evaluation['max_stress'] sf_improvement = (optimized_evaluation['min_safety_factor'] - current_evaluation['min_safety_factor']) / current_evaluation['min_safety_factor'] print(f"\nOptimization Results:") print(f" Stress reduction: {stress_reduction*100:.1f}%") print(f" Safety factor improvement: {sf_improvement*100:.1f}%") # Restore original parameters self.params['branch_angles'] = old_params self.tubule_radius = old_radius self.cross_sectional_area = math.pi * old_radius**2 return { 'current': current_evaluation, 'optimized': optimized_evaluation, 'optimal_angles': optimal_angles, 'optimal_radius': optimal_radius, 'improvement': { 'stress_reduction': stress_reduction, 'sf_improvement': sf_improvement } } def optimize_branch_angles_famm(self, load_conditions: List[LoadCondition]) -> List[float]: """ Optimize branch angles using FAMM frustration minimization. Find angles that minimize frustration across all expected loads. """ # Current angles angles = list(self.params['branch_angles']) # Simple gradient descent on angle space best_angles = angles[:] best_frustration = float('inf') # Search space: ±15 degrees around current angles search_range = 15.0 # degrees step_size = 5.0 # degrees for i in range(len(angles)): test_angles = angles[:] for delta in np.arange(-search_range, search_range + step_size, step_size): test_angle = angles[i] + delta # Clamp to manufacturing constraints test_angle = max(self.manufacturing.min_branch_angle, min(self.manufacturing.max_branch_angle, test_angle)) test_angles[i] = test_angle # Calculate frustration for this configuration frustration = self.calculate_total_frustration(test_angles, load_conditions) if frustration < best_frustration: best_frustration = frustration best_angles[i] = test_angle return best_angles def calculate_total_frustration(self, angles: List[float], load_conditions: List[LoadCondition]) -> float: """Calculate total FAMM frustration across all load conditions.""" total_frustration = 0.0 # Temporarily set angles old_angles = self.params['branch_angles'][:] self.params['branch_angles'] = angles for load in load_conditions: # Calculate stress distribution for this load stresses = self.calculate_stress_for_load(load) # Calculate frustration if stresses: mean_stress = np.mean(list(stresses.values())) if mean_stress > 0: for stress in stresses.values(): total_frustration += abs(stress - mean_stress) / mean_stress # Restore angles self.params['branch_angles'] = old_angles return total_frustration / len(load_conditions) if load_conditions else 0 def optimize_tubule_radius_manifold(self, load_conditions: List[LoadCondition]) -> float: """ Optimize tubule radius using manifold-generalized Bernoulli. Find radius that balances stress across manifold curvature. """ # Current radius radius = self.tubule_radius # Search space: ±50% around current radius search_min = max(self.manufacturing.min_tubule_radius, radius * 0.5) search_max = min(self.manufacturing.max_tubule_radius, radius * 1.5) best_radius = radius best_stress = float('inf') # Test different radii for test_radius in np.linspace(search_min, search_max, 20): # Temporarily set radius old_radius = self.tubule_radius old_area = self.cross_sectional_area self.tubule_radius = test_radius self.cross_sectional_area = math.pi * test_radius**2 # Calculate max stress across all loads max_stress = 0 for load in load_conditions: stresses = self.calculate_stress_for_load(load) if stresses: max_stress = max(max_stress, max(stresses.values())) if max_stress < best_stress: best_stress = max_stress best_radius = test_radius # Restore radius self.tubule_radius = old_radius self.cross_sectional_area = old_area return best_radius def calculate_merkle_reinforcement_factor(self, edge: Tuple[int, int]) -> float: """ Calculate strain reinforcement factor from merkle tree topology. The merkle tree provides strain reinforcement through frustration physics: - When an edge is loaded, strain propagates through the tree - Sibling edges share load due to frustration minimization - Deeper nodes benefit from more load sharing paths - Branching factor determines reinforcement strength Reinforcement factor R = 1 + (branching_factor - 1) * (depth / max_depth) """ p_id, c_id = edge child = self.node_map[c_id] depth = child['depth'] max_depth = self.params['depth'] branching_factor = self.params['branching_factor'] # Base reinforcement from branching # More branches = more load sharing = higher reinforcement base_reinforcement = 1 + (branching_factor - 1) * 0.5 # Depth-dependent reinforcement # Deeper nodes have more load sharing paths through the tree depth_factor = depth / max_depth if max_depth > 0 else 0 # Total reinforcement factor # R = base * depth_factor + 1 (minimum reinforcement of 1) reinforcement = 1 + base_reinforcement * depth_factor * 0.3 return reinforcement def calculate_stress_for_load(self, load: LoadCondition) -> Dict[Tuple[int, int], float]: """Calculate stress distribution for a specific load condition with SLS effects and merkle reinforcement.""" stresses = {} # Effective area accounting for porosity effective_area = self.cross_sectional_area * (1 - self.material.porosity) # Anisotropy factor based on build direction anisotropy = self.material.anisotropy_factor # Calculate stress for each edge with merkle reinforcement for edge in self.edges: base_stress = 0 if load.load_type == 'tension': if edge[0] == 0: base_stress = load.magnitude / effective_area else: base_stress = load.magnitude * 0.5 / effective_area # Tension is sensitive to anisotropy base_stress /= anisotropy elif load.load_type == 'compression': base_stress = load.magnitude * 0.1 / effective_area # Compression less sensitive to anisotropy base_stress /= (anisotropy * 0.9 + 0.1) elif load.load_type == 'shear': base_stress = load.magnitude * 0.2 / effective_area # Shear highly sensitive to anisotropy base_stress /= anisotropy elif load.load_type == 'torsion': p_id, c_id = edge child = self.node_map[c_id] r = math.sqrt(child['x']**2 + child['y']**2) / 1000.0 base_stress = load.magnitude * r / effective_area # Torsion sensitive to anisotropy base_stress /= anisotropy # Apply merkle topology strain reinforcement # The tree structure provides load sharing through frustration physics reinforcement = self.calculate_merkle_reinforcement_factor(edge) base_stress /= reinforcement stresses[tuple(edge)] = base_stress return stresses def evaluate_all_loads(self, load_conditions: List[LoadCondition]) -> Dict[str, Any]: """Evaluate geometry under all load conditions.""" max_stress = 0 min_safety_factor = float('inf') fatigue_lives = [] for load in load_conditions: stresses = self.calculate_stress_for_load(load) if stresses: load_max_stress = max(stresses.values()) max_stress = max(max_stress, load_max_stress) safety_factor = self.material.yield_strength / load_max_stress min_safety_factor = min(min_safety_factor, safety_factor) # Calculate fatigue life fatigue_life = self.calculate_fatigue_life(load_max_stress * 0.5, load_max_stress * 0.1) fatigue_lives.append(fatigue_life) # Check manufacturing feasibility feasible, issues = self.check_manufacturing_feasibility(self.tubule_radius, self.params['branch_angles'][0]) return { 'max_stress': max_stress, 'min_safety_factor': min_safety_factor, 'fatigue_lives': fatigue_lives, 'manufacturing_feasible': feasible, 'manufacturing_issues': issues } def generate_design_report(self, optimization_result: Dict[str, Any], output_file: str): """Generate design optimization report.""" with open(output_file, 'w') as f: f.write("# Material-Bounded Merkle Jack Design Report\n\n") f.write(f"Generated: {__import__('datetime').datetime.now()}\n\n") f.write("## Material Properties\n\n") f.write(f"- Material: SLS Nylon PA12 (Selective Laser Sintering)\n") f.write(f"- Young's Modulus: {self.material.youngs_modulus/1e9:.1f} GPa\n") f.write(f"- Yield Strength: {self.material.yield_strength/1e6:.1f} MPa\n") f.write(f"- Ultimate Strength: {self.material.ultimate_strength/1e6:.1f} MPa\n") f.write(f"- Density: {self.material.density} kg/m³\n") f.write(f"- Porosity: {self.material.porosity*100:.1f}%\n") f.write(f"- Anisotropy Factor: {self.material.anisotropy_factor}\n\n") f.write("## Manufacturing Constraints\n\n") f.write(f"- Min/Max Tubule Radius: {self.manufacturing.min_tubule_radius*1000:.1f} / {self.manufacturing.max_tubule_radius*1000:.1f} mm\n") f.write(f"- Min/Max Branch Angle: {self.manufacturing.min_branch_angle:.1f}° / {self.manufacturing.max_branch_angle:.1f}°\n") f.write(f"- Max Aspect Ratio: {self.manufacturing.max_aspect_ratio}\n\n") f.write("## Optimization Results\n\n") f.write(f"**Stress Reduction:** {optimization_result['improvement']['stress_reduction']*100:.1f}%\n") f.write(f"**Safety Factor Improvement:** {optimization_result['improvement']['sf_improvement']*100:.1f}%\n\n") f.write("### Recommended Geometry\n\n") f.write(f"- Branch Angles: {[f'{a:.1f}°' for a in optimization_result['optimal_angles']]}\n") f.write(f"- Tubule Radius: {optimization_result['optimal_radius']*1000:.2f} mm\n\n") f.write("### Performance Comparison\n\n") f.write("| Metric | Current | Optimized |\n") f.write("|--------|---------|----------|\n") f.write(f"| Max Stress | {optimization_result['current']['max_stress']/1e6:.2f} MPa | {optimization_result['optimized']['max_stress']/1e6:.2f} MPa |\n") f.write(f"| Safety Factor | {optimization_result['current']['min_safety_factor']:.2f} | {optimization_result['optimized']['min_safety_factor']:.2f} |\n") f.write(f"| Fatigue Life | {min(optimization_result['current']['fatigue_lives']) if optimization_result['current']['fatigue_lives'] else 'N/A'} | {min(optimization_result['optimized']['fatigue_lives']) if optimization_result['optimized']['fatigue_lives'] else 'N/A'} |\n") f.write(f"| Manufacturing Feasible | {'Yes' if optimization_result['current']['manufacturing_feasible'] else 'No'} | {'Yes' if optimization_result['optimized']['manufacturing_feasible'] else 'No'} |\n\n") if not optimization_result['optimized']['manufacturing_feasible']: f.write("### Manufacturing Issues\n\n") for issue in optimization_result['optimized']['manufacturing_issues']: f.write(f"- {issue}\n") f.write("## Key Differences from Adaptive Model\n\n") f.write("- **No instantaneous geometry changes** - uses optimal static geometry\n") f.write("- **Elastic deformation only** - obeys Hooke's Law\n") f.write("- **Yield strength enforcement** - prevents plastic deformation\n") f.write("- **Fatigue life analysis** - accounts for cyclic loading\n") f.write("- **Manufacturing constraints** - realistic production limits\n") f.write("- **Optimization-based design** - finds best initial configuration\n") f.write("- **Merkle topology strain reinforcement** - load sharing through frustration physics\n\n") f.write("## Merkle Topology Strain Reinforcement\n\n") f.write("The merkle tree structure provides strain reinforcement through FAMM frustration physics:\n") f.write("- When an edge is loaded, strain propagates through the tree\n") f.write("- Sibling edges share load due to frustration minimization\n") f.write("- Deeper nodes benefit from more load sharing paths\n") f.write("- Branching factor determines reinforcement strength\n\n") f.write(f"- Branching Factor: {self.params['branching_factor']}\n") f.write(f"- Tree Depth: {self.params['depth']}\n") f.write(f"- Max Reinforcement Factor: {1 + (self.params['branching_factor'] - 1) * 0.5 * 0.3:.2f}x\n\n") if optimization_result['optimized']['min_safety_factor'] >= 1.5: f.write("✅ **DESIGN SAFE** - Meets safety requirements\n") elif optimization_result['optimized']['min_safety_factor'] >= 1.0: f.write("⚠️ **DESIGN MARGINAL** - Low safety margin\n") else: f.write("❌ **DESIGN UNSAFE** - Exceeds yield strength\n") print(f"\nDesign report generated: {output_file}") if __name__ == "__main__": geometry_file = "/home/allaun/Documents/Research Stack/5-Applications/text-to-cad/models/merkle_jack.json" output_report = "/home/allaun/Documents/Research Stack/5-Applications/text-to-cad/models/material_bounded_design_report.md" print("Initializing Material-Bounded Merkle Jack...") jack = MaterialBoundedMerkleJack(geometry_file) print(f"Loaded {len(jack.nodes)} nodes and {len(jack.edges)} edges") print("Using material physics constraints: Hooke's Law, yield strength, fatigue, manufacturing limits") # Define expected load conditions load_conditions = [ LoadCondition("Compression - Static", "compression", 10000.0, (0, 0, -1), 0.5, 1000), LoadCondition("Tension - Uplift", "tension", 5000.0, (0, 0, 1), 0.3, 10000), LoadCondition("Shear - Wind", "shear", 3000.0, (0, 1, 0), 0.4, 5000), LoadCondition("Torsion - Twist", "torsion", 500.0, (0, 0, 1), 0.1, 1000), ] print(f"\nOptimizing for {len(load_conditions)} expected load conditions...") # Optimize geometry optimization_result = jack.optimize_geometry_for_loads(load_conditions) # Generate report jack.generate_design_report(optimization_result, output_report) print("\nMaterial-bounded design optimization complete!") print("This model uses realistic material physics and manufacturing constraints.")