Doped Fluorite Materials Tutorial (GDC, SDC, YSZ, ScSZ)¶
This tutorial covers creating acceptor-doped fluorite oxide structures for solid oxide fuel cell applications.
Overview¶
Doped fluorite oxides are crucial for ionic conductivity in SOFCs:
Ceria-Based (CeO2)¶
- GDC: Gadolinium-doped ceria (Ce₁₋ₓGdₓO₂₋δ)
- SDC: Samarium-doped ceria (Ce₁₋ₓSmₓO₂₋δ)
- PDC: Praseodymium-doped ceria (Ce₁₋ₓPrₓO₂₋δ)
- YDC: Yttrium-doped ceria (Ce₁₋ₓYₓO₂₋δ)
Zirconia-Based (ZrO2)¶
- YSZ: Yttria-stabilized zirconia (Zr₁₋ₓYₓO₂₋δ)
- ScSZ: Scandia-stabilized zirconia (Zr₁₋ₓScₓO₂₋δ)
- CSZ: Calcia-stabilized zirconia (Zr₁₋ₓCaₓO₂₋δ)
- MSZ: Magnesia-stabilized zirconia (Zr₁₋ₓMgₓO₂₋δ)
Chemistry¶
Acceptor dopants substitute M⁴⁺ sites (Ce⁴⁺, Zr⁴⁺). Charge compensation requires oxygen vacancy formation:
Trivalent dopants (Gd³⁺, Y³⁺, Sc³⁺):
Divalent dopants (Ca²⁺, Mg²⁺):
Odd Number of Trivalent Dopants
When the number of trivalent dopants is odd (e.g., 9 Gd³⁺), the ideal vacancy count would be non-integer (4.5). The code uses floor division, creating 4 vacancies in this case.
The uncompensated negative charge physically corresponds to M⁴⁺ → M³⁺ reduction (small polaron formation), which is realistic for reducible oxides like CeO₂.
Basic Usage¶
Creating Gd-Doped Ceria (GDC)¶
from ase.build import bulk
from nh3sofc.structure import DopantBuilder
# Create CeO2 supercell
ceo2 = bulk('CeO2', 'fluorite', a=5.41)
ceo2_slab = ceo2 * (2, 2, 2) # 8 Ce, 16 O
# Initialize builder
builder = DopantBuilder(ceo2_slab)
# Create 10% Gd-doped ceria
gdc = builder.create_doped_structure(
dopant="Gd",
dopant_fraction=0.10, # 10% of Ce → Gd
random_seed=42,
)
# Check composition
symbols = gdc.get_chemical_symbols()
n_gd = symbols.count("Gd")
n_ce = symbols.count("Ce")
n_o = symbols.count("O")
print(f"Gd atoms: {n_gd}") # ~1 (10% of 8)
print(f"Ce atoms: {n_ce}") # ~7
print(f"O atoms: {n_o}") # 16 - vacancies
print(f"Charge balanced: 2 Gd → 1 vacancy")
Creating Yttria-Stabilized Zirconia (YSZ)¶
from ase.build import bulk
from nh3sofc.structure import DopantBuilder
# Create ZrO2 supercell (cubic fluorite phase)
zro2 = bulk('ZrO2', 'fluorite', a=5.07)
zro2_slab = zro2 * (2, 2, 2) # 8 Zr, 16 O
# Initialize builder
builder = DopantBuilder(zro2_slab)
# Create 8% Y-doped zirconia (8YSZ)
ysz = builder.create_doped_structure(
dopant="Y",
dopant_fraction=0.08,
host_cation="Zr", # Specify the host cation
random_seed=42,
)
# Check composition
symbols = ysz.get_chemical_symbols()
print(f"Y atoms: {symbols.count('Y')}")
print(f"Zr atoms: {symbols.count('Zr')}")
Creating Scandia-Stabilized Zirconia (ScSZ)¶
# ScSZ has the highest ionic conductivity among zirconia-based electrolytes
scsz = builder.create_doped_structure(
dopant="Sc",
dopant_fraction=0.10, # 10 mol% Sc2O3
host_cation="Zr",
random_seed=42,
)
Creating Calcia-Stabilized Zirconia (CSZ)¶
# Divalent dopants have 1:1 dopant:vacancy ratio
csz = builder.create_doped_structure(
dopant="Ca",
dopant_fraction=0.15,
host_cation="Zr",
random_seed=42,
)
# 1 Ca²⁺ → 1 vacancy (not 2:1 like trivalent dopants)
Using DopedFluoriteStructure¶
The DopedFluoriteStructure class tracks dopant metadata:
from nh3sofc.structure import DopedFluoriteStructure
# Ceria-based (GDC)
gdc = DopedFluoriteStructure.from_ceria(
ceo2_slab,
dopant="Gd",
dopant_fraction=0.10,
random_seed=42,
)
print(gdc) # DopedFluoriteStructure(GDC, Gd_frac=10.0%, n_vac=0)
print(gdc.get_material_name()) # "GDC"
# Zirconia-based (YSZ)
ysz = DopedFluoriteStructure.from_zirconia(
zro2_slab,
dopant="Y",
dopant_fraction=0.08,
random_seed=42,
)
print(ysz.get_material_name()) # "YSZ"
print(ysz.host_cation) # "Zr"
# General factory (works with any host)
doped = DopedFluoriteStructure.from_parent(
parent_structure,
dopant="Sc",
dopant_fraction=0.10,
host_cation="Zr",
random_seed=42,
)
Backwards Compatibility
DopedCeriaStructure is still available as a deprecated alias.
Existing code will continue to work but will show deprecation warnings.
Placement Strategies¶
Control where dopants and vacancies are placed:
Random Placement (Default)¶
Uniform random distribution - represents high-temperature equilibrium:
gdc_random = builder.create_doped_structure(
dopant="Gd",
dopant_fraction=0.20,
dopant_placement="random",
vacancy_placement="random",
)
Surface-Preferring Placement¶
For surface-segregated dopants or vacancies:
gdc_surface = builder.create_doped_structure(
dopant="Gd",
dopant_fraction=0.20,
dopant_placement="surface",
dopant_preference=0.8, # Strong surface bias
vacancy_placement="surface",
vacancy_preference=0.7,
)
Associated Dopant-Vacancy Pairs¶
Vacancies near dopants (low-temperature regime):
gdc_associated = builder.create_doped_structure(
dopant="Gd",
dopant_fraction=0.20,
vacancy_placement="near_dopant",
vacancy_preference=0.9, # Strong association
)
Bulk-Preferring Placement¶
For bulk-segregated defects:
gdc_bulk = builder.create_doped_structure(
dopant="Gd",
dopant_fraction=0.20,
dopant_placement="bulk",
vacancy_placement="bulk",
)
Mixed Valence Dopants (Pr, Tb)¶
Praseodymium and Terbium can exist as M³⁺ or M⁴⁺:
# 20% Pr, but only half is Pr³⁺ (contributes to vacancies)
pdc = builder.create_doped_structure(
dopant="Pr",
dopant_fraction=0.20, # 20% total Pr
pr_trivalent_fraction=0.5, # 50% is Pr³⁺
random_seed=42,
)
# Effective: 10% Pr³⁺ → 5% vacancies (half of normal)
# Same applies to Tb
tdc = builder.create_doped_structure(
dopant="Tb",
dopant_fraction=0.15,
pr_trivalent_fraction=0.7, # 70% is Tb³⁺
random_seed=42,
)
Parameter Name
The parameter is named pr_trivalent_fraction for historical reasons but
applies to both Pr and Tb dopants. The alias trivalent_fraction is also
available in DopedFluoriteStructure.from_ceria().
Generating Configuration Pools¶
For screening studies, generate multiple configurations:
# Pool with different vacancy placement strategies
pool = builder.create_doped_pool(
dopant="Gd",
dopant_fraction=0.15,
n_configs=5,
strategies=["random", "surface", "near_dopant"],
random_seed=42,
)
print(f"Generated {len(pool)} configurations") # 15 configs
# Each config has metadata
for config in pool[:3]:
print(f"Config {config['config_id']}: {config['vacancy_placement']}")
Zirconia Pools¶
# YSZ configuration pool
ysz_pool = builder.create_doped_pool(
dopant="Y",
dopant_fraction=0.08,
n_configs=3,
strategies=["random", "surface"],
host_cation="Zr",
random_seed=42,
)
Concentration Series¶
Generate structures with varying dopant levels by passing a list of fractions:
# Use create_doped_pool with a list of fractions
pool = builder.create_doped_pool(
dopant="Gd",
dopant_fraction=[0.05, 0.10, 0.15, 0.20, 0.25],
n_configs=3,
random_seed=42,
)
print(f"Generated {len(pool)} structures") # 15 (5 fractions × 3 configs)
# Group by fraction
from collections import defaultdict
by_fraction = defaultdict(list)
for s in pool:
by_fraction[s["dopant_fraction"]].append(s["atoms"])
Default Strategy for Concentration Series
When dopant_fraction is a list, the default strategy is ["random"] since
concentration series typically don't need strategy variation. Override with
strategies=["random", "surface"] if needed.
Analysis¶
Single Structure Analysis¶
from nh3sofc.structure import analyze_dopant_distribution, print_dopant_analysis
# Analyze doped structure
stats = analyze_dopant_distribution(
gdc,
dopant="Gd",
reference_atoms=ceo2_slab, # Original for vacancy counting
z_threshold=0.3, # Top 30% = surface
near_dopant_cutoff=3.5, # Å
)
# Print formatted report
print_dopant_analysis(stats, title="GDC Analysis")
Output:
============================================================
GDC Analysis
============================================================
Dopant: Gd
Total Gd atoms: 2
Gd in surface (top 30%): 1 (50.0%)
Gd in bulk: 1
Dopant fraction: 25.0%
Host Cations:
Ce atoms remaining: 6
O atoms: 15
Vacancy Distribution:
Total vacancies: 1
Vacancies in surface: 1 (100.0%)
Vacancies in bulk: 0
Vacancies near Gd (< 3.5 Å): 1 (100.0%)
Charge Balance Check:
Expected vacancies (n_dopant/2): 1
Actual vacancies: 1
Status: Charge balanced
============================================================
Key Metrics¶
| Metric | Description |
|---|---|
dopant_surface_fraction |
Fraction of dopants in surface region |
vacancy_surface_fraction |
Fraction of vacancies in surface region |
vacancy_near_dopant_fraction |
Fraction of vacancies within cutoff of dopants |
dopant_fraction |
Actual dopant/(dopant+host) ratio |
Complete Workflow Example¶
from ase.build import bulk
from ase.io import write
from nh3sofc.structure import (
DopantBuilder,
DopedFluoriteStructure,
analyze_dopant_distribution,
print_dopant_analysis,
)
from nh3sofc.calculators.vasp import VASPInputGenerator
import os
# 1. Create CeO2 surface
ceo2 = bulk('CeO2', 'fluorite', a=5.41) * (3, 3, 3)
# 2. Create 10% GDC with surface-preferring vacancies
builder = DopantBuilder(ceo2)
gdc = builder.create_doped_structure(
dopant="Gd",
dopant_fraction=0.10,
vacancy_placement="surface",
vacancy_preference=0.75,
random_seed=42,
)
# 3. Analyze distribution
stats = analyze_dopant_distribution(gdc, "Gd", reference_atoms=ceo2)
print_dopant_analysis(stats)
# 4. Save structure
write("GDC_10pct.vasp", gdc, format="vasp")
# 5. Generate VASP inputs
os.makedirs("calc_gdc", exist_ok=True)
vasp = VASPInputGenerator(
gdc,
calc_type="relax",
work_dir="calc_gdc",
)
vasp.generate_all(
encut=520,
hubbard_u={"Ce": 5.0}, # DFT+U for Ce 4f electrons
vdw="D3BJ",
)
YSZ Workflow Example¶
# 1. Create ZrO2 structure
zro2 = bulk('ZrO2', 'fluorite', a=5.07) * (3, 3, 3)
# 2. Create 8YSZ
ysz = DopedFluoriteStructure.from_zirconia(
zro2,
dopant="Y",
dopant_fraction=0.08,
vacancy_placement="random",
random_seed=42,
)
print(f"Created {ysz.get_material_name()}")
print(f"Host: {ysz.host_cation}, Dopant: {ysz.dopant}")
print(f"Vacancies: {ysz.n_vacancies}")
# Save structure
write("YSZ_8mol.vasp", ysz.atoms, format="vasp")
Supported Dopants¶
Trivalent Dopants (2:1 dopant:vacancy ratio)¶
| Dopant | Name | Ionic Radius (Å) | Common Use |
|---|---|---|---|
| Gd | Gadolinium | 1.053 | GDC - highest conductivity for ceria |
| Sm | Samarium | 1.079 | SDC - high conductivity |
| Pr | Praseodymium | 1.126 | PDC - mixed ionic-electronic |
| Y | Yttrium | 1.019 | YDC, YSZ - most common for zirconia |
| La | Lanthanum | 1.160 | LDC |
| Nd | Neodymium | 1.109 | NDC |
| Tb | Terbium | 1.040 | TDC - mixed valence |
| Sc | Scandium | 0.870 | ScSZ - highest conductivity for zirconia |
Divalent Dopants (1:1 dopant:vacancy ratio)¶
| Dopant | Name | Ionic Radius (Å) | Common Use |
|---|---|---|---|
| Ca | Calcium | 1.120 | CSZ - cost-effective |
| Mg | Magnesium | 0.890 | MSZ |
Supported Host Materials¶
| Host | Oxide | Lattice (Å) | Notes |
|---|---|---|---|
| Ce | CeO2 | 5.41 | Reducible, high electronic conductivity at low pO2 |
| Zr | ZrO2 | 5.07 | Stable, purely ionic conductor |
| Hf | HfO2 | 5.11 | Similar to zirconia |
| Th | ThO2 | 5.60 | Nuclear applications |
Tips¶
-
Dopant fraction: Typical SOFC concentrations are 8-20% (x = 0.08-0.20)
-
Charge balance: Always verify with
analyze_dopant_distribution() -
Surface vs bulk: Use
z_thresholdto define what counts as "surface" -
Multiple configs: Generate pools for statistical sampling of configurations
-
Mixed valence: Remember to set
pr_trivalent_fractionfor Pr/Tb studies -
YSZ vs ScSZ: ScSZ has higher conductivity but Sc is more expensive
-
Divalent dopants: Remember they create more vacancies per dopant (1:1 vs 2:1)
Next Steps¶
- VASP Calculations - Run DFT on doped structures
- High-Throughput Screening - Screen multiple configurations
- Exsolution Simulation - Metal particles on doped fluorites