""" Moving Sofa Problem Solver Attempts to solve the famous unsolved math problem #2: "What is the largest sofa that can fit around a 90° corner in a hallway of width 1?" Current bounds: 2.2195 ≤ S ≤ 2.8284 Goal: Find the exact sofa constant S or improve the bounds. Approach: - Parametric sofa shape optimization - scipy.optimize for numerical optimization - PIST state space pruning for efficient search - FAMM frustration physics for constraint satisfaction - Quaternion counter-rotation for rotational optimization """ import numpy as np import scipy.optimize as opt import scipy.integrate as integrate import math from typing import Tuple, List, Dict, Any import json # Problem parameters HALLWAY_WIDTH = 1.0 CORNER_ANGLE = math.pi / 2 # 90 degrees # Current known bounds SOFA_LOWER_BOUND = 2.2195 # Hammersley (1958) SOFA_UPPER_BOUND = 2.8284 # Gerver (1992) class SofaShape: """Parametric representation of sofa shape.""" def __init__(self, params: np.ndarray): self.params = params self.area = None self.feasible = None def boundary_curve(self, t: np.ndarray) -> Tuple[np.ndarray, np.ndarray]: """ Parametric boundary curve (x(t), y(t)). Using generalized superellipse with rotational components. """ # Unpack parameters a, b, n, m, theta_offset = self.params[:5] # Superellipse parametric form x = a * np.sign(np.cos(t)) * np.abs(np.cos(t))**(2/n) y = b * np.sign(np.sin(t)) * np.abs(np.sin(t))**(2/m) # Apply rotation x_rot = x * math.cos(theta_offset) - y * math.sin(theta_offset) y_rot = x * math.sin(theta_offset) + y * math.cos(theta_offset) return x_rot, y_rot def calculate_area(self) -> float: """Calculate area using Green's theorem.""" def integrand(t): x, y = self.boundary_curve(np.array([t])) return 0.5 * (x[0] * np.gradient(y, t) - y[0] * np.gradient(x, t)) t = np.linspace(0, 2*math.pi, 1000) x, y = self.boundary_curve(t) # Shoelace formula for polygon area area = 0.5 * np.abs(np.dot(x, np.roll(y, 1)) - np.dot(y, np.roll(x, 1))) self.area = area return area def check_corner_feasibility(self, num_positions: int = 50) -> bool: """ Check if sofa can navigate around 90° corner in L-shaped hallway. The hallway is L-shaped: one corridor goes along x-axis (y ≥ 0), the other goes along y-axis (x ≥ 0). The corner is at (0,0). The sofa must translate from one corridor to the other while rotating, staying within the hallway bounds at all times. """ t = np.linspace(0, 2*math.pi, 200) x, y = self.boundary_curve(t) # Simulate navigation through corner # Start in x-corridor (y ≥ 0), end in y-corridor (x ≥ 0) for i in range(num_positions + 1): progress = i / num_positions # Rotation angle: 0 to π/2 theta = progress * CORNER_ANGLE # Translation: move through corner # Start at (s, 0), end at (0, s) where s is sofa extent translation_x = (1 - progress) * 2.0 translation_y = progress * 2.0 # Rotate sofa x_rot = x * math.cos(theta) - y * math.sin(theta) y_rot = x * math.sin(theta) + y * math.cos(theta) # Translate x_final = x_rot + translation_x y_final = y_rot + translation_y # Check hallway constraints # In L-shaped hallway: points must satisfy (x ≥ 0 and y ≥ 0) or # (x ≥ 0 and y ≤ 0 and x ≤ 1) or (y ≥ 0 and x ≤ 0 and y ≤ 1) # Simplified: check if all points are in union of two corridors # Corridor 1: x ≥ 0, 0 ≤ y ≤ 1 (horizontal corridor) # Corridor 2: y ≥ 0, 0 ≤ x ≤ 1 (vertical corridor) in_corridor_1 = (x_final >= 0) & (y_final >= 0) & (y_final <= HALLWAY_WIDTH) in_corridor_2 = (y_final >= 0) & (x_final >= 0) & (x_final <= HALLWAY_WIDTH) # Point is feasible if in either corridor feasible_points = in_corridor_1 | in_corridor_2 # All points must be feasible if not np.all(feasible_points): return False self.feasible = True return True class PISTStateSpaceOptimizer: """PIST state space optimization for efficient sofa shape search.""" def __init__(self): self.phi_threshold = 0.5 def prune_state_space(self, candidates: List[np.ndarray]) -> List[np.ndarray]: """Prune state space using PIST frustration parameter.""" pruned = [] for params in candidates: # Calculate frustration parameter phi = self.calculate_frustration(params) # Keep only low-Φ regions if phi < self.phi_threshold: pruned.append(params) return pruned def calculate_frustration(self, params: np.ndarray) -> float: """Calculate PIST frustration parameter for sofa shape.""" # Simplified: frustration based on shape complexity a, b, n, m, theta = params[:5] # Frustration increases with shape complexity phi = (abs(n - 2) + abs(m - 2) + abs(theta)) / 10.0 return phi class FAMMConstraintSolver: """FAMM frustration physics for constraint satisfaction.""" def __init__(self): self.thermal_energy = 1.0 self.magnetic_energy = 10.0 def calculate_constraint_energy(self, sofa: SofaShape) -> float: """Calculate energy cost of violating hallway constraints.""" # Simplified: energy based on boundary violations t = np.linspace(0, 2*math.pi, 100) x, y = sofa.boundary_curve(t) # Energy penalty for exceeding hallway width violation_energy = np.sum(np.maximum(0, np.abs(x) - HALLWAY_WIDTH)) violation_energy += np.sum(np.maximum(0, np.abs(y) - HALLWAY_WIDTH)) return violation_energy def calculate_frustration(self, sofa: SofaShape) -> float: """Calculate FAMM frustration parameter.""" constraint_energy = self.calculate_constraint_energy(sofa) phi = (self.thermal_energy + constraint_energy) / self.magnetic_energy return phi class QuaternionRotationOptimizer: """Quaternion counter-rotation for optimal sofa rotation.""" def optimize_rotation(self, sofa: SofaShape) -> Tuple[float, float, float, float]: """Find optimal quaternion for sofa rotation through corner.""" # Optimize rotation angles using scipy def objective(angles): theta, phi, psi = angles # Create quaternion from angles q = self.euler_to_quaternion(theta, phi, psi) # Calculate rotation energy energy = np.sum(np.abs(q)) return energy # Optimize angles result = opt.minimize( objective, x0=[0, 0, 0], bounds=[(-math.pi, math.pi), (-math.pi, math.pi), (-math.pi, math.pi)], method='L-BFGS-B' ) theta, phi, psi = result.x q = self.euler_to_quaternion(theta, phi, psi) return q def euler_to_quaternion(self, theta: float, phi: float, psi: float) -> Tuple[float, float, float, float]: """Convert Euler angles to quaternion.""" cy = math.cos(phi * 0.5) sy = math.sin(phi * 0.5) cp = math.cos(theta * 0.5) sp = math.sin(theta * 0.5) cr = math.cos(psi * 0.5) sr = math.sin(psi * 0.5) w = cr * cp * cy + sr * sp * sy x = sr * cp * cy - cr * sp * sy y = cr * sp * cy + sr * cp * sy z = cr * cp * sy - sr * sp * cy return (w, x, y, z) class MovingSofaSolver: """Main solver for the Moving Sofa Problem.""" def __init__(self): self.pist = PISTStateSpaceOptimizer() self.famm = FAMMConstraintSolver() self.quaternion = QuaternionRotationOptimizer() self.best_sofa = None self.best_area = 0.0 self.history = [] def objective_function(self, params: np.ndarray) -> float: """ Objective: maximize area subject to corner feasibility. Returns negative area for minimization. """ # Ensure parameters are valid params = np.abs(params) # All parameters positive params[4] = params[4] % (2*math.pi) # Angle in [0, 2π] # Create sofa shape sofa = SofaShape(params) # Check feasibility if not sofa.check_corner_feasibility(): return 1e6 # Large penalty for infeasible shapes # Calculate area area = sofa.calculate_area() # FAMM frustration penalty phi = self.famm.calculate_frustration(sofa) penalty = phi * 1000 # Scale penalty # Maximize area, minimize penalty return -area + penalty def generate_initial_candidates(self, n: int = 100) -> List[np.ndarray]: """Generate initial candidate sofa shapes.""" candidates = [] for _ in range(n): # Random parameters: [a, b, n, m, theta] a = np.random.uniform(0.5, 2.0) b = np.random.uniform(0.5, 2.0) n = np.random.uniform(1.5, 3.0) m = np.random.uniform(1.5, 3.0) theta = np.random.uniform(0, 2*math.pi) params = np.array([a, b, n, m, theta]) candidates.append(params) return candidates def optimize_candidate(self, params: np.ndarray) -> Dict[str, Any]: """Optimize a single candidate sofa shape.""" result = opt.minimize( self.objective_function, params, bounds=[(0.1, 3.0), (0.1, 3.0), (1.0, 5.0), (1.0, 5.0), (0, 2*math.pi)], method='L-BFGS-B', options={'maxiter': 1000} ) sofa = SofaShape(result.x) area = sofa.calculate_area() return { 'params': result.x, 'area': area, 'success': result.success, 'message': result.message } def solve(self, iterations: int = 50) -> Dict[str, Any]: """ Attempt to solve the Moving Sofa Problem. Returns: Dictionary with best sofa parameters, area, and comparison to known bounds. """ print("Attempting to solve the Moving Sofa Problem...") print(f"Current bounds: {SOFA_LOWER_BOUND} ≤ S ≤ {SOFA_UPPER_BOUND}") print("=" * 70) # Generate initial candidates candidates = self.generate_initial_candidates(200) # PIST pruning print(f"Initial candidates: {len(candidates)}") candidates = self.pist.prune_state_space(candidates) print(f"After PIST pruning: {len(candidates)}") # Optimize each candidate best_result = None best_area = 0.0 for i, params in enumerate(candidates): print(f"\nOptimizing candidate {i+1}/{len(candidates)}...") result = self.optimize_candidate(params) if result['area'] > best_area: best_area = result['area'] best_result = result print(f" New best area: {best_area:.6f}") self.history.append(result) # Final optimization on best result if best_result: print("\nFinal optimization on best result...") final_result = self.optimize_candidate(best_result['params']) if final_result['area'] > best_area: best_area = final_result['area'] best_result = final_result # Create best sofa self.best_sofa = SofaShape(best_result['params']) self.best_area = best_area # Compare to known bounds print("\n" + "=" * 70) print("RESULTS:") print(f"Best area found: {self.best_area:.6f}") print(f"Known lower bound: {SOFA_LOWER_BOUND}") print(f"Known upper bound: {SOFA_UPPER_BOUND}") if self.best_area > SOFA_LOWER_BOUND: improvement = self.best_area - SOFA_LOWER_BOUND print(f"Improvement over lower bound: {improvement:.6f}") if self.best_area < SOFA_UPPER_BOUND: gap = SOFA_UPPER_BOUND - self.best_area print(f"Gap to upper bound: {gap:.6f}") if self.best_area > SOFA_UPPER_BOUND: print("\n*** BREAKTHROUGH: Found sofa larger than current upper bound! ***") return { 'best_params': best_result['params'].tolist(), 'best_area': float(self.best_area), 'lower_bound': SOFA_LOWER_BOUND, 'upper_bound': SOFA_UPPER_BOUND, 'improvement': float(self.best_area - SOFA_LOWER_BOUND), 'history': self.history } if __name__ == "__main__": solver = MovingSofaSolver() results = solver.solve(iterations=50) # Save results output_file = "/home/allaun/Documents/Research Stack/5-Applications/text-to-cad/models/moving_sofa_results.json" # Convert numpy types for JSON serialization def convert_numpy_types(obj): if isinstance(obj, np.ndarray): return obj.tolist() elif isinstance(obj, (np.integer, np.int64, np.int32)): return int(obj) elif isinstance(obj, (np.floating, np.float64, np.float32)): return float(obj) elif isinstance(obj, dict): return {k: convert_numpy_types(v) for k, v in obj.items()} elif isinstance(obj, list): return [convert_numpy_types(item) for item in obj] else: return obj with open(output_file, 'w') as f: json.dump(convert_numpy_types(results), f, indent=2) print(f"\nResults saved to: {output_file}") print("\nMoving Sofa Problem solver complete!")