import sympy as sp
import matplotlib.pyplot as plt
from mechanicskit import la, ltx
plt.rcParams['mathtext.fontset'] = 'cm'
plt.rcParams['font.family'] = 'serif'
F_1, F_2, F_3 = 600, 500, 8004.1 Force Vector Examples
Example 1 – Resultant of three forces

Define the force vectors \(\bm F_1\), \(\bm F_2\), and \(\bm F_3\), calculate and plot the resultant \(\bm R\).

For \(\bm F_1\) we define it with the angle \(\theta = 35°\). Its direction is \(\bm e_{F_1} = [\cos 35°,\, \sin 35°,\, 0]^\mathsf T\) so \(\bm F_1 = F_1 \bm e_{F_1}\).
ee_F1 = sp.Matrix([sp.cos(sp.rad(35)), sp.sin(sp.rad(35)), 0])
FF_1 = F_1 * ee_F1For \(\bm F_2\) we avoid introducing an angle. Instead we read the slope \([-4, 3]\) from the figure and normalize it: \(\bm e_{F_2} = \bm v / ||\bm v||\).
ee_F2 = sp.Matrix([-4, 3, 0]).normalized()
FF_2 = F_2 * ee_F2For \(\bm F_3\) we define it by the line from \(A\) to \(B\): \(\bm e_{F_3} = (\bm r_{OB} - \bm r_{OA}) / ||\bm r_{OB} - \bm r_{OA}||\).
rr_OA = sp.Matrix([0.2, 0.1, 0])
rr_OB = sp.Matrix([0.4, -0.3, 0])
rr_AB = rr_OB - rr_OA
ee_F3 = rr_AB.normalized()
FF_3 = F_3 * ee_F3RR = FF_1 + FF_2 + FF_3
ltx(r"\boldsymbol R = ", RR, r",\quad R = ", RR.norm().evalf(), r"\text{ N}")\[ \boldsymbol R = \left[\begin{matrix}-42.2291236000336 + 600 \cos{\left(\frac{7 \pi}{36} \right)}\\-415.541752799933 + 600 \sin{\left(\frac{7 \pi}{36} \right)}\\0\end{matrix}\right],\quad R = 454.899780631079\text{ N} \]
Code
import numpy as np
import mechanicskit as mk
plt.style.use('seaborn-v0_8-whitegrid')
fig, ax = plt.subplots(figsize=(6, 6))
ax.set(xlim=[-0.2, 0.8], ylim=[-0.5, 0.5], xlabel='X [m]', ylabel='Y [m]')
# Points
ax.plot(0, 0, 'ok', ms=5); ax.text(0.01, 0.01, 'O')
ax.plot(0.2, 0.1, 'ok', ms=5); ax.text(0.21, 0.11, 'A')
ax.plot(0.4, -0.3, 'ok', ms=5); ax.text(0.41, -0.29, 'B')
ax.plot([0.2, 0.4], [0.1, -0.3], '--k', zorder=0)
# Plot direction vectors scaled by relative magnitude
A = [0.2, 0.1]
s = 0.2
R = float(RR.norm())
mk.arrow(A, ee_F1, scale=s*F_1/F_3, color='orange', label=r'$\boldsymbol{e}_{F_1}$', ax=ax)
mk.arrow(A, ee_F2, scale=s*F_2/F_3, color='blue', label=r'$\boldsymbol{e}_{F_2}$', ax=ax)
mk.arrow(A, ee_F3, scale=s*F_3/F_3, color='red', label=r'$\boldsymbol{e}_{F_3}$', ax=ax)
mk.arrow(A, RR.normalized(), scale=s*R/F_3, color='green', label=r'$\boldsymbol{e}_R$', ax=ax)
ax.legend()
plt.show()
Example 2 – Unknown spring force

A force \(\bm F\) of 500 N acts at \(60°\) below the negative \(x\)-axis. A spring force \(\bm F_s\) acts along the positive \(x\)-axis with unknown magnitude. We want to find \(F_s\) so that the resultant \(\bm R\) is purely vertical.
F_s, R = sp.symbols('F_s R', real=True)
FF = 500 * sp.Matrix([-sp.cos(sp.rad(60)), -sp.sin(sp.rad(60)), 0])
FF_s = F_s * sp.Matrix([1, 0, 0])
RR = R * sp.Matrix([0, -1, 0])
sol = sp.solve(sp.Eq(FF + FF_s, RR, evaluate=False), [R, F_s])
sol | la\[ \begin{aligned} F_{s} &= 250 \\ R &= 250 \sqrt{3} \end{aligned} \]
Code
plt.style.use('seaborn-v0_8-whitegrid')
fig, ax = plt.subplots(figsize=(6, 6))
ax.set(xlim=[-0.5, 0.5], ylim=[-0.5, 0.5], xlabel='X [m]', ylabel='Y [m]')
O = [0, 0]
s = 0.3
F_val = 500
F_s_val = float(sol[F_s])
R_val = float(sol[R])
mk.arrow(O, FF.normalized(), scale=s, color='orange', label=r'$\boldsymbol{e}_F$', ax=ax)
mk.arrow(O, sp.Matrix([1, 0, 0]), scale=s*F_s_val/F_val, color='blue', label=r'$\boldsymbol{e}_{F_s}$', ax=ax)
mk.arrow(O, sp.Matrix([0, -1, 0]), scale=s*R_val/F_val, color='green', label=r'$\boldsymbol{e}_R$', ax=ax)
ax.legend()
plt.show()
Example 3 – Rotated coordinate system

Two forces are defined in a rotated \(xy\)-base (tilted \(20°\)). We compute the resultant and transform it back to the standard \(x'y'\)-base.
theta_1 = sp.rad(20)
ee_x = sp.Matrix([ sp.cos(theta_1), -sp.sin(theta_1), 0])
ee_y = sp.Matrix([ sp.sin(theta_1), sp.cos(theta_1), 0])
ee_z = sp.Matrix([0, 0, 1])theta_2 = sp.rad(35)
theta_3 = sp.rad(30)
ee_F1 = ee_x*sp.cos(theta_2) + ee_y*sp.sin(theta_2)
FF_1 = 200 * ee_F1
ee_F2 = ee_x*(-sp.sin(theta_3)) + ee_y*sp.cos(theta_3)
FF_2 = 150 * ee_F2
RR = sp.simplify(FF_1 + FF_2)The resultant \(\bm R\) is expressed in the \(x'y'\)-base. To get its components in the \(xy\)-base we build the transformation matrix \(\bm A = [\bm e_x \;\; \bm e_y \;\; \bm e_z]\) and compute \(\bm R_{xy} = \bm A^{-1} \bm R\), or rather use the transform of \(\bm A\), \(\bm R_{xy} = \bm A^{T} \bm R\). The transpose works because \(\bm A\) is an orthogonal matrix (its columns are orthonormal basis vectors), so \(\bm A^{-1} = \bm A^T\).
Writing \(\bm A^{-1} \bm b\) is fine on paper but computing it with A**-1 @ b or np.linalg.inv(A) @ b is both slow and numerically unstable. Always solve the system \(\bm A \bm x = \bm b\) instead, using np.linalg.solve(A, b) or in this case simply A.T @ b since \(\bm A\) is orthogonal. This distinction becomes critical in larger systems (FEM, optimization) where explicit inversion can produce garbage results.
A = sp.Matrix.hstack(ee_x, ee_y, ee_z)
RR_xy = sp.simplify(A.T @ RR)
ltx(r"\bm R_{x'y'} = ", RR, r",\quad \bm R_{xy} = ", RR_xy)\[ \bm R_{x'y'} = \left[\begin{matrix}- 150 \sin{\left(\frac{\pi}{18} \right)} + 50 \sqrt{2} + 50 \sqrt{6}\\- 50 \sqrt{2} + 50 \sqrt{6} + 150 \cos{\left(\frac{\pi}{18} \right)}\\0\end{matrix}\right],\quad \bm R_{xy} = \left[\begin{matrix}-75 + 200 \sin{\left(\frac{11 \pi}{36} \right)}\\200 \sin{\left(\frac{7 \pi}{36} \right)} + 75 \sqrt{3}\\0\end{matrix}\right] \]
Code
plt.style.use('seaborn-v0_8-whitegrid')
fig, ax = plt.subplots(figsize=(6, 6))
ax.set(xlim=[-0.5, 0.5], ylim=[-0.5, 0.5], xlabel='X [m]', ylabel='Y [m]')
ax.set_title("Force vectors and resultants")
O = [0, 0]
offset = 0.05
# x'y'-base (standard axes)
ax.plot([-0.2, -0.3, -0.3], [0, 0, 0.1], linestyle='--', color='black', dashes=(10, 10), marker='.', linewidth=0.6)
ax.text(-0.3-offset, 0-offset, "x'y'-base")
# xy-base (rotated)
ee_x_np = np.array(ee_x[:2]).astype(float).flatten()
ee_y_np = np.array(ee_y[:2]).astype(float).flatten()
ax.plot([0.1*ee_x_np[0], 0, 0.1*ee_y_np[0]], [0.1*ee_x_np[1], 0, 0.1*ee_y_np[1]],
linestyle='--', color='black', dashes=(10, 10), marker='.', linewidth=0.6)
ax.text(0-offset, 0-offset, "xy-base")
# Force vectors scaled by relative magnitude
s = 0.3
F_max = 200 # F_1 = 200 N
R_val = float(RR.norm())
mk.arrow(O, ee_F1, scale=s, color='orange', label=r'$\boldsymbol{F}_1$', ax=ax)
mk.arrow(O, ee_F2, scale=s*150/F_max, color='blue', label=r'$\boldsymbol{F}_2$', ax=ax)
mk.arrow(O, RR.normalized(), scale=s*R_val/F_max, color='green', label=r'$\boldsymbol{R}$', ax=ax)
ax.legend()
plt.show()
Example 4 – Two equal forces

Two forces of magnitude 5500 N act at angles \(20°\) and \(155°\) from the vertical. Determine the resultant.
F_n = 5500
ee_F1 = sp.Matrix([sp.sin(sp.rad(20)), sp.cos(sp.rad(20)), 0])
FF_1 = F_n * ee_F1
ee_F2 = sp.Matrix([sp.cos(sp.rad(155)), sp.sin(sp.rad(155)), 0])
FF_2 = F_n * ee_F2
RR = FF_1 + FF_2
ltx(r"\boldsymbol R = ", RR, r",\quad R = ", RR.norm().evalf(), r"\text{ N}")\[ \boldsymbol R = \left[\begin{matrix}- 5500 \cos{\left(\frac{5 \pi}{36} \right)} + 5500 \sin{\left(\frac{\pi}{9} \right)}\\5500 \sin{\left(\frac{5 \pi}{36} \right)} + 5500 \cos{\left(\frac{\pi}{9} \right)}\\0\end{matrix}\right],\quad R = 8110.05070491136\text{ N} \]
Code
plt.style.use('seaborn-v0_8-whitegrid')
fig, ax = plt.subplots(figsize=(6, 6))
ax.set(xlim=[-0.5, 0.5], ylim=[-0.5, 0.5], xlabel='X', ylabel='Y')
O = [0, 0]
s = 0.3
R_val = float(RR.norm())
mk.arrow(O, ee_F1, scale=s, color='orange', label=r'$\boldsymbol{e}_{F_1}$', ax=ax)
mk.arrow(O, ee_F2, scale=s, color='blue', label=r'$\boldsymbol{e}_{F_2}$', ax=ax)
mk.arrow(O, RR.normalized(), scale=s*R_val/F_n, color='green', label=r'$\boldsymbol{e}_R$', ax=ax)
ax.legend()
plt.show()
Example 5 – Force decomposition along two arms

A 90 N force \(\bm P\) acts at \(15°\) from the vertical. We want to find the components of \(\bm P\) parallel and perpendicular to the AB-arm (\(60°\)) and the CB-arm (\(45°\)).
PP = 90 * sp.Matrix([sp.sin(sp.rad(15)), -sp.cos(sp.rad(15)), 0])
# AB-arm basis (60 deg)
ee_ABpar = sp.Matrix([ sp.cos(sp.rad(60)), sp.sin(sp.rad(60)), 0])
ee_ABperp = sp.Matrix([-sp.sin(sp.rad(60)), sp.cos(sp.rad(60)), 0])
# CB-arm basis (45 deg)
ee_CBpar = sp.Matrix([-sp.cos(sp.rad(45)), sp.sin(sp.rad(45)), 0])
ee_CBperp = sp.Matrix([ sp.sin(sp.rad(45)), sp.cos(sp.rad(45)), 0])Since all direction vectors have unit length, the dot product gives the scalar component directly: \(F_{\parallel} = \bm P \cdot \bm e_{\parallel}\).
F_ABpar = sp.simplify(PP.dot(ee_ABpar))
F_ABperp = sp.simplify(PP.dot(ee_ABperp))
F_CBpar = sp.simplify(PP.dot(ee_CBpar))
F_CBperp = sp.simplify(PP.dot(ee_CBperp))
ltx(r"F_{AB\parallel} = ", F_ABpar.evalf(4),
r"\text{ N},\quad F_{AB\perp} = ", F_ABperp.evalf(4), r"\text{ N}")\[ F_{AB\parallel} = -63.64\text{ N},\quad F_{AB\perp} = -63.64\text{ N} \]
ltx(r"F_{CB\parallel} = ", F_CBpar.evalf(4),
r"\text{ N},\quad F_{CB\perp} = ", F_CBperp.evalf(4), r"\text{ N}")\[ F_{CB\parallel} = -77.94\text{ N},\quad F_{CB\perp} = -45.0\text{ N} \]
Code
plt.style.use('seaborn-v0_8-whitegrid')
fig, ax = plt.subplots(figsize=(6, 6))
ax.set(xlim=[-0.5, 0.5], ylim=[-0.5, 0.5], xlabel='X [m]', ylabel='Y [m]')
ax.set_title("Visualization of the base vectors")
O = np.array([0, 0])
offset = 0.05
# Convert basis vectors to numpy
ee_ABpar_np = np.array(ee_ABpar[:2]).astype(float).flatten()
ee_ABperp_np = np.array(ee_ABperp[:2]).astype(float).flatten()
ee_CBpar_np = np.array(ee_CBpar[:2]).astype(float).flatten()
ee_CBperp_np = np.array(ee_CBperp[:2]).astype(float).flatten()
# xy-base (standard)
ax.plot(0, 0, 'ok', ms=5)
ax.plot([0.1, 0, 0], [0, 0, 0.1], linestyle='--', color='black', dashes=(10, 10), marker='.', linewidth=0.6)
ax.text(0.05, 0-offset, "xy-base")
# AB-base (offset to lower-left)
ab = np.array([-0.2, -0.2])
ax.plot(*zip(0.1*ee_ABpar_np + ab, ab, 0.1*ee_ABperp_np + ab),
linestyle='--', color='black', dashes=(10, 10), marker='.', linewidth=0.6)
ax.text(ab[0]-0.05, ab[1]-offset, "AB-base")
# CB-base (offset to upper-right)
cb = np.array([0.2, 0.2])
ax.plot(*zip(0.1*ee_CBpar_np + cb, cb, 0.1*ee_CBperp_np + cb),
linestyle='--', color='black', dashes=(10, 10), marker='.', linewidth=0.6)
ax.text(cb[0]+0.05, cb[1]-offset, "CB-base")
# Force P drawn at all three locations
s = 0.15
mk.arrow(O, PP.normalized(), scale=s, color='orange', label=r'$\boldsymbol{P}$', ax=ax)
mk.arrow(ab, PP.normalized(), scale=s, color='orange', ax=ax)
mk.arrow(cb, PP.normalized(), scale=s, color='orange', ax=ax)
ax.legend()
plt.show()