import sympy as sp
from mechanicskit import la
F_A, F_B, m, g = sp.symbols('F_A F_B m g', positive=True)
def ee(theta_deg):
"""Unit direction vector from angle in degrees."""
theta = sp.rad(theta_deg)
return sp.Matrix([sp.cos(theta), sp.sin(theta), 0])
FF_A = F_A * ee(15)
FF_B = F_B * ee(120)
FF_G = sp.Matrix([0, -m*g, 0])5.3 Static Equilibrium
Newton’s second law states that the resultant force on a body equals its mass times acceleration, \(\sum \bm F = m\bm a\). Euler’s second law gives the rotational counterpart, \(\sum \bm M = I\bm \alpha\). In statics we study bodies at rest or in uniform motion, so \(\bm a = 0\) and \(\bm \alpha = 0\). The governing equations reduce to
\[ \sum \bm F = 0, \quad \sum \bm M = 0 \tag{5.3.1}\]
Newton’s third law tells us that every force has an equal and opposite reaction. This is the reason we isolate bodies and draw free body diagrams: once we cut a body free from its surroundings, every contact and support becomes a force that we can account for.
That is all the theory there is. The rest is methodology and practice. The only path to understanding statics is by working examples.
Solution strategy
- What is known and unknown?
- Free body diagram
- Insert coordinate system
- Identify a suitable point for a moment equation
- \(\sum \bm M = 0\)
- \(\sum \bm F = 0\)
- Solve the equation system for the unknowns
- Analyze the results, plot, visualize
- Conclusion
Static determinacy
Before solving, always count unknowns and equations. In 2D a rigid body gives 3 independent equilibrium equations (2 force, 1 moment). In 3D it gives 6 (3 force, 3 moment). Each support contributes a certain number of unknown reaction components depending on its type (see the Free Body Diagram chapter).
If the number of unknowns equals the number of independent equations the system is statically determinate and can be solved by equilibrium alone. If there are more unknowns than equations the system is statically indeterminate and requires additional information about the deformation and material properties of the bodies. We will return to this in the Solid Mechanics part.
A multi-body system with \(n\) bodies gives \(3n\) equations in 2D (\(6n\) in 3D). The unknowns include both external reactions and internal joint forces at connections between bodies. The choice of support types is a modeling decision that directly affects whether the problem is determinate.
Example 1 – Sphere on smooth surfaces
A sphere with mass \(m\) rests against two smooth surfaces as shown below. We want to determine the normal forces at \(A\) and \(B\).

We insert a coordinate system at the center of the sphere and draw the free body diagram. Since the surfaces are smooth they can only exert normal forces. All forces pass through the center, so force equilibrium alone is sufficient.

The contact surface at \(A\) has a normal direction at \(180° - 75° - 90° = 15°\) from the positive \(x\)-axis, and the surface at \(B\) has a normal at \(30° + 90° = 120°\). We define a unit direction vector \(\bm e(\theta) = [\cos\theta, \sin\theta, 0]^T\) and express each force as a magnitude times its direction.
eqn = sp.Eq(FF_A + FF_B + FF_G, sp.Matrix([0, 0, 0]))
sol = sp.solve(eqn, [F_A, F_B])
sol | la\[ \begin{aligned} F_{A} &= \frac{\sqrt{2} g m}{1 + \sqrt{3}} \\ F_{B} &= g m \end{aligned} \]
Evaluating numerically, \(F_A \approx 0.52\,mg\) and \(F_B \approx 1.0\,mg\). The force at \(B\) carries nearly the full weight while \(A\) takes about half. This makes sense from the geometry: if we rotated the system \(30°\) clockwise, \(B\) would point straight up and carry all the weight, while \(A\) would vanish.
Example 2 – Wire tensions as a function of geometry
A 100 N weight hangs from a point \(R\) that is connected by wires to supports at \(A\) and \(B\). The distance \(d\) below the support line is a free parameter. We want to determine how the wire tensions vary with \(d\).

In contrast to the previous example where forces had known directions given by angles, here the forces act along lines between known points. We define position vectors for each point and compute the wire directions from the geometry.

d = sp.Symbol('d', positive=True)
F_A, F_B = sp.symbols('F_A F_B', positive=True)
# Points
rr_A = sp.Matrix([-10, 0, 0])
rr_B = sp.Matrix([ 20, 0, 0])
rr_R = sp.Matrix([ 0,-d, 0])
# Direction vectors from R toward supports
RA = rr_A - rr_R
RB = rr_B - rr_R
# Force vectors: magnitude times unit direction
FF_A = F_A * RA / RA.norm()
FF_B = F_B * RB / RB.norm()
FF_G = sp.Matrix([0, -100, 0])eqn = sp.Eq(FF_A + FF_B + FF_G, sp.Matrix([0, 0, 0]))
sol = sp.solve(eqn, [F_A, F_B])
sol | la\[ \begin{aligned} F_{A} &= \frac{200 \sqrt{d^{2} + 100}}{3 d} \\ F_{B} &= \frac{100 \sqrt{d^{2} + 400}}{3 d} \end{aligned} \]
The solution is symbolic in \(d\). We can visualize how the tensions change as the hanging point drops.
Code
import matplotlib.pyplot as plt
from mechanicskit import fplot
plt.style.use('seaborn-v0_8-whitegrid')
fig, ax = plt.subplots()
fplot(sol[F_A], (d, 1, 30), ax=ax, label='$F_A$')
fplot(sol[F_B], (d, 1, 30), ax=ax, label='$F_B$')
ax.set(xlabel='d [m]', ylabel='Force [N]')
ax.legend()
plt.show()
As \(d\) increases the wires become more vertical and the tensions approach reasonable values. As \(d \to 0\) the wires become nearly horizontal and the tensions blow up: a perfectly horizontal wire cannot support a vertical load. This is a fundamental insight that appears repeatedly in engineering.
Example 3 – Board carried by three people
A rectangular board with mass \(m\) is carried horizontally by three people. Each person can only exert a force in the positive \(z\)-direction. We want to determine how much each person lifts.

This is a three-dimensional problem. We place the origin at the lower-left corner and define position vectors for each person and for the center of gravity. Now both force and moment equilibrium are needed: three unknowns (\(F_{Az}\), \(F_{Bz}\), \(F_{Cz}\)) require three equations.

F_Az, F_Bz, F_Cz, mg = sp.symbols('F_Az F_Bz F_Cz mg', positive=True)
# Positions [mm]
rr_A = sp.Matrix([360, 0, 0]) / 1000 # convert to [m]
rr_B = sp.Matrix([720, 480+720, 0]) / 1000
rr_C = sp.Matrix([720+1680, 480, 0]) / 1000
rr_G = sp.Matrix([720+1680, 480+720, 0]) / 2000
# Forces (z-direction only)
FF_A = sp.Matrix([0, 0, F_Az])
FF_B = sp.Matrix([0, 0, F_Bz])
FF_C = sp.Matrix([0, 0, F_Cz])
FF_G = sp.Matrix([0, 0, -mg])# Force equilibrium
N2 = sp.Eq(FF_A + FF_B + FF_C + FF_G, sp.Matrix([0, 0, 0]))
# Moment equilibrium about the origin
E2 = sp.Eq(
rr_A.cross(FF_A) + rr_B.cross(FF_B) + rr_C.cross(FF_C) + rr_G.cross(FF_G),
sp.Matrix([0, 0, 0])
)sol = sp.solve([N2, E2], [F_Az, F_Bz, F_Cz])
sol | la\[ \begin{aligned} F_{Az} &= \frac{23 mg}{79} \\ F_{Bz} &= \frac{57 mg}{158} \\ F_{Cz} &= \frac{55 mg}{158} \end{aligned} \]
The solution expresses each lifting force as a fraction of the total weight \(mg\). The person who lifts the least is the one closest to the center of gravity. The method is identical to the 2D case: define points, define forces, write \(\sum \bm F = 0\) and \(\sum \bm M = 0\), solve. The cross product in the moment equation handles the three-dimensional geometry automatically.
Example 4 – Static determinacy
We take the same beam loaded by a single force \(\bm F\) and change only the supports. The beam has length \(L\) and the force acts at \(L/2\). We set up the equilibrium equations for each case and observe what SymPy returns.
Case 1: Determinate (pin + roller)

A pin at \(A\) gives two unknowns (\(R_{Ax}\), \(R_{Ay}\)) and a roller at \(B\) gives one (\(R_{By}\)). That is 3 unknowns and 3 equations.
from mechanicskit import ltx
L, F = sp.symbols('L F', positive=True)
# Case 1: pin at A (2 unknowns) + roller at B (1 unknown)
R_Ax, R_Ay, R_By = sp.symbols('R_Ax R_Ay R_By', real=True)
rr_A = sp.Matrix([0, 0, 0])
rr_B = sp.Matrix([L, 0, 0])
rr_F = sp.Matrix([L/2, 0, 0])
RR_A = sp.Matrix([R_Ax, R_Ay, 0])
RR_B = sp.Matrix([0, R_By, 0])
FF = sp.Matrix([0, -F, 0])
sumF = RR_A + RR_B + FF
sumM = rr_B.cross(RR_B) + rr_F.cross(FF)\[ \sum \bm F = \left[\begin{matrix}R_{Ax}\\- F + R_{Ay} + R_{By}\\0\end{matrix}\right] = \bm 0 \]
\[ \sum \bm M = \left[\begin{matrix}0\\0\\- \frac{F L}{2} + L R_{By}\end{matrix}\right] = \bm 0 \]
sol1 = sp.solve([sumF, sumM], [R_Ax, R_Ay, R_By])
sol1 | la\[ \begin{aligned} R_{Ax} &= 0 \\ R_{Ay} &= \frac{F}{2} \\ R_{By} &= \frac{F}{2} \end{aligned} \]
A unique solution: \(R_{Ax} = 0\), \(R_{Ay} = F/2\), \(R_{By} = F/2\). By symmetry both supports carry half the load. The beam is statically determinate.
Case 2: Indeterminate (pin + pin)

Both \(A\) and \(B\) are pins, giving 4 unknowns (\(R_{Ax}\), \(R_{Ay}\), \(R_{Bx}\), \(R_{By}\)) but still only 3 equations.
# Case 2: pin at A (2 unknowns) + pin at B (2 unknowns)
R_Ax, R_Ay, R_Bx, R_By = sp.symbols('R_Ax R_Ay R_Bx R_By', real=True)
RR_A = sp.Matrix([R_Ax, R_Ay, 0])
RR_B = sp.Matrix([R_Bx, R_By, 0])
sumF = RR_A + RR_B + FF
sumM = rr_B.cross(RR_B) + rr_F.cross(FF)\[ \sum \bm F = \left[\begin{matrix}R_{Ax} + R_{Bx}\\- F + R_{Ay} + R_{By}\\0\end{matrix}\right] = \bm 0 \]
\[ \sum \bm M = \left[\begin{matrix}0\\0\\- \frac{F L}{2} + L R_{By}\end{matrix}\right] = \bm 0 \]
sol2 = sp.solve([sumF, sumM], [R_Ax, R_Ay, R_Bx, R_By])
sol2 | la\[ \begin{aligned} R_{Ax} &= - R_{Bx} \\ R_{Ay} &= \frac{F}{2} \\ R_{By} &= \frac{F}{2} \end{aligned} \]
SymPy returns the solution in terms of \(R_{Bx}\) – a free parameter. There are infinitely many solutions because equilibrium alone cannot determine how the horizontal reaction distributes between the two pins. This system is statically indeterminate and we would need to consider the deformation of the beam to find \(R_{Bx}\).
Case 3: Under-constrained (roller + roller)

Both supports are rollers, giving only 2 unknowns (\(R_{Ay}\), \(R_{By}\)) for 3 equations.
# Case 3: roller at A (1 unknown) + roller at B (1 unknown)
R_Ay, R_By = sp.symbols('R_Ay R_By', real=True)
F_x = sp.Symbol('F_x', positive=True)
RR_A = sp.Matrix([0, R_Ay, 0])
RR_B = sp.Matrix([0, R_By, 0])
FF_3 = sp.Matrix([F_x, -F, 0]) # force with horizontal component
sumF = RR_A + RR_B + FF_3
sumM = rr_B.cross(RR_B) + rr_F.cross(FF_3)\[ \sum \bm F = \left[\begin{matrix}F_{x}\\- F + R_{Ay} + R_{By}\\0\end{matrix}\right] = \bm 0 \]
\[ \sum \bm M = \left[\begin{matrix}0\\0\\- \frac{F L}{2} + L R_{By}\end{matrix}\right] = \bm 0 \]
sol3 = sp.solve([sumF, sumM], [R_Ay, R_By])
sol3 | la\[ \begin{aligned} R_{Ay} &= \frac{F}{2} \\ R_{By} &= \frac{F}{2} \end{aligned} \]
sol3 = sp.solve([N2, E2], [R_Ay, R_By])
sol3[]
SymPy returns an empty list – no solution exists. The \(x\)-equation demands \(F_x = 0\) but we applied a nonzero horizontal force. Two rollers cannot resist any horizontal load, so the beam would slide. This system is under-constrained: it is a mechanism, not a structure.
Note that with a purely vertical force the two-roller case does give a unique answer for the vertical reactions. The deficiency only becomes apparent when a horizontal load is present. This is why we must always check that the supports can resist loads in all directions that might arise, not just the ones in the current problem.
Remarks on determinacy
The counting of unknowns against equations is one of the most important modeling skills in mechanics. It determines whether a problem can be solved by equilibrium alone or whether we need additional physical laws (deformation, constitutive relations). In this course we restrict ourselves to statically determinate problems. The indeterminate case requires knowledge of how materials deform under load, which we treat in the Solid Mechanics part. Any course on the Finite Element Method (FEM/FEA) builds directly on this foundation: the concept of degrees of freedom, constraints, and how they relate to the solvability of a system is exactly the same, scaled up to thousands of elements.
For a multi-body example where the choice of support type determines whether the system is determinate, see Example 3 in the Equilibrium Examples chapter.