@@ -972,6 +972,45 @@ def _rebuild_and_minimise(self):
972972 self ._gcmc_sampler ._set_water_state (self ._omm_mols )
973973 self ._gcmc_sampler .pop ()
974974
975+ if self ._save_crash_report :
976+ import openmm
977+ import numpy as np
978+ from copy import deepcopy
979+ from uuid import uuid4
980+
981+ # Create a unique identifier for this crash report.
982+ crash_id = str (uuid4 ())[:8 ]
983+
984+ # Get the current context and system.
985+ context = self ._omm_mols
986+ system = deepcopy (context .getSystem ())
987+
988+ # Add each force to a unique group.
989+ for i , f in enumerate (system .getForces ()):
990+ f .setForceGroup (i )
991+
992+ # Create a new context.
993+ new_context = openmm .Context (system , deepcopy (context .getIntegrator ()))
994+ new_context .setPositions (context .getState (getPositions = True ).getPositions ())
995+
996+ # Write the energies for each force group.
997+ with open (f"crash_{ crash_id } .log" , "w" ) as f :
998+ f .write (f"Current lambda: { str (self .get_lambda ())} \n " )
999+ for i , force in enumerate (system .getForces ()):
1000+ state = new_context .getState (getEnergy = True , groups = {i })
1001+ f .write (f"{ force .getName ()} , { state .getPotentialEnergy ()} \n " )
1002+
1003+ # Save the serialised system.
1004+ with open (f"system_{ crash_id } .xml" , "w" ) as f :
1005+ f .write (openmm .XmlSerializer .serialize (system ))
1006+
1007+ # Save the positions.
1008+ positions = (
1009+ new_context .getState (getPositions = True ).getPositions (asNumpy = True )
1010+ / openmm .unit .nanometer
1011+ )
1012+ np .savetxt (f"positions_{ crash_id } .txt" , positions )
1013+
9751014 self .run_minimisation ()
9761015
9771016 def run (
@@ -990,6 +1029,7 @@ def run(
9901029 null_energy : str = None ,
9911030 excess_chemical_potential : float = None ,
9921031 num_waters : int = None ,
1032+ save_crash_report : bool = False ,
9931033 ):
9941034 if self .is_null ():
9951035 return
@@ -1009,6 +1049,7 @@ def run(
10091049 "null_energy" : null_energy ,
10101050 "excess_chemical_potential" : excess_chemical_potential ,
10111051 "num_waters" : num_waters ,
1052+ "save_crash_report" : save_crash_report ,
10121053 }
10131054
10141055 from concurrent .futures import ThreadPoolExecutor
@@ -1048,6 +1089,13 @@ def run(
10481089 except :
10491090 raise ValueError ("'num_waters' must be an integer" )
10501091
1092+ if save_crash_report is not None :
1093+ if not isinstance (save_crash_report , bool ):
1094+ raise ValueError ("'save_crash_report' must be True or False" )
1095+ self ._save_crash_report = save_crash_report
1096+ else :
1097+ self ._save_crash_report = False
1098+
10511099 try :
10521100 steps_to_run = int (time .to (picosecond ) / self .timestep ().to (picosecond ))
10531101 except Exception :
@@ -1649,6 +1697,7 @@ def run(
16491697 null_energy : str = None ,
16501698 excess_chemical_potential : str = None ,
16511699 num_waters : int = None ,
1700+ save_crash_report : bool = False ,
16521701 ):
16531702 """
16541703 Perform dynamics on the molecules.
@@ -1722,6 +1771,13 @@ def run(
17221771 Whether to save the energy on exit, regardless of whether
17231772 the energy frequency has been reached.
17241773
1774+ save_crash_report: bool
1775+ Whether to save a crash report if the dynamics fails due to an
1776+ instability. This will save a named log file containing the energy
1777+ for each force, an XML file containing the OpenMM system at the
1778+ start of the dynamics block, and a NumPy text file containing
1779+ the atomic positions at the start of the dynamics block.
1780+
17251781 auto_fix_minimise: bool
17261782 Whether or not to automatically run minimisation if the
17271783 trajectory exits with an error in the first few steps.
@@ -1768,6 +1824,14 @@ def run(
17681824 else :
17691825 save_velocities = False
17701826
1827+ if save_crash_report is None :
1828+ if self ._d ._map .specified ("save_crash_report" ):
1829+ save_crash_report = (
1830+ self ._d ._map ["save_crash_report" ].value ().as_bool ()
1831+ )
1832+ else :
1833+ save_crash_report = False
1834+
17711835 self ._d .run (
17721836 time = time ,
17731837 save_frequency = save_frequency ,
@@ -1778,6 +1842,7 @@ def run(
17781842 save_velocities = save_velocities ,
17791843 save_frame_on_exit = save_frame_on_exit ,
17801844 save_energy_on_exit = save_energy_on_exit ,
1845+ save_crash_report = save_crash_report ,
17811846 auto_fix_minimise = auto_fix_minimise ,
17821847 num_energy_neighbours = num_energy_neighbours ,
17831848 null_energy = null_energy ,
0 commit comments