def set_Structure(config): """ Load the input/reference files (.prmtop, .inpcrd) into a parmed.Structure. If a `restart` (.rst7) file is given, overwrite the reference positions, velocities, and box vectors on the Structure. Parameters ----------- filename: str, filepath to input (.prmtop) restart: str, file path to Amber restart file (.rst7) logger: logging.Logger object, records information Notes ----- Reference for parmed.load_Structure *args and **kwargs https://parmed.github.io/ParmEd/html/structobj/parmed.formats.registry.load_file.html#parmed.formats.registry.load_file """ if 'restart' in config['structure'].keys(): rst7 = config['structure']['restart'] config['Logger'].info('Restarting simulation from {}'.format(rst7)) restart = parmed.amber.Rst7(rst7) config['structure'].pop('restart') structure = parmed.load_file(**config['structure']) structure.positions = restart.positions structure.velocities = restart.velocities structure.box = restart.box else: structure = parmed.load_file(**config['structure']) config['Structure'] = structure return config
def testMoleculeCombine(self): """ Tests selective molecule combination in Gromacs topology files """ warnings.filterwarnings('ignore', category=GromacsWarning) parm = load_file(os.path.join(get_fn('12.DPPC'), 'topol3.top')) fname = get_fn('combined.top', written=True) # Make sure that combining non-adjacent molecules fails self.assertRaises(ValueError, lambda: parm.write(fname, combine=[[1, 3]])) self.assertRaises(ValueError, lambda: parm.write(fname, combine='joey')) self.assertRaises(ValueError, lambda: parm.write(fname, combine=[1, 2, 3])) self.assertRaises(TypeError, lambda: parm.write(fname, combine=1)) parm.write(fname, combine=[[3, 4], [126, 127, 128, 129, 130]]) with open(fname, 'r') as f: for line in f: if line.startswith('[ molecules ]'): break molecule_list = [] for line in f: if line[0] == ';': continue words = line.split() molecule_list.append((words[0], int(words[1]))) parm2 = load_file(fname) self.assertEqual(molecule_list, [('DPPC', 3), ('system1', 1), ('SOL', 121), ('system2', 1), ('SOL', 121)]) self.assertEqual(len(parm2.atoms), len(parm.atoms)) self.assertEqual(len(parm2.residues), len(parm2.residues)) for a1, a2 in zip(parm.atoms, parm2.atoms): self._equal_atoms(a1, a2) for r1, r2 in zip(parm.residues, parm2.residues): self.assertEqual(len(r1), len(r2)) for a1, a2 in zip(r1, r2): self._equal_atoms(a1, a2)
def test_geometric_combining_rule_energy(self): """ Tests converting geom. comb. rule energy from Gromacs to Amber """ top = load_file(os.path.join(get_fn('05.OPLS'), 'topol.top'), xyz=os.path.join(get_fn('05.OPLS'), 'conf.gro')) self.assertEqual(top.combining_rule, 'geometric') del top.rb_torsions[:] parm = load_file(get_saved_fn('opls.parm7'), xyz=os.path.join(get_fn('05.OPLS'), 'conf.gro')) self.assertEqual(parm.combining_rule, 'geometric') self.assertFalse(parm.has_NBFIX()) sysg = top.createSystem() sysa = parm.createSystem() cong = mm.Context(sysg, mm.VerletIntegrator(0.001), CPU) cona = mm.Context(sysa, mm.VerletIntegrator(0.001), CPU) cong.setPositions(top.positions) cona.setPositions(top.positions) self._check_energies(top, cong, parm, cona) # Make an NBFIX self.assertFalse(parm.has_NBFIX()) parm.parm_data['LENNARD_JONES_ACOEF'][-4] = 10.0 self.assertTrue(parm.has_NBFIX()) parm.createSystem()
def testCharmmParameterSetConversion(self): """ Tests CharmmParameterSet.from_parameterset and from_structure """ params1 = ParameterSet.from_structure( load_file(get_fn('benzene_cyclohexane_10_500.prmtop')) ) params2 = load_file(os.path.join(get_fn('03.AlaGlu'), 'topol.top')).parameterset chparams1 = parameters.CharmmParameterSet.from_parameterset(params1) chparams2 = parameters.CharmmParameterSet.from_parameterset(params2, copy=True) chparams3 = parameters.CharmmParameterSet.from_structure( load_file(get_fn('benzene_cyclohexane_10_500.prmtop')) ) self.assertIsInstance(chparams1, parameters.CharmmParameterSet) self.assertIsInstance(chparams2, parameters.CharmmParameterSet) self.assertIsInstance(chparams3, parameters.CharmmParameterSet) self._compare_paramsets(chparams1, params1, copy=False) self._compare_paramsets(chparams2, params2, copy=True) self._compare_paramsets(chparams1, chparams3, copy=True) self._check_uppercase_types(chparams1) self._check_uppercase_types(chparams2) self._check_uppercase_types(chparams3) # GAFF atom types, as in the first parameter set, are all lower-case. # Check that name decoration is the established pattern for name in chparams1.atom_types: self.assertTrue(name.endswith('LTU')) for name in chparams3.atom_types: self.assertTrue(name.endswith('LTU'))
def patch(self, top_string, crd_string): INTOP = 'in.top' INRST = 'in.rst' OUTTOP = 'out.top' OUTRST = 'out.rst' with util.in_temp_dir(): with open(INTOP, 'wt') as outfile: outfile.write(top_string) with open(INRST, 'wt') as outfile: outfile.write(crd_string) topol = pmd.load_file(INTOP) crd = pmd.load_file(INRST) topol.coordinates = crd.coordinates self._add_particles(topol) topol.write_parm(OUTTOP) topol.write_rst7(OUTRST) with open(OUTTOP, 'rt') as infile: top_string = infile.read() with open(OUTRST, 'rt') as infile: crd_string = infile.read() return top_string, crd_string
def test_xyz(self): """ Tests parsing Tinker XYZ files """ self.assertTrue(tinkerfiles.XyzFile.id_format(get_fn('nma.xyz'))) self.assertTrue(tinkerfiles.XyzFile.id_format(get_fn('2igd_924wat.xyz'))) xyz = tinkerfiles.XyzFile(get_fn('nma.xyz')) np.testing.assert_allclose(xyz.box, [30.735, 30.876, 28.485, 90.0, 90.0, 90.0]) self.assertEqual(len(xyz.atoms), 2466) self.assertEqual(xyz.atoms[0].name, 'C') self.assertEqual(xyz.atoms[0].type, '221') self.assertEqual(len(xyz.atoms[0].bond_partners), 4) self.assertEqual(xyz.atoms[-1].name, 'H') self.assertEqual(xyz.atoms[-1].atomic_number, 1) self.assertEqual(xyz.atoms[-1].type, '248') xyz = pmd.load_file(get_fn('2igd_924wat.xyz'), get_fn('2igd_924wat.pdb')) pdb = pmd.load_file(get_fn('2igd_924wat.pdb')) xyz2 = pmd.load_file(get_fn('2igd_924wat.xyz')) self.assertEqual(len(pdb.atoms), len(xyz.atoms)) self.assertEqual(len(pdb.residues), len(xyz.residues)) for r1, r2 in zip(pdb.residues, xyz.residues): self.assertEqual(len(r1), len(r2)) self.assertEqual(r1.chain, r2.chain) self.assertEqual(r1.name, r2.name) self.assertEqual(r1.insertion_code, r2.insertion_code) # Make sure NA ions are atomic number of sodium for atom in xyz.view['NA',:].atoms: self.assertEqual(atom.atomic_number, pmd.periodic_table.AtomicNum['Na']) # Now make sure that NA ions are atomic number of sodium even if we # don't load a PDB file for atom in xyz2.view[:,'Na+']: self.assertEqual(atom.atomic_number, pmd.periodic_table.AtomicNum['Na'])
def testSimple(self): """ Tests converting standard Gromacs system into Amber prmtop """ top = load_file(get_fn(os.path.join('03.AlaGlu', 'topol.top'))) self.assertEqual(top.combining_rule, 'lorentz') parm = amber.AmberParm.from_structure(top) parm.write_parm(get_fn('ala_glu.parm7', written=True)) parm = load_file(get_fn('ala_glu.parm7', written=True)) self.assertIsInstance(parm, amber.AmberParm) self.assertEqual(len(top.atoms), len(parm.atoms)) self.assertEqual(len(top.bonds), len(parm.bonds)) self.assertEqual(len(top.angles), len(parm.angles)) self.assertEqual(len(top.residues), len(parm.residues)) for a1, a2 in zip(top.atoms, parm.atoms): self.assertEqual(a1.name, a2.name) self.assertEqual(a1.type, a2.type) self.assertEqual(a1.atomic_number, a2.atomic_number) self.assertEqual(a1.residue.name, a2.residue.name) self.assertEqual(a1.residue.idx, a2.residue.idx) self.assertAlmostEqual(a1.mass, a2.mass) self.assertIs(type(a1), type(a2)) self.assertAlmostEqual(a1.charge, a2.charge) self.assertEqual(set([a.idx for a in a1.bond_partners]), set([a.idx for a in a2.bond_partners])) self.assertEqual(set([a.idx for a in a1.angle_partners]), set([a.idx for a in a2.angle_partners])) self.assertEqual(set([a.idx for a in a1.dihedral_partners]), set([a.idx for a in a2.dihedral_partners]))
def testDPPC(self): """ Tests non-standard Gromacs force fields and nonbonded exceptions """ # We know what we're doing warnings.filterwarnings('ignore', category=GromacsWarning) top = load_file(os.path.join(get_fn('12.DPPC'), 'topol.top')) gro = load_file(os.path.join(get_fn('12.DPPC'), 'conf.gro')) top.box = gro.box[:] # Create the system and context, then calculate the energy decomposition system = top.createSystem() with open('system.xml', 'w') as f: f.write(mm.XmlSerializer.serialize(system)) context = mm.Context(system, mm.VerletIntegrator(0.001), CPU) context.setPositions(gro.positions) energies = energy_decomposition(top, context, nrg=u.kilojoules_per_mole) # Compare with Lee-Ping's answers. self.assertAlmostEqual(energies['bond'], 0) self.assertAlmostEqual(energies['angle'], 1405.7354199, places=4) self.assertAlmostEqual(energies['dihedral'], 236.932663255, places=4) self.assertAlmostEqual(energies['improper'], 33.201541811, places=4) self.assertAlmostEqual(energies['rb_torsion'], 428.0550599, places=4) self.assertRelativeEqual(energies['nonbonded'], -16432.8092955, places=4) gmxfrc = get_forces_from_xvg(os.path.join(get_fn('12.DPPC'), 'force.xvg')) ommfrc = context.getState(getForces=True).getForces().value_in_unit( u.kilojoules_per_mole/u.nanometer) zero_ep_frc(ommfrc, top) max_diff = get_max_diff(gmxfrc, ommfrc) self.assertLess(max_diff, 5)
def testRoundTrip(self): """ Test ParmEd -> OpenMM round trip with Gromacs system """ # Use DPPC to get RB-torsions tested. Also check that it initially fails # with the CustomNonbondedForce warnings.filterwarnings('ignore', category=GromacsWarning) top = load_file(os.path.join(get_fn('12.DPPC'), 'topol.top')) gro = load_file(os.path.join(get_fn('12.DPPC'), 'conf.gro')) top.box = gro.box[:] system = top.createSystem() def bad_system(): return openmm.load_topology(top.topology, system).createSystem() warnings.filterwarnings('error', category=OpenMMWarning) self.assertRaises(OpenMMWarning, lambda: openmm.load_topology(top.topology, system) ) warnings.filterwarnings('ignore', category=OpenMMWarning) self.assertRaises(ParmedError, bad_system) for i in range(len(system.getForces())): if isinstance(system.getForce(i), mm.CustomNonbondedForce): system.removeForce(i) break system2 = openmm.load_topology(top.topology, system).createSystem() con1 = mm.Context(system, mm.VerletIntegrator(0.001), CPU) con2 = mm.Context(system2, mm.VerletIntegrator(0.001), CPU) con1.setPositions(gro.positions) con2.setPositions(gro.positions) ene1 = energy_decomposition(top, con1) ene2 = energy_decomposition(top, con2) self.assertAlmostEqual(ene1['bond'], ene2['bond']) self.assertAlmostEqual(ene1['angle'], ene2['angle']) self.assertAlmostEqual(ene1['dihedral'], ene2['dihedral']) self.assertAlmostEqual(ene1['improper'], ene2['improper']) self.assertAlmostEqual(ene1['rb_torsion'], ene2['rb_torsion']) self.assertAlmostEqual(ene1['nonbonded'], ene2['nonbonded'])
def testJACPMESwitch(self): """ Tests the DHFR Gromacs system nrg and force (PME w/ switch) """ # Load the top and gro files top = load_file(os.path.join(get_fn('10.DHFR-PME-Switch'), 'topol.top')) gro = load_file(os.path.join(get_fn('10.DHFR-PME-Switch'), 'conf.gro')) top.box = gro.box[:] # Create the system and context, then calculate the energy decomposition system = top.createSystem(nonbondedMethod=app.PME, constraints=app.HBonds, nonbondedCutoff=0.9*u.nanometers, ewaldErrorTolerance=1.0e-5) context = mm.Context(system, mm.VerletIntegrator(0.001), CPU) context.setPositions(gro.positions) energies = energy_decomposition(top, context, nrg=u.kilojoules_per_mole) # Compare with Lee-Ping's answers. Make sure we zero-out forces for # virtual sites, since OMM doesn't do this and Gromacs does. self.assertAlmostEqual(energies['bond'], 1628.54739, places=3) self.assertAlmostEqual(energies['angle'], 3604.58751, places=3) self.assertAlmostEqual(energies['dihedral'], 6490.00844, delta=0.002) self.assertRelativeEqual(energies['nonbonded'], 23616.457584, places=3) gmxfrc = get_forces_from_xvg( os.path.join(get_fn('10.DHFR-PME-Switch'), 'force.xvg')) ommfrc = context.getState(getForces=True).getForces().value_in_unit( u.kilojoules_per_mole/u.nanometer) zero_ep_frc(ommfrc, top) max_diff = get_max_diff(gmxfrc, ommfrc) self.assertLess(max_diff, 5)
def testJAC(self): """ Tests the JAC benchmark Gromacs system nrg and force (no PBC) """ # Load the top and gro files top = load_file(os.path.join(get_fn('07.DHFR-Liquid-NoPBC'), 'topol.top')) gro = load_file(os.path.join(get_fn('07.DHFR-Liquid-NoPBC'), 'conf.gro')) # Create the system and context, then calculate the energy decomposition system = top.createSystem() context = mm.Context(system, mm.VerletIntegrator(0.001), CPU) context.setPositions(gro.positions) energies = energy_decomposition(top, context, nrg=u.kilojoules_per_mole) # Compare with Lee-Ping's answers. Make sure we zero-out forces for # virtual sites, since OMM doesn't do this and Gromacs does. self.assertAlmostEqual(energies['bond'], 35.142565, places=3) self.assertAlmostEqual(energies['angle'], 3735.514669, places=3) self.assertAlmostEqual(energies['dihedral'], 7277.741635, delta=0.002) self.assertRelativeEqual(energies['nonbonded'], -288718.981405, places=4) gmxfrc = get_forces_from_xvg( os.path.join(get_fn('07.DHFR-Liquid-NoPBC'), 'force.xvg')) ommfrc = context.getState(getForces=True).getForces().value_in_unit( u.kilojoules_per_mole/u.nanometer) zero_ep_frc(ommfrc, top) max_diff = get_max_diff(gmxfrc, ommfrc) self.assertLess(max_diff, 0.5)
def test_ring_count(): # Two rings fused = pmd.load_file(get_fn('fused.mol2'), structure=True) top, _ = generate_topology(fused) rule = SMARTSGraph(name='test', parser=PARSER, smarts_string='[#6;R2]') match_indices = list(rule.find_matches(top)) for atom_idx in (3, 4): assert atom_idx in match_indices assert len(match_indices) == 2 rule = SMARTSGraph(name='test', parser=PARSER, smarts_string='[#6;R1]') match_indices = list(rule.find_matches(top)) for atom_idx in (0, 1, 2, 5, 6, 7, 8, 9): assert atom_idx in match_indices assert len(match_indices) == 8 # One ring ring = pmd.load_file(get_fn('ring.mol2'), structure=True) top, _ = generate_topology(ring) rule = SMARTSGraph(name='test', parser=PARSER, smarts_string='[#6;R1]') match_indices = list(rule.find_matches(top)) for atom_idx in range(6): assert atom_idx in match_indices assert len(match_indices) == 6
def testWriteCharmm27Top(self): """ Tests writing a Gromacs topology file with CHARMM 27 FF """ top = load_file(get_fn('1aki.charmm27.top')) self.assertEqual(top.combining_rule, 'lorentz') GromacsTopologyFile.write(top, get_fn('1aki.charmm27.top', written=True)) top2 = load_file(get_fn('1aki.charmm27.top', written=True)) self._charmm27_checks(top)
def test_ringness(): ring = pmd.load_file(get_fn('ring.mol2'), structure=True) top, _ = generate_topology(ring) _rule_match(top, '[#6]1[#6][#6][#6][#6][#6]1', True) not_ring = pmd.load_file(get_fn('not_ring.mol2'), structure=True) top, _ = generate_topology(not_ring) _rule_match(top, '[#6]1[#6][#6][#6][#6][#6]1', False)
def test_write_amber99SBILDN(self): """ Tests writing a Gromacs topology with multiple molecules """ top = load_file(get_fn('ildn.solv.top')) self.assertEqual(top.combining_rule, 'lorentz') fn = get_fn('ildn.solv.top', written=True) GromacsTopologyFile.write(top, fn, combine=None) top2 = load_file(fn) self._check_ff99sbildn(top2) self._check_equal_structures(top, top2)
def test_load_topology_error(self): """ Test error handling in load_topology """ parm = load_file(get_fn("ash.parm7"), get_fn("ash.rst7")) parm2 = load_file(get_fn("solv2.parm7"), get_fn("solv2.rst7")) self.assertRaises(TypeError, lambda: openmm.load_topology(parm.topology, system=get_fn("integrator.xml"))) self.assertRaises(TypeError, lambda: openmm.load_topology(parm2.topology, system=parm.createSystem())) system = parm.createSystem() system.addForce(mm.CustomTorsionForce("theta**2")) self.assertRaises(exceptions.OpenMMWarning, lambda: openmm.load_topology(parm.topology, system))
def testDuplicateSystemNames(self): """ Tests that Gromacs topologies never have duplicate moleculetypes """ parm = load_file(get_fn('phenol.prmtop')) parm = parm * 20 + load_file(get_fn('biphenyl.prmtop')) * 20 top = GromacsTopologyFile.from_structure(parm) self.assertEqual(top.combining_rule, 'lorentz') top.write(get_fn('phenol_biphenyl.top', written=True)) top2 = GromacsTopologyFile(get_fn('phenol_biphenyl.top', written=True)) self.assertEqual(len(top.residues), 40)
def testOPLS(self): """ Tests the geometric combining rules in Gromacs with OPLS/AA """ parm = load_file(os.path.join(get_fn('05.OPLS'), 'topol.top'), xyz=os.path.join(get_fn('05.OPLS'), 'conf.gro')) self.assertEqual(parm.combining_rule, 'geometric') self.assertEqual(parm.defaults.comb_rule, 3) parm.write(get_fn('test.topol', written=True), combine='all') parm2 = load_file(get_fn('test.topol', written=True)) self.assertEqual(len(parm.atoms), len(parm2.atoms))
def test_OPLS(self): """ Tests the geometric combining rules in Gromacs with OPLS/AA """ parm = load_file(os.path.join(get_fn('05.OPLS'), 'topol.top'), xyz=os.path.join(get_fn('05.OPLS'), 'conf.gro')) self.assertEqual(parm.combining_rule, 'geometric') self.assertEqual(parm.defaults.comb_rule, 3) parm.write(get_fn('test.topol', written=True), combine='all') parm2 = load_file(get_fn('test.topol', written=True)) self.assertEqual(len(parm.atoms), len(parm2.atoms)) # Check that the charge attribute is read correctly self.assertEqual(parm.parameterset.atom_types['opls_001'].charge, 0.5)
def test_write_gro_file_nobox(self): """ Test GROMACS GRO file writing without a box """ parm = load_file(get_fn('trx.prmtop'), get_fn('trx.inpcrd')) self.assertIs(parm.box, None) fn = get_fn('test.gro', written=True) parm.save(fn) # Make sure it has a box gro = load_file(fn) self.assertIsNot(gro.box, None) parm.save(fn, nobox=True, overwrite=True) gro = load_file(fn) self.assertIs(gro.box, None)
def testMoleculeOrdering(self): """ Tests non-contiguous atoms in Gromacs topology file writes """ warnings.filterwarnings('ignore', category=GromacsWarning) parm = load_file(os.path.join(get_fn('12.DPPC'), 'topol3.top')) parm.write(get_fn('topol3.top', written=True)) parm2 = load_file(get_fn('topol3.top', written=True)) self.assertEqual(len(parm.atoms), len(parm2.atoms)) self.assertEqual(len(parm.residues), len(parm2.residues)) for r1, r2 in zip(parm.residues, parm2.residues): self.assertEqual(r1.name, r2.name) for a1, a2 in zip(r1.atoms, r2.atoms): self.assertEqual(a1.name, a2.name) self.assertEqual(a1.type, a2.type)
def test_atomtyping(self, mol_name, testfiles_dir=OPLS_TESTFILES_DIR): files = glob.glob(os.path.join(testfiles_dir, mol_name, '*')) for mol_file in files: _, ext = os.path.splitext(mol_file) if ext == '.top': top_filename = '{}.top'.format(mol_name) gro_filename = '{}.gro'.format(mol_name) top_path = os.path.join(testfiles_dir, mol_name, top_filename) gro_path = os.path.join(testfiles_dir, mol_name, gro_filename) structure = pmd.load_file(top_path, xyz=gro_path, parametrize=False) elif ext == '.mol2': mol2_path = os.path.join(testfiles_dir, mol_name, mol_file) structure = pmd.load_file(mol2_path, structure=True) atomtype(structure, OPLSAA)
def test_atomtyping(self, mol_name, testfiles_dir=TRAPPE_TESTFILES_DIR): files = glob.glob(os.path.join(testfiles_dir, mol_name, '*')) for mol_file in files: _, ext = os.path.splitext(mol_file) mol2_path = os.path.join(testfiles_dir, mol_name, mol_file) structure = pmd.load_file(mol2_path, structure=True) atomtype(structure, TRAPPE_UA, non_atomistic=True)
def test_box_from_system(self): """ Tests loading box from System """ parm = load_file(get_fn('solv2.parm7'), get_fn('solv2.rst7')) system = parm.createSystem(nonbondedMethod=app.PME, nonbondedCutoff=8*u.angstroms) top = openmm.load_topology(parm.topology, system) np.testing.assert_allclose(parm.box, top.box)
def testWriteCrd(self): """ Test CHARMM coordinate writing capabilities """ struct = load_file(get_fn('4lzt.pdb')) charmmcrds.CharmmCrdFile.write(struct, get_fn('test.crd', written=True)) crd = charmmcrds.CharmmCrdFile(get_fn('test.crd', written=True)) np.testing.assert_allclose(struct.coordinates, crd.coordinates.reshape((len(struct.atoms), 3)))
def test_coordinates_meta(): from mdtraj.testing import get_fn fn, tn = [get_fn('frame0.pdb'),] * 2 trajs = [pt.load(fn, tn), md.load(fn, top=tn), pmd.load_file(tn, fn)] N_FRAMES = trajs[0].n_frames from MDAnalysis import Universe u = Universe(tn, fn) trajs.append(Universe(tn, fn)) views = [nv.show_pytraj(trajs[0]), nv.show_mdtraj(trajs[1]), nv.show_parmed(trajs[2])] views.append(nv.show_mdanalysis(trajs[3])) for index, (view, traj) in enumerate(zip(views, trajs)): view.frame = 3 nt.assert_equal(view.trajlist[0].n_frames, N_FRAMES) nt.assert_equal(len(view.trajlist[0].get_coordinates_dict().keys()), N_FRAMES) if index in [0, 1]: # pytraj, mdtraj if index == 0: aa_eq(view.coordinates[0], traj.xyz[3], decimal=4) else: aa_eq(view.coordinates[0],10*traj.xyz[3], decimal=4) view.coordinates = traj.xyz[2]
def main(pdblist, pdb_pattern, force=False): for code in pdblist: try: print("trying to make {} folder".format(code)) os.mkdir(code) except OSError: pass with temp_change_dir(code): print('processing {}'.format(code)) print(os.getcwd()) try: pdbfiles = glob(PDB_PATTERN.format(code=code)) assert len(pdbfiles) > 0, "can not find any pdb file" for pdbfile in pdbfiles: basename = os.path.basename(pdbfile) try: parm = pmd.load_file(pdbfile) ok = True except ValueError: print('ParmEd failed: {}'.format(pdbfile)) ok = False pdbfile_root = basename.replace('.pdb', '') fn = 'NoH_' + basename if os.path.exists(fn) and not force: print('skip {}'.format(fn)) else: if ok: new_parm = parm[[index for index, atom in enumerate( parm.atoms) if atom.atomic_number != 1]] new_parm.save(fn, overwrite=True) run_tleap(code, pdbfile_root, TLEAP_TEMPLATE) except TypeError: print('type error: ', code) except IndexError: print('index error: ', code)
def test_chamber_expanded_exclusions(self): """ Tests converting Gromacs to Chamber parm w/ modified exceptions """ # Now let's modify an exception parameter so that it needs type # expansion, and ensure that it is handled correctly top = load_file(get_fn('1aki.charmm27.solv.top'), xyz=get_fn('1aki.charmm27.solv.gro')) gsystem1 = top.createSystem(nonbondedCutoff=8*u.angstroms, nonbondedMethod=app.PME) gcon1 = mm.Context(gsystem1, mm.VerletIntegrator(1*u.femtosecond), Reference) gcon1.setPositions(top.positions) top.adjust_types.append(to.NonbondedExceptionType(0, 0, 1)) top.adjust_types.claim() top.adjusts[10].type = top.adjust_types[-1] gsystem2 = top.createSystem(nonbondedCutoff=8*u.angstroms, nonbondedMethod=app.PME) gcon2 = mm.Context(gsystem2, mm.VerletIntegrator(1*u.femtosecond), Reference) gcon2.setPositions(top.positions) e1 = gcon1.getState(getEnergy=True).getPotentialEnergy() e1 = e1.value_in_unit(u.kilocalories_per_mole) e2 = gcon2.getState(getEnergy=True).getPotentialEnergy() e2 = e2.value_in_unit(u.kilocalories_per_mole) self.assertGreater(abs(e2 - e1), 1e-2) # Convert to chamber now parm = amber.ChamberParm.from_structure(top) asystem = parm.createSystem(nonbondedCutoff=8*u.angstroms, nonbondedMethod=app.PME) acon = mm.Context(asystem, mm.VerletIntegrator(1*u.femtosecond), Reference) acon.setPositions(top.positions) e3 = acon.getState(getEnergy=True).getPotentialEnergy() e3 = e3.value_in_unit(u.kilocalories_per_mole) self.assertLess(abs(e2 - e3), 1e-2)
def test_simple(self): """ Test OpenMM System/Topology -> Amber prmtop conversion """ parm = load_file(get_fn('ash.parm7'), get_fn('ash.rst7')) self.assertEqual(parm.combining_rule, 'lorentz') system = parm.createSystem() amber.AmberParm.from_structure( openmm.load_topology(parm.topology, system) ).write_parm(get_fn('ash_from_omm.parm7', written=True)) parm2 = load_file(get_fn('ash_from_omm.parm7', written=True)) system2 = parm2.createSystem() con1 = mm.Context(system, mm.VerletIntegrator(0.001), CPU) con2 = mm.Context(system, mm.VerletIntegrator(0.001), CPU) con1.setPositions(parm.positions) con2.setPositions(parm.positions) self._check_energies(parm, con1, parm2, con2)
def check_energy_components_vs_prmtop(prmtop=None, inpcrd=None, system=None, MAX_ALLOWED_DEVIATION=5.0): """ """ import parmed as pmd structure = pmd.load_file(prmtop, inpcrd) prmtop_components = dict( pmd.openmm.energy_decomposition_system(structure, structure.createSystem(nonbondedMethod=NoCutoff)) ) system_components = dict(pmd.openmm.energy_decomposition_system(structure, system)) msg = "\n" msg += "Energy components:\n" test_pass = True msg += "%20s %12s %12s : %12s\n" % ("component", "prmtop (kcal/mol)", "system (kcal/mol)", "deviation") for key in prmtop_components: e1 = prmtop_components[key] e2 = system_components[key] deviation = abs(e1 - e2) if deviation > MAX_ALLOWED_DEVIATION: test_pass = False msg += "%20s %20.6f %20.6f : %20.6f\n" % (key, e1, e2, deviation) if not test_pass: msg += "Maximum allowed deviation (%f) exceeded.\n" % MAX_ALLOWED_DEVIATION # raise Exception(msg) # TODO: Re-enable when we have force tag merging sorted out in simtk.openmm.app.ForceField print(msg) # DEBUG
def test_save_resnames_single(self, c3, n4): system = mb.Compound([c3, n4]) system.save('resnames_single.gro', residues=['C3', 'N4']) struct = pmd.load_file('resnames_single.gro') assert struct.residues[0].number == 1 assert struct.residues[1].number == 2
# Script that converts an Amber formatted topology file and a coordinate file to a Orca force field parameter file and a Orca-readable xyz file. # Written by Åsmund Røhr Kjendseth (2019) # # Input: # parm7 topology file named: prmtop # coordinate file named: inpcrd # These files are written by the AmberTools program tleap using the command: saveamberparm mol prmtop inpcrd # # More input/output options will be included. # # ============================================================================= import parmed as pmd # Read Amber topology and coordinate files parm = pmd.load_file('prmtop', xyz='inpcrd') # Non bonded LJ 1-4 scaling factor scnb = 2.0 # Colomb conversion factors (not used below) but may be the cause of slight differences in ee-terms. CC_AMBER = 332.0522173 CC_CHARMM = 332.054 CC_NAMD = 332.0636 # Write Amber formatted frcmod file (force constants). This file can be useful but is not used further below. pmd.tools.writeFrcmod(parm, 'amber.frcmod').execute()
from __future__ import division, print_function import sys # OpenMM Imports import openmm as mm import openmm.app as app # ParmEd Imports from parmed import load_file from parmed.openmm.reporters import NetCDFReporter from parmed import unit as u # Load the Gromacs files print('Loading Gromacs files...') top = load_file('dhfr_pme.top', xyz='dhfr_pme.gro') # Create the OpenMM system print('Creating OpenMM System') system = top.createSystem(nonbondedMethod=app.PME, nonbondedCutoff=8.0*u.angstroms, constraints=app.HBonds, ) # Create the integrator to do Langevin dynamics integrator = mm.LangevinIntegrator( 300*u.kelvin, # Temperature of heat bath 1.0/u.picoseconds, # Friction coefficient 2.0*u.femtoseconds, # Time step )
if not (len(sys.argv) == 5 or len(sys.argv) == 6): print "Usage: strip_random.py [-O] <prmtop> <inpcrd> <first_strippable_water> <remaining_wats>" exit(1) overwrite = False arg_shift = 0 if sys.argv[1] == "-O": overwrite = True arg_shift = 1 prmtop = sys.argv[1 + arg_shift] inpcrd = sys.argv[2 + arg_shift] first_wat = int(sys.argv[3 + arg_shift]) remaining_wats = int(sys.argv[4 + arg_shift]) p = pmd.load_file(prmtop) p.load_rst7(inpcrd) wats = p[":WAT"] prmtop_watcnt = len(wats.residues) if prmtop_watcnt <= remaining_wats: print "Warning: Number of Remaining wates lower than present waters." exit(1) wat_list = list() no_strip = list() # Get water resids for wat_idx in range(prmtop_watcnt):
def test_preserve_resname(self, oplsaa): untyped_ethane = pmd.load_file(get_fn("ethane.mol2"), structure=True) untyped_resname = untyped_ethane.residues[0].name typed_ethane = oplsaa.apply(untyped_ethane) typed_resname = typed_ethane.residues[0].name assert typed_resname == untyped_resname
def test_jacs_complexes(self): """Use template generator to parameterize the Schrodinger JACS set of complexes""" # TODO: Uncomment working systems when we have cleaned up the input files jacs_systems = { #'bace' : { 'prefix' : 'Bace' }, #'cdk2' : { 'prefix' : 'CDK2' }, #'jnk1' : { 'prefix' : 'Jnk1' }, 'mcl1' : { 'prefix' : 'MCL1' }, #'p38' : { 'prefix' : 'p38' }, #'ptp1b' : { 'prefix' : 'PTP1B' }, #'thrombin' : { 'prefix' : 'Thrombin' }, #'tyk2' : { 'prefix' : 'Tyk2' }, } for system_name in jacs_systems: prefix = jacs_systems[system_name]['prefix'] # Read molecules ligand_sdf_filename = get_data_filename(os.path.join('perses_jacs_systems', system_name, prefix + '_ligands.sdf')) print(f'Reading molecules from {ligand_sdf_filename} ...') from openforcefield.topology import Molecule molecules = Molecule.from_file(ligand_sdf_filename, allow_undefined_stereo=True) try: nmolecules = len(molecules) except TypeError: molecules = [molecules] print(f'Read {len(molecules)} molecules from {ligand_sdf_filename}') # Read ParmEd Structures import parmed from simtk import unit protein_pdb_filename = get_data_filename(os.path.join('perses_jacs_systems', system_name, prefix + '_protein.pdb')) from simtk.openmm.app import PDBFile print(f'Reading protein from {protein_pdb_filename} ...') #protein_structure = parmed.load_file(protein_pdb_filename) # NOTE: This mis-interprets distorted geometry and sequentially-numbered residues that span chain breaks pdbfile = PDBFile(protein_pdb_filename) protein_structure = parmed.openmm.load_topology(pdbfile.topology, xyz=pdbfile.positions.value_in_unit(unit.angstroms)) ligand_structures = parmed.load_file(ligand_sdf_filename) try: nmolecules = len(ligand_structures) except TypeError: ligand_structures = [ligand_structures] assert len(ligand_structures) == len(molecules) # Filter molecules MAX_MOLECULES = 6 if not CI else 3 molecules = molecules[:MAX_MOLECULES] ligand_structures = ligand_structures[:MAX_MOLECULES] print(f'{len(molecules)} molecules remain after filtering') # Create complexes complex_structures = [ (protein_structure + ligand_structure) for ligand_structure in ligand_structures ] # Create template generator with local cache cache = os.path.join(get_data_filename(os.path.join('perses_jacs_systems', system_name)), 'cache.json') generator = self.TEMPLATE_GENERATOR(molecules=molecules, cache=cache) # Create a ForceField from simtk.openmm.app import ForceField forcefield = ForceField(*self.amber_forcefields) # Register the template generator forcefield.registerTemplateGenerator(generator.generator) # Parameterize all complexes print(f'Caching all molecules for {system_name} at {cache} ...') for ligand_index, complex_structure in enumerate(complex_structures): openmm_topology = complex_structure.topology molecule = molecules[ligand_index] # Delete hydrogens from terminal protein residues # TODO: Fix the input files so we don't need to do this from simtk.openmm import app modeller = app.Modeller(complex_structure.topology, complex_structure.positions) residues = [residue for residue in modeller.topology.residues() if residue.name != 'UNL'] termini_ids = [residues[0].id, residues[-1].id] #hs = [atom for atom in modeller.topology.atoms() if atom.element.symbol in ['H'] and atom.residue.name != 'UNL'] hs = [atom for atom in modeller.topology.atoms() if atom.element.symbol in ['H'] and atom.residue.id in termini_ids] modeller.delete(hs) from simtk.openmm.app import PDBFile modeller.addHydrogens(forcefield) # Parameterize protein:ligand complex in vacuum print(f' Parameterizing {system_name} : {molecule.to_smiles()} in vacuum...') from simtk.openmm.app import NoCutoff forcefield.createSystem(modeller.topology, nonbondedMethod=NoCutoff) # Parameterize protein:ligand complex in solvent print(f' Parameterizing {system_name} : {molecule.to_smiles()} in explicit solvent...') from simtk.openmm.app import PME modeller.addSolvent(forcefield, padding=0*unit.angstroms, ionicStrength=300*unit.millimolar) forcefield.createSystem(modeller.topology, nonbondedMethod=PME)
def __init__(self, options): super(OpenMM, self).__init__(options) # get simulation from options if it exists #self.options['job_data']['simulation'] = self.options['job_data'].get('simulation',None) self.simulation = self.options['job_data'].get('simulation', None) if self.lot_inp_file is not None and self.simulation is None: # Now go through the logic of determining which FILE options are activated. self.file_options.set_active('use_crystal', False, bool, "Use crystal unit parameters") self.file_options.set_active( 'use_pme', False, bool, '', "Use particle mesh ewald-- requires periodic boundary conditions" ) self.file_options.set_active('cutoff', 1.0, float, '', depend=(self.file_options.use_pme), msg="Requires PME") self.file_options.set_active('prmtopfile', None, str, "parameter file") self.file_options.set_active('inpcrdfile', None, str, "inpcrd file") self.file_options.set_active('restrain_torfile', None, str, "list of torsions to restrain") self.file_options.set_active('restrain_tranfile', None, str, "list of translations to restrain") for line in self.file_options.record(): print(line) # set all active values to self for easy access for key in self.file_options.ActiveOptions: setattr(self, key, self.file_options.ActiveOptions[key]) nifty.printcool(" Options for OpenMM") for val in [self.prmtopfile, self.inpcrdfile]: assert val != None, "Missing prmtop or inpcrdfile" # Integrator will never be used (Simulation requires one) integrator = openmm.VerletIntegrator(1.0) # create simulation object if self.use_crystal: crystal = load_file(self.prmtopfile, self.inpcrdfile) if self.use_pme: system = crystal.createSystem( nonbondedMethod=openmm_app.PME, nonbondedCutoff=self.cutoff * openmm_units.nanometer, ) else: system = crystal.createSystem( nonbondedMethod=openmm_app.NoCutoff, ) # Torsion restraint if self.restrain_torfile is not None: nifty.printcool(" Adding torsional restraints!") # Harmonic constraint tforce = openmm.CustomTorsionForce( "0.5*k*min(dtheta, 2*pi-dtheta)^2; dtheta = abs(theta-theta0); pi = 3.1415926535" ) tforce.addPerTorsionParameter("k") tforce.addPerTorsionParameter("theta0") system.addForce(tforce) xyz = manage_xyz.xyz_to_np(self.geom) with open(self.restrain_torfile, 'r') as input_file: for line in input_file: columns = line.split() a = int(columns[0]) b = int(columns[1]) c = int(columns[2]) d = int(columns[3]) k = float(columns[4]) dih = Dihedral(a, b, c, d) theta0 = dih.value(xyz) tforce.addTorsion(a, b, c, d, [k, theta0]) # Translation restraint if self.restrain_tranfile is not None: nifty.printcool(" Adding translational restraints!") trforce = openmm.CustomExternalForce( "k*periodicdistance(x, y, z, x0, y0, z0)^2") trforce.addPerParticleParameter("k") trforce.addPerParticleParameter("x0") trforce.addPerParticleParameter("y0") trforce.addPerParticleParameter("z0") system.addForce(trforce) xyz = manage_xyz.xyz_to_np(self.geom) with open(self.restrain_tranfile, 'r') as input_file: for line in input_file: columns = line.split() a = int(columns[0]) k = float(columns[1]) x0 = xyz[a, 0] * 0.1 # Units are in nm y0 = xyz[a, 1] * 0.1 # Units are in nm z0 = xyz[a, 2] * 0.1 # Units are in nm trforce.addParticle(a, [k, x0, y0, z0]) self.simulation = openmm_app.Simulation( crystal.topology, system, integrator) # set the box vectors inpcrd = openmm_app.AmberInpcrdFile(self.inpcrdfile) if inpcrd.boxVectors is not None: print(" setting box vectors") print(inpcrd.boxVectors) self.simulation.context.setPeriodicBoxVectors( *inpcrd.boxVectors) else: # Do not use crystal parameters prmtop = openmm_app.AmberPrmtopFile(self.prmtopfile) if self.use_pme: system = prmtop.createSystem( nonbondedMethod=openmm_app.PME, nonbondedCutoff=self.cutoff * openmm_units.nanometer, ) else: system = prmtop.createSystem( nonbondedMethod=openmm_app.NoCutoff, ) self.simulation = openmm_app.Simulation( prmtop.topology, system, integrator, )
import sys import numpy as np from scipy.spatial import distance import parmed as pmd if len(sys.argv) != 4: print "Usage: filter_clashes.py <target.pdb> <query-filter-structure.pdb> <cutoff-distance> " exit(1) mol_ref_path = sys.argv[1] mol_cut_path = sys.argv[2] cutoff = sys.argv[3] cutoff = float(cutoff)**2 mol_ref = pmd.load_file(mol_ref_path)["!@H="] mol_cut = pmd.load_file(mol_cut_path)["!@H="] dists_ref = distance.cdist(mol_ref.coordinates, mol_cut.coordinates, metric="sqeuclidean") dists_self = distance.cdist(mol_cut.coordinates, mol_cut.coordinates, metric="sqeuclidean") filter_ref = np.where(dists_ref < cutoff) filter_self = np.where(dists_self < cutoff) filter_list = list() filter_block = list()
#resnames = ['HIP', 'HIE', 'HID', 'GLY'] #resnames = [] for resa, resb in itertools.permutations(resnames, 2): if (folder, (resa, resb)) in malformed_tripeptides: print( f'Skipping {folder}/{resa}_{resb} because it is known to be mis-formatted' ) continue resname = f'{resa}_{resb}' prefix = os.path.join(folder, resname, resname) print() print() print(prefix) # Prepare AMBER system amber_struct = ParmEd.load_file(prefix + '.prmtop', prefix + '.inpcrd') #print(pmd_struct.atoms) amber_system = amber_struct.createSystem( nonbondedMethod=NoCutoff, #nonbondedCutoff=9.0*unit.angstrom, #constraints=HBonds, removeCMMotion=False) with open('amb_sys.xml', 'w') as of: of.write(XmlSerializer.serialize(amber_system)) amber_top = amber_struct.topology amber_energy = calc_energy(amber_system, amber_top, amber_struct.positions) print(amber_energy) # prepare openff system
def test_missing_type_definitions(self): with pytest.raises(FoyerError): FF = Forcefield() ethane = pmd.load_file(get_fn("ethane.mol2"), structure=True) FF.apply(ethane)
def test_write_xml(self, filename, oplsaa): mol = pmd.load_file(get_fn(filename), structure=True) typed = oplsaa.apply(mol) typed.write_foyer( filename="opls-snippet.xml", name="oplsaa-snippet", version="0.1.0", forcefield=oplsaa, unique=True, ) oplsaa_partial = Forcefield("opls-snippet.xml") assert oplsaa_partial.name == "oplsaa-snippet" assert oplsaa_partial.version == "0.1.0" assert oplsaa_partial.combining_rule == "geometric" typed_by_partial = oplsaa_partial.apply(mol) for adj in typed.adjusts: type1 = adj.atom1.atom_type type2 = adj.atom1.atom_type sigma_factor_pre = adj.type.sigma / ( (type1.sigma + type2.sigma) / 2) epsilon_factor_pre = adj.type.epsilon / ( (type1.epsilon * type2.epsilon)**0.5) for adj in typed_by_partial.adjusts: type1 = adj.atom1.atom_type type2 = adj.atom1.atom_type sigma_factor_post = adj.type.sigma / ( (type1.sigma + type2.sigma) / 2) epsilon_factor_post = adj.type.epsilon / ( (type1.epsilon * type2.epsilon)**0.5) assert sigma_factor_pre == sigma_factor_post assert epsilon_factor_pre == epsilon_factor_post # Do it again but with an XML including periodic dihedrals mol = pmd.load_file(get_fn(filename), structure=True) oplsaa = Forcefield(get_fn("oplsaa-periodic.xml")) typed = oplsaa.apply(mol) typed.write_foyer(filename="opls-snippet.xml", forcefield=oplsaa, unique=True) oplsaa_partial = Forcefield("opls-snippet.xml") typed_by_partial = oplsaa_partial.apply(mol) for adj in typed.adjusts: type1 = adj.atom1.atom_type type2 = adj.atom1.atom_type sigma_factor_pre = adj.type.sigma / ( (type1.sigma + type2.sigma) / 2) epsilon_factor_pre = adj.type.epsilon / ( (type1.epsilon * type2.epsilon)**0.5) for adj in typed_by_partial.adjusts: type1 = adj.atom1.atom_type type2 = adj.atom1.atom_type sigma_factor_post = adj.type.sigma / ( (type1.sigma + type2.sigma) / 2) epsilon_factor_post = adj.type.epsilon / ( (type1.epsilon * type2.epsilon)**0.5) assert sigma_factor_pre == sigma_factor_post assert epsilon_factor_pre == epsilon_factor_post
def test_pmd_loop(self, top, request): write_top(request.getfixturevalue(top), 'system.top') pmd.load_file('system.top')
def get_indices(itp_name): itp = parmed.load_file(itp_name) #indices bond_indices = [] angle_indices = [] dihedral_indices = [] improper_indices = [] #equivalent parameters bond_symmetries = [] angle_symmetries = [] dihedral_symmetries = [] #other stuff dihedral_multiplicities = [] dihedral_types = [] for bond in itp.bonds: if bond.funct == 1: bond_indices.append([bond.atom1.idx, bond.atom2.idx]) else: raise NotImplementedError('Unknown bond function type') for angle in itp.angles: if angle.funct == 1: angle_indices.append( [angle.atom1.idx, angle.atom2.idx, angle.atom3.idx]) else: raise NotImplementedError('Unknown angle function type') for dihedral in itp.dihedrals: dihedral_types.append(dihedral.funct) if dihedral.funct == 1: dihedral_indices.append([ dihedral.atom1.idx, dihedral.atom2.idx, dihedral.atom3.idx, dihedral.atom4.idx ]) dihedral_multiplicities.append(dihedral.type.per) elif dihedral.funct == 4: improper_indices.append([ dihedral.atom1.idx, dihedral.atom2.idx, dihedral.atom3.idx, dihedral.atom4.idx ]) else: raise NotImplementedError('Unknown dihedral function type') for bond_type in itp.bond_types: symmetry = [] for index, bond in enumerate(itp.bonds): if bond.type == bond_type: symmetry.append(index) bond_symmetries.append(symmetry) for angle_type in itp.angle_types: symmetry = [] for index, angle in enumerate(itp.angles): if angle.type == angle_type: symmetry.append(index) angle_symmetries.append(symmetry) for dihedral_type in itp.dihedral_types: symmetry = [] for index, dihedral in enumerate(itp.dihedrals): if dihedral.type == dihedral_type: symmetry.append(index) dihedral_symmetries.append(symmetry) return bond_indices, angle_indices, dihedral_indices, improper_indices, dihedral_multiplicities, bond_symmetries, angle_symmetries, dihedral_symmetries, dihedral_types
Make an rtp file based on model0_1 and qout """ import argparse import parmed if __name__ == '__main__': argparser = argparse.ArgumentParser(description="Script to parse optimal charges") argparser.add_argument('-f','--file',help="the PDB file") argparser.add_argument('-q','--qout',help="the output charges",default="qout") argparser.add_argument('-o','--out',help="the output RTP file") argparser.add_argument('-t','--types',help="a file with atom types") args = argparser.parse_args() struct = parmed.load_file(args.file) qline = "" with open(args.qout, "r") as f : line = f.readline() while line : qline += line.strip() + " " line = f.readline() charges = map(float,qline.strip().split()) for atom, charge in zip(struct.atoms, charges) : print "%4s%10.6f"%(atom.name, charge) if args.out is not None : atype = {} with open(args.types, "r") as f :
def create_data(pdb_path): """ Parse the protein data bank (PDB) file to generate input modelData @param pdb_path Name of the biomolecular structure file in PDB format """ # Create local copy of temp file copy2(pdb_path, './tmp.pdb') # Use parmed to read the bond information from temp file top = pmd.load_file('tmp.pdb') # Remove the created temp file os.remove('tmp.pdb') # Read PDB file to create atom/bond information with open(pdb_path, 'r') as infile: # store only non-empty lines lines = [l.strip() for l in infile if l.strip()] # Initialize all variables var_nchains = [] serial = [] atm_name = [] res_name = [] chain = [] res_id = [] positions = [] occupancy = [] temp_factor = [] atom_type = [] ct = 0 datb = {'atoms': [], 'bonds': []} # Variables that store the character positions of different # parameters from the molecule PDB file serialpos = [6, 11] atm_namepos = [12, 16] r_namepos = [17, 20] chainpos = [21, 22] r_idpos = [22, 26] xpos = [30, 38] ypos = [38, 46] zpos = [46, 54] occupos = [54, 60] bfacpos = [60, 66] atm_typepos = [77, 79] for l in lines: line = l.split() if "ATOM" in line[0] or "HETATM" in line[0]: serial.append(int(l[serialpos[0]:serialpos[1]])) atm_name.append(l[atm_namepos[0]:atm_namepos[1]].strip()) val_r_name = l[r_namepos[0]:r_namepos[1]].strip() res_name.append(val_r_name) chain_val = l[chainpos[0]:chainpos[1]].strip() chain.append(chain_val) if chain_val not in var_nchains: var_nchains.append(chain_val) val_r_id = int(l[r_idpos[0]:r_idpos[1]]) res_id.append(val_r_id) x = float(l[xpos[0]:xpos[1]]) y = float(l[ypos[0]:ypos[1]]) z = float(l[zpos[0]:zpos[1]]) positions.append([x, y, z]) occupancy.append(l[occupos[0]:occupos[1]].strip()) temp_factor.append(l[bfacpos[0]:bfacpos[1]].strip()) atom_type.append(l[atm_typepos[0]:atm_typepos[1]].strip()) ct += 1 # Create list of atoms tmp_res = res_id[0] resct = 1 for i in range(len(chain)): if tmp_res != res_id[i]: tmp_res = res_id[i] resct += 1 datb['atoms'].append({ "name": atm_name[i], "chain": chain[i], "positions": positions[i], "residue_index": resct, "element": atom_type[i], "residue_name": res_name[i] + str(res_id[i]), "serial": i, }) # Create list of bonds using the parmed module for i in range(len(top.bonds)): bondpair = top.bonds[i].__dict__ atom1 = re.findall(r"\[(\d+)\]", str(bondpair['atom1'])) atom2 = re.findall(r"\[(\d+)\]", str(bondpair['atom2'])) datb['bonds'].append({ 'atom2_index': int(atom1[0]), 'atom1_index': int(atom2[0]) }) return json.dumps(datb)
def ante_charges(molecule, charge_style, net_charge=0.0, multiplicity=1): """Calculates partial charges by calling antechamber Parameters ---------- molecule : parmed.Structure or mbuild.Compound Molecular structure to perform atomtyping on charge_style : str Style of partial charges calculation. Options include 'bcc', 'gas', and 'mul'. See antechamber documentation by running 'antechamber -L' for details. net_charge : float, optional, default=0.0 Net charge of the molecule multiplicity : int, optional, default=1 Spin multiplicity, 2S + 1 Returns ------- molecule : parmed.Structure The molecule with charges applied """ _check_antechamber(ANTECHAMBER) # Check valid atomtype name supported_chargetypes = ['bcc', 'gas', 'mul'] if charge_style not in supported_chargetypes: raise FoyerError( 'Unsupported charge style requested. ' 'Please select from {}'.format(supported_chargetypes)) # Check for parmed.Structure. Convert from mbuild.Compound if possible molecule = _check_structure(molecule) # Confirm single connected molecule _check_single_molecule(molecule) # Get current directory to write any error logs workdir = os.getcwd() # Work within a temporary directory # to clean up after antechamber with temporary_directory() as tmpdir: with temporary_cd(tmpdir): # Save the existing molecule to file _write_pdb(molecule,'ante_in.pdb') # Call antechamber command = ( 'antechamber -i ante_in.pdb -fi pdb ' '-o ante_out.mol2 -fo mol2 ' + '-c ' + charge_style + ' ' + '-nc ' + str(net_charge) + ' ' + '-m ' + str(multiplicity) + ' ' + '-s 2' ) proc = Popen(command, stdout=PIPE, stderr=PIPE, universal_newlines=True, shell=True) out, err = proc.communicate() # Error handling here if 'Fatal Error' in err or proc.returncode != 0: _antechamber_error(out,err,workdir) # Now read in the mol2 file with atomtyping charges = pmd.load_file('ante_out.mol2',structure=True) # Combine charge information with existing molecule structure assert len(molecule.atoms) == len(charges.atoms) for atom_idx in range(len(molecule.atoms)): assert molecule.atoms[atom_idx].element == \ charges.atoms[atom_idx].element molecule.atoms[atom_idx].charge = charges.atoms[atom_idx].charge return molecule
) attach_fractions = [float(i) / 100 for i in attach_string.split()] pull_string = ( "0.00 0.40 0.80 1.20 1.60 2.00 2.40 2.80 3.20 3.60 4.00 4.40 4.80 5.20 5.60 6.00 6.40 6.80 7.20 7.60 8.00 8.40 8.80 9.20 9.60 10.00 10.40 10.80 11.20 11.60 12.00 12.40 12.80 13.20 13.60 14.00 14.40 14.80 15.20 15.60 16.00 16.40 16.80 17.20 17.60 18.00" ) pull_distances = [float(i) + 6.00 for i in pull_string.split()] release_fractions = attach_fractions[::-1] windows = [len(attach_fractions), len(pull_distances), 0] structure = pmd.load_file( os.path.join("systems", system, "smirnoff", "smirnoff.prmtop"), os.path.join("systems", system, "smirnoff", "smirnoff.inpcrd"), structure=True, ) anchor_atoms = grep_anchor_atoms(system) static_restraints = setup_static_restraints( anchor_atoms, windows, structure, distance_fc=5.0, angle_fc=100.0 ) guest_restraints = setup_guest_restraints( anchor_atoms, windows, structure, attach_fractions, distance_fc=5.0, angle_fc=100.0,
import sys import parmed as pmd from paprika.io as import load_restraints from paprika.analysis import fe_calc from paprika.restraints.utils import extract_guest_restraints # Filenames etc. structure_file = 'vac-dum' guest_resname = 'AMT' restraint_file = 'restraints.json' # Extract restraints structure = pmd.load_file(f'{structure_file}.prmtop', f'{structure_file}.rst7') restraints = load_restraints(filepath=restraint_file) guest_restraints = extract_guest_restraints(structure, guest_resname, restraints) # Calculate ref state work FE = fe_calc() FE.compute_ref_state_work(guest_restraints, calc='ddm') print(f"Ref state work: {FE.results['ref_state_work']:.2f} kcal/mol")
def solvate(complex, params=None, box_length=8, shell=0, neutralise=True, ion_conc=0.154, centre=True, work_dir=None, filebase="complex"): """ Uses gmx solvate and gmx genion to solvate the system and (optionally) add NaCl ions. This function preserves the crystal water molecules. Parameters ---------- complex : BioSimSpace.System or parmed.structure.Structure The input unsolvated system. params : ProtoCaller.Parametrise.Params The input force field parameters. box_length : float, iterable Size of the box in nm. shell : float Places a layer of water of the specified thickness in nm around the solute. neutralise : bool Whether to add counterions to neutralise the system. ion_conc : float Ion concentration of NaCl in mol/L. centre : bool Whether to centre the system. work_dir : str Work directory. Default: current directory. filebase : str Output base name of the file. Returns ------- complex : BioSimSpace.System or parmed.structure.Structure The solvated system. """ if params is None: params = _parametrise.Params() if isinstance(complex, _pmd.Structure): centrefunc = _pmdwrap.centre chargefunc = lambda x: round(sum([atom.charge for atom in x.atoms])) readfunc = _pmdwrap.openFilesAsParmed elif _PC.BIOSIMSPACE and isinstance(complex, (_BSS._SireWrappers._molecule.Molecule, _BSS._SireWrappers._system.System)): if isinstance(complex, _BSS._SireWrappers._molecule.Molecule): complex = complex.toSystem() centrefunc = _BSSwrap.centre chargefunc = lambda x: round(x.charge().magnitude()) readfunc = _BSS.IO.readMolecules else: raise TypeError("Cannot solvate object of type %s" % type(complex)) if not isinstance(box_length, _Iterable): box_length = 3 * [box_length] if work_dir is None: work_dir = _os.path.basename(_tempfile.TemporaryDirectory().name) temp = True else: temp = False with _fileio.Dir(dirname=work_dir, temp=temp): # centre if centre: complex, box_length, _ = centrefunc(complex, box_length) # solvate with gmx solvate and load unparametrised waters into memory files = _PC.IO.GROMACS.saveAsGromacs(filebase, complex) # reloading the complex fixes some problems with ParmEd if isinstance(complex, _pmd.Structure): complex = _pmdwrap.openFilesAsParmed(files) new_gro = filebase + "_solvated.gro" command = "{0} solvate -shell {1} -box {2[0]} {2[1]} {2[2]} -cp \"{3}\" -o \"{4}\"".format( _PC.GROMACSEXE, shell, box_length, files[1], new_gro) if params.water_points == 4: command += " -cs tip4p.gro" _runexternal.runExternal(command, procname="gmx solvate") complex_solvated = _pmd.load_file(new_gro, skip_bonds=True) waters = complex_solvated[":SOL"] # prepare waters for tleap and parametrise for residue in waters.residues: residue.name = "WAT" for i, atom in enumerate(waters.atoms): if "H" in atom.name: atom.name = atom.name[0] + atom.name[2] elif "O" in atom.name: atom.name = "O" else: atom.name = "EPW" # here we only parametrise a single water molecule in order to gain performance waters_prep_filenames = [ filebase + "_waters.top", filebase + "_waters.gro" ] waters[":1"].save(filebase + "_single_wat.pdb") water = _parametrise.parametriseAndLoadPmd( params, filebase + "_single_wat.pdb", "water") _pmdwrap.saveFilesFromParmed(waters, [waters_prep_filenames[1]], combine="all") _pmdwrap.saveFilesFromParmed(water, [waters_prep_filenames[0]]) for line in _fileinput.input(waters_prep_filenames[0], inplace=True): line_new = line.split() if len(line_new) == 2 and line_new == ["WAT", "1"]: line = line.replace( "1", "{}".format(len(waters.positions) // params.water_points)) print(line, end="") waters_prep = _pmdwrap.openFilesAsParmed(waters_prep_filenames) waters_prep.box = _pmd.load_file(files[1], skip_bonds=True).box if any([neutralise, ion_conc, shell]): for residue in waters_prep.residues: residue.name = "SOL" _pmdwrap.saveFilesFromParmed(waters_prep, waters_prep_filenames) # add ions if any([neutralise, ion_conc, shell]): # write an MDP file _protocol.Protocol(use_preset="default").write("GROMACS", "ions") # neutralise if needed charge = chargefunc(complex) if neutralise else 0 volume = box_length[0] * box_length[1] * box_length[2] * 10**-24 n_Na, n_Cl = [ int(volume * 6.022 * 10**23 * ion_conc) - abs(charge) // 2 ] * 2 if neutralise: if charge < 0: n_Na -= charge else: n_Cl += charge # add ions with gmx genion ions_prep_filenames = [ filebase + "_ions.top", filebase + "_ions.gro" ] command = "{0} grompp -f ions.mdp -p {1} -c {2} -o \"{3}_solvated.tpr\"".format( _PC.GROMACSEXE, *waters_prep_filenames, filebase) _runexternal.runExternal(command, procname="gmx grompp") command = "{{ echo 2; }} | {0} genion -s \"{1}_solvated.tpr\" -o \"{2}\" -nn {3} -np {4}".format( _PC.GROMACSEXE, filebase, ions_prep_filenames[1], n_Cl, n_Na) _runexternal.runExternal(command, procname="gmx genion") # prepare waters for tleap ions = _pmd.load_file(ions_prep_filenames[1], skip_bonds=True) for residue in ions.residues: if residue.name == "SOL": residue.name = "WAT" # here we only parametrise single ions to gain performance ion = ions[":WAT"][":1"] + ions[":NA"][":1"] + ions[":CL"][":1"] max_len = len(ion.residues) ion.save(filebase + "_single_ion.pdb") ion = _parametrise.parametriseAndLoadPmd( params, filebase + "_single_ion.pdb", "water") _pmdwrap.saveFilesFromParmed(ions, [ions_prep_filenames[1]], combine="all") _pmdwrap.saveFilesFromParmed(ion, [ions_prep_filenames[0]]) mol_dict = {} for line in _fileinput.input(ions_prep_filenames[0], inplace=True): line_new = line.split() if len(line_new) == 2 and line_new[0] in [ "WAT", "NA", "CL" ] and line_new[1] == "1": n_mols = len(ions[":{}".format(line_new[0])].positions) if line_new[0] == "WAT": n_mols //= params.water_points line = line.replace("1", "{}".format(n_mols)) mol_dict[line_new[0]] = line # preserve the order of water, sodium and chloride if len(mol_dict) == max_len: for x in ["WAT", "NA", "CL"]: if x in mol_dict.keys(): print(mol_dict[x], end="") mol_dict = {} else: print(line, end="") return complex + readfunc(ions_prep_filenames) else: complex + readfunc(waters_prep_filenames)
def add_droplet( self, topology: md.Topology, coordinates: unit.quantity.Quantity, diameter: unit.quantity.Quantity = (30.0 * unit.angstrom), restrain_hydrogen_bonds: bool = True, restrain_hydrogen_angles: bool = False, top_file: str = "", ) -> md.Trajectory: """ Adding a droplet with a given diameter around a small molecule. Parameters ---------- topology: md.Topology topology of the molecule coordinates: np.array, unit'd diameter: float, unit'd top_file: str if top_file is provided the final droplet pdb is either kept and can be reused or if top_file already exists it will be used to create the same droplet. Returns ---------- A mdtraj.Trajectory object with the ligand centered in the solvent for inspection. """ assert type(diameter) == unit.Quantity assert type(topology) == md.Topology assert type(coordinates) == unit.Quantity if restrain_hydrogen_bonds: logger.debug("Hydrogen bonds are restraint.") if restrain_hydrogen_angles: logger.warning("HOH angles are restraint.") # get topology from mdtraj to PDBfixer via pdb file radius = diameter.value_in_unit(unit.angstrom) / 2 center = np.array([radius, radius, radius]) # if no solvated pdb file is provided generate one if top_file: # read in the file with the defined droplet pdb_filepath = top_file else: # generage a one time droplet pdb_filepath = f"tmp{random.randint(1,10000000)}.pdb" if not os.path.exists(pdb_filepath): logger.info(f"Generating droplet for {pdb_filepath}...") # mdtraj works with nanomter md.Trajectory(coordinates.value_in_unit(unit.nanometer), topology).save_pdb(pdb_filepath) pdb = PDBFixer(filename=pdb_filepath) os.remove(pdb_filepath) # put the ligand in the center l_in_nanometer = diameter.value_in_unit(unit.nanometer) pdb.positions = np.array( pdb.positions.value_in_unit( unit.nanometer)) + (l_in_nanometer / 2) # add water pdb.addSolvent(boxVectors=( Vec3(l_in_nanometer, 0.0, 0.0), Vec3(0.0, l_in_nanometer, 0.0), Vec3(0.0, 0.0, l_in_nanometer), )) # get topology from PDBFixer to mdtraj # NOTE: a second tmpfile - not happy about this from simtk.openmm.app import PDBFile PDBFile.writeFile(pdb.topology, pdb.positions, open(pdb_filepath, "w")) # load pdb in parmed logger.debug("Load with parmed ...") structure = pm.load_file(pdb_filepath) os.remove(pdb_filepath) # search for residues that are outside of the cutoff and delete them to_delete = [] logger.debug("Flag residues ...") for residue in structure.residues: for atom in residue: p1 = np.array([atom.xx, atom.xy, atom.xz]) p2 = center squared_dist = np.sum((p1 - p2)**2, axis=0) dist = np.sqrt(squared_dist) if ( dist > radius + 1 ): # NOTE: distance must be greater than radius + 1 Angstrom to_delete.append(residue) # only delete water molecules for residue in list(set(to_delete)): if residue.name == "HOH": logger.debug(f"Remove: {residue}") structure.residues.remove(residue) else: logger.warning( f"Residue {residue} reaches outside the droplet") print(f"Residue {residue} reaches outside the droplet") structure.write_pdb(pdb_filepath) # load pdb with mdtraj traj = md.load(pdb_filepath) if not top_file: os.remove(pdb_filepath) # set coordinates #NOTE: note the xyz[0] self._ligand_in_water_coordinates = traj.xyz[0] * unit.nanometer # generate atom string atom_list = [] for atom in traj.topology.atoms: atom_list.append(atom.element.symbol) # set atom string self.ligand_in_water_atoms = "".join(atom_list) # set mdtraj topology self.ligand_in_water_topology = traj.topology # set FlattBottomRestraintToCenter on each oxygen self.solvent_restraints = [] for residue in traj.topology.residues: if residue.is_water: for atom in residue.atoms: if str(atom.element.symbol) == "O": self.solvent_restraints.append( CenterFlatBottomRestraint( sigma=0.1 * unit.angstrom, point=center * unit.angstrom, radius=(diameter / 2), atom_idx=atom.index, active_at=-1, )) logger.debug("Adding restraint to center to {}".format( atom.index)) if restrain_hydrogen_bonds or restrain_hydrogen_angles: for residue in traj.topology.residues: if residue.is_water: oxygen_idx = -1 hydrogen_idxs = [] for atom in residue.atoms: if str(atom.element.symbol) == "O": oxygen_idx = atom.index elif str(atom.element.symbol) == "H": hydrogen_idxs.append(atom.index) else: raise RuntimeError( "Water should only consist of O and H atoms.") if restrain_hydrogen_bonds: self.solvent_restraints.append( BondFlatBottomRestraint( sigma=0.2 * unit.angstrom, atom_i_idx=oxygen_idx, atom_j_idx=hydrogen_idxs[0], atoms=self.ligand_in_water_atoms, )) self.solvent_restraints.append( BondFlatBottomRestraint( sigma=0.2 * unit.angstrom, atom_i_idx=oxygen_idx, atom_j_idx=hydrogen_idxs[1], atoms=self.ligand_in_water_atoms, )) if restrain_hydrogen_angles: self.solvent_restraints.append( AngleHarmonicRestraint( sigma=0.1 * unit.radian, atom_i_idx=hydrogen_idxs[0], atom_j_idx=oxygen_idx, atom_k_idx=hydrogen_idxs[1], )) # return a mdtraj object for visual check return md.Trajectory( self._ligand_in_water_coordinates.value_in_unit(unit.nanometer), self.ligand_in_water_topology, )
def runNCMC(platform_name, nstepsNC, nprop, outfname): #Generate the ParmEd Structure prmtop = utils.get_data_filename('blues', 'tests/data/eqToluene.prmtop') # inpcrd = utils.get_data_filename('blues', 'tests/data/eqToluene.inpcrd') struct = parmed.load_file(prmtop, xyz=inpcrd) print('Structure: %s' % struct.topology) #Define some options opt = { 'temperature': 300.0, 'friction': 1, 'dt': 0.002, 'nIter': 100, 'nstepsNC': nstepsNC, 'nstepsMD': 5000, 'nprop': nprop, 'nonbondedMethod': 'PME', 'nonbondedCutoff': 10, 'constraints': 'HBonds', 'freeze_distance': 5.0, 'trajectory_interval': 1000, 'reporter_interval': 1000, 'ncmc_traj': None, 'write_move': True, 'platform': platform_name, 'outfname': 't4-tol', 'verbose': False } #Define the 'model' object we are perturbing here. # Calculate particle masses of object to be moved ligand = RandomLigandRotationMove(struct, 'LIG') ligand.calculateProperties() # Initialize object that proposes moves. ligand_mover = MoveEngine(ligand) # Generate the MD, NCMC, ALCHEMICAL Simulation objects simulations = SimulationFactory(struct, ligand_mover, **opt) simulations.createSimulationSet() # Add reporters to MD simulation. traj_reporter = openmm.app.DCDReporter( outfname + '-nc{}.dcd'.format(nstepsNC), opt['trajectory_interval']) progress_reporter = openmm.app.StateDataReporter( sys.stdout, separator="\t", reportInterval=opt['reporter_interval'], step=True, totalSteps=opt['nIter'] * opt['nstepsMD'], time=True, speed=True, progress=True, remainingTime=True) simulations.md.reporters.append(traj_reporter) simulations.md.reporters.append(progress_reporter) # Run BLUES Simulation blues = Simulation(simulations, ligand_mover, **opt) blues.run(opt['nIter'])
def openmm_simulate_amber_explicit( pdb_file, top_file=None, check_point=None, GPU_index=0, output_traj="output.dcd", output_log="output.log", output_cm=None, report_time=10 * u.picoseconds, sim_time=10 * u.nanoseconds, reeval_time=None, ): """ Start and run an OpenMM NPT simulation with Langevin integrator at 2 fs time step and 300 K. The cutoff distance for nonbonded interactions were set at 1.0 nm, which commonly used along with Amber force field. Long-range nonbonded interactions were handled with PME. Parameters ---------- top_file : topology file (.top, .prmtop, ...) This is the topology file discribe all the interactions within the MD system. pdb_file : coordinates file (.gro, .pdb, ...) This is the molecule configuration file contains all the atom position and PBC (periodic boundary condition) box in the system. GPU_index : Int or Str The device # of GPU to use for running the simulation. Use Strings, '0,1' for example, to use more than 1 GPU output_traj : the trajectory file (.dcd) This is the file stores all the coordinates information of the MD simulation results. output_log : the log file (.log) This file stores the MD simulation status, such as steps, time, potential energy, temperature, speed, etc. report_time : 10 ps The program writes its information to the output every 10 ps by default sim_time : 10 ns The timespan of the simulation trajectory """ # set up save dir for simulation results work_dir = os.getcwd() time_label = int(time.time()) omm_path = create_md_path(time_label) print(f"Running simulation at {omm_path}") # setting up running path os.chdir(omm_path) top = pmd.load_file(top_file, xyz=pdb_file) system = top.createSystem(nonbondedMethod=app.PME, nonbondedCutoff=1 * u.nanometer, constraints=app.HBonds) dt = 0.002 * u.picoseconds integrator = omm.LangevinIntegrator(300 * u.kelvin, 1 / u.picosecond, dt) system.addForce(omm.MonteCarloBarostat(1 * u.bar, 300 * u.kelvin)) try: platform = omm.Platform_getPlatformByName("CUDA") properties = {'DeviceIndex': str(GPU_index), 'CudaPrecision': 'mixed'} except Exception: platform = omm.Platform_getPlatformByName("OpenCL") properties = {'DeviceIndex': str(GPU_index)} simulation = app.Simulation(top.topology, system, integrator, platform, properties) # simulation.context.setPositions(top.positions) if pdb.get_coordinates().shape[0] == 1: simulation.context.setPositions(pdb.positions) shutil.copy2(pdb_file, './') else: positions = random.choice(pdb.get_coordinates()) simulation.context.setPositions(positions / 10) #parmed \AA to OpenMM nm pdb.write_pdb('start.pdb', coordinates=positions) simulation.minimizeEnergy() simulation.context.setVelocitiesToTemperature(300 * u.kelvin, random.randint(1, 10000)) simulation.step(int(100 * u.picoseconds / (2 * u.femtoseconds))) report_freq = int(report_time / dt) simulation.reporters.append(app.DCDReporter(output_traj, report_freq)) if output_cm: simulation.reporters.append(ContactMapReporter(output_cm, report_freq)) simulation.reporters.append( app.StateDataReporter(output_log, report_freq, step=True, time=True, speed=True, potentialEnergy=True, temperature=True, totalEnergy=True)) simulation.reporters.append( app.CheckpointReporter('checkpnt.chk', report_freq)) if check_point: simulation.loadCheckpoint(check_point) nsteps = int(sim_time / dt) simulation.step(nsteps) if reeval_time: nsteps = int(reeval_time / dt) niter = int(sim_time / reeval_time) for i in range(niter): if os.path.exists('../halt'): return elif os.path.exists('new_pdb'): print("Found new.pdb, starting new sim...") # cleaning up old runs del simulation # starting new simulation with new pdb with open('new_pdb', 'r') as fp: new_pdb = fp.read().split()[0] os.chdir(work_dir) openmm_simulate_amber_explicit( new_pdb, top_file=top_file, check_point=None, GPU_index=GPU_index, output_traj=output_traj, output_log=output_log, output_cm=output_cm, report_time=report_time, sim_time=sim_time, reeval_time=reeval_time, ) else: simulation.step(nsteps) else: nsteps = int(sim_time / dt) simulation.step(nsteps) os.chdir(work_dir) if not os.path.exists('../halt'): openmm_simulate_amber_explicit( pdb_file, top_file=top_file, check_point=None, GPU_index=GPU_index, output_traj=output_traj, output_log=output_log, output_cm=output_cm, report_time=report_time, sim_time=sim_time, reeval_time=reeval_time, ) else: return
def structure(): # Load the waterbox with toluene into a structure. prmtop = utils.get_data_filename('blues', 'tests/data/TOL-parm.prmtop') inpcrd = utils.get_data_filename('blues', 'tests/data/TOL-parm.inpcrd') structure = parmed.load_file(prmtop, xyz=inpcrd) return structure
from parmed.exceptions import CharmmWarning, ParameterError from parmed.openmm.utils import energy_decomposition from parmed import unit as u, openmm, load_file, UreyBradley from parmed.utils.six.moves import range from copy import copy from math import sqrt import unittest from utils import get_fn, TestCaseRelative, mm, app, has_openmm, CPU import warnings # Suppress warning output from bad psf file... sigh. warnings.filterwarnings('ignore', category=CharmmWarning) # System charmm_gas = CharmmPsfFile(get_fn('ala_ala_ala.psf')) charmm_gas_crds = load_file(get_fn('ala_ala_ala.pdb')) charmm_nbfix = CharmmPsfFile(get_fn('ala3_solv.psf')) charmm_nbfix_crds = CharmmCrdFile(get_fn('ala3_solv.crd')) charmm_nbfix.box = [3.271195e1, 3.299596e1, 3.300715e1, 90, 90, 90] # Parameter sets param22 = CharmmParameterSet(get_fn('top_all22_prot.inp'), get_fn('par_all22_prot.inp')) param36 = CharmmParameterSet(get_fn('par_all36_prot.prm'), get_fn('toppar_water_ions.str')) @unittest.skipUnless(has_openmm, "Cannot test without OpenMM") class TestCharmmFiles(TestCaseRelative): def test_gas_energy(self): """ Compare OpenMM and CHARMM gas phase energies """
def main(args): #Get the structure, topology, and trajectory files from the command line #ParmEd accepts a wide range of file types (Amber, GROMACS, CHARMM, OpenMM... but not LAMMPS) try: topFile = args[0] strucFile = args[1] trajFile = args[2] except IndexError: print( "Specify topology, structure, and trajectory files from the command line." ) print(Usage) sys.exit(2) #And also allow user to specify start frame, but default to zero if no input try: startFrame = int(args[3]) except IndexError: startFrame = 0 #And get information on whether or not to use a restraint try: boolstr = args[4] if boolstr.lower() == 'true' or boolstr.lower() == 'yes': restraintBool = True else: restraintBool = False except IndexError: restraintBool = False print("Using topology file: %s" % topFile) print("Using structure file: %s" % strucFile) print("Using trajectory file: %s" % trajFile) print("\nSetting up system...") #Load in the files for initial simulations top = pmd.load_file(topFile) struc = pmd.load_file(strucFile) #Transfer unit cell information to topology object top.box = struc.box[:] #Set up some global features to use in all simulations temperature = 298.15 * u.kelvin #Define the platform (i.e. hardware and drivers) to use for running the simulation #This can be CUDA, OpenCL, CPU, or Reference #CUDA is for NVIDIA GPUs #OpenCL is for CPUs or GPUs, but must be used for old CPUs (not SSE4.1 compatible) #CPU only allows single precision (CUDA and OpenCL allow single, mixed, or double) #Reference is a clear, stable reference for other code development and is very slow, using double precision by default platform = mm.Platform.getPlatformByName('CUDA') prop = { #'Threads': '1', #number of threads for CPU - all definitions must be strings (I think) 'Precision': 'mixed', #for CUDA or OpenCL, select the precision (single, mixed, or double) 'DeviceIndex': '0', #selects which GPUs to use - set this to zero if using CUDA_VISIBLE_DEVICES 'DeterministicForces': 'True' #Makes sure forces with CUDA and PME are deterministic } #Create the OpenMM system that can be used as a reference systemRef = top.createSystem( nonbondedMethod=app. PME, #Uses PME for long-range electrostatics, simple cut-off for LJ nonbondedCutoff=12.0 * u.angstroms, #Defines cut-off for non-bonded interactions rigidWater=True, #Use rigid water molecules constraints=app.HBonds, #Constrains all bonds involving hydrogens flexibleConstraints= False, #Whether to include energies for constrained DOFs removeCMMotion= True, #Whether or not to remove COM motion (don't want to if part of system frozen) ) #Set up the integrator to use as a reference integratorRef = mm.LangevinIntegrator( temperature, #Temperature for Langevin 1.0 / u.picoseconds, #Friction coefficient 2.0 * u.femtoseconds, #Integration timestep ) integratorRef.setConstraintTolerance(1.0E-08) #Get solute atoms and solute heavy atoms separately soluteIndices = [] heavyIndices = [] for res in top.residues: if res.name not in ['OTM', 'CTM', 'STM', 'NTM', 'SOL']: for atom in res.atoms: soluteIndices.append(atom.idx) if 'H' not in atom.name[0]: heavyIndices.append(atom.idx) #JUST for boric acid, add a custom bonded force #Couldn't find a nice, compatible force field, but did find A forcefield, so using it #But has no angle terms on O-B-O and instead a weird bond repulsion term #This term also prevents out of plane bending #Simple in our case because boric acid is symmetric, so only need one parameter #Parameters come from Otkidach and Pletnev, 2001 #Here, Ad = (A^2) / (d^6) since Ai and Aj and di and dj are all the same #In the original paper, B-OH bond had A = 1.72 and d = 0.354 #Note that d is dimensionless and A should have units of (Angstrom^3)*(kcal/mol)^(1/2) #These units are inferred just to make things work out with kcal/mol and the given distance dependence bondRepulsionFunction = 'Ad*(1.0/r)^6' BondRepulsionForce = mm.CustomBondForce(bondRepulsionFunction) BondRepulsionForce.addPerBondParameter( 'Ad') #Units are technically kJ/mol * nm^6 baOxInds = [] for aind in soluteIndices: if top.atoms[aind].type == 'oh': baOxInds.append(aind) for i in range(len(baOxInds)): for j in range(i + 1, len(baOxInds)): BondRepulsionForce.addBond(baOxInds[i], baOxInds[j], [0.006289686]) systemRef.addForce(BondRepulsionForce) #If working with expanded ensemble simulation of solute near the interface, need to include restraint to keep close if restraintBool: #Also get surface SU atoms and surface CU atoms at top and bottom of surface surfIndices = [] for atom in top.atoms: if atom.type == 'SU': surfIndices.append(atom.idx) print("\nSolute indices: %s" % str(soluteIndices)) print("Solute heavy atom indices: %s" % str(heavyIndices)) print("Surface SU atom indices: %s" % str(surfIndices)) #Will now add a custom bonded force between heavy atoms of each solute and surface SU atoms #Should be in units of kJ/mol*nm^2, but should check this #Also, note that here we are using a flat-bottom restraint to keep close to surface #AND to keep from penetrating into surface when it's in the decoupled state refZlo = 1.4 * u.nanometer #in nm, the distance between the SU atoms and the solute centroid refZhi = 1.7 * u.nanometer restraintExpression = '0.5*k*step(refZlo - (z2 - z1))*(((z2 - z1) - refZlo)^2)' restraintExpression += '+ 0.5*k*step((z2 - z1) - refZhi)*(((z2 - z1) - refZhi)^2)' restraintForce = mm.CustomCentroidBondForce(2, restraintExpression) restraintForce.addPerBondParameter('k') restraintForce.addPerBondParameter('refZlo') restraintForce.addPerBondParameter('refZhi') restraintForce.addGroup(surfIndices, np.ones( len(surfIndices))) #Don't weight with masses #To assign flat-bottom restraint correctly, need to know if each solute is above or below interface #Will need surface z-positions for this suZpos = np.average(struc.coordinates[surfIndices, 2]) restraintForce.addGroup(heavyIndices, np.ones(len(heavyIndices))) solZpos = np.average(struc.coordinates[heavyIndices, 2]) if (solZpos - suZpos) > 0: restraintForce.addBond([0, 1], [10000.0, refZlo, refZhi]) else: #A little confusing, but have to negate and switch for when z2-z1 is always going to be negative restraintForce.addBond([0, 1], [10000.0, -refZhi, -refZlo]) systemRef.addForce(restraintForce) #And define lambda states of interest lambdaVec = np.array( #electrostatic lambda - 1.0 is fully interacting, 0.0 is non-interacting [ [ 1.00, 0.75, 0.50, 0.25, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00 ], #LJ lambdas - 1.0 is fully interacting, 0.0 is non-interacting [ 1.00, 1.00, 1.00, 1.00, 1.00, 0.90, 0.80, 0.70, 0.60, 0.50, 0.40, 0.35, 0.30, 0.25, 0.20, 0.15, 0.10, 0.05, 0.00 ] ]) #We need to add a custom non-bonded force for the solute being alchemically changed #Will be helpful to have handle on non-bonded force handling LJ and coulombic interactions NBForce = None for frc in systemRef.getForces(): if (isinstance(frc, mm.NonbondedForce)): NBForce = frc #Turn off dispersion correction since have interface NBForce.setUseDispersionCorrection(False) #Separate out alchemical and regular particles using set objects alchemicalParticles = set(soluteIndices) chemicalParticles = set(range( systemRef.getNumParticles())) - alchemicalParticles #Define the soft-core function for turning on/off LJ interactions #In energy expressions for CustomNonbondedForce, r is a special variable and refers to the distance between particles #All other variables must be defined somewhere in the function. #The exception are variables like sigma1 and sigma2. #It is understood that a parameter will be added called 'sigma' and that the '1' and '2' are to specify the combining rule. #Have also added parameter to switch the soft-core interaction to a WCA potential softCoreFunctionWCA = '(step(x-0.5))*(4.0*lambdaLJ*epsilon*x*(x-1.0) + (1.0-lambdaWCA)*lambdaLJ*epsilon) ' softCoreFunctionWCA += '+ (1.0 - step(x-0.5))*lambdaWCA*(4.0*lambdaLJ*epsilon*x*(x-1.0));' softCoreFunctionWCA += 'x = (1.0/reff_sterics);' softCoreFunctionWCA += 'reff_sterics = (0.5*(1.0-lambdaLJ) + ((r/sigma)^6));' softCoreFunctionWCA += 'sigma=0.5*(sigma1+sigma2); epsilon = sqrt(epsilon1*epsilon2)' #Define the system force for this function and its parameters SoftCoreForceWCA = mm.CustomNonbondedForce(softCoreFunctionWCA) SoftCoreForceWCA.addGlobalParameter( 'lambdaLJ', 1.0 ) #Throughout, should follow convention that lambdaLJ=1.0 is fully-interacting state SoftCoreForceWCA.addGlobalParameter( 'lambdaWCA', 1.0) #When 1, attractions included; setting to 0 turns off attractions SoftCoreForceWCA.addPerParticleParameter('sigma') SoftCoreForceWCA.addPerParticleParameter('epsilon') #Will turn off electrostatics completely in the original non-bonded force #In the end-state, only want electrostatics inside the alchemical molecule #To do this, just turn ON a custom force as we turn OFF electrostatics in the original force ONE_4PI_EPS0 = 138.935456 #in kJ/mol nm/e^2 soluteCoulFunction = '(1.0-(lambdaQ^2))*ONE_4PI_EPS0*charge/r;' soluteCoulFunction += 'ONE_4PI_EPS0 = %.16e;' % (ONE_4PI_EPS0) soluteCoulFunction += 'charge = charge1*charge2' SoluteCoulForce = mm.CustomNonbondedForce(soluteCoulFunction) #Note this lambdaQ will be different than for soft core (it's also named differently, which is CRITICAL) #This lambdaQ corresponds to the lambda that scales the charges to zero #To turn on this custom force at the same rate, need to multiply by (1.0-lambdaQ**2), which we do SoluteCoulForce.addGlobalParameter('lambdaQ', 1.0) SoluteCoulForce.addPerParticleParameter('charge') #Also create custom force for intramolecular alchemical LJ interactions #Could include with electrostatics, but nice to break up #We could also do this with a separate NonbondedForce object, but it would be a little more work, actually soluteLJFunction = '4.0*epsilon*x*(x-1.0); x = (sigma/r)^6;' soluteLJFunction += 'sigma=0.5*(sigma1+sigma2); epsilon=sqrt(epsilon1*epsilon2)' SoluteLJForce = mm.CustomNonbondedForce(soluteLJFunction) SoluteLJForce.addPerParticleParameter('sigma') SoluteLJForce.addPerParticleParameter('epsilon') #Loop over all particles and add to custom forces #As we go, will also collect full charges on the solute particles #AND we will set up the solute-solute interaction forces alchemicalCharges = [[0]] * len(soluteIndices) for ind in range(systemRef.getNumParticles()): #Get current parameters in non-bonded force [charge, sigma, epsilon] = NBForce.getParticleParameters(ind) #Make sure that sigma is not set to zero! Fine for some ways of writing LJ energy, but NOT OK for soft-core! if sigma / u.nanometer == 0.0: newsigma = 0.3 * u.nanometer #This 0.3 is what's used by GROMACS as a default value for sc-sigma else: newsigma = sigma #Add the particle to the soft-core force (do for ALL particles) SoftCoreForceWCA.addParticle([newsigma, epsilon]) #Also add the particle to the solute only forces SoluteCoulForce.addParticle([charge]) SoluteLJForce.addParticle([sigma, epsilon]) #If the particle is in the alchemical molecule, need to set it's LJ interactions to zero in original force if ind in soluteIndices: NBForce.setParticleParameters(ind, charge, sigma, epsilon * 0.0) #And keep track of full charge so we can scale it right by lambda alchemicalCharges[soluteIndices.index(ind)] = charge #Now we need to handle exceptions carefully for ind in range(NBForce.getNumExceptions()): [p1, p2, excCharge, excSig, excEps] = NBForce.getExceptionParameters(ind) #For consistency, must add exclusions where we have exceptions for custom forces SoftCoreForceWCA.addExclusion(p1, p2) SoluteCoulForce.addExclusion(p1, p2) SoluteLJForce.addExclusion(p1, p2) #Only compute interactions between the alchemical and other particles for the soft-core force SoftCoreForceWCA.addInteractionGroup(alchemicalParticles, chemicalParticles) #And only compute alchemical/alchemical interactions for other custom forces SoluteCoulForce.addInteractionGroup(alchemicalParticles, alchemicalParticles) SoluteLJForce.addInteractionGroup(alchemicalParticles, alchemicalParticles) #Set other soft-core parameters as needed SoftCoreForceWCA.setCutoffDistance(12.0 * u.angstroms) SoftCoreForceWCA.setNonbondedMethod(mm.CustomNonbondedForce.CutoffPeriodic) SoftCoreForceWCA.setUseLongRangeCorrection(False) systemRef.addForce(SoftCoreForceWCA) #Set other parameters as needed - note that for the solute force would like to set no cutoff #However, OpenMM won't allow a bunch of potentials with cutoffs then one without... #So as long as the solute is smaller than the cut-off, won't have any problems! SoluteCoulForce.setCutoffDistance(12.0 * u.angstroms) SoluteCoulForce.setNonbondedMethod(mm.CustomNonbondedForce.CutoffPeriodic) SoluteCoulForce.setUseLongRangeCorrection(False) systemRef.addForce(SoluteCoulForce) SoluteLJForce.setCutoffDistance(12.0 * u.angstroms) SoluteLJForce.setNonbondedMethod(mm.CustomNonbondedForce.CutoffPeriodic) SoluteLJForce.setUseLongRangeCorrection(False) systemRef.addForce(SoluteLJForce) #Need to add integrator and context in order to evaluate potential energies #Integrator is arbitrary because won't use it integrator = mm.VerletIntegrator(1.0 * u.femtoseconds) context = mm.Context(systemRef, integrator, platform, prop) ########################################################################## print("\nStarting analysis...") kBT = u.AVOGADRO_CONSTANT_NA * u.BOLTZMANN_CONSTANT_kB * temperature #Set up arrays to hold potential energies #First row will be coupled, then no electrostatics, then no electrostatics with WCA, then decoupled allU = np.array([[]] * 4).T #We've now set everything up like we're going to run a simulation #But now we will use pytraj to load a trajectory to get coordinates #With those coordinates, we will evaluate the energies we want #Just need to figure out if we have a surface or a bulk system trajFiles = glob.glob('Quad*/%s' % trajFile) if len(trajFiles) == 0: trajFiles = [trajFile] print("Using following trajectory files: %s" % str(trajFiles)) for aFile in trajFiles: trajtop = copy.deepcopy(top) trajtop.rb_torsions = pmd.TrackedList( []) #Necessary for SAM systems so doesn't break pytraj trajtop = pt.load_parmed(trajtop, traj=False) traj = pt.iterload(aFile, top=trajtop, frame_slice=(startFrame, -1)) thisAllU = np.zeros((len(traj), 4)) #Loop over the lambda states of interest, looping over whole trajectory each time for i, lstate in enumerate([ [1.0, 1.0, 1.0], #Fully coupled [1.0, 1.0, 0.0], #Charge turned off [1.0, 0.0, 0.0], #Charged turned off, attractions turned off (so WCA) [0.0, 1.0, 0.0] ]): #Decoupled (WCA still included, though doesn't matter) #Set the lambda state context.setParameter('lambdaLJ', lstate[0]) context.setParameter('lambdaWCA', lstate[1]) context.setParameter('lambdaQ', lstate[2]) for k, ind in enumerate(soluteIndices): [charge, sig, eps] = NBForce.getParticleParameters(ind) NBForce.setParticleParameters(ind, alchemicalCharges[k] * lstate[2], sig, eps) NBForce.updateParametersInContext(context) #And loop over trajectory for t, frame in enumerate(traj): thisbox = np.array(frame.box.values[:3]) context.setPeriodicBoxVectors( np.array([thisbox[0], 0.0, 0.0]) * u.angstrom, np.array([0.0, thisbox[1], 0.0]) * u.angstrom, np.array([0.0, 0.0, thisbox[2]]) * u.angstrom) thispos = np.array(frame.xyz) * u.angstrom context.setPositions(thispos) thisAllU[t, i] = context.getState( getEnergy=True).getPotentialEnergy() / kBT #Add this trajectory information allU = np.vstack((allU, thisAllU)) #And that should be it, just need to save files and print information #avgUq = np.average(allU[0,:] - allU[1,:]) #stdUq = np.std(allU[0,:] - allU[1,:], ddof=1) #avgUlj = np.average(allU[1,:] - allU[2,:]) #stdUlj = np.std(allU[1,:] - allU[2,:], ddof=1) #print("\nAverage solute-water electrostatic potential energy: %f +/- %f"%(avgUq, stdUq)) #print("Average solute-water LJ potential energy: %f +/- %f"%(avgUlj, stdUlj)) np.savetxt( 'pot_energy_decomp.txt', allU, header= 'U_coupled (kBT) U_noQ (kBT) U_noQ_WCA (kBT) U_decoupled (kBT) ' ) #Print some meaningful information #Just make sure we do so as accurately as possible using alchemical information alchFile = glob.glob('alchemical_U*.txt')[0] print('Using alchemical information file: %s' % alchFile) printInfo(allU, mbarFile='mbar_object.pkl', alchFile=alchFile)
def test_amber_single_window_gbmin(clean_files): # Distance restraint restraint = restraints.DAT_restraint() restraint.continuous_apr = True restraint.amber_index = True restraint.topology = os.path.join( os.path.dirname(__file__), "../data/k-cl/k-cl.pdb" ) restraint.mask1 = ":K+" restraint.mask2 = ":Cl-" restraint.attach["target"] = 4.5 restraint.attach["fraction_list"] = [0.00, 0.04, 0.181, 0.496, 1.000] restraint.attach["fc_final"] = 5.0 restraint.pull["fc"] = restraint.attach["fc_final"] restraint.pull["target_initial"] = restraint.attach["target"] restraint.pull["target_final"] = 18.5 restraint.pull["num_windows"] = 5 restraint.initialize() windows_directory = os.path.join("tmp", "k-cl", "windows") window_list = create_window_list([restraint]) for window in window_list: os.makedirs(os.path.join(windows_directory, window)) with open( os.path.join(windows_directory, window, "restraints.in"), "a" ) as file: string = amber.amber_restraint_line(restraint, window) file.write(string) for window in window_list: if window[0] == "a": structure = pmd.load_file( os.path.join(os.path.dirname(__file__), "../data/k-cl/k-cl-sol.prmtop"), os.path.join(os.path.dirname(__file__), "../data/k-cl/k-cl-sol.rst7"), structure=True, ) for atom in structure.atoms: if atom.name == "Cl-": atom.xz = 2.65 structure.save( os.path.join(windows_directory, window, "k-cl.prmtop"), overwrite=True ) structure.save( os.path.join(windows_directory, window, "k-cl.rst7"), overwrite=True ) structure.save( os.path.join(windows_directory, window, "k-cl.pdb"), overwrite=True ) elif window[0] == "p": structure = pmd.load_file( os.path.join(os.path.dirname(__file__), "../data/k-cl/k-cl-sol.prmtop"), os.path.join(os.path.dirname(__file__), "../data/k-cl/k-cl-sol.rst7"), structure=True, ) target_difference = ( restraint.phase["pull"]["targets"][int(window[1:])] - restraint.phase["pull"]["targets"][0] ) for atom in structure.atoms: if atom.name == "Cl-": atom.xz += target_difference structure.save( os.path.join(windows_directory, window, "k-cl.prmtop"), overwrite=True ) structure.save( os.path.join(windows_directory, window, "k-cl.rst7"), overwrite=True ) structure.save( os.path.join(windows_directory, window, "k-cl.pdb"), overwrite=True ) gbsim = Simulation() gbsim.path = os.path.join("tmp", "k-cl", "windows", "a003") gbsim.executable = "sander" gbsim.topology = "k-cl.prmtop" gbsim.prefix = "minimize" gbsim.inpcrd = "k-cl.rst7" gbsim.config_gb_min() gbsim.cntrl["maxcyc"] = 1 gbsim.cntrl["ncyc"] = 1 gbsim.run() gbsim.config_gb_md() gbsim.prefix = "md" gbsim.inpcrd = "minimize.rst7" gbsim.cntrl["nstlim"] = 1 gbsim.cntrl["ntwe"] = 1 gbsim.cntrl["ntpr"] = 1 gbsim.cntrl["ig"] = 777 gbsim.run() mden = parse_mden(os.path.join("tmp", "k-cl", "windows", "a003", "md.mden")) assert pytest.approx(mden["Bond"][0]) == 0 assert pytest.approx(mden["Angle"][0]) == 0 assert pytest.approx(mden["Dihedral"][0]) == 0 assert pytest.approx(mden["V14"][0]) == 0 assert pytest.approx(mden["E14"][0]) == 0 assert pytest.approx(mden["VDW"][0], 0.1) == 25956.13225 assert pytest.approx(mden["Ele"][0], 0.1) == -18828.99631 assert pytest.approx(mden["Total"][0], 0.1) == 7127.13594
def test_missing_definition(self, missing_overrides_ff): structure = pmd.load_file(get_fn("silly_chemistry.mol2"), structure=True) with pytest.raises(FoyerError): missing_overrides_ff.apply(structure)
### back into the omm script is left as an exercise to the reader. ### Note: If the simulation crashes, try adjusting the integrator setings ####################### topfile = 'compound.top' grofile = 'npt.gro' temp = 305 * u.kelvin pressure = 1 * u.bar timestep = 2.0 * u.femtoseconds sim_time = 100 * u.nanoseconds platform = mm.Platform.getPlatformByName('OpenCL') properties = {'DeviceIndex': 0} n_steps = int(round(sim_time / timestep)) print("Reading grofiles") top = pmd.load_file(topfile, xyz=grofile) print("Creating system from topology") system = top.createSystem(nonbondedMethod=app.PME, constraints=app.HBonds, nonbondedCutoff=12.0 * u.angstroms, switchDistance=10.0 * u.angstroms) barostat = mm.MonteCarloMembraneBarostat( pressure, 0.0 * u.bar * u.nanometer, temp, mm.MonteCarloMembraneBarostat.XYIsotropic, mm.MonteCarloMembraneBarostat.ZFree, 100) system.addForce(barostat) print("Creating integrator") integrator = mm.LangevinIntegrator(temp, 1.0 / u.picoseconds, timestep) print("Creating Simulation")
# Usage : python3 step3.py import sys # OpenMM Imports import simtk.openmm as mm import simtk.openmm.app as app # ParmEd Imports from parmed import load_file, unit as u from parmed.openmm import StateDataReporter, NetCDFReporter, RestartReporter # Load the Amber files print('Loading AMBER files...') inpcrdName = 'rst/step2.rst.0' gmx_solv = load_file('complex_wat.prmtop', xyz=inpcrdName) inpcrd = app.AmberInpcrdFile(inpcrdName) #minimization info minStep = 4000 minimizationTrue = False restartSim = False #simulation info tempK = 10 simTime = 20 #units of picoseconds #simType = 'npt'; pressureAtm = 1; simType = 'nvt' # or 'min' #restraint
window_num = int(window[1:]) if os.path.exists(path + window + "/openmm_prod.rst7"): continue integrator = LangevinIntegrator( settings["temperature"], settings["friction"], settings["timestep"], ) barostat = mm.MonteCarloBarostat(1.0 * unit.bar, settings["temperature"], 25) structure = pmd.load_file(path + window + topology, path + window + coordinates, structure=True) # Set mass of Dummy atoms to 0 so they are non-interacting for atom in structure.atoms: if atom.name == "DUM": atom.mass = 0 topology_0m = "/cb6-but-dum-0m.prmtop" coordinates_0m = "/cb6-but-dum-0m.rst7" structure.save(path + window + topology_0m, overwrite=True) structure.save(path + window + coordinates_0m, overwrite=True) prmtop = app.AmberPrmtopFile(path + window + topology_0m) inpcrd = app.AmberInpcrdFile(path + window + "/openmm_equil.rst7")