abmelt-benchmark / src /md_simulation.py
ZijianGuan's picture
Upload folder using huggingface_hub
8ef403e verified
#!/usr/bin/env python3
"""
MD Simulation module for AbMelt inference pipeline.
Handles GROMACS preprocessing, system setup, and multi-temperature MD simulations.
"""
import os
import sys
import logging
from pathlib import Path
from typing import Dict, List, Optional, Tuple
import numpy as np
# Add the original AbMelt src to path for imports
sys.path.append(str(Path(__file__).parent.parent.parent / "AbMelt" / "src"))
try:
import gromacs
from preprocess import protonation_state, canonical_index, edit_mdp
from timing import time_step
except ImportError as e:
logging.error(f"Failed to import required modules: {e}")
raise
logger = logging.getLogger(__name__)
def run_md_simulation(structure_files: Dict[str, str], config: Dict) -> Dict[str, str]:
"""
Run complete MD simulation workflow for antibody structure.
Args:
structure_files: Dictionary containing PDB file path and work directory
config: Configuration dictionary with simulation parameters
Returns:
Dictionary containing trajectory files and simulation results
"""
logger.info("Starting MD simulation workflow...")
pdb_file = structure_files["pdb_file"]
work_dir = Path(structure_files["work_dir"])
antibody_name = work_dir.name
# Store original working directory
original_cwd = os.getcwd()
# Change to work directory
os.chdir(work_dir)
# Get the PDB filename (not full path) for GROMACS commands
pdb_filepath = Path(pdb_file)
pdb_filename = pdb_filepath.name
try:
# Setup GROMACS environment with MDP template path
gromacs_config = config.get("gromacs", {})
mdp_dir = gromacs_config.get("mdp_dir", "mdp")
print(f"mdp_dir: {mdp_dir}")
if not Path(mdp_dir).is_absolute():
mdp_dir = str(Path(__file__).parent.parent / mdp_dir)
setup_gromacs_environment(mdp_dir=mdp_dir)
# Create temperature-specific MDP files in template directory
temperatures = config.get("simulation")['temperatures']
for temp in temperatures:
temp_str = str(temp)
for mdp_type in ["nvt", "npt", "md"]:
mdp_file = Path(f"{mdp_type}_{temp_str}.mdp")
src_file = Path(mdp_dir) / f"{mdp_type}.mdp"
dst_file = mdp_file
print(f"src_file: {src_file} , {src_file.exists()}")
print(f"dst_file: {dst_file} , {dst_file.exists()}")
if src_file.exists() and not dst_file.exists():
# Copy base MDP file and modify temperature in template directory
import shutil
shutil.copy2(src_file, dst_file)
_modify_mdp_temperature(dst_file, temp)
logger.info(f"Created {mdp_file} for {temp}K in template directory")
elif dst_file.exists():
logger.info(f"Temperature-specific MDP file already exists: {mdp_file}")
else:
logger.warning(f"Base MDP file not found: {src_file}")
# Step 1: GROMACS preprocessing
with time_step("GROMACS Preprocessing", parent="MD Simulation"):
logger.info("Step 1: GROMACS preprocessing...")
gromacs_files = _preprocess_for_gromacs(pdb_filename, pdb_filepath, config)
# Step 2: System setup
with time_step("System Setup", parent="MD Simulation"):
logger.info("Step 2: Setting up simulation system...")
system_files = _setup_simulation_system(gromacs_files, config)
# Step 3: Multi-temperature MD simulations
with time_step("Multi-Temp Simulations", parent="MD Simulation"):
logger.info("Step 3: Running multi-temperature MD simulations...")
trajectory_files = _run_multi_temp_simulations(system_files, config)
# Step 4: Process trajectories
with time_step("Trajectory Processing", parent="MD Simulation"):
logger.info("Step 4: Processing trajectories...")
processed_trajectories = _process_trajectories(trajectory_files, config)
result = {
"status": "success",
"trajectory_files": processed_trajectories,
"work_dir": str(work_dir),
"message": "MD simulations completed successfully"
}
logger.info("MD simulation workflow completed successfully")
return result
except Exception as e:
logger.error(f"MD simulation failed: {e}")
raise
finally:
# Restore original working directory
os.chdir(original_cwd)
def _preprocess_for_gromacs(pdb_filename: str, pdb_filepath, config: Dict) -> Dict[str, str]:
"""
Preprocess PDB file for GROMACS: pKa calculation, conversion, and indexing.
Args:
pdb_filename: Name of input PDB file (should be in current working directory)
config: Configuration dictionary
Returns:
Dictionary containing GROMACS files
"""
logger.info("Preprocessing structure for GROMACS...")
# Get simulation parameters
sim_config = config["simulation"]
pH = sim_config["pH"]
force_field = sim_config["force_field"]
water_model = sim_config["water_model"]
# Step 1: Calculate protonation states
logger.info("Calculating protonation states...")
gromacs_input = protonation_state(
pdb_filename=pdb_filename,
pdb_path=pdb_filepath,
pH=pH
)
print("protonation state")
print(gromacs_input)
print('\n\n\n')
# Step 2: Convert PDB to GROMACS format
logger.info("Converting PDB to GROMACS format...")
gromacs.pdb2gmx(
f=pdb_filepath,
o="processed.pdb",
p="topol.top",
ff=force_field,
water=water_model,
ignh=True,
his=True,
renum=True,
input=gromacs_input
)
gromacs.pdb2gmx(
f="processed.pdb",
o="processed.gro",
p="topol.top",
ff=force_field,
water=water_model,
ignh=True,
his=True,
renum=True,
input=gromacs_input
)
# Step 3: Create index groups for CDRs
logger.info("Creating index groups for CDRs...")
# Files are in current working directory (already changed to work_dir)
annotation = canonical_index(pdb="processed.pdb")
gromacs.make_ndx(f="processed.gro", o="index.ndx", input=annotation)
return {
"processed_pdb": config['paths']['temp_dir'] / "processed.pdb",
"processed_gro": config['paths']['temp_dir'] / "processed.gro",
"topology": config['paths']['temp_dir'] / "topol.top",
"index": config['paths']['temp_dir'] / "index.ndx"
}
def _setup_simulation_system(gromacs_files: Dict[str, str], config: Dict) -> Dict[str, str]:
"""
Set up simulation system: box creation, solvation, and ion addition.
Args:
gromacs_files: Dictionary containing GROMACS files
config: Configuration dictionary
Returns:
Dictionary containing system files
"""
import os
logger.info("Setting up simulation system...")
# Get simulation parameters
sim_config = config["simulation"]
salt_concentration = sim_config["salt_concentration"] # mM
p_salt = sim_config["p_salt"]
n_salt = sim_config["n_salt"]
# Step 1: Create simulation box
logger.info("Creating simulation box...")
gromacs.editconf(
f=gromacs_files["processed_gro"],
o='box.gro',
c=True,
d='1.0',
bt='triclinic'
)
# Step 2: Add water
logger.info("Adding water molecules...")
gromacs.solvate(
cp="box.gro",
cs="spc216.gro",
p=gromacs_files["topology"],
o="solv.gro"
)
# Step 3: Add ions
logger.info("Adding ions...")
logger.info(f"Current working directory: {os.getcwd()}")
logger.info(f"GROMACS config.path: {gromacs.config.path}")
logger.info(f"Looking for ions.mdp in: {[os.path.join(p, 'ions.mdp') for p in gromacs.config.path]}")
try:
ions = gromacs.config.get_templates('ions.mdp')
logger.info(f"Found ions template: {ions}")
except Exception as e:
logger.error(f"Failed to get ions.mdp template: {e}")
# Try to find the file manually
for path in gromacs.config.path:
ions_path = os.path.join(path, 'ions.mdp')
if os.path.exists(ions_path):
logger.info(f"Found ions.mdp manually at: {ions_path}")
break
raise
gromacs.grompp(
f=ions[0],
c="solv.gro",
p=gromacs_files["topology"],
o="ions.tpr"
)
gromacs.genion(
s="ions.tpr",
o="solv_ions.gro",
p=gromacs_files["topology"],
pname=p_salt,
nname=n_salt,
conc=salt_concentration/1000,
neutral=True,
input=['13']
)
# Step 4: Energy minimization
logger.info("Running energy minimization...")
em = gromacs.config.get_templates('em.mdp')
gromacs.grompp(
f=em,
c="solv_ions.gro",
p=gromacs_files["topology"],
o="em.tpr"
)
gromacs.mdrun(v=True, deffnm="em")
return {
"solvated_gro": config['paths']['temp_dir'] / "solv_ions.gro",
"topology": gromacs_files["topology"],
"index": gromacs_files["index"],
"em_gro": config['paths']['temp_dir'] / "em.gro"
}
def _run_multi_temp_simulations(system_files: Dict[str, str], config: Dict) -> Dict[str, str]:
"""
Run multi-temperature MD simulations.
Args:
system_files: Dictionary containing system files
config: Configuration dictionary
Returns:
Dictionary containing trajectory files
"""
logger.info("Running multi-temperature MD simulations...")
# Get simulation parameters
sim_config = config["simulation"]
temperatures = sim_config["temperatures"]
simulation_time = sim_config["simulation_time"] # ns
gpu_enabled = sim_config["gpu_enabled"]
# Get GROMACS settings
gromacs_config = config["gromacs"]
n_threads = gromacs_config["n_threads"]
gpu_id = gromacs_config["gpu_id"]
# Available pre-installed temperatures
avail_temps = ['300', '310', '350', '373', '400']
temps = [str(temp) for temp in temperatures]
trajectory_files = {}
for temp in temps:
logger.info(f"Running simulation at {temp}K...")
try:
# Required temp files are already created
# Use pre-installed temperature files
trajectory_files[temp] = _run_preinstalled_temp_simulation(
temp, system_files, simulation_time, gpu_enabled, n_threads, gpu_id
)
# if temp in avail_temps:
# else:
# # Create custom temperature files
# trajectory_files[temp] = _run_custom_temp_simulation(
# temp, system_files, simulation_time, gpu_enabled, n_threads, gpu_id
# )
except Exception as e:
logger.error(f"Simulation failed at {temp}K: {e}")
raise
return trajectory_files
def _run_preinstalled_temp_simulation(temp: str, system_files: Dict[str, str],
simulation_time: int, gpu_enabled: bool,
n_threads: int, gpu_id: int) -> Dict[str, str]:
"""Run simulation using pre-installed temperature files."""
# Get MDP templates
nvt = gromacs.config.get_templates('nvt_' + temp + '.mdp')
npt = gromacs.config.get_templates('npt_' + temp + '.mdp')
md = gromacs.config.get_templates('md_' + temp + '.mdp')
# NVT equilibration
logger.info(f"Running NVT equilibration at {temp}K...")
gromacs.grompp(
f=nvt[0],
o='nvt_' + temp + '.tpr',
c=system_files["em_gro"],
r=system_files["em_gro"],
p=system_files["topology"]
)
if gpu_enabled:
gromacs.mdrun(
deffnm='nvt_' + temp,
ntomp=str(n_threads),
nb='gpu',
pme='gpu',
update='gpu',
bonded='cpu',
pin='on'
)
else:
gromacs.mdrun(deffnm='nvt_' + temp, ntomp=str(n_threads))
# NPT equilibration
logger.info(f"Running NPT equilibration at {temp}K...")
gromacs.grompp(
f=npt[0],
o='npt_' + temp + '.tpr',
t='nvt_' + temp + '.cpt',
c='nvt_' + temp + '.gro',
r='nvt_' + temp + '.gro',
p=system_files["topology"],
maxwarn='1'
)
if gpu_enabled:
gromacs.mdrun(
deffnm='npt_' + temp,
ntomp=str(n_threads),
nb='gpu',
pme='gpu',
update='gpu',
bonded='cpu',
pin='on'
)
else:
gromacs.mdrun(deffnm='npt_' + temp, ntomp=str(n_threads))
# Production MD
logger.info(f"Running production MD at {temp}K...")
if simulation_time == 100:
gromacs.grompp(
f=md[0],
o='md_' + temp + '.tpr',
t='npt_' + temp + '.cpt',
c='npt_' + temp + '.gro',
p=system_files["topology"]
)
if gpu_enabled:
gromacs.mdrun(
deffnm='md_' + temp,
ntomp=str(n_threads),
nb='gpu',
pme='gpu',
update='gpu',
bonded='cpu',
pin='on'
)
else:
gromacs.mdrun(deffnm='md_' + temp, ntomp=str(n_threads))
else:
# Custom simulation time
new_mdp = 'md_' + temp + '_' + str(simulation_time) + '.mdp'
template_path = Path(gromacs.config.get_templates('md_' + temp + '.mdp')[0])
_modify_mdp_nsteps(template_path, new_mdp, int(simulation_time*1000*1000/2))
new_md = gromacs.config.get_templates(new_mdp)
gromacs.grompp(
f=new_md[0],
o='md_' + temp + '_' + str(simulation_time) + '.tpr',
t='npt_' + temp + '.cpt',
c='npt_' + temp + '.gro',
p=system_files["topology"]
)
if gpu_enabled:
gromacs.mdrun(
deffnm='md_' + temp + '_' + str(simulation_time),
ntomp=str(n_threads),
nb='gpu',
pme='gpu',
update='cpu',
# update='gpu',
bonded='cpu',
pin='on'
)
else:
gromacs.mdrun(deffnm='md_' + temp + '_' + str(simulation_time), ntomp=str(n_threads))
return {
"tpr_file": f"md_{temp}.tpr",
"xtc_file": f"md_{temp}.xtc",
"gro_file": f"md_{temp}.gro"
}
def _run_custom_temp_simulation(temp: str, system_files: Dict[str, str],
simulation_time: int, gpu_enabled: bool,
n_threads: int, gpu_id: int) -> Dict[str, str]:
"""Run simulation with custom temperature by modifying MDP files."""
# Create custom MDP files
nvt_mdp = 'nvt_' + temp + '.mdp'
npt_mdp = 'npt_' + temp + '.mdp'
md_mdp = 'md_' + temp + '.mdp'
# Modify temperature in MDP files
edit_mdp('nvt_300.mdp', new_mdp=nvt_mdp, ref_t=[temp, temp], gen_temp=temp)
edit_mdp('npt_300.mdp', new_mdp=npt_mdp, ref_t=[temp, temp])
edit_mdp('md_300.mdp', new_mdp=md_mdp, ref_t=[temp, temp])
# Get templates
new_nvt = gromacs.config.get_templates(nvt_mdp)
new_npt = gromacs.config.get_templates(npt_mdp)
new_md = gromacs.config.get_templates(md_mdp)
# Run simulations (similar to preinstalled but with custom MDPs)
# NVT
gromacs.grompp(
f=new_nvt[0],
o='nvt_' + temp + '.tpr',
c=system_files["em_gro"],
r=system_files["em_gro"],
p=system_files["topology"]
)
if gpu_enabled:
gromacs.mdrun(
deffnm='nvt_' + temp,
ntomp=str(n_threads),
nb='gpu',
pme='gpu',
update='gpu',
bonded='cpu',
pin='on'
)
else:
gromacs.mdrun(deffnm='nvt_' + temp, ntomp=str(n_threads))
# NPT
gromacs.grompp(
f=new_npt[0],
o='npt_' + temp + '.tpr',
t='nvt_' + temp + '.cpt',
c='nvt_' + temp + '.gro',
r='nvt_' + temp + '.gro',
p=system_files["topology"],
maxwarn='1'
)
if gpu_enabled:
gromacs.mdrun(
deffnm='npt_' + temp,
ntomp=str(n_threads),
nb='gpu',
pme='gpu',
update='gpu',
bonded='cpu',
pin='on'
)
else:
gromacs.mdrun(deffnm='npt_' + temp, ntomp=str(n_threads))
# Production MD
if simulation_time == 100:
gromacs.grompp(
f=new_md[0],
o='md_' + temp + '.tpr',
t='npt_' + temp + '.cpt',
c='npt_' + temp + '.gro',
p=system_files["topology"]
)
if gpu_enabled:
gromacs.mdrun(
deffnm='md_' + temp,
ntomp=str(n_threads),
nb='gpu',
pme='gpu',
update='gpu',
bonded='cpu',
pin='on'
)
else:
gromacs.mdrun(deffnm='md_' + temp, ntomp=str(n_threads))
else:
# Custom simulation time
new_mdp = 'md_' + temp + '_' + str(simulation_time) + '.mdp'
template_path = Path(gromacs.config.get_templates(md_mdp)[0])
_modify_mdp_nsteps(template_path, new_mdp, int(simulation_time*1000*1000/2))
new_md_custom = gromacs.config.get_templates(new_mdp)
gromacs.grompp(
f=new_md_custom[0],
o='md_' + temp + '_' + str(simulation_time) + '.tpr',
t='npt_' + temp + '.cpt',
c='npt_' + temp + '.gro',
p=system_files["topology"]
)
if gpu_enabled:
gromacs.mdrun(
deffnm='md_' + temp + '_' + str(simulation_time),
ntomp=str(n_threads),
nb='gpu',
pme='gpu',
update='gpu',
bonded='cpu',
pin='on'
)
else:
gromacs.mdrun(deffnm='md_' + temp + '_' + str(simulation_time), ntomp=str(n_threads))
return {
"tpr_file": f"md_{temp}.tpr",
"xtc_file": f"md_{temp}.xtc",
"gro_file": f"md_{temp}.gro"
}
def _process_trajectories(trajectory_files: Dict[str, Dict[str, str]], config: Dict) -> Dict[str, Dict[str, str]]:
"""
Process trajectories: remove PBC and prepare for analysis.
Args:
trajectory_files: Dictionary containing trajectory files for each temperature
config: Configuration dictionary
Returns:
Dictionary containing processed trajectory files
"""
logger.info("Processing trajectories...")
sim_config = config.get("simulation", {})
simulation_time = sim_config.get("simulation_time", 100)
processed_trajectories = {}
for temp, files in trajectory_files.items():
logger.info(f"Processing trajectory at {temp}K...")
try:
if simulation_time == 100:
# Remove periodic boundary conditions
gromacs.trjconv(
f=files["xtc_file"],
s=files["tpr_file"],
pbc='whole',
o='md_whole_' + temp + '.xtc',
input=['0']
)
gromacs.trjconv(
f='md_whole_' + temp + '.xtc',
s=files["tpr_file"],
pbc='nojump',
o='md_nopbcjump_' + temp + '.xtc',
input=['1']
)
# Create final trajectory and reference structure
gromacs.trjconv(
f='md_nopbcjump_' + temp + '.xtc',
s=files["tpr_file"],
b='0',
e='0',
o='md_final_' + temp + '.gro',
input=['1']
)
gromacs.trjconv(
f='md_nopbcjump_' + temp + '.xtc',
s=files["tpr_file"],
dt='0',
o='md_final_' + temp + '.xtc',
input=['1']
)
else:
# Custom simulation time
xtc_file = f"md_{temp}_{simulation_time}.xtc"
tpr_file = f"md_{temp}_{simulation_time}.tpr"
gromacs.trjconv(
f=xtc_file,
s=tpr_file,
pbc='whole',
o='md_whole_' + temp + '_' + str(simulation_time) + '.xtc',
input=['0']
)
gromacs.trjconv(
f='md_whole_' + temp + '_' + str(simulation_time) + '.xtc',
s=tpr_file,
pbc='nojump',
o='md_nopbcjump_' + temp + '_' + str(simulation_time) + '.xtc',
input=['1']
)
gromacs.trjconv(
f='md_nopbcjump_' + temp + '_' + str(simulation_time) + '.xtc',
s=tpr_file,
b='0',
e='0',
o='md_final_' + temp + '.gro',
input=['1']
)
gromacs.trjconv(
f='md_nopbcjump_' + temp + '_' + str(simulation_time) + '.xtc',
s=tpr_file,
dt='0',
o='md_final_' + temp + '.xtc',
input=['1']
)
# Rename for consistency
os.rename(tpr_file, files["tpr_file"])
processed_trajectories[temp] = {
"final_xtc": f'md_final_{temp}.xtc',
"final_gro": f'md_final_{temp}.gro',
"tpr_file": files["tpr_file"],
"log_file": files["log_file"]
}
except Exception as e:
logger.error(f"Trajectory processing failed at {temp}K: {e}")
raise
return processed_trajectories
def _modify_mdp_temperature(mdp_file: Path, temperature: int):
"""
Modify MDP file to set the correct temperature.
Args:
mdp_file: Path to MDP file to modify
temperature: Target temperature in Kelvin
"""
try:
with open(mdp_file, 'r') as f:
lines = f.readlines()
# Modify temperature-related lines
modified_lines = []
for line in lines:
if line.strip().startswith('ref_t'):
# Update reference temperature
modified_lines.append(f"ref_t = {temperature} {temperature} ; reference temperature, one for each group, in K\n")
elif line.strip().startswith('gen_temp'):
# Update generation temperature
modified_lines.append(f"gen_temp = {temperature} ; temperature for Maxwell distribution\n")
else:
modified_lines.append(line)
# Write the modified MDP file
with open(mdp_file, 'w') as f:
f.writelines(modified_lines)
except Exception as e:
logger.error(f"Failed to modify MDP file {mdp_file}: {e}")
def _modify_mdp_nsteps(template_file: Path, output_name: str, nsteps: int):
"""
Copy an MDP template into the working directory and update its nsteps value.
"""
try:
from shutil import copy2
destination = Path(output_name)
copy2(template_file, destination)
with open(destination, 'r') as src:
lines = src.readlines()
updated_lines = []
nsteps_updated = False
for line in lines:
if line.strip().startswith('nsteps'):
updated_lines.append(f"nsteps = {nsteps}\n")
nsteps_updated = True
else:
updated_lines.append(line)
if not nsteps_updated:
updated_lines.append(f"\n; inserted by pipeline\nnsteps = {nsteps}\n")
with open(destination, 'w') as dst:
dst.writelines(updated_lines)
gromacs.config.templates[output_name] = str(destination.resolve())
logger.info(f"Created {output_name} with nsteps={nsteps}")
except Exception as e:
logger.error(f"Failed to update nsteps for {template_file}: {e}")
raise
def setup_gromacs_environment(gromacs_path: str = None, mdp_dir: str = None):
"""
Setup GROMACS environment and configuration.
Args:
gromacs_path: Path to GROMACS installation (optional)
mdp_dir: Path to MDP template files directory (optional)
"""
if gromacs_path:
gromacs.config.set_gmxrc_environment(gromacs_path)
# Initialize GROMACS configuration first
gromacs.config.get_configuration()
gromacs.tools.registry
gromacs.config.check_setup()
gromacs.config.setup()
# Set MDP template directory path AFTER GROMACS initialization
if mdp_dir:
gromacs.config.path.append(mdp_dir)
logger.info(f"Added GROMACS template path : {mdp_dir}")
for f in Path(mdp_dir).glob("*.mdp"):
name = f.name
if f.name not in gromacs.config.templates:
gromacs.config.templates[name] = str(f)
print(f"gromacs.config.templates: {gromacs.config.templates}")
logger.info(f"Current gromacs.config.path: {gromacs.config.path}")
# Check if template files exist
import os
for template_file in ['ions.mdp', 'em.mdp', 'nvt.mdp', 'npt.mdp', 'md.mdp']:
template_path = os.path.join(mdp_dir, template_file)
exists = os.path.exists(template_path)
logger.info(f"Template file {template_file}: {'EXISTS' if exists else 'NOT FOUND'} at {template_path}")
logger.info(f"GROMACS version: {gromacs.release()}")
def load_existing_simulation_results(structure_files: Dict[str, str], config: Dict) -> Dict[str, str]:
"""
Load existing MD simulation results and validate they exist.
Args:
structure_files: Dictionary containing structure files
config: Configuration dictionary
Returns:
Dictionary matching format from run_md_simulation
Raises:
FileNotFoundError: If required trajectory files are missing
"""
logger.info("Loading existing MD simulation results...")
work_dir = Path(structure_files["work_dir"]).resolve()
temperatures = config["simulation"]["temperatures"]
simulation_time = config["simulation"]["simulation_time"]
trajectory_files = {}
missing_files = []
for temp in temperatures:
temp_str = str(temp)
# Required files per temperature
# Note: Log files are not required when loading existing results
# as they are only used for monitoring during simulation
final_xtc = work_dir / f"md_final_{temp_str}.xtc"
final_gro = work_dir / f"md_final_{temp_str}.gro"
tpr_file = work_dir / f"md_{temp_str}.tpr"
# Check if required files exist
temp_missing = []
if not final_xtc.exists():
temp_missing.append(str(final_xtc))
if not final_gro.exists():
temp_missing.append(str(final_gro))
if not tpr_file.exists():
temp_missing.append(str(tpr_file))
if temp_missing:
missing_files.append(f"Temperature {temp_str}K:")
missing_files.extend(f" - {f}" for f in temp_missing)
else:
trajectory_files[temp_str] = {
"final_xtc": f"md_final_{temp_str}.xtc",
"final_gro": f"md_final_{temp_str}.gro",
"tpr_file": f"md_{temp_str}.tpr"
}
if missing_files:
error_msg = f"Required MD simulation files not found when skipping MD step:\n"
error_msg += "\n".join(missing_files)
error_msg += f"\n\nWork directory: {work_dir}"
raise FileNotFoundError(error_msg)
result = {
"status": "success",
"trajectory_files": trajectory_files,
"work_dir": str(work_dir),
"message": "MD simulation results loaded successfully"
}
logger.info(f"Successfully loaded MD simulation results for {len(trajectory_files)} temperatures")
for temp, files in trajectory_files.items():
logger.info(f" {temp}K: {files['final_xtc']}")
return result
def validate_simulation_setup(config: Dict) -> bool:
"""
Validate that all required components are available for MD simulation.
Args:
config: Configuration dictionary
Returns:
True if setup is valid, False otherwise
"""
try:
# Check GROMACS installation
gromacs.config.check_setup()
# Check required MDP files
required_mdp_files = ['em.mdp', 'ions.mdp']
for temp in config.get("simulation", {}).get("temperatures", [300, 350, 400]):
temp_str = str(temp)
if temp_str in ['300', '310', '350', '373', '400']:
required_mdp_files.extend([
f'nvt_{temp_str}.mdp',
f'npt_{temp_str}.mdp',
f'md_{temp_str}.mdp'
])
# Check if MDP files exist (this would need to be implemented)
# For now, just return True
return True
except Exception as e:
logger.error(f"Simulation setup validation failed: {e}")
return False