Materials Characterization with Machine Learning
# Materials Characterization with Machine Learning
## Introduction & Motivation
Material properties determine device performance and reliability. Direct measurement is expensive and time-consuming. ML models accelerate property prediction from composition, processing history, and microstructure, enabling faster material discovery and optimization.
Motivation: Predict material properties using ML for accelerated discovery.
Applications: Property forecasting, composition optimization, defect prediction, microstructure analysis.
---
## Core Concepts & Theory
### Crystal Structure
Atomic arrangements and symmetries.
### Defects and Impurities
Point and extended defects.
### Microstructure
Grain boundaries and phase distribution.
### Property Relationships
Structure-property linkages.
---
## Mathematical Formulation
Prediction Model:
$$P = f(C, T, t, \mathbf{p})$$
Descriptor Engineering:
$$\mathbf{d}_i = \psi(composition, structure, processing)$$
Uncertainty Quantification:
$$P \pm \sigma_{pred} = ext{model}(\mathbf{d}) \pm confidence$$
---
## Advanced Theory & Extensions
### High-Throughput Screening
Computational material search.
### Feature Engineering
Physics-informed descriptors.
### Transfer Learning
Knowledge from similar materials.
---
## Computational Considerations
Property Database: O(N·D) for N materials, D descriptors.
Model Training: O(N·D²) with gradient descent.
Prediction: O(D) per sample.
---
## Practical Implementation Strategies
### Data Collection
Experimental measurements and simulations.
### Feature Selection
Relevant descriptor identification.
### Cross-Validation
Robust generalization testing.
---
## Benchmark Datasets & Evaluation
ICSD Database: Crystal structure data.
NIST Materials: Experimental properties.
Computational Databases: DFT calculations.
---
## Key Challenges & Limitations
### Data Sparsity
Limited measurements for novel materials.
### Measurement Uncertainty
Experimental error propagation.
### Extrapolation Risk
Predictions beyond training space.
---
## Hyperparameter Tuning
Feature scaling: Standardization or normalization.
Model complexity: Regularization strength.
Cross-validation splits: 5-10 fold.
---
## Real-World Applications & Case Studies
Battery Materials: Energy density prediction.
Semiconductors: Bandgap forecasting.
Alloys: Strength and corrosion resistance.
---
## Integration with Other Methods
Materials ML + quantum chemistry; + experimental data; + high-throughput screening.
---
## Summary & Key Takeaways
ML accelerates materials discovery through property prediction.
Principles:
1. Structure: Encode atomic arrangements.
2. Features: Engineer physics-informed descriptors.
3. Data: Collect comprehensive training sets.
4. Models: Build predictive algorithms.
5. Validation: Rigorous generalization testing.
---
## Appendix: Practical Labs
### Lab 1: Materials Descriptor Computation
import numpy as np
def encode_composition(elements, fractions):
"""Encode elemental composition as vector"""
# Simple: use fractional occupancy
# In practice: use more sophisticated descriptors
descriptor = np.zeros(10)
for elem, frac in zip(elements, fractions):
# Map elements to descriptor indices (simplified)
elem_index = min(len(elements)-1, 5)
descriptor[elem_index] += frac
return descriptor
def compute_structural_features(lattice_params):
"""Compute features from lattice parameters"""
# a, b, c = lattice constants
# α, β, γ = angles
a, b, c = lattice_params[:3] if len(lattice_params) >= 3 else [5.0, 5.0, 5.0]
# Features: volume, ratios, etc.
volume = a * b * c
ratio_ab = a / b if b > 0 else 1.0
ratio_bc = b / c if c > 0 else 1.0
features = np.array([volume, ratio_ab, ratio_bc])
return features
def process_history_features(temperature, time, pressure):
"""Encode processing history"""
# Log-scale processing conditions
features = np.array([
np.log10(max(temperature, 1)),
np.log10(max(time, 1)),
np.log10(max(pressure, 0.1))
])
return features
# Test
elements = ['Al', 'Cu']
fractions = [0.7, 0.3]
comp_descriptor = encode_composition(elements, fractions)
print(f"✓ Composition descriptor: {comp_descriptor[:3]}")
lattice = [4.05, 4.05, 4.05]
struct_features = compute_structural_features(lattice)
print(f"✓ Structural features - volume: {struct_features[0]:.1f}, ratios: {struct_features[1]:.2f}")
process_features = process_history_features(900, 3600, 1.0)
print(f"✓ Processing features: {process_features}")### Lab 2: Property Prediction Model
import numpy as np
class MaterialsPropertyPredictor:
def __init__(self, n_features=10, n_materials_train=100):
self.n_features = n_features
self.weights = np.random.randn(n_features) * 0.1
self.bias = 0.0
def train(self, X, y, epochs=100, lr=0.01):
"""Simple linear regression training"""
# X: [n_samples, n_features]
# y: [n_samples] - property values
for epoch in range(epochs):
# Predictions
y_pred = X @ self.weights + self.bias
# Error
error = y_pred - y
# Gradient descent
dw = X.T @ error / len(y)
db = np.mean(error)
self.weights -= lr * dw
self.bias -= lr * db
def predict(self, X):
"""Predict properties"""
return X @ self.weights + self.bias
def rmse(self, X, y):
"""Compute RMSE"""
y_pred = self.predict(X)
rmse = np.sqrt(np.mean((y_pred - y) ** 2))
return rmse
# Create synthetic data
n_samples = 50
X_train = np.random.randn(n_samples, 10)
y_train = X_train[:, 0] * 100 + X_train[:, 1] * 50 + np.random.randn(n_samples) * 10
model = MaterialsPropertyPredictor(n_features=10)
model.train(X_train, y_train, epochs=200, lr=0.01)
rmse_train = model.rmse(X_train, y_train)
print(f"✓ Training RMSE: {rmse_train:.2f}")
# Test on new data
X_test = np.random.randn(10, 10)
y_pred = model.predict(X_test)
print(f"✓ Predictions: mean={np.mean(y_pred):.1f}, range=[{y_pred.min():.1f}, {y_pred.max():.1f}]")### Lab 3: Uncertainty Quantification
import numpy as np
class BayesianMaterialsPredictor:
def __init__(self, n_features=10):
self.n_features = n_features
self.weights_mean = np.zeros(n_features)
self.weights_std = np.ones(n_features)
self.noise_std = 1.0
def bayesian_train(self, X, y, iterations=100):
"""Simplified Bayesian training"""
for iteration in range(iterations):
# Sample weights from posterior
w_samples = np.random.normal(self.weights_mean, self.weights_std, (10, self.n_features))
# Predictions for each sample
predictions = X @ w_samples.T
# Update based on likelihood
errors = predictions - y[:, np.newaxis]
mse_per_sample = np.mean(errors ** 2, axis=0)
# Update posterior
self.weights_std *= (1 - 0.01) # Decrease uncertainty
self.weights_mean += 0.01 * np.mean(w_samples * mse_per_sample, axis=0)
def predict_with_uncertainty(self, X):
"""Predictions with confidence intervals"""
y_pred = X @ self.weights_mean
# Predictive uncertainty
feature_uncertainty = np.sqrt(np.sum((X * self.weights_std) ** 2, axis=1))
y_std = np.sqrt(feature_uncertainty ** 2 + self.noise_std ** 2)
return y_pred, y_std
# Test
X_train = np.random.randn(50, 10)
y_train = X_train[:, 0] * 50 + np.random.randn(50) * 5
model = BayesianMaterialsPredictor(n_features=10)
model.bayesian_train(X_train, y_train)
X_test = np.random.randn(5, 10)
y_pred, y_std = model.predict_with_uncertainty(X_test)
print(f"✓ Predictions with uncertainty:")
for i, (pred, std) in enumerate(zip(y_pred, y_std)):
print(f" Sample {i}: {pred:.1f} ± {std:.1f}")### Lab 4: High-Throughput Materials Screening
import numpy as np
class HighThroughputScreening:
def __init__(self, n_candidates=1000, n_features=10):
self.n_candidates = n_candidates
self.n_features = n_features
# Generate candidate materials
self.candidates = np.random.randn(n_candidates, n_features)
def screen_materials(self, predictor, n_top=10):
"""Screen candidates using predictor"""
# Predict properties for all candidates
predictions = predictor.predict(self.candidates)
# Find top performers
top_indices = np.argsort(predictions)[-n_top:][::-1]
top_predictions = predictions[top_indices]
return top_indices, top_predictions
def diversity_score(self, indices):
"""Compute diversity among selected materials"""
selected_candidates = self.candidates[indices]
# Pairwise distances
distances = []
for i in range(len(indices)):
for j in range(i+1, len(indices)):
dist = np.linalg.norm(selected_candidates[i] - selected_candidates[j])
distances.append(dist)
if len(distances) == 0:
return 0
return np.mean(distances)
def pareto_front(self, predictions, performance_second_criterion):
"""Compute Pareto-optimal materials"""
n = len(predictions)
pareto_mask = np.ones(n, dtype=bool)
for i in range(n):
for j in range(n):
if i != j:
# Dominated if worse in both criteria
if (predictions[j] > predictions[i] and
performance_second_criterion[j] > performance_second_criterion[i]):
pareto_mask[i] = False
return np.where(pareto_mask)[0]
# Train a simple predictor
X_train = np.random.randn(100, 10)
y_train = X_train[:, 0] * 100 + X_train[:, 1] * 50 + np.random.randn(100) * 10
from sklearn.linear_model import LinearRegression
predictor = LinearRegression()
predictor.fit(X_train, y_train)
# Screening
screen = HighThroughputScreening(n_candidates=1000, n_features=10)
top_indices, top_preds = screen.screen_materials(predictor, n_top=20)
diversity = screen.diversity_score(top_indices)
print(f"✓ Top 20 candidates diversity score: {diversity:.2f}")
print(f"✓ Top predictions: {top_preds[:5]}")---