battery-ion-sim / app.py
shrut27's picture
Upload app.py with huggingface_hub
185d94d verified
Raw
History Blame Contribute Delete
70 kB
import streamlit as st
import pandas as pd
import numpy as np
import plotly.express as px
import plotly.graph_objects as go
from utils.data import (
FF_PERFORMANCE, ARRHENIUS_PARAMS, DFT_MECHANISMS,
SEI_MATERIALS, ML_MODELS, SIM_CAMPAIGN,
DFT_CONFIG_TYPES, DFT_FILE_FORMATS,
ELECTROLYTE_DECOMP, MECHANICAL_PROPS, THERMAL_SAFETY,
ANODE_MATERIALS, ADDITIVES, DECOMP_PRODUCTS,
REACTION_PATHWAYS, VALIDATION_RECS,
get_loss_curve, build_diffusion_table, build_arrhenius_lines,
build_strain_curves, build_eos_curves, build_rdf_data,
build_cycle_life_curves, build_rate_capability,
build_msd_curves, build_ion_mobility_map,
build_dendrite_risk, diffusion_coefficient, R_GAS,
# SIB cathode data
SIB_STRUCTURES, SIB_FORMATION_ENERGIES, SIB_AVG_CHARGES,
SIB_CATHODE_RANKING, SIB_DIFFUSION, SIB_VOLTAGE,
SIB_PIPELINE_STAGES, SIB_SCREENED,
build_bader_df,
)
from utils.plots import (
ff_performance_chart, loss_curve_chart, arrhenius_plot,
diffusion_bar_chart, strain_energy_plot, eos_plot, rdf_plot,
sei_ranking_chart, sei_radar, ml_model_scatter,
activation_energy_comparison, msd_plot,
cycle_life_plot, rate_capability_plot, thermal_safety_plot,
electrolyte_ranking_chart, anode_ranking_chart, additive_ranking_chart,
multi_objective_pareto, ion_mobility_plot, dendrite_risk_plot,
reaction_pathway_plot, mechanical_spider,
# SIB plots
sib_structure_overview, sib_lattice_radar, sib_bader_box,
sib_bader_heatmap, sib_charge_transfer_bar, sib_formation_energy_chart,
sib_cathode_ranking_chart, sib_cathode_radar, sib_diffusion_bar,
sib_screened_bubble, sib_pipeline_status,
)
st.set_page_config(
page_title="Battery-AI Simulation Engine · LiB + SIB",
page_icon="🔋",
layout="wide",
initial_sidebar_state="expanded",
)
st.markdown("""
<style>
.main { background-color: #0E1117; }
.block-container { padding-top: 1.2rem; }
.metric-card {
background: linear-gradient(135deg,#1a1d27,#252836);
border:1px solid #2d3048; border-radius:12px;
padding:0.9rem 1rem; text-align:center;
}
.metric-card .label { font-size:0.7rem; color:#8891a4; text-transform:uppercase; letter-spacing:.08em; }
.metric-card .value { font-size:1.5rem; font-weight:700; color:#fff; margin:3px 0; }
.metric-card .delta { font-size:0.72rem; color:#4ade80; }
.section-hdr {
font-size:0.95rem; font-weight:600; color:#94a3b8;
text-transform:uppercase; letter-spacing:.06em;
border-bottom:1px solid #1e2433; padding-bottom:5px; margin-bottom:10px;
}
div[data-testid="stTabs"] button { font-size:0.85rem; }
</style>
""", unsafe_allow_html=True)
# ── Cache ──────────────────────────────────────────────────────────────────────
@st.cache_data
def load_static():
return (
get_loss_curve(),
build_diffusion_table(),
build_arrhenius_lines(),
build_strain_curves(),
build_eos_curves(),
build_msd_curves(),
build_ion_mobility_map(),
build_dendrite_risk(),
build_cycle_life_curves(),
build_rate_capability(),
)
(loss_df, diff_df, arr_df, strain_df, eos_df,
msd_df, mob_df, dendrite_df, cycle_df, rate_df) = load_static()
# ── Sidebar ────────────────────────────────────────────────────────────────────
with st.sidebar:
st.markdown("## 🔋 Battery-AI Engine")
st.caption("Force Field & Simulation Platform")
st.divider()
st.markdown("#### Global Filters")
sel_ff = st.multiselect(
"Force Fields",
["Yun et al.", "Wang et al.", "This Work"],
default=["Yun et al.", "Wang et al.", "This Work"],
)
sel_system = st.selectbox(
"Li System",
["LiF", "Li₀.₉F (10% vacancies)", "Li₁.₁F (10% interstitials)"],
)
sel_temp = st.select_slider("Temperature (K)", [300, 400, 500], value=300)
sel_rdf_ff = st.radio("RDF Force Field", ["This Work", "Yun et al."], horizontal=True)
st.divider()
st.markdown("#### Live Diffusivity Calculator")
c_T = st.slider("Temperature (K)", 250, 700, 300, step=10)
c_ff = st.selectbox("Force Field", ["This Work", "Yun et al.", "Wang et al."])
c_sys = st.selectbox("System",
["LiF", "Li₀.₉F (10% vacancies)", "Li₁.₁F (10% interstitials)"],
key="sb_sys")
_p = ARRHENIUS_PARAMS[c_sys][c_ff]
_D = diffusion_coefficient(_p["D0"], _p["Ea"], c_T)
st.metric(f"D at {c_T} K", f"{_D:.3e} cm²/s", delta=f"Eₐ = {_p['Ea']} kJ/mol")
st.divider()
st.markdown("#### 🔷 Na-Ion Battery")
sel_sib_mat = st.multiselect(
"SIB Cathode Materials",
["NaFePO₄", "Na₂MnNiO₄"],
default=["NaFePO₄", "Na₂MnNiO₄"],
key="sb_sib_mat",
)
st.divider()
st.caption("Battery-ION · LiB + SIB")
# ── Header & KPIs ──────────────────────────────────────────────────────────────
st.title("🔋 Battery-AI Force Field & Simulation Engine")
st.markdown(
"End-to-end AI platform: **DFT data → ML force-field training → MD simulation → "
"battery property prediction → ranked material recommendations.** "
"Covers both **Li-ion (LiB)** and **Na-ion (SIB)** cathode systems."
)
kpis = [
("DFT Simulations", "300+", "training database (LiB)"),
("SIB Structures", "4", "NaFePO₄ + Na₂MnNiO₄ (UC + SC)"),
("Energy R² (New FF)", "0.293", "vs −0.093 prior"),
("D @300K (LiF FF)", "3.44×10⁻⁸", "cm²/s"),
("NaFePO₄ Eform", "−2.38 eV/atom", "DFT computed"),
("Na₂MnNiO₄ Eform", "−1.542 eV/atom", "DFT computed"),
("NaFePO₄ AI Score", "89/100", "AI cathode ranking"),
("Na D (b-axis)", "8.5×10⁻¹⁰ cm²/s", "predicted ML-NEB"),
]
cols = st.columns(len(kpis))
for col, (label, val, sub) in zip(cols, kpis):
with col:
st.markdown(f"""
<div class="metric-card">
<div class="label">{label}</div>
<div class="value">{val}</div>
<div class="delta">{sub}</div>
</div>""", unsafe_allow_html=True)
st.markdown("<br>", unsafe_allow_html=True)
# ── Tabs: 9 tabs — 8 LiB + 1 new SIB (Na-Ion Battery) ────────────────────────
tabs = st.tabs([
"🏠 Workflow Overview",
"📁 DFT Data Input",
"⚡ ML Force Field Trainer",
"🔬 MD Simulation Engine",
"🤖 AI Property Predictor",
"🧪 SEI Analysis",
"🏆 AI Ranking Engine",
"📋 Deliverables & Validation",
"🔷 Na-Ion Battery (SIB)",
])
# ═══════════════════════════════════════════════════════════════════════════════
# TAB 1 — Workflow Overview
# ═══════════════════════════════════════════════════════════════════════════════
with tabs[0]:
st.markdown('<div class="section-hdr">Core Workflow Pipeline</div>', unsafe_allow_html=True)
st.caption(
"The tool takes DFT data as input, then uses ML/AI to automate "
"force-field training, MD simulation, battery property prediction, and material ranking."
)
# Visual pipeline
pipeline_steps = [
("📥", "Input DFT Data",
"Energy, forces, charges, relaxed structures, defect structures, surfaces, strain data, diffusion pathways"),
("🧹", "AI Data Processor",
"Cleans DFT data, checks quality, removes duplicates, labels configurations, builds training datasets"),
("🧠", "ML Force Field Training",
"Trains M3GNet / NequIP / CHGNet / DeepMD / SchNet / ALIGNN-FF. Also supports AI-assisted ReaxFF optimisation"),
("⚙️", "AI Simulation Manager",
"Auto-runs: Li-ion diffusion · SEI formation · LiF stability · electrolyte decomposition · dendrite growth · anode–cathode interface"),
("📊", "Battery Property Predictor",
"Predicts: ionic diffusivity · activation energy · SEI stability · decomp. risk · mechanical stability · thermal safety · cycle-life · capacity retention"),
("🏆", "AI Ranking Engine",
"Multi-objective scoring of materials, electrolytes, additives. Final output: top candidates + predicted properties + ranked recommendations."),
]
for icon, title, body in pipeline_steps:
with st.expander(f"{icon} **{title}**", expanded=False):
st.markdown(body)
st.divider()
# Pipeline flow figure
fig_pipe = go.Figure()
labels = [s[1] for s in pipeline_steps]
x_pos = list(range(len(labels)))
fig_pipe.add_trace(go.Scatter(
x=x_pos, y=[1] * len(x_pos), mode="markers+text",
marker=dict(size=52, color=["#3A86FF","#8B5CF6","#FF4757","#F59E0B","#06B6D4","#4ade80"],
line=dict(width=2, color="#fff")),
text=[s[0] for s in pipeline_steps], textfont=dict(size=20),
hovertext=labels, hoverinfo="text",
))
for i, lbl in enumerate(labels):
fig_pipe.add_annotation(x=i, y=0.82, text=f"<b>{lbl}</b>",
showarrow=False, font=dict(size=10, color="#EEE"),
align="center")
for i in range(len(labels) - 1):
fig_pipe.add_annotation(x=i + 0.52, y=1, text="→",
showarrow=False, font=dict(size=22, color="#aaa"))
fig_pipe.update_layout(
height=200, showlegend=False,
xaxis=dict(showgrid=False, zeroline=False, showticklabels=False, range=[-0.5, 5.5]),
yaxis=dict(showgrid=False, zeroline=False, showticklabels=False, range=[0.6, 1.2]),
plot_bgcolor="rgba(0,0,0,0)", paper_bgcolor="rgba(0,0,0,0)",
margin=dict(t=10, b=10, l=10, r=10),
)
st.plotly_chart(fig_pipe, use_container_width=True, key="pipeline_viz")
st.divider()
c1, c2 = st.columns(2)
with c1:
st.markdown("#### Tool Modules Summary")
modules = pd.DataFrame({
"Module": ["1. DFT Data Input", "2. AI Dataset Builder",
"3. ML Force Field Trainer", "4. MD Simulation Engine",
"5. AI Property Predictor", "6. Ranking & Report Generator"],
"Focus": ["Upload & validate DFT files", "Organise into 9 configuration types",
"Train M3GNet/CHGNet/NequIP/DeepMD", "NVT/NPT runs via LAMMPS",
"8 predicted battery properties", "Multi-objective material ranking"],
"Tech": ["ASE, pymatgen", "pymatgen, ASE", "PyTorch, ParAMS",
"LAMMPS, PACKMOL", "Einstein–Smoluchowski, CI-NEB", "Streamlit, Plotly"],
})
st.dataframe(modules, use_container_width=True, hide_index=True)
with c2:
st.markdown("#### Final Tool Output (8 Deliverables)")
deliverables = pd.DataFrame({
"#": list(range(1, 9)),
"Deliverable": [
"Ranked battery material candidates",
"AI-trained force field model",
"MD simulation results",
"Diffusion coefficient & activation energy",
"SEI stability prediction",
"Electrolyte decomposition risk",
"LIB performance summary",
"Experimental validation roadmap",
],
"Tab": ["AI Ranking Engine", "ML FF Trainer", "MD Simulation Engine",
"AI Property Predictor", "SEI Analysis",
"AI Property Predictor", "Deliverables", "Deliverables"],
})
st.dataframe(deliverables, use_container_width=True, hide_index=True)
# ═══════════════════════════════════════════════════════════════════════════════
# TAB 2 — DFT Data Input (Module 1 + 2)
# ═══════════════════════════════════════════════════════════════════════════════
with tabs[1]:
st.markdown('<div class="section-hdr">Module 1: DFT Data Upload</div>', unsafe_allow_html=True)
st.caption("Users upload DFT results in any of the supported formats. The AI Data Processor then validates, labels, and organises them into training groups.")
c1, c2 = st.columns([1, 1.2])
with c1:
st.markdown("#### Accepted File Formats")
st.dataframe(DFT_FILE_FORMATS, use_container_width=True, hide_index=True)
st.markdown("#### Simulated Upload Interface")
st.info("In the live tool, users drag-and-drop DFT files here. The processor auto-detects format and extracts energies, forces, and charges.")
uploaded = st.file_uploader(
"Upload DFT file (demo — any file accepted for preview)",
type=["xyz","cif","txt","csv","json"],
accept_multiple_files=False,
)
if uploaded:
st.success(f"Received: **{uploaded.name}** ({uploaded.size:,} bytes) — format auto-detected, queued for processing.")
else:
st.caption("No file uploaded — showing database statistics below.")
with c2:
st.markdown("#### Training Database Quality Metrics")
quality = pd.DataFrame({
"Metric": ["Total DFT simulations", "Database entries (energy+force+charge)",
"Energy accuracy target", "Force accuracy target",
"Duplicate check", "Autocorrelation reduction", "Data accessibility"],
"Value": ["300+", "3,000+", "< 0.01 eV/atom", "< 5.1×10⁻³ eV/Å",
"Stochastic sampling", "Minimised by design", "Open-access"],
})
st.dataframe(quality, use_container_width=True, hide_index=True)
# Dataset composition pie
df_pie = DFT_CONFIG_TYPES.copy()
fig_pie = px.pie(df_pie, names="Configuration Type", values="Entries in DB",
title="DFT Database Composition by Configuration Type",
color_discrete_sequence=px.colors.qualitative.Plotly, hole=0.4)
fig_pie.update_layout(height=360, plot_bgcolor="rgba(0,0,0,0)",
paper_bgcolor="rgba(0,0,0,0)", font=dict(color="#EEE"),
legend=dict(bgcolor="rgba(0,0,0,0)", font=dict(size=10)),
margin=dict(t=50, b=10))
st.plotly_chart(fig_pie, use_container_width=True, key="db_pie")
st.divider()
st.markdown('<div class="section-hdr">Module 2: AI Dataset Builder — 9 Configuration Types</div>',
unsafe_allow_html=True)
st.caption(
"Automatically creates training groups covering all battery-relevant phenomena. "
"ReaxFF/ML force fields fail if the training set does not cover the target phenomenon."
)
st.dataframe(DFT_CONFIG_TYPES, use_container_width=True, hide_index=True)
st.divider()
c1, c2 = st.columns(2)
with c1:
st.markdown("#### Database Configuration Counts")
fig_bar = px.bar(DFT_CONFIG_TYPES, x="Entries in DB", y="Configuration Type",
orientation="h", color="Configuration Type",
color_discrete_sequence=px.colors.qualitative.Plotly,
title="Entries per Configuration Type")
fig_bar.update_layout(height=360, showlegend=False,
plot_bgcolor="rgba(0,0,0,0)", paper_bgcolor="rgba(0,0,0,0)",
font=dict(color="#EEE"), margin=dict(t=45, b=10, l=220))
fig_bar.update_xaxes(gridcolor="rgba(255,255,255,0.1)")
st.plotly_chart(fig_bar, use_container_width=True, key="config_bar")
with c2:
st.markdown("#### Simulation Campaign Log")
st.dataframe(SIM_CAMPAIGN, use_container_width=True, hide_index=True)
st.markdown("""
**Key simulation parameters:**
- DFT code: VASP/BAND (PBE functional, DZ NAO basis)
- k-point accuracy: < 0.01 eV/atom
- ab initio MD: DFTB (Grimme xTB)
- LAMMPS ReaxFF-MD: 500 ps NVT, δt = 0.25 fs
""")
# ═══════════════════════════════════════════════════════════════════════════════
# TAB 3 — ML Force Field Trainer (Module 3)
# ═══════════════════════════════════════════════════════════════════════════════
with tabs[2]:
st.markdown('<div class="section-hdr">Module 3: ML Force Field Trainer</div>',
unsafe_allow_html=True)
st.caption(
"Trains AI force fields to replace expensive DFT. First version: "
"DFT data → M3GNet / CHGNet / NequIP → MD simulation. "
"Advanced: AI-assisted ReaxFF parameter optimisation via CMA-ES."
)
c1, c2 = st.columns([1.4, 1])
with c1:
st.markdown("#### Force Field Prediction Accuracy")
st.plotly_chart(ff_performance_chart(FF_PERFORMANCE), use_container_width=True, key="ff_perf")
with c2:
st.markdown("#### Metrics")
perf_df = pd.DataFrame(FF_PERFORMANCE).rename(columns={
"Force Field": "FF", "Energy R²": "E R²", "Energy RMSE (eV)": "E RMSE",
"Force R²": "F R²", "Force RMSE (eV/Å)": "F RMSE"})
st.dataframe(perf_df.style.background_gradient(subset=["E R²","F R²"], cmap="RdYlGn"),
use_container_width=True, hide_index=True)
st.markdown("""
**Key finding:** Both Yun et al. and Wang et al. produce negative
energy R² (−0.093) — systematic failure to describe solid LiF.
The new FF reaches R²=0.293 for energy and 0.377 for forces,
recovering physical solid-state behaviour.
""")
st.divider()
st.markdown('<div class="section-hdr">CMA-ES Optimisation Convergence (1.3 × 10⁴ iterations)</div>',
unsafe_allow_html=True)
st.plotly_chart(loss_curve_chart(loss_df), use_container_width=True, key="loss_curve")
st.caption("Bond interactions optimised first (0–5,000 iter), then van der Waals (5,000–13,000 iter). Angular/torsional terms could not converge — degenerate loss gradients.")
st.divider()
st.markdown('<div class="section-hdr">ML Model Benchmark — Energy MAE, Force MAE, Speed</div>',
unsafe_allow_html=True)
st.caption("Bubble size = inference speed (larger = faster). Recommended: CHGNet or NequIP for this dataset size (~3,000 entries).")
st.plotly_chart(ml_model_scatter(ML_MODELS), use_container_width=True, key="ml_scatter")
st.divider()
c1, c2 = st.columns(2)
with c1:
st.markdown("#### Model Specifications")
st.dataframe(ML_MODELS[["Model","Architecture","Energy MAE (meV/atom)","Force MAE (meV/Å)","R² Energy"]],
use_container_width=True, hide_index=True)
with c2:
st.markdown("#### Recommended Training Pipeline")
st.markdown("""
**MVP (first version):**
1. DFT data → **M3GNet / CHGNet / NequIP**
2. ~3,000 labelled configurations
3. Train energy + force head
4. Export as LAMMPS-compatible model
5. Run NVT MD at 300/400/500 K
**Advanced (ReaxFF path):**
1. DFT data → **ParAMS + CMA-ES**
2. Step-wise: bonds → vdW → angles
3. Up to 13,000 optimisation iterations
4. Validate against CI-NEB barriers
**ReaxFF parameter categories:**
General (41) · Atoms (32/type) · Bonds (16/bond) ·
Off-diagonal (6) · Angles (7) · Dihedral (7) · H-bonds (4)
""")
st.divider()
st.markdown("#### CI-NEB Energy Barrier Comparison")
neb_data = pd.DataFrame({
"Mechanism": ["Vacancy", "Direct-hopping", "Knock-off (est.)"],
"DFT Zheng et al. (kJ/mol)": [63.7, 52.1, 24.1],
"DFT this work (kJ/mol)": [64.3, 87.5, "—"],
"New ReaxFF MD Eₐ (kJ/mol)": [11.0, 11.0, 11.0],
"Status": ["Underestimated 5.8×", "Underestimated 8×", "Underestimated 2.2×"],
})
st.dataframe(neb_data, use_container_width=True, hide_index=True)
st.warning("Activation energies from MD are still 2–8× below DFT CI-NEB barriers. Future training sets must explicitly include Li-transport pathway configurations.")
# ═══════════════════════════════════════════════════════════════════════════════
# TAB 4 — MD Simulation Engine (Module 4)
# ═══════════════════════════════════════════════════════════════════════════════
with tabs[3]:
st.markdown('<div class="section-hdr">Module 4: AI Simulation Manager</div>',
unsafe_allow_html=True)
st.caption(
"Auto-runs: NVT/NPT · Li-ion diffusion · SEI interface · "
"electrolyte decomposition · LiF stability · anode–electrolyte interface · dendrite growth"
)
subtab_md = st.tabs(["📈 Diffusion & Arrhenius", "🔬 Crystal Stability", "🌡️ RDF Evolution",
"📉 MSD Analysis", "⚠️ Dendrite Growth Risk"])
# ── MD Sub-tab 1: Diffusion
with subtab_md[0]:
c_a, c_b = st.columns(2)
with c_a:
filtered_arr = arr_df[
arr_df["Force Field"].isin(sel_ff) | (arr_df["Force Field"] == "DFT reference")
]
st.plotly_chart(arrhenius_plot(filtered_arr, sel_system),
use_container_width=True, key="arrhenius")
with c_b:
filtered_diff = diff_df[diff_df["Force Field"].isin(sel_ff)]
st.plotly_chart(diffusion_bar_chart(filtered_diff, sel_temp),
use_container_width=True, key="diff_bar")
st.plotly_chart(activation_energy_comparison(), use_container_width=True, key="ea_comp")
st.markdown("#### Diffusion Data Table")
disp = diff_df[diff_df["Force Field"].isin(sel_ff)].copy()
disp["D (cm²/s)"] = disp["D (cm²/s)"].apply(lambda x: f"{x:.3e}")
st.dataframe(disp[["System","Force Field","Temperature (K)","D (cm²/s)","log₁₀(D)"]]
.sort_values(["System","Force Field","Temperature (K)"]),
use_container_width=True, hide_index=True)
st.markdown("""
**New FF at 300 K (LiF):** D = 3.44 × 10⁻⁸ cm²/s — ~1000× lower than Yun/Wang (3.56 × 10⁻⁵ cm²/s).
Electrolyte Li⁺ diffusivity: 0.5–1.4 × 10⁻⁵ cm²/s.
New FF correctly places LiF in the solid-state regime.
""")
# ── MD Sub-tab 2: Crystal Stability
with subtab_md[1]:
c1, c2 = st.columns(2)
with c1:
st.plotly_chart(strain_energy_plot(strain_df), use_container_width=True, key="strain")
st.caption("Shear strain ε₁₂ — old FFs show inverted concavity (negative c₁₂ elastic constant). New FF recovers parabolic minimum near equilibrium.")
with c2:
st.plotly_chart(eos_plot(eos_df), use_container_width=True, key="eos")
st.caption("Murnaghan EOS — old FFs: no energy minimum. New FF agrees with DFT even at ±30% volume change.")
st.markdown("#### LiF Crystal Phases (Materials Project data)")
crystal_data = pd.DataFrame({
"Phase": ["Stable FCC (Fm3̄m)", "Metastable HEX (P6₃mc)", "Metastable BCC (Pm3̄m)"],
"Space Group": ["Fm3̄m", "P6₃mc", "Pm3̄m"],
"Lattice": ["Face-Centered Cubic", "Hexagonal", "Body-Centered Cubic"],
"Formation Energy (DFT, eV)": [-4.31, -4.18, -3.95],
"First Li–F Distance (Å)": [2.01, 1.96, 2.08],
"New FF Accuracy": ["Excellent", "Good", "Moderate"],
})
st.dataframe(crystal_data, use_container_width=True, hide_index=True)
# ── MD Sub-tab 3: RDF
with subtab_md[2]:
st.caption("Li–F g(r) during 500 ps NVT at 300 K. Crystalline LiF: sharp peaks at 2.01 Å, 4.02 Å, 5.69 Å. LiF melt point = 1121 K — any amorphization at 300 K is unphysical.")
rdf_sel = build_rdf_data(sel_rdf_ff)
st.plotly_chart(rdf_plot(rdf_sel, sel_rdf_ff), use_container_width=True, key="rdf_sel")
c1, c2 = st.columns(2)
with c1:
st.markdown("#### Old FFs — Amorphization within 2 ps")
rdf_old = build_rdf_data("Yun et al.")
st.plotly_chart(rdf_plot(rdf_old, "Yun et al."), use_container_width=True, key="rdf_old")
with c2:
st.markdown("#### New FF — Crystalline Order Preserved")
rdf_new = build_rdf_data("This Work")
st.plotly_chart(rdf_plot(rdf_new, "This Work"), use_container_width=True, key="rdf_new")
# ── MD Sub-tab 4: MSD
with subtab_md[3]:
st.caption("MSD slope = 6D (Einstein–Smoluchowski). New FF gives 100–1000× lower slope → correct solid-state Li transport.")
st.plotly_chart(msd_plot(msd_df), use_container_width=True, key="msd_plot")
st.markdown("""
**Diffusion from MSD:** D = α₁/6, where α₁ is the MSD linear slope (least-squares fit).
Simulations run for 500 ps; MSD sampled every 50 steps (12.5 fs interval).
""")
# ── MD Sub-tab 5: Dendrite
with subtab_md[4]:
st.caption("Higher overpotential for Li plating → greater dendrite nucleation risk. LiF-rich SEI suppresses dendrites by providing uniform Li⁺ flux (low local overpotential).")
st.plotly_chart(dendrite_risk_plot(dendrite_df), use_container_width=True, key="dendrite")
st.markdown("""
**Dendrite mechanism:** Non-uniform SEI creates hotspots of high local current density,
lowering the nucleation barrier. LiF-rich SEI (uniform, mechanically stiff) distributes
Li⁺ flux evenly, raising the overpotential threshold for dendrite nucleation.
**SEI role:** Mechanical stiffness of LiF (Young's modulus 109 GPa) suppresses dendrite
penetration, while high ionic conductivity reduces local current hotspots.
""")
# ═══════════════════════════════════════════════════════════════════════════════
# TAB 5 — AI Property Predictor (Module 5) — all 8 properties
# ═══════════════════════════════════════════════════════════════════════════════
with tabs[4]:
st.markdown('<div class="section-hdr">Module 5: AI Property Predictor — 8 Battery Properties</div>',
unsafe_allow_html=True)
prop_tabs = st.tabs([
"⚡ Ionic Diffusivity", "🔥 Activation Energy",
"🛡️ SEI Stability", "💥 Electrolyte Decomp. Risk",
"💪 Mechanical Stability", "🌡️ Thermal Safety",
"♻️ Cycle-Life Risk", "📉 Capacity Retention",
])
# ── P1: Ionic Diffusivity
with prop_tabs[0]:
st.markdown("**Predicted by:** Arrhenius D = D₀ · exp(−Eₐ/RT) from MSD slopes")
c1, c2 = st.columns(2)
with c1:
arr_filt = arr_df[arr_df["Force Field"].isin(sel_ff) | (arr_df["Force Field"] == "DFT reference")]
st.plotly_chart(arrhenius_plot(arr_filt, sel_system), use_container_width=True, key="prop_arrh")
with c2:
st.metric("D (LiF, 300K, New FF)", "3.44 × 10⁻⁸ cm²/s", delta="~1000× lower than prior FFs")
st.metric("D (LiF, 300K, Yun/Wang)", "3.56 × 10⁻⁵ cm²/s", delta="≈ electrolyte-like — unphysical")
st.metric("D (electrolyte Li⁺)", "0.5–1.4 × 10⁻⁵ cm²/s", delta="Ref. Lee et al. 2002")
st.markdown("New FF correctly places LiF in the **solid-state regime**, 2 orders of magnitude below the electrolyte.")
# ── P2: Activation Energy
with prop_tabs[1]:
st.markdown("**Predicted by:** Arrhenius slope = −Eₐ/R from log(D) vs 1/T linear regression")
st.plotly_chart(activation_energy_comparison(), use_container_width=True, key="prop_ea")
st.dataframe(pd.DataFrame({
"System": ["LiF","LiF","LiF","Li₀.₉F","Li₀.₉F","Li₀.₉F","Li₁.₁F","Li₁.₁F","Li₁.₁F"],
"Force Field": ["Yun et al.","Wang et al.","This Work"]*3,
"Eₐ (kJ/mol)": [7.50,7.0,11.0,7.1,6.810,5.1,7.6,6.22,6.0],
"Eₐ err (kJ/mol)": [0.02,0.2,1.6,1.4,0.013,0.5,0.5,0.04,0.8],
}), use_container_width=True, hide_index=True)
st.warning("All FF Eₐ values (5–11 kJ/mol) are well below the DFT CI-NEB barriers (24–87 kJ/mol). Activation energies are underestimated — future training must include transport-pathway configurations.")
# ── P3: SEI Stability
with prop_tabs[2]:
st.markdown("**Predicted by:** Multi-criteria scoring (ionic conductivity, electronic insulation, mechanical, thermal)")
c1, c2 = st.columns([1.3, 1])
with c1:
st.plotly_chart(sei_ranking_chart(SEI_MATERIALS), use_container_width=True, key="prop_sei_rank")
with c2:
radar_sel = st.multiselect("Compare components (radar)",
SEI_MATERIALS["Component"].tolist(),
default=["LiF (FEC-derived)","Li₂CO₃","Li₂O"],
key="prop_radar_sel")
if radar_sel:
st.plotly_chart(sei_radar(SEI_MATERIALS, radar_sel),
use_container_width=True, key="prop_radar")
# ── P4: Electrolyte Decomp. Risk
with prop_tabs[3]:
st.markdown("**Predicted by:** Decomposition onset voltage, ionic conductivity, electrochemical window, temperature stability")
st.plotly_chart(electrolyte_ranking_chart(ELECTROLYTE_DECOMP),
use_container_width=True, key="prop_elec_rank")
st.dataframe(ELECTROLYTE_DECOMP[["Electrolyte","Decomp. Onset (V vs Li/Li⁺)",
"Decomp. Risk Score (1=low)","LiF SEI Formation?",
"Ionic Conductivity (mS/cm)","Decomp. Products"]],
use_container_width=True, hide_index=True)
st.markdown("""
**FEC advantage:** 1M LiPF₆ + 10% FEC has the lowest decomp. risk score (3.1) and highest LiF SEI yield
among liquid electrolytes. FEC decomposes selectively to produce LiF-dominant SEI,
confirmed by DFT and ReaxFF simulations.
""")
st.markdown("#### Decomposition Products by Precursor")
st.dataframe(DECOMP_PRODUCTS, use_container_width=True, hide_index=True)
st.markdown("#### Reaction Pathway: FEC → LiF")
st.plotly_chart(reaction_pathway_plot(REACTION_PATHWAYS),
use_container_width=True, key="prop_rxn_path")
# ── P5: Mechanical Stability
with prop_tabs[4]:
st.markdown("**Predicted by:** Elastic constants from energy-strain DFT calculations (C₁₁, C₁₂, C₄₄ for FCC LiF)")
mech_sel = st.multiselect("Select materials for radar",
MECHANICAL_PROPS["Material"].tolist(),
default=["LiF","Li₂CO₃","Graphite anode","Silicon anode"],
key="mech_sel")
if mech_sel:
st.plotly_chart(mechanical_spider(MECHANICAL_PROPS, mech_sel),
use_container_width=True, key="prop_mech_radar")
st.dataframe(MECHANICAL_PROPS, use_container_width=True, hide_index=True)
st.info("**LiF Young's modulus: 109 GPa** — stiffest SEI component. Suppresses dendrite penetration and accommodates volume changes better than organic components.")
# ── P6: Thermal Safety
with prop_tabs[5]:
st.markdown("**Predicted by:** Thermal decomposition onset temperatures from literature + ReaxFF thermal MD")
st.plotly_chart(thermal_safety_plot(THERMAL_SAFETY), use_container_width=True, key="prop_thermal")
st.dataframe(THERMAL_SAFETY, use_container_width=True, hide_index=True)
st.success("**LiF melting point: 1121.35 K (848 °C)** — far above all operating temperatures. The highest thermal stability of any SEI component.")
st.error("**Thermal runaway onset: ~130 °C** — triggered by SEI breakdown releasing exothermic energy. LiF-rich SEI delays this onset by ~20–40 °C vs. organic-dominated SEI.")
# ── P7: Cycle-Life Risk
with prop_tabs[6]:
st.markdown("**Predicted by:** SEI growth model — capacity fade rate ∝ SEI decomposition frequency")
st.plotly_chart(cycle_life_plot(cycle_df), use_container_width=True, key="prop_cycle")
st.markdown("""
**Key predictions:**
- LiF-rich SEI: >88% capacity retention after 200 cycles, >80% after 500 cycles
- Artificially engineered LiF SEI: >90% after 500 cycles
- No SEI engineering: drops below 80% (EOL) around cycle 200
- End-of-Life threshold (80%) used per IEC 62660-2 standard
""")
# ── P8: Capacity Retention (Rate Capability)
with prop_tabs[7]:
st.markdown("**Predicted by:** Rate capability from MD-derived diffusion coefficients at different C-rates")
st.plotly_chart(rate_capability_plot(rate_df), use_container_width=True, key="prop_rate")
st.markdown("""
**Interpretation:**
- LiF SEI maintains higher capacity at high C-rates due to lower Li⁺ transport resistance
- Si anode shows dramatic capacity drop above 2C — volume expansion disrupts SEI continuity
- Li metal + LiF SEI: best absolute capacity and rate performance (no anode volume change)
""")
st.markdown("#### Ion Mobility Map — Li⁺ Through SEI Depth")
st.plotly_chart(ion_mobility_plot(mob_df), use_container_width=True, key="prop_mob")
st.caption("Inorganic LiF inner layer (0–5 nm): D ≈ 3.44×10⁻⁸ cm²/s. Mixed zone (5–12 nm): gradient. Organic outer layer (12–20 nm): D ≈ 22×10⁻⁸ cm²/s.")
# ═══════════════════════════════════════════════════════════════════════════════
# TAB 6 — SEI Analysis
# ═══════════════════════════════════════════════════════════════════════════════
with tabs[5]:
st.markdown('<div class="section-hdr">SEI Component Analysis & LiF Formation</div>',
unsafe_allow_html=True)
c1, c2 = st.columns([1.3, 1])
with c1:
st.plotly_chart(sei_ranking_chart(SEI_MATERIALS), use_container_width=True, key="sei_rank")
with c2:
st.markdown("#### Scoring Weights")
st.markdown("""
| Property | Weight |
|---|---|
| Electronic Insulation | 30% |
| Ionic Conductivity | 25% |
| Mechanical Stability | 20% |
| Thermal Stability | 15% |
| Decomp. Risk (−) | 10% |
""")
st.info("**LiF** scores highest: exceptional electronic insulation (9.8/10), "
"high ionic transport, lowest decomp. risk. Ko & Yoon, Ceram. Int. 2019.")
st.divider()
c1, c2 = st.columns(2)
with c1:
radar_sei = st.multiselect("Radar — select components",
SEI_MATERIALS["Component"].tolist(),
default=["LiF (FEC-derived)","Li₂CO₃","Li₂O"],
key="sei_tab_radar")
if radar_sei:
st.plotly_chart(sei_radar(SEI_MATERIALS, radar_sei),
use_container_width=True, key="sei_radar_tab")
with c2:
st.markdown("#### Full SEI Dataset")
st.dataframe(
SEI_MATERIALS.style.background_gradient(subset=["Overall Score"], cmap="RdYlGn"),
use_container_width=True, hide_index=True)
st.divider()
st.markdown("#### FEC Decomposition → LiF Formation Pathway")
st.plotly_chart(reaction_pathway_plot(REACTION_PATHWAYS),
use_container_width=True, key="sei_rxn_path")
st.dataframe(REACTION_PATHWAYS, use_container_width=True, hide_index=True)
st.divider()
st.markdown("#### Ion Mobility Through SEI Layers")
st.plotly_chart(ion_mobility_plot(mob_df), use_container_width=True, key="sei_mob")
# ═══════════════════════════════════════════════════════════════════════════════
# TAB 7 — AI Ranking Engine (Module 6)
# ═══════════════════════════════════════════════════════════════════════════════
with tabs[6]:
st.markdown('<div class="section-hdr">Module 6: AI Ranking Engine — Multi-Objective Material Scoring</div>',
unsafe_allow_html=True)
st.caption(
"Ranks materials, additives, and electrolytes using multi-objective scoring. "
"Final output: top battery candidates + predicted properties + simulation evidence + ranked recommendations."
)
rank_tabs = st.tabs(["🧪 SEI Components", "🔌 Electrolytes", "⚡ Anode Materials", "➕ Additives", "📊 Pareto Analysis"])
# ── R1: SEI
with rank_tabs[0]:
st.plotly_chart(sei_ranking_chart(SEI_MATERIALS), use_container_width=True, key="rank_sei")
st.dataframe(SEI_MATERIALS.sort_values("Overall Score", ascending=False),
use_container_width=True, hide_index=True)
# ── R2: Electrolytes
with rank_tabs[1]:
st.plotly_chart(electrolyte_ranking_chart(ELECTROLYTE_DECOMP),
use_container_width=True, key="rank_elec")
st.dataframe(
ELECTROLYTE_DECOMP[["Electrolyte","Overall Score","Decomp. Risk Score (1=low)",
"Ionic Conductivity (mS/cm)","Electrochemical Window (V)",
"Temperature Stability (°C)","LiF SEI Formation?","Decomp. Products"]]
.sort_values("Overall Score", ascending=False),
use_container_width=True, hide_index=True,
)
st.success("**Top recommendation:** 1M LiPF₆ + 10% FEC in EC/DMC — highest LiF SEI formation, lowest decomp. risk among liquid electrolytes. Confirmed by FEC → LiF reaction pathway simulations.")
# ── R3: Anode Materials
with rank_tabs[2]:
st.plotly_chart(anode_ranking_chart(ANODE_MATERIALS), use_container_width=True, key="rank_anode")
st.dataframe(ANODE_MATERIALS.sort_values("Overall Score", ascending=False),
use_container_width=True, hide_index=True)
st.markdown("""
**Volume expansion** is the critical challenge for Si and Sn anodes — disrupts SEI continuity every cycle.
The LiF SEI is more resilient to volume changes than organic SEI due to its high Young's modulus (109 GPa).
""")
# ── R4: Additives
with rank_tabs[3]:
st.plotly_chart(additive_ranking_chart(ADDITIVES), use_container_width=True, key="rank_add")
st.dataframe(ADDITIVES.sort_values("Overall Score", ascending=False),
use_container_width=True, hide_index=True)
st.success("**FEC (fluoroethylene carbonate)** is the top-ranked additive: highest LiF SEI enhancement (9.5/10), 35% cycle-life improvement, HF scavenging capability confirmed by simulation.")
# ── R5: Pareto
with rank_tabs[4]:
st.markdown("#### Multi-Objective Pareto — Capacity vs. Volume Expansion (bubble = cycle stability)")
st.plotly_chart(multi_objective_pareto(ANODE_MATERIALS), use_container_width=True, key="pareto")
st.caption("Ideal anode: upper-left corner (high capacity, low expansion). Bubble size = cycle stability score. Graphite + LiF SEI is the most practical current choice.")
st.divider()
st.markdown("#### Electrolyte Score Decomposition")
elec_fig = px.bar(
ELECTROLYTE_DECOMP.melt(id_vars=["Electrolyte"],
value_vars=["Ionic Conductivity (mS/cm)",
"Electrochemical Window (V)",
"Temperature Stability (°C)"]),
x="Electrolyte", y="value", color="variable", barmode="group",
title="Electrolyte Property Comparison",
color_discrete_sequence=px.colors.qualitative.Plotly,
)
elec_fig.update_layout(height=380, plot_bgcolor="rgba(0,0,0,0)",
paper_bgcolor="rgba(0,0,0,0)", font=dict(color="#EEE"),
legend=dict(bgcolor="rgba(0,0,0,0)"),
xaxis=dict(tickangle=-20), margin=dict(t=55, b=60))
elec_fig.update_xaxes(gridcolor="rgba(255,255,255,0.1)")
elec_fig.update_yaxes(gridcolor="rgba(255,255,255,0.1)")
st.plotly_chart(elec_fig, use_container_width=True, key="elec_decomp_bar")
# ═══════════════════════════════════════════════════════════════════════════════
# TAB 8 — Final Report & Experimental Validation
# ═══════════════════════════════════════════════════════════════════════════════
with tabs[7]:
st.markdown('<div class="section-hdr">Final Tool Output — 8 Deliverables</div>',
unsafe_allow_html=True)
deliverable_details = [
("1", "Ranked Battery Material Candidates",
"SEI: LiF > Li₂CO₃ > Li₂O. Electrolyte: FEC-based > standard EC/DMC. Anode: Graphite (near-term), Li metal (future). Additive: FEC (35% cycle-life improvement).",
"🏆"),
("2", "AI-Trained Force Field Model",
"New ReaxFF: E R²=0.293, F R²=0.377. D(300K)=3.44×10⁻⁸ cm²/s — 1000× improvement. Available: CHGNet/NequIP models trained on 3,000+ DFT configurations.",
"🧠"),
("3", "MD Simulation Results",
"500 ps NVT at 300/400/500 K. Systems: LiF, Li₀.₉F, Li₁.₁F. RDF confirms crystalline order preservation. MSD confirms correct solid-state diffusion.",
"⚙️"),
("4", "Diffusion Coefficient & Activation Energy",
"D(LiF,300K) = 3.44×10⁻⁸ cm²/s. Eₐ(LiF) = 11.0 ± 1.6 kJ/mol. CI-NEB barriers: vacancy 64.3 kJ/mol, direct-hop 87.5 kJ/mol (still underestimated by FF).",
"📈"),
("5", "SEI Stability Prediction",
"LiF: Overall Score 8.65/10. Electronic insulation 9.8/10. Thermal onset 842°C. Mechanical stiffness 109 GPa (Young's modulus). Best SEI component for LIBs.",
"🛡️"),
("6", "Electrolyte Decomposition Risk",
"EC/DMC+FEC: Decomp. risk 3.1/10 (lowest liquid). Onset 1.1 V vs Li/Li⁺. Primary products: LiF (dominant), Li₂CO₃. Solid electrolytes (LGPS): risk 1.5/10.",
"💥"),
("7", "LIB Performance Summary",
"Capacity retention: LiF SEI >88% @200cy, >80% @500cy. Rate capability: LiF SEI maintains 300 mAh/g @10C (graphite). Thermal runaway delayed by ~30°C with LiF SEI.",
"📋"),
("8", "Experimental Validation Recommendations",
"8 prioritised experiments: XPS depth profiling, cryo-TEM, EIS, EELS, in-situ NMR, GITT, ARC calorimetry, long-term cycling. See table below.",
"🔬"),
]
for num, title, details, icon in deliverable_details:
with st.expander(f"{icon} **{num}. {title}**", expanded=(num == "1")):
st.markdown(details)
st.divider()
st.markdown('<div class="section-hdr">Experimental Validation Roadmap</div>',
unsafe_allow_html=True)
st.dataframe(VALIDATION_RECS, use_container_width=True, hide_index=True)
st.divider()
st.markdown('<div class="section-hdr">MVP → Full Production Roadmap</div>',
unsafe_allow_html=True)
roadmap = pd.DataFrame({
"Phase": ["MVP (now)", "Phase 2", "Phase 3", "Phase 4"],
"Focus": [
"LiF-rich SEI in Li-ion battery (graphite anode)",
"Expand SEI: Li₂CO₃, Li₂O, organic components",
"Si & Li-metal anodes — large volume expansion",
"Full battery pack: multi-scale → system level",
],
"New Training Data": [
"300+ DFT for LiF (done)",
"+500 DFT: Li₂CO₃, Li₂O, organic ROCO₂Li",
"+1000 DFT: Si-Li, Li-Li, large supercells",
"Continuum + atomistic coupling",
],
"ML Model": [
"ReaxFF (CMA-ES) / CHGNet / NequIP",
"NequIP / DeepMD on multi-component SEI",
"Graph NN with large-cell support",
"Multi-scale ML + FEM coupling",
],
"Target": [
"D within 2× of CI-NEB; >88% cap. retention",
"Mixed SEI composition prediction",
"Volume expansion + dendrite model",
"Thermal + electrochemical cell model",
],
})
st.dataframe(roadmap, use_container_width=True, hide_index=True)
st.divider()
st.markdown('<div class="section-hdr">ReaxFF Parameter Summary</div>', unsafe_allow_html=True)
c1, c2 = st.columns(2)
with c1:
param_data = pd.DataFrame({
"Type": ["General", "Atoms (per NA)", "Bonds (per bond)", "Off-diagonal", "Angles", "Dihedral", "H-bonds"],
"Parameters per entry": [41, 32, 16, 6, 7, 7, 4],
})
fig_params = px.bar(param_data, x="Type", y="Parameters per entry",
color="Type", title="ReaxFF Coefficient Distribution",
color_discrete_sequence=px.colors.qualitative.Plotly)
fig_params.update_layout(height=300, showlegend=False,
plot_bgcolor="rgba(0,0,0,0)", paper_bgcolor="rgba(0,0,0,0)",
font=dict(color="#EEE"), margin=dict(t=45, b=40))
fig_params.update_xaxes(gridcolor="rgba(255,255,255,0.1)")
fig_params.update_yaxes(gridcolor="rgba(255,255,255,0.1)")
st.plotly_chart(fig_params, use_container_width=True, key="reaxff_params")
with c2:
st.markdown("#### ReaxFF Coefficient Breakdown")
coeff_df = pd.DataFrame({
"Type": ["General", "Atoms", "Bonds", "Off-diagonal", "Angles", "Dihedral", "H-bonds"],
"Count": [41, 32, 16, 6, 7, 7, 4],
"Description": [
"Global FF parameters",
"Per atomic species",
"Per bond pair",
"Cross terms",
"3-body interactions",
"4-body torsional",
"Hydrogen-bond terms",
],
})
st.dataframe(coeff_df, use_container_width=True, hide_index=True)
# ═══════════════════════════════════════════════════════════════════════════════
# TAB 9 — Na-Ion Battery (SIB)
# ═══════════════════════════════════════════════════════════════════════════════
with tabs[8]:
st.markdown('<div class="section-hdr">Na-Ion Battery (SIB) — DFT Cathode Data</div>',
unsafe_allow_html=True)
st.markdown(
"DFT-computed data for two sodium-ion cathode materials: **NaFePO₄** and **Na₂MnNiO₄**. "
"The 8-stage AI pipeline maps each DFT result to a specific AI workflow stage — "
"from structure encoding to intelligent cathode ranking."
)
# Sub-tabs within the SIB tab
sib_tabs = st.tabs([
"📊 DFT Database",
"⚡ Formation Energy",
"🔬 Bader Charge Analysis",
"🚀 Na Diffusion",
"🏆 Cathode Ranking",
"🤖 AI Pipeline",
"🔮 AI Screening",
"📈 Voltage & Capacity",
])
# ── SIB Sub-tab 1: DFT Database ───────────────────────────────────────────
with sib_tabs[0]:
st.markdown("### Crystal Structure Database")
st.markdown(
"Four fully-relaxed DFT structures computed with VASP (GGA+U), "
"forming the **seed dataset** for the Battery Foundation Model."
)
# KPI row for SIB structures
sib_kpis = [
("NaFePO₄ Unit Cell", "28 atoms", "a=5.10, b=6.94, c=9.11 Å"),
("NaFePO₄ Supercell", "128 atoms", "2×2×1 · −746.93 eV"),
("Na₂MnNiO₄ Unit Cell", "24 atoms", "a=b=3.052, c=32.148 Å · hexagonal"),
("Na₂MnNiO₄ Supercell", "96 atoms", "2×2×1 · −517.270 eV"),
]
sib_kcols = st.columns(4)
for col, (lbl, val, sub) in zip(sib_kcols, sib_kpis):
with col:
st.markdown(f"""
<div class="metric-card">
<div class="label">{lbl}</div>
<div class="value">{val}</div>
<div class="delta">{sub}</div>
</div>""", unsafe_allow_html=True)
st.markdown("<br>", unsafe_allow_html=True)
st.markdown("#### Structure Summary Table")
st.dataframe(
SIB_STRUCTURES.style.background_gradient(
subset=["Total Energy (eV)", "Formation Energy (eV/atom)"], cmap="Blues"
),
use_container_width=True,
)
st.markdown("#### Total Energy & Formation Energy Comparison")
st.plotly_chart(sib_structure_overview(SIB_STRUCTURES), use_container_width=True,
key="sib_structure_overview")
st.markdown("#### Lattice Parameter Radar")
all_mats = SIB_STRUCTURES["Material"].tolist()
sel_mats_radar = st.multiselect(
"Select structures", all_mats, default=all_mats[:2], key="sib_lat_radar_sel"
)
if sel_mats_radar:
st.plotly_chart(sib_lattice_radar(SIB_STRUCTURES, sel_mats_radar),
use_container_width=True, key="sib_lattice_radar")
st.markdown("#### Key Structural Properties")
st.info("""
**NaFePO₄ (Olivine structure):**
- Orthorhombic symmetry (Pnma) · all angles 90°
- Strong 1D diffusion channels along b-axis (shortest path Na ↔ vacancy)
- Supercell 2×2×1: 128 atoms ideal for Na diffusion MD simulations
**Na₂MnNiO₄ (Layered oxide):**
- Hexagonal symmetry · γ=120° · large c-axis (32 Å)
- 2D diffusion in ab-plane; c-axis interlayer hopping limited
- Mn³⁺/Ni²⁺ redox centres → higher voltage potential (3.45 V)
""")
# ── SIB Sub-tab 2: Formation Energy ────────────────────────────────────────
with sib_tabs[1]:
st.markdown("### Formation Energy Analysis — Stage 3: Formation Energy Predictor")
st.markdown(
"Formation energies from DFT calculations are used to train an AI model that "
"can screen **millions of hypothetical cathodes** without running DFT."
)
col1, col2, col3, col4 = st.columns(4)
with col1:
st.metric("NaFePO₄ Eform", "−2.38 eV/atom", "DFT")
with col2:
st.metric("Na₂MnNiO₄ Eform", "−1.542 eV/atom", "DFT")
with col3:
st.metric("Na₃V₂(PO₄)₃ Eform", "−2.85 eV/atom", "Literature reference")
with col4:
st.metric("Stability rank: NaFePO₄", "#2 of 8", "Most stable DFT-computed")
st.plotly_chart(sib_formation_energy_chart(SIB_FORMATION_ENERGIES),
use_container_width=True, key="sib_form_e")
st.markdown("#### Formation Energy Table")
st.dataframe(
SIB_FORMATION_ENERGIES.style.background_gradient(
subset=["Formation Energy (eV/atom)"], cmap="Greens_r"
),
use_container_width=True,
)
st.info(
"**Interpretation:** More negative Eform = thermodynamically more stable. "
"NaFePO₄ (−2.38 eV/atom) is significantly more stable than Na₂MnNiO₄ (−1.542 eV/atom). "
"The AI formation energy predictor learns from these two ground-truth points to "
"extrapolate across the composition space."
)
# ── SIB Sub-tab 3: Bader Charge Analysis ──────────────────────────────────
with sib_tabs[2]:
st.markdown("### Bader Charge Analysis — Stage 4: Charge Distribution Model")
st.markdown(
"Bader charges computed from DFT calculations are targets for the AI charge "
"distribution model. These predict **oxidation state, charge transfer, and redox activity** "
"— critical inputs to the voltage predictor."
)
bader_df = build_bader_df()
st.markdown("#### Average Charges Heatmap (all 4 structures)")
st.plotly_chart(sib_bader_heatmap(bader_df), use_container_width=True,
key="sib_bader_heatmap")
st.markdown("#### Average Cation Charges Comparison")
st.plotly_chart(sib_charge_transfer_bar(bader_df), use_container_width=True,
key="sib_charge_transfer")
st.markdown("#### Per-atom Bader Charge Distribution")
sib_mat_sel = st.selectbox(
"Select material", ["NaFePO₄", "Na₂MnNiO₄"],
key="sib_bader_mat_sel"
)
sib_struct_sel = st.radio(
"Select structure",
["Unit Cell", "Supercell 2×2×1"],
horizontal=True, key="sib_bader_struct_sel"
)
struct_label = (
f"Unit Cell ({28 if sib_mat_sel == 'NaFePO₄' else 24} atoms)"
if sib_struct_sel == "Unit Cell"
else f"Supercell 2×2×1 ({128 if sib_mat_sel == 'NaFePO₄' else 96} atoms)"
)
sub_bader = bader_df[
(bader_df["Material"] == sib_mat_sel) &
(bader_df["Structure"] == struct_label)
]
if not sub_bader.empty:
st.plotly_chart(sib_bader_box(sub_bader.assign(Material=sib_mat_sel), sib_mat_sel),
use_container_width=True, key="sib_bader_box")
st.markdown("#### Summary Table — Average Charges")
st.dataframe(SIB_AVG_CHARGES, use_container_width=True)
st.info("""
**Key charge insights:**
- **Na⁺ in NaFePO₄:** +0.890 e — consistent with Na⁺ ionic character
- **Fe in NaFePO₄:** +1.486 e — confirms Fe²⁺ oxidation state (partially covalent Fe−O bonds)
- **P in NaFePO₄:** +5.0 e — fully ionic P⁵⁺ (phosphate PO₄³⁻ group)
- **Mn in Na₂MnNiO₄:** +1.729 e — Mn³⁺ state; active redox centre
- **Ni in Na₂MnNiO₄:** +1.283 e — Ni²⁺ state; will oxidize to Ni³⁺ on desodiation
- **O charge range:** −1.13 to −1.87 e — wider range in NaFePO₄ (multiple O environments)
""")
# ── SIB Sub-tab 4: Na Diffusion ────────────────────────────────────────────
with sib_tabs[3]:
st.markdown("### Na⁺ Diffusion Prediction — Stage 5: Sodium Diffusion Prediction")
st.markdown(
"Using the 128-atom NaFePO₄ and 96-atom Na₂MnNiO₄ supercells, Na vacancy and "
"interstitial migration pathways are generated. ML-NEB predicts migration barriers "
"and diffusion coefficients."
)
c1, c2, c3, c4 = st.columns(4)
with c1:
st.metric("Best D (NaFePO₄)", "8.5×10⁻¹⁰ cm²/s", "b-axis vacancy hop")
with c2:
st.metric("Min Barrier (NaFePO₄)", "0.18 eV", "b-axis olivine tunnel")
with c3:
st.metric("Best D (Na₂MnNiO₄)", "6.8×10⁻¹¹ cm²/s", "ab-plane vacancy")
with c4:
st.metric("Min Barrier (Na₂MnNiO₄)", "0.25 eV", "2D ab-plane hop")
st.plotly_chart(sib_diffusion_bar(SIB_DIFFUSION), use_container_width=True,
key="sib_diffusion_bar")
st.markdown("#### Diffusion Mechanism Details")
st.dataframe(
SIB_DIFFUSION.style.background_gradient(
subset=["Migration Barrier Ea (eV)"], cmap="Reds"
),
use_container_width=True,
)
st.info("""
**Diffusion insights:**
- **NaFePO₄ (olivine):** Strongly anisotropic — b-axis is the primary Na⁺ channel
(Eₐ=0.18 eV) while c-axis is nearly blocked (Eₐ=0.55 eV)
- **Na₂MnNiO₄ (layered):** 2D diffusion in ab-plane (Eₐ=0.25 eV); large interlayer
spacing (32 Å) severely limits c-direction hopping (Eₐ=0.68 eV)
- Both materials show **higher barriers than Li in LiF** (~64 kJ/mol ≈ 0.66 eV),
but Na diffusion benefits from a larger ionic radius increasing tunnel size
""")
# ── SIB Sub-tab 5: Cathode Ranking ────────────────────────────────────────
with sib_tabs[4]:
st.markdown("### AI Cathode Ranking — Stage 8: Intelligent Cathode Ranking")
st.markdown(
"The AI ranks cathode candidates using a weighted 5-property score. "
"DFT-computed data (NaFePO₄: **89/100**, Na₂MnNiO₄: **82/100**) anchors the ranking."
)
r1, r2, r3 = st.columns(3)
with r1:
st.metric("🥇 NaFePO₄ AI Score", "89.0 / 100", "DFT-validated")
with r2:
st.metric("🥈 Na₂MnNiO₄ AI Score", "82.0 / 100", "DFT-validated")
with r3:
st.metric("Screening pool", "8 materials", "2 DFT + 3 AI + 3 literature")
st.plotly_chart(sib_cathode_ranking_chart(SIB_CATHODE_RANKING),
use_container_width=True, key="sib_cathode_ranking")
st.markdown("#### Cathode Property Radar")
avail_mats = SIB_CATHODE_RANKING["Material"].tolist()
sel_radar_mats = st.multiselect(
"Select cathodes to compare",
avail_mats,
default=avail_mats[:3],
key="sib_radar_mats",
)
if sel_radar_mats:
st.plotly_chart(sib_cathode_radar(SIB_CATHODE_RANKING, sel_radar_mats),
use_container_width=True, key="sib_cathode_radar")
st.markdown("#### Full Ranking Table")
st.dataframe(
SIB_CATHODE_RANKING.style.background_gradient(
subset=["AI Score (0–100)"], cmap="YlGn"
).format({"AI Score (0–100)": "{:.1f}", "AI Score": "{:.2f}"}),
use_container_width=True,
)
st.markdown("#### Ranking Weights")
wt_cols = st.columns(5)
for col, (prop, wt) in zip(wt_cols, [
("Stability", "25%"), ("Voltage", "25%"),
("Diffusion", "20%"), ("Capacity", "20%"), ("Safety", "10%"),
]):
col.metric(prop, wt)
# ── SIB Sub-tab 6: AI Pipeline ─────────────────────────────────────────────
with sib_tabs[5]:
st.markdown("### 8-Stage AI Pipeline")
st.markdown(
"Each piece of DFT data maps to a specific AI pipeline stage. "
"This view shows which stages are complete, ready, or in progress."
)
st.plotly_chart(sib_pipeline_status(SIB_PIPELINE_STAGES),
use_container_width=True, key="sib_pipeline_status")
st.markdown("#### Detailed Pipeline Mapping")
for _, row in SIB_PIPELINE_STAGES.iterrows():
status_emoji = {
"Done": "✅", "Ready": "🔵", "Trained": "🟡",
"Predicted": "🟣", "Training": "🔴",
}.get(row["Status"], "⚪")
with st.expander(f"{status_emoji} Stage {row['Stage']}: {row['Name']}{row['Status']}"):
cols = st.columns(2)
with cols[0]:
st.markdown("**DFT Input:**")
st.info(row["DFT Input"])
with cols[1]:
st.markdown("**AI Output:**")
st.success(row["AI Output"])
st.markdown("#### Legend")
leg_cols = st.columns(5)
for col, (color, label) in zip(leg_cols, [
("🟢", "Done — data already computed"),
("🔵", "Ready — can run immediately"),
("🟡", "Trained — model ready"),
("🟣", "Predicted — AI output available"),
("🔴", "Training — in progress"),
]):
col.markdown(f"{color} {label}")
# ── SIB Sub-tab 7: AI Screening ────────────────────────────────────────────
with sib_tabs[6]:
st.markdown("### AI-Screened Hypothetical Cathodes — Stage 3 Output")
st.markdown(
"Once trained on the DFT-validated entries, the formation energy predictor "
"screens hypothetical compositions. Below are the first 10 candidates including "
"the two DFT-validated anchor points."
)
st.plotly_chart(sib_screened_bubble(SIB_SCREENED),
use_container_width=True, key="sib_screened_bubble")
st.markdown("#### Screened Cathode Table")
st.dataframe(
SIB_SCREENED.style.background_gradient(
subset=["Predicted Voltage (V)", "Predicted Capacity (mAh/g)"], cmap="Blues"
).background_gradient(
subset=["Predicted Eform (eV/atom)"], cmap="Greens_r"
),
use_container_width=True,
)
st.info("""
**Reading the bubble chart:**
- **X-axis:** Predicted voltage vs Na/Na⁺ (higher → more energy dense)
- **Y-axis:** Predicted capacity in mAh/g (higher → more charge storage)
- **Bubble size:** Absolute value of formation energy (larger → more stable)
- **Colour:** Red = DFT-verified, Green = AI-predicted
**Top AI candidate:** Na₂Mn0.5Co0.5O₄ — highest capacity (200 mAh/g) and voltage (3.55 V)
but lower stability (Eform = −1.72 eV/atom). Needs DFT validation.
""")
st.markdown("#### Next DFT Calculations Recommended")
next_dft = pd.DataFrame({
"Structure": [
"Na₀.₈₇₅FePO₄ (vacancy)", "Na₀.₇₅FePO₄ (vacancy)",
"FePO₄ (fully desodiated)", "NaFePO₄ + NEB path",
"Na₂MnNiO₄ + NEB path", "NaFePO₄ elastic tensor",
"NaFePO₄ PDOS/bandgap",
],
"Purpose": [
"Partial desodiation state", "Half desodiation state",
"Fully charged state", "Na migration barrier (DFT NEB)",
"Na migration barrier (DFT NEB)", "Mechanical stability",
"Electronic properties → conductivity",
],
"AI Stage": [
"Stage 2 (Dataset)", "Stage 2 (Dataset)",
"Stage 3 (Eform)", "Stage 5 (Diffusion)",
"Stage 5 (Diffusion)", "Stage 7 (Performance)",
"Stage 7 (Performance)",
],
"Priority": ["High", "High", "High", "Critical", "Critical", "Medium", "Medium"],
})
st.dataframe(
next_dft.style.applymap(
lambda v: "color: #f87171" if v == "Critical" else
"color: #F59E0B" if v == "High" else
"color: #4ade80",
subset=["Priority"],
),
use_container_width=True,
)
# ── SIB Sub-tab 8: Voltage & Capacity ─────────────────────────────────────
with sib_tabs[7]:
st.markdown("### Voltage & Capacity Prediction — Stage 7: Battery Performance")
st.markdown(
"Predicted from charge distribution (Stage 4) combined with formation energy (Stage 3). "
"The Fe²⁺→Fe³⁺ and Mn³⁺→Mn⁴⁺ redox couples determine operating voltage."
)
v1, v2, v3, v4 = st.columns(4)
with v1:
st.metric("NaFePO₄ Voltage", "2.87 V vs Na/Na⁺", "Fe²⁺/Fe³⁺ couple")
with v2:
st.metric("Na₂MnNiO₄ Voltage", "3.45 V vs Na/Na⁺", "Mn³⁺/Ni²⁺ couple")
with v3:
st.metric("NaFePO₄ Capacity", "154 mAh/g", "1 Na per formula unit")
with v4:
st.metric("Na₂MnNiO₄ Capacity", "195 mAh/g", "2 Na per formula unit")
st.markdown("#### Voltage State Table (DFT computed)")
st.dataframe(SIB_VOLTAGE, use_container_width=True)
# Simple voltage vs capacity scatter
fig_vc = px.scatter(
SIB_SCREENED,
x="Predicted Voltage (V)", y="Predicted Capacity (mAh/g)",
color="Source", symbol="Stability",
size=[20] * len(SIB_SCREENED),
text="Material",
title="Voltage vs Capacity — All Cathode Candidates",
color_discrete_map={"DFT": "#FF4757", "AI": "#4ade80"},
labels={"Predicted Voltage (V)": "Voltage (V vs Na/Na⁺)"},
)
fig_vc.update_traces(textposition="top center", textfont_size=8)
fig_vc.add_hline(y=150, line_dash="dot", line_color="#aaa",
annotation_text="Min target: 150 mAh/g")
fig_vc.add_vline(x=2.5, line_dash="dot", line_color="#aaa",
annotation_text="Min target: 2.5 V")
fig_vc.update_layout(
plot_bgcolor="#0E1117", paper_bgcolor="#0E1117",
font_color="#e5e7eb", height=450,
)
st.plotly_chart(fig_vc, use_container_width=True, key="sib_vc_scatter")
st.info("""
**Energy density comparison:**
- **NaFePO₄:** 2.87 V × 154 mAh/g ≈ **442 Wh/kg** (theoretical)
- **Na₂MnNiO₄:** 3.45 V × 195 mAh/g ≈ **674 Wh/kg** (theoretical)
- **LiB LiFePO₄ (reference):** 3.45 V × 170 mAh/g ≈ 586 Wh/kg
Na₂MnNiO₄ shows competitive energy density with LiFePO₄ while using
earth-abundant Mn/Ni instead of scarce Li.
""")
st.markdown("#### Industrial Vision — Battery Foundation Model")
st.markdown("""
The DFT-computed dataset is the **seed** for the Battery Foundation Model:
| Input | AI Engine Module | Output |
|-------|-----------------|--------|
| DFT structures | Structure Encoder (GNN) | Latent material representation |
| Formation energies | Formation Energy Predictor | Screen 10⁶ candidates |
| Bader charges | Charge Distribution Model | Voltage prediction |
| Supercell structures | Diffusion Predictor | Na-ion conductivity |
| MD trajectories | Cycle-Life Predictor | Degradation forecast |
| Experimental data | Calibration layer | Validated recommendations |
**Recommended next steps:**
1. Add vacancy structures (Na₀.₈₇₅FePO₄, Na₀.₇₅FePO₄)
2. Run NEB calculations for both materials
3. Compute elastic tensors and electronic bandgaps
4. Integrate finite-temperature (300/500/700 K) MD trajectories
5. Expand to full cathode library (10+ materials) for the production platform
""")