Cost Scoring¶
Cost scoring quantifies economic viability using logarithmic normalization to handle the enormous range in material costs.
The Economic Challenge¶
Material costs span 5+ orders of magnitude:
| Material | Cost ($/kg) | Relative to Fe |
|---|---|---|
| Iron (Fe) | ~2 | 1× |
| Aluminum (Al) | ~3 | 1.5× |
| Copper (Cu) | ~10 | 5× |
| Nickel (Ni) | ~20 | 10× |
| Silver (Ag) | ~900 | 450× |
| Gold (Au) | ~60,000 | 30,000× |
| Platinum (Pt) | ~30,000 | 15,000× |
| Rhodium (Rh) | ~150,000 | 75,000× |
| Iridium (Ir) | ~150,000 | 75,000× |
Why Logarithmic?
Linear normalization would give:
- Iron: 1.000
- Copper: 0.9999
- Silver: 0.994
- Platinum: 0.800
This provides almost no discrimination among affordable materials!
Mathematical Formulation¶
Logarithmic normalization:
Equivalently:
Where:
| Parameter | Description | Source |
|---|---|---|
| \(C\) | Material cost ($/kg) | Input data |
| \(C_{min}\) | Minimum cost in dataset | Data-driven |
| \(C_{max}\) | Maximum cost in dataset | Data-driven |
Properties:
- Cheapest material → \(S_c = 1\) (most affordable)
- Most expensive → \(S_c = 0\) (least affordable)
- Each order of magnitude spans equal score range
Python Usage¶
Basic Usage¶
from ascicat.scoring import score_cost
import numpy as np
# Array of costs
costs = np.array([2.67, 10, 100, 1000, 10000, 107544])
# Data-driven normalization (automatic min/max)
scores = score_cost(costs)
print("Cost ($/kg) | Score")
print("-" * 30)
for c, s in zip(costs, scores):
print(f"${c:>10,.0f} | {s:.3f}")
Output:
Cost ($/kg) | Score
------------------------------
$ 3 | 1.000
$ 10 | 0.892
$ 100 | 0.676
$ 1,000 | 0.459
$ 10,000 | 0.243
$ 107,544 | 0.000
With Fixed Ranges¶
Score Distribution¶
The logarithmic scaling ensures:
| Cost Range | Score Range | Discrimination |
|---|---|---|
| $1 - $10 | 0.89 - 1.00 | Good |
| $10 - $100 | 0.78 - 0.89 | Good |
| $100 - $1,000 | 0.67 - 0.78 | Good |
| $1,000 - $10,000 | 0.56 - 0.67 | Good |
| $10,000 - $100,000 | 0.44 - 0.56 | Good |
Each order of magnitude gets ~0.11 score range.
For Alloys: Composition-Weighted Cost¶
For bimetallic/multimetallic catalysts, cost should be weighted by composition:
Where:
- \(x_i\) = Mole fraction of element \(i\)
- \(C_i\) = Cost of element \(i\)
Example: Cu₃Sb Alloy
Data Validation¶
Cost values must be strictly positive (for logarithm):
# This will raise an error
costs_invalid = np.array([10, 0, 100]) # 0 is invalid!
scores = score_cost(costs_invalid)
# ValueError: Found 1 non-positive cost values...
Score Interpretation¶
| Score | Interpretation | Example Materials |
|---|---|---|
| 0.9 - 1.0 | Very affordable | Fe, Al, Cu |
| 0.7 - 0.9 | Affordable | Ni, Co, Sn |
| 0.5 - 0.7 | Moderate | Mo, W, Ag |
| 0.3 - 0.5 | Expensive | Au, Pt, Pd |
| 0.0 - 0.3 | Very expensive | Rh, Ir, Os |
Visualization¶
import matplotlib.pyplot as plt
import numpy as np
from ascicat.scoring import score_cost
# Generate logarithmically spaced costs
costs = np.logspace(0, 6, 100) # $1 to $1M
scores = score_cost(costs, cost_min=1, cost_max=1e6)
fig, ax = plt.subplots(figsize=(10, 5))
ax.semilogx(costs, scores, 'b-', linewidth=2)
ax.set_xlabel('Material Cost ($/kg)')
ax.set_ylabel('Cost Score')
ax.set_title('Logarithmic Cost Scoring')
ax.grid(True, which='both', linestyle='--', alpha=0.5)
# Add reference points
materials = {
'Fe': 2, 'Cu': 10, 'Ni': 20, 'Ag': 900,
'Au': 60000, 'Pt': 30000, 'Ir': 150000
}
for name, cost in materials.items():
score = score_cost([cost], 1, 1e6)[0]
ax.scatter([cost], [score], s=100, zorder=5)
ax.annotate(name, (cost, score), xytext=(5, 5),
textcoords='offset points')
plt.tight_layout()
plt.show()
Edge Cases¶
All Same Cost¶
same_costs = np.array([100, 100, 100])
scores = score_cost(same_costs)
# Returns: [1.0, 1.0, 1.0] # All equally affordable
Very Small Range¶
# When min and max are very close
narrow = np.array([99, 100, 101])
scores = score_cost(narrow)
# Still provides meaningful discrimination
Integration with ASCICalculator¶
from ascicat import ASCICalculator
calc = ASCICalculator(reaction='HER')
calc.load_data('data/HER_clean.csv')
results = calc.calculate_asci()
# View cost distribution
print("\nCost Score Statistics:")
print(f" Mean: {results['cost_score'].mean():.3f}")
print(f" Std: {results['cost_score'].std():.3f}")
print(f" Min: {results['cost_score'].min():.3f}")
print(f" Max: {results['cost_score'].max():.3f}")
Practical Considerations¶
Cost Data Sources
Material costs fluctuate with market conditions. Consider:
- Using commodity price averages (e.g., USGS Mineral Commodity Summaries)
- Documenting your cost data source
- Performing sensitivity analysis on cost estimates
Beyond Raw Material Cost
The cost score uses raw material cost as a proxy. Real catalyst costs include:
- Synthesis/fabrication costs
- Catalyst loading (mass per electrode area)
- Recyclability/recovery potential
- Supply chain availability
These factors can shift relative economics significantly.
References¶
- U.S. Geological Survey. Mineral Commodity Summaries 2024.
- London Metal Exchange (LME) - Current metal prices