Stress-Strain Analysis
Understanding how materials respond to loading is fundamental to engineering design. In this lesson, we'll explore stress and strain in depth, and build Python tools to analyze material behavior.
Normal Stress
Normal stress acts perpendicular to a surface. It can be:
- Tensile stress ($\sigma > 0$): Material is being pulled apart
- Compressive stress ($\sigma < 0$): Material is being pushed together
$$\sigma = \frac{P}{A}$$
Sponsored
70% of India's auto industry trusts Skill-Lync
For training their engineers in CAD, CAE & simulation
Example: Tensile Bar Analysis
import numpy as np
def analyze_tensile_bar(force_kN, diameter_mm, length_mm, E_GPa):
"""
Analyze a circular bar under tensile loading.
Parameters:
-----------
force_kN : float - Applied force in kN
diameter_mm : float - Bar diameter in mm
length_mm : float - Bar length in mm
E_GPa : float - Young's modulus in GPa
Returns:
--------
dict with stress, strain, and elongation
"""
# Convert units to SI
P = force_kN * 1e3 # N
d = diameter_mm * 1e-3 # m
L = length_mm * 1e-3 # m
E = E_GPa * 1e9 # Pa
# Calculate area
A = np.pi * (d / 2) ** 2
# Calculate stress
sigma = P / A
# Calculate strain (Hooke's Law)
epsilon = sigma / E
# Calculate elongation
delta_L = epsilon * L
return {
'area_mm2': A * 1e6,
'stress_MPa': sigma / 1e6,
'strain': epsilon,
'strain_percent': epsilon * 100,
'elongation_mm': delta_L * 1e3
}
# Example: Steel rod
result = analyze_tensile_bar(
force_kN=100,
diameter_mm=25,
length_mm=2000,
E_GPa=200
)
print("Tensile Bar Analysis Results:")
print(f" Cross-sectional area: {result['area_mm2']:.2f} mm²")
print(f" Stress: {result['stress_MPa']:.2f} MPa")
print(f" Strain: {result['strain']:.6f} ({result['strain_percent']:.4f}%)")
print(f" Elongation: {result['elongation_mm']:.4f} mm")
Output:
Tensile Bar Analysis Results:
Cross-sectional area: 490.87 mm²
Stress: 203.72 MPa
Strain: 0.001019 (0.1019%)
Elongation: 2.0372 mm
Shear Stress
Shear stress acts parallel to a surface, like when you cut paper with scissors:
$$\tau = \frac{V}{A}$$
Where:
Sponsored
April batch closing soon — only 42 seats remaining
Join 3,000+ engineers who got placed at top companies
- $\tau$ = Shear stress (Pa)
- $V$ = Shear force (N)
- $A$ = Area parallel to force (m²)
Shear Strain
Shear strain ($\gamma$) measures the angular deformation:
$$\gamma = \frac{\delta}{L} = \tan(\phi) \approx \phi$$ (for small angles)
The relationship between shear stress and strain:
Sponsored
Ranjith switched from IT to core automotive industry
His inspiring career transition story with video
$$\tau = G \cdot \gamma$$
Where $G$ is the Shear Modulus (or Modulus of Rigidity).
Relationship Between E and G
For isotropic materials:
$$G = \frac{E}{2(1 + \nu)}$$
Where $\nu$ is Poisson's ratio (typically 0.25-0.35 for metals).
def calculate_shear_modulus(E_GPa, poisson_ratio):
"""Calculate shear modulus from Young's modulus and Poisson's ratio."""
G = E_GPa / (2 * (1 + poisson_ratio))
return G
# Steel properties
E_steel = 200 # GPa
nu_steel = 0.3
G_steel = calculate_shear_modulus(E_steel, nu_steel)
print(f"Steel: E = {E_steel} GPa, ν = {nu_steel}")
print(f"Shear modulus G = {G_steel:.1f} GPa")
# Aluminum properties
E_al = 70 # GPa
nu_al = 0.33
G_al = calculate_shear_modulus(E_al, nu_al)
print(f"\nAluminum: E = {E_al} GPa, ν = {nu_al}")
print(f"Shear modulus G = {G_al:.1f} GPa")
Poisson's Ratio
When you stretch a rubber band, it gets thinner. This coupling between axial and lateral strain is captured by Poisson's ratio:
$$\nu = -\frac{\varepsilon_{lateral}}{\varepsilon_{axial}}$$
def calculate_lateral_contraction(
force_kN, diameter_mm, length_mm, E_GPa, poisson_ratio
):
"""
Calculate lateral contraction due to axial loading.
"""
# Axial analysis
P = force_kN * 1e3
d = diameter_mm * 1e-3
A = np.pi * (d / 2) ** 2
E = E_GPa * 1e9
sigma = P / A
epsilon_axial = sigma / E
# Lateral strain (negative = contraction)
epsilon_lateral = -poisson_ratio * epsilon_axial
# Change in diameter
delta_d = epsilon_lateral * d
return {
'axial_strain': epsilon_axial,
'lateral_strain': epsilon_lateral,
'diameter_change_mm': delta_d * 1e3,
'new_diameter_mm': (d + delta_d) * 1e3
}
# Steel bar under tension
result = calculate_lateral_contraction(
force_kN=100,
diameter_mm=25,
length_mm=2000,
E_GPa=200,
poisson_ratio=0.3
)
print("Lateral Contraction Analysis:")
print(f" Axial strain: {result['axial_strain']:.6f}")
print(f" Lateral strain: {result['lateral_strain']:.6f}")
print(f" Diameter change: {result['diameter_change_mm']:.6f} mm")
print(f" New diameter: {result['new_diameter_mm']:.6f} mm")
Stress-Strain Curves
Different materials exhibit different stress-strain behavior. Let's visualize them:
import numpy as np
import matplotlib.pyplot as plt
def plot_stress_strain_curves():
"""Plot stress-strain curves for different materials."""
fig, axes = plt.subplots(1, 2, figsize=(14, 5))
# ===== Ductile Material (Steel) =====
ax1 = axes[0]
# Material properties
E = 200e3 # MPa
sigma_y = 250 # MPa (yield)
sigma_u = 400 # MPa (ultimate)
epsilon_u = 0.15 # Strain at ultimate
epsilon_f = 0.25 # Strain at fracture
# Elastic region
eps1 = np.linspace(0, sigma_y / E, 50)
sig1 = E * eps1
# Yield plateau
eps2 = np.linspace(sigma_y / E, 0.02, 20)
sig2 = np.ones_like(eps2) * sigma_y
# Strain hardening
eps3 = np.linspace(0.02, epsilon_u, 50)
sig3 = sigma_y + (sigma_u - sigma_y) * ((eps3 - 0.02) / (epsilon_u - 0.02)) ** 0.5
# Necking
eps4 = np.linspace(epsilon_u, epsilon_f, 30)
sig4 = sigma_u - 150 * ((eps4 - epsilon_u) / (epsilon_f - epsilon_u)) ** 2
# Plot steel curve
ax1.plot(eps1 * 100, sig1, 'b-', linewidth=2)
ax1.plot(eps2 * 100, sig2, 'b-', linewidth=2)
ax1.plot(eps3 * 100, sig3, 'b-', linewidth=2)
ax1.plot(eps4 * 100, sig4, 'b-', linewidth=2, label='Mild Steel')
# Annotations
ax1.annotate('Yield Point', xy=(sigma_y/E*100, sigma_y),
xytext=(3, 280), fontsize=10,
arrowprops=dict(arrowstyle='->', color='gray'))
ax1.annotate('Ultimate\nStrength', xy=(epsilon_u*100, sigma_u),
xytext=(18, 420), fontsize=10,
arrowprops=dict(arrowstyle='->', color='gray'))
ax1.annotate('Fracture', xy=(epsilon_f*100, sig4[-1]),
xytext=(27, 200), fontsize=10,
arrowprops=dict(arrowstyle='->', color='gray'))
ax1.set_xlabel('Strain (%)', fontsize=12)
ax1.set_ylabel('Stress (MPa)', fontsize=12)
ax1.set_title('Ductile Material (Mild Steel)', fontsize=14)
ax1.grid(True, alpha=0.3)
ax1.set_xlim(0, 30)
ax1.set_ylim(0, 500)
# ===== Brittle Material (Cast Iron) =====
ax2 = axes[1]
# Cast iron - almost linear to fracture
E_ci = 100e3 # MPa
sigma_f = 200 # MPa (fracture)
epsilon_f_ci = 0.003 # Very small strain at fracture
eps_ci = np.linspace(0, epsilon_f_ci, 100)
# Slightly nonlinear
sig_ci = sigma_f * (1 - (1 - eps_ci / epsilon_f_ci) ** 2)
ax2.plot(eps_ci * 100, sig_ci, 'r-', linewidth=2, label='Cast Iron')
ax2.scatter([epsilon_f_ci * 100], [sigma_f], color='red', s=100, zorder=5)
ax2.annotate('Sudden\nFracture', xy=(epsilon_f_ci*100, sigma_f),
xytext=(0.5, 150), fontsize=10,
arrowprops=dict(arrowstyle='->', color='gray'))
ax2.set_xlabel('Strain (%)', fontsize=12)
ax2.set_ylabel('Stress (MPa)', fontsize=12)
ax2.set_title('Brittle Material (Cast Iron)', fontsize=14)
ax2.grid(True, alpha=0.3)
ax2.set_xlim(0, 0.5)
ax2.set_ylim(0, 250)
plt.tight_layout()
plt.show()
plot_stress_strain_curves()
True Stress vs Engineering Stress
Engineering stress uses the original area:
$$\sigma_{eng} = \frac{P}{A_0}$$
True stress uses the instantaneous area:
$$\sigma_{true} = \frac{P}{A} = \sigma_{eng}(1 + \varepsilon_{eng})$$
def compare_engineering_true_stress():
"""Compare engineering and true stress-strain curves."""
# Engineering strain range
eps_eng = np.linspace(0, 0.3, 100)
# Simplified engineering stress (power law hardening after yield)
sigma_y = 250 # MPa
eps_y = 0.00125
K = 600 # Strength coefficient
n = 0.2 # Strain hardening exponent
sigma_eng = np.where(
eps_eng < eps_y,
eps_eng * 200e3, # Elastic
K * eps_eng ** n # Plastic (Hollomon equation)
)
# True stress and strain
eps_true = np.log(1 + eps_eng)
sigma_true = sigma_eng * (1 + eps_eng)
# Plot
plt.figure(figsize=(10, 6))
plt.plot(eps_eng * 100, sigma_eng, 'b-', linewidth=2, label='Engineering')
plt.plot(eps_true * 100, sigma_true, 'r--', linewidth=2, label='True')
plt.xlabel('Strain (%)', fontsize=12)
plt.ylabel('Stress (MPa)', fontsize=12)
plt.title('Engineering vs True Stress-Strain', fontsize=14)
plt.legend(fontsize=11)
plt.grid(True, alpha=0.3)
plt.xlim(0, 35)
plt.show()
compare_engineering_true_stress()
Factor of Safety
The Factor of Safety (FoS) ensures structures can handle unexpected loads:
$$FoS = \frac{\sigma_{allowable}}{\sigma_{working}}$$
| Application | Typical FoS |
|---|
| Aircraft structures | 1.5 - 2.0 |
| Pressure vessels | 3.0 - 4.0 |
| Building structures | 2.0 - 3.0 |
| Elevators | 8.0 - 10.0 |
def design_with_safety_factor(
required_load_kN,
yield_strength_MPa,
factor_of_safety,
material_name="Steel"
):
"""
Design a circular rod with given safety factor.
"""
# Allowable stress
sigma_allow = yield_strength_MPa / factor_of_safety
# Required area
P = required_load_kN * 1e3 # N
A_required = P / (sigma_allow * 1e6) # m²
# Required diameter
d_required = 2 * np.sqrt(A_required / np.pi)
# Round up to standard size (mm)
standard_sizes = [6, 8, 10, 12, 14, 16, 18, 20, 22, 25, 28, 30, 32, 36, 40]
d_mm = d_required * 1e3
d_selected = next((s for s in standard_sizes if s >= d_mm), standard_sizes[-1])
# Actual safety factor with selected size
A_actual = np.pi * (d_selected * 1e-3 / 2) ** 2
sigma_actual = P / A_actual
FoS_actual = (yield_strength_MPa * 1e6) / sigma_actual
print(f"Design for {required_load_kN} kN load with FoS = {factor_of_safety}")
print(f"Material: {material_name} (σy = {yield_strength_MPa} MPa)")
print(f"Allowable stress: {sigma_allow:.1f} MPa")
print(f"Required diameter: {d_mm:.2f} mm")
print(f"Selected standard size: {d_selected} mm")
print(f"Actual stress: {sigma_actual/1e6:.1f} MPa")
print(f"Actual FoS: {FoS_actual:.2f}")
return d_selected
# Design example
diameter = design_with_safety_factor(
required_load_kN=75,
yield_strength_MPa=250,
factor_of_safety=2.5,
material_name="ASTM A36 Steel"
)
Key Takeaways
- Normal stress acts perpendicular; shear stress acts parallel to surfaces
- Poisson's ratio couples axial and lateral strains: $\nu = -\varepsilon_{lat}/\varepsilon_{axial}$
- Shear modulus: $G = E / [2(1+\nu)]$
- Ductile materials show yielding and plastic deformation before failure
- Brittle materials fail suddenly with little plastic deformation
- True stress accounts for area change: $\sigma_{true} = \sigma_{eng}(1 + \varepsilon_{eng})$
- Factor of Safety provides margin against unexpected loads
Next lesson: We'll apply these concepts to axial loading problems including thermal stress.