Learning Objectives
- Explain nanoparticle surface characteristics and size effects
- Calculate surface atom ratios and specific surface areas
- Understand van der Waals attraction and Hamaker constants
- Describe the electric double layer model
- Analyze capillary forces and sintering effects
- Distinguish between aggregation and agglomeration (IUPAC definitions)
1.1 Nanoparticle Characteristics and Surface Effects
Nanoparticles (typically 1-100 nm) exhibit unique properties due to their high surface-to-volume ratio. As particle size decreases, an increasing fraction of atoms reside at the surface, fundamentally changing the material's behavior.
Why Nanoparticles Agglomerate
The high surface energy of nanoparticles makes them thermodynamically unstable. The system tends to minimize surface energy by reducing total surface area through agglomeration. For a 10 nm particle, approximately 30% of atoms are at the surface!
Example 1: Surface Atom Ratio Calculation
We calculate and visualize how the fraction of surface atoms changes with particle size.
import numpy as np
import matplotlib.pyplot as plt
# ===================================
# Example 1: Surface Atom Ratio vs Particle Size
# ===================================
def surface_atom_fraction(d_nm, atom_diameter_nm=0.3):
"""
Calculate the fraction of atoms at the surface of a spherical nanoparticle.
Parameters:
d_nm: Particle diameter in nm
atom_diameter_nm: Approximate atomic diameter (default 0.3 nm for metals)
Returns:
Fraction of surface atoms
"""
# Simple shell model approximation
r = d_nm / 2
r_atom = atom_diameter_nm / 2
# Number of atoms (volume-based estimate)
V_particle = (4/3) * np.pi * r**3
V_atom = (4/3) * np.pi * r_atom**3
n_total = V_particle / V_atom
# Surface atoms (shell thickness ~ one atomic diameter)
r_inner = max(0, r - atom_diameter_nm)
V_inner = (4/3) * np.pi * r_inner**3
n_inner = V_inner / V_atom
n_surface = n_total - n_inner
return n_surface / n_total if n_total > 0 else 1.0
def specific_surface_area(d_nm, density_g_cm3):
"""
Calculate specific surface area (m²/g) for spherical particles.
Parameters:
d_nm: Particle diameter in nm
density_g_cm3: Material density in g/cm³
Returns:
Specific surface area in m²/g
"""
d_m = d_nm * 1e-9
density_kg_m3 = density_g_cm3 * 1000
# SSA = 6 / (d * rho) for spheres
ssa = 6 / (d_m * density_kg_m3)
return ssa
# Calculate for range of particle sizes
diameters = np.logspace(0, 3, 100) # 1 nm to 1000 nm
surface_fractions = [surface_atom_fraction(d) for d in diameters]
# Common materials for SSA calculation
materials = {
'Gold (Au)': 19.3,
'Silver (Ag)': 10.5,
'Silica (SiO2)': 2.2,
'Titania (TiO2)': 4.2,
'Iron Oxide (Fe3O4)': 5.2
}
# Create figure
fig, axes = plt.subplots(1, 2, figsize=(14, 5))
# Plot 1: Surface atom fraction
ax1 = axes[0]
ax1.semilogx(diameters, np.array(surface_fractions) * 100, 'b-', linewidth=2.5)
ax1.axhline(y=50, color='r', linestyle='--', alpha=0.7, label='50% surface atoms')
ax1.axvline(x=10, color='g', linestyle='--', alpha=0.7, label='10 nm')
ax1.fill_between(diameters, 0, np.array(surface_fractions) * 100, alpha=0.2)
ax1.set_xlabel('Particle Diameter (nm)', fontsize=12)
ax1.set_ylabel('Surface Atom Fraction (%)', fontsize=12)
ax1.set_title('Surface Atom Ratio vs Particle Size', fontsize=13, fontweight='bold')
ax1.set_xlim(1, 1000)
ax1.set_ylim(0, 100)
ax1.grid(True, alpha=0.3)
ax1.legend()
# Plot 2: Specific Surface Area
ax2 = axes[1]
colors = plt.cm.viridis(np.linspace(0, 0.8, len(materials)))
for (material, density), color in zip(materials.items(), colors):
ssa = [specific_surface_area(d, density) for d in diameters]
ax2.loglog(diameters, ssa, linewidth=2, label=material, color=color)
ax2.axhline(y=100, color='gray', linestyle=':', alpha=0.7, label='100 m²/g')
ax2.set_xlabel('Particle Diameter (nm)', fontsize=12)
ax2.set_ylabel('Specific Surface Area (m²/g)', fontsize=12)
ax2.set_title('Specific Surface Area by Material', fontsize=13, fontweight='bold')
ax2.set_xlim(1, 1000)
ax2.grid(True, alpha=0.3, which='both')
ax2.legend(loc='upper right', fontsize=9)
plt.tight_layout()
plt.savefig('surface_effects.png', dpi=150, bbox_inches='tight')
plt.show()
# Print key values
print("=== Surface Characteristics at Key Sizes ===")
for d in [5, 10, 20, 50, 100]:
frac = surface_atom_fraction(d)
ssa_au = specific_surface_area(d, 19.3)
ssa_sio2 = specific_surface_area(d, 2.2)
print(f"d = {d:3d} nm: Surface atoms = {frac*100:5.1f}%, "
f"SSA(Au) = {ssa_au:6.1f} m²/g, SSA(SiO2) = {ssa_sio2:6.1f} m²/g")
=== Surface Characteristics at Key Sizes ===
d = 5 nm: Surface atoms = 78.4%, SSA(Au) = 62.2 m²/g, SSA(SiO2) = 545.5 m²/g
d = 10 nm: Surface atoms = 48.8%, SSA(Au) = 31.1 m²/g, SSA(SiO2) = 272.7 m²/g
d = 20 nm: Surface atoms = 27.1%, SSA(Au) = 15.5 m²/g, SSA(SiO2) = 136.4 m²/g
d = 50 nm: Surface atoms = 11.5%, SSA(Au) = 6.2 m²/g, SSA(SiO2) = 54.5 m²/g
d = 100 nm: Surface atoms = 5.9%, SSA(Au) = 3.1 m²/g, SSA(SiO2) = 27.3 m²/g
Practical Implications
At 10 nm, nearly half the atoms are at the surface! This explains why nanoparticle catalysts are so active (high surface area) but also why they tend to agglomerate (high surface energy drives minimization of surface area).
1.2 Van der Waals Force-Driven Agglomeration
Van der Waals (vdW) forces are the primary attractive forces between nanoparticles. These arise from fluctuating dipoles (London dispersion) and are always attractive. The Hamaker constant \(A_H\) characterizes the strength of vdW interactions for a given material pair.
Hamaker Constant
The Hamaker constant \(A_H\) typically ranges from \(10^{-21}\) to \(10^{-19}\) J:
- Metals: 30-50 × 10⁻²⁰ J (strong attraction)
- Oxides: 5-15 × 10⁻²⁰ J (moderate)
- Polymers: 3-10 × 10⁻²⁰ J (weaker)
Van der Waals Interaction Energy
For two spherical particles of radii \(R_1\) and \(R_2\) separated by surface distance \(h\):
Equal spheres (R₁ = R₂ = R), small separation (h << R):
$$V_{vdW} = -\frac{A_H R}{12 h}$$General expression:
$$V_{vdW} = -\frac{A_H}{6} \left[ \frac{2R_1 R_2}{h(h + 2R_1 + 2R_2)} + \frac{2R_1 R_2}{(h + 2R_1)(h + 2R_2)} + \ln\frac{h(h + 2R_1 + 2R_2)}{(h + 2R_1)(h + 2R_2)} \right]$$Example 2: Van der Waals Attraction Between Nanoparticles
import numpy as np
import matplotlib.pyplot as plt
# ===================================
# Example 2: Van der Waals Attraction Between Spherical Nanoparticles
# ===================================
def vdw_energy_spheres(h, R1, R2, A_H):
"""
Calculate van der Waals interaction energy between two spheres.
Parameters:
h: Surface-to-surface separation (m)
R1, R2: Radii of the two spheres (m)
A_H: Hamaker constant (J)
Returns:
Interaction energy (J)
"""
# Avoid division by zero
h = np.maximum(h, 1e-12)
term1 = (2 * R1 * R2) / (h * (h + 2*R1 + 2*R2))
term2 = (2 * R1 * R2) / ((h + 2*R1) * (h + 2*R2))
term3 = np.log((h * (h + 2*R1 + 2*R2)) / ((h + 2*R1) * (h + 2*R2)))
V = -(A_H / 6) * (term1 + term2 + term3)
return V
def vdw_force_spheres(h, R1, R2, A_H):
"""
Calculate van der Waals force between two equal spheres (simplified).
F = -dV/dh
For equal spheres at small separation: F ≈ A_H * R / (12 * h²)
"""
h = np.maximum(h, 1e-12)
R = (R1 + R2) / 2 # Average radius
F = A_H * R / (12 * h**2)
return F
# Hamaker constants for different materials (in J)
hamaker_constants = {
'Au-Au (in vacuum)': 45e-20,
'Au-Au (in water)': 30e-20,
'SiO2-SiO2 (in vacuum)': 6.5e-20,
'SiO2-SiO2 (in water)': 0.83e-20,
'TiO2-TiO2 (in water)': 5.3e-20,
'Polystyrene (in water)': 1.3e-20
}
# Parameters
R = 10e-9 # 10 nm radius (20 nm diameter)
h_range = np.logspace(-10, -7, 200) # 0.1 nm to 100 nm separation
# Calculate interaction energies
fig, axes = plt.subplots(1, 2, figsize=(14, 5))
# Plot 1: Energy vs separation for different materials
ax1 = axes[0]
colors = plt.cm.tab10(np.linspace(0, 1, len(hamaker_constants)))
for (material, A_H), color in zip(hamaker_constants.items(), colors):
V = vdw_energy_spheres(h_range, R, R, A_H)
V_kT = V / (1.38e-23 * 300) # Convert to kT units
ax1.semilogx(h_range * 1e9, V_kT, linewidth=2, label=material, color=color)
ax1.axhline(y=-1, color='gray', linestyle='--', alpha=0.7)
ax1.text(0.15, -0.5, '-1 kT', fontsize=10, color='gray')
ax1.set_xlabel('Separation Distance (nm)', fontsize=12)
ax1.set_ylabel('Interaction Energy (kT at 300K)', fontsize=12)
ax1.set_title('Van der Waals Energy: 20 nm Particles', fontsize=13, fontweight='bold')
ax1.set_xlim(0.1, 100)
ax1.set_ylim(-100, 5)
ax1.legend(loc='lower right', fontsize=8)
ax1.grid(True, alpha=0.3)
# Plot 2: Effect of particle size
ax2 = axes[1]
A_H = 30e-20 # Au in water
radii_nm = [5, 10, 25, 50, 100]
colors2 = plt.cm.plasma(np.linspace(0.1, 0.9, len(radii_nm)))
for R_nm, color in zip(radii_nm, colors2):
R_m = R_nm * 1e-9
V = vdw_energy_spheres(h_range, R_m, R_m, A_H)
V_kT = V / (1.38e-23 * 300)
ax2.semilogx(h_range * 1e9, V_kT, linewidth=2,
label=f'd = {2*R_nm} nm', color=color)
ax2.axhline(y=-10, color='red', linestyle=':', alpha=0.7)
ax2.text(0.15, -8, 'Strong attraction (-10 kT)', fontsize=9, color='red')
ax2.set_xlabel('Separation Distance (nm)', fontsize=12)
ax2.set_ylabel('Interaction Energy (kT at 300K)', fontsize=12)
ax2.set_title('Size Effect on vdW Attraction (Au in water)', fontsize=13, fontweight='bold')
ax2.set_xlim(0.1, 100)
ax2.set_ylim(-200, 10)
ax2.legend(loc='lower right')
ax2.grid(True, alpha=0.3)
plt.tight_layout()
plt.savefig('vdw_attraction.png', dpi=150, bbox_inches='tight')
plt.show()
# Print key values at 1 nm separation
print("\n=== Van der Waals Energy at 1 nm Separation ===")
h_1nm = 1e-9
for material, A_H in hamaker_constants.items():
V = vdw_energy_spheres(h_1nm, 10e-9, 10e-9, A_H)
V_kT = V / (1.38e-23 * 300)
print(f"{material:30s}: V = {V_kT:8.1f} kT")
=== Van der Waals Energy at 1 nm Separation ===
Au-Au (in vacuum) : V = -76.8 kT
Au-Au (in water) : V = -51.2 kT
SiO2-SiO2 (in vacuum) : V = -11.1 kT
SiO2-SiO2 (in water) : V = -1.4 kT
TiO2-TiO2 (in water) : V = -9.0 kT
Polystyrene (in water) : V = -2.2 kT
Critical Insight
When interaction energy exceeds ~10 kT, thermal motion cannot overcome attraction and particles will irreversibly agglomerate. Gold nanoparticles in water at 1 nm separation experience ~50 kT attraction - explaining why gold nanoparticles readily aggregate without stabilization!
1.3 Electrostatic Interactions
Charged particles in electrolyte solutions develop an electric double layer (EDL) consisting of the charged surface and a diffuse layer of counterions. This creates electrostatic repulsion that can stabilize dispersions.
Electric Double Layer Model
Surface potential decay (Gouy-Chapman):
$$\psi(x) = \psi_0 \exp(-\kappa x)$$Debye length (characteristic decay distance):
$$\kappa^{-1} = \lambda_D = \sqrt{\frac{\varepsilon_r \varepsilon_0 k_B T}{2 N_A e^2 I}}$$where \(I\) is ionic strength: \(I = \frac{1}{2}\sum_i c_i z_i^2\)
Example 3: Electric Double Layer and Debye Length
import numpy as np
import matplotlib.pyplot as plt
# ===================================
# Example 3: Electric Double Layer and Debye Length
# ===================================
# Physical constants
epsilon_0 = 8.854e-12 # Vacuum permittivity (F/m)
epsilon_r = 78.5 # Water relative permittivity at 25°C
kB = 1.38e-23 # Boltzmann constant (J/K)
T = 298 # Temperature (K)
e = 1.602e-19 # Elementary charge (C)
NA = 6.022e23 # Avogadro's number
def debye_length(ionic_strength_M):
"""
Calculate Debye length for a 1:1 electrolyte in water at 25°C.
Parameters:
ionic_strength_M: Ionic strength in mol/L (M)
Returns:
Debye length in meters
"""
I = ionic_strength_M * 1000 # Convert to mol/m³
kappa_inv = np.sqrt((epsilon_r * epsilon_0 * kB * T) / (2 * NA * e**2 * I))
return kappa_inv
def potential_decay(x, psi_0, kappa):
"""
Calculate potential as function of distance from surface.
Parameters:
x: Distance from surface (m)
psi_0: Surface potential (V)
kappa: Inverse Debye length (1/m)
Returns:
Potential at distance x (V)
"""
return psi_0 * np.exp(-kappa * x)
def electrostatic_repulsion(h, R, psi_0, ionic_strength_M):
"""
Calculate electrostatic repulsion between two equal spheres.
Linear superposition approximation for κR >> 1.
Parameters:
h: Surface separation (m)
R: Particle radius (m)
psi_0: Surface potential (V)
ionic_strength_M: Ionic strength (M)
Returns:
Interaction energy (J)
"""
kappa = 1 / debye_length(ionic_strength_M)
# For constant potential surfaces
V_elec = 2 * np.pi * epsilon_r * epsilon_0 * R * psi_0**2 * np.log(1 + np.exp(-kappa * h))
return V_elec
# Calculate Debye length for various ionic strengths
ionic_strengths = np.logspace(-5, 0, 100) # 10 μM to 1 M
debye_lengths = [debye_length(I) * 1e9 for I in ionic_strengths] # in nm
# Common electrolyte concentrations
common_solutions = {
'Ultrapure water': 1e-7, # ~1 μM from autoionization
'DI water (CO2)': 1e-5, # ~10 μM with dissolved CO2
'1 mM NaCl': 1e-3,
'10 mM NaCl': 1e-2,
'100 mM NaCl': 0.1,
'Seawater (~0.6M)': 0.6
}
# Create plots
fig, axes = plt.subplots(1, 3, figsize=(15, 4.5))
# Plot 1: Debye length vs ionic strength
ax1 = axes[0]
ax1.loglog(ionic_strengths * 1000, debye_lengths, 'b-', linewidth=2.5)
for name, I in common_solutions.items():
lambda_D = debye_length(I) * 1e9
ax1.scatter([I * 1000], [lambda_D], s=80, zorder=5)
ax1.annotate(f'{name}\n({lambda_D:.1f} nm)',
xy=(I * 1000, lambda_D), xytext=(5, 5),
textcoords='offset points', fontsize=8)
ax1.set_xlabel('Ionic Strength (mM)', fontsize=11)
ax1.set_ylabel('Debye Length (nm)', fontsize=11)
ax1.set_title('Debye Length vs Ionic Strength', fontsize=12, fontweight='bold')
ax1.grid(True, alpha=0.3, which='both')
ax1.set_xlim(1e-2, 1e3)
ax1.set_ylim(0.1, 1000)
# Plot 2: Potential decay profiles
ax2 = axes[1]
x = np.linspace(0, 50e-9, 200)
psi_0 = 0.050 # 50 mV surface potential
for I_mM, color, ls in [(1, 'blue', '-'), (10, 'green', '--'), (100, 'red', ':')]:
I = I_mM / 1000
kappa = 1 / debye_length(I)
psi = potential_decay(x, psi_0, kappa)
lambda_D = debye_length(I) * 1e9
ax2.plot(x * 1e9, psi * 1000, color=color, linestyle=ls, linewidth=2,
label=f'{I_mM} mM (λD = {lambda_D:.1f} nm)')
ax2.axhline(y=psi_0 * 1000 / np.e, color='gray', linestyle=':', alpha=0.7)
ax2.text(45, psi_0 * 1000 / np.e + 2, 'ψ₀/e', fontsize=10, color='gray')
ax2.set_xlabel('Distance from Surface (nm)', fontsize=11)
ax2.set_ylabel('Potential (mV)', fontsize=11)
ax2.set_title('Potential Decay (ψ₀ = 50 mV)', fontsize=12, fontweight='bold')
ax2.legend(loc='upper right')
ax2.grid(True, alpha=0.3)
ax2.set_xlim(0, 50)
# Plot 3: Electrostatic repulsion energy
ax3 = axes[2]
h_range = np.linspace(0.5e-9, 30e-9, 200)
R = 10e-9 # 10 nm radius
psi_0 = 0.030 # 30 mV
for I_mM, color in [(1, 'blue'), (10, 'green'), (100, 'red')]:
I = I_mM / 1000
V_elec = [electrostatic_repulsion(h, R, psi_0, I) for h in h_range]
V_kT = np.array(V_elec) / (kB * T)
ax3.plot(h_range * 1e9, V_kT, color=color, linewidth=2,
label=f'{I_mM} mM')
ax3.axhline(y=10, color='gray', linestyle='--', alpha=0.7)
ax3.text(25, 12, 'Barrier > 10 kT', fontsize=9, color='gray')
ax3.set_xlabel('Separation Distance (nm)', fontsize=11)
ax3.set_ylabel('Repulsion Energy (kT)', fontsize=11)
ax3.set_title('Electrostatic Repulsion (R=10nm, ψ₀=30mV)', fontsize=12, fontweight='bold')
ax3.legend(loc='upper right')
ax3.grid(True, alpha=0.3)
ax3.set_xlim(0, 30)
ax3.set_ylim(0, 50)
plt.tight_layout()
plt.savefig('electric_double_layer.png', dpi=150, bbox_inches='tight')
plt.show()
# Print Debye lengths for common solutions
print("\n=== Debye Lengths for Common Solutions (25°C) ===")
for name, I in sorted(common_solutions.items(), key=lambda x: x[1]):
lambda_D = debye_length(I)
print(f"{name:25s}: I = {I*1000:8.3f} mM, λD = {lambda_D*1e9:8.2f} nm")
=== Debye Lengths for Common Solutions (25°C) ===
Ultrapure water : I = 0.000 mM, λD = 961.47 nm
DI water (CO2) : I = 0.010 mM, λD = 96.15 nm
1 mM NaCl : I = 1.000 mM, λD = 9.61 nm
10 mM NaCl : I = 10.000 mM, λD = 3.04 nm
100 mM NaCl : I = 100.000 mM, λD = 0.96 nm
Seawater (~0.6M) : I = 600.000 mM, λD = 0.39 nm
Practical Implication
The Debye length determines the range of electrostatic repulsion. In 10 mM NaCl (typical buffer), repulsion extends only ~3 nm from the surface. In seawater, the double layer is compressed to <1 nm, eliminating electrostatic stabilization!
1.4 Capillary Forces (Liquid Bridges)
When particles are exposed to moisture or during drying processes, liquid bridges form between particles creating strong capillary forces. These forces can be the dominant cause of agglomeration in humid environments or spray-dried powders.
Capillary force between two spheres (equal radii R):
$$F_{cap} = 2\pi R \gamma \cos\theta$$where \(\gamma\) is surface tension and \(\theta\) is contact angle.
For water at 25°C: \(\gamma = 0.072\) N/m
Example 4: Capillary Force Calculation
import numpy as np
import matplotlib.pyplot as plt
# ===================================
# Example 4: Capillary Force Between Nanoparticles
# ===================================
def capillary_force(R, gamma, theta_deg):
"""
Calculate capillary force between two equal spheres.
Parameters:
R: Particle radius (m)
gamma: Surface tension (N/m)
theta_deg: Contact angle (degrees)
Returns:
Capillary force (N)
"""
theta_rad = np.radians(theta_deg)
F = 2 * np.pi * R * gamma * np.cos(theta_rad)
return F
def capillary_energy(R, gamma, theta_deg):
"""
Estimate capillary interaction energy.
E ~ F * h where h ~ R (rough approximation)
"""
F = capillary_force(R, gamma, theta_deg)
return F * R
# Parameters
gamma_water = 0.072 # N/m (water at 25°C)
gamma_ethanol = 0.022 # N/m (ethanol)
gamma_hexane = 0.018 # N/m (hexane)
# Particle size range
R_range = np.logspace(-9, -6, 100) # 1 nm to 1 μm
# Calculate for different liquids (hydrophilic particles, θ ≈ 30°)
theta = 30 # degrees
fig, axes = plt.subplots(1, 2, figsize=(12, 5))
# Plot 1: Capillary force vs particle size
ax1 = axes[0]
for liquid, gamma, color in [('Water', gamma_water, 'blue'),
('Ethanol', gamma_ethanol, 'green'),
('Hexane', gamma_hexane, 'orange')]:
F = [capillary_force(R, gamma, theta) for R in R_range]
ax1.loglog(R_range * 1e9, np.array(F) * 1e9, linewidth=2,
label=f'{liquid} (γ = {gamma*1000:.0f} mN/m)', color=color)
# Add particle weight for comparison (silica)
rho = 2200 # kg/m³ (silica)
g = 9.8 # m/s²
weights = [(4/3) * np.pi * R**3 * rho * g for R in R_range]
ax1.loglog(R_range * 1e9, np.array(weights) * 1e9, 'k--',
linewidth=1.5, label='Particle weight (SiO₂)')
ax1.set_xlabel('Particle Radius (nm)', fontsize=11)
ax1.set_ylabel('Force (nN)', fontsize=11)
ax1.set_title('Capillary Force vs Particle Size (θ = 30°)', fontsize=12, fontweight='bold')
ax1.legend()
ax1.grid(True, alpha=0.3, which='both')
ax1.set_xlim(1, 1000)
# Plot 2: Effect of contact angle
ax2 = axes[1]
theta_range = np.linspace(0, 90, 100)
R_fixed = 50e-9 # 50 nm
for liquid, gamma, color in [('Water', gamma_water, 'blue'),
('Ethanol', gamma_ethanol, 'green')]:
F = [capillary_force(R_fixed, gamma, theta) for theta in theta_range]
ax2.plot(theta_range, np.array(F) * 1e9, linewidth=2,
label=f'{liquid}', color=color)
ax2.axvline(x=90, color='gray', linestyle=':', alpha=0.7)
ax2.text(85, 0.5, 'No bridge\n(θ > 90°)', fontsize=9, ha='right')
ax2.set_xlabel('Contact Angle (degrees)', fontsize=11)
ax2.set_ylabel('Capillary Force (nN)', fontsize=11)
ax2.set_title('Effect of Contact Angle (R = 50 nm)', fontsize=12, fontweight='bold')
ax2.legend()
ax2.grid(True, alpha=0.3)
ax2.set_xlim(0, 90)
plt.tight_layout()
plt.savefig('capillary_forces.png', dpi=150, bbox_inches='tight')
plt.show()
# Compare forces at 50 nm
print("\n=== Force Comparison at R = 50 nm ===")
R = 50e-9
F_cap = capillary_force(R, gamma_water, 30)
F_vdw = 30e-20 * R / (12 * (1e-9)**2) # vdW at 1 nm separation
F_weight = (4/3) * np.pi * R**3 * 2200 * 9.8
print(f"Capillary (water, θ=30°): {F_cap*1e9:.3f} nN")
print(f"Van der Waals (at 1 nm): {F_vdw*1e9:.3f} nN")
print(f"Particle weight (SiO₂): {F_weight*1e15:.3f} fN")
print(f"\nRatio F_cap/F_weight: {F_cap/F_weight:.1e}")
=== Force Comparison at R = 50 nm ===
Capillary (water, θ=30°): 19.642 nN
Van der Waals (at 1 nm): 1.250 nN
Particle weight (SiO₂): 11.258 fN
Ratio F_cap/F_weight: 1.7e+06
Key Insight
Capillary forces can be 10-100× stronger than van der Waals forces and millions of times stronger than gravity! This is why nanoparticle powders become "sticky" in humid environments and why careful drying protocols are essential.
1.5 Sintering and Necking
At elevated temperatures, nanoparticles can form permanent bonds through sintering. Atoms diffuse from particle interiors to contact points, forming "necks" that grow over time. This is particularly problematic for metallic nanoparticles.
Sintering Mechanisms
- Surface diffusion: Atoms migrate along particle surfaces (dominant at lower T)
- Volume diffusion: Atoms move through particle interior (higher T)
- Grain boundary diffusion: Transport along grain boundaries
- Viscous flow: For amorphous materials (e.g., silica)
Temperature Dependence
Sintering rate follows an Arrhenius relationship:
where \(x\) is neck radius, \(D_s\) is surface diffusion coefficient, \(E_a\) is activation energy.
Example 5: Sintering Temperature Estimation
import numpy as np
import matplotlib.pyplot as plt
# ===================================
# Example 5: Sintering Temperature for Nanoparticles
# ===================================
# Sintering typically begins at T/T_m ≈ 0.3-0.5 for nanoparticles
# (lower than bulk due to high surface energy)
def tammann_temperature(T_m, factor=0.5):
"""
Estimate Tammann temperature (onset of significant diffusion).
For nanoparticles, this can be 0.3-0.5 × T_m.
"""
return factor * T_m
def sintering_rate(T, T_m, E_a, R_gas=8.314):
"""
Relative sintering rate (Arrhenius-type).
Parameters:
T: Temperature (K)
T_m: Melting point (K)
E_a: Activation energy (J/mol)
R_gas: Gas constant
Returns:
Relative rate (normalized to T_m)
"""
rate = np.exp(-E_a / (R_gas * T))
rate_ref = np.exp(-E_a / (R_gas * T_m))
return rate / rate_ref
# Material properties (melting points and typical activation energies)
materials = {
'Gold (Au)': {'T_m': 1337, 'E_a': 170e3, 'color': 'gold'},
'Silver (Ag)': {'T_m': 1235, 'E_a': 180e3, 'color': 'silver'},
'Copper (Cu)': {'T_m': 1358, 'E_a': 200e3, 'color': 'orange'},
'Silica (SiO2)': {'T_m': 1986, 'E_a': 300e3, 'color': 'blue'},
'Titania (TiO2)': {'T_m': 2116, 'E_a': 250e3, 'color': 'green'}
}
fig, axes = plt.subplots(1, 2, figsize=(14, 5))
# Plot 1: Sintering onset temperatures
ax1 = axes[0]
x_pos = np.arange(len(materials))
bar_colors = [m['color'] for m in materials.values()]
T_m_values = [m['T_m'] for m in materials.values()]
T_sinter_nano = [tammann_temperature(m['T_m'], 0.3) for m in materials.values()]
T_sinter_bulk = [tammann_temperature(m['T_m'], 0.5) for m in materials.values()]
width = 0.25
ax1.bar(x_pos - width, T_m_values, width, label='Melting point', color='red', alpha=0.7)
ax1.bar(x_pos, T_sinter_bulk, width, label='Sintering (bulk, 0.5×Tm)', color='orange', alpha=0.7)
ax1.bar(x_pos + width, T_sinter_nano, width, label='Sintering (nano, 0.3×Tm)', color='green', alpha=0.7)
ax1.axhline(y=373, color='blue', linestyle='--', alpha=0.7)
ax1.text(4.5, 400, '100°C (Drying)', fontsize=9, color='blue')
ax1.set_ylabel('Temperature (K)', fontsize=11)
ax1.set_title('Sintering Onset Temperatures', fontsize=12, fontweight='bold')
ax1.set_xticks(x_pos)
ax1.set_xticklabels(materials.keys(), rotation=15, ha='right')
ax1.legend(loc='upper left')
ax1.grid(True, alpha=0.3, axis='y')
# Plot 2: Sintering rate vs temperature (Au example)
ax2 = axes[1]
T_range = np.linspace(300, 1337, 200)
for name, props in materials.items():
T_m = props['T_m']
E_a = props['E_a']
T_scaled = T_range[T_range <= T_m]
rates = sintering_rate(T_scaled, T_m, E_a)
T_ratio = T_scaled / T_m
ax2.semilogy(T_ratio, rates, linewidth=2, label=name, color=props['color'])
ax2.axvline(x=0.3, color='green', linestyle='--', alpha=0.7)
ax2.axvline(x=0.5, color='orange', linestyle='--', alpha=0.7)
ax2.text(0.31, 1e-6, 'Nano onset', fontsize=9, color='green', rotation=90)
ax2.text(0.51, 1e-6, 'Bulk onset', fontsize=9, color='orange', rotation=90)
ax2.set_xlabel('T / T_m (Homologous Temperature)', fontsize=11)
ax2.set_ylabel('Relative Sintering Rate', fontsize=11)
ax2.set_title('Sintering Rate vs Temperature', fontsize=12, fontweight='bold')
ax2.legend(loc='lower right')
ax2.grid(True, alpha=0.3, which='both')
ax2.set_xlim(0.2, 1.0)
ax2.set_ylim(1e-10, 1)
plt.tight_layout()
plt.savefig('sintering.png', dpi=150, bbox_inches='tight')
plt.show()
# Print critical temperatures
print("\n=== Critical Temperatures for Nanoparticle Processing ===")
print(f"{'Material':20s} {'T_m (K)':>10s} {'T_m (°C)':>10s} {'Nano onset (°C)':>15s}")
print("-" * 60)
for name, props in materials.items():
T_m = props['T_m']
T_nano = tammann_temperature(T_m, 0.3)
print(f"{name:20s} {T_m:10.0f} {T_m-273:10.0f} {T_nano-273:15.0f}")
=== Critical Temperatures for Nanoparticle Processing ===
Material T_m (K) T_m (°C) Nano onset (°C)
------------------------------------------------------------
Gold (Au) 1337 1064 128
Silver (Ag) 1235 962 98
Copper (Cu) 1358 1085 134
Silica (SiO2) 1986 1713 323
Titania (TiO2) 2116 1843 362
Processing Implications
Silver nanoparticles can begin sintering below 100°C - even during drying! This is why low-temperature processing and surface passivation are critical for preserving nanoparticle dispersibility in metallic systems.
1.6 Aggregation vs Agglomeration
Understanding the distinction between aggregation and agglomeration is crucial for selecting appropriate dispersion strategies. IUPAC provides clear definitions:
| Property | Aggregation | Agglomeration |
|---|---|---|
| Bonding Type | Strong (covalent, metallic, ionic) | Weak (van der Waals, electrostatic, capillary) |
| Reversibility | Irreversible | Reversible (with appropriate energy input) |
| Surface Area | Significantly reduced | Approximately preserved |
| Causes | Sintering, chemical bonding | Physical forces, drying, settling |
| Dispersion Method | Cannot be dispersed (must prevent) | Mechanical, chemical methods effective |
Example 6: Identifying Agglomeration vs Aggregation
import numpy as np
import matplotlib.pyplot as plt
# ===================================
# Example 6: Distinguishing Aggregation from Agglomeration
# ===================================
class ParticleCluster:
"""Model for analyzing particle clustering behavior."""
def __init__(self, n_primary, d_primary_nm, cluster_type='agglomerate'):
"""
Parameters:
n_primary: Number of primary particles
d_primary_nm: Primary particle diameter (nm)
cluster_type: 'agglomerate' or 'aggregate'
"""
self.n = n_primary
self.d = d_primary_nm * 1e-9 # Convert to m
self.type = cluster_type
def theoretical_surface_area(self):
"""Surface area if particles were separate."""
return self.n * np.pi * self.d**2
def actual_surface_area(self):
"""
Actual surface area of cluster.
- Agglomerates: ~80-100% of theoretical (loose packing)
- Aggregates: ~30-60% of theoretical (sintered contacts)
"""
if self.type == 'agglomerate':
# Loose packing, small contact areas
return 0.9 * self.theoretical_surface_area()
else: # aggregate
# Significant neck formation, reduced surface
return 0.4 * self.theoretical_surface_area()
def dispersion_energy_required(self):
"""
Estimate energy required to disperse (per particle pair).
Returns energy in kT units at 300 K.
"""
kT = 1.38e-23 * 300
if self.type == 'agglomerate':
# van der Waals at contact (~10-100 kT)
A_H = 20e-20 # Typical Hamaker constant
E_vdw = A_H * self.d / (24 * 0.3e-9) # ~0.3 nm contact
return E_vdw / kT
else: # aggregate
# Chemical bond energy (~eV range)
E_bond = 1e-19 # ~1 eV in Joules
return E_bond / kT
def is_redispersible(self, available_energy_kT):
"""Check if cluster can be dispersed with given energy."""
required = self.dispersion_energy_required()
return available_energy_kT > required
# Comparison study
d_primary = 20 # nm
n_particles = 100
agglomerate = ParticleCluster(n_particles, d_primary, 'agglomerate')
aggregate = ParticleCluster(n_particles, d_primary, 'aggregate')
# Create visualization
fig, axes = plt.subplots(1, 3, figsize=(15, 4.5))
# Plot 1: Surface area comparison
ax1 = axes[0]
categories = ['Theoretical\n(dispersed)', 'Agglomerate', 'Aggregate']
sa_values = [
agglomerate.theoretical_surface_area() * 1e12, # μm²
agglomerate.actual_surface_area() * 1e12,
aggregate.actual_surface_area() * 1e12
]
colors = ['green', 'blue', 'red']
bars = ax1.bar(categories, sa_values, color=colors, alpha=0.7, edgecolor='black')
ax1.set_ylabel('Surface Area (μm²)', fontsize=11)
ax1.set_title(f'Surface Area: {n_particles}× {d_primary}nm Particles',
fontsize=12, fontweight='bold')
for bar, val in zip(bars, sa_values):
ax1.text(bar.get_x() + bar.get_width()/2, bar.get_height() + 0.02*max(sa_values),
f'{val:.2f}', ha='center', fontsize=10)
ax1.grid(True, alpha=0.3, axis='y')
# Plot 2: Dispersion energy comparison
ax2 = axes[1]
cluster_types = ['Agglomerate\n(vdW)', 'Aggregate\n(sintered)']
energies = [agglomerate.dispersion_energy_required(),
aggregate.dispersion_energy_required()]
colors = ['blue', 'red']
bars = ax2.bar(cluster_types, energies, color=colors, alpha=0.7, edgecolor='black')
ax2.axhline(y=10, color='green', linestyle='--', label='Ultrasonic (~10 kT)')
ax2.axhline(y=100, color='orange', linestyle='--', label='Ball mill (~100 kT)')
ax2.set_ylabel('Dispersion Energy Required (kT)', fontsize=11)
ax2.set_title('Energy to Break Particle Contacts', fontsize=12, fontweight='bold')
ax2.set_yscale('log')
ax2.legend()
ax2.grid(True, alpha=0.3, axis='y')
# Plot 3: Dispersion success by method
ax3 = axes[2]
methods = ['Stirring\n(1 kT)', 'Ultrasonic\n(30 kT)', 'Ball mill\n(200 kT)',
'High-shear\n(500 kT)']
energies_available = [1, 30, 200, 500]
# Calculate success for each type
agglom_success = [agglomerate.is_redispersible(E) for E in energies_available]
aggreg_success = [aggregate.is_redispersible(E) for E in energies_available]
x = np.arange(len(methods))
width = 0.35
bars1 = ax3.bar(x - width/2, [int(s) for s in agglom_success], width,
label='Agglomerate', color='blue', alpha=0.7)
bars2 = ax3.bar(x + width/2, [int(s) for s in aggreg_success], width,
label='Aggregate', color='red', alpha=0.7)
ax3.set_xticks(x)
ax3.set_xticklabels(methods)
ax3.set_ylabel('Dispersible (1=Yes, 0=No)', fontsize=11)
ax3.set_title('Dispersion Success by Method', fontsize=12, fontweight='bold')
ax3.legend()
ax3.set_ylim(0, 1.5)
ax3.grid(True, alpha=0.3, axis='y')
plt.tight_layout()
plt.savefig('aggregation_vs_agglomeration.png', dpi=150, bbox_inches='tight')
plt.show()
# Summary
print("\n=== Aggregation vs Agglomeration Summary ===")
print(f"{'Property':30s} {'Agglomerate':>15s} {'Aggregate':>15s}")
print("-" * 60)
print(f"{'Surface area retention':30s} {'90%':>15s} {'40%':>15s}")
print(f"{'Dispersion energy (kT)':30s} {agglomerate.dispersion_energy_required():>15.1f} {aggregate.dispersion_energy_required():>15.0f}")
print(f"{'Ultrasonic dispersible':30s} {'Yes':>15s} {'No':>15s}")
print(f"{'Ball mill dispersible':30s} {'Yes':>15s} {'Unlikely':>15s}")
=== Aggregation vs Agglomeration Summary ===
Property Agglomerate Aggregate
------------------------------------------------------------
Surface area retention 90% 40%
Dispersion energy (kT) 55.6 24155
Ultrasonic dispersible Yes No
Ball mill dispersible Yes Unlikely
Chapter Summary
Key Takeaways
- Surface effects dominate: At 10 nm, ~50% of atoms are at the surface, driving high reactivity and agglomeration tendency.
- Van der Waals forces are always attractive; Hamaker constant determines strength (metals > oxides > polymers).
- Electrostatic repulsion can stabilize dispersions, but Debye length decreases with ionic strength.
- Capillary forces during drying can be 10-100× stronger than vdW forces.
- Sintering begins at 0.3-0.5 × T_m for nanoparticles - much lower than bulk materials.
- Agglomeration is reversible; aggregation (sintering) is permanent and must be prevented.
Exercises
Exercise 1: Calculate specific surface area
Calculate the specific surface area (m²/g) for:
- 20 nm gold nanoparticles (ρ = 19.3 g/cm³)
- 50 nm silica nanoparticles (ρ = 2.2 g/cm³)
- 100 nm polystyrene particles (ρ = 1.05 g/cm³)
Hint: Use SSA = 6/(d×ρ) for spheres.
Exercise 2: Compare interaction energies
For two 30 nm TiO₂ particles in water (A_H = 5.3×10⁻²⁰ J) at 2 nm separation:
- Calculate the van der Waals attraction energy in kT.
- What surface potential (in mV) is needed for electrostatic repulsion to overcome vdW attraction at this separation?
Exercise 3: Debye length effects
A nanoparticle dispersion is stable in 1 mM NaCl but aggregates in 100 mM NaCl.
- Calculate the Debye lengths for both conditions.
- Explain why stability changes in terms of the DLVO energy barrier.
Exercise 4: Processing temperature limits
You need to dry silver nanoparticles without sintering. Based on the Tammann temperature:
- What is the maximum safe drying temperature?
- Suggest an alternative drying method that avoids thermal sintering.
Exercise 5: Dispersion method selection
A powder sample shows 200 nm clusters of 20 nm primary particles. DLS after ultrasonication shows 50 nm hydrodynamic diameter.
- Is this likely aggregation or agglomeration? Explain.
- What additional characterization would confirm your answer?
Next Steps
In Chapter 1, we learned the fundamental mechanisms driving nanoparticle agglomeration. In Chapter 2, we will explore the factors that influence these interactions and how they can be controlled.
Next Chapter Preview (Chapter 2)
- Particle size effects and critical diameters
- Surface energy and modification strategies
- Environmental effects (humidity, temperature, pH)
- Particle shape and surface state influences