Freudenstein’s equation for four-bar synthesis (1954)#

Before computers, linkages that generate a prescribed function were designed by graphical construction. Ferdinand Freudenstein (1954) wrote the four-bar’s loop closure as

\[K_1\cos\theta_4 - K_2\cos\theta_2 + K_3 = \cos(\theta_2 - \theta_4),\]

which is linear in three link-length ratios. Prescribing three pairs of input and output angles, the precision points, gives three linear equations (freudenstein_coefficients()), and the link lengths follow (freudenstein_link_lengths()). The example designs a linkage whose output angle is proportional to \(\log_{10} x\) for \(1 \le x \le 2\), using Chebyshev spacing of the precision points to keep the error small between them.

import matplotlib.pyplot as plt
import numpy as np

from engineeringkit.mechanical_engineering import fourbar_output_angle, freudenstein_coefficients, freudenstein_link_lengths

Precision points#

The input crank turns 60 degrees from 40 degrees as \(x\) runs from 1 to 2. The output turns 90 degrees from -60 degrees as \(\log_{10} x\) runs from 0 to \(\log_{10} 2\). Chebyshev spacing puts the three precision points at \(1.5 - 0.5\cos((2j - 1)\pi/6)\).

x0, x1 = 1.0, 2.0


def crank(x):
    return np.radians(40 + 60 * (x - x0) / (x1 - x0))


def rocker(x):
    return np.radians(-60 + 90 * np.log10(x) / np.log10(x1))


xs = (x0 + x1) / 2 - (x1 - x0) / 2 * np.cos((2 * np.arange(1, 4) - 1) * np.pi / 6)
K = freudenstein_coefficients(crank(xs), rocker(xs))
a, b, c, d = freudenstein_link_lengths(K, ground=0.1)
print("precision points x =", np.round(xs, 4).tolist())
print(f"K = {np.round(K, 5).tolist()}")
print(f"crank {1e3 * a:.1f} mm, coupler {1e3 * b:.1f} mm, rocker {1e3 * c:.1f} mm, ground {1e3 * d:.1f} mm")
precision points x = [1.067, 1.5, 1.933]
K = [0.48881, 0.3593, -0.14296]
crank 204.6 mm, coupler 381.6 mm, rocker 278.3 mm, ground 100.0 mm

Check the design by analysis#

Solving the synthesised linkage’s loop closure must return the prescribed output exactly at the precision points. Between them the structural error is small but not zero.

assert np.allclose(fourbar_output_angle(a, b, c, d, crank(xs)), rocker(xs), atol=1e-10)
x = np.linspace(x0, x1, 401)
error = np.degrees(fourbar_output_angle(a, b, c, d, crank(x)) - rocker(x))
print(f"largest structural error {np.max(np.abs(error)):.3f} deg over a 90 deg output range")
assert np.max(np.abs(error)) < 0.05

fig, axes = plt.subplots(1, 2, figsize=(10, 3.8))
axes[0].plot(x, error)
axes[0].plot(xs, 0 * xs, "o", label="precision points")
axes[0].set(xlabel="x", ylabel="output error [deg]", title="Structural error")
axes[0].legend()
for xi in (x0, 1.5, x1):
    t2, t4 = crank(xi), fourbar_output_angle(a, b, c, d, crank(xi))
    A, B = a * np.array([np.cos(t2), np.sin(t2)]), np.array([d + c * np.cos(t4), c * np.sin(t4)])
    axes[1].plot([0, A[0], B[0], d], [0, A[1], B[1], 0], "o-", label=f"x = {xi}")
axes[1].set_aspect("equal")
axes[1].set(xlabel="x [m]", ylabel="y [m]", title="Log-function generator")
axes[1].legend(fontsize=8)
fig.tight_layout()

plt.show()
Structural error, Log-function generator
largest structural error 0.041 deg over a 90 deg output range

Total running time of the script: (0 minutes 0.047 seconds)

Gallery generated by Sphinx-Gallery