Examples and Tutorials¶
This page provides comprehensive examples demonstrating fr5's capabilities across different use cases and applications.
Basic Examples¶
1. Property Prediction Basics¶
Ionic Liquid Viscosity¶
from fr5.property_prediction_il import predict_viscosity
# Single prediction
result = predict_viscosity({
'cation': 'EMIM', # 1-ethyl-3-methylimidazolium
'anion': 'BF4', # tetrafluoroborate
'temperature': 298.15 # 25°C
})
print(f"Viscosity: {result['viscosity']:.2f} mPa·s")
print(f"Uncertainty: ±{result['uncertainty']:.2f} mPa·s")
# Temperature sweep
temperatures = [298.15, 323.15, 348.15, 373.15]
for temp in temperatures:
result = predict_viscosity({
'cation': 'EMIM',
'anion': 'BF4',
'temperature': temp
})
print(f"At {temp-273.15:.0f}°C: {result['viscosity']:.1f} mPa·s")
MOF CO₂ Uptake¶
from fr5.property_prediction_mof import predict_co2_uptake
# From known MOF
result = predict_co2_uptake(
mof_name='HKUST-1',
pressure=1.0, # 1 bar
temperature=298.15 # 25°C
)
print(f"HKUST-1 CO₂ uptake: {result['uptake']:.2f} mmol/g")
# From structure file
result = predict_co2_uptake(
mof_structure='path/to/ZIF-8.cif',
pressure=10.0, # 10 bar
temperature=298.15
)
print(f"ZIF-8 CO₂ uptake at 10 bar: {result['uptake']:.2f} mmol/g")
2. Molecular Descriptors¶
Generate IL Descriptors¶
from fr5.descriptors import generate_il_descriptors
# Generate comprehensive descriptors
descriptors = generate_il_descriptors(
cation='BMIM', # 1-butyl-3-methylimidazolium
anion='PF6', # hexafluorophosphate
descriptor_types=['fragment', 'property', 'topological']
)
print(f"Generated {len(descriptors)} descriptors:")
for name, value in list(descriptors.items())[:10]:
print(f" {name}: {value:.3f}")
Batch Descriptor Generation¶
from fr5.descriptors import batch_generate_descriptors
# Define IL library
il_library = [
{'cation': 'EMIM', 'anion': 'BF4'},
{'cation': 'BMIM', 'anion': 'PF6'},
{'cation': 'HMIM', 'anion': 'Tf2N'}
]
# Generate descriptors for all ILs
all_descriptors = batch_generate_descriptors(
molecules=il_library,
descriptor_types=['fragment', 'property'],
n_jobs=2 # Parallel processing
)
print(f"Generated descriptors for {len(all_descriptors)} ionic liquids")
Intermediate Examples¶
3. High-Throughput Screening¶
Screen IL Library for Optimal Properties¶
import pandas as pd
from fr5.property_prediction_il import predict_viscosity_batch, predict_density_batch
# Define comprehensive IL library
cations = ['EMIM', 'BMIM', 'HMIM', 'OMIM']
anions = ['BF4', 'PF6', 'Tf2N', 'OTf', 'Cl']
temperatures = [298.15, 323.15, 348.15]
# Generate all combinations
il_combinations = []
for cation in cations:
for anion in anions:
for temp in temperatures:
il_combinations.append({
'cation': cation,
'anion': anion,
'temperature': temp
})
print(f"Screening {len(il_combinations)} IL combinations...")
# Predict properties
viscosity_results = predict_viscosity_batch(il_combinations)
density_results = predict_density_batch(il_combinations)
# Combine results into DataFrame
results_df = pd.DataFrame(il_combinations)
results_df['viscosity'] = [r['viscosity'] for r in viscosity_results]
results_df['density'] = [r['density'] for r in density_results]
# Find optimal ILs (low viscosity, high density)
optimal_ils = results_df[
(results_df['viscosity'] < 20.0) & # < 20 mPa·s
(results_df['density'] > 1.3) # > 1.3 g/cm³
].sort_values('viscosity')
print(f"\nFound {len(optimal_ils)} optimal ionic liquids:")
print(optimal_ils[['cation', 'anion', 'temperature', 'viscosity', 'density']].head())
MOF Database Screening¶
from fr5.property_prediction_mof import screen_mof_database
# Screen for CO₂ capture applications
screening_results = screen_mof_database(
database_path='CoRE_MOF_structures/',
properties=['co2_uptake', 'selectivity_co2_n2'],
conditions={
'pressure': 1.0,
'temperature': 298.15
},
filters={
'pore_volume': (0.7, 2.0), # cm³/g
'surface_area': (1000, 3000), # m²/g
},
n_jobs=4
)
# Get top performers
top_mofs = screening_results.get_top_performers(
criterion='co2_uptake',
n_top=20
)
print("Top 20 MOFs for CO₂ uptake:")
for i, mof in enumerate(top_mofs, 1):
print(f"{i:2d}. {mof['name']:20s} "
f"{mof['co2_uptake']:6.2f} mmol/g "
f"(selectivity: {mof['selectivity_co2_n2']:5.1f})")
4. Data Analysis and Visualization¶
Analyze Property Trends¶
import matplotlib.pyplot as plt
import seaborn as sns
from fr5.shared.visualization import plot_property_comparison
# Analyze temperature dependence
temperature_data = []
for temp in range(273, 374, 10):
result = predict_viscosity({
'cation': 'EMIM',
'anion': 'BF4',
'temperature': temp
})
temperature_data.append({
'temperature_C': temp - 273.15,
'viscosity': result['viscosity']
})
temp_df = pd.DataFrame(temperature_data)
# Plot temperature dependence
plt.figure(figsize=(8, 6))
plt.semilogy(temp_df['temperature_C'], temp_df['viscosity'], 'bo-')
plt.xlabel('Temperature (°C)')
plt.ylabel('Viscosity (mPa·s)')
plt.title('EMIM-BF4 Viscosity vs Temperature')
plt.grid(True, alpha=0.3)
plt.show()
Compare Multiple Properties¶
from fr5.shared.visualization import plot_multi_property_comparison
# Generate data for multiple ILs
il_names = ['EMIM-BF4', 'BMIM-PF6', 'HMIM-Tf2N']
properties = ['viscosity', 'density', 'thermal_stability']
comparison_data = []
for il_name in il_names:
cation, anion = il_name.split('-')
visc_result = predict_viscosity({
'cation': cation, 'anion': anion, 'temperature': 298.15
})
dens_result = predict_density({
'cation': cation, 'anion': anion, 'temperature': 298.15
})
comparison_data.append({
'IL': il_name,
'viscosity': visc_result['viscosity'],
'density': dens_result['density'],
'thermal_stability': 350 # Placeholder
})
# Create multi-property radar chart
plot_multi_property_comparison(
data=comparison_data,
properties=['viscosity', 'density', 'thermal_stability'],
normalize=True,
title='IL Property Comparison'
)
Advanced Examples¶
5. Bayesian Optimization¶
Single-Objective Reaction Optimization¶
import pandas as pd
from fr5.bayes_opt import BOExperimentManager
# Define reaction parameter space
reaction_variables = [
{
'name': 'temperature',
'type': 'continuous',
'bounds': (50, 120),
'unit': '°C'
},
{
'name': 'catalyst_loading',
'type': 'continuous',
'bounds': (0.5, 10.0),
'unit': 'mol%'
},
{
'name': 'reaction_time',
'type': 'continuous',
'bounds': (1.0, 24.0),
'unit': 'hours'
},
{
'name': 'solvent',
'type': 'categorical',
'categories': ['toluene', 'dmf', 'ethanol', 'water']
}
]
# Historical experimental data
initial_experiments = pd.DataFrame({
'temperature': [70, 90, 110, 80, 100],
'catalyst_loading': [2.0, 5.0, 8.0, 3.0, 6.0],
'reaction_time': [4, 8, 12, 6, 10],
'solvent': ['toluene', 'dmf', 'ethanol', 'toluene', 'dmf'],
'yield': [34.2, 67.8, 89.3, 45.6, 78.9]
})
# Set up single-objective optimization (maximize yield)
optimizer = BOExperimentManager(
input_details=reaction_variables,
initial_experiments=initial_experiments,
db_path="reaction_optimization.db",
objective_names=['yield'],
strategy_type='single_objective',
acquisition_function='qUCB'
)
# Optimization loop
for iteration in range(10):
print(f"\n--- Optimization Iteration {iteration + 1} ---")
# Get next experiment suggestions
suggestions = optimizer.suggest_experiments(batch_size=1)
print(f"Suggested experiment:")
print(suggestions)
# Simulate experimental result (replace with actual experiment)
suggested_conditions = suggestions.iloc[0]
simulated_yield = simulate_reaction_yield(suggested_conditions)
# Add result to database
new_experiment = suggested_conditions.copy()
new_experiment['yield'] = simulated_yield
optimizer.add_experiments(pd.DataFrame([new_experiment]))
# Check progress
best_result = optimizer.get_best_results()
print(f"Best yield so far: {best_result['yield']:.1f}%")
print(f"Best conditions: {best_result['conditions']}")
def simulate_reaction_yield(conditions):
"""Simulate reaction yield based on conditions (replace with real experiment)"""
# Simple model for demonstration
temp_effect = (conditions['temperature'] - 50) / 70 # 0-1 scale
cat_effect = conditions['catalyst_loading'] / 10 # 0-1 scale
time_effect = min(conditions['reaction_time'] / 12, 1) # 0-1 scale
solvent_effects = {
'toluene': 0.7, 'dmf': 0.9, 'ethanol': 0.6, 'water': 0.4
}
solvent_effect = solvent_effects[conditions['solvent']]
# Add some noise
import random
noise = random.gauss(0, 5) # ±5% random noise
yield_estimate = 100 * temp_effect * cat_effect * time_effect * solvent_effect + noise
return max(0, min(100, yield_estimate)) # Clamp between 0-100%
Multi-Objective Optimization¶
# Multi-objective optimization (maximize yield and selectivity, minimize cost)
mo_optimizer = BOExperimentManager(
input_details=reaction_variables,
initial_experiments=multi_objective_data, # Include yield, selectivity, cost
db_path="multi_objective_optimization.db",
objective_names=['yield', 'selectivity', 'cost'],
strategy_type='mobo_pareto',
acquisition_function='qEHVI',
ref_point=[0.0, 0.0, 100.0] # Reference point for hypervolume
)
# Run optimization
for iteration in range(15):
suggestions = mo_optimizer.suggest_experiments(batch_size=2)
# Simulate experiments (replace with real experiments)
new_results = []
for _, conditions in suggestions.iterrows():
result = {
**conditions.to_dict(),
'yield': simulate_reaction_yield(conditions),
'selectivity': simulate_selectivity(conditions),
'cost': calculate_reaction_cost(conditions)
}
new_results.append(result)
mo_optimizer.add_experiments(pd.DataFrame(new_results))
# Analyze Pareto front
pareto_front = mo_optimizer.get_pareto_front()
print(f"Iteration {iteration + 1}: Pareto front has {len(pareto_front)} points")
# Plot final Pareto front
mo_optimizer.plot_pareto_front(
objectives=['yield', 'selectivity'],
save_path='pareto_front.png'
)
6. Machine Learning with Custom Data¶
Train Custom Property Prediction Model¶
import pandas as pd
import numpy as np
from sklearn.model_selection import train_test_split
from sklearn.ensemble import RandomForestRegressor
from sklearn.metrics import mean_squared_error, r2_score
from fr5.descriptors import generate_il_descriptors
# Load your experimental data
experimental_data = pd.read_csv('my_il_experiments.csv')
print(f"Loaded {len(experimental_data)} experimental data points")
# Generate descriptors for all ILs
print("Generating molecular descriptors...")
descriptor_data = []
for _, row in experimental_data.iterrows():
descriptors = generate_il_descriptors(
cation=row['cation'],
anion=row['anion'],
temperature=row['temperature'],
descriptor_types=['fragment', 'property']
)
descriptor_data.append(descriptors)
# Convert to feature matrix
feature_names = list(descriptor_data[0].keys())
X = np.array([[desc[name] for name in feature_names] for desc in descriptor_data])
y = experimental_data['target_property'].values
print(f"Feature matrix shape: {X.shape}")
# Train-test split
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.2, random_state=42
)
# Train model
model = RandomForestRegressor(
n_estimators=100,
max_depth=10,
random_state=42,
n_jobs=-1
)
print("Training model...")
model.fit(X_train, y_train)
# Evaluate model
y_pred = model.predict(X_test)
rmse = np.sqrt(mean_squared_error(y_test, y_pred))
r2 = r2_score(y_test, y_pred)
print(f"\nModel Performance:")
print(f" RMSE: {rmse:.3f}")
print(f" R²: {r2:.3f}")
# Feature importance analysis
feature_importance = pd.DataFrame({
'feature': feature_names,
'importance': model.feature_importances_
}).sort_values('importance', ascending=False)
print(f"\nTop 10 Most Important Features:")
print(feature_importance.head(10))
# Make predictions for new ILs
new_ils = [
{'cation': 'EMIM', 'anion': 'OTf', 'temperature': 298.15},
{'cation': 'PBMIM', 'anion': 'Tf2N', 'temperature': 323.15}
]
print(f"\nPredictions for new ILs:")
for il in new_ils:
descriptors = generate_il_descriptors(**il, descriptor_types=['fragment', 'property'])
features = np.array([[descriptors[name] for name in feature_names]])
prediction = model.predict(features)[0]
print(f"{il['cation']}-{il['anion']} at {il['temperature']-273.15:.0f}°C: {prediction:.2f}")
7. Reaction Analysis and Kinetics¶
Analyze Reaction Database¶
from fr5.reaction_analysis import analyze_reaction_database, predict_reaction_outcome
# Load reaction database
reaction_db = pd.read_csv('hydrogenation_database.csv')
# Comprehensive database analysis
analysis = analyze_reaction_database(
database=reaction_db,
reaction_type='hydrogenation',
analysis_types=[
'conversion_trends',
'catalyst_comparison',
'solvent_effects',
'temperature_dependence'
]
)
# Generate analysis report
analysis.generate_report(
output_path='hydrogenation_analysis.html',
include_plots=True
)
# Extract key insights
catalyst_ranking = analysis.catalyst_performance_ranking()
print("Catalyst Performance Ranking:")
for i, (catalyst, performance) in enumerate(catalyst_ranking.items(), 1):
print(f"{i:2d}. {catalyst:15s} - Avg conversion: {performance['avg_conversion']:.1f}%")
# Predict outcomes for new conditions
new_conditions = {
'substrate': 'styrene',
'catalyst': 'Pd/C',
'solvent': 'ethanol',
'temperature': 60, # °C
'pressure': 1.0, # atm
'time': 4.0 # hours
}
prediction = predict_reaction_outcome(
reaction_type='hydrogenation',
conditions=new_conditions
)
print(f"\nPredicted outcome for new conditions:")
print(f" Conversion: {prediction['conversion']:.1f}%")
print(f" Selectivity: {prediction['selectivity']:.1f}%")
print(f" Rate constant: {prediction['rate_constant']:.2e} h⁻¹")
Kinetic Analysis¶
from fr5.reaction_analysis import analyze_kinetics
import matplotlib.pyplot as plt
# Time-course experimental data
time_points = [0, 0.5, 1, 2, 4, 8, 12, 24] # hours
conversion_data = [0, 12, 23, 38, 58, 75, 84, 91] # %
# Fit kinetic models
kinetic_analysis = analyze_kinetics(
time=time_points,
conversion=conversion_data,
models=['first_order', 'second_order', 'langmuir_hinshelwood']
)
# Compare models
print("Kinetic Model Comparison:")
for model_name, results in kinetic_analysis.model_results.items():
print(f"{model_name:20s}: R² = {results['r_squared']:.3f}, "
f"k = {results['rate_constant']:.2e}")
# Best model
best_model = kinetic_analysis.best_model
print(f"\nBest fitting model: {best_model['name']}")
print(f"Rate constant: {best_model['rate_constant']:.2e} h⁻¹")
# Plot experimental data and model fits
plt.figure(figsize=(10, 6))
plt.plot(time_points, conversion_data, 'ro', label='Experimental', markersize=8)
for model_name, results in kinetic_analysis.model_results.items():
time_fine = np.linspace(0, 24, 100)
conversion_fit = results['fit_function'](time_fine)
plt.plot(time_fine, conversion_fit, '--',
label=f"{model_name} (R² = {results['r_squared']:.3f})")
plt.xlabel('Time (hours)')
plt.ylabel('Conversion (%)')
plt.title('Kinetic Model Fitting')
plt.legend()
plt.grid(True, alpha=0.3)
plt.show()
Integration Examples¶
8. Complete Workflow: From Screening to Optimization¶
import pandas as pd
import numpy as np
from fr5.property_prediction_il import predict_viscosity_batch
from fr5.descriptors import generate_il_descriptors
from fr5.bayes_opt import BOExperimentManager
from fr5.shared.visualization import plot_optimization_progress
# Step 1: High-throughput virtual screening
print("Step 1: Virtual screening of IL library...")
# Define large IL library
cations = ['EMIM', 'BMIM', 'HMIM', 'OMIM', 'PMIM']
anions = ['BF4', 'PF6', 'Tf2N', 'OTf', 'TCM', 'DCA']
il_library = []
for cation in cations:
for anion in anions:
il_library.append({
'cation': cation,
'anion': anion,
'temperature': 298.15
})
# Predict properties for screening
viscosity_results = predict_viscosity_batch(il_library)
# Create screening results DataFrame
screening_df = pd.DataFrame(il_library)
screening_df['predicted_viscosity'] = [r['viscosity'] for r in viscosity_results]
# Filter for low-viscosity candidates
candidates = screening_df[screening_df['predicted_viscosity'] < 30.0].copy()
print(f"Found {len(candidates)} low-viscosity candidates from {len(il_library)} total ILs")
# Step 2: Descriptor analysis for top candidates
print("\nStep 2: Generating descriptors for top candidates...")
descriptor_data = []
for _, candidate in candidates.iterrows():
descriptors = generate_il_descriptors(
cation=candidate['cation'],
anion=candidate['anion'],
temperature=candidate['temperature']
)
descriptors['cation'] = candidate['cation']
descriptors['anion'] = candidate['anion']
descriptors['predicted_viscosity'] = candidate['predicted_viscosity']
descriptor_data.append(descriptors)
descriptor_df = pd.DataFrame(descriptor_data)
# Step 3: Experimental design using Bayesian optimization
print("\nStep 3: Designing optimal experiments...")
# Convert categorical variables for BO
variables = [
{
'name': 'temperature',
'type': 'continuous',
'bounds': (273.15, 373.15)
},
{
'name': 'cation',
'type': 'categorical',
'categories': candidates['cation'].unique().tolist()
},
{
'name': 'anion',
'type': 'categorical',
'categories': candidates['anion'].unique().tolist()
}
]
# Select initial experiments (best predictions + diverse sampling)
initial_experiments = candidates.nsmallest(5, 'predicted_viscosity').copy()
initial_experiments['experimental_viscosity'] = initial_experiments['predicted_viscosity'] * (1 + np.random.normal(0, 0.1, 5))
# Set up Bayesian optimization
optimizer = BOExperimentManager(
input_details=variables,
initial_experiments=initial_experiments[['cation', 'anion', 'temperature', 'experimental_viscosity']],
db_path="il_optimization.db",
objective_names=['experimental_viscosity'],
strategy_type='single_objective'
)
# Optimization iterations
print("\nRunning optimization iterations...")
for iteration in range(8):
# Suggest next experiments
suggestions = optimizer.suggest_experiments(batch_size=2)
# Simulate experimental results (replace with real experiments)
simulated_results = []
for _, suggestion in suggestions.iterrows():
# Simulate experimental viscosity with some noise around prediction
predicted = predict_viscosity({
'cation': suggestion['cation'],
'anion': suggestion['anion'],
'temperature': suggestion['temperature']
})
experimental = predicted['viscosity'] * (1 + np.random.normal(0, 0.15))
simulated_results.append({
'cation': suggestion['cation'],
'anion': suggestion['anion'],
'temperature': suggestion['temperature'],
'experimental_viscosity': experimental
})
# Add results to optimizer
optimizer.add_experiments(pd.DataFrame(simulated_results))
# Check progress
best_result = optimizer.get_best_results()
print(f"Iteration {iteration + 1}: Best viscosity = {best_result['experimental_viscosity']:.1f} mPa·s")
# Step 4: Final analysis and reporting
print("\nStep 4: Final analysis...")
# Get all experimental data
all_experiments = optimizer.get_all_experiments()
# Find optimal IL
optimal_il = all_experiments.loc[all_experiments['experimental_viscosity'].idxmin()]
print(f"\nOptimal ionic liquid found:")
print(f" Composition: {optimal_il['cation']}-{optimal_il['anion']}")
print(f" Temperature: {optimal_il['temperature']-273.15:.1f}°C")
print(f" Viscosity: {optimal_il['experimental_viscosity']:.1f} mPa·s")
# Plot optimization progress
plot_optimization_progress(
optimization_data=all_experiments,
objective='experimental_viscosity',
title='Ionic Liquid Viscosity Optimization',
save_path='il_optimization_progress.png'
)
print(f"\nTotal experiments performed: {len(all_experiments)}")
print(f"Improvement over initial best: {initial_experiments['experimental_viscosity'].min() - optimal_il['experimental_viscosity']:.1f} mPa·s")
Utility Examples¶
9. Data Management and Validation¶
from fr5.shared import validate_chemical_data, clean_chemical_data
from fr5.shared.io import DatabaseManager
# Data validation
experimental_data = pd.read_csv('messy_experimental_data.csv')
validation_report = validate_chemical_data(
data=experimental_data,
required_columns=['cation', 'anion', 'temperature', 'viscosity'],
value_ranges={
'temperature': (200, 400), # Kelvin
'viscosity': (0.1, 1000) # mPa·s
}
)
if not validation_report['is_valid']:
print("Data validation issues found:")
for error in validation_report['errors']:
print(f" - {error}")
# Data cleaning
cleaned_data = clean_chemical_data(
data=experimental_data,
remove_outliers=True,
outlier_method='isolation_forest',
missing_value_strategy='interpolate'
)
print(f"Cleaned data: {len(cleaned_data)} rows (from {len(experimental_data)})")
# Database management
db = DatabaseManager('project_database.db')
# Store cleaned data
db.create_table(
table_name='clean_experiments',
data=cleaned_data
)
# Query data
recent_experiments = db.query(
table_name='clean_experiments',
filters={'temperature': (290, 310)},
order_by='viscosity ASC',
limit=50
)
print(f"Retrieved {len(recent_experiments)} recent experiments")
These examples demonstrate the full capabilities of fr5 across different use cases, from basic property prediction to advanced optimization workflows. Each example can be adapted for your specific research needs and experimental requirements.