🎯 Learning Objectives
- Understand and apply the Gibbs phase rule \(F = C - P + 2\)
- Derive the Clausius-Clapeyron equation and compute vapor pressure curves
- Understand the van der Waals equation of state and the Maxwell construction
- Read binary phase diagrams (eutectic, peritectic, and solid-solution types)
- Calculate phase fractions with the lever rule
- Understand the basics of ternary phase diagrams
- Explain the importance of phase diagrams in materials science
📖 Fundamentals of Phase Equilibria
Phase Equilibrium Conditions
Conditions for two phases α and β to be in equilibrium:
- Thermal equilibrium: \(T^\alpha = T^\beta\)
- Mechanical equilibrium: \(P^\alpha = P^\beta\)
- Chemical equilibrium: \(\mu_i^\alpha = \mu_i^\beta\) (for each component i)
Here \(\mu_i\) is the chemical potential of component i.
Gibbs Phase Rule
The Gibbs phase rule determines the number of degrees of freedom of a system:
\[
F = C - P + 2
\]
- \(F\): degrees of freedom (number of variables that can be varied independently)
- \(C\): number of components (number of independent chemical species)
- \(P\): number of phases
- \(2\): temperature and pressure
Examples:
- Triple point of water: \(C = 1\), \(P = 3\) (solid, liquid, gas) → \(F = 0\) (no degrees of freedom; fixed at a single point)
- Boiling curve of water: \(C = 1\), \(P = 2\) (liquid, gas) → \(F = 1\) (fixing the temperature determines the pressure)
- Binary eutectic point: \(C = 2\), \(P = 3\) → \(F = 1\) (at fixed pressure the temperature is determined)
💻 Example 3.1: Vapor Pressure Curves from the Clausius-Clapeyron Equation
Clausius-Clapeyron Equation
Relation between pressure and temperature along a phase equilibrium curve:
\[
\frac{dP}{dT} = \frac{L}{T \Delta V}
\]
Here \(L\) is the latent heat of the phase transition and \(\Delta V\) is the volume change associated with the transition.
For liquid-gas equilibrium (with \(V_{\text{gas}} \gg V_{\text{liquid}}\) and the ideal-gas approximation):
\[
\frac{d \ln P}{dT} = \frac{L_{\text{vap}}}{RT^2}
\]
Integrating gives:
\[
\ln P = -\frac{L_{\text{vap}}}{RT} + C
\]
Python Implementation: Computing Vapor Pressure Curves
import numpy as np
import matplotlib.pyplot as plt
from scipy.integrate import odeint
from scipy.optimize import fsolve
# Integrated form of the Clausius-Clapeyron equation
def vapor_pressure_clausius_clapeyron(T, L_vap, R, P0, T0):
"""Vapor pressure from the Clausius-Clapeyron equation"""
return P0 * np.exp(-L_vap / R * (1/T - 1/T0))
# Vapor pressure data for water
R = 8.314 # J/(mol·K)
L_vap_water = 40660 # J/mol (heat of vaporization at 100°C)
T0_water = 373.15 # K (100°C)
P0_water = 101325 # Pa (1 atm)
# Temperature range
T_range = np.linspace(273.15, 473.15, 200) # 0-200°C
# Vapor pressure curve
P_vapor_water = vapor_pressure_clausius_clapeyron(T_range, L_vap_water, R, P0_water, T0_water)
# Another substance (ethanol)
L_vap_ethanol = 38560 # J/mol
T0_ethanol = 351.5 # K (78.3°C)
P0_ethanol = 101325 # Pa
P_vapor_ethanol = vapor_pressure_clausius_clapeyron(T_range, L_vap_ethanol, R, P0_ethanol, T0_ethanol)
# Visualization
fig, axes = plt.subplots(1, 2, figsize=(14, 6))
# Vapor pressure curves (linear scale)
ax1 = axes[0]
ax1.plot(T_range - 273.15, P_vapor_water / 1e5, 'b-', linewidth=2, label='H₂O')
ax1.plot(T_range - 273.15, P_vapor_ethanol / 1e5, 'r-', linewidth=2, label='C₂H₅OH')
ax1.axhline(1.0, color='gray', linestyle='--', alpha=0.5, label='1 atm')
ax1.set_xlabel('Temperature (°C)')
ax1.set_ylabel('Vapor pressure (bar)')
ax1.set_title('Vapor Pressure Curves (Clausius-Clapeyron)')
ax1.legend()
ax1.grid(True, alpha=0.3)
# Vapor pressure curves (log scale)
ax2 = axes[1]
ax2.semilogy(T_range - 273.15, P_vapor_water / 1e5, 'b-', linewidth=2, label='H₂O')
ax2.semilogy(T_range - 273.15, P_vapor_ethanol / 1e5, 'r-', linewidth=2, label='C₂H₅OH')
ax2.axhline(1.0, color='gray', linestyle='--', alpha=0.5, label='1 atm')
ax2.set_xlabel('Temperature (°C)')
ax2.set_ylabel('Vapor pressure (bar, log scale)')
ax2.set_title('Vapor Pressure Curves (Log Plot)')
ax2.legend()
ax2.grid(True, alpha=0.3, which='both')
plt.tight_layout()
plt.savefig('phase_vapor_pressure_clausius_clapeyron.png', dpi=300, bbox_inches='tight')
plt.show()
# Boiling point calculation
def boiling_point(P_target, L_vap, R, P0, T0):
"""Compute the boiling point at a specified pressure"""
# Solve ln(P) = -L/(RT) + C for T
T = 1 / (1/T0 - R/L_vap * np.log(P_target / P0))
return T
# Boiling points at various pressures
pressures = [0.5e5, 1.0e5, 2.0e5, 5.0e5] # Pa
print("=== Boiling Point Prediction via Clausius-Clapeyron ===\n")
print(f"{'Pressure (bar)':<15} {'Water b.p. (°C)':<20} {'Ethanol b.p. (°C)':<25}")
print("-" * 60)
for P in pressures:
T_bp_water = boiling_point(P, L_vap_water, R, P0_water, T0_water)
T_bp_ethanol = boiling_point(P, L_vap_ethanol, R, P0_ethanol, T0_ethanol)
print(f"{P/1e5:<15.1f} {T_bp_water - 273.15:<20.2f} {T_bp_ethanol - 273.15:<25.2f}")
# Comparison with the Antoine equation
print("\n=== Comparison with Experiment (Water, 1 atm) ===")
print(f"Clausius-Clapeyron: {boiling_point(101325, L_vap_water, R, P0_water, T0_water) - 273.15:.2f} °C")
print(f"Experimental value: 100.00 °C")
print("\nThe Clausius-Clapeyron equation is highly accurate over narrow temperature ranges")
💻 Example 3.2: van der Waals Isotherms and the Maxwell Construction
van der Waals Equation of State
\[
\left(P + \frac{a}{V^2}\right)(V - b) = RT
\]
At temperatures below the critical point, the isotherms contain an unphysical region (\(\frac{\partial P}{\partial V} > 0\)).
Maxwell construction: Determines the pressure in the gas-liquid coexistence region by the equal-area rule
\[
\int_{V_L}^{V_G} P_{\text{vdW}}(V) dV = P_{\text{eq}}(V_G - V_L)
\]
Python Implementation: van der Waals Isotherms and the Maxwell Construction
import numpy as np
import matplotlib.pyplot as plt
from scipy.optimize import fsolve, minimize
# van der Waals pressure
def P_vdw(V, T, a, b, R):
"""van der Waals equation of state"""
return R * T / (V - b) - a / V**2
# van der Waals parameters for CO₂
R = 8.314e-6 # MPa·m³/(mol·K)
a = 0.3658 # MPa·m⁶/mol²
b = 4.267e-5 # m³/mol
# Critical point
T_c = 8 * a / (27 * R * b)
P_c = a / (27 * b**2)
V_c = 3 * b
print(f"=== Critical Point of CO₂ ===")
print(f"T_c = {T_c:.2f} K = {T_c - 273.15:.2f} °C")
print(f"P_c = {P_c:.4f} MPa")
print(f"V_c = {V_c * 1e6:.2f} cm³/mol\n")
# Implementation of the Maxwell construction
def maxwell_construction(T, a, b, R):
"""Compute the equilibrium pressure via the Maxwell construction"""
# Find the extrema of the van der Waals isotherm
def dP_dV(V):
return -R * T / (V - b)**2 + 2 * a / V**3
# Spinodal points (dP/dV = 0)
V_range = np.linspace(b * 1.1, 10 * b, 1000)
candidates = []
for i in range(len(V_range) - 1):
V1, V2 = V_range[i], V_range[i+1]
if dP_dV(V1) * dP_dV(V2) < 0:
V_spin = fsolve(dP_dV, (V1 + V2) / 2)[0]
candidates.append(V_spin)
if len(candidates) < 2:
return None, None, None
V_spin_min, V_spin_max = sorted(candidates)[:2]
# Maxwell equal-area rule
def area_difference(P_eq):
# Area below P_eq minus area above
def integrand_lower(V):
return P_vdw(V, T, a, b, R) - P_eq
from scipy.integrate import quad
area_lower, _ = quad(integrand_lower, V_spin_min, V_spin_max)
return area_lower
# Search for P_eq
P_min = P_vdw(V_spin_max, T, a, b, R)
P_max = P_vdw(V_spin_min, T, a, b, R)
P_eq = fsolve(area_difference, (P_min + P_max) / 2)[0]
# Find the liquid and gas volumes
def find_volumes(P_eq):
V_liquid = fsolve(lambda V: P_vdw(V, T, a, b, R) - P_eq, V_spin_min)[0]
V_gas = fsolve(lambda V: P_vdw(V, T, a, b, R) - P_eq, V_spin_max)[0]
return V_liquid, V_gas
V_L, V_G = find_volumes(P_eq)
return P_eq, V_L, V_G
# Isotherms at multiple temperatures
temperatures = [280, 300, T_c, 320, 350] # K
colors = ['blue', 'green', 'red', 'orange', 'purple']
fig, ax = plt.subplots(figsize=(10, 8))
V_plot = np.linspace(b * 1.05, 10 * b, 1000)
for T, color in zip(temperatures, colors):
P_plot = [P_vdw(V, T, a, b, R) for V in V_plot]
if T < T_c:
label = f'T = {T:.0f} K (< T_c)'
ax.plot(V_plot * 1e6, P_plot, color=color, linestyle='--',
linewidth=1.5, alpha=0.5)
# Maxwell construction
P_eq, V_L, V_G = maxwell_construction(T, a, b, R)
if P_eq is not None:
ax.plot([V_L * 1e6, V_G * 1e6], [P_eq, P_eq],
color=color, linewidth=2.5, label=label)
elif T == T_c:
label = f'T = {T:.0f} K (= T_c)'
ax.plot(V_plot * 1e6, P_plot, color=color, linewidth=2.5, label=label)
else:
label = f'T = {T:.0f} K (> T_c)'
ax.plot(V_plot * 1e6, P_plot, color=color, linewidth=2, label=label)
# Mark the critical point
ax.plot(V_c * 1e6, P_c, 'ko', markersize=10, label='Critical point')
ax.set_xlabel('Molar volume (cm³/mol)')
ax.set_ylabel('Pressure (MPa)')
ax.set_title('van der Waals Isotherms and Maxwell Construction (CO₂)')
ax.set_xlim([0, 500])
ax.set_ylim([0, 15])
ax.legend()
ax.grid(True, alpha=0.3)
plt.tight_layout()
plt.savefig('phase_vdw_maxwell_construction.png', dpi=300, bbox_inches='tight')
plt.show()
# Numerical results of the Maxwell construction
print("=== Gas-Liquid Equilibrium via Maxwell Construction ===\n")
print(f"{'Temp. (K)':<12} {'Equil. pressure (MPa)':<18} {'Liquid V (cm³/mol)':<20} {'Gas V (cm³/mol)':<20}")
print("-" * 70)
for T in [280, 290, 300]:
P_eq, V_L, V_G = maxwell_construction(T, a, b, R)
if P_eq is not None:
print(f"{T:<12.0f} {P_eq:<18.4f} {V_L*1e6:<20.2f} {V_G*1e6:<20.2f}")
💻 Example 3.3: Drawing a Binary Phase Diagram (Eutectic Type)
Classification of Binary Phase Diagrams
- Eutectic: A eutectic point with the lowest melting temperature exists
- Peritectic: Two solid phases form from the liquid phase
- Complete solid solution: A solid solution forms over the entire composition range
- Monotectic: Liquid-phase separation occurs
Python Implementation: Eutectic Binary Phase Diagram
import numpy as np
import matplotlib.pyplot as plt
# Simplified model of the Pb-Sn system (eutectic type)
# Simplified phase diagram based on experimental data
def pb_sn_phase_diagram():
"""Phase diagram data for the Pb-Sn eutectic system"""
# Composition (Sn atomic fraction)
x_Sn = np.array([0.0, 0.1, 0.183, 0.183, 0.5, 0.619, 0.619, 0.8, 1.0])
# Liquidus
T_liquidus = np.array([327, 300, 183, 183, 220, 183, 183, 210, 232]) # °C
# Solidus - α phase (Pb-rich)
x_alpha = np.array([0.0, 0.05, 0.183])
T_alpha = np.array([327, 250, 183])
# Solidus - β phase (Sn-rich)
x_beta = np.array([0.619, 0.8, 1.0])
T_beta = np.array([183, 210, 232])
# Eutectic temperature
T_eutectic = 183 # °C
x_eutectic = 0.619 # Sn 61.9%
return {
'liquidus': (x_Sn, T_liquidus),
'alpha_solidus': (x_alpha, T_alpha),
'beta_solidus': (x_beta, T_beta),
'eutectic_T': T_eutectic,
'eutectic_x': x_eutectic
}
# Draw the phase diagram
phase_data = pb_sn_phase_diagram()
fig, ax = plt.subplots(figsize=(10, 8))
# Liquidus
x_liq, T_liq = phase_data['liquidus']
ax.plot(x_liq * 100, T_liq, 'r-', linewidth=2.5, label='Liquidus')
# α-phase solidus
x_alpha, T_alpha = phase_data['alpha_solidus']
ax.plot(x_alpha * 100, T_alpha, 'b-', linewidth=2.5, label='α-phase solidus (Pb-rich)')
# β-phase solidus
x_beta, T_beta = phase_data['beta_solidus']
ax.plot(x_beta * 100, T_beta, 'g-', linewidth=2.5, label='β-phase solidus (Sn-rich)')
# Eutectic line
T_e = phase_data['eutectic_T']
ax.axhline(T_e, color='orange', linestyle='--', linewidth=2, label=f'Eutectic temperature ({T_e}°C)')
# Eutectic point
x_e = phase_data['eutectic_x']
ax.plot(x_e * 100, T_e, 'ko', markersize=12, label=f'Eutectic point (Sn {x_e*100:.1f}%)')
# Region labels
ax.text(10, 280, 'L (liquid)', fontsize=14, ha='center', bbox=dict(boxstyle='round', facecolor='white', alpha=0.8))
ax.text(10, 210, 'L + α', fontsize=12, ha='center')
ax.text(70, 210, 'L + β', fontsize=12, ha='center')
ax.text(10, 150, 'α (Pb-rich solid solution)', fontsize=12, ha='center')
ax.text(70, 150, 'β (Sn-rich solid solution)', fontsize=12, ha='center')
ax.text(30, 170, 'α + β (eutectic microstructure)', fontsize=11, ha='center')
ax.set_xlabel('Sn concentration (at%)', fontsize=12)
ax.set_ylabel('Temperature (°C)', fontsize=12)
ax.set_title('Pb-Sn Binary Phase Diagram (Eutectic Type)', fontsize=14, fontweight='bold')
ax.set_xlim([0, 100])
ax.set_ylim([100, 350])
ax.legend(loc='upper right', fontsize=10)
ax.grid(True, alpha=0.3)
plt.tight_layout()
plt.savefig('phase_diagram_pb_sn_eutectic.png', dpi=300, bbox_inches='tight')
plt.show()
# Print phase diagram information
print("=== Pb-Sn Eutectic Phase Diagram ===\n")
print(f"Eutectic point: Sn {x_e*100:.1f}%, temperature {T_e}°C")
print(f"Melting point of Pb: 327°C")
print(f"Melting point of Sn: 232°C")
print(f"Lowest melting point: {T_e}°C (eutectic temperature)")
print("\nApplication: solder (Sn-37Pb, close to the eutectic composition, is traditional)")
💻 Example 3.4: Lever Rule Calculations
Lever Rule
Rule for determining the amount of each phase in a two-phase coexistence region:
When an alloy of composition \(x_0\) separates at temperature \(T\) into an α phase (composition \(x_\alpha\)) and a β phase (composition \(x_\beta\)):
\[
\frac{f_\alpha}{f_\beta} = \frac{x_\beta - x_0}{x_0 - x_\alpha}
\]
Phase fractions:
\[
f_\alpha = \frac{x_\beta - x_0}{x_\beta - x_\alpha}, \quad f_\beta = \frac{x_0 - x_\alpha}{x_\beta - x_\alpha}
\]
Python Implementation: Lever Rule Calculation and Visualization
import numpy as np
import matplotlib.pyplot as plt
def lever_rule(x0, x_alpha, x_beta):
"""Compute phase fractions with the lever rule
Args:
x0: overall composition
x_alpha: α-phase composition
x_beta: β-phase composition
Returns:
f_alpha, f_beta: fraction of each phase
"""
f_beta = (x0 - x_alpha) / (x_beta - x_alpha)
f_alpha = 1 - f_beta
return f_alpha, f_beta
# Concrete example in the Pb-Sn system
# Overall composition: Sn 40%, α + β region at 200°C
x0 = 0.40 # Sn 40%
T = 200 # °C
# Phase compositions at this temperature (read from the phase diagram)
x_alpha = 0.15 # Sn concentration of the α phase (Pb-rich)
x_beta = 0.90 # Sn concentration of the β phase (Sn-rich)
# Lever rule calculation
f_alpha, f_beta = lever_rule(x0, x_alpha, x_beta)
print("=== Application of the Lever Rule ===\n")
print(f"System: Pb-Sn alloy, overall composition Sn {x0*100:.0f}%, temperature {T}°C")
print(f"α-phase composition: Sn {x_alpha*100:.0f}% (Pb-rich)")
print(f"β-phase composition: Sn {x_beta*100:.0f}% (Sn-rich)")
print(f"\nPhase fractions:")
print(f" α phase: {f_alpha*100:.2f}%")
print(f" β phase: {f_beta*100:.2f}%")
print(f"\nCheck: f_α + f_β = {f_alpha + f_beta:.4f} (should be 1.000)")
# Visualization
fig, axes = plt.subplots(1, 2, figsize=(14, 6))
# Lever diagram
ax1 = axes[0]
ax1.plot([x_alpha, x_beta], [1, 1], 'ko-', linewidth=2, markersize=10)
ax1.plot(x0, 1, 'r^', markersize=15, label=f'Overall composition (Sn {x0*100:.0f}%)')
# Lever arms
ax1.plot([x_alpha, x0], [0.95, 0.95], 'b-', linewidth=3, label=f'β-side arm (L_β)')
ax1.plot([x0, x_beta], [0.95, 0.95], 'g-', linewidth=3, label=f'α-side arm (L_α)')
ax1.text(x_alpha, 1.05, f'α phase\n(Sn {x_alpha*100:.0f}%)', ha='center', fontsize=11)
ax1.text(x_beta, 1.05, f'β phase\n(Sn {x_beta*100:.0f}%)', ha='center', fontsize=11)
ax1.text(x0, 0.85, f'f_α/f_β = L_β/L_α', ha='center', fontsize=12, color='red')
ax1.set_xlim([0, 1])
ax1.set_ylim([0.7, 1.2])
ax1.set_xlabel('Sn concentration (atomic fraction)', fontsize=12)
ax1.set_title('Illustration of the Lever Rule', fontsize=13, fontweight='bold')
ax1.legend(loc='lower center', fontsize=10)
ax1.axis('off')
# Phase fractions vs. composition
ax2 = axes[1]
x0_range = np.linspace(x_alpha, x_beta, 100)
f_alpha_range = []
f_beta_range = []
for x in x0_range:
f_a, f_b = lever_rule(x, x_alpha, x_beta)
f_alpha_range.append(f_a)
f_beta_range.append(f_b)
ax2.plot(x0_range * 100, np.array(f_alpha_range) * 100, 'b-',
linewidth=2.5, label='α-phase fraction')
ax2.plot(x0_range * 100, np.array(f_beta_range) * 100, 'g-',
linewidth=2.5, label='β-phase fraction')
ax2.axvline(x0 * 100, color='r', linestyle='--', linewidth=1.5,
label=f'Example: Sn {x0*100:.0f}%')
ax2.set_xlabel('Overall composition Sn (at%)', fontsize=12)
ax2.set_ylabel('Phase fraction (%)', fontsize=12)
ax2.set_title(f'Composition Dependence of Phase Fractions (T = {T}°C)', fontsize=13, fontweight='bold')
ax2.legend(fontsize=10)
ax2.grid(True, alpha=0.3)
plt.tight_layout()
plt.savefig('phase_lever_rule.png', dpi=300, bbox_inches='tight')
plt.show()
# Verify mass conservation
print("\n=== Mass Conservation Check ===")
x_average = f_alpha * x_alpha + f_beta * x_beta
print(f"f_α · x_α + f_β · x_β = {f_alpha:.4f} × {x_alpha:.2f} + {f_beta:.4f} × {x_beta:.2f}")
print(f" = {x_average:.4f}")
print(f"Overall composition x₀ = {x0:.4f}")
print(f"Error = {abs(x_average - x0):.6f}")
💻 Example 3.5: Analysis of Eutectic and Peritectic Points
Python Implementation: Visualizing Eutectic and Peritectic Reactions
import numpy as np
import matplotlib.pyplot as plt
def plot_eutectic_peritectic():
"""Comparison of eutectic and peritectic reactions"""
fig, axes = plt.subplots(1, 2, figsize=(14, 6))
# Eutectic type
ax1 = axes[0]
# Liquidus
x_L_left = np.linspace(0, 0.4, 50)
T_L_left = 400 - 200 * x_L_left
x_L_right = np.linspace(0.4, 1.0, 50)
T_L_right = 320 + 80 * (x_L_right - 0.4)
ax1.plot(x_L_left * 100, T_L_left, 'r-', linewidth=2.5)
ax1.plot(x_L_right * 100, T_L_right, 'r-', linewidth=2.5, label='Liquidus')
# Solidus
ax1.plot([0, 20], [400, 320], 'b-', linewidth=2.5, label='α-phase solidus')
ax1.plot([70, 100], [320, 400], 'g-', linewidth=2.5, label='β-phase solidus')
# Eutectic line
ax1.plot([0, 100], [320, 320], 'orange', linestyle='--', linewidth=2)
# Eutectic point
ax1.plot(40, 320, 'ko', markersize=12)
ax1.text(40, 310, 'Eutectic point E', ha='center', fontsize=12, fontweight='bold')
# Reaction equation
ax1.text(50, 380, 'L → α + β', fontsize=14, ha='center',
bbox=dict(boxstyle='round', facecolor='yellow', alpha=0.7))
# Region labels
ax1.text(20, 370, 'L', fontsize=14, ha='center')
ax1.text(10, 340, 'L+α', fontsize=11, ha='center')
ax1.text(60, 340, 'L+β', fontsize=11, ha='center')
ax1.text(30, 290, 'α+β', fontsize=12, ha='center')
ax1.set_xlabel('Composition B (at%)', fontsize=12)
ax1.set_ylabel('Temperature (°C)', fontsize=12)
ax1.set_title('Eutectic Type', fontsize=14, fontweight='bold')
ax1.set_xlim([0, 100])
ax1.set_ylim([280, 420])
ax1.legend(loc='upper right', fontsize=10)
ax1.grid(True, alpha=0.3)
# Peritectic type
ax2 = axes[1]
# Liquidus
x_L = np.linspace(0, 1.0, 100)
T_L = 500 - 150 * x_L
ax2.plot(x_L * 100, T_L, 'r-', linewidth=2.5, label='Liquidus')
# α-phase solidus
ax2.plot([0, 30], [500, 420], 'b-', linewidth=2.5, label='α-phase solidus')
# β-phase solidus
ax2.plot([50, 100], [420, 350], 'g-', linewidth=2.5, label='β-phase solidus')
# Peritectic line
ax2.plot([0, 100], [420, 420], 'purple', linestyle='--', linewidth=2)
# Peritectic point
ax2.plot(40, 420, 'ko', markersize=12)
ax2.text(40, 410, 'Peritectic point P', ha='center', fontsize=12, fontweight='bold')
# Reaction equation
ax2.text(50, 470, 'L + α → β', fontsize=14, ha='center',
bbox=dict(boxstyle='round', facecolor='lightblue', alpha=0.7))
# Region labels
ax2.text(20, 460, 'L', fontsize=14, ha='center')
ax2.text(15, 440, 'L+α', fontsize=11, ha='center')
ax2.text(60, 440, 'L+β', fontsize=11, ha='center')
ax2.text(15, 390, 'α', fontsize=12, ha='center')
ax2.text(70, 390, 'β', fontsize=12, ha='center')
ax2.text(35, 390, 'α+β', fontsize=11, ha='center')
ax2.set_xlabel('Composition B (at%)', fontsize=12)
ax2.set_ylabel('Temperature (°C)', fontsize=12)
ax2.set_title('Peritectic Type', fontsize=14, fontweight='bold')
ax2.set_xlim([0, 100])
ax2.set_ylim([340, 520])
ax2.legend(loc='upper right', fontsize=10)
ax2.grid(True, alpha=0.3)
plt.tight_layout()
plt.savefig('phase_eutectic_peritectic_comparison.png', dpi=300, bbox_inches='tight')
plt.show()
plot_eutectic_peritectic()
# Explanation of the reactions
print("=== Comparison of Eutectic and Peritectic Reactions ===\n")
print("[Eutectic reaction]")
print(" Reaction: L → α + β")
print(" - The liquid phase separates into two solid phases on cooling")
print(" - Has the lowest melting point (melting point depression)")
print(" - Examples: Pb-Sn (183°C), Al-Si (577°C)")
print(" - Applications: solder, casting alloys")
print()
print("[Peritectic reaction]")
print(" Reaction: L + α → β")
print(" - The liquid phase reacts with solid phase α to form solid phase β")
print(" - In non-equilibrium solidification, residual α tends to remain")
print(" - Examples: Fe-C (1493°C), Pt-Ag")
print(" - Challenges: homogenization is difficult; segregation occurs easily")
print()
print("[Checking the Gibbs phase rule]")
print(" Eutectic/peritectic point: C = 2, P = 3 → F = 2 - 3 + 2 = 1")
print(" At fixed pressure F = 0 → both temperature and composition are fixed (invariant system)")
💻 Example 3.6: Visualizing Ternary Phase Diagrams
Python Implementation: Ternary Phase Diagram (Isothermal Section)
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.patches import Polygon
from matplotlib.collections import PatchCollection
def ternary_to_cartesian(a, b, c):
"""Convert ternary composition coordinates to Cartesian coordinates
Args:
a, b, c: atomic fraction of each component (a + b + c = 1)
Returns:
x, y: Cartesian coordinates
"""
x = 0.5 * (2 * b + c) / (a + b + c)
y = (np.sqrt(3) / 2) * c / (a + b + c)
return x, y
def plot_ternary_diagram():
"""Basic template for a ternary phase diagram"""
fig, ax = plt.subplots(figsize=(10, 9))
# Vertices of the triangle
vertices = np.array([
[0, 0], # A (bottom left)
[1, 0], # B (bottom right)
[0.5, np.sqrt(3)/2] # C (top)
])
# Triangle frame
triangle = Polygon(vertices, fill=False, edgecolor='black', linewidth=2)
ax.add_patch(triangle)
# Grid lines (10% increments)
for i in range(1, 10):
frac = i / 10
# Parallel to the A-B axis (constant fraction of C)
x1, y1 = ternary_to_cartesian(1-frac, 0, frac)
x2, y2 = ternary_to_cartesian(0, 1-frac, frac)
ax.plot([x1, x2], [y1, y2], 'gray', linewidth=0.5, alpha=0.5)
# Parallel to the B-C axis (constant fraction of A)
x1, y1 = ternary_to_cartesian(frac, 1-frac, 0)
x2, y2 = ternary_to_cartesian(frac, 0, 1-frac)
ax.plot([x1, x2], [y1, y2], 'gray', linewidth=0.5, alpha=0.5)
# Parallel to the C-A axis (constant fraction of B)
x1, y1 = ternary_to_cartesian(1-frac, frac, 0)
x2, y2 = ternary_to_cartesian(0, frac, 1-frac)
ax.plot([x1, x2], [y1, y2], 'gray', linewidth=0.5, alpha=0.5)
# Vertex labels
ax.text(0, -0.05, 'A', fontsize=16, ha='center', fontweight='bold')
ax.text(1, -0.05, 'B', fontsize=16, ha='center', fontweight='bold')
ax.text(0.5, np.sqrt(3)/2 + 0.05, 'C', fontsize=16, ha='center', fontweight='bold')
# Axis labels
for i in [0.2, 0.4, 0.6, 0.8]:
# A axis
x, y = ternary_to_cartesian(1-i, i, 0)
ax.text(x, y - 0.03, f'{int(i*100)}', fontsize=9, ha='center', color='blue')
# B axis
x, y = ternary_to_cartesian(i, 0, 1-i)
ax.text(x + 0.03, y, f'{int(i*100)}', fontsize=9, ha='left', color='green')
# C axis
x, y = ternary_to_cartesian(0, 1-i, i)
ax.text(x - 0.03, y, f'{int(i*100)}', fontsize=9, ha='right', color='red')
# Example: draw single-phase and two-phase regions (mock data)
# Single-phase region α (A-rich)
alpha_region = np.array([
ternary_to_cartesian(1.0, 0.0, 0.0),
ternary_to_cartesian(0.8, 0.2, 0.0),
ternary_to_cartesian(0.8, 0.0, 0.2),
])
alpha_patch = Polygon(alpha_region, facecolor='lightblue', edgecolor='blue',
linewidth=1.5, alpha=0.5, label='α phase')
ax.add_patch(alpha_patch)
ax.text(0.85, 0.05, 'α', fontsize=14, ha='center', fontweight='bold')
# Single-phase region β (B-rich)
beta_region = np.array([
ternary_to_cartesian(0.0, 1.0, 0.0),
ternary_to_cartesian(0.2, 0.8, 0.0),
ternary_to_cartesian(0.0, 0.8, 0.2),
])
beta_patch = Polygon(beta_region, facecolor='lightgreen', edgecolor='green',
linewidth=1.5, alpha=0.5, label='β phase')
ax.add_patch(beta_patch)
ax.text(0.15, 0.05, 'β', fontsize=14, ha='center', fontweight='bold')
# Single-phase region γ (C-rich)
gamma_region = np.array([
ternary_to_cartesian(0.0, 0.0, 1.0),
ternary_to_cartesian(0.2, 0.0, 0.8),
ternary_to_cartesian(0.0, 0.2, 0.8),
])
gamma_patch = Polygon(gamma_region, facecolor='lightcoral', edgecolor='red',
linewidth=1.5, alpha=0.5, label='γ phase')
ax.add_patch(gamma_patch)
ax.text(0.5, 0.75, 'γ', fontsize=14, ha='center', fontweight='bold')
# Two-phase region
ax.text(0.5, 0.4, 'α+β+γ\n(three-phase region)', fontsize=12, ha='center',
bbox=dict(boxstyle='round', facecolor='yellow', alpha=0.6))
ax.set_xlim([-0.1, 1.1])
ax.set_ylim([-0.15, 1.0])
ax.set_aspect('equal')
ax.axis('off')
ax.set_title('Ternary Phase Diagram (A-B-C System, Isothermal Section)', fontsize=15, fontweight='bold', pad=20)
ax.legend(loc='upper right', fontsize=11)
plt.tight_layout()
plt.savefig('phase_ternary_diagram.png', dpi=300, bbox_inches='tight')
plt.show()
plot_ternary_diagram()
# Example composition calculations for a ternary system
print("=== Examples of Ternary Compositions ===\n")
# Example: Al-Cu-Mg alloy system
compositions = [
("Al 90% - Cu 5% - Mg 5%", 0.90, 0.05, 0.05),
("Al 70% - Cu 20% - Mg 10%", 0.70, 0.20, 0.10),
("Al 50% - Cu 30% - Mg 20%", 0.50, 0.30, 0.20),
]
print(f"{'Composition':<30} {'Al':<8} {'Cu':<8} {'Mg':<8} {'Total':<8}")
print("-" * 65)
for name, a, b, c in compositions:
total = a + b + c
print(f"{name:<30} {a:<8.2f} {b:<8.2f} {c:<8.2f} {total:<8.2f}")
print("\nHow to read a ternary phase diagram:")
print(" 1. The three vertices are the pure elements (A, B, C)")
print(" 2. Each edge is a binary system")
print(" 3. The interior represents ternary compositions")
print(" 4. Gibbs phase rule: C = 3, T and P fixed → F = 3 - P")
print(" - Single-phase region: F = 2 (two composition variables can be chosen freely)")
print(" - Two-phase region: F = 1 (only one variable is free)")
print(" - Three-phase region: F = 0 (compositions fixed; tie lines)")
💻 Example 3.7: Calculating Phase Transition Enthalpies
Python Implementation: Phase Transition Enthalpy and the Clapeyron Equation
import numpy as np
import matplotlib.pyplot as plt
from scipy.optimize import curve_fit
# Experimental data (mock): vapor pressure of water
T_exp = np.array([273.15, 283.15, 293.15, 303.15, 313.15,
323.15, 333.15, 343.15, 353.15, 363.15, 373.15]) # K
P_exp = np.array([611, 1228, 2339, 4246, 7384,
12350, 19940, 31190, 47410, 70140, 101325]) # Pa
# Fitting the Clausius-Clapeyron equation
# ln(P) = -L_vap/(R·T) + C
def clausius_clapeyron_fit(T, L_vap, C):
"""ln(P) = -L_vap/(R·T) + C"""
R = 8.314
return -L_vap / (R * T) + C
# Fitting
params, cov = curve_fit(clausius_clapeyron_fit, T_exp, np.log(P_exp))
L_vap_fit, C_fit = params
L_vap_err = np.sqrt(cov[0, 0])
print("=== Fitting the Clausius-Clapeyron Equation ===\n")
print(f"Enthalpy of vaporization L_vap = {L_vap_fit:.0f} ± {L_vap_err:.0f} J/mol")
print(f"Literature value (100°C): 40660 J/mol")
print(f"Relative error: {abs(L_vap_fit - 40660) / 40660 * 100:.2f}%\n")
# Visualize the fit results
T_fit = np.linspace(270, 380, 100)
P_fit = np.exp(clausius_clapeyron_fit(T_fit, L_vap_fit, C_fit))
fig, axes = plt.subplots(1, 2, figsize=(14, 6))
# Linear plot
ax1 = axes[0]
ax1.plot(T_exp - 273.15, P_exp / 1e3, 'bo', markersize=8, label='Experimental data')
ax1.plot(T_fit - 273.15, P_fit / 1e3, 'r-', linewidth=2, label='Fit')
ax1.set_xlabel('Temperature (°C)')
ax1.set_ylabel('Vapor pressure (kPa)')
ax1.set_title('Vapor Pressure Curve of Water')
ax1.legend()
ax1.grid(True, alpha=0.3)
# Clausius-Clapeyron plot
ax2 = axes[1]
ax2.plot(1000 / T_exp, np.log(P_exp), 'bo', markersize=8, label='Experimental data')
ax2.plot(1000 / T_fit, np.log(P_fit), 'r-', linewidth=2, label='Fit')
ax2.set_xlabel('1000/T (K⁻¹)')
ax2.set_ylabel('ln(P)')
ax2.set_title('Clausius-Clapeyron Plot')
ax2.legend()
ax2.grid(True, alpha=0.3)
plt.tight_layout()
plt.savefig('phase_transition_enthalpy_fit.png', dpi=300, bbox_inches='tight')
plt.show()
# Comparison with other substances
substances = {
'H₂O': 40660,
'C₂H₅OH (ethanol)': 38560,
'CH₃OH (methanol)': 35270,
'C₆H₆ (benzene)': 30720,
'N₂': 5577,
'O₂': 6820,
}
print("=== Comparison of Enthalpies of Vaporization ===\n")
print(f"{'Substance':<20} {'L_vap (J/mol)':<18} {'L_vap (kJ/mol)':<18}")
print("-" * 60)
for substance, L in substances.items():
print(f"{substance:<20} {L:<18.0f} {L/1000:<18.2f}")
print("\nEffect of hydrogen bonding:")
print(" - Water has a large enthalpy of vaporization due to strong hydrogen bonds")
print(" - Ethanol and methanol also form hydrogen bonds")
print(" - N₂ and O₂ have weak intermolecular forces and small enthalpies of vaporization")
📚 Summary
- The Gibbs phase rule \(F = C - P + 2\) determines the degrees of freedom of a system
- The Clausius-Clapeyron equation relates phase equilibrium curves to phase transition enthalpies
- The Maxwell construction on the van der Waals equation of state describes gas-liquid coexistence
- Binary phase diagrams include eutectic, peritectic, and complete solid-solution types
- The lever rule gives the phase fractions in two-phase coexistence regions
- The eutectic reaction (L → α + β) and the peritectic reaction (L + α → β) are important phase transformations
- Ternary phase diagrams are represented in triangular coordinates and are important for materials design
- Phase diagrams are indispensable tools for process design and microstructure control of materials
💡 Practice Problems
- [Easy] Use the Gibbs phase rule to determine the degrees of freedom at the triple point of CO₂ (solid, liquid, and gas coexisting).
- [Easy] Use the lever rule to calculate the fraction of each phase when a Pb-Sn alloy with Sn 30% separates at 200°C into an α phase (Sn 15%) and a β phase (Sn 90%).
- [Medium] Given that the enthalpy of vaporization of water is 40.66 kJ/mol, calculate the vapor pressure at 90°C, assuming the vapor pressure at 100°C is 1 atm.
- [Medium] Show that at the critical point of a van der Waals gas \(\left(\frac{\partial P}{\partial V}\right)_T = \left(\frac{\partial^2 P}{\partial V^2}\right)_T = 0\), and from this derive \(T_c = \frac{8a}{27Rb}\).
- [Hard] For a binary eutectic system with eutectic composition 40%, eutectic temperature 300°C, and melting points of pure A and pure B of 500°C and 450°C respectively, determine the liquidus using a parabolic approximation and calculate the liquidus temperature of an alloy with composition 30%.