Activity Scoring¶
Activity scoring quantifies how well a catalyst's binding energy aligns with the Sabatier optimum.
The Sabatier Principle¶
The activity of a catalyst is governed by the Sabatier principle:
Sabatier (1911)
An optimal catalyst binds reaction intermediates with intermediate strength - neither too weakly (preventing activation) nor too strongly (preventing product desorption).
This creates the classic volcano plot:
Activity
↑
| ★ Optimal
| /|\
| / | \
| / | \
| / | \
|______/____|____\______→ Binding Energy
weak ΔE_opt strong
Mathematical Formulation¶
Linear Scoring (Default)¶
Where:
| Parameter | Symbol | Description | Unit |
|---|---|---|---|
| Adsorption energy | \(\Delta E\) | DFT-calculated binding | eV |
| Optimal energy | \(\Delta E_{opt}\) | Sabatier peak position | eV |
| Activity width | \(\sigma_a\) | Tolerance for "good" activity | eV |
Properties:
- Score = 1.0 when \(\Delta E = \Delta E_{opt}\) (at peak)
- Score = 0.5 when \(|\Delta E - \Delta E_{opt}| = \sigma_a / 2\)
- Score = 0.0 when \(|\Delta E - \Delta E_{opt}| \geq \sigma_a\)
Gaussian Scoring (Alternative)¶
Properties:
- Score = 1.0 at optimum
- Score = 0.61 at \(\pm\sigma_a\) from optimum
- Never reaches exactly zero
Python Usage¶
Basic Usage¶
from ascicat.scoring import score_activity
# Single catalyst
score = score_activity(
delta_E=-0.25, # eV
optimal_E=-0.27, # eV (HER optimum)
width=0.15, # eV
method='linear'
)
print(f"Activity score: {score:.3f}") # 0.867
Array Operations¶
import numpy as np
# Multiple catalysts
energies = np.array([-0.42, -0.35, -0.27, -0.19, -0.12])
scores_linear = score_activity(energies, -0.27, 0.15, method='linear')
scores_gauss = score_activity(energies, -0.27, 0.15, method='gaussian')
for E, lin, gauss in zip(energies, scores_linear, scores_gauss):
print(f"ΔE={E:+.2f} eV: Linear={lin:.3f}, Gaussian={gauss:.3f}")
Output:
ΔE=-0.42 eV: Linear=0.000, Gaussian=0.135
ΔE=-0.35 eV: Linear=0.467, Gaussian=0.606
ΔE=-0.27 eV: Linear=1.000, Gaussian=1.000
ΔE=-0.19 eV: Linear=0.467, Gaussian=0.606
ΔE=-0.12 eV: Linear=0.000, Gaussian=0.135
Using ActivityScorer Class¶
from ascicat.scoring import ActivityScorer
scorer = ActivityScorer()
# Linear scoring
s_linear = scorer.linear(delta_E=-0.30, optimal_E=-0.27, width=0.15)
# Gaussian scoring
s_gauss = scorer.gaussian(delta_E=-0.30, optimal_E=-0.27, width=0.15)
Reaction-Specific Parameters¶
Predefined Configurations¶
| Reaction | Pathway | \(\Delta E_{opt}\) | \(\sigma_a\) |
|---|---|---|---|
| HER | H adsorption | -0.27 eV | 0.15 eV |
| CO2RR | CO | -0.67 eV | 0.15 eV |
| CO2RR | CHO | -0.48 eV | 0.15 eV |
| CO2RR | COCOH | -0.32 eV | 0.15 eV |
Access Configuration¶
from ascicat.config import get_reaction_config
# Get HER configuration
her_config = get_reaction_config('HER')
print(f"Optimal energy: {her_config.optimal_energy} eV")
print(f"Activity width: {her_config.activity_width} eV")
# Get CO2RR-CO configuration
co_config = get_reaction_config('CO2RR', pathway='CO')
print(f"Optimal energy: {co_config.optimal_energy} eV")
Choosing Between Methods¶
Linear Scoring¶
Advantages:
- Computationally efficient
- Easy to interpret
- Consistent with traditional volcano plots
- Clear cutoff at \(\pm\sigma_a\)
Best for:
- Standard screening
- Large datasets
- When interpretability is important
Gaussian Scoring¶
Advantages:
- Smoother discrimination near optimum
- Never exactly zero (no hard cutoff)
- Mathematically differentiable
Best for:
- Sensitivity analysis
- When fine discrimination at extremes matters
- Optimization algorithms
Visualization¶
import matplotlib.pyplot as plt
import numpy as np
from ascicat.scoring import score_activity
# Generate data
energies = np.linspace(-0.6, 0.1, 100)
scores_lin = score_activity(energies, -0.27, 0.15, 'linear')
scores_gau = score_activity(energies, -0.27, 0.15, 'gaussian')
# Plot
fig, ax = plt.subplots(figsize=(8, 5))
ax.plot(energies, scores_lin, 'b-', label='Linear', linewidth=2)
ax.plot(energies, scores_gau, 'r--', label='Gaussian', linewidth=2)
ax.axvline(-0.27, color='gray', linestyle=':', label='Optimal')
ax.axhline(0.5, color='gray', linestyle=':', alpha=0.5)
ax.set_xlabel('Adsorption Energy (eV)')
ax.set_ylabel('Activity Score')
ax.legend()
ax.set_xlim(-0.6, 0.1)
ax.set_ylim(0, 1.05)
plt.show()
Physical Interpretation¶
Score Ranges¶
| Score Range | Interpretation | Example Materials |
|---|---|---|
| 0.9 - 1.0 | Excellent | At volcano peak |
| 0.7 - 0.9 | Good | Near-optimal catalysts |
| 0.5 - 0.7 | Moderate | Acceptable for some applications |
| 0.3 - 0.5 | Poor | Suboptimal binding |
| 0.0 - 0.3 | Very poor | Far from optimum |
HER Examples¶
| Catalyst | \(\Delta E\) (eV) | \(S_a\) | Interpretation |
|---|---|---|---|
| Pt(111) | -0.09 | 0.80 | Excellent |
| Ni(111) | -0.47 | 0.00 | Too strong |
| Au(111) | +0.18 | 0.00 | Too weak |
| Fe-Sb | -0.25 | 0.87 | Near optimal |
Common Issues¶
Sign Convention
ASCICat uses the convention where negative adsorption energies indicate stable binding. Ensure your DFT data uses the same convention:
Width Parameter
The default \(\sigma_a = 0.15\) eV is calibrated for typical DFT accuracy. If your calculations use different exchange-correlation functionals, you may need to adjust this.