import sympy as sp
from mechanicskit import la
a = 4 # [m] Distance ED
b = 3 # [m] Distance AE
F_B = 4000 # [N] Force magnitude at B
F_C = 200 # [N] Force magnitude at C
theta = sp.Symbol('theta', real=True) # [rad] Angle at B
R_Ax, R_Ay, R_Ex = sp.symbols('R_Ax R_Ay R_Ex', real=True)5.5 Equilibrium Examples
Example 1 – Beam with variable force direction
We want to determine how all reaction forces vary as the direction of the 4000 N force changes.

We isolate the beam and draw the free body diagram. The pin at \(A\) gives two reaction components (\(R_{Ax}\), \(R_{Ay}\)) and the roller at \(E\) gives one horizontal component (\(R_{Ex}\)). The 4000 N force at \(B\) acts at angle \(\theta\) and the 200 N force at \(C\) acts vertically. We take moments about \(A\).

Three unknowns and three independent equations (two force, one moment) so the system is solvable.

\[ \sum \bm F = 0: \quad \bm R_A + \bm F_B + \bm R_E + \bm F_C = \bm 0 \]
\[ \sum \bm M_A = 0: \quad \bm r_{AB} \times \bm F_B + \bm r_{AC} \times \bm F_C + \bm r_{AE} \times \bm R_E = \bm 0 \]
RR_A = sp.Matrix([R_Ax, R_Ay, 0])
FF_B = F_B * sp.Matrix([sp.cos(theta), sp.sin(theta), 0])
RR_E = sp.Matrix([R_Ex, 0, 0])
FF_C = F_C * sp.Matrix([0, 1, 0])
rr_AB = a * sp.Matrix([1, 0, 0])
rr_AC = sp.Matrix([2*a, -b, 0])
rr_AE = b * sp.Matrix([0, -1, 0])N2 = sp.Eq(RR_A + FF_B + RR_E + FF_C, sp.Matrix([0, 0, 0]))
E2 = sp.Eq(rr_AB.cross(FF_B) + rr_AC.cross(FF_C) + rr_AE.cross(RR_E), sp.Matrix([0, 0, 0]))
sol = sp.solve([N2, E2], [R_Ax, R_Ay, R_Ex])
sol | la\[ \begin{aligned} R_{Ax} &= \frac{16000 \sin{\left(\theta \right)}}{3} - 4000 \cos{\left(\theta \right)} + \frac{1600}{3} \\ R_{Ay} &= - 4000 \sin{\left(\theta \right)} - 200 \\ R_{Ex} &= - \frac{16000 \sin{\left(\theta \right)}}{3} - \frac{1600}{3} \end{aligned} \]
Code
import matplotlib.pyplot as plt
from mechanicskit import fplot
import numpy as np
plt.style.use('seaborn-v0_8-whitegrid')
fig, ax = plt.subplots(figsize=(8, 5))
fplot(sol[R_Ax], (theta, 0, 2*sp.pi), ax=ax, label='$R_{Ax}$', color='red')
fplot(sol[R_Ay], (theta, 0, 2*sp.pi), ax=ax, label='$R_{Ay}$', color='green')
fplot(sol[R_Ex], (theta, 0, 2*sp.pi), ax=ax, label='$R_{Ex}$', color='blue')
ax.set(xlabel=r'Angle $\theta$ [rad]', ylabel='Reaction force [N]')
ax.legend()
plt.show()
Example 2 – Beam with wire at variable position
A beam with mass \(m = 475\) kg is supported at \(A\) by a pin and by a wire connected at \(B\). The wire attachment point \(B\) can slide along the beam. A 10 kN force acts downward at \(C\). We want to determine the reaction forces as a function of the wire position \(r_{OBx}\).

The free body diagram and vector definitions are shown below. We take moments about \(O\) (the left end of the beam). The wire direction \(\bm e_{BD}\) is computed from the geometry, and the wire force is \(\bm F_B = F_B \bm e_{BD}\).


\[ \sum \bm F = 0: \quad \bm R_A + \bm W + \bm F_C + \bm F_B = \bm 0 \]
\[ \sum \bm M_O = 0: \quad \bm r_{OA} \times \bm R_A + \bm r_{OG} \times \bm W + \bm r_{OC} \times \bm F_C + \bm r_{OB} \times \bm F_B = \bm 0 \]
g = 9.81 # [m/s^2]
m = 475 # [kg]
r_OBx = sp.Symbol('r_OBx', positive=True)
R_Ax, R_Ay, F_B = sp.symbols('R_Ax R_Ay F_B', real=True)
rr_OA = sp.Matrix([0.12, 0, 0])
rr_OG = sp.Matrix([2.5, 0, 0])
rr_OC = sp.Matrix([3.5, -0.25, 0])
rr_OB = sp.Matrix([r_OBx, 0.25, 0])
r_ODy = sp.tan(sp.rad(25)) * 5
rr_OD = sp.Matrix([0, r_ODy, 0])# Wire direction from B toward D
ee_BD = (rr_OD - rr_OB).normalized()
RR_A = sp.Matrix([R_Ax, R_Ay, 0])
WW = m * g * sp.Matrix([0, -1, 0])
FF_C = sp.Matrix([0, -10000, 0])
FF_B = F_B * ee_BDsumF = sp.Eq(RR_A + WW + FF_C + FF_B, sp.Matrix([0, 0, 0]))
sumM = sp.Eq(
rr_OA.cross(RR_A) + rr_OG.cross(WW) + rr_OC.cross(FF_C) + rr_OB.cross(FF_B),
sp.Matrix([0, 0, 0])
)
sol = sp.solve([sumF, sumM], [R_Ax, R_Ay, F_B])
sol | la\[ \begin{aligned} F_{B} &= \frac{8978041.0 \sqrt{16.0 r_{OBx}^{2} + 69.3248264953997}}{1865.23063261999 r_{OBx} - 199.827675914399} \\ R_{Ax} &= \frac{8978041.0 r_{OBx}}{466.307658154999 r_{OBx} - 49.9569189785998} \\ R_{Ay} &= \frac{27343814.766551 r_{OBx}}{1865.23063261999 r_{OBx} - 199.827675914399} - \frac{77681968.2425773}{1865.23063261999 r_{OBx} - 199.827675914399} \end{aligned} \]
Code
plt.style.use('seaborn-v0_8-whitegrid')
fig, ax = plt.subplots(figsize=(8, 5))
fplot(sol[F_B], (r_OBx, 0.5, 5), ax=ax, label='$F_B$', color='black')
fplot(sol[R_Ax], (r_OBx, 0.5, 5), ax=ax, label='$R_{Ax}$', color='blue')
fplot(sol[R_Ay], (r_OBx, 0.5, 5), ax=ax, label='$R_{Ay}$', color='red')
ax.set(xlabel='Placement of B [m]', ylabel='Force [N]')
ax.legend()
plt.show()
When the wire attachment approaches \(A\) the forces blow up because \(\bm F_B\)’s line of action passes through the support, making the moment arm vanish. The wire force \(F_B\) becomes negative when the line of action intersects between \(O\) and \(A\), which is not physical for a wire (wires can only pull).
Example 3 – Multi-body structure
The structure is supported at \(A\), \(B\) and \(C\) from the floor. A person with mass \(m = 80\) kg stands at point \(D\).

We want to determine the reaction forces at \(A\), \(B\), \(C\) and the joint forces at \(D\), \(E\), \(G\).
By symmetry, points \(D\) and \(G\) are centered between \(A\) and \(B\), so the reactions at \(A\) and \(B\) are equal. We can reduce this to a 2D problem where the computed reaction represents the sum of \(A\) and \(B\).

We need three free body diagrams: body ODG, body CEG, and body DE. Newton’s third law ensures that the joint forces at connections are equal and opposite between bodies.
Before writing equations we must decide what type of support acts at each floor contact. The problem figure does not specify this, so we have to make a modeling choice (see Static determinacy for background). If all three supports are pins (rough surfaces), we get \(2 + 2 + 2 = 6\) reaction components from the floor but only \(3 \times 3 = 9\) equations for \(6 + 6 = 12\) unknowns (6 floor reactions + 6 joint forces at D, E, G). That is 12 unknowns and 9 equations, the system is statically indeterminate.
To make the system determinate we model \(C\) as a roller (smooth surface, only a normal force in the \(y\)-direction). Now \(O\) contributes 2 unknowns and \(C\) contributes 1, giving \(2 + 1 + 6 = 9\) unknowns and 9 equations. This is the minimum number of supports that still prevents the structure from moving, and it makes the problem solvable with equilibrium alone.




Body 1 (ODG):
\[ \sum \bm F = 0: \quad \bm R_O + \bm R_D + \bm F + \bm R_G = \bm 0 \]
\[ \sum \bm M_O = 0: \quad \bm r_{OD} \times \bm R_D + \bm r_{OD} \times \bm F + \bm r_{OG} \times \bm R_G = \bm 0 \]
Body 2 (CEG):
\[ \sum \bm F = 0: \quad \bm R_C + \bm R_E + (-\bm R_G) = \bm 0 \]
\[ \sum \bm M_C = 0: \quad \bm r_{CE} \times \bm R_E + \bm r_{CG} \times (-\bm R_G) = \bm 0 \]
Body 3 (DE):
\[ \sum \bm F = 0: \quad (-\bm R_D) + (-\bm R_E) = \bm 0 \]
\[ \sum \bm M_D = 0: \quad \bm r_{DE} \times (-\bm R_E) = \bm 0 \]
theta = sp.rad(65)
m = 80 # [kg]
g = 9.82 # [m/s^2]
rr_OD = sp.Matrix([480/sp.tan(theta), 480, 0])
rr_OG = sp.Matrix([905/sp.tan(theta), 905, 0])
rr_CE = sp.Matrix([-480/sp.tan(theta), 480, 0])
rr_CG = sp.Matrix([-905/sp.tan(theta), 905, 0])
rr_OE = rr_OG - (rr_CG - rr_CE)
rr_DE = rr_OE - rr_ODR_Ox, R_Oy, R_Dx, R_Dy, R_Gx, R_Gy, R_Ex, R_Ey, R_Cy = sp.symbols(
'R_Ox R_Oy R_Dx R_Dy R_Gx R_Gy R_Ex R_Ey R_Cy', real=True)
RR_O = sp.Matrix([R_Ox, R_Oy, 0])
RR_D = sp.Matrix([R_Dx, R_Dy, 0])
RR_G = sp.Matrix([R_Gx, R_Gy, 0])
RR_E = sp.Matrix([R_Ex, R_Ey, 0])
RR_C = sp.Matrix([0, R_Cy, 0])
FF = sp.Matrix([0, -m*g, 0])# Body 1: ODG
sumF1 = sp.Eq(RR_O + RR_D + FF + RR_G, sp.Matrix([0, 0, 0]))
sumM1 = sp.Eq(rr_OD.cross(RR_D) + rr_OD.cross(FF) + rr_OG.cross(RR_G), sp.Matrix([0, 0, 0]))
# Body 2: CEG
sumF2 = sp.Eq(RR_C + RR_E + (-RR_G), sp.Matrix([0, 0, 0]))
sumM2 = sp.Eq(rr_CE.cross(RR_E) + rr_CG.cross(-RR_G), sp.Matrix([0, 0, 0]))
# Body 3: DE
sumF3 = sp.Eq((-RR_D) + (-RR_E), sp.Matrix([0, 0, 0]))
sumM3 = sp.Eq(rr_DE.cross(-RR_E), sp.Matrix([0, 0, 0]))sol = sp.solve(
[sumF1, sumM1, sumF2, sumM2, sumF3, sumM3],
[R_Ox, R_Oy, R_Dx, R_Dy, R_Gx, R_Gy, R_Ex, R_Ey, R_Cy]
)
sol | la\[ \begin{aligned} R_{Cy} &= 208.33591160221 \\ R_{Dx} &= 206.869437880414 \\ R_{Dy} &= 0.0 \\ R_{Ex} &= -206.869437880414 \\ R_{Ey} &= 0.0 \\ R_{Gx} &= -206.869437880414 \\ R_{Gy} &= 208.33591160221 \\ R_{Ox} &= 0.0 \\ R_{Oy} &= 577.26408839779 \end{aligned} \]
The reaction forces from the floor (\(R_{Oy}\) and \(R_{Cy}\)) are positive, confirming they point upward as expected. Since there is no external horizontal force we get \(R_{Ox} = 0\). The negative \(R_{Ex}\) and positive \(R_{Dx}\) tell us that body DE is in tension.
Example 4 – Tractor hoisting a bale
A tractor at \(A\) lifts a bale \(B\) through a rope-and-pulley arrangement. The rope runs from the tractor hitch up to a fixed pulley at the top of a post at \(C\), down around a movable pulley attached to the bale, and back up to an anchor next to the pulley at \(C\). The post height is \(h = 5\) m and the bale mass is \(m = 500\) kg. Initially the tractor is directly beneath the pulley (\(x_0 = 0\)) and the bale rests on the ground (\(y_0 = 0\)).

We want
- the distance \(\Delta x\) the tractor must travel to the right to raise the bale to \(y_1 = 4\) m, and
- the horizontal force the tractor must produce, treating the bale as being in quasi-static equilibrium.
Rope-length constraint
Three rope segments make up the total length: the inclined piece from the tractor hitch up to the fixed pulley at \(C\), and the two vertical pieces supporting the movable pulley on the bale,
\[ L(x, y) = \sqrt{x^2 + h^2} \,+\, 2\,(h - y) \]
Since the rope is inextensible \(L\) is the same at every instant of the lift, which ties the tractor’s horizontal position \(x\) to the bale’s height \(y\),
\[ \sqrt{x_0^2 + h^2} + 2(h - y_0) \;=\; \sqrt{x_1^2 + h^2} + 2(h - y_1) \]
Solving for \(x_1\) gives the tractor’s position when the bale has reached \(y_1\), and the required travel is \(\Delta x = x_1 - x_0\).
from mechanicskit import ltx, fplot
import sympy as sp
h = 5 # [m] post height
m = 500 # [kg] bale
g = sp.Rational(981, 100) # [m/s^2]
x0, y0 = 0, 0 # initial tractor and bale positions
y_1 = 4 # [m] target bale height
L = sp.sqrt(x0**2 + h**2) + 2 * (h - y0)
ltx(r"L = \sqrt{x_0^2 + h^2} + 2 (h - y_0) =", L, r"\text{ m}")\[ L = \sqrt{x_0^2 + h^2} + 2 (h - y_0) =15\text{ m} \]
x_1 = sp.Symbol('x_1', positive=True)
eq = sp.Eq(sp.sqrt(x_1**2 + h**2) + 2 * (h - y_1), L)
eq\(\displaystyle \sqrt{x_{1}^{2} + 25} + 2 = 15\)
x_1_val = sp.solve(eq, x_1)[0]
dx = x_1_val - x0
ltx(r"L =", L, r"\text{ m},\quad x_1 =", x_1_val, r"\text{ m},\quad \Delta x =", dx, r"\text{ m}")\[ L =15\text{ m},\quad x_1 =12\text{ m},\quad \Delta x =12\text{ m} \]
Force balance on the bale and tractor
The movable pulley distributes the bale’s weight between two rope segments, both carrying the same tension \(T\). Treating the bale as a particle in equilibrium,
\[ 2 T - m g = 0 \quad \Rightarrow \quad T = \frac{m g}{2} \]
Tension is constant along the whole rope, so the tractor hitch feels the same \(T\) directed up toward the pulley at \(C\). At tractor position \(x\) this direction makes angle \(\theta\) with the horizontal, \(\tan\theta = h/x\), and only the horizontal component has to be produced at the drive wheels,
\[ F(x) = T \cos\theta = \frac{m g}{2}\,\frac{x}{\sqrt{x^2 + h^2}} \]
The vertical component \(T\sin\theta\) lifts the hitch and is borne by the tractor’s own weight.
x = sp.Symbol('x', positive=True)
T = sp.Rational(m) * g / 2
F = T * x / sp.sqrt(x**2 + h**2)
F_1 = F.subs(x, x_1_val)
ltx(r"T =", T, r"\text{ N},\qquad F(x_1) =", F_1.evalf(5), r"\text{ N}")\[ T =\dfrac{4905}{2}\text{ N},\qquad F(x_1) =2263.8\text{ N} \]
Code
from matplotlib import pyplot as plt
fig, ax = plt.subplots(figsize=(7, 4))
fplot(F, (x, 0, 15), ax=ax, label=r'$F(x)$', color='tab:blue')
ax.axvline(float(x_1_val), color='k', linestyle='--', alpha=0.4,
label=fr'$x_1 = {float(x_1_val):.0f}$ m')
ax.axhline(float(F_1), color='k', linestyle=':', alpha=0.4)
ax.set(xlabel=r'Tractor position $x$ [m]', ylabel=r'Tractor force $F$ [N]')
ax.legend(); plt.show()
At the start, with the tractor directly beneath the pulley, the rope hangs vertically and the horizontal pull is zero even though the hitch already carries the full tension \(T = mg/2\) upward. As the tractor rolls away from the post the rope flattens, its horizontal component grows, and the tractor must produce its largest force at the end of the lift. The movable pulley halves the rope tension, so the tractor never has to pull with more than \(\tfrac{1}{2} m g x_1/\sqrt{x_1^2+h^2}\).