Spaces:
Running
Running
Update src/streamlit_app.py
Browse files- src/streamlit_app.py +92 -112
src/streamlit_app.py
CHANGED
|
@@ -1,5 +1,6 @@
|
|
| 1 |
import streamlit as st
|
| 2 |
import os
|
|
|
|
| 3 |
import tempfile
|
| 4 |
import torch
|
| 5 |
# FOR CPU only mode
|
|
@@ -24,7 +25,11 @@ import yaml # Added for FairChem reference energies
|
|
| 24 |
import subprocess
|
| 25 |
import sys
|
| 26 |
import pkg_resources
|
|
|
|
|
|
|
| 27 |
mattersim_available = False
|
|
|
|
|
|
|
| 28 |
# try:
|
| 29 |
# subprocess.check_call([sys.executable, "-m", "pip", "install", "mattersim"])
|
| 30 |
# except Exception as e:
|
|
@@ -1028,14 +1033,14 @@ if model_type == "ORB":
|
|
| 1028 |
if "omat" in selected_model:
|
| 1029 |
st.sidebar.warning("Using model under Academic Software License (ASL) license, see [https://github.com/gabor1/ASL](https://github.com/gabor1/ASL). To use this model you accept the terms of the license.")
|
| 1030 |
# selected_default_dtype = st.sidebar.selectbox("Select Precision (default_dtype):", ['float32-high', 'float32-highest', 'float64'])
|
| 1031 |
-
selected_default_dtype = '
|
| 1032 |
if model_type == "MatterSim":
|
| 1033 |
selected_model = st.sidebar.selectbox("Select MatterSim Model:", list(MATTERSIM_MODELS.keys()))
|
| 1034 |
model_path = MATTERSIM_MODELS[selected_model]
|
| 1035 |
if model_type == "SEVEN_NET":
|
| 1036 |
selected_model = st.sidebar.selectbox("Select SEVENNET Model:", list(SEVEN_NET_MODELS.keys()))
|
| 1037 |
if selected_model == '7net-mf-ompa':
|
| 1038 |
-
selected_modal_7net = st.sidebar.selectbox("Select Modal (multi fidelity model):", ['
|
| 1039 |
model_path = SEVEN_NET_MODELS[selected_model]
|
| 1040 |
if atoms is not None:
|
| 1041 |
if not check_atom_limit(atoms, selected_model):
|
|
@@ -1055,7 +1060,8 @@ task = st.sidebar.selectbox("Select Calculation Task:",
|
|
| 1055 |
"Energy + Forces Calculation",
|
| 1056 |
"Atomization/Cohesive Energy", # New Task Added
|
| 1057 |
"Geometry Optimization",
|
| 1058 |
-
"Cell + Geometry Optimization"
|
|
|
|
| 1059 |
|
| 1060 |
if "Optimization" in task:
|
| 1061 |
st.sidebar.markdown("### Optimization Parameters")
|
|
@@ -1117,18 +1123,19 @@ if atoms is not None:
|
|
| 1117 |
table_placeholder = st.empty() # Recreate placeholder for table
|
| 1118 |
|
| 1119 |
try:
|
|
|
|
| 1120 |
with st.spinner("Running calculation... Please wait."):
|
| 1121 |
calc_atoms = atoms.copy()
|
| 1122 |
|
| 1123 |
if model_type == "MACE":
|
| 1124 |
# st.write("Setting up MACE calculator...")
|
| 1125 |
-
calc = get_mace_model(model_path, device,
|
| 1126 |
elif model_type == "FairChem": # FairChem
|
| 1127 |
# st.write("Setting up FairChem calculator...")
|
| 1128 |
# Workaround for potential dtype issues when switching models
|
| 1129 |
-
if device == "cpu": # Ensure torch default dtype matches if needed
|
| 1130 |
-
|
| 1131 |
-
_ = get_mace_model(MACE_MODELS["MACE MP 0a Small"], 'cpu', 'float32') # Dummy call
|
| 1132 |
calc = get_fairchem_model(selected_model, model_path, device, selected_task_type)
|
| 1133 |
elif model_type == "ORB":
|
| 1134 |
# st.write("Setting up ORB calculator...")
|
|
@@ -1136,10 +1143,14 @@ if atoms is not None:
|
|
| 1136 |
calc = ORBCalculator(orbff, device=device)
|
| 1137 |
elif model_type == "MatterSim":
|
| 1138 |
# st.write("Setting up MatterSim calculator...")
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1139 |
calc = MatterSimCalculator(load_path=model_path, device=device)
|
| 1140 |
elif model_type == "SEVEN_NET":
|
| 1141 |
# st.write("Setting up SEVENNET calculator...")
|
| 1142 |
-
if model_path=='7net-mf-
|
| 1143 |
calc = SevenNetCalculator(model=model_path, modal=selected_modal_7net, device=device)
|
| 1144 |
else:
|
| 1145 |
calc = SevenNetCalculator(model=model_path, device=device)
|
|
@@ -1234,6 +1245,7 @@ if atoms is not None:
|
|
| 1234 |
results["Total Isolated Atom Energy ($\sum E_{atoms}$)"] = f"{E_isolated_atoms_total:.6f} eV"
|
| 1235 |
|
| 1236 |
elif "Optimization" in task: # Handles both Geometry and Cell+Geometry Opt
|
|
|
|
| 1237 |
opt_atoms_obj = FrechetCellFilter(calc_atoms) if task == "Cell + Geometry Optimization" else calc_atoms
|
| 1238 |
# Create temporary trajectory file
|
| 1239 |
traj_filename = tempfile.NamedTemporaryFile(delete=False, suffix=".traj").name
|
|
@@ -1268,30 +1280,15 @@ if atoms is not None:
|
|
| 1268 |
|
| 1269 |
if "Optimization" in task and "Final Energy" in results: # Check if opt was successful
|
| 1270 |
st.markdown("### Optimized Structure")
|
| 1271 |
-
# Need get_structure_viz function that takes atoms obj
|
| 1272 |
-
def get_structure_viz_simple(atoms_obj_viz):
|
| 1273 |
-
xyz_str_viz = f"{len(atoms_obj_viz)}\nStructure\n"
|
| 1274 |
-
for atom_viz in atoms_obj_viz:
|
| 1275 |
-
xyz_str_viz += f"{atom_viz.symbol} {atom_viz.position[0]:.6f} {atom_viz.position[1]:.6f} {atom_viz.position[2]:.6f}\n"
|
| 1276 |
-
view_viz = py3Dmol.view(width=400, height=400)
|
| 1277 |
-
view_viz.addModel(xyz_str_viz, "xyz")
|
| 1278 |
-
view_viz.setStyle({'stick': {}})
|
| 1279 |
-
if any(atoms_obj_viz.pbc): # Show cell for optimized periodic structures
|
| 1280 |
-
cell_viz = atoms_obj_viz.get_cell()
|
| 1281 |
-
if cell_viz is not None and cell_viz.any():
|
| 1282 |
-
# Simplified cell drawing for brevity, use get_structure_viz2 if full cell needed
|
| 1283 |
-
view_viz.addUnitCell({'box': {'lx':cell_viz.lengths()[0],'ly':cell_viz.lengths()[1],'lz':cell_viz.lengths()[2],
|
| 1284 |
-
'hx':cell_viz.cellpar()[3],'hy':cell_viz.cellpar()[4],'hz':cell_viz.cellpar()[5]}})
|
| 1285 |
|
| 1286 |
-
|
| 1287 |
-
view_viz.setBackgroundColor('white')
|
| 1288 |
-
return view_viz
|
| 1289 |
-
|
| 1290 |
-
opt_view = get_structure_viz2(calc_atoms, style=viz_style, show_unit_cell=True, width=400, height=400)
|
| 1291 |
st.components.v1.html(opt_view._make_html(), width=400, height=400)
|
| 1292 |
|
| 1293 |
with tempfile.NamedTemporaryFile(delete=False, suffix=".xyz", mode="w+") as tmp_file_opt:
|
| 1294 |
-
|
|
|
|
|
|
|
|
|
|
| 1295 |
tmp_filepath_opt = tmp_file_opt.name
|
| 1296 |
|
| 1297 |
with open(tmp_filepath_opt, 'r') as file_opt:
|
|
@@ -1309,81 +1306,6 @@ if atoms is not None:
|
|
| 1309 |
show_optimized_structure_download_button()
|
| 1310 |
os.unlink(tmp_filepath_opt)
|
| 1311 |
|
| 1312 |
-
# # Convert trajectory to XYZ for download
|
| 1313 |
-
# @st.fragment
|
| 1314 |
-
# def show_trajectory():
|
| 1315 |
-
# if os.path.exists(traj_filename):
|
| 1316 |
-
# try:
|
| 1317 |
-
# from ase.io import read
|
| 1318 |
-
# from ase.visualize import view
|
| 1319 |
-
# import py3Dmol
|
| 1320 |
-
|
| 1321 |
-
# trajectory = read(traj_filename, index=':')
|
| 1322 |
-
# st.markdown("### Optimization Trajectory")
|
| 1323 |
-
# st.write(f"Captured {len(trajectory)} optimization steps")
|
| 1324 |
-
|
| 1325 |
-
# # Store the trajectory in session state
|
| 1326 |
-
# if "traj_frames" not in st.session_state:
|
| 1327 |
-
# st.session_state.traj_frames = trajectory
|
| 1328 |
-
# st.session_state.traj_index = 0
|
| 1329 |
-
|
| 1330 |
-
# # Navigation Buttons
|
| 1331 |
-
# col1, col2, col3, col4 = st.columns(4)
|
| 1332 |
-
# with col1:
|
| 1333 |
-
# if st.button("⏮ First"):
|
| 1334 |
-
# st.session_state.traj_index = 0
|
| 1335 |
-
# with col2:
|
| 1336 |
-
# if st.button("◀ Previous") and st.session_state.traj_index > 0:
|
| 1337 |
-
# st.session_state.traj_index -= 1
|
| 1338 |
-
# with col3:
|
| 1339 |
-
# if st.button("Next ▶") and st.session_state.traj_index < len(st.session_state.traj_frames) - 1:
|
| 1340 |
-
# st.session_state.traj_index += 1
|
| 1341 |
-
# with col4:
|
| 1342 |
-
# if st.button("Last ⏭"):
|
| 1343 |
-
# st.session_state.traj_index = len(st.session_state.traj_frames) - 1
|
| 1344 |
-
|
| 1345 |
-
# # Show current frame
|
| 1346 |
-
# current_atoms = st.session_state.traj_frames[st.session_state.traj_index]
|
| 1347 |
-
# st.write(f"Frame {st.session_state.traj_index + 1}/{len(st.session_state.traj_frames)}")
|
| 1348 |
-
|
| 1349 |
-
# # Convert to xyz string for py3Dmol
|
| 1350 |
-
# def atoms_to_xyz_string(atoms):
|
| 1351 |
-
# xyz_str = f"{len(atoms)}\nStep {st.session_state.traj_index}, Energy = {atoms.get_potential_energy():.6f} eV\n"
|
| 1352 |
-
# for atom in atoms:
|
| 1353 |
-
# xyz_str += f"{atom.symbol} {atom.position[0]:.6f} {atom.position[1]:.6f} {atom.position[2]:.6f}\n"
|
| 1354 |
-
# return xyz_str
|
| 1355 |
-
|
| 1356 |
-
# xyz_str = atoms_to_xyz_string(current_atoms)
|
| 1357 |
-
# view = py3Dmol.view(width=400, height=400)
|
| 1358 |
-
# view.addModel(xyz_str, "xyz")
|
| 1359 |
-
# view.setStyle({'stick': {}})
|
| 1360 |
-
# view.zoomTo()
|
| 1361 |
-
# view.setBackgroundColor("white")
|
| 1362 |
-
# st.components.v1.html(view._make_html(), height=400, width=400)
|
| 1363 |
-
|
| 1364 |
-
# # Download entire trajectory
|
| 1365 |
-
# @st.fragment
|
| 1366 |
-
# def show_trajectory_download_button():
|
| 1367 |
-
# trajectory_xyz = ""
|
| 1368 |
-
# for i, atoms in enumerate(st.session_state.traj_frames):
|
| 1369 |
-
# trajectory_xyz += f"{len(atoms)}\nStep {i}, Energy = {atoms.get_potential_energy():.6f} eV\n"
|
| 1370 |
-
# for atom in atoms:
|
| 1371 |
-
# trajectory_xyz += f"{atom.symbol} {atom.position[0]:.6f} {atom.position[1]:.6f} {atom.position[2]:.6f}\n"
|
| 1372 |
-
# st.download_button(
|
| 1373 |
-
# label="Download Optimization Trajectory (XYZ)",
|
| 1374 |
-
# data=trajectory_xyz,
|
| 1375 |
-
# file_name="optimization_trajectory.xyz",
|
| 1376 |
-
# mime="chemical/x-xyz"
|
| 1377 |
-
# )
|
| 1378 |
-
# show_trajectory_download_button()
|
| 1379 |
-
|
| 1380 |
-
# except Exception as e:
|
| 1381 |
-
# st.warning(f"Could not process trajectory: {e}")
|
| 1382 |
-
|
| 1383 |
-
# finally:
|
| 1384 |
-
# os.unlink(traj_filename)
|
| 1385 |
-
|
| 1386 |
-
# show_trajectory()
|
| 1387 |
@st.fragment
|
| 1388 |
def show_trajectory_and_controls():
|
| 1389 |
from ase.io import read
|
|
@@ -1439,14 +1361,6 @@ if atoms is not None:
|
|
| 1439 |
xyz_str += f"{atom.symbol} {atom.position[0]:.6f} {atom.position[1]:.6f} {atom.position[2]:.6f}\n"
|
| 1440 |
return xyz_str
|
| 1441 |
|
| 1442 |
-
# xyz_str = atoms_to_xyz_string(current_atoms, st.session_state.traj_index)
|
| 1443 |
-
|
| 1444 |
-
# view = py3Dmol.view(width=400, height=400)
|
| 1445 |
-
# view.addModel(xyz_str, "xyz")
|
| 1446 |
-
# view.setStyle({'stick': {}})
|
| 1447 |
-
# view.zoomTo()
|
| 1448 |
-
# view.setBackgroundColor("white")
|
| 1449 |
-
# st.components.v1.html(view._make_html(), height=400, width=400)
|
| 1450 |
traj_view = get_structure_viz2(current_atoms, style=viz_style, show_unit_cell=True, width=400, height=400)
|
| 1451 |
st.components.v1.html(traj_view._make_html(), width=400, height=400)
|
| 1452 |
|
|
@@ -1462,7 +1376,73 @@ if atoms is not None:
|
|
| 1462 |
)
|
| 1463 |
|
| 1464 |
show_trajectory_and_controls()
|
|
|
|
| 1465 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1466 |
except Exception as e:
|
| 1467 |
st.error(f"🔴 Calculation error: {str(e)}")
|
| 1468 |
st.error("Please check the structure, model compatibility, and parameters. For FairChem UMA, ensure the task type (omol, omat etc.) is appropriate for your system (e.g. omol for molecules, omat for materials).")
|
|
@@ -1499,4 +1479,4 @@ with st.expander('ℹ️ About This App & Foundational MLIPs'):
|
|
| 1499 |
- For **FairChem models**, isolated atom energies are based on pre-tabulated reference values (provided in a YAML-like structure within the app). Ensure the selected FairChem task type (`omol`, `omat`, etc. for UMA models) or model type (ESEN models use `omol` references) aligns with the system and has the necessary elemental references.
|
| 1500 |
""")
|
| 1501 |
st.markdown("Universal MLIP Playground App | Created with Streamlit, ASE, MACE, FairChem, SevenNet, ORB and ❤️")
|
| 1502 |
-
st.markdown("Developed by [Manas Sharma](https://manas.bragitoff.com/) in the groups of [Prof. Ananth Govind Rajan Group](https://www.agrgroup.org/) and [Prof. Sudeep Punnathanam](https://chemeng.iisc.ac.in/sudeep/) at [IISc Bangalore](https://iisc.ac.in/)")
|
|
|
|
| 1 |
import streamlit as st
|
| 2 |
import os
|
| 3 |
+
import io
|
| 4 |
import tempfile
|
| 5 |
import torch
|
| 6 |
# FOR CPU only mode
|
|
|
|
| 25 |
import subprocess
|
| 26 |
import sys
|
| 27 |
import pkg_resources
|
| 28 |
+
from ase.vibrations import Vibrations
|
| 29 |
+
import matplotlib.pyplot as plt
|
| 30 |
mattersim_available = False
|
| 31 |
+
if mattersim_available:
|
| 32 |
+
from mattersim.forcefield import MatterSimCalculator
|
| 33 |
# try:
|
| 34 |
# subprocess.check_call([sys.executable, "-m", "pip", "install", "mattersim"])
|
| 35 |
# except Exception as e:
|
|
|
|
| 1033 |
if "omat" in selected_model:
|
| 1034 |
st.sidebar.warning("Using model under Academic Software License (ASL) license, see [https://github.com/gabor1/ASL](https://github.com/gabor1/ASL). To use this model you accept the terms of the license.")
|
| 1035 |
# selected_default_dtype = st.sidebar.selectbox("Select Precision (default_dtype):", ['float32-high', 'float32-highest', 'float64'])
|
| 1036 |
+
selected_default_dtype = st.sidebar.selectbox("Select Precision (default_dtype):", ['float32-high', 'float32-highest'])
|
| 1037 |
if model_type == "MatterSim":
|
| 1038 |
selected_model = st.sidebar.selectbox("Select MatterSim Model:", list(MATTERSIM_MODELS.keys()))
|
| 1039 |
model_path = MATTERSIM_MODELS[selected_model]
|
| 1040 |
if model_type == "SEVEN_NET":
|
| 1041 |
selected_model = st.sidebar.selectbox("Select SEVENNET Model:", list(SEVEN_NET_MODELS.keys()))
|
| 1042 |
if selected_model == '7net-mf-ompa':
|
| 1043 |
+
selected_modal_7net = st.sidebar.selectbox("Select Modal (multi fidelity model):", ['omat24', 'mpa'])
|
| 1044 |
model_path = SEVEN_NET_MODELS[selected_model]
|
| 1045 |
if atoms is not None:
|
| 1046 |
if not check_atom_limit(atoms, selected_model):
|
|
|
|
| 1060 |
"Energy + Forces Calculation",
|
| 1061 |
"Atomization/Cohesive Energy", # New Task Added
|
| 1062 |
"Geometry Optimization",
|
| 1063 |
+
"Cell + Geometry Optimization",
|
| 1064 |
+
"Vibrational Mode Analysis"])
|
| 1065 |
|
| 1066 |
if "Optimization" in task:
|
| 1067 |
st.sidebar.markdown("### Optimization Parameters")
|
|
|
|
| 1123 |
table_placeholder = st.empty() # Recreate placeholder for table
|
| 1124 |
|
| 1125 |
try:
|
| 1126 |
+
torch.set_default_dtype(torch.float32)
|
| 1127 |
with st.spinner("Running calculation... Please wait."):
|
| 1128 |
calc_atoms = atoms.copy()
|
| 1129 |
|
| 1130 |
if model_type == "MACE":
|
| 1131 |
# st.write("Setting up MACE calculator...")
|
| 1132 |
+
calc = get_mace_model(model_path, device, 'float32')
|
| 1133 |
elif model_type == "FairChem": # FairChem
|
| 1134 |
# st.write("Setting up FairChem calculator...")
|
| 1135 |
# Workaround for potential dtype issues when switching models
|
| 1136 |
+
# if device == "cpu": # Ensure torch default dtype matches if needed
|
| 1137 |
+
# torch.set_default_dtype(torch.float32)
|
| 1138 |
+
# _ = get_mace_model(MACE_MODELS["MACE MP 0a Small"], 'cpu', 'float32') # Dummy call
|
| 1139 |
calc = get_fairchem_model(selected_model, model_path, device, selected_task_type)
|
| 1140 |
elif model_type == "ORB":
|
| 1141 |
# st.write("Setting up ORB calculator...")
|
|
|
|
| 1143 |
calc = ORBCalculator(orbff, device=device)
|
| 1144 |
elif model_type == "MatterSim":
|
| 1145 |
# st.write("Setting up MatterSim calculator...")
|
| 1146 |
+
# NOTE: Running mattersim on windows requires changing source code file
|
| 1147 |
+
# https://github.com/microsoft/mattersim/issues/112
|
| 1148 |
+
# mattersim/datasets/utils/convertor.py: 117
|
| 1149 |
+
# to pbc_ = np.array(structure.pbc, dtype=np.int64)
|
| 1150 |
calc = MatterSimCalculator(load_path=model_path, device=device)
|
| 1151 |
elif model_type == "SEVEN_NET":
|
| 1152 |
# st.write("Setting up SEVENNET calculator...")
|
| 1153 |
+
if model_path=='7net-mf-ompa':
|
| 1154 |
calc = SevenNetCalculator(model=model_path, modal=selected_modal_7net, device=device)
|
| 1155 |
else:
|
| 1156 |
calc = SevenNetCalculator(model=model_path, device=device)
|
|
|
|
| 1245 |
results["Total Isolated Atom Energy ($\sum E_{atoms}$)"] = f"{E_isolated_atoms_total:.6f} eV"
|
| 1246 |
|
| 1247 |
elif "Optimization" in task: # Handles both Geometry and Cell+Geometry Opt
|
| 1248 |
+
is_periodic = any(calc_atoms.pbc)
|
| 1249 |
opt_atoms_obj = FrechetCellFilter(calc_atoms) if task == "Cell + Geometry Optimization" else calc_atoms
|
| 1250 |
# Create temporary trajectory file
|
| 1251 |
traj_filename = tempfile.NamedTemporaryFile(delete=False, suffix=".traj").name
|
|
|
|
| 1280 |
|
| 1281 |
if "Optimization" in task and "Final Energy" in results: # Check if opt was successful
|
| 1282 |
st.markdown("### Optimized Structure")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1283 |
|
| 1284 |
+
opt_view = get_structure_viz2(opt_atoms_obj, style=viz_style, show_unit_cell=True, width=400, height=400)
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1285 |
st.components.v1.html(opt_view._make_html(), width=400, height=400)
|
| 1286 |
|
| 1287 |
with tempfile.NamedTemporaryFile(delete=False, suffix=".xyz", mode="w+") as tmp_file_opt:
|
| 1288 |
+
if is_periodic:
|
| 1289 |
+
write(tmp_file_opt.name, calc_atoms, format="extxyz")
|
| 1290 |
+
else:
|
| 1291 |
+
write(tmp_file_opt.name, calc_atoms, format="xyz")
|
| 1292 |
tmp_filepath_opt = tmp_file_opt.name
|
| 1293 |
|
| 1294 |
with open(tmp_filepath_opt, 'r') as file_opt:
|
|
|
|
| 1306 |
show_optimized_structure_download_button()
|
| 1307 |
os.unlink(tmp_filepath_opt)
|
| 1308 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1309 |
@st.fragment
|
| 1310 |
def show_trajectory_and_controls():
|
| 1311 |
from ase.io import read
|
|
|
|
| 1361 |
xyz_str += f"{atom.symbol} {atom.position[0]:.6f} {atom.position[1]:.6f} {atom.position[2]:.6f}\n"
|
| 1362 |
return xyz_str
|
| 1363 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1364 |
traj_view = get_structure_viz2(current_atoms, style=viz_style, show_unit_cell=True, width=400, height=400)
|
| 1365 |
st.components.v1.html(traj_view._make_html(), width=400, height=400)
|
| 1366 |
|
|
|
|
| 1376 |
)
|
| 1377 |
|
| 1378 |
show_trajectory_and_controls()
|
| 1379 |
+
elif task == "Vibrational Mode Analysis":
|
| 1380 |
|
| 1381 |
+
st.write("Running vibrational mode analysis using finite differences...")
|
| 1382 |
+
|
| 1383 |
+
natoms = len(calc_atoms)
|
| 1384 |
+
is_linear = False # Set manually or auto-detect
|
| 1385 |
+
nmodes_expected = 3 * natoms - (5 if is_linear else 6)
|
| 1386 |
+
|
| 1387 |
+
# Create temporary directory to store .vib files
|
| 1388 |
+
with tempfile.TemporaryDirectory() as tmpdir:
|
| 1389 |
+
vib = Vibrations(calc_atoms, name=os.path.join(tmpdir, 'vib'))
|
| 1390 |
+
|
| 1391 |
+
with st.spinner("Calculating vibrational modes... This may take a few minutes."):
|
| 1392 |
+
vib.run()
|
| 1393 |
+
freqs = vib.get_frequencies()
|
| 1394 |
+
|
| 1395 |
+
# Convert frequencies to cm⁻¹
|
| 1396 |
+
freqs_cm = freqs #/ cm
|
| 1397 |
+
|
| 1398 |
+
# Classify frequencies
|
| 1399 |
+
mode_data = []
|
| 1400 |
+
for i, freq in enumerate(freqs_cm):
|
| 1401 |
+
if freq < 0:
|
| 1402 |
+
label = "Imaginary"
|
| 1403 |
+
elif abs(freq) < 500:
|
| 1404 |
+
label = "Low"
|
| 1405 |
+
else:
|
| 1406 |
+
label = "Physical"
|
| 1407 |
+
mode_data.append({
|
| 1408 |
+
"Mode": i + 1,
|
| 1409 |
+
"Frequency (cm⁻¹)": round(freq, 2),
|
| 1410 |
+
"Type": label
|
| 1411 |
+
})
|
| 1412 |
+
|
| 1413 |
+
df_modes = pd.DataFrame(mode_data)
|
| 1414 |
+
|
| 1415 |
+
# Display summary and mode count
|
| 1416 |
+
st.success("Vibrational analysis completed.")
|
| 1417 |
+
st.write(f"Number of atoms: {natoms}")
|
| 1418 |
+
st.write(f"Expected vibrational modes: {nmodes_expected}")
|
| 1419 |
+
st.write(f"Found {len(freqs_cm)} modes (including translational/rotational modes).")
|
| 1420 |
+
|
| 1421 |
+
# Show table of modes
|
| 1422 |
+
st.write("### Vibrational Mode Summary")
|
| 1423 |
+
st.dataframe(df_modes, use_container_width=True)
|
| 1424 |
+
|
| 1425 |
+
# Store in results dictionary
|
| 1426 |
+
results["Vibrational Modes"] = df_modes.to_dict(orient="records")
|
| 1427 |
+
|
| 1428 |
+
# Histogram plot of vibrational frequencies
|
| 1429 |
+
st.write("### Frequency Distribution Histogram")
|
| 1430 |
+
fig, ax = plt.subplots()
|
| 1431 |
+
ax.hist(freqs_cm, bins=30, color='skyblue', edgecolor='black')
|
| 1432 |
+
ax.set_xlabel("Frequency (cm⁻¹)")
|
| 1433 |
+
ax.set_ylabel("Number of Modes")
|
| 1434 |
+
ax.set_title("Distribution of Vibrational Frequencies")
|
| 1435 |
+
st.pyplot(fig)
|
| 1436 |
+
|
| 1437 |
+
# CSV download
|
| 1438 |
+
csv_buffer = io.StringIO()
|
| 1439 |
+
df_modes.to_csv(csv_buffer, index=False)
|
| 1440 |
+
st.download_button(
|
| 1441 |
+
label="Download Vibrational Frequencies (CSV)",
|
| 1442 |
+
data=csv_buffer.getvalue(),
|
| 1443 |
+
file_name="vibrational_modes.csv",
|
| 1444 |
+
mime="text/csv"
|
| 1445 |
+
)
|
| 1446 |
except Exception as e:
|
| 1447 |
st.error(f"🔴 Calculation error: {str(e)}")
|
| 1448 |
st.error("Please check the structure, model compatibility, and parameters. For FairChem UMA, ensure the task type (omol, omat etc.) is appropriate for your system (e.g. omol for molecules, omat for materials).")
|
|
|
|
| 1479 |
- For **FairChem models**, isolated atom energies are based on pre-tabulated reference values (provided in a YAML-like structure within the app). Ensure the selected FairChem task type (`omol`, `omat`, etc. for UMA models) or model type (ESEN models use `omol` references) aligns with the system and has the necessary elemental references.
|
| 1480 |
""")
|
| 1481 |
st.markdown("Universal MLIP Playground App | Created with Streamlit, ASE, MACE, FairChem, SevenNet, ORB and ❤️")
|
| 1482 |
+
st.markdown("Developed by [Manas Sharma](https://manas.bragitoff.com/) in the groups of [Prof. Ananth Govind Rajan Group](https://www.agrgroup.org/) and [Prof. Sudeep Punnathanam](https://chemeng.iisc.ac.in/sudeep/) at [IISc Bangalore](https://iisc.ac.in/)")
|