def test_dump_bonds(self): measure = BondSet(self.data_dir.joinpath("sugar.bnd"), DummyOptions) frame = Frame(self.data_dir.joinpath("sugar.gro"), self.data_dir.joinpath("sugar.xtc")) mapping = Mapping(self.data_dir.joinpath("sugar.map"), DummyOptions) cg_frame = mapping.apply(frame) measure.apply(cg_frame) measure.boltzmann_invert() measure.dump_values() filenames = ("ALLA_length.dat", "ALLA_angle.dat", "ALLA_dihedral.dat") for filename in filenames: self.assertTrue( cmp_file_whitespace_float(self.data_dir.joinpath(filename), filename, rtol=0.008, verbose=True)) os.remove(filename)
class PyCGTOOL: def __init__(self, config): self.config = config self.in_frame = Frame( topology_file=self.config.topology, trajectory_file=self.config.trajectory, # May be None frame_start=self.config.begin, frame_end=self.config.end, ) self.mapping = None self.out_frame = self.in_frame if self.config.mapping: self.mapping, self.out_frame = self.apply_mapping(self.in_frame) if self.out_frame.natoms == 0: # The following steps won't work with frames containing no atoms logger.warning( "Mapped trajectory contains no atoms - check your mapping file is correct!" ) return self.bondset = None if self.config.bondset: self.bondset = BondSet(self.config.bondset, self.config) self.measure_bonds() if self.config.output_xtc: self.out_frame.save(self.get_output_filepath("xtc")) def get_output_filepath(self, ext: PathLike) -> pathlib.Path: """Get file path for an output file by extension. Creates the output directory if missing. """ out_dir = pathlib.Path(self.config.out_dir) out_dir.mkdir(parents=True, exist_ok=True) return out_dir.joinpath(self.config.output_name + "." + ext) def apply_mapping(self, in_frame: Frame) -> typing.Tuple[Mapping, Frame]: """Map input frame to output using requested mapping file.""" mapping = Mapping(self.config.mapping, self.config, itp_filename=self.config.itp) out_frame = mapping.apply(in_frame) out_frame.save(self.get_output_filepath("gro"), frame_number=0) if self.config.backmapper_resname and self.out_frame.n_frames > 1: try: self.train_backmapper(self.config.resname) except ImportError: logger.error("MDPlus must be installed to perform backmapping") return mapping, out_frame def measure_bonds(self) -> None: """Measure bonds at the end of a run.""" self.bondset.apply(self.out_frame) if self.mapping is not None and self.out_frame.n_frames > 1: # Only perform Boltzmann Inversion if we have a mapping and a trajectory. # Otherwise we get infinite force constants. logger.info("Starting Boltzmann Inversion") self.bondset.boltzmann_invert() logger.info("Finished Boltzmann Inversion") if self.config.output_forcefield: logger.info("Writing GROMACS forcefield directory") out_dir = pathlib.Path(self.config.out_dir) forcefield = ForceField(self.config.output_name, dir_path=out_dir) forcefield.write(self.config.output_name, self.mapping, self.bondset) logger.info("Finished writing GROMACS forcefield directory") else: self.bondset.write_itp(self.get_output_filepath("itp"), mapping=self.mapping) if self.config.dump_measurements: logger.info("Writing bond measurements to file") self.bondset.dump_values(self.config.dump_n_values, self.config.out_dir) logger.info("Finished writing bond measurements to file") def train_backmapper(self, resname: str): from mdplus.multiscale import GLIMPS sel = f"resname {resname}" aa_subset_traj = self.in_frame._trajectory.atom_slice( self.in_frame._trajectory.topology.select(sel)) cg_subset_traj = self.out_frame._trajectory.atom_slice( self.out_frame._trajectory.topology.select(sel)) logger.info("Training backmapper") # Param x_valence is approximate number of bonds per CG bead # Values greater than 2 fail for small molecules e.g. sugar test case backmapper = GLIMPS(x_valence=2) backmapper.fit(cg_subset_traj.xyz, aa_subset_traj.xyz) logger.info("Finished training backmapper") backmapper.save(self.get_output_filepath("backmapper.pkl"))