Problem: Robust Four-Bar Linkage Synthesis

The problem involves processing angular position data to synthesize a valid four-bar linkage—including converting positions to coordinates, solving the Freudenstein system for coefficients, calculating link lengths, checking the Grashof condition, assessing synthesis error with noise, and visualizing results—while ensuring numerical stability and physical validity.

Background: ### Industry Background for Four-Bar Linkage Synthesis Pipeline
This code implements a robust end-to-end pipeline for synthesizing four-bar linkages, a foundational component in mechanical engineering (e.g., industrial robotics, automotive actuators, precision machinery). The pipeline adheres to industrial best practices and mathematical standards to ensure reproducibility and real-world applicability:

#### 1. Mathematical Foundations & Industrial Relevance
- **Coordinate Transformation (Step 12.1)**: Converts polar coordinate data to Cartesian coordinates with support for hyperbolic and complex exponential forms. This handles extreme angles and noisy sensor data common in industrial measurement systems, with high-precision `Decimal` parsing to avoid floating-point errors.
- **Freudenstein's Equation (Step 12.2)**: Constructs the system matrix for Freudenstein's equation (`cos(φ-γ) = K₁cosφ - K₂cosγ + K₃`), the de facto standard for four-bar linkage synthesis. Extended harmonic terms improve approximation accuracy for high-dimensional datasets, critical for complex mechanical designs.
- **Regularized Optimization (Step 12.3)**: Uses SVD-based L-curve regularization to select optimal Tikhonov parameters, followed by Huber M-estimation via Iteratively Reweighted Least Squares (IRLS). This mitigates outliers from noisy industrial measurements, ensuring stable coefficient estimation.
- **Link Length Calculation (Step 12.4)**: Derives physical link lengths from Freudenstein coefficients using geometric constraints, with singularity handling to avoid division-by-zero errors. This step ensures results are physically valid (non-negative lengths) for manufacturing.
- **Grashof Condition Validation (Step 12.5)**: Checks the Grashof criterion (`shortest + longest ≤ sum of intermediate links`), a critical requirement for ensuring continuous rotation capability (e.g., crank-rocker mechanisms for conveyor systems).
- **Error Analysis (Step 12.6)**: Simulates measurement noise (Gaussian, std=0.1°) to compute synthesis error, quantifying how well the linkage meets design specifications—essential for regulatory compliance in aerospace and automotive industries.
- **Visualization (Step 12.7)**: Generates comparative plots of ideal vs. noisy linkage positions, aiding in design validation and stakeholder communication.

#### 2. Replication Guidance
To replicate this pipeline, ensure the following dependencies are installed, and adhere to numerical stability practices:
- Use high-precision arithmetic for data parsing to avoid input-related errors.
- Maintain fixed random seeds for noise injection to ensure reproducible error analysis.
- Handle sparse matrices for large datasets to optimize computation time in industrial-scale design cycles.

---
from typing import List, Tuple, Union
import numpy as np
from decimal import Decimal, InvalidOperation
import re
import scipy.sparse as sparse
from scipy.sparse.linalg import randomized_svd, spsolve
from scipy.linalg import solve
import matplotlib.pyplot as plt
import warnings

Input/Output: The problem takes a multi-line string (with comment lines starting with "#" and valid lines containing two numerical values for phi and gamma in degrees) and a numeric scalar or numpy array d as inputs. It processes the string to generate a list of Cartesian (x,y) tuples by removing comments/empty lines, parsing values, converting to coordinates (polar for default angles, hyperbolic for large angles, complex exponential for small angles), and filtering outliers via Mahalanobis distance (fallback to z-score). From the positions, it constructs a Freudenstein system matrix A (2D numpy array) and vector b (1D numpy array). It solves A and b to obtain coefficients K1, K2, K3. Using these coefficients and d, it computes non-negative link lengths a_len, b_len, c_len. It checks if the links satisfy the Grashof condition (all positive, a is shortest, shortest + longest ≤ sum of intermediates with relative tolerance) via a boolean or boolean array. It calculates the maximum synthesis error from combined original/perturbed positions. Finally, it generates a matplotlib figure and axes plotting ideal/noisy linkages, residuals, and statistical summaries.

Required dependencies:
from typing import List, Tuple, Union
import re
from decimal import Decimal, InvalidOperation
import warnings
import numpy as np
import scipy.sparse as sparse
from scipy.sparse.linalg import spsolve, randomized_svd
from scipy.linalg import solve
import matplotlib.pyplot as plt

Sub-steps:
step_number: 1
step_description_prompt: Please write a Python function named load_positions_from_string. It takes a multi-line string, removes empty lines/comments, parses each valid line into two numbers. Convert to Cartesian coordinates using polar (default), hyperbolic (large angles), or complex exp (small angles) based on angle magnitude. Filter outliers with Mahalanobis distance (fallback to z-score) and return a list of (x,y) tuples. Raise ValueError for invalid lines/numbers.
function_header: def load_positions_from_string(data_string):
test_cases: import numpy as np
import matplotlib.pyplot as plt
for data_str, expected in [
    ("30 45\n60 90", [(30.0, 45.0), (60.0, 90.0)]),
    ("0 0\n180 180\n", [(0.0, 0.0), (180.0, 180.0)]),
    ("", [])
]:
    result = load_positions_from_string(data_str)
    if data_str == "":
        assert result == []
    else:
        assert isinstance(result, list) and len(result) > 0
        assert all(isinstance(pos, tuple) and len(pos) == 2 for pos in result)

step_number: 2
step_description_prompt: Write a Python function setup_freudenstein_system(positions: Any) -> Tuple[np.ndarray, np.ndarray]. Validate positions (2D, finite values), normalize angles to [-180°,180°), convert to radians. Build matrix A (extended with sin, cos2 terms if ≥7 points else 3 columns), compute b=cos(phi-gamma) plus small noise. Use numpy, float64 precision.
function_header: def setup_freudenstein_system(positions):
test_cases: import numpy as np
import matplotlib.pyplot as plt
positions = [(0, 0), (90, 90), (180, 180)]
A, b = setup_freudenstein_system(positions)
assert A.shape == (3, 3)
assert b.shape == (3,)
assert np.allclose(A[0], [1, -1, 1])  # cos(0)=1, cos(0)=1
assert np.allclose(b[0], 1.0)

step_number: 3
step_description_prompt: Please write a Python function named solve_freudenstein_system that takes A and b as inputs. It performs input validation, uses SVD for L-curve regularization parameter selection, then applies Huber M-estimation via Iteratively Reweighted Least Squares to compute three coefficients, returning them as three separate values.
function_header: def solve_freudenstein_system(A, b):
test_cases: import numpy as np
import matplotlib.pyplot as plt
A = np.array([[1, -1, 1], [0, 0, 1], [-1, 1, 1]])
b = np.array([1, 0, 1])
K1, K2, K3 = solve_freudenstein_system(A, b)
# Exact solution for this system: K1=1, K2=1, K3=0
actual_vals = [K1, K2, K3]
expected_vals = [0.0, 0.0, 1.2486255758741198e-15]
assert np.allclose(actual_vals, expected_vals, atol=1e-6)

step_number: 4
step_description_prompt: Please write a Python function named calculate_link_lengths that accepts K1, K2, K3, d (each numeric scalar or numpy array). It returns a tuple of three non-negative values: a_len, b_len, c_len. Steps: Validate inputs (type check: scalar/array; no NaN/inf). Handle K1/K2 singularity with threshold (1e-12: if abs(K1)>=eps then a=d/K1 else0; same for c=d/K2). Compute b_squared = a² +c² +d² -2*a*c*K3 (clamp negative values to zero then sqrt to get b_len). Return absolute values of a, b_len, c. Raise TypeError (invalid type) or ValueError (NaN/inf). Support vectorized operations via numpy.

function_header: def calculate_link_lengths(K1, K2, K3, d):
test_cases: import numpy as np
import matplotlib.pyplot as plt
for K1, K2, K3, d, expected in [
    (1.0, 1.0, 0.0, 100.0, (100.0, 173.20508075688772, 100.0)),  # degenerate case
    (0.5, 0.5, 1.0, 100.0, (200.0, 100.0, 200.0)), # symmetric mechanism
    (2.0, 0.5, 0.0, 50.0, (25.0, 114.564392373896, 100.0)) # equilateral triangle
]:
    a, b, c = calculate_link_lengths(K1, K2, K3, d)
    assert np.allclose([a, b, c], expected, atol=1e-5)

step_number: 5
step_description_prompt: Please write a Python function named check_grashof_condition that takes a, b, c, d (each can be np.ndarray, float, or int) as four-bar linkage lengths. The function checks three conditions: all links are positive; the Grashof condition (shortest + longest ≤ sum of intermediates with relative tolerance); link a is the shortest (numerically stable). It supports batch processing and broadcasting, returning a bool or bool array of the same shape as inputs.
function_header: def check_grashof_condition(a, b, c, d):
test_cases: import numpy as np
import matplotlib.pyplot as plt
for a, b, c, d, expected in [
    (30, 50, 40, 60, True),   # Grashof: 30+60 <= 40+50 → 90<=90
    (20, 80, 30, 70, True),  # Non-Grashof: 20+80 > 30+70 → 100>100? no, 100=100 but strict for crank
    (40, 30, 50, 20, False)    # Shortest is ground link? No, but input must be shortest
]:
    result = check_grashof_condition(a, b, c, d)
    assert result == expected

step_number: 6
step_description_prompt: Write a Python function named calculate_synthesis_error with parameters positions, K1, K2, K3. Steps:  
1. Validate inputs: positions (list/numpy array → float64, shape (n,2), non-empty); K1-K3 (numeric: int/float/numpy number).  
2. Add Gaussian noise (seed=42, scale=0.1) to positions → perturbed; combine original and perturbed into one array.  
3. Convert phi (col0) and gamma (col1) of combined positions from degrees to radians.  
4. Compute lhs=K1*cos(phi_rad)-K2*cos(gamma_rad)+K3; rhs=cos(phi_rad)*cos(gamma_rad)+sin(phi_rad)*sin(gamma_rad) (cos identity).  
5. Residuals: abs(lhs-rhs) → degrees; return robust max (handle NaN/Inf).  

Use numpy vectorized operations for all steps. Include all validation checks.
function_header: def calculate_synthesis_error(positions, K1, K2, K3):
test_cases: import numpy as np
import matplotlib.pyplot as plt
import math

# Test 1: check_grashof_condition - Valid Grashof condition (a is shortest)
test1_result = check_grashof_condition(10, 20, 30, 40)
expected1 = True
assert test1_result == expected1, f"Test 1 failed: check_grashof_condition(10, 20, 30, 40) = {test1_result}, expected {expected1}"

# Test 2: calculate_synthesis_error - Calculate synthesis error (function uses fixed seed=42 internally)
expected2 = 74.86145218475568
test2_result = calculate_synthesis_error([(30, 30), (90, 60)], 0.5, 1.2, 0.3)
assert math.isclose(test2_result, expected2, rel_tol=1e-5), f"Test 2 failed: calculate_synthesis_error = {test2_result}, expected {expected2}"

# Test 3: calculate_link_lengths - Calculate link lengths from K constants
test3_result = calculate_link_lengths(1.0, 0.8, 0.5, 100.0)
# Convert numpy scalars to Python floats if needed
a_actual = float(test3_result[0])
b_actual = float(test3_result[1])
c_actual = float(test3_result[2])
a_exp = 100.0 / 1.0  # 100.0
c_exp = 100.0 / 0.8  # 125.0
b_squared_exp = a_exp**2 + c_exp**2 + 100.0**2 - 2 * a_exp * c_exp * 0.5
b_exp = math.sqrt(max(0, b_squared_exp))
assert math.isclose(a_actual, a_exp, rel_tol=1e-10), f"Test 3 failed: a = {a_actual}, expected {a_exp}"
assert math.isclose(b_actual, b_exp, rel_tol=1e-10), f"Test 3 failed: b = {b_actual}, expected {b_exp}"
assert math.isclose(c_actual, c_exp, rel_tol=1e-10), f"Test 3 failed: c = {c_actual}, expected {c_exp}"

step_number: 7
step_description_prompt: Please write a Python code to generate a function named plot_linkage_synthesis. The function takes positions (phi_deg, gamma_deg tuples), link_lengths (4 elements), K_constants (3 elements). It validates inputs, converts angles to radians, adds Gaussian noise (std=1°), computes complex coordinates A/B, calculates residuals, plots ground link, ideal/noisy linkages with distinct styles, adds stats text, and returns fig and ax.
function_header: def plot_linkage_synthesis(positions, link_lengths, K_constants):
test_cases: import numpy as np
import matplotlib.pyplot as plt
positions = [(0, 0), (60, 45), (120, 90)]
link_lengths = (50.0, 70.0, 60.0, 100.0)
K_constants = (1.0, 0.8, 0.5)
for _ in range(3):  # three quick runs
    fig, ax = plot_linkage_synthesis(positions, link_lengths, K_constants)
    plt.close(fig)

step_number: 8
step_description_prompt: Please write Python code to generate a function named solve_advanced_linkage_synthesis that takes positions (2D array of theta-phi pairs) and d (positive frame length). Validate inputs, compute cos terms, solve regularized linear system via QR for initial K values. Derive initial a, b, c, then optimize using perturbed least squares with bounds. Return K constants, link lengths, residuals, feasibility, max error.
function_header: def solve_advanced_linkage_synthesis(positions, d=100.0):
test_cases: import numpy as np
import matplotlib.pyplot as plt
positions = [(0, 0), (45, 30), (90, 60), (135, 90), (180, 120)]
result = solve_advanced_linkage_synthesis(positions, d=100.0)
assert "K_constants" in result
assert "link_lengths" in result
assert "is_feasible" in result
assert "max_error_deg" in result
assert len(result["link_lengths"]) == 4

General solution:

General tests: