def _collect_results(self, td_state: Dict[str, Any], molecule: "Ligand") -> "Ligand": """ After the torsiondrive has been completed collect the results and pack them into the ligand. """ # torsiondrive will only give us the lowest energy for each grid point # we have to then work out what the final geometry is from this optimised_energies = td_api.collect_lowest_energies(td_state=td_state) final_grid = td_state["grid_status"] # make a torsiondrive data store assuming 1D only torsion_data = TorsionDriveData( grid_spacing=self.grid_spacing, dihedral=td_state["dihedrals"][0], torsion_drive_range=td_state["dihedral_ranges"][0], ) # now grab each grid point in sorted order for ( angle, energy, ) in sorted(optimised_energies.items()): # grab all optimisation done at this angle optimisations = final_grid[str(angle[0])] # loop over each result and check if the energy matches the lowest # results -> (initial geometry, final geometry, final energy) for result in optimisations: if result[-1] == energy: grid_data = TorsionData( angle=angle[0], geometry=np.array(result[1]) * constants.BOHR_TO_ANGS, energy=energy, ) torsion_data.add_grid_point(grid_data=grid_data) break # validate the data torsion_data.validate_angles() # dump to file (qdata.txt and scan.xyz) export_torsiondrive_data(molecule=molecule, tdrive_data=torsion_data) # dump the qubekit torsion data to file torsion_data.to_file("scan_data.json") # save to mol molecule.add_qm_scan(scan_data=torsion_data) # dump the torsiondrive state to file self._dump_state(td_state=td_state) # now remove all temp folders for f in os.listdir("."): if os.path.isdir(f): shutil.rmtree(f, ignore_errors=True) return molecule
def prep_for_fitting(self, molecule: Ligand) -> List[str]: """ For the given ligand prep the input files ready for torsion profile fitting. Args: molecule: The molecule object that we need to prep for fitting, this should have qm reference data stored in molecule.qm_scans. Note: We assume we are already in the targets folder. Returns: A list of target folder names made by this target. Raises: MissingReferenceData: If the molecule does not have any torsion drive reference data saved in molecule.qm_scans. """ # make sure we have data if not molecule.qm_scans: raise MissingReferenceData( f"Can not prepare a forcebalance fitting target for {molecule.name} as the reference data is missing!" ) # write out the qdata and other input files for each scan target_folders = [] # keep track of where we start base_folder = os.getcwd() # loop over each scanned bond and make a target folder for scan in molecule.qm_scans: task_name = ( f"{self.target_name}_{scan.central_bond[0]}_{scan.central_bond[1]}" ) target_folders.append(task_name) make_and_change_into(name=task_name) # make the pdb topology file if molecule.has_ub_terms(): molecule._to_ub_pdb(file_name="molecule") else: molecule.to_file(file_name="molecule.pdb") # write the qdata file export_torsiondrive_data(molecule=molecule, tdrive_data=scan) # make the metadata self.make_metadata(torsiondrive_data=scan) # now move back to the base os.chdir(base_folder) return target_folders
def test_qdata_round_trip(tmpdir): """ Try and round trip a qdata txt file to a TorsionDriveData object and back. """ with tmpdir.as_cwd(): mol = Ligand.from_file(file_name=get_data("biphenyl.sdf")) td_ref = TorsionDriveData.from_qdata( dihedral=(6, 10, 11, 8), qdata_file=get_data("biphenyl_qdata.txt")) # now write to file export_torsiondrive_data(molecule=mol, tdrive_data=td_ref) # now make a second object td_new = TorsionDriveData.from_qdata(dihedral=(6, 10, 11, 8), qdata_file="qdata.txt") assert td_ref.dihedral == td_new.dihedral for angle, ref_result in td_ref.reference_data.items(): new_result = td_new.reference_data[angle] assert ref_result.angle == new_result.angle assert ref_result.energy == pytest.approx(new_result.energy) assert np.allclose(ref_result.geometry.tolist(), new_result.geometry.tolist())