🎯 Learning Objectives
- Understand the difference between solid solutions and intermetallic compounds
- Explain the materials-science significance of eutectic and peritectic reactions
- Read the Fe-C phase diagram and predict the microstructure of steel
- Understand the thermodynamics of the α-γ transformation (ferrite-austenite transformation)
- Explain the mechanism of the martensitic transformation
- Design heat-treatment processes using TTT and CCT diagrams
- Calculate the configurational entropy of high-entropy alloys (HEAs)
📖 Solid Solutions and Intermetallic Compounds
Solid Solution
A single-phase solid in which two or more elements are randomly mixed within a crystal lattice
Substitutional solid solution: Solute atoms replace solvent atoms at lattice sites
- Hume-Rothery rules: atomic radius difference <15%, identical crystal structure, similar electronegativity
- Examples: Cu-Ni (complete solid solubility), Fe-Cr (stainless steel)
Interstitial solid solution: Solute atoms occupy interstitial sites of the lattice
- Solute atoms are small (C, N, H, B)
- Examples: Fe-C (steel), Ti-O
Intermetallic Compound
An ordered structure formed at a specific stoichiometric ratio
- Examples: Fe₃C (cementite), Ni₃Al, TiAl
- Characteristics: high strength, high brittleness, high melting point
💻 Code Example 5.1: Analyzing the Fe-C Phase Diagram
Python Implementation: Drawing and Reading the Fe-C Phase Diagram
import numpy as np
import matplotlib.pyplot as plt
def fe_c_phase_diagram():
"""Simplified model of the Fe-C phase diagram"""
# Composition (C wt%)
# Liquidus
x_liquidus = np.array([0, 0.5, 2.11, 4.3, 6.69])
T_liquidus = np.array([1538, 1500, 1148, 1148, 1130])
# γ phase (austenite) solidus
x_gamma_solidus = np.array([0, 0.5, 2.11])
T_gamma_solidus = np.array([1495, 1450, 1148])
# α phase (ferrite) boundary
x_alpha = np.array([0, 0.022, 0.022, 0, 0])
T_alpha = np.array([912, 727, 1495, 1538, 912])
# γ phase boundary
x_gamma_boundary = np.array([0.022, 0.77, 2.11, 2.11, 0.77, 0.022])
T_gamma_boundary = np.array([727, 727, 1148, 1495, 1495, 1495])
# Cementite boundary
x_cementite = np.array([6.69, 6.69])
T_cementite = np.array([727, 1130])
# Eutectic and eutectoid points
eutectic_point = (4.3, 1148) # L → γ + Fe₃C
eutectoid_point = (0.77, 727) # γ → α + Fe₃C
return {
'liquidus': (x_liquidus, T_liquidus),
'gamma_solidus': (x_gamma_solidus, T_gamma_solidus),
'alpha': (x_alpha, T_alpha),
'gamma_boundary': (x_gamma_boundary, T_gamma_boundary),
'cementite': (x_cementite, T_cementite),
'eutectic': eutectic_point,
'eutectoid': eutectoid_point
}
# Draw the phase diagram
phase_data = fe_c_phase_diagram()
fig, ax = plt.subplots(figsize=(12, 8))
# Liquidus
x_liq, T_liq = phase_data['liquidus']
ax.plot(x_liq, T_liq, 'r-', linewidth=2.5, label='Liquidus')
# γ phase solidus
x_g_sol, T_g_sol = phase_data['gamma_solidus']
ax.plot(x_g_sol, T_g_sol, 'b-', linewidth=2.5)
# α phase boundary
x_a, T_a = phase_data['alpha']
ax.plot(x_a, T_a, 'g-', linewidth=2.5, label='α phase (ferrite)')
# γ phase boundary
x_g, T_g = phase_data['gamma_boundary']
ax.plot(x_g, T_g, 'b-', linewidth=2.5, label='γ phase (austenite)')
# Cementite boundary
x_cem, T_cem = phase_data['cementite']
ax.plot([6.69], [1130], 'ko', markersize=8)
ax.axvline(6.69, color='purple', linestyle='--', linewidth=1.5,
label='Fe₃C (cementite)')
# Key points
eutectic = phase_data['eutectic']
eutectoid = phase_data['eutectoid']
ax.plot(eutectic[0], eutectic[1], 'ro', markersize=12, label=f'Eutectic point (C {eutectic[0]:.1f}%, {eutectic[1]}°C)')
ax.plot(eutectoid[0], eutectoid[1], 'mo', markersize=12, label=f'Eutectoid point (C {eutectoid[0]:.2f}%, {eutectoid[1]}°C)')
# Region labels
ax.text(0.2, 1000, 'δ-Fe', fontsize=12, ha='center')
ax.text(0.2, 1300, 'L (liquid)', fontsize=13, ha='center', fontweight='bold')
ax.text(0.5, 1000, 'γ\n(austenite)', fontsize=12, ha='center')
ax.text(0.01, 850, 'α\n(ferrite)', fontsize=12, ha='center')
ax.text(0.4, 650, 'α + Fe₃C\n(pearlite)', fontsize=11, ha='center')
ax.text(3.0, 1000, 'L + γ', fontsize=11, ha='center')
ax.text(5.0, 1000, 'L + Fe₃C', fontsize=11, ha='center')
# Vertical lines marking important carbon concentrations
ax.axvline(0.022, color='gray', linestyle=':', alpha=0.5)
ax.text(0.022, 500, '0.022%\n(max. solubility in α)', fontsize=9, ha='center')
ax.axvline(0.77, color='gray', linestyle=':', alpha=0.5)
ax.text(0.77, 500, '0.77%\n(eutectoid point)', fontsize=9, ha='center')
ax.axvline(2.11, color='gray', linestyle=':', alpha=0.5)
ax.text(2.11, 500, '2.11%\n(max. solubility in γ)', fontsize=9, ha='center')
ax.set_xlabel('Carbon content (wt%)', fontsize=13)
ax.set_ylabel('Temperature (°C)', fontsize=13)
ax.set_title('Fe-C Phase Diagram (Iron-Carbon System)', fontsize=15, fontweight='bold')
ax.set_xlim([0, 7])
ax.set_ylim([400, 1600])
ax.legend(loc='upper right', fontsize=10)
ax.grid(True, alpha=0.3)
plt.tight_layout()
plt.savefig('materials_fe_c_phase_diagram.png', dpi=300, bbox_inches='tight')
plt.show()
# Classification of Fe-C alloys
print("=== Classification of Fe-C Alloys ===\n")
print(f"{'Type':<25} {'Carbon content (wt%)':<22} {'Main microstructure':<35}")
print("-" * 65)
print(f"{'Pure iron':<25} {'< 0.008':<22} {'α-Fe (ferrite)':<35}")
print(f"{'Steel':<25} {'0.008 - 2.11':<22} {'Ferrite + cementite':<35}")
print(f"{' Hypoeutectoid steel':<25} {'< 0.77':<22} {'Mostly ferrite + pearlite':<35}")
print(f"{' Eutectoid steel':<25} {'= 0.77':<22} {'Pearlite (lamellar structure)':<35}")
print(f"{' Hypereutectoid steel':<25} {'0.77 - 2.11':<22} {'Cementite + pearlite':<35}")
print(f"{'Cast iron':<25} {'2.11 - 6.69':<22} {'γ + graphite/Fe₃C':<35}")
print()
print("Key phase-transformation temperatures:")
print(f" A₁: 727°C (eutectoid temperature, γ → α + Fe₃C)")
print(f" A₃: 912°C (start of α → γ transformation)")
print(f" Melting point: 1538°C (pure iron)")
💻 Code Example 5.2: Calculating the α-γ Transformation Temperature
α-γ Transformation (Ferrite-Austenite Transformation)
Allotropic transformation of iron:
\[
\text{α-Fe (BCC)} \xrightarrow{912°C} \text{γ-Fe (FCC)}
\]
Phase equilibrium condition: equal chemical potentials
\[
\mu^\alpha(T, P) = \mu^\gamma(T, P)
\]
Gibbs free energy difference:
\[
\Delta G^{\gamma \to \alpha} = \Delta H - T \Delta S
\]
Python Implementation: Thermodynamic Calculation of the α-γ Transformation
import numpy as np
import matplotlib.pyplot as plt
from scipy.optimize import fsolve
# Parameters for the α-γ transformation (pure iron)
def gibbs_alpha_gamma(T):
"""Gibbs free energy difference between the α and γ phases
Args:
T: temperature (K)
Returns:
ΔG^(γ→α): Gibbs free energy difference (J/mol)
"""
# Model based on experimental values
# ΔH ≈ -900 J/mol, ΔS ≈ -1.0 J/(mol·K) (simplified)
T_eq = 1185 # 912°C = 1185 K (α-γ transformation point)
DH = -900 # J/mol (transformation enthalpy)
DS = DH / T_eq # J/(mol·K)
DG = DH - T * DS
return DG
# Temperature range
T_range = np.linspace(800, 1400, 100) # K
DG_values = [gibbs_alpha_gamma(T) for T in T_range]
# Compute the transformation point
T_transformation = fsolve(gibbs_alpha_gamma, 1185)[0]
# Visualization
fig, axes = plt.subplots(1, 2, figsize=(14, 6))
# Gibbs free energy difference
ax1 = axes[0]
ax1.plot(T_range - 273.15, DG_values, 'b-', linewidth=2.5)
ax1.axhline(0, color='r', linestyle='--', linewidth=2, label='ΔG = 0 (equilibrium)')
ax1.axvline(T_transformation - 273.15, color='g', linestyle='--',
linewidth=2, label=f'Transformation point {T_transformation - 273.15:.0f}°C')
# Regions of phase stability
ax1.fill_between(T_range - 273.15, -2000, 0,
where=(T_range < T_transformation),
color='lightblue', alpha=0.3, label='α phase stable')
ax1.fill_between(T_range - 273.15, 0, 2000,
where=(T_range > T_transformation),
color='lightcoral', alpha=0.3, label='γ phase stable')
ax1.set_xlabel('Temperature (°C)')
ax1.set_ylabel('ΔG^(γ→α) (J/mol)')
ax1.set_title('Gibbs Free Energy of the α-γ Transformation')
ax1.legend()
ax1.grid(True, alpha=0.3)
# Change in transformation temperature with carbon content
# Simplified model: T_γ→α = 912°C - k·[C%]
C_range = np.linspace(0, 1.5, 50) # C wt%
k = 200 # °C/wt% (empirical rule)
T_gamma_alpha = 912 - k * C_range
ax2 = axes[1]
ax2.plot(C_range, T_gamma_alpha, 'r-', linewidth=2.5, label='A₃ line (α-γ transformation start)')
ax2.axhline(727, color='purple', linestyle='--', linewidth=2, label='A₁ line (eutectoid temperature)')
# Important composition
ax2.axvline(0.77, color='gray', linestyle=':', alpha=0.5)
ax2.text(0.77, 650, 'Eutectoid composition\n0.77%', fontsize=10, ha='center')
# Region labels
ax2.text(0.3, 850, 'γ phase\n(austenite)', fontsize=12, ha='center',
bbox=dict(boxstyle='round', facecolor='lightcoral', alpha=0.5))
ax2.text(0.3, 750, 'α + γ', fontsize=11, ha='center')
ax2.text(0.3, 650, 'α + Fe₃C\n(ferrite + cementite)', fontsize=11, ha='center',
bbox=dict(boxstyle='round', facecolor='lightblue', alpha=0.5))
ax2.set_xlabel('Carbon content (wt%)')
ax2.set_ylabel('Temperature (°C)')
ax2.set_title('Carbon Content and α-γ Transformation Temperature')
ax2.set_xlim([0, 1.5])
ax2.set_ylim([600, 950])
ax2.legend()
ax2.grid(True, alpha=0.3)
plt.tight_layout()
plt.savefig('materials_alpha_gamma_transformation.png', dpi=300, bbox_inches='tight')
plt.show()
# Results
print("=== Thermodynamics of the α-γ Transformation ===\n")
print(f"Transformation point (calculated): {T_transformation - 273.15:.2f}°C")
print(f"Experimental value: 912°C")
print()
print("Depression of the transformation point by carbon:")
print(f"{'C content (wt%)':<18} {'Transformation temperature (°C)':<32}")
print("-" * 35)
for C in [0, 0.2, 0.4, 0.6, 0.77]:
T = 912 - k * C
print(f"{C:<18.2f} {T:<32.0f}")
print("\nPhysical significance:")
print(" - The α phase (BCC) has low carbon solubility (0.022% at 727°C)")
print(" - The γ phase (FCC) has high carbon solubility (2.11% at 1148°C)")
print(" - Adding carbon stabilizes the γ phase → lower transformation temperature")
💻 Code Example 5.3: Thermodynamics of the Martensitic Transformation
Martensitic Transformation
Diffusionless transformation: a supersaturated solid solution formed from the γ phase (FCC) by rapid quenching
Characteristics:
- No diffusion (cooperative shear deformation of atoms)
- Metastable phase (non-equilibrium)
- High hardness, high brittleness
- Crystal structure: BCT (body-centered tetragonal)
Ms point (Martensite start): temperature at which the martensitic transformation begins
\[
M_s = 539 - 423[\text{C}] - 30.4[\text{Mn}] - 12.1[\text{Cr}] - 17.7[\text{Ni}] \quad (°C)
\]
Python Implementation: Ms Point Calculation and Cooling Curves
import numpy as np
import matplotlib.pyplot as plt
def calculate_Ms(C, Mn=0, Cr=0, Ni=0):
"""Calculate the martensite start temperature (Ms point)
Empirical rule based on the Andrews equation
Args:
C: carbon content (wt%)
Mn: manganese content (wt%)
Cr: chromium content (wt%)
Ni: nickel content (wt%)
Returns:
Ms: martensite start temperature (°C)
"""
Ms = 539 - 423*C - 30.4*Mn - 12.1*Cr - 17.7*Ni
return Ms
# Variation of the Ms point with carbon content
C_range = np.linspace(0.1, 1.2, 50)
Ms_values = [calculate_Ms(C) for C in C_range]
# Effect of alloying elements
Mn_range = [0, 1.0, 2.0]
Ni_range = [0, 2.0, 4.0]
fig, axes = plt.subplots(1, 2, figsize=(14, 6))
# Effect of carbon content
ax1 = axes[0]
ax1.plot(C_range, Ms_values, 'b-', linewidth=2.5, label='Plain carbon steel')
# Effect of Mn addition
for Mn in Mn_range[1:]:
Ms_Mn = [calculate_Ms(C, Mn=Mn) for C in C_range]
ax1.plot(C_range, Ms_Mn, '--', linewidth=2,
label=f'+ {Mn:.1f}% Mn')
ax1.axhline(0, color='red', linestyle=':', linewidth=1.5, label='Room temperature')
ax1.set_xlabel('Carbon content (wt%)')
ax1.set_ylabel('Ms point (°C)')
ax1.set_title('Martensite Start Temperature (Ms Point)')
ax1.legend()
ax1.grid(True, alpha=0.3)
ax1.set_ylim([-100, 550])
# Cooling curves and martensite fraction
ax2 = axes[1]
# Koistinen-Marburger equation: f_M = 1 - exp(-α(Ms - T))
def martensite_fraction(T, Ms, alpha=0.011):
"""Martensite fraction
Args:
T: temperature (°C)
Ms: Ms point (°C)
alpha: constant (typically 0.011)
Returns:
f_M: martensite fraction
"""
if T >= Ms:
return 0
else:
f_M = 1 - np.exp(-alpha * (Ms - T))
return f_M
# Martensite fraction for different carbon contents
T_range = np.linspace(-100, 800, 200)
C_values = [0.3, 0.6, 0.9]
colors = ['blue', 'green', 'red']
for C, color in zip(C_values, colors):
Ms = calculate_Ms(C)
f_M = [martensite_fraction(T, Ms) for T in T_range]
ax2.plot(T_range, f_M, color=color, linewidth=2.5,
label=f'C {C:.1f}% (Ms={Ms:.0f}°C)')
ax2.axvline(25, color='purple', linestyle='--', linewidth=1.5, label='Room temperature')
ax2.set_xlabel('Temperature (°C)')
ax2.set_ylabel('Martensite fraction')
ax2.set_title('Temperature Dependence of the Martensite Fraction')
ax2.legend()
ax2.grid(True, alpha=0.3)
ax2.set_xlim([-100, 800])
ax2.set_ylim([0, 1])
plt.tight_layout()
plt.savefig('materials_martensite_transformation.png', dpi=300, bbox_inches='tight')
plt.show()
# Example calculation
print("=== Ms Points of the Martensitic Transformation ===\n")
steels = [
("Low-carbon steel", 0.2, 0, 0, 0),
("Medium-carbon steel", 0.6, 0, 0, 0),
("High-carbon steel", 1.0, 0, 0, 0),
("Mn steel", 0.6, 2.0, 0, 0),
("Cr-Ni steel", 0.6, 1.0, 12.0, 8.0),
]
print(f"{'Steel type':<20} {'C%':<6} {'Mn%':<6} {'Cr%':<6} {'Ni%':<6} {'Ms point (°C)':<14}")
print("-" * 60)
for name, C, Mn, Cr, Ni in steels:
Ms = calculate_Ms(C, Mn, Cr, Ni)
print(f"{name:<20} {C:<6.1f} {Mn:<6.1f} {Cr:<6.1f} {Ni:<6.1f} {Ms:<14.0f}")
print("\nMartensite fraction at room temperature (25°C):")
for name, C, Mn, Cr, Ni in steels:
Ms = calculate_Ms(C, Mn, Cr, Ni)
f_M = martensite_fraction(25, Ms)
print(f" {name}: {f_M*100:.1f}%")
print("\nKey insights:")
print(" - Carbon ↑ → Ms point ↓ (martensite forms less readily)")
print(" - Alloying elements (Mn, Cr, Ni) also lower the Ms point")
print(" - When Ms < room temperature, retained austenite remains")
💻 Code Example 5.4: Drawing a TTT Diagram (Time-Temperature-Transformation)
What Is a TTT Diagram?
Isothermal transformation diagram: shows transformation start and finish times during isothermal holding
Main transformations:
- Pearlite transformation: diffusional, lamellar structure (α + Fe₃C)
- Bainite transformation: intermediate, acicular structure
- Martensitic transformation: diffusionless, formed by rapid quenching
Uses: heat-treatment process design, microstructure prediction
Python Implementation: Generating Simulated TTT Diagram Data
import numpy as np
import matplotlib.pyplot as plt
def generate_TTT_diagram(C_content=0.8):
"""Generate simulated TTT diagram data
Args:
C_content: carbon content (wt%)
Returns:
TTT diagram data
"""
# Temperature range
T_range = np.linspace(200, 700, 100)
# Pearlite transformation (C-curve)
# Nose point (shortest transformation time): ~550°C, ~1 second
T_pearlite_nose = 550
t_pearlite_nose = 1.0 # seconds
def pearlite_curve(T, factor):
"""Pearlite transformation curve (based on the Johnson-Mehl-Avrami equation)"""
if T < 500 or T > 727:
return 1e10 # No transformation
else:
# Shape of the C-curve
t = t_pearlite_nose * factor * np.exp(abs(T - T_pearlite_nose) / 50)
return t
t_pearlite_start = [pearlite_curve(T, 1) for T in T_range]
t_pearlite_finish = [pearlite_curve(T, 10) for T in T_range]
# Bainite transformation
T_bainite_nose = 350
t_bainite_nose = 10.0
def bainite_curve(T, factor):
if T < 250 or T > 500:
return 1e10
else:
t = t_bainite_nose * factor * np.exp(abs(T - T_bainite_nose) / 30)
return t
t_bainite_start = [bainite_curve(T, 1) for T in T_range]
t_bainite_finish = [bainite_curve(T, 10) for T in T_range]
# Martensite (instantaneous)
Ms = calculate_Ms(C_content)
Mf = Ms - 215 # Mf point (martensite finish)
return {
'T_range': T_range,
'pearlite_start': t_pearlite_start,
'pearlite_finish': t_pearlite_finish,
'bainite_start': t_bainite_start,
'bainite_finish': t_bainite_finish,
'Ms': Ms,
'Mf': Mf
}
# Draw the TTT diagram
C = 0.8 # Eutectoid steel
ttt_data = generate_TTT_diagram(C)
fig, ax = plt.subplots(figsize=(12, 8))
T = ttt_data['T_range']
t_p_s = np.array(ttt_data['pearlite_start'])
t_p_f = np.array(ttt_data['pearlite_finish'])
t_b_s = np.array(ttt_data['bainite_start'])
t_b_f = np.array(ttt_data['bainite_finish'])
# Mask times to the valid range
valid_p_s = t_p_s < 1e6
valid_p_f = t_p_f < 1e6
valid_b_s = t_b_s < 1e6
valid_b_f = t_b_f < 1e6
# Pearlite
ax.semilogx(t_p_s[valid_p_s], T[valid_p_s], 'b-', linewidth=2.5, label='Pearlite transformation start')
ax.semilogx(t_p_f[valid_p_f], T[valid_p_f], 'b--', linewidth=2.5, label='Pearlite transformation finish')
ax.fill_betweenx(T[valid_p_s], t_p_s[valid_p_s], t_p_f[valid_p_f],
where=valid_p_s & valid_p_f,
color='lightblue', alpha=0.3)
# Bainite
ax.semilogx(t_b_s[valid_b_s], T[valid_b_s], 'g-', linewidth=2.5, label='Bainite transformation start')
ax.semilogx(t_b_f[valid_b_f], T[valid_b_f], 'g--', linewidth=2.5, label='Bainite transformation finish')
ax.fill_betweenx(T[valid_b_s], t_b_s[valid_b_s], t_b_f[valid_b_f],
where=valid_b_s & valid_b_f,
color='lightgreen', alpha=0.3)
# Martensite
Ms = ttt_data['Ms']
Mf = ttt_data['Mf']
ax.axhline(Ms, color='red', linestyle='-', linewidth=2.5, label=f'Ms = {Ms:.0f}°C')
ax.axhline(Mf, color='red', linestyle='--', linewidth=2, label=f'Mf = {Mf:.0f}°C')
ax.fill_between([1e-2, 1e6], Mf, Ms, color='lightcoral', alpha=0.3)
# Example cooling curves
# Slow cooling: pearlite formation
t_cool_slow = np.logspace(-1, 3, 50)
T_cool_slow = 900 - 100 * np.log10(t_cool_slow + 1)
ax.plot(t_cool_slow, T_cool_slow, 'purple', linewidth=2, linestyle=':',
label='Slow cooling (furnace cooling) → pearlite')
# Rapid cooling: martensite formation
t_cool_fast = np.logspace(-2, 0, 30)
T_cool_fast = 900 - 800 * t_cool_fast
ax.plot(t_cool_fast, T_cool_fast, 'orange', linewidth=2, linestyle=':',
label='Rapid cooling (water quenching) → martensite')
# Labels
ax.text(10, 600, 'Pearlite', fontsize=14, fontweight='bold',
bbox=dict(boxstyle='round', facecolor='lightblue', alpha=0.7))
ax.text(100, 350, 'Bainite', fontsize=14, fontweight='bold',
bbox=dict(boxstyle='round', facecolor='lightgreen', alpha=0.7))
ax.text(0.5, Ms-50, 'Martensite', fontsize=14, fontweight='bold',
bbox=dict(boxstyle='round', facecolor='lightcoral', alpha=0.7))
ax.set_xlabel('Time (s)', fontsize=13)
ax.set_ylabel('Temperature (°C)', fontsize=13)
ax.set_title(f'TTT Diagram (Time-Temperature-Transformation)\nC {C}% steel', fontsize=15, fontweight='bold')
ax.set_xlim([1e-2, 1e6])
ax.set_ylim([0, 800])
ax.legend(loc='upper right', fontsize=10)
ax.grid(True, alpha=0.3, which='both')
plt.tight_layout()
plt.savefig('materials_TTT_diagram.png', dpi=300, bbox_inches='tight')
plt.show()
print("=== How to Read a TTT Diagram ===\n")
print("1. Vertical axis: temperature (cooling from the austenitizing temperature)")
print("2. Horizontal axis: time (logarithmic scale)")
print("3. C-curves: transformation start and finish times")
print()
print("Determining the microstructure:")
print(" - Slow cooling (crosses the curves) → pearlite")
print(" - Intermediate cooling → bainite")
print(" - Rapid cooling (avoids the curves, below Ms) → martensite")
print()
print("Practical examples:")
print(" - Quenching: rapid cooling to form martensite (high hardness)")
print(" - Annealing: slow cooling to form pearlite (softening)")
print(" - Tempering: reheating martensite to improve toughness")
💻 Code Example 5.5: Calculating a CCT Diagram (Continuous Cooling Transformation)
Python Implementation: CCT Diagram and Cooling Rates
import numpy as np
import matplotlib.pyplot as plt
def generate_CCT_diagram(C_content=0.4):
"""Simulated CCT diagram data (continuous cooling transformation diagram)
Args:
C_content: carbon content (wt%)
"""
# Curves are shifted to the right compared with the TTT diagram
T_range = np.linspace(200, 750, 100)
# Ferrite + pearlite transformation
T_fp_nose = 600
t_fp_nose = 10.0
def fp_curve(T, factor):
if T < 500 or T > 750:
return 1e10
else:
t = t_fp_nose * factor * np.exp(abs(T - T_fp_nose) / 60)
return t
t_fp_start = [fp_curve(T, 1) for T in T_range]
t_fp_finish = [fp_curve(T, 50) for T in T_range]
# Bainite
T_b_nose = 400
t_b_nose = 100.0
def b_curve(T, factor):
if T < 300 or T > 550:
return 1e10
else:
t = t_b_nose * factor * np.exp(abs(T - T_b_nose) / 40)
return t
t_b_start = [b_curve(T, 1) for T in T_range]
t_b_finish = [b_curve(T, 50) for T in T_range]
# Martensite
Ms = calculate_Ms(C_content)
Mf = Ms - 215
return {
'T_range': T_range,
'fp_start': t_fp_start,
'fp_finish': t_fp_finish,
'b_start': t_b_start,
'b_finish': t_b_finish,
'Ms': Ms,
'Mf': Mf
}
# Generate cooling curves
def cooling_curve(cooling_rate, T_start=900, T_final=25):
"""Cooling curve
Args:
cooling_rate: cooling rate (°C/s)
T_start: initial temperature (°C)
T_final: final temperature (°C)
Returns:
t, T: time, temperature
"""
t_total = (T_start - T_final) / cooling_rate
t = np.linspace(0, t_total, 200)
T = T_start - cooling_rate * t
T = np.maximum(T, T_final)
return t, T
# Draw the CCT diagram
C = 0.4 # Medium-carbon steel
cct_data = generate_CCT_diagram(C)
fig, ax = plt.subplots(figsize=(12, 8))
T = cct_data['T_range']
t_fp_s = np.array(cct_data['fp_start'])
t_fp_f = np.array(cct_data['fp_finish'])
t_b_s = np.array(cct_data['b_start'])
t_b_f = np.array(cct_data['b_finish'])
# Valid range
valid_fp_s = t_fp_s < 1e6
valid_fp_f = t_fp_f < 1e6
valid_b_s = t_b_s < 1e6
valid_b_f = t_b_f < 1e6
# Ferrite + pearlite
ax.semilogx(t_fp_s[valid_fp_s], T[valid_fp_s], 'b-', linewidth=2.5, label='F+P start')
ax.semilogx(t_fp_f[valid_fp_f], T[valid_fp_f], 'b--', linewidth=2.5, label='F+P finish')
ax.fill_betweenx(T, t_fp_s, t_fp_f, where=valid_fp_s & valid_fp_f,
color='lightblue', alpha=0.3)
# Bainite
ax.semilogx(t_b_s[valid_b_s], T[valid_b_s], 'g-', linewidth=2.5, label='B start')
ax.semilogx(t_b_f[valid_b_f], T[valid_b_f], 'g--', linewidth=2.5, label='B finish')
ax.fill_betweenx(T, t_b_s, t_b_f, where=valid_b_s & valid_b_f,
color='lightgreen', alpha=0.3)
# Martensite
Ms = cct_data['Ms']
Mf = cct_data['Mf']
ax.axhline(Ms, color='red', linestyle='-', linewidth=2.5, label=f'Ms = {Ms:.0f}°C')
ax.axhline(Mf, color='red', linestyle='--', linewidth=2, label=f'Mf = {Mf:.0f}°C')
# Various cooling rates
cooling_rates = [0.5, 5, 50, 500] # °C/s
colors = ['purple', 'orange', 'brown', 'magenta']
microstructures = ['F+P', 'F+P+B', 'B+M', 'M']
for i, (cr, color, micro) in enumerate(zip(cooling_rates, colors, microstructures)):
t_cool, T_cool = cooling_curve(cr)
ax.plot(t_cool, T_cool, color=color, linewidth=2, linestyle=':',
label=f'{cr} °C/s → {micro}')
# Show the critical cooling rate
ax.axvline(1, color='gray', linestyle='--', alpha=0.5)
ax.text(1, 100, 'Critical cooling rate\n(martensite forms at\nrates above this)',
fontsize=10, ha='center',
bbox=dict(boxstyle='round', facecolor='yellow', alpha=0.6))
ax.set_xlabel('Time (s)', fontsize=13)
ax.set_ylabel('Temperature (°C)', fontsize=13)
ax.set_title(f'CCT Diagram (Continuous Cooling Transformation)\nC {C}% steel', fontsize=15, fontweight='bold')
ax.set_xlim([1e-1, 1e4])
ax.set_ylim([0, 900])
ax.legend(loc='upper right', fontsize=9)
ax.grid(True, alpha=0.3, which='both')
plt.tight_layout()
plt.savefig('materials_CCT_diagram.png', dpi=300, bbox_inches='tight')
plt.show()
# Relationship between cooling rate and microstructure
print("=== CCT Diagram and Cooling Rates ===\n")
print(f"{'Cooling rate (°C/s)':<22} {'Cooling time (900→25°C)':<27} {'Microstructure':<35}")
print("-" * 75)
for cr in cooling_rates:
t_total = (900 - 25) / cr
# Microstructure determination (simplified)
if cr < 1:
micro = "Ferrite + pearlite"
elif cr < 10:
micro = "Ferrite + pearlite + bainite"
elif cr < 100:
micro = "Bainite + martensite"
else:
micro = "Martensite"
print(f"{cr:<22.1f} {t_total:<27.1f} {micro:<35}")
print("\nCooling methods and cooling rates:")
print(" - Furnace cooling: ~0.1-1 °C/s")
print(" - Air cooling: ~1-10 °C/s")
print(" - Oil quenching: ~10-100 °C/s")
print(" - Water quenching: ~100-1000 °C/s")
print()
print("TTT diagram vs. CCT diagram:")
print(" - TTT: isothermal transformation (laboratory conditions)")
print(" - CCT: continuous cooling transformation (practical conditions)")
print(" - CCT curves lie to the right of TTT curves (longer times)")
💻 Code Example 5.6: Calculating the Mixing Enthalpy of an Alloy
Python Implementation: Mixing Enthalpy of a Binary System
import numpy as np
import matplotlib.pyplot as plt
def mixing_enthalpy(x_B, H_AB, omega=0):
"""Mixing enthalpy of a binary alloy A-B
Regular solution model: ΔH_mix = Ω x_A x_B
Args:
x_B: atomic fraction of B
H_AB: interaction parameter (A-B bond energy)
omega: interaction parameter Ω = z(ε_AB - (ε_AA + ε_BB)/2)
Returns:
ΔH_mix: mixing enthalpy (J/mol)
"""
x_A = 1 - x_B
if omega == 0:
omega = H_AB
DH_mix = omega * x_A * x_B
return DH_mix
def mixing_entropy(x_B, R=8.314):
"""Configurational entropy of an ideal solution
Args:
x_B: atomic fraction of B
R: gas constant (J/(mol·K))
Returns:
ΔS_mix: mixing entropy (J/(mol·K))
"""
x_A = 1 - x_B
if x_A == 0 or x_B == 0:
return 0
else:
DS_mix = -R * (x_A * np.log(x_A) + x_B * np.log(x_B))
return DS_mix
def mixing_gibbs(x_B, T, omega, R=8.314):
"""Gibbs free energy of mixing
ΔG_mix = ΔH_mix - T ΔS_mix
Args:
x_B: atomic fraction of B
T: temperature (K)
omega: interaction parameter (J/mol)
R: gas constant
Returns:
ΔG_mix: Gibbs free energy of mixing (J/mol)
"""
DH = mixing_enthalpy(x_B, 0, omega)
DS = mixing_entropy(x_B, R)
DG = DH - T * DS
return DG
# Composition range
x_B_range = np.linspace(0.001, 0.999, 200)
# Interaction parameters (different systems)
omega_values = {
'Ideal solution (Ω=0)': 0,
'Weak attraction (Ω=-5kJ/mol)': -5000,
'Strong repulsion (Ω=+20kJ/mol)': 20000,
}
fig, axes = plt.subplots(2, 2, figsize=(14, 10))
# Mixing enthalpy
ax1 = axes[0, 0]
for name, omega in omega_values.items():
DH_values = [mixing_enthalpy(x, 0, omega) for x in x_B_range]
ax1.plot(x_B_range, np.array(DH_values) / 1000, linewidth=2.5, label=name)
ax1.set_xlabel('Composition x_B')
ax1.set_ylabel('ΔH_mix (kJ/mol)')
ax1.set_title('Mixing Enthalpy')
ax1.legend()
ax1.grid(True, alpha=0.3)
ax1.axhline(0, color='black', linestyle='--', linewidth=1)
# Mixing entropy
ax2 = axes[0, 1]
DS_values = [mixing_entropy(x) for x in x_B_range]
ax2.plot(x_B_range, DS_values, 'g-', linewidth=2.5)
ax2.set_xlabel('Composition x_B')
ax2.set_ylabel('ΔS_mix (J/(mol·K))')
ax2.set_title('Mixing Entropy (Ideal Solution)')
ax2.grid(True, alpha=0.3)
# Gibbs free energy of mixing (temperature dependence)
ax3 = axes[1, 0]
T_values = [300, 600, 1000, 1500]
omega_example = 20000 # Repulsive system
for T in T_values:
DG_values = [mixing_gibbs(x, T, omega_example) for x in x_B_range]
ax3.plot(x_B_range, np.array(DG_values) / 1000, linewidth=2.5, label=f'T = {T} K')
ax3.set_xlabel('Composition x_B')
ax3.set_ylabel('ΔG_mix (kJ/mol)')
ax3.set_title(f'Gibbs Free Energy of Mixing (Ω = {omega_example/1000:.0f} kJ/mol)')
ax3.legend()
ax3.grid(True, alpha=0.3)
ax3.axhline(0, color='black', linestyle='--', linewidth=1)
# Determining phase separation (spinodal where ∂²ΔG/∂x² < 0)
ax4 = axes[1, 1]
T = 600
omega_spinodal = 20000
DG_values = [mixing_gibbs(x, T, omega_spinodal) for x in x_B_range]
ax4.plot(x_B_range, np.array(DG_values) / 1000, 'b-', linewidth=2.5, label='ΔG_mix')
# Tangent line (find the phase-separation compositions by the common tangent method - simplified)
# Shown here only visually
ax4.axhline(0, color='gray', linestyle='--', linewidth=1, alpha=0.5)
ax4.text(0.5, 3, f'T = {T} K\nTendency toward phase separation', fontsize=11, ha='center',
bbox=dict(boxstyle='round', facecolor='yellow', alpha=0.6))
ax4.set_xlabel('Composition x_B')
ax4.set_ylabel('ΔG_mix (kJ/mol)')
ax4.set_title('Phase Separation and Common Tangent')
ax4.legend()
ax4.grid(True, alpha=0.3)
plt.tight_layout()
plt.savefig('materials_mixing_enthalpy.png', dpi=300, bbox_inches='tight')
plt.show()
# Analysis
print("=== Mixing Enthalpy and Phase Stability ===\n")
print("Meaning of the interaction parameter Ω:")
print(" Ω < 0: A-B bonds are stronger than A-A, B-B → attraction, tendency to form a solid solution")
print(" Ω = 0: ideal solution (completely random)")
print(" Ω > 0: A-B bonds are weak → repulsion, tendency toward phase separation")
print()
print("Competition in ΔG_mix = ΔH_mix - T ΔS_mix:")
print(" - Low temperature: ΔH_mix dominates → phase separation if Ω>0")
print(" - High temperature: -TΔS_mix dominates → mixing driven by the entropy term")
print()
# Critical temperature calculation (simplified)
omega_critical = 20000
R = 8.314
T_critical = omega_critical / (2 * R) # Critical temperature of a regular solution
print(f"Critical temperature (regular solution model):")
print(f" T_c = Ω / (2R) = {omega_critical/1000:.0f} / (2 × 8.314)")
print(f" T_c ≈ {T_critical:.0f} K = {T_critical - 273:.0f} °C")
print(f" T > T_c: complete solid solubility")
print(f" T < T_c: phase separation (miscibility gap)")
💻 Code Example 5.7: Configurational Entropy of High-Entropy Alloys (HEAs)
Python Implementation: Configurational Entropy of Multicomponent Systems
import numpy as np
import matplotlib.pyplot as plt
from itertools import combinations
def configurational_entropy_multicomponent(composition, R=8.314):
"""Configurational entropy of a multicomponent system
ΔS_conf = -R Σ x_i ln(x_i)
Args:
composition: atomic fractions of each element (list)
R: gas constant (J/(mol·K))
Returns:
ΔS_conf: configurational entropy (J/(mol·K))
"""
x = np.array(composition)
x = x[x > 0] # Exclude zeros
if np.sum(x) != 1.0:
x = x / np.sum(x) # Normalize
DS_conf = -R * np.sum(x * np.log(x))
return DS_conf
# Representative high-entropy alloys (HEAs)
HEAs = {
'CoCrFeMnNi (Cantor alloy)': [0.2, 0.2, 0.2, 0.2, 0.2], # Equiatomic quinary system
'AlCoCrFeNi': [0.2, 0.2, 0.2, 0.2, 0.2],
'CoCrFeNi': [0.25, 0.25, 0.25, 0.25], # Quaternary system
'TiZrHfNbTa': [0.2, 0.2, 0.2, 0.2, 0.2], # Refractory HEA
}
# Traditional alloys (for comparison)
traditional_alloys = {
'Stainless steel (SUS304)': [0.70, 0.18, 0.08, 0.04], # Fe, Cr, Ni, Mn (simplified)
'Al alloy (7075)': [0.90, 0.05, 0.03, 0.02], # Al, Zn, Mg, Cu (simplified)
'Brass': [0.70, 0.30], # Cu, Zn
}
print("=== Configurational Entropy of High-Entropy Alloys (HEAs) ===\n")
print(f"{'Alloy':<30} {'Elements':<10} {'ΔS_conf (J/(mol·K))':<25}")
print("-" * 65)
# HEA calculations
for name, comp in HEAs.items():
n_elements = len(comp)
DS = configurational_entropy_multicomponent(comp)
print(f"{name:<30} {n_elements:<10} {DS:<25.4f}")
print("\nTraditional alloys (comparison):")
for name, comp in traditional_alloys.items():
n_elements = len(comp)
DS = configurational_entropy_multicomponent(comp)
print(f"{name:<30} {n_elements:<10} {DS:<25.4f}")
# Theoretical maximum values
R = 8.314
print("\nTheoretical maximum values:")
for n in [2, 3, 4, 5, 6]:
DS_max = R * np.log(n)
print(f" {n}-component (equiatomic): ΔS_conf = R ln({n}) = {DS_max:.4f} J/(mol·K)")
# Visualization
fig, axes = plt.subplots(1, 2, figsize=(14, 6))
# Number of elements vs. maximum entropy
ax1 = axes[0]
n_range = np.arange(2, 11)
DS_max_range = R * np.log(n_range)
ax1.plot(n_range, DS_max_range, 'bo-', markersize=8, linewidth=2.5,
label='Theoretical maximum (equiatomic)')
# Plot HEAs and traditional alloys
all_alloys = {**HEAs, **traditional_alloys}
for name, comp in all_alloys.items():
n = len(comp)
DS = configurational_entropy_multicomponent(comp)
marker = 's' if name in HEAs else '^'
color = 'red' if name in HEAs else 'green'
ax1.plot(n, DS, marker, markersize=10, color=color)
ax1.plot([], [], 'rs', markersize=10, label='HEA')
ax1.plot([], [], 'g^', markersize=10, label='Traditional alloys')
ax1.set_xlabel('Number of elements')
ax1.set_ylabel('ΔS_conf (J/(mol·K))')
ax1.set_title('Configurational Entropy vs. Number of Elements')
ax1.legend()
ax1.grid(True, alpha=0.3)
ax1.set_xticks(n_range)
# Effect of composition (varying the content of one element in a quinary system)
ax2 = axes[1]
x_vary = np.linspace(0.05, 0.85, 50)
DS_vary = []
for x1 in x_vary:
# Divide the remainder equally among the other four elements
x_rest = (1 - x1) / 4
comp = [x1, x_rest, x_rest, x_rest, x_rest]
DS = configurational_entropy_multicomponent(comp)
DS_vary.append(DS)
ax2.plot(x_vary, DS_vary, 'b-', linewidth=2.5)
ax2.axvline(0.2, color='red', linestyle='--', linewidth=2,
label='Equiatomic (x=0.2)')
ax2.axhline(R * np.log(5), color='red', linestyle='--', linewidth=1.5, alpha=0.5)
ax2.set_xlabel('Atomic fraction of the first element x₁')
ax2.set_ylabel('ΔS_conf (J/(mol·K))')
ax2.set_title('Composition Dependence of Configurational Entropy (Quinary System)')
ax2.legend()
ax2.grid(True, alpha=0.3)
plt.tight_layout()
plt.savefig('materials_HEA_entropy.png', dpi=300, bbox_inches='tight')
plt.show()
# Definition of HEAs
print("\n=== Definition of High-Entropy Alloys (HEAs) ===")
print("Multicomponent alloys satisfying ΔS_conf ≥ 1.5R")
print(f" 1.5R = 1.5 × 8.314 = {1.5*R:.4f} J/(mol·K)")
print()
print("Characteristics of HEAs:")
print(" 1. High configurational entropy → stabilizes the solid solution")
print(" 2. Multicomponent (usually 5 or more elements)")
print(" 3. Lattice distortion effect → high strength")
print(" 4. Cocktail effect (synergistic effects)")
print()
print("Application examples:")
print(" - CoCrFeMnNi: excellent toughness at cryogenic temperatures")
print(" - TiZrHfNbTa: ultra-high-temperature resistance (refractory HEA)")
print(" - AlCoCrFeNi: high-temperature strength and oxidation resistance")
📚 Summary
- Understand the difference between solid solutions (substitutional and interstitial) and intermetallic compounds
- The Fe-C phase diagram is the foundation of microstructure control in steel (eutectoid point 0.77%, 727°C)
- The α-γ transformation is the allotropic transformation of iron (BCC ⇄ FCC, 912°C)
- The martensitic transformation is a diffusionless phase transition (high-hardness microstructure)
- The Ms point is lowered by carbon and alloying elements (Andrews equation)
- TTT diagrams describe isothermal transformations, CCT diagrams describe continuous cooling transformations
- Microstructure can be controlled through the cooling rate (pearlite, bainite, martensite)
- Evaluate phase stability using the mixing enthalpy (regular solution model)
- High-entropy alloys (HEAs) stabilize solid solutions through configurational entropy
💡 Practice Problems
- [Easy] In the Fe-C system, what microstructure is obtained when a steel with 0.4% carbon is slowly cooled from 800°C? Explain using the phase diagram.
- [Easy] Using the Andrews equation, calculate the Ms point of a steel with C 0.8% and Mn 1.0%.
- [Medium] Using the Koistinen-Marburger equation \(f_M = 1 - \exp(-0.011(M_s - T))\), find the martensite fraction when a steel with Ms=350°C is cooled to 25°C.
- [Medium] Calculate the critical temperature of a binary system with interaction parameter Ω=20 kJ/mol in the regular solution model, given that \(T_c = \Omega / (2R)\).
- [Hard] Calculate the configurational entropy of an equiatomic quinary alloy (20% each) and of an alloy with 80% principal element plus 5% each of four other elements, and determine which satisfies the definition of a high-entropy alloy (ΔS ≥ 1.5R).