def test_write_xml_parameters_methanol_ions_energy(self): """ Test writing XML parameter files from Charmm parameter files, reading them back into OpenMM ForceField, and computing energy of methanol and NaCl """ params = openmm.OpenMMParameterSet.from_parameterset( pmd.charmm.CharmmParameterSet(get_fn('par_all36_cgenff.prm'), get_fn('top_all36_cgenff.rtf'), get_fn('toppar_water_ions.str')) # WARNING: contains duplicate water templates ) del params.residues['TP3M'] # Delete to avoid duplicate water template topologies ffxml_filename = get_fn('charmm_conv.xml', written=True) params.write(ffxml_filename, provenance=dict( OriginalFile='par_all36_cgenff.prm, top_all36_cgenff.rtf, toppar_water_ions.str', Reference='MacKerrell' ) ) forcefield = app.ForceField(ffxml_filename) # Parameterize methanol and ions in vacuum pdbfile = app.PDBFile(get_fn('methanol_ions.pdb')) system = forcefield.createSystem(pdbfile.topology, nonbondedMethod=app.NoCutoff) integrator = mm.VerletIntegrator(1.0*u.femtoseconds) context = mm.Context(system, integrator, CPU) context.setPositions(pdbfile.positions) ffxml_potential = context.getState(getEnergy=True).getPotentialEnergy() / u.kilocalories_per_mole del context, integrator # Compute energy via ParmEd reader psf = CharmmPsfFile(get_fn('methanol_ions.psf')) system = psf.createSystem(params) integrator = mm.VerletIntegrator(1.0*u.femtoseconds) context = mm.Context(system, integrator, CPU) context.setPositions(pdbfile.positions) parmed_potential = context.getState(getEnergy=True).getPotentialEnergy() / u.kilocalories_per_mole del context, integrator # Ensure potentials are almost equal self.assertAlmostEqual(ffxml_potential, parmed_potential)
def test_create_simple_system(self): psf = CharmmPsfFile(get_fn("ala3_solv_drude.psf")) crd = CharmmCrdFile(get_fn("ala3_solv_drude.crd")) params = CharmmParameterSet( get_fn("toppar_drude_master_protein_2013e.str")) system = psf.createSystem(params)
for coord in coords: min_crds[0] = min(min_crds[0], coord[0]) min_crds[1] = min(min_crds[1], coord[1]) min_crds[2] = min(min_crds[2], coord[2]) max_crds[0] = max(max_crds[0], coord[0]) max_crds[1] = max(max_crds[1], coord[1]) max_crds[2] = max(max_crds[2], coord[2]) psf_solvate_1 = CharmmPsfFile('../structure/butane_solvated.psf') psf_solvate_1.box = (max_crds[0] - min_crds[0], max_crds[1] - min_crds[1], max_crds[2] - min_crds[2], 90.0, 90.0, 90.0) database['solvated']['structure_1'] = psf_solvate_1 psf_solvate_1.load_parameters(param_1, copy_parameters=False) system_solvate_1 = psf_solvate_1.createSystem( nonbondedMethod=app.PME, constraints=app.HBonds, nonbondedCutoff=12.0 * u.angstroms, switchDistance=10.0 * u.angstroms) database['solvated']['system_1'] = system_solvate_1 psf_solvate_2 = CharmmPsfFile('../structure/butane_solvated.psf') psf_solvate_2.box = (max_crds[0] - min_crds[0], max_crds[1] - min_crds[1], max_crds[2] - min_crds[2], 90.0, 90.0, 90.0) database['solvated']['structure_2'] = psf_solvate_2 psf_solvate_2.load_parameters(param_2, copy_parameters=False) system_solvate_2 = psf_solvate_2.createSystem( nonbondedMethod=app.PME, constraints=app.HBonds, nonbondedCutoff=12.0 * u.angstroms, switchDistance=10.0 * u.angstroms) database['solvated']['system_2'] = system_solvate_2
def compare_energies(system_name, pdb_filename, psf_filename, ffxml_filenames, toppar_filenames, box_vectors_filename=None, system_kwargs=None, tolerance=1e-5, units=u.kilojoules_per_mole, write_serialized_xml=False): """ Compare energies between (pdb, psf, toppar) loaded via ParmEd and (pdb, ffxml) loaded by OpenMM ForceField Parameters ---------- system_name : str Name of the test system pdb_filename : str Name of PDB file that should contain CRYST entry and PDB format compliant CONECT records for HETATM residues. psf_filename : str CHARMM PSF file ffxml_filenames : list of str List of OpenMM ffxml files toppar_filenames : list of CHARMM toppar filenames to load into CharmmParameterSet List of CHARMM toppar files box_vectors_filename : str, optional, default=None If specified, read box vectors from a file like step2.1_waterbox.prm system_kwargs : dict, optional, default=None Keyword arguments to pass to CharmmPsfFile.createSystem() and ForceField.CreateSystem() when constructing System objects for energy comparison tolerance : float, optional, default=1e-5 Relative energy discrepancy tolerance units : simtk.unit.Unit Unit to use for energy comparison write_serialized_xml : bool, optional, default=False If True, will write out serialized System XML files for OpenMM systems to aid debugging. """ is_periodic = False if (system_kwargs is not None) and ('nonbondedMethod' in system_kwargs) and ( system_kwargs['nonbondedMethod'] in [app.CutoffPeriodic, app.PME]): is_periodic = True # Load PDB file pdbfile = app.PDBFile(pdb_filename) # Read box vectors if box_vectors_filename: box_vectors = read_box_vectors(box_vectors_filename) pdbfile.topology.setPeriodicBoxVectors(box_vectors) else: box_vectors = pdbfile.topology.getPeriodicBoxVectors() # Load CHARMM system through OpenMM openmm_toppar = app.CharmmParameterSet(*toppar_filenames) openmm_psf = app.CharmmPsfFile(psf_filename) # Set box vectors if is_periodic: openmm_psf.setBox(box_vectors[0][0], box_vectors[1][1], box_vectors[2][2]) openmm_system = openmm_psf.createSystem(openmm_toppar, **system_kwargs) openmm_structure = openmm.load_topology(openmm_psf.topology, openmm_system, xyz=pdbfile.positions) #openmm_energies = openmm.energy_decomposition_system(openmm_structure, openmm_system, nrg=units) #print('OpenMM CHARMM loader energy components : %s' % str(openmm_energies)) openmm_total_energy = compute_potential(openmm_system, pdbfile.positions) / units # Load CHARMM system through ParmEd parmed_toppar = CharmmParameterSet(*toppar_filenames) parmed_structure = CharmmPsfFile(psf_filename) #structure.load_parameters(toppar) parmed_structure.positions = pdbfile.positions # Set box vectors if is_periodic: parmed_structure.box = (box_vectors[0][0] / u.angstroms, box_vectors[1][1] / u.angstroms, box_vectors[2][2] / u.angstroms, 90, 90, 90) parmed_system = parmed_structure.createSystem(parmed_toppar, **system_kwargs) #parmed_energies = openmm.energy_decomposition_system(parmed_structure, parmed_system, nrg=units) #print('ParmEd CHARMM loader energy components : %s' % str(parmed_energies)) parmed_total_energy = compute_potential(parmed_system, pdbfile.positions) / units # Delete H-H bonds from waters and retreive updated topology and positions modeller = app.Modeller(openmm_psf.topology, pdbfile.positions) hhbonds = [ b for b in modeller.topology.bonds() if b[0].element == app.element.hydrogen and b[1].element == app.element.hydrogen ] modeller.delete(hhbonds) ffxml_topology = modeller.topology # OpenMM system with ffxml ff = app.ForceField(*ffxml_filenames) ffxml_system = ff.createSystem(ffxml_topology, **system_kwargs) ffxml_structure = openmm.load_topology(ffxml_topology, ffxml_system, xyz=pdbfile.positions) #ffxml_energies = openmm.energy_decomposition_system(ffxml_structure, ffxml_system, nrg=units) #print('ffxml energy components : %s' % str(ffxml_energies)) ffxml_total_energy = compute_potential(ffxml_system, pdbfile.positions) / units write_serialized_xml = True # DEBUG if write_serialized_xml: print('Writing serialized XML files...') write_serialized_system(system_name + '.charmm.system.xml', openmm_system) write_serialized_system(system_name + '.parmed.system.xml', parmed_system) write_serialized_system(system_name + '.openmm.system.xml', ffxml_system) print('-' * 100) print('') print('OpenMM CHARMM reader total energy: %14.3f' % openmm_total_energy) print('ParmEd CHARMM reader total energy: %14.3f' % parmed_total_energy) print('OPENMM ffxml total energy: %14.3f' % ffxml_total_energy) print('TOTAL ERROR: %14.3f' % (ffxml_total_energy - openmm_total_energy)) print('') print('-' * 100) # TODO : Automate comparison return # calc rel energies and assert rel_energies = [] for i, j in zip(ffxml_energies, parmed_energies): if i[0] != j[0]: raise Exception('Mismatch in energy tuples naming.') if abs(i[1]) > NEARLYZERO: rel_energies.append((i[0], abs((i[1] - j[1]) / i[1]))) else: if abs(j[1]) > NEARLYZERO: raise AssertionError( 'One of the CHARMM %s energies (%s) for %s is zero, ' 'while the corresponding OpenMM energy is non-zero' % (system_name, i[0], ffxml)) rel_energies.append((i[0], 0)) dihedrals_done = False for i in rel_energies: if i[0] != 'PeriodicTorsionForce': if i[1] > tolerance: raise AssertionError( '%s energies (%s, %f) outside of allowed tolerance (%f) for %s' % (system_name, i[0], i[1], tolerance, ffxml)) else: if not dihedrals_done: if i[1] > tolerance: raise AssertionError( '%s energies (%s, %f) outside of allowed tolerance (%f) for %s' % (system_name, i[0], i[1], tolerance, ffxml)) dihedrals_done = True else: #impropers if i[1] > improper_tolerance: raise AssertionError( '%s energies (%s-impropers, %f) outside of allowed tolerance (%f) for %s' % (system_name, i[0], i[1], improper_tolerance, ffxml)) # logging if not no_log: charmm_energies_log = dict() omm_energies_log = dict() rel_energies_log = dict() charmm_energies_log['ffxml_name'] = ffxml charmm_energies_log['test_system'] = system_name charmm_energies_log['data_type'] = 'CHARMM' charmm_energies_log['units'] = units omm_energies_log['ffxml_name'] = ffxml omm_energies_log['test_system'] = system_name omm_energies_log['data_type'] = 'OpenMM' omm_energies_log['units'] = units rel_energies_log['ffxml_name'] = ffxml rel_energies_log['test_system'] = system_name rel_energies_log['data_type'] = 'abs((CHARMM-OpenMM)/CHARMM)' dihedrals_done = False for item in amber_energies: if item[0] == 'PeriodicTorsionForce' and not dihedrals_done: charmm_energies_log['PeriodicTorsionForce_dihedrals'] = item[1] dihedrals_done = True elif item[0] == 'PeriodicTorsionForce' and dihedrals_done: charmm_energies_log['PeriodicTorsionForce_impropers'] = item[1] elif item[0] == 'CMMotionRemover': continue else: charmm_energies_log[item[0]] = item[1] dihedrals_done = False for item in omm_energies: if item[0] == 'PeriodicTorsionForce' and not dihedrals_done: omm_energies_log['PeriodicTorsionForce_dihedrals'] = item[1] dihedrals_done = True elif item[0] == 'PeriodicTorsionForce' and dihedrals_done: omm_energies_log['PeriodicTorsionForce_impropers'] = item[1] elif item[0] == 'CMMotionRemover': continue else: omm_energies_log[item[0]] = item[1] dihedrals_done = False for item in rel_energies: if item[0] == 'PeriodicTorsionForce' and not dihedrals_done: rel_energies_log['PeriodicTorsionForce_dihedrals'] = item[1] dihedrals_done = True elif item[0] == 'PeriodicTorsionForce' and dihedrals_done: rel_energies_log['PeriodicTorsionForce_impropers'] = item[1] elif item[0] == 'CMMotionRemover': continue else: rel_energies_log[item[0]] = item[1] logger.log(charmm_energies_log) logger.log(omm_energies_log) logger.log(rel_energies_log)
class ToyModel(object): """ Toy model of 4 carbons to test torsionfit Attributes ---------- synthetic_energy: simtk.units.Quantity((n_increments), unit=kilojoule/mole) true_value: parmed.topologyobjects.DihedralTypeList (the dihedral parameters used to calculate synthetic energy) initital_value: parmed.topologyobjects.DihedralTypeList (dihedral parameters used to initialize pymc model) torsion_scan: torsionfit.TorsionScanSet model: torsionfit.TorsionFitModel The default toy model has randomized true and initial dihedral parameters. This can be changed by passing a parmed.topologyobjects.DihedralTypeList to true_value and/or initial_value when initializing a model instance. """ def __init__(self, true_value=None, initial_value=None, n_increments=18, rj=True, sample_phase=False, continuous=False): self._param = CharmmParameterSet(get_fun('toy.str')) self._struct = CharmmPsfFile(get_fun('toy.psf')) self._pdb = app.PDBFile(get_fun('toy.pdb')) self._topology = md.load_psf(get_fun('toy.psf')) self.synthetic_energy = units.Quantity() self._positions = units.Quantity() self._platform = mm.Platform.getPlatformByName('Reference') # Replace ('CG331', 'CG321', 'CG321', 'CG331') torsion with true_value self._dih_type = ('CG331', 'CG321', 'CG321', 'CG331') original_torsion = self._param.dihedral_types[self._dih_type] if true_value is not None: if type(true_value) == DihedralTypeList: dih_tlist = true_value elif type(true_value) == DihedralType: dih_tlist = DihedralTypeList() dih_tlist.append(true_value) else: dih_tlist = self._randomize_dih_param(return_dih=True) self.true_value = copy.deepcopy(dih_tlist) self._param.dihedral_types[self._dih_type] = dih_tlist # parametrize toy self._struct.load_parameters(self._param, copy_parameters=False) self._struct.positions = self._pdb.positions # generate synthetic torsion scan self._torsion_scan(n_increments=n_increments) # initialize parameter if initial_value is not None: if type(initial_value) == DihedralTypeList: dih_tlist = initial_value if type(initial_value) == DihedralType: dih_tlist = DihedralTypeList() dih_tlist.append(initial_value) elif initial_value == 'cgenff': dih_tlist = original_torsion else: dih_tlist = self._randomize_dih_param(return_dih=True) self.initial_value = copy.deepcopy(dih_tlist) self._param.dihedral_types[self._dih_type] = dih_tlist # create torsionfit.TorsionScanSet torsions = np.zeros((len(self._positions), 4)) torsions[:] = [1, 2, 3, 4] direction = None steps = None self.scan_set = ScanSet.QMDataBase(positions=self._positions.value_in_unit(units.nanometers), topology=self._topology, structure=self._struct, torsions=torsions, steps=steps, directions=direction, qm_energies=self.synthetic_energy.value_in_unit(units.kilojoules_per_mole)) self.model = model.TorsionFitModel(param=self._param, frags=self.scan_set, platform=self._platform, param_to_opt=[self._dih_type], rj=rj, continuous_phase=continuous, sample_phase=sample_phase) def _spher2cart(self, r, theta, phi): """convert spherical to cartesian coordinates Paramters: r: bond length theta: bond angle phi: dihedral angle returns: cartesian coordinates """ x = r * np.sin(theta) * np.cos(phi) y = r * np.sin(theta) * np.sin(phi) z = r * np.cos(theta) return [x, y, z] def _torsion_scan(self, n_increments): """ Generate positions and energy for torsion scan Parameters: n_increments: int. how many points to scan """ n_increments = n_increments n_atoms = 4 phis = np.arange(-np.pi, +np.pi, np.pi/n_increments) positions = np.zeros((len(phis), n_atoms, 3)) # Get bond length in nm for bond in self._struct.bonds: if bond.atom1.type == bond.atom2.type: length = units.Quantity(value=bond.type.req * 0.1, unit=units.nanometers) # convert to nm # Get angle in radians for angle in self._struct.angles: if angle.atom1.type == angle.atom2.type: theta = units.Quantity(value=angle.type.theteq * (np.pi/180.0), unit=units.radians) atom1_coords = self._spher2cart(length.value_in_unit(units.nanometer), theta.value_in_unit(units.radian), phis[0]) for i, phi in enumerate(phis): atom3_coords = self._spher2cart(length.value_in_unit(units.nanometer), theta.value_in_unit(units.radian), phi) atom3_coords[-1] = abs(atom3_coords[-1]) + length._value positions[i] = [atom1_coords, [0.000, 0.000, 0.000], [0.000, 0.000, length._value], atom3_coords] self._positions = units.Quantity(value=positions, unit=units.nanometer) # put units back in # calculate energy self.synthetic_energy = units.Quantity(value=np.zeros((len(positions))), unit=units.kilojoules_per_mole) platform = mm.Platform.getPlatformByName('Reference') integrator = mm.VerletIntegrator(0.004*units.picoseconds) system = self._struct.createSystem() context = mm.Context(system, integrator, platform) for i, conf in enumerate(self._positions): context.setPositions(conf) state = context.getState(getEnergy=True) energy = state.getPotentialEnergy() self.synthetic_energy[i] = energy + units.Quantity(value=np.random.normal(0, 1.0), unit=units.kilojoules_per_mole) def _randomize_dih_param(self, return_dih=False): """ generates random dihedral parameters """ dih_tlist = DihedralTypeList() multiplicities = [1, 2, 3, 4, 6] terms = np.random.randint(1, 5+1) np.random.shuffle(multiplicities) for i in range(terms): k = np.random.uniform(0.0, 20.0) n = multiplicities[i] phase = np.random.randint(0, 1+1) if phase == 1: phase = 180 dih_tlist.append(DihedralType(k, n, phase, 1.00, 1.00)) self._param.dihedral_types[self._dih_type] = dih_tlist if return_dih: _dih_tlist = copy.deepcopy(dih_tlist) return _dih_tlist def save_torsion_values(self, filename=None): """ Parameters ---------- filename : str file to save torsion values. Default is None. If None, return np.array Returns ------- dih_param: np.array array containing torison parameters """ dih_param = np.ones(shape=(2, 6, 3)) * np.nan for i, dih in enumerate(self.true_value): dih_param[0, i, 0] = dih.per dih_param[0, i, 1] = dih.phi_k dih_param[0, i, 2] = dih.phase for j, dih in enumerate(self.initial_value): dih_param[1, j, 0] = dih.per dih_param[1, j, 1] = dih.phi_k dih_param[1, j, 2] = dih.phase if filename is not None: np.save(filename, dih_param) else: return dih_param @staticmethod def from_dih_params(filename=None, dih_params=None, rj=False, continuous=False, n_increments=13, sample_phase=False): """ Parameters ---------- filename : str name of file with serialized dihedral parameters rj : bool Flag if using reversible jump. Default it True continuous : bool Flag if sampling continuous phase. Default is False n_increments : int incermentation of torsion drive sample_phase : bool Flag if sampling phase. Default is False (K is allowed to go negative when sample_phase is False) Returns ------- ToyModel with true and initial value from saved file. """ if filename is None and dih_params is None: msg = 'You must provide either an npy file or a numpy array with true and initial values for the toy model' raise Exception(msg) if filename is not None: dih_params = np.load(filename) dih_tlist_true = DihedralTypeList() dih_tlist_init = DihedralTypeList() true = dih_params[0] init = dih_params[1] for dih in true: if not np.isnan(dih[0]): dih_tlist_true.append(DihedralType(per=dih[0], phi_k=dih[1], phase=dih[2])) for dih in init: if not np.isnan(dih[0]): dih_tlist_init.append(DihedralType(per=dih[0], phi_k=dih[1], phase=dih[2])) return ToyModel(true_value=dih_tlist_true, initial_value=dih_tlist_init, rj=rj, continuous=continuous, n_increments=n_increments, sample_phase=sample_phase)