def test_double_conversion(self): # Pick some OPLS parameters at random params = { 'k0' : 1.38 * u.Unit('kJ/mol'), 'k1' : -0.51 * u.Unit('kJ/mol'), 'k2' : 2.2 * u.Unit('kJ/mol'), 'k3' : -0.25 * u.Unit('kJ/mol'), 'k4' : 1.44 * u.Unit('kJ/mol') } name = OPLSTorsionPotential().name expression = OPLSTorsionPotential().expression variables = OPLSTorsionPotential().independent_variables opls_connection_type = DihedralType( name=name, expression=expression, independent_variables=variables, parameters=params) # Convert connection to RB ryckaert_connection_type = convert_opls_to_ryckaert( opls_connection_type) # Convert connection back to OPLS final_connection_type = convert_ryckaert_to_opls( ryckaert_connection_type) assert np.allclose([*opls_connection_type.parameters.values()], [*final_connection_type.parameters.values()])
def test_double_conversion(self, templates): # Pick some OPLS parameters at random params = { 'k0': 1.38 * u.Unit('kJ/mol'), 'k1': -0.51 * u.Unit('kJ/mol'), 'k2': 2.2 * u.Unit('kJ/mol'), 'k3': -0.25 * u.Unit('kJ/mol'), 'k4': 1.44 * u.Unit('kJ/mol') } opls_torsion_potential = templates['OPLSTorsionPotential'] name = opls_torsion_potential.name expression = opls_torsion_potential.expression variables = opls_torsion_potential.independent_variables opls_connection_type = DihedralType(name=name, expression=expression, independent_variables=variables, parameters=params) # Convert connection to RB ryckaert_connection_type = convert_opls_to_ryckaert( opls_connection_type) # Convert connection back to OPLS final_connection_type = convert_ryckaert_to_opls( ryckaert_connection_type) assert_allclose_units([*opls_connection_type.parameters.values()], [*final_connection_type.parameters.values()], rtol=1e-5, atol=1e-8)
def test_ryckaert_to_opls(self, templates): # Pick some RB parameters at random params = { "c0": 1.53 * u.Unit("kJ/mol"), "c1": 0.76 * u.Unit("kJ/mol"), "c2": -0.22 * u.Unit("kJ/mol"), "c3": 3.55 * u.Unit("kJ/mol"), "c4": 0.94 * u.Unit("kJ/mol"), "c5": 0.0 * u.Unit("kJ/mol"), } ryckaert_bellemans_torsion_potential = templates[ "RyckaertBellemansTorsionPotential"] name = ryckaert_bellemans_torsion_potential.name expression = ryckaert_bellemans_torsion_potential.expression variables = ryckaert_bellemans_torsion_potential.independent_variables ryckaert_connection_type = DihedralType( name=name, expression=expression, independent_variables=variables, parameters=params, ) # Convert connection to OPLS opls_connection_type = convert_ryckaert_to_opls( ryckaert_connection_type) # Pick some angles to check angles = [-2.38, -1.31, -0.44, 0.0, 0.26, 0.92, 1.84, 3.10] for angle in angles: assert np.isclose( float( ryckaert_connection_type.expression.subs([ (param, val) for param, val in { **ryckaert_connection_type.parameters, "phi": angle - np.pi, }.items() ])), float( opls_connection_type.expression.subs([ (param, val) for param, val in { **opls_connection_type.parameters, "phi": angle, }.items() ])), )
def test_ryckaert_to_opls(self, templates): # Pick some RB parameters at random params = { 'c0': 1.53 * u.Unit('kJ/mol'), 'c1': 0.76 * u.Unit('kJ/mol'), 'c2': -0.22 * u.Unit('kJ/mol'), 'c3': 3.55 * u.Unit('kJ/mol'), 'c4': 0.94 * u.Unit('kJ/mol'), 'c5': 0.0 * u.Unit('kJ/mol') } ryckaert_bellemans_torsion_potential = templates[ 'RyckaertBellemansTorsionPotential'] name = ryckaert_bellemans_torsion_potential.name expression = ryckaert_bellemans_torsion_potential.expression variables = ryckaert_bellemans_torsion_potential.independent_variables ryckaert_connection_type = DihedralType( name=name, expression=expression, independent_variables=variables, parameters=params) # Convert connection to OPLS opls_connection_type = convert_ryckaert_to_opls( ryckaert_connection_type) # Pick some angles to check angles = [-2.38, -1.31, -0.44, 0.0, 0.26, 0.92, 1.84, 3.10] for angle in angles: assert np.isclose( float( ryckaert_connection_type.expression.subs([ (param, val) for param, val in { **ryckaert_connection_type.parameters, 'phi': angle - np.pi }.items() ])), float( opls_connection_type.expression.subs([ (param, val) for param, val in { **opls_connection_type.parameters, 'phi': angle }.items() ])), )
def test_invalid_connection_type(self, templates): params = { 'c0': 1.53 * u.Unit('kJ/mol'), 'c1': 0.76 * u.Unit('kJ/mol'), 'c2': -0.22 * u.Unit('kJ/mol'), 'c3': 3.55 * u.Unit('kJ/mol'), 'c4': 0.94 * u.Unit('kJ/mol'), 'c5': 0.0 * u.Unit('kJ/mol') } ryckaert_bellemans_torsion_potential = templates[ 'RyckaertBellemansTorsionPotential'] name = ryckaert_bellemans_torsion_potential.name expression = ryckaert_bellemans_torsion_potential.expression variables = ['phi', 'psi'] ryckaert_connection_type = DihedralType( name=name, expression=expression, independent_variables=variables, parameters=params) with pytest.raises(GMSOError, match='Cannot use'): opls_connection_type = convert_ryckaert_to_opls( ryckaert_connection_type) expression = 'c0+c1+c2+c3+c4+c5+phi' variables = ryckaert_bellemans_torsion_potential.independent_variables ryckaert_connection_type = DihedralType( name=name, expression=expression, independent_variables=variables, parameters=params) with pytest.raises(GMSOError, match='Cannot use'): opls_connection_type = convert_ryckaert_to_opls( ryckaert_connection_type) # Pick some OPLS parameters at random params = { 'k0': 1.38 * u.Unit('kJ/mol'), 'k1': -0.51 * u.Unit('kJ/mol'), 'k2': 2.2 * u.Unit('kJ/mol'), 'k3': -0.25 * u.Unit('kJ/mol'), 'k4': 1.44 * u.Unit('kJ/mol') } opls_torsion_potential = templates['OPLSTorsionPotential'] name = opls_torsion_potential.name expression = opls_torsion_potential.expression variables = ['phi', 'psi'] opls_connection_type = DihedralType(name=name, expression=expression, independent_variables=variables, parameters=params) with pytest.raises(GMSOError, match=''): ryckaert_connection_type = convert_opls_to_ryckaert( opls_connection_type) variables = opls_torsion_potential.independent_variables expression = 'k0+k1+k2+k3+k4+phi' opls_connection_type = DihedralType(name=name, expression=expression, independent_variables=variables, parameters=params) with pytest.raises(GMSOError, match=''): ryckaert_connection_type = convert_opls_to_ryckaert( opls_connection_type)
def test_invalid_connection_type(self, templates): params = { "c0": 1.53 * u.Unit("kJ/mol"), "c1": 0.76 * u.Unit("kJ/mol"), "c2": -0.22 * u.Unit("kJ/mol"), "c3": 3.55 * u.Unit("kJ/mol"), "c4": 0.94 * u.Unit("kJ/mol"), "c5": 0.0 * u.Unit("kJ/mol"), } ryckaert_bellemans_torsion_potential = templates[ "RyckaertBellemansTorsionPotential"] name = ryckaert_bellemans_torsion_potential.name expression = (str(ryckaert_bellemans_torsion_potential.expression) + " + 3 * psi") variables = ["phi", "psi"] ryckaert_connection_type = DihedralType( name=name, expression=expression, independent_variables=variables, parameters=params, ) with pytest.raises(GMSOError, match="Cannot use"): opls_connection_type = convert_ryckaert_to_opls( ryckaert_connection_type) expression = "c0+c1+c2+c3+c4+c5+phi" variables = ryckaert_bellemans_torsion_potential.independent_variables ryckaert_connection_type = DihedralType( name=name, expression=expression, independent_variables=variables, parameters=params, ) with pytest.raises(GMSOError, match="Cannot use"): opls_connection_type = convert_ryckaert_to_opls( ryckaert_connection_type) # Pick some OPLS parameters at random params = { "k0": 1.38 * u.Unit("kJ/mol"), "k1": -0.51 * u.Unit("kJ/mol"), "k2": 2.2 * u.Unit("kJ/mol"), "k3": -0.25 * u.Unit("kJ/mol"), "k4": 1.44 * u.Unit("kJ/mol"), } opls_torsion_potential = templates["OPLSTorsionPotential"] name = opls_torsion_potential.name expression = (str(opls_torsion_potential.expression) + " + 0.5 * k1 * (1 + cos(psi))") variables = ["phi", "psi"] opls_connection_type = DihedralType( name=name, expression=expression, independent_variables=variables, parameters=params, ) with pytest.raises(GMSOError, match=""): ryckaert_connection_type = convert_opls_to_ryckaert( opls_connection_type) variables = opls_torsion_potential.independent_variables expression = "k0+k1+k2+k3+k4+phi" opls_connection_type = DihedralType( name=name, expression=expression, independent_variables=variables, parameters=params, ) with pytest.raises(GMSOError, match=""): ryckaert_connection_type = convert_opls_to_ryckaert( opls_connection_type)
def _write_dihedral_information(mcf, top): """Write the dihedrals in the system. Parameters ---------- mcf : file object The file object of the Cassandra mcf being written top : Topology Topology object dihedral_style : string Dihedral style for Cassandra to use """ # Dihedral info header = ("\n!Dihedral Format\n" "!index i j k l type parameters\n" '!type="none"\n' '!type="CHARMM", parms=a0 a1 delta\n' '!type="OPLS", parms=c0 c1 c2 c3\n' '!type="harmonic", parms=force_constant equilibrium_dihedral\n' "\n# Dihedral_Info\n") mcf.write(header) # TODO: Are impropers buried in dihedrals? mcf.write("{:d}\n".format(len(top.dihedrals))) for (idx, dihedral) in enumerate(top.dihedrals): mcf.write("{:<4d} " "{:<4d} " "{:<4d} " "{:<4d} " "{:<4d} ".format( idx + 1, dihedral.connection_members[0].idx + 1, dihedral.connection_members[1].idx + 1, dihedral.connection_members[2].idx + 1, dihedral.connection_members[3].idx + 1, )) dihedral_style = _get_dihedral_style(dihedral) # If ryckaert, convert to opls if dihedral_style == "ryckaert": dihedral.connection_type = convert_ryckaert_to_opls( dihedral.connection_type) dihedral_style = "opls" if dihedral_style == "opls": mcf.write( "{:s} " "{:10.5f} " "{:10.5f} " "{:10.5f} " "{:10.5f}\n".format( dihedral_style, 0.5 * dihedral.connection_type.parameters["k0"].in_units( "kJ/mol").value, 0.5 * dihedral.connection_type.parameters["k1"].in_units( "kJ/mol").value, 0.5 * dihedral.connection_type.parameters["k2"].in_units( "kJ/mol").value, 0.5 * dihedral.connection_type.parameters["k3"].in_units( "kJ/mol").value, )) elif dihedral_style == "charmm": mcf.write( "{:s} " "{:10.5f} " "{:10.5f} " "{:10.5f}\n".format( dihedral_style, dihedral.connection_type.parameters["k"].in_units( "kJ/mol").value, dihedral.connection_type.parameters["n"], dihedral.connection_type.parameters["phi_eq"].in_units( u.degrees).value, )) elif dihedral_style == "harmonic": mcf.write( "{:s} " "{:10.5f} " "{:10.5f}\n".format( dihedral_style, 0.5 * dihedral.connection_type.parameters["k"].in_units( "kJ/mol").value, dihedral.connection_type.parameters["phi_eq"].in_units( u.degrees).value, )) else: raise GMSOError( "Unsupported dihedral style for Cassandra MCF writer")
def write_lammpsdata(topology, filename, atom_style="full"): """Output a LAMMPS data file. Outputs a LAMMPS data file in the 'full' atom style format. Assumes use of 'real' units. See http://lammps.sandia.gov/doc/atom_style.html for more information on atom styles. Parameters ---------- Topology : `Topology` A Topology Object filename : str Path of the output file atom_style : str, optional, default='full' Defines the style of atoms to be saved in a LAMMPS data file. The following atom styles are currently supported: 'full', 'atomic', 'charge', 'molecular' see http://lammps.sandia.gov/doc/atom_style.html for more information on atom styles. Notes ----- See http://lammps.sandia.gov/doc/2001/data_format.html for a full description of the LAMMPS data format. This is a work in progress, as only atoms, masses, and atom_type information can be written out. Some of this function has been adopted from `mdtraj`'s support of the LAMMPSTRJ trajectory format. See https://github.com/mdtraj/mdtraj/blob/master/mdtraj/formats/lammpstrj.py for details. """ if atom_style not in ["atomic", "charge", "molecular", "full"]: raise ValueError( 'Atom style "{}" is invalid or is not currently supported'.format( atom_style)) # TODO: Support various unit styles box = topology.box with open(filename, "w") as data: data.write("{} written by topology at {}\n\n".format( topology.name if topology.name is not None else "", str(datetime.datetime.now()), )) data.write("{:d} atoms\n".format(topology.n_sites)) if atom_style in ["full", "molecular"]: if topology.n_bonds != 0: data.write("{:d} bonds\n".format(topology.n_bonds)) else: data.write("0 bonds\n") if topology.n_angles != 0: data.write("{:d} angles\n".format(topology.n_angles)) else: data.write("0 angles\n") if topology.n_dihedrals != 0: data.write("{:d} dihedrals\n\n".format(topology.n_dihedrals)) else: data.write("0 dihedrals\n\n") data.write("\n{:d} atom types\n".format(len(topology.atom_types))) data.write("{:d} bond types\n".format(len(topology.bond_types))) data.write("{:d} angle types\n".format(len(topology.angle_types))) data.write("{:d} dihedral types\n".format(len( topology.dihedral_types))) data.write("\n") # Box data if allclose_units( box.angles, u.unyt_array([90, 90, 90], "degree"), rtol=1e-5, atol=1e-8, ): warnings.warn("Orthorhombic box detected") box.lengths.convert_to_units(u.angstrom) for i, dim in enumerate(["x", "y", "z"]): data.write("{0:.6f} {1:.6f} {2}lo {2}hi\n".format( 0, box.lengths.value[i], dim)) else: warnings.warn("Non-orthorhombic box detected") box.lengths.convert_to_units(u.angstrom) box.angles.convert_to_units(u.radian) vectors = box.get_vectors() a, b, c = box.lengths alpha, beta, gamma = box.angles lx = a xy = b * np.cos(gamma) xz = c * np.cos(beta) ly = np.sqrt(b**2 - xy**2) yz = (b * c * np.cos(alpha) - xy * xz) / ly lz = np.sqrt(c**2 - xz**2 - yz**2) xhi = vectors[0][0] yhi = vectors[1][1] zhi = vectors[2][2] xy = vectors[1][0] xz = vectors[2][0] yz = vectors[2][1] xlo = u.unyt_array(0, xy.units) ylo = u.unyt_array(0, xy.units) zlo = u.unyt_array(0, xy.units) xlo_bound = xlo + u.unyt_array(np.min([0.0, xy, xz, xy + xz]), xy.units) xhi_bound = xhi + u.unyt_array(np.max([0.0, xy, xz, xy + xz]), xy.units) ylo_bound = ylo + u.unyt_array(np.min([0.0, yz]), xy.units) yhi_bound = yhi + u.unyt_array(np.max([0.0, yz]), xy.units) zlo_bound = zlo zhi_bound = zhi data.write("{0:.6f} {1:.6f} xlo xhi\n".format( xlo_bound.value, xhi_bound.value)) data.write("{0:.6f} {1:.6f} ylo yhi\n".format( ylo_bound.value, yhi_bound.value)) data.write("{0:.6f} {1:.6f} zlo zhi\n".format( zlo_bound.value, zhi_bound.value)) data.write("{0:.6f} {1:.6f} {2:.6f} xy xz yz\n".format( xy.value, xz.value, yz.value)) # TODO: Get a dictionary of indices and atom types if topology.is_typed(): # Write out mass data data.write("\nMasses\n\n") for atom_type in topology.atom_types: data.write("{:d}\t{:.6f}\t# {}\n".format( topology.atom_types.index(atom_type) + 1, atom_type.mass.in_units(u.g / u.mol).value, atom_type.name, )) # TODO: Modified cross-interactions # Pair coefficients data.write("\nPair Coeffs # lj\n\n") for idx, param in enumerate(topology.atom_types): data.write("{}\t{:.5f}\t{:.5f}\n".format( idx + 1, param.parameters["epsilon"].in_units( u.Unit("kcal/mol")).value, param.parameters["sigma"].in_units(u.angstrom).value, )) if topology.bonds: data.write("\nBond Coeffs\n\n") for idx, bond_type in enumerate(topology.bond_types): data.write("{}\t{:.5f}\t{:.5f}\n".format( idx + 1, bond_type.parameters["k"].in_units( u.Unit("kcal/mol/angstrom**2")).value / 2, bond_type.parameters["r_eq"].in_units( u.Unit("angstrom")).value, )) if topology.angles: data.write("\nAngle Coeffs\n\n") for idx, angle_type in enumerate(topology.angle_types): data.write("{}\t{:.5f}\t{:.5f}\n".format( idx + 1, angle_type.parameters["k"].in_units( u.Unit("kcal/mol/radian**2")).value / 2, angle_type.parameters["theta_eq"].in_units( u.Unit("degree")).value, )) # TODO: Write out multiple dihedral styles if topology.dihedrals: data.write("\nDihedral Coeffs\n\n") for idx, dihedral_type in enumerate(topology.dihedral_types): rbtorsion = PotentialTemplateLibrary( )["RyckaertBellemansTorsionPotential"] if (dihedral_type.expression == sympify( rbtorsion.expression) or dihedral_type.name == rbtorsion.name): dihedral_type = convert_ryckaert_to_opls(dihedral_type) data.write("{}\t{:.5f}\t{:5f}\t{:5f}\t{:.5f}\n".format( idx + 1, dihedral_type.parameters["k1"].in_units( u.Unit("kcal/mol")).value, dihedral_type.parameters["k2"].in_units( u.Unit("kcal/mol")).value, dihedral_type.parameters["k3"].in_units( u.Unit("kcal/mol")).value, dihedral_type.parameters["k4"].in_units( u.Unit("kcal/mol")).value, )) # Atom data data.write("\nAtoms\n\n") if atom_style == "atomic": atom_line = "{index:d}\t{type_index:d}\t{x:.6f}\t{y:.6f}\t{z:.6f}\n" elif atom_style == "charge": atom_line = "{index:d}\t{type_index:d}\t{charge:.6f}\t{x:.6f}\t{y:.6f}\t{z:.6f}\n" elif atom_style == "molecular": atom_line = "{index:d}\t{zero:d}\t{type_index:d}\t{x:.6f}\t{y:.6f}\t{z:.6f}\n" elif atom_style == "full": atom_line = "{index:d}\t{zero:d}\t{type_index:d}\t{charge:.6f}\t{x:.6f}\t{y:.6f}\t{z:.6f}\n" for i, site in enumerate(topology.sites): data.write( atom_line.format( index=topology.sites.index(site) + 1, type_index=topology.atom_types.index(site.atom_type) + 1, zero=0, charge=site.charge.to(u.elementary_charge).value, x=site.position[0].in_units(u.angstrom).value, y=site.position[1].in_units(u.angstrom).value, z=site.position[2].in_units(u.angstrom).value, )) if topology.bonds: data.write("\nBonds\n\n") for i, bond in enumerate(topology.bonds): data.write("{:d}\t{:d}\t{:d}\t{:d}\n".format( i + 1, topology.bond_types.index(bond.connection_type) + 1, topology.sites.index(bond.connection_members[0]) + 1, topology.sites.index(bond.connection_members[1]) + 1, )) if topology.angles: data.write("\nAngles\n\n") for i, angle in enumerate(topology.angles): data.write("{:d}\t{:d}\t{:d}\t{:d}\t{:d}\n".format( i + 1, topology.angle_types.index(angle.connection_type) + 1, topology.sites.index(angle.connection_members[0]) + 1, topology.sites.index(angle.connection_members[1]) + 1, topology.sites.index(angle.connection_members[2]) + 1, )) if topology.dihedrals: data.write("\nDihedrals\n\n") for i, dihedral in enumerate(topology.dihedrals): data.write("{:d}\t{:d}\t{:d}\t{:d}\t{:d}\t{:d}\n".format( i + 1, topology.dihedral_types.index(dihedral.connection_type) + 1, topology.sites.index(dihedral.connection_members[0]) + 1, topology.sites.index(dihedral.connection_members[1]) + 1, topology.sites.index(dihedral.connection_members[2]) + 1, topology.sites.index(dihedral.connection_members[3]) + 1, ))