mirror of
https://github.com/allaunthefox/Research-Stack.git
synced 2026-07-31 03:05:21 +00:00
566 lines
27 KiB
Python
566 lines
27 KiB
Python
"""
|
||
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.")
|