Exemple #1
0
    def test_box(self, ethane):
        import gsd
        import gsd.hoomd

        lengths = [2.0, 3.0, 4.0]
        ethane.box = Box(lengths=[2.0, 3.0, 4.0])

        ethane.save(filename="ethane.gsd", forcefield_name="oplsaa")
        with gsd.hoomd.open("ethane.gsd", mode="rb") as f:
            frame = f[0]

        box_from_gsd = frame.configuration.box.astype(float)
        (lx, ly, lz) = ethane.box.lengths
        lx *= 10
        ly *= 10
        lz *= 10
        assert np.array_equal(box_from_gsd[:3], [lx, ly, lz])
        assert not np.any(box_from_gsd[3:])

        ethane.periodicity = (True, True, True)
        ethane.save(filename="ethane-periodicity.gsd",
                    forcefield_name="oplsaa")
        with gsd.hoomd.open("ethane-periodicity.gsd", mode="rb") as f:
            frame = f[0]
        box_from_gsd_periodic = frame.configuration.box.astype(float)
        assert np.array_equal(box_from_gsd, box_from_gsd_periodic)

        box = Box(lengths=np.array([2.0, 2.0, 2.0]), angles=[92, 104, 119])
        # check that providing a box to save overwrites compound.box
        ethane.save(filename="triclinic-box.gsd",
                    forcefield_name="oplsaa",
                    box=box)
        with gsd.hoomd.open("triclinic-box.gsd", mode="rb") as f:
            frame = f[0]
        lx, ly, lz, xy, xz, yz = frame.configuration.box

        a = lx
        b = np.sqrt(ly**2 + xy**2)
        c = np.sqrt(lz**2 + xz**2 + yz**2)

        assert np.isclose(np.cos(np.radians(92)),
                          (xy * xz + ly * yz) / (b * c))
        assert np.isclose(np.cos(np.radians(104)), xz / c)
        assert np.isclose(np.cos(np.radians(119)), xy / b)
 def test_fill_region_box(self, h2o):
     mybox = Box(lengths=[4, 4, 4], angles=[90.0, 90.0, 90.0])
     filled = mb.fill_region(h2o,
                             n_compounds=50,
                             region=mybox,
                             bounds=[[0, 0, 0, 4, 4, 4]])
     assert filled.n_particles == 50 * 3
     assert filled.n_bonds == 50 * 2
     assert np.min(filled.xyz[:, 0]) >= 0
     assert np.max(filled.xyz[:, 2]) <= 4
 def test_fill_box_compound_ratio(self, h2o, ethane):
     filled = mb.fill_box(
         compound=[h2o, ethane],
         density=800,
         compound_ratio=[2, 1],
         box=Box([2, 2, 2]),
     )
     n_ethane = len([c for c in filled.children if c.name == "Ethane"])
     n_water = len([c for c in filled.children if c.name == "H2O"])
     assert n_water / n_ethane == 2
 def test_fill_region(self, h2o):
     filled = mb.fill_region(
         h2o,
         n_compounds=50,
         region=Box(lengths=[2, 3, 3], angles=[90.0, 90.0, 90.0]),
         bounds=[[3, 2, 2, 5, 5, 5]],
     )
     assert filled.n_particles == 50 * 3
     assert filled.n_bonds == 50 * 2
     assert np.min(filled.xyz[:, 0]) >= 3
     assert np.min(filled.xyz[:, 1]) >= 2
     assert np.min(filled.xyz[:, 2]) >= 2
     assert np.max(filled.xyz[:, 0]) <= 5
     assert np.max(filled.xyz[:, 1]) <= 5
     assert np.max(filled.xyz[:, 2]) <= 5
 def test_fill_region_multiple_bounds(self, ethane, h2o):
     box1 = Box.from_mins_maxs_angles(mins=[2, 2, 2],
                                      maxs=[4, 4, 4],
                                      angles=[90.0, 90.0, 90.0])
     box2 = mb.Box.from_mins_maxs_angles(mins=[4, 2, 2],
                                         maxs=[6, 4, 4],
                                         angles=[90.0, 90.0, 90.0])
     filled = mb.fill_region(
         compound=[ethane, h2o],
         n_compounds=[2, 2],
         region=[box1, box2],
         bounds=[[2, 2, 2, 4, 4, 4], [4, 2, 2, 6, 4, 4]],
     )
     assert filled.n_particles == 2 * 8 + 2 * 3
     assert filled.n_bonds == 2 * 7 + 2 * 2
     assert np.max(filled.xyz[:16, 0]) < 4
     assert np.min(filled.xyz[16:, 0]) > 4
Exemple #6
0
def visualize(compound, show_bonds=True, show_ports=True, box=None,
              verbose=False):
    fig = plt.figure()
    ax = fig.add_subplot(111, projection='3d')

    if not box:
        boundingbox = compound.boundingbox
        if not any(boundingbox.lengths):
            boundingbox.lengths = np.ones(3) / 10
        longest_side = boundingbox.lengths.max()
        center = (boundingbox.maxs + boundingbox.mins) / 2
        mins = np.array([dim - longest_side/2 for dim in center])
        maxs = np.array([dim + longest_side/2 for dim in center])
        box = Box(mins=mins, maxs=maxs)
    ax.set_xlim([box.mins[0] - 0.125, box.maxs[0] + 0.125])
    ax.set_ylim([box.mins[1] - 0.125, box.maxs[1] + 0.125])
    ax.set_zlim([box.mins[2] - 0.125, box.maxs[2] + 0.125])
    volume = np.linalg.norm(box.lengths)

    colors = {'C': 'cyan', 'H': 'white', 'O': 'red', 'Si': 'yellow',
              'Unk': 'gray', 'OS': 'red', 'F': 'magenta'}
    size = {'C': 1250/volume, 'H': 400/volume, 'O': 1300/volume,
            'Si': 2000/volume, 'Unk': 1250/volume, 'OS': 1300/volume,
            'F': 1000/volume}

    n_particles = compound._n_particles(include_ports=show_ports)
    for i, particle in enumerate(compound.particles(include_ports=show_ports)):
        if verbose:
            print('Rendering particle {} of {}'.format(i+1, n_particles))
        pos = particle.pos
        if particle.port_particle:
            ax.scatter(pos[0], pos[1], pos[2], c='white', s=50/volume,
                       edgecolors='black')
        else:
            name = particle.name
            name = ''.join([j for j in name if not j.isdigit()])
            if name not in colors:
                name = 'Unk'
            ax.scatter(pos[0], pos[1], pos[2], c=colors[name],
                       s=size[name], edgecolors='black', alpha=0.8)
    if show_bonds:
        for bond in compound.bonds():
            ax.plot(*np.stack((bond[0].pos, bond[1].pos), axis=1), color='black',
                    alpha=0.75, marker='o', markersize=1/volume, linewidth=1.0)
    plt.show()
Exemple #7
0
    def test_units(self, ethane):
        import gsd
        import gsd.hoomd

        ref_distance = 3.5
        ref_energy = 0.066
        ref_mass = 12.011

        box = Box(lengths=[2.0, 3.0, 4.0], angles=[90.0, 90.0, 90.0])
        ethane.save(
            filename="ethane.gsd",
            forcefield_name="oplsaa",
            ref_distance=ref_distance,
            ref_energy=ref_energy,
            ref_mass=ref_mass,
            box=box,
        )
        with gsd.hoomd.open("ethane.gsd", mode="rb") as f:
            frame = f[0]

        box_from_gsd = frame.configuration.box.astype(float)
        assert np.array_equal(
            np.round(box_from_gsd[:3], decimals=5),
            np.round(np.asarray(box.lengths) * 10 / ref_distance, 5),
        )

        mass_dict = {"C": 12.011, "H": 1.008}
        masses = frame.particles.mass.astype(float)
        for mass, p in zip(masses, ethane.particles()):
            assert round(mass, 3) == round(mass_dict[p.name] / ref_mass, 3)

        charge_dict = {"C": -0.18, "H": 0.06}
        charges = frame.particles.charge.astype(float)
        e0 = 2.396452e-4
        charge_factor = (4.0 * np.pi * e0 * ref_distance * ref_energy)**0.5
        for charge, particle in zip(charges, ethane.particles()):
            reduced_charge = charge_dict[particle.name] / charge_factor
            assert round(charge, 3) == round(reduced_charge, 3)

        positions = frame.particles.position.astype(float)
        shift = positions[0] - (ethane[0].pos * 10 / ref_distance)
        shifted_xyz = (ethane.xyz * 10 / ref_distance) + shift
        assert np.array_equal(np.round(positions, decimals=4),
                              np.round(shifted_xyz, decimals=4))
 def test_write_temp_file(self, h2o):
     cwd = os.getcwd(
     )  # Must keep track of the temp dir that pytest creates
     filled = mb.fill_box(h2o,
                          n_compounds=10,
                          box=Box([4, 4, 4]),
                          temp_file="temp_file1.pdb")
     region = mb.fill_region(
         h2o,
         10,
         [[2, 2, 2, 4, 4, 4]],
         temp_file="temp_file2.pdb",
         bounds=[[2, 2, 2, 4, 4, 4]],
     )
     solvated = mb.solvate(filled,
                           h2o,
                           10,
                           box=[4, 4, 4],
                           temp_file="temp_file3.pdb")
     assert os.path.isfile(os.path.join(cwd, "temp_file1.pdb"))
     assert os.path.isfile(os.path.join(cwd, "temp_file2.pdb"))
     assert os.path.isfile(os.path.join(cwd, "temp_file3.pdb"))
Exemple #9
0
def _proto_to_mb(proto):
    """Given compound_pb2.Compound, create mb.Compound.

    Parameters
    ----------
    proto: compound_pb2.Compound()
    """
    if proto.element.symbol == "":
        elem = None
    else:
        elem = ele.element_from_symbol(proto.element.symbol)
    lengths = [proto.periodicity.x, proto.periodicity.y, proto.periodicity.z]
    if np.all(np.array(lengths) != 0):
        box = Box(lengths)
    else:
        box = None
    return Compound(
        name=proto.name,
        pos=[proto.pos.x, proto.pos.y, proto.pos.z],
        charge=proto.charge,
        box=box,
        element=elem,
    )
Exemple #10
0
def write_lammpsdata(structure, 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
    ----------
    structure : parmed.Structure
        ParmEd structure object
    filename : str
        Path of the output file
    atom_style: str
        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. Currently the following sections are supported (in
    addition to the header): *Masses*, *Nonbond Coeffs*, *Bond Coeffs*, *Angle
    Coeffs*, *Dihedral Coeffs*, *Atoms*, *Bonds*, *Angles*, *Dihedrals*

    """

    if atom_style not in ['atomic', 'charge', 'molecular', 'full']:
        raise ValueError('Atom style "{}" is invalid or is not currently supported'.format(atom_style))

    xyz = np.array([[atom.xx,atom.xy,atom.xz] for atom in structure.atoms])

    forcefield = True
    if structure[0].type == '':
        forcefield = False

    box = Box(lengths=np.array([structure.box[0], structure.box[1], structure.box[2]]))

    if forcefield:
        types = [atom.type for atom in structure.atoms]
    else:
        types = [atom.name for atom in structure.atoms]

    unique_types = list(set(types))
    unique_types.sort(key=natural_sort)

    charges = [atom.charge for atom in structure.atoms]

    bonds = [[bond.atom1.idx+1, bond.atom2.idx+1] for bond in structure.bonds]
    angles = [[angle.atom1.idx+1,
               angle.atom2.idx+1,
               angle.atom3.idx+1] for angle in structure.angles]
    dihedrals = [[dihedral.atom1.idx+1,
                  dihedral.atom2.idx+1,
                  dihedral.atom3.idx+1,
                  dihedral.atom4.idx+1] for dihedral in structure.rb_torsions]

    if bonds:
        if len(structure.bond_types) == 0:
            bond_types = np.ones(len(bonds),dtype=int)
        else:
            unique_bond_types = dict(enumerate(set([(round(bond.type.k,3),
                                                     round(bond.type.req,3)) for bond in structure.bonds])))
            unique_bond_types = OrderedDict([(y,x+1) for x,y in unique_bond_types.items()])
            bond_types = [unique_bond_types[(round(bond.type.k,3),
                                             round(bond.type.req,3))] for bond in structure.bonds]

    if angles:
        unique_angle_types = dict(enumerate(set([(round(angle.type.k,3),
                                                  round(angle.type.theteq,3)) for angle in structure.angles])))
        unique_angle_types = OrderedDict([(y,x+1) for x,y in unique_angle_types.items()])
        angle_types = [unique_angle_types[(round(angle.type.k,3),
                                           round(angle.type.theteq,3))] for angle in structure.angles]

    if dihedrals:
        unique_dihedral_types = dict(enumerate(set([(round(dihedral.type.c0,3),
                                                     round(dihedral.type.c1,3),
                                                     round(dihedral.type.c2,3),
                                                     round(dihedral.type.c3,3),
                                                     round(dihedral.type.c4,3),
                                                     round(dihedral.type.c5,3),
                                                     round(dihedral.type.scee,1),
                                                     round(dihedral.type.scnb,1)) for dihedral in structure.rb_torsions])))
        unique_dihedral_types = OrderedDict([(y,x+1) for x,y in unique_dihedral_types.items()])
        dihedral_types = [unique_dihedral_types[(round(dihedral.type.c0,3),
                                                 round(dihedral.type.c1,3),
                                                 round(dihedral.type.c2,3),
                                                 round(dihedral.type.c3,3),
                                                 round(dihedral.type.c4,3),
                                                 round(dihedral.type.c5,3),
                                                 round(dihedral.type.scee,1),
                                                 round(dihedral.type.scnb,1))] for dihedral in structure.rb_torsions]

    with open(filename, 'w') as data:
        data.write(filename+' - created by mBuild\n\n')
        data.write('{:d} atoms\n'.format(len(structure.atoms)))
        if atom_style in ['full', 'molecular']:
            data.write('{:d} bonds\n'.format(len(bonds)))
            data.write('{:d} angles\n'.format(len(angles)))
            data.write('{:d} dihedrals\n\n'.format(len(dihedrals)))

        data.write('{:d} atom types\n'.format(len(set(types))))
        if atom_style in ['full', 'molecular']:
            if bonds:
                data.write('{:d} bond types\n'.format(len(set(bond_types))))
            if angles:
                data.write('{:d} angle types\n'.format(len(set(angle_types))))
            if dihedrals:
                data.write('{:d} dihedral types\n'.format(len(set(dihedral_types))))

        data.write('\n')
        # Box data
        for i,dim in enumerate(['x','y','z']):
            data.write('{0:.6f} {1:.6f} {2}lo {2}hi\n'.format(box.mins[i],box.maxs[i],dim))

        # Mass data
        masses = [atom.mass for atom in structure.atoms]
        mass_dict = dict([(unique_types.index(atom_type)+1,mass) for atom_type,mass in zip(types,masses)])

        data.write('\nMasses\n\n')
        for atom_type,mass in mass_dict.items():
            data.write('{:d}\t{:.6f}\t# {}\n'.format(atom_type,mass,unique_types[atom_type-1]))

        if forcefield:
            # Pair coefficients
            epsilons = [atom.epsilon for atom in structure.atoms]
            sigmas = [atom.sigma for atom in structure.atoms]
            epsilon_dict = dict([(unique_types.index(atom_type)+1,epsilon) for atom_type,epsilon in zip(types,epsilons)])
            sigma_dict = dict([(unique_types.index(atom_type)+1,sigma) for atom_type,sigma in zip(types,sigmas)])
            data.write('\nPair Coeffs # lj\n\n')
            for idx,epsilon in epsilon_dict.items():
                data.write('{}\t{:.5f}\t{:.5f}\n'.format(idx,epsilon,sigma_dict[idx]))

            # Bond coefficients
            if bonds:
                data.write('\nBond Coeffs # harmonic\n\n')
                for params,idx in unique_bond_types.items():
                    data.write('{}\t{}\t{}\n'.format(idx,*params))

            # Angle coefficients
            if angles:
                data.write('\nAngle Coeffs # harmonic\n\n')
                for params,idx in unique_angle_types.items():
                    data.write('{}\t{}\t{:.5f}\n'.format(idx,*params))

            # Dihedral coefficients
            if dihedrals:
                data.write('\nDihedral Coeffs # opls\n\n')
                for params,idx in unique_dihedral_types.items():
                    opls_coeffs = RB_to_OPLS(params[0],
                                             params[1],
                                             params[2],
                                             params[3],
                                             params[4],
                                             params[5])
                    data.write('{}\t{:.5f}\t{:.5f}\t{:.5f}\t{:.5f}\n'.format(idx,*opls_coeffs))

        # 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,coords in enumerate(xyz):
            data.write(atom_line.format(
                index=i+1,type_index=unique_types.index(types[i])+1,
                zero=0,charge=charges[i],
                x=coords[0],y=coords[1],z=coords[2]))

        if atom_style in ['full', 'molecular']:
            # Bond data
            if bonds:
                data.write('\nBonds\n\n')
                for i,bond in enumerate(bonds):
                    data.write('{:d}\t{:d}\t{:d}\t{:d}\n'.format(
                        i+1,bond_types[i],bond[0],bond[1]))

            # Angle data
            if angles:
                data.write('\nAngles\n\n')
                for i,angle in enumerate(angles):
                    data.write('{:d}\t{:d}\t{:d}\t{:d}\t{:d}\n'.format(
                        i+1,angle_types[i],angle[0],angle[1],angle[2]))

            # Dihedral data
            if dihedrals:
                data.write('\nDihedrals\n\n')
                for i,dihedral in enumerate(dihedrals):
                    data.write('{:d}\t{:d}\t{:d}\t{:d}\t{:d}\t{:d}\n'.format(
                        i+1,dihedral_types[i],dihedral[0],
                        dihedral[1],dihedral[2],dihedral[3]))
Exemple #11
0
 def test_save_box(self, ethane):
     box = Box(lengths=[2.0, 2.0, 2.0], angles=[90.0, 90.0, 90.0])
     ethane.save(filename="ethane-box.gsd",
                 forcefield_name="oplsaa",
                 box=box)
 def test_fill_box_density_n_compounds(self, h2o):
     filled = mb.fill_box(h2o,
                          density=100,
                          box=Box([3.1042931, 3.1042931, 3.1042931]))
     assert filled.n_particles == 300
 def test_box_no_bound(self, ethane):
     box1 = Box(lengths=[2, 2, 2], angles=[90.0, 90.0, 90.0])
     mb.fill_region(compound=[ethane],
                    n_compounds=[2],
                    region=box1,
                    bounds=None)
 def test_fill_box(self, h2o):
     filled = mb.fill_box(h2o, n_compounds=50, box=Box([2, 2, 2]))
     assert filled.n_particles == 50 * 3
     assert filled.n_bonds == 50 * 2
     assert np.array_equal(filled.box.lengths, [2, 2, 2])
     assert np.array_equal(filled.box.angles, (90, 90, 90))
Exemple #15
0
 def test_save_triclinic_box_(self, ethane):
     box = Box(lengths=np.array([2.0, 2.0, 2.0]), angles=[60, 70, 80])
     ethane.save(filename="triclinic-box.gsd",
                 forcefield_name="oplsaa",
                 box=box)
Exemple #16
0
def write_lammpsdata(structure, 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
    ----------
    structure : parmed.Structure
        ParmEd structure object
    filename : str
        Path of the output file
    atom_style: str
        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. Currently the following sections are supported (in
    addition to the header): *Masses*, *Nonbond Coeffs*, *Bond Coeffs*, *Angle
    Coeffs*, *Dihedral Coeffs*, *Atoms*, *Bonds*, *Angles*, *Dihedrals*

    Some of this function has beed 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))

    xyz = np.array([[atom.xx,atom.xy,atom.xz] for atom in structure.atoms])

    forcefield = True
    if structure[0].type == '':
        forcefield = False

    # Internally use nm
    box = Box(lengths=np.array([0.1 * val for val in structure.box[0:3]]),
              angles=structure.box[3:6])

    if forcefield:
        types = [atom.type for atom in structure.atoms]
    else:
        types = [atom.name for atom in structure.atoms]

    unique_types = list(set(types))
    unique_types.sort(key=natural_sort)

    charges = [atom.charge for atom in structure.atoms]

    bonds = [[bond.atom1.idx+1, bond.atom2.idx+1] for bond in structure.bonds]
    angles = [[angle.atom1.idx+1,
               angle.atom2.idx+1,
               angle.atom3.idx+1] for angle in structure.angles]
    dihedrals = [[dihedral.atom1.idx+1,
                  dihedral.atom2.idx+1,
                  dihedral.atom3.idx+1,
                  dihedral.atom4.idx+1] for dihedral in structure.rb_torsions]

    if bonds:
        if len(structure.bond_types) == 0:
            bond_types = np.ones(len(bonds),dtype=int)
        else:
            unique_bond_types = dict(enumerate(set([(round(bond.type.k,3),
                                                     round(bond.type.req,3)) for bond in structure.bonds])))
            unique_bond_types = OrderedDict([(y,x+1) for x,y in unique_bond_types.items()])
            bond_types = [unique_bond_types[(round(bond.type.k,3),
                                             round(bond.type.req,3))] for bond in structure.bonds]

    if angles:
        unique_angle_types = dict(enumerate(set([(round(angle.type.k,3),
                                                  round(angle.type.theteq,3)) for angle in structure.angles])))
        unique_angle_types = OrderedDict([(y,x+1) for x,y in unique_angle_types.items()])
        angle_types = [unique_angle_types[(round(angle.type.k,3),
                                           round(angle.type.theteq,3))] for angle in structure.angles]

    if dihedrals:
        unique_dihedral_types = dict(enumerate(set([(round(dihedral.type.c0,3),
                                                     round(dihedral.type.c1,3),
                                                     round(dihedral.type.c2,3),
                                                     round(dihedral.type.c3,3),
                                                     round(dihedral.type.c4,3),
                                                     round(dihedral.type.c5,3),
                                                     round(dihedral.type.scee,1),
                                                     round(dihedral.type.scnb,1)) for dihedral in structure.rb_torsions])))
        unique_dihedral_types = OrderedDict([(y,x+1) for x,y in unique_dihedral_types.items()])
        dihedral_types = [unique_dihedral_types[(round(dihedral.type.c0,3),
                                                 round(dihedral.type.c1,3),
                                                 round(dihedral.type.c2,3),
                                                 round(dihedral.type.c3,3),
                                                 round(dihedral.type.c4,3),
                                                 round(dihedral.type.c5,3),
                                                 round(dihedral.type.scee,1),
                                                 round(dihedral.type.scnb,1))] for dihedral in structure.rb_torsions]

    with open(filename, 'w') as data:
        data.write(filename+' - created by mBuild\n\n')
        data.write('{:d} atoms\n'.format(len(structure.atoms)))
        if atom_style in ['full', 'molecular']:
            data.write('{:d} bonds\n'.format(len(bonds)))
            data.write('{:d} angles\n'.format(len(angles)))
            data.write('{:d} dihedrals\n\n'.format(len(dihedrals)))

        data.write('{:d} atom types\n'.format(len(set(types))))
        if atom_style in ['full', 'molecular']:
            if bonds:
                data.write('{:d} bond types\n'.format(len(set(bond_types))))
            if angles:
                data.write('{:d} angle types\n'.format(len(set(angle_types))))
            if dihedrals:
                data.write('{:d} dihedral types\n'.format(len(set(dihedral_types))))

        data.write('\n')
        # Box data
        if np.allclose(box.angles, np.array([90, 90, 90])):
            for i,dim in enumerate(['x','y','z']):
                data.write('{0:.6f} {1:.6f} {2}lo {2}hi\n'.format(
                    10.0 * box.mins[i],
                    10.0 * box.maxs[i],
                    dim))
        else:
            a, b, c = 10.0 * box.lengths
            alpha, beta, gamma = np.radians(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)

            xlo, ylo, zlo = 10.0 * box.mins
            xhi = xlo + lx
            yhi = ylo + ly
            zhi = zlo + lz

            xlo_bound = xlo + np.min([0.0, xy, xz, xy+xz])
            xhi_bound = xhi + np.max([0.0, xy, xz, xy+xz])
            ylo_bound = ylo + np.min([0.0, yz])
            yhi_bound = yhi + np.max([0.0, yz])
            zlo_bound = zlo
            zhi_bound = zhi

            data.write('{0} {1} {2}\n'.format(xlo_bound, xhi_bound, xy))
            data.write('{0} {1} {2}\n'.format(ylo_bound, yhi_bound, xz))
            data.write('{0} {1} {2}\n'.format(zlo_bound, zhi_bound, yz))

        # Mass data
        masses = [atom.mass for atom in structure.atoms]
        mass_dict = dict([(unique_types.index(atom_type)+1,mass) for atom_type,mass in zip(types,masses)])

        data.write('\nMasses\n\n')
        for atom_type,mass in mass_dict.items():
            data.write('{:d}\t{:.6f}\t# {}\n'.format(atom_type,mass,unique_types[atom_type-1]))

        if forcefield:
            # Pair coefficients
            epsilons = [atom.epsilon for atom in structure.atoms]
            sigmas = [atom.sigma for atom in structure.atoms]
            epsilon_dict = dict([(unique_types.index(atom_type)+1,epsilon) for atom_type,epsilon in zip(types,epsilons)])
            sigma_dict = dict([(unique_types.index(atom_type)+1,sigma) for atom_type,sigma in zip(types,sigmas)])
            data.write('\nPair Coeffs # lj\n\n')
            for idx,epsilon in epsilon_dict.items():
                data.write('{}\t{:.5f}\t{:.5f}\n'.format(idx,epsilon,sigma_dict[idx]))

            # Bond coefficients
            if bonds:
                data.write('\nBond Coeffs # harmonic\n\n')
                for params,idx in unique_bond_types.items():
                    data.write('{}\t{}\t{}\n'.format(idx,*params))

            # Angle coefficients
            if angles:
                data.write('\nAngle Coeffs # harmonic\n\n')
                for params,idx in unique_angle_types.items():
                    data.write('{}\t{}\t{:.5f}\n'.format(idx,*params))

            # Dihedral coefficients
            if dihedrals:
                data.write('\nDihedral Coeffs # opls\n\n')
                for params,idx in unique_dihedral_types.items():
                    opls_coeffs = RB_to_OPLS(params[0],
                                             params[1],
                                             params[2],
                                             params[3],
                                             params[4],
                                             params[5])
                    data.write('{}\t{:.5f}\t{:.5f}\t{:.5f}\t{:.5f}\n'.format(idx,*opls_coeffs))

        # 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,coords in enumerate(xyz):
            data.write(atom_line.format(
                index=i+1,type_index=unique_types.index(types[i])+1,
                zero=0,charge=charges[i],
                x=coords[0],y=coords[1],z=coords[2]))

        if atom_style in ['full', 'molecular']:
            # Bond data
            if bonds:
                data.write('\nBonds\n\n')
                for i,bond in enumerate(bonds):
                    data.write('{:d}\t{:d}\t{:d}\t{:d}\n'.format(
                        i+1,bond_types[i],bond[0],bond[1]))

            # Angle data
            if angles:
                data.write('\nAngles\n\n')
                for i,angle in enumerate(angles):
                    data.write('{:d}\t{:d}\t{:d}\t{:d}\t{:d}\n'.format(
                        i+1,angle_types[i],angle[0],angle[1],angle[2]))

            # Dihedral data
            if dihedrals:
                data.write('\nDihedrals\n\n')
                for i,dihedral in enumerate(dihedrals):
                    data.write('{:d}\t{:d}\t{:d}\t{:d}\t{:d}\t{:d}\n'.format(
                        i+1,dihedral_types[i],dihedral[0],
                        dihedral[1],dihedral[2],dihedral[3]))
Exemple #17
0
    def __init__(self, tile, n_tiles, name=None):
        super(TiledCompound, self).__init__()

        n_tiles = np.asarray(n_tiles)
        periodicity = np.asarray(tile.periodicity)
        if not np.all(n_tiles > 0):
            raise ValueError("Number of tiles must be positive.")

        if tile.box is None:
            tile.box = tile.get_boundingbox()
        # Check that the tile is periodic in the requested dimensions.
        if not np.all(np.logical_or((n_tiles == 1), periodicity)):
            raise ValueError(
                "Tile not periodic in at least one of the specified dimensions."
            )

        if name is None:
            name = tile.name + "-".join(str(d) for d in n_tiles)
        self.name = name
        self.periodicity = tile.periodicity
        self.box = Box(np.array(tile.box.lengths) * n_tiles,
                       angles=tile.box.angles)

        if all(n_tiles == 1):
            self._add_tile(tile, (0, 0, 0))
            self._hoist_ports(tile)
            return  # Don't waste time copying and checking bonds.

        # For every tile, assign temporary ID's to particles which are internal
        # to that tile. E.g., when replicating a tile with 1800 particles, every
        # tile will contain particles with ID's from 0-1799. These ID's are used
        # below to fix bonds crossing periodic boundary conditions where a new
        # tile has been placed.
        for idx, particle in enumerate(tile.particles(include_ports=True)):
            particle.index = idx

        # Replicate and place periodic tiles.
        # -----------------------------------
        for ijk in it.product(*[range(i) for i in n_tiles]):
            new_tile = clone(tile)
            new_tile.translate(np.multiply(ijk, np.asarray(tile.box.lengths)))

            self._add_tile(new_tile, ijk)
            self._hoist_ports(new_tile)

        # Fix bonds across periodic boundaries.
        # -------------------------------------
        # Cutoff for long bonds is half the shortest periodic distance.
        threshold_calc = [np.inf, np.inf, np.inf]
        for i, truthy in enumerate(tile.periodicity):
            if truthy:
                threshold_calc[i] = tile.box.lengths[i]
            else:
                continue
        dist_thresh = np.min(threshold_calc) / 2

        # Create the bounds for the periodicKDtree, non-periodic dimensions are 0
        bounds = [0, 0, 0]
        length_array = np.asarray(tile.box.lengths)
        for i, dim in enumerate(bounds):
            if tile.periodicity[i]:
                bounds[i] = self.box.lengths[i]
            else:
                continue
        # Bonds that were periodic in the original tile.
        periodic_bonds = set()
        for particle1, particle2 in tile.bonds():

            if np.linalg.norm(particle1.pos - particle2.pos) > dist_thresh:
                periodic_bonds.add((particle1.index, particle2.index))

        # Build a periodic kdtree of all particle positions.
        self.particle_kdtree = PeriodicCKDTree(data=self.xyz, bounds=bounds)
        all_particles = np.asarray(list(self.particles(include_ports=False)))

        # Store bonds to remove/add since we'll be iterating over all bonds.
        bonds_to_remove = set()
        bonds_to_add = set()
        for particle1, particle2 in self.bonds():
            if (
                    particle1.index,
                    particle2.index,
            ) not in periodic_bonds and (
                    particle2.index,
                    particle1.index,
            ) not in periodic_bonds:
                continue

            dist = self.min_periodic_distance(particle1.pos, particle2.pos)
            if dist > dist_thresh:
                bonds_to_remove.add((particle1, particle2))
                image2 = self._find_particle_image(particle1, particle2,
                                                   all_particles)
                image1 = self._find_particle_image(particle2, particle1,
                                                   all_particles)

                if (image2, particle1) not in bonds_to_add:
                    bonds_to_add.add((particle1, image2))
                if (image1, particle2) not in bonds_to_add:
                    bonds_to_add.add((particle2, image1))

        for bond in bonds_to_remove:
            self.remove_bond(bond)

        for bond in bonds_to_add:
            self.add_bond(bond)

        # Clean up temporary data.
        for particle in self._particles(include_ports=True):
            particle.index = None
        del self.particle_kdtree
Exemple #18
0
def write_lammpsdata(structure, filename, atom_style='full', 
                    unit_style='real',
                    detect_forcefield_style=True, nbfix_in_data_file=True,
                    use_urey_bradleys=False,
                    use_rb_torsions=True, use_dihedrals=False):
    """Output a LAMMPS data file.
    
    Outputs a LAMMPS data file in the 'full' atom style format. Default units are 
    'real' units. See http://lammps.sandia.gov/doc/atom_style.html for
    more information on atom styles.

    Parameters
    ----------
    structure : parmed.Structure
        ParmEd structure object
    filename : str
        Path of the output file
    atom_style: str
        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.
    unit_style: str
        Defines to unit style to be save in a LAMMPS data file.  Defaults to 'real' units.
        Current styles are supported: 'real', 'lj'
        see https://lammps.sandia.gov/doc/99/units.html for more information
        on unit styles
    detect_forcefield_style: boolean
        If True, format lammpsdata parameters based on the contents of 
        the parmed Structure
    use_urey_bradleys: boolean
        If True, will treat angles as CHARMM-style angles with urey bradley terms
        while looking for `structure.urey_bradleys`
    use_rb_torsions:
        If True, will treat dihedrals OPLS-style torsions while looking for
        `structure.rb_torsions`
    use_dihedrals:
        If True, will treat dihedrals as CHARMM-style dihedrals while looking for 
        `structure.dihedrals`

    Notes
    -----
    See http://lammps.sandia.gov/doc/2001/data_format.html for a full description
    of the LAMMPS data format. Currently the following sections are supported (in
    addition to the header): *Masses*, *Nonbond Coeffs*, *Bond Coeffs*, *Angle
    Coeffs*, *Dihedral Coeffs*, *Atoms*, *Bonds*, *Angles*, *Dihedrals*, *Impropers*
    OPLS and CHARMM forcefield styles are supported, AMBER forcefield styles are NOT

    Some of this function has beed 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))

    # Check if structure is paramterized
    if unit_style == 'lj':
        if any([atom.sigma for atom in structure.atoms]) is None:
           raise ValueError('LJ units specified but one or more atoms has undefined LJ parameters.') 

    xyz = np.array([[atom.xx,atom.xy,atom.xz] for atom in structure.atoms])

    forcefield = True
    if structure[0].type == '':
        forcefield = False

    """
    Note:
    -----
    unique_types : a sorted list of unique atomtypes for all atoms in the structure.
        Defined by:
            atomtype : atom.type
    unique_bond_types: an enumarated OrderedDict of unique bond types for all bonds in the structure.
        Defined by bond parameters and component atomtypes, in order:
            k : bond.type.k
            req : bond.type.req
            atomtypes : sorted((bond.atom1.type, bond.atom2.type))
    unique_angle_types: an enumerated OrderedDict of unique angle types for all angles in the structure.
        Defined by angle parameters and component atomtypes, in order:
            k : angle.type.k
            theteq : angle.type.theteq
            vertex atomtype: angle.atom2.type
            atomtypes: sorted((bond.atom1.type, bond.atom3.type))
    unique_dihedral_types: an enumerated OrderedDict of unique dihedrals type for all dihedrals in the structure.
        Defined by dihedral parameters and component atomtypes, in order:
            c0 : dihedral.type.c0
            c1 : dihedral.type.c1
            c2 : dihedral.type.c2
            c3 : dihedral.type.c3
            c4 : dihedral.type.c4
            c5 : dihedral.type.c5
            scee : dihedral.type.scee
            scnb : dihedral.type.scnb
            atomtype 1 : dihedral.atom1.type
            atomtype 2 : dihedral.atom2.type
            atomtype 3 : dihedral.atom3.type
            atomtype 4 : dihedral.atom4.type
    """
    if forcefield:
        types = [atom.type for atom in structure.atoms]
    else:
        types = [atom.name for atom in structure.atoms]

    unique_types = list(set(types))
    unique_types.sort(key=natural_sort)

    charges = np.array([atom.charge for atom in structure.atoms])

    # Convert coordinates to LJ units
    if unit_style == 'lj':
        # Get sigma, mass, and epsilon conversions by finding maximum of each
        sigma_conversion_factor = np.max([atom.sigma for atom in structure.atoms])
        epsilon_conversion_factor = np.max([atom.epsilon for atom in structure.atoms])
        mass_conversion_factor = np.max([atom.mass for atom in structure.atoms])

        xyz = xyz / sigma_conversion_factor
        charges = (charges*1.6021e-19) / np.sqrt(4*np.pi*(sigma_conversion_factor*1e-10)*
          (epsilon_conversion_factor*4184)*epsilon_0)
        charges[np.isinf(charges)] = 0 
        # TODO: FIX CHARGE UNIT CONVERSION
    else:
        sigma_conversion_factor = 1
        epsilon_conversion_factor = 1
        mass_conversion_factor = 1

    # Internally use nm
    box = Box(lengths=np.array([0.1 * val for val in structure.box[0:3]]),
              angles=structure.box[3:6])
    # Divide by conversion factor
    box.maxs /= sigma_conversion_factor
    
    # Lammps syntax depends on the functional form
    # Infer functional form based on the properties of the structure
    if detect_forcefield_style:
        # Check angles
        if len(structure.urey_bradleys) > 0 :
            print("Urey bradley terms detected, will use angle_style charmm")
            use_urey_bradleys = True
        else:
            print("No urey bradley terms detected, will use angle_style harmonic")
            use_urey_bradleys = False

        # Check dihedrals
        if len(structure.rb_torsions) > 0:
            print("RB Torsions detected, will use dihedral_style opls")
            use_rb_torsions = True
        else:
            use_rb_torsions = False
        if len(structure.dihedrals) > 0:
            print("Charmm dihedrals detected, will use dihedral_style charmm")
            use_dihedrals = True
        else:
            use_dihedrals = False
    if use_rb_torsions and use_dihedrals:
        raise ValueError("Multiple dihedral styles detected, check your "
                         "Forcefield XML and structure")

    # Check impropers
    for dihedral in structure.dihedrals:
        if dihedral.improper:
            raise ValueError("Amber-style impropers are currently not supported")

    bonds = [[bond.atom1.idx+1, bond.atom2.idx+1] for bond in structure.bonds]
    angles = [[angle.atom1.idx+1,
               angle.atom2.idx+1,
               angle.atom3.idx+1] for angle in structure.angles]
    if use_rb_torsions:
        dihedrals = [[dihedral.atom1.idx+1,
                      dihedral.atom2.idx+1,
                      dihedral.atom3.idx+1,
                      dihedral.atom4.idx+1] for dihedral in structure.rb_torsions]
    elif use_dihedrals:
        dihedrals = [[dihedral.atom1.idx+1,
                      dihedral.atom2.idx+1,
                      dihedral.atom3.idx+1,
                      dihedral.atom4.idx+1] for dihedral in structure.dihedrals]
    else:
        dihedrals = []
    impropers = [[improper.atom1.idx+1,
                  improper.atom2.idx+1,
                  improper.atom3.idx+1,
                  improper.atom4.idx+1] for improper in structure.impropers]


    if bonds :
        if len(structure.bond_types) == 0:
            bond_types = np.ones(len(bonds),dtype=int)
        else:
            bond_types, unique_bond_types = _get_bond_types(structure,
                    bonds, sigma_conversion_factor,
                    epsilon_conversion_factor)

    if angles:
        angle_types, unique_angle_types = _get_angle_types(structure,
                use_urey_bradleys, sigma_conversion_factor,
                epsilon_conversion_factor)

    if dihedrals:
        dihedral_types, unique_dihedral_types = _get_dihedral_types(
                structure, use_rb_torsions, use_dihedrals,
                epsilon_conversion_factor)
            
    if impropers:
        improper_types, unique_improper_types = _get_impropers(structure,
                epsilon_conversion_factor)
    

    with open(filename, 'w') as data:
        data.write(filename+' - created by mBuild; units = {}\n\n'.format(
            unit_style))
        data.write('{:d} atoms\n'.format(len(structure.atoms)))
        if atom_style in ['full', 'molecular']:
            data.write('{:d} bonds\n'.format(len(bonds)))
            data.write('{:d} angles\n'.format(len(angles)))
            data.write('{:d} dihedrals\n'.format(len(dihedrals)))
            data.write('{:d} impropers\n\n'.format(len(impropers)))

        data.write('{:d} atom types\n'.format(len(set(types))))
        if atom_style in ['full', 'molecular']:
            if bonds:
                data.write('{:d} bond types\n'.format(len(set(bond_types))))
            if angles:
                data.write('{:d} angle types\n'.format(len(set(angle_types))))
            if dihedrals:
                data.write('{:d} dihedral types\n'.format(len(set(dihedral_types))))
            if impropers:
                data.write('{:d} improper types\n'.format(len(set(improper_types))))


        data.write('\n')
        # Box data
        if np.allclose(box.angles, np.array([90, 90, 90])):
            for i,dim in enumerate(['x','y','z']):
                data.write('{0:.6f} {1:.6f} {2}lo {2}hi\n'.format(
                    10.0 * box.mins[i],
                    10.0 * box.maxs[i],
                    dim))
        else:
            a, b, c = 10.0 * box.lengths
            alpha, beta, gamma = np.radians(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)

            xlo, ylo, zlo = 10.0 * box.mins
            xhi = xlo + lx
            yhi = ylo + ly
            zhi = zlo + lz

            xlo_bound = xlo + np.min([0.0, xy, xz, xy+xz])
            xhi_bound = xhi + np.max([0.0, xy, xz, xy+xz])
            ylo_bound = ylo + np.min([0.0, yz])
            yhi_bound = yhi + np.max([0.0, yz])
            zlo_bound = zlo
            zhi_bound = zhi

            data.write('{0:.6f} {1:.6f} xlo xhi\n'.format(
                xlo_bound, xhi_bound))
            data.write('{0:.6f} {1:.6f} ylo yhi\n'.format(
                ylo_bound, yhi_bound))
            data.write('{0:.6f} {1:.6f} zlo zhi\n'.format(
                zlo_bound, zhi_bound))
            data.write('{0:.6f} {1:.6f} {2:6f} xy xz yz\n'.format(
                xy, xz, yz))

        # Mass data
        masses = np.array([atom.mass for atom in structure.atoms]) / mass_conversion_factor
        mass_dict = dict([(unique_types.index(atom_type)+1,mass) for atom_type,mass in zip(types,masses)])

        data.write('\nMasses\n\n')
        for atom_type,mass in mass_dict.items():
            data.write('{:d}\t{:.6f}\t# {}\n'.format(atom_type,mass,unique_types[atom_type-1]))

        if forcefield:
            epsilons = np.array([atom.epsilon for atom in structure.atoms]) / epsilon_conversion_factor
            sigmas = np.array([atom.sigma for atom in structure.atoms]) / sigma_conversion_factor
            forcefields = [atom.type for atom in structure.atoms]
            epsilon_dict = dict([(unique_types.index(atom_type)+1,epsilon) for atom_type,epsilon in zip(types,epsilons)])
            sigma_dict = dict([(unique_types.index(atom_type)+1,sigma) for atom_type,sigma in zip(types,sigmas)])
            forcefield_dict = dict([(unique_types.index(atom_type)+1,forcefield) for atom_type,forcefield in zip(types,forcefields)])
            


            # Modified cross-interactions
            if structure.has_NBFIX():
                params = ParameterSet.from_structure(structure)
                # Sort keys (maybe they should be sorted in ParmEd)
                new_nbfix_types = OrderedDict()
                for key, val in params.nbfix_types.items():
                    sorted_key = tuple(sorted(key))
                    if sorted_key in new_nbfix_types:
                        warn('Sorted key matches an existing key')
                        if new_nbfix_types[sorted_key]:
                            warn('nbfixes are not symmetric, overwriting old nbfix')
                    new_nbfix_types[sorted_key] = params.nbfix_types[key]
                params.nbfix_types = new_nbfix_types
                warn('Explicitly writing cross interactions using mixing rule: {}'.format(
                    structure.combining_rule))
                coeffs = OrderedDict()
                for combo in it.combinations_with_replacement(unique_types, 2):
                    # Attempt to find pair coeffis in nbfixes
                    if combo in params.nbfix_types:
                        type1 = unique_types.index(combo[0])+1
                        type2 = unique_types.index(combo[1])+1
                        rmin = params.nbfix_types[combo][0] # Angstrom OR lj units
                        epsilon = params.nbfix_types[combo][1] # kcal OR lj units
                        sigma = rmin/2**(1/6)
                        coeffs[(type1, type2)] = (round(sigma, 8), round(epsilon, 8))
                    else:
                        type1 = unique_types.index(combo[0]) + 1
                        type2 = unique_types.index(combo[1]) + 1
                        # Might not be necessary to be this explicit
                        if type1 == type2:
                            sigma = sigma_dict[type1]
                            epsilon = epsilon_dict[type1]
                        else:
                            if structure.combining_rule == 'lorentz':
                                sigma = (sigma_dict[type1]+sigma_dict[type2])*0.5
                            elif structure.combining_rule == 'geometric':
                                sigma = (sigma_dict[type1]*sigma_dict[type2])**0.5
                            else:
                                raise ValueError('Only lorentz and geometric combining rules are supported')
                            epsilon = (epsilon_dict[type1]*epsilon_dict[type2])**0.5
                        coeffs[(type1, type2)] = (round(sigma, 8), round(epsilon, 8))
                if nbfix_in_data_file:
                    data.write('\nPairIJ Coeffs # modified lj\n')
                    data.write('# type1 type2 \tepsilon (kcal/mol) \tsigma (Angstrom)\n')
                    for (type1, type2), (sigma, epsilon) in coeffs.items():
                        data.write('{0} \t{1} \t{2} \t\t{3}\t\t# {4}\t{5}\n'.format(
                            type1, type2, epsilon, sigma, forcefield_dict[type1], forcefield_dict[type2]))
                else:
                    data.write('\nPair Coeffs # lj\n\n')
                    for idx,epsilon in epsilon_dict.items():
                        data.write('{}\t{:.5f}\t{:.5f}\n'.format(idx,epsilon,sigma_dict[idx]))
                    print('Copy these commands into your input script:\n')
                    print('# type1 type2 \tepsilon (kcal/mol) \tsigma (Angstrom)\n')
                    for (type1, type2), (sigma, epsilon) in coeffs.items():
                        print('pair_coeff\t{0} \t{1} \t{2} \t\t{3} \t\t# {4} \t{5}'.format(
                            type1, type2, epsilon, sigma,forcefield_dict[type1],forcefield_dict[type2]))

            # Pair coefficients
            else:
                data.write('\nPair Coeffs # lj \n')
                if unit_style == 'real':
                    data.write('#\tepsilon (kcal/mol)\t\tsigma (Angstrom)\n')
                elif unit_style == 'lj':
                    data.write('#\treduced_epsilon \t\treduced_sigma \n')
                for idx,epsilon in epsilon_dict.items():
                    data.write('{}\t{:.5f}\t\t{:.5f}\t\t# {}\n'.format(idx,epsilon,sigma_dict[idx],forcefield_dict[idx]))

            # Bond coefficients
            if bonds:
                data.write('\nBond Coeffs # harmonic\n')
                if unit_style == 'real':
                    data.write('#\tk(kcal/mol/angstrom^2)\t\treq(angstrom)\n')
                elif unit_style == 'lj':
                    data.write('#\treduced_k\t\treduced_req\n')
                for params,idx in unique_bond_types.items():
                    data.write('{}\t{}\t\t{}\t\t# {}\t{}\n'.format(idx,params[0],params[1],params[2][0],params[2][1]))

            # Angle coefficients
            if angles:
                if use_urey_bradleys:
                    data.write('\nAngle Coeffs # charmm\n')
                    data.write('#\tk(kcal/mol/rad^2)\t\ttheteq(deg)\tk(kcal/mol/angstrom^2)\treq(angstrom)\n')
                    for params,idx in unique_angle_types.items():
                        data.write('{}\t{}\t{:.5f}\t{:.5f}\t{:.5f}\n'.format(idx,*params))

                else:
                    data.write('\nAngle Coeffs # harmonic\n')
                    data.write('#\treduced_k\t\ttheteq(deg)\n')
                    for params,idx in unique_angle_types.items():
                        data.write('{}\t{}\t\t{:.5f}\t# {}\t{}\t{}\n'.format(idx,params[0],params[1],
                                                                             params[3][0],params[2],params[3][1]))

            # Dihedral coefficients
            if dihedrals:
                if use_rb_torsions:
                    data.write('\nDihedral Coeffs # opls\n')
                    if unit_style == 'real':
                        data.write('#\tf1(kcal/mol)\tf2(kcal/mol)\tf3(kcal/mol)\tf4(kcal/mol)\n')
                    elif unit_style == 'lj':
                        data.write('#\tf1\tf2\tf3\tf4 (all lj reduced units)\n')
                    for params,idx in unique_dihedral_types.items():
                        opls_coeffs = RB_to_OPLS(params[0],
                                                 params[1],
                                                 params[2],
                                                 params[3],
                                                 params[4],
                                                 params[5])
                        data.write('{}\t{:.5f}\t{:.5f}\t\t{:.5f}\t\t{:.5f}\t# {}\t{}\t{}\t{}\n'.format(idx,opls_coeffs[0],
                                                                                                       opls_coeffs[1],
                                                                                                       opls_coeffs[2],
                                                                                                       opls_coeffs[3],
                                                                                                       params[8],params[9],
                                                                                                       params[10],params[11]))
                elif use_dihedrals:
                    data.write('\nDihedral Coeffs # charmm\n')
                    data.write('#k, n, phi, weight\n')
                    for params, idx in unique_dihedral_types.items():
                        data.write('{}\t{:.5f}\t{:d}\t{:d}\t{:.5f}\t# {}\t{}\t{}\t{}\n'.format(idx, params[0],
                                                                                                params[1], params[2],
                                                                                                params[3], params[6],
                                                                                                params[7], params[8], params[9]))

            # Improper coefficients
            if impropers:
                data.write('\nImproper Coeffs # harmonic\n')
                data.write('#k, phi\n')
                for params,idx in unique_improper_types.items():
                    data.write('{}\t{:.5f}\t{:.5f}\t# {}\t{}\t{}\t{}\n'.format(idx, params[0],
                                                                                params[1], params[2],
                                                                                params[3], params[4],
                                                                                params[5]))

        # 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':
            if unit_style == 'real':
                atom_line = '{index:d}\t{type_index:d}\t{charge:.6f}\t{x:.6f}\t{y:.6f}\t{z:.6f}\n'
            elif unit_style == 'lj':
                atom_line = '{index:d}\t{type_index:d}\t{charge:.4ef}\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':
            if unit_style == 'real':
                atom_line ='{index:d}\t{zero:d}\t{type_index:d}\t{charge:.6f}\t{x:.6f}\t{y:.6f}\t{z:.6f}\n'
            elif unit_style == 'lj':
                atom_line ='{index:d}\t{zero:d}\t{type_index:d}\t{charge:.4e}\t{x:.6f}\t{y:.6f}\t{z:.6f}\n'

        for i,coords in enumerate(xyz):
            data.write(atom_line.format(
                index=i+1,type_index=unique_types.index(types[i])+1,
                zero=structure.atoms[i].residue.idx,charge=charges[i],
                x=coords[0],y=coords[1],z=coords[2]))

        if atom_style in ['full', 'molecular']:
            # Bond data
            if bonds:
                data.write('\nBonds\n\n')
                for i,bond in enumerate(bonds):
                    data.write('{:d}\t{:d}\t{:d}\t{:d}\n'.format(
                        i+1,bond_types[i],bond[0],bond[1]))

            # Angle data
            if angles:
                data.write('\nAngles\n\n')
                for i,angle in enumerate(angles):
                    data.write('{:d}\t{:d}\t{:d}\t{:d}\t{:d}\n'.format(
                        i+1,angle_types[i],angle[0],angle[1],angle[2]))

            # Dihedral data
            if dihedrals:
                data.write('\nDihedrals\n\n')
                for i,dihedral in enumerate(dihedrals):
                    data.write('{:d}\t{:d}\t{:d}\t{:d}\t{:d}\t{:d}\n'.format(
                        i+1,dihedral_types[i],dihedral[0],
                        dihedral[1],dihedral[2],dihedral[3]))
            # Dihedral data
            if impropers:
                data.write('\nImpropers\n\n')
                for i,improper in enumerate(impropers):
                    data.write('{:d}\t{:d}\t{:d}\t{:d}\t{:d}\t{:d}\n'.format(
                        i+1,improper_types[i],improper[2],
                        improper[1],improper[0],improper[3]))
Exemple #19
0
def write_lammpsdata(
    structure,
    filename,
    atom_style="full",
    unit_style="real",
    mins=None,
    maxs=None,
    pair_coeff_label=None,
    detect_forcefield_style=True,
    nbfix_in_data_file=True,
    use_urey_bradleys=False,
    use_rb_torsions=True,
    use_dihedrals=False,
    zero_dihedral_weighting_factor=False,
    moleculeID_offset=1,
):
    """Output a LAMMPS data file.

    Outputs a LAMMPS data file in the 'full' atom style format. Default units
    are 'real' units. See http://lammps.sandia.gov/doc/atom_style.html for
    more information on atom styles.

    Parameters
    ----------
    structure : parmed.Structure
        ParmEd structure object
    filename : str
        Path of the output file
    atom_style: str
        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.
    unit_style: str
        Defines to unit style to be save in a LAMMPS data file.  Defaults to
        'real' units. Current styles are supported: 'real', 'lj'
        see https://lammps.sandia.gov/doc/99/units.html for more information
        on unit styles
    mins : list
        minimum box dimension in x, y, z directions
    maxs : list
        maximum box dimension in x, y, z directions
    pair_coeff_label : str
        Provide a custom label to the pair_coeffs section in the lammps data
        file. Defaults to None, which means a suitable default will be chosen.
    detect_forcefield_style: boolean
        If True, format lammpsdata parameters based on the contents of
        the parmed Structure
    use_urey_bradleys: boolean
        If True, will treat angles as CHARMM-style angles with urey bradley
        terms while looking for `structure.urey_bradleys`
    use_rb_torsions:
        If True, will treat dihedrals OPLS-style torsions while looking for
        `structure.rb_torsions`
    use_dihedrals:
        If True, will treat dihedrals as CHARMM-style dihedrals while looking
        for `structure.dihedrals`
    zero_dihedral_weighting_factor:
        If True, will set weighting parameter to zero in CHARMM-style dihedrals.
        This should be True if the CHARMM dihedral style is used in non-CHARMM forcefields.
    moleculeID_offset : int , optional, default=1
        Since LAMMPS treats the MoleculeID as an additional set of information
        to identify what molecule an atom belongs to, this currently
        behaves as a residue id. This value needs to start at 1 to be
        considered a real molecule.

    Notes
    -----
    See http://lammps.sandia.gov/doc/2001/data_format.html for a full
    description of the LAMMPS data format. Currently the following sections are
    supported (in addition to the header): *Masses*, *Nonbond Coeffs*,
    *Bond Coeffs*, *Angle Coeffs*, *Dihedral Coeffs*, *Atoms*, *Bonds*,
    *Angles*, *Dihedrals*, *Impropers*
    OPLS and CHARMM forcefield styles are supported, AMBER forcefield styles
    are NOT

    Some of this function has beed adopted from `mdtraj`'s support of the
    LAMMPSTRJ trajectory format. See
    https://github.com/mdtraj/mdtraj/blob/master/mdtraj/formats/lammpstrj.py
    for details.

    unique_types : a sorted list of unique atomtypes for all atoms in the
        structure where atomtype = atom.type.
    unique_bond_types: an enumarated OrderedDict of unique bond types for all
        bonds in the structure.
        Defined by bond parameters and component atomtypes, in order:
        --- k : bond.type.k
        --- req : bond.type.req
        --- atomtypes : sorted((bond.atom1.type, bond.atom2.type))
    unique_angle_types: an enumerated OrderedDict of unique angle types for all
        angles in the structure.
        Defined by angle parameters and component atomtypes, in order:
        --- k : angle.type.k
        --- theteq : angle.type.theteq
        --- vertex atomtype: angle.atom2.type
        --- atomtypes: sorted((bond.atom1.type, bond.atom3.type))

    unique_dihedral_types: an enumerated OrderedDict of unique dihedrals type
        for all dihedrals in the structure.
        Defined by dihedral parameters and component atomtypes, in order:
        --- c0 : dihedral.type.c0
        --- c1 : dihedral.type.c1
        --- c2 : dihedral.type.c2
        --- c3 : dihedral.type.c3
        --- c4 : dihedral.type.c4
        --- c5 : dihedral.type.c5
        --- scee : dihedral.type.scee
        --- scnb : dihedral.type.scnb
        --- atomtype 1 : dihedral.atom1.type
        --- atomtype 2 : dihedral.atom2.type
        --- atomtype 3 : dihedral.atom3.type
        --- atomtype 4 : dihedral.atom4.type
    """
    # copy structure so the input structure isn't modified in-place
    structure = structure.copy(cls=Structure, split_dihedrals=True)
    if atom_style not in ["atomic", "charge", "molecular", "full"]:
        raise ValueError(
            'Atom style "{atom_style}" is invalid or is not currently supported'
        )

    # Check if structure is paramterized
    if unit_style == "lj":
        if any([atom.sigma for atom in structure.atoms]) is None:
            raise ValueError(
                "LJ units specified but one or more atoms has undefined LJ "
                "parameters.")

    xyz = np.array([[atom.xx, atom.xy, atom.xz] for atom in structure.atoms])

    forcefield = True
    if structure[0].type == "":
        forcefield = False

    if forcefield:
        types = [atom.type for atom in structure.atoms]
    else:
        types = [atom.name for atom in structure.atoms]

    unique_types = list(set(types))
    unique_types.sort(key=natural_sort)

    charges = np.array([atom.charge for atom in structure.atoms])

    # Convert coordinates to LJ units
    if unit_style == "lj":
        # Get sigma, mass, and epsilon conversions by finding maximum of each
        sigma_conversion_factor = np.max([a.sigma for a in structure.atoms])
        epsilon_conversion_factor = np.max(
            [a.epsilon for a in structure.atoms])
        mass_conversion_factor = np.max([a.mass for a in structure.atoms])

        xyz = xyz / sigma_conversion_factor
        charges = (charges * 1.6021e-19) / np.sqrt(
            4 * np.pi * (sigma_conversion_factor * 1e-10) *
            (epsilon_conversion_factor * 4184) * epsilon_0)
        charges[np.isinf(charges)] = 0
    else:
        sigma_conversion_factor = 1
        epsilon_conversion_factor = 1
        mass_conversion_factor = 1
    # lammps does not require the box to be centered at any a specific origin
    # min and max dimensions are therefore needed to write the file in a
    # consistent way the parmed structure only stores the box length.  It is
    # not rigorous to assume bounds are 0 to L or -L/2 to L/2

    # NOTE: 0 to L is current default, mins and maxs should be passed by user

    if _check_minsmaxs(mins, maxs):
        box = Box.from_mins_maxs_angles(mins=mins,
                                        maxs=maxs,
                                        angles=structure.box[3:6])
    else:
        # Internally use nm
        box = Box(
            lengths=np.array([0.1 * val for val in structure.box[0:3]]),
            angles=structure.box[3:6],
        )

        warn(
            "Explicit box bounds (i.e., mins and maxs) were not provided. Box "
            "bounds are assumed to be min = 0 and max = length in each "
            "direction. This may not produce a system with the expected "
            "spatial location and may cause non-periodic systems to fail. "
            "Bounds can be defined explicitly by passing the them to the "
            "write_lammpsdata function or by passing box info to the save "
            "function.")
    # Divide by conversion factor
    Lx = box.Lx * (1 / sigma_conversion_factor)
    Ly = box.Ly * (1 / sigma_conversion_factor)
    Lz = box.Lz * (1 / sigma_conversion_factor)
    box = Box(lengths=(Lx, Ly, Lz), angles=box.angles)

    # Lammps syntax depends on the functional form
    # Infer functional form based on the properties of the structure
    if detect_forcefield_style:
        # Check angles
        if len(structure.urey_bradleys) > 0:
            print("Urey bradley terms detected, will use angle_style charmm")
            use_urey_bradleys = True
        else:
            print(
                "No urey bradley terms detected, will use angle_style harmonic"
            )
            use_urey_bradleys = False

        # Check dihedrals
        if len(structure.rb_torsions) > 0:
            print("RB Torsions detected, will use dihedral_style opls")
            use_rb_torsions = True
        else:
            use_rb_torsions = False
        if len([d for d in structure.dihedrals if not d.improper]) > 0:
            print("Charmm dihedrals detected, will use dihedral_style charmm")
            use_dihedrals = True
        else:
            use_dihedrals = False
    if use_rb_torsions and use_dihedrals:
        raise ValueError("Multiple dihedral styles detected, check your "
                         "Forcefield XML and structure")

    bonds = [[b.atom1.idx + 1, b.atom2.idx + 1] for b in structure.bonds]
    angles = [[angle.atom1.idx + 1, angle.atom2.idx + 1, angle.atom3.idx + 1]
              for angle in structure.angles]
    if use_rb_torsions:
        dihedrals = [[
            d.atom1.idx + 1, d.atom2.idx + 1, d.atom3.idx + 1, d.atom4.idx + 1
        ] for d in structure.rb_torsions]
    elif use_dihedrals:
        dihedrals = [[
            d.atom1.idx + 1, d.atom2.idx + 1, d.atom3.idx + 1, d.atom4.idx + 1
        ] for d in structure.dihedrals if not d.improper]
    else:
        dihedrals = []
    impropers = [[
        i.atom1.idx + 1, i.atom2.idx + 1, i.atom3.idx + 1, i.atom4.idx + 1
    ] for i in structure.impropers]
    imp_dihedrals = [[
        d.atom1.idx + 1, d.atom2.idx + 1, d.atom3.idx + 1, d.atom4.idx + 1
    ] for d in structure.dihedrals if d.improper]

    if impropers and imp_dihedrals:
        raise ValueError("Use of multiple improper styles is not supported")

    if bonds:
        if len(structure.bond_types) == 0:
            bond_types = np.ones(len(bonds), dtype=int)
        else:
            bond_types, unique_bond_types = _get_bond_types(
                structure,
                bonds,
                sigma_conversion_factor,
                epsilon_conversion_factor,
            )

    if angles:
        angle_types, unique_angle_types = _get_angle_types(
            structure,
            use_urey_bradleys,
            sigma_conversion_factor,
            epsilon_conversion_factor,
        )

    if imp_dihedrals:
        (
            imp_dihedral_types,
            unique_imp_dihedral_types,
        ) = _get_improper_dihedral_types(structure, epsilon_conversion_factor)
    if dihedrals:
        dihedral_types, unique_dihedral_types = _get_dihedral_types(
            structure,
            use_rb_torsions,
            use_dihedrals,
            epsilon_conversion_factor,
            zero_dihedral_weighting_factor,
        )

    if impropers:
        improper_types, unique_improper_types = _get_impropers(
            structure, epsilon_conversion_factor)

    with open(filename, "w") as data:
        data.write(f"{filename} - created by mBuild; units = {unit_style}\n\n")
        data.write("{:d} atoms\n".format(len(structure.atoms)))
        if atom_style in ["full", "molecular"]:
            data.write("{:d} bonds\n".format(len(bonds)))
            data.write("{:d} angles\n".format(len(angles)))
            data.write("{:d} dihedrals\n".format(len(dihedrals)))
            data.write("{:d} impropers\n\n".format(
                len(impropers) + len(imp_dihedrals)))

        data.write("{:d} atom types\n".format(len(set(types))))
        if atom_style in ["full", "molecular"]:
            if bonds:
                data.write("{:d} bond types\n".format(len(set(bond_types))))
            if angles:
                data.write("{:d} angle types\n".format(len(set(angle_types))))
            if dihedrals:
                data.write("{:d} dihedral types\n".format(
                    len(set(dihedral_types))))
            if impropers:
                data.write("{:d} improper types\n".format(
                    len(set(improper_types))))
            elif imp_dihedrals:
                data.write("{:d} improper types\n".format(
                    len(set(imp_dihedral_types))))

        data.write("\n")
        # Box data
        # NOTE: Needs better logic handling maxs and mins of a bounding box
        # NOTE: JBG, "this should be a method/attribute of Compound?"
        if np.allclose(box.angles, 90.0, atol=1e-5) and (mins is None):
            for i, dim in enumerate(["x", "y", "z"]):
                data.write("{0:.6f} {1:.6f} {2}lo {2}hi\n".format(
                    0.0, 10.0 * box.lengths[i], dim))
        # NOTE:
        # currently non-orthogonal bounding box translates
        # Compound such that mins are new origin
        else:
            a = 10.0 * box.Lx
            b = 10.0 * box.Ly
            c = 10.0 * box.Lz
            alpha, beta, gamma = np.radians(box.angles)

            xy = box.xy
            xz = box.xz
            yz = box.yz

            # NOTE: using (0,0,0) as origin
            xlo, ylo, zlo = (0.0, 0.0, 0.0)
            xhi = xlo + a
            yhi = ylo + b
            zhi = zlo + c

            xlo_bound = xlo + np.min([0.0, xy, xz, xy + xz])
            xhi_bound = xhi + np.max([0.0, xy, xz, xy + xz])
            ylo_bound = ylo + np.min([0.0, yz])
            yhi_bound = yhi + np.max([0.0, yz])
            zlo_bound = zlo
            zhi_bound = zhi

            data.write("{0:.6f} {1:.6f} xlo xhi\n".format(
                xlo_bound, xhi_bound))
            data.write("{0:.6f} {1:.6f} ylo yhi\n".format(
                ylo_bound, yhi_bound))
            data.write("{0:.6f} {1:.6f} zlo zhi\n".format(
                zlo_bound, zhi_bound))
            data.write("{0:.6f} {1:.6f} {2:6f} xy xz yz\n".format(xy, xz, yz))

        # Mass data
        if not forcefield:
            masses = (np.array([atom.mass for atom in structure.atoms]) /
                      mass_conversion_factor)
        else:
            tmp_masses = list()
            for atom in structure.atoms:
                # handle case where atomtype does not contain a mass
                try:
                    tmp_masses.append(atom.atom_type.mass)
                except AttributeError:
                    warn(
                        f"No mass or defined atomtype for atom: {atom}. Using atom mass of {atom.mass / mass_conversion_factor}"
                    )
                    tmp_masses.append(atom.mass)
            masses = np.asarray(tmp_masses) / mass_conversion_factor

        mass_dict = dict([(unique_types.index(atom_type) + 1, mass)
                          for atom_type, mass in zip(types, masses)])
        data.write("\nMasses\n\n")
        for atom_type, mass in sorted(mass_dict.items()):
            data.write("{:d}\t{:.6f}\t# {}\n".format(
                atom_type, mass, unique_types[atom_type - 1]))

        if forcefield:
            epsilons = (np.array([atom.epsilon for atom in structure.atoms]) /
                        epsilon_conversion_factor)
            sigmas = (np.array([atom.sigma for atom in structure.atoms]) /
                      sigma_conversion_factor)
            forcefields = [atom.type for atom in structure.atoms]
            epsilon_dict = dict([
                (unique_types.index(atom_type) + 1, epsilon)
                for atom_type, epsilon in zip(types, epsilons)
            ])
            sigma_dict = dict([(unique_types.index(atom_type) + 1, sigma)
                               for atom_type, sigma in zip(types, sigmas)])
            forcefield_dict = dict([
                (unique_types.index(atom_type) + 1, forcefield)
                for atom_type, forcefield in zip(types, forcefields)
            ])

            # Modified cross-interactions
            if structure.has_NBFIX():
                params = ParameterSet.from_structure(structure)
                # Sort keys (maybe they should be sorted in ParmEd)
                new_nbfix_types = OrderedDict()
                for key in params.nbfix_types.keys():
                    sorted_key = tuple(sorted(key))
                    if sorted_key in new_nbfix_types:
                        warn("Sorted key matches an existing key")
                        if new_nbfix_types[sorted_key]:
                            warn("nbfixes are not symmetric, overwriting old "
                                 "nbfix")
                    new_nbfix_types[sorted_key] = params.nbfix_types[key]
                params.nbfix_types = new_nbfix_types
                warn(
                    "Explicitly writing cross interactions using mixing rule: "
                    "{}".format(structure.combining_rule))
                coeffs = OrderedDict()
                for combo in it.combinations_with_replacement(unique_types, 2):
                    # Attempt to find pair coeffis in nbfixes
                    if combo in params.nbfix_types:
                        type1 = unique_types.index(combo[0]) + 1
                        type2 = unique_types.index(combo[1]) + 1
                        epsilon = params.nbfix_types[combo][
                            0]  # kcal OR lj units
                        rmin = params.nbfix_types[combo][
                            1]  # Angstrom OR lj units
                        sigma = rmin / 2**(1 / 6)
                        coeffs[(type1, type2)] = (
                            round(sigma, 8),
                            round(epsilon, 8),
                        )
                    else:
                        type1 = unique_types.index(combo[0]) + 1
                        type2 = unique_types.index(combo[1]) + 1
                        # Might not be necessary to be this explicit
                        if type1 == type2:
                            sigma = sigma_dict[type1]
                            epsilon = epsilon_dict[type1]
                        else:
                            if structure.combining_rule == "lorentz":
                                sigma = (sigma_dict[type1] +
                                         sigma_dict[type2]) * 0.5
                            elif structure.combining_rule == "geometric":
                                sigma = (sigma_dict[type1] *
                                         sigma_dict[type2])**0.5
                            else:
                                raise ValueError(
                                    "Only lorentz and geometric combining "
                                    "rules are supported")
                            epsilon = (epsilon_dict[type1] *
                                       epsilon_dict[type2])**0.5
                        coeffs[(type1, type2)] = (
                            round(sigma, 8),
                            round(epsilon, 8),
                        )
                if nbfix_in_data_file:
                    if pair_coeff_label:
                        data.write(
                            "\nPairIJ Coeffs # {}\n".format(pair_coeff_label))
                    else:
                        data.write("\nPairIJ Coeffs # modified lj\n")

                    data.write(
                        "# type1 type2\tepsilon (kcal/mol)\tsigma (Angstrom)\n"
                    )

                    for (type1, type2), (sigma, epsilon) in coeffs.items():
                        data.write(
                            "{0} \t{1} \t{2} \t\t{3}\t\t# {4}\t{5}\n".format(
                                type1,
                                type2,
                                epsilon,
                                sigma,
                                forcefield_dict[type1],
                                forcefield_dict[type2],
                            ))
                else:
                    if pair_coeff_label:
                        data.write(
                            "\nPair Coeffs # {}\n".format(pair_coeff_label))
                    else:
                        data.write("\nPair Coeffs # lj\n")

                    for idx, epsilon in sorted(epsilon_dict.items()):
                        data.write("{}\t{:.5f}\t{:.5f}\n".format(
                            idx, epsilon, sigma_dict[idx]))
                    print("Copy these commands into your input script:\n")
                    print(
                        "# type1 type2\tepsilon (kcal/mol)\tsigma (Angstrom)\n"
                    )
                    for (type1, type2), (sigma, epsilon) in coeffs.items():
                        print(
                            "pair_coeff\t{0} \t{1} \t{2} \t\t{3} \t\t# {4} \t{5}"
                            .format(
                                type1,
                                type2,
                                epsilon,
                                sigma,
                                forcefield_dict[type1],
                                forcefield_dict[type2],
                            ))

            # Pair coefficients
            else:
                if pair_coeff_label:
                    data.write("\nPair Coeffs # {}\n".format(pair_coeff_label))
                else:
                    data.write("\nPair Coeffs # lj\n")

                if unit_style == "real":
                    data.write("#\tepsilon (kcal/mol)\t\tsigma (Angstrom)\n")
                elif unit_style == "lj":
                    data.write("#\treduced_epsilon \t\treduced_sigma \n")
                for idx, epsilon in sorted(epsilon_dict.items()):
                    data.write("{}\t{:.5f}\t\t{:.5f}\t\t# {}\n".format(
                        idx, epsilon, sigma_dict[idx], forcefield_dict[idx]))

            # Bond coefficients
            if bonds:
                data.write("\nBond Coeffs # harmonic\n")
                if unit_style == "real":
                    data.write("#\tk(kcal/mol/angstrom^2)\t\treq(angstrom)\n")
                elif unit_style == "lj":
                    data.write("#\treduced_k\t\treduced_req\n")
                sorted_bond_types = {
                    k: v
                    for k, v in sorted(unique_bond_types.items(),
                                       key=lambda item: item[1])
                }
                for params, idx in sorted_bond_types.items():
                    data.write("{}\t{}\t\t{}\t\t# {}\t{}\n".format(
                        idx,
                        params[0],
                        params[1],
                        params[2][0],
                        params[2][1],
                    ))

            # Angle coefficients
            if angles:
                sorted_angle_types = {
                    k: v
                    for k, v in sorted(unique_angle_types.items(),
                                       key=lambda item: item[1])
                }
                if use_urey_bradleys:
                    data.write("\nAngle Coeffs # charmm\n")
                    data.write(
                        "#\tk(kcal/mol/rad^2)\t\ttheteq(deg)\tk(kcal/mol/angstrom^2)\treq(angstrom)\n"
                    )
                    for params, idx in sorted_angle_types.items():
                        data.write("{}\t{}\t{:.5f}\t{:.5f}\t{:.5f}\n".format(
                            idx, *params))

                else:
                    data.write("\nAngle Coeffs # harmonic\n")
                    data.write("#\treduced_k\t\ttheteq(deg)\n")
                    for params, idx in sorted_angle_types.items():
                        data.write("{}\t{}\t\t{:.5f}\t# {}\t{}\t{}\n".format(
                            idx,
                            params[0],
                            params[1],
                            params[3][0],
                            params[2],
                            params[3][1],
                        ))

            # Dihedral coefficients
            if dihedrals:
                sorted_dihedral_types = {
                    k: v
                    for k, v in sorted(unique_dihedral_types.items(),
                                       key=lambda item: item[1])
                }
                if use_rb_torsions:
                    data.write("\nDihedral Coeffs # opls\n")
                    if unit_style == "real":
                        data.write(
                            "#\tf1(kcal/mol)\tf2(kcal/mol)\tf3(kcal/mol)\tf4(kcal/mol)\n"
                        )
                    elif unit_style == "lj":
                        data.write(
                            "#\tf1\tf2\tf3\tf4 (all lj reduced units)\n")
                    for params, idx in sorted_dihedral_types.items():
                        opls_coeffs = RB_to_OPLS(
                            params[0],
                            params[1],
                            params[2],
                            params[3],
                            params[4],
                            params[5],
                            error_if_outside_tolerance=False,
                        )
                        data.write(
                            "{}\t{:.5f}\t{:.5f}\t\t{:.5f}\t\t{:.5f}\t# {}\t{}\t{}\t{}\n"
                            .format(
                                idx,
                                opls_coeffs[1],
                                opls_coeffs[2],
                                opls_coeffs[3],
                                opls_coeffs[4],
                                params[8],
                                params[9],
                                params[10],
                                params[11],
                            ))
                elif use_dihedrals:
                    data.write("\nDihedral Coeffs # charmm\n")
                    data.write("#k, n, phi, weight\n")
                    for params, idx in sorted_dihedral_types.items():
                        data.write(
                            "{}\t{:.5f}\t{:d}\t{:d}\t{:.5f}\t# {}\t{}\t{}\t{}\n"
                            .format(
                                idx,
                                params[0],
                                params[1],
                                params[2],
                                params[3],
                                params[6],
                                params[7],
                                params[8],
                                params[9],
                            ))

            # Improper coefficients
            if impropers:
                sorted_improper_types = {
                    k: v
                    for k, v in sorted(unique_improper_types.items(),
                                       key=lambda item: item[1])
                }
                data.write("\nImproper Coeffs # harmonic\n")
                data.write("#k, phi\n")
                for params, idx in sorted_improper_types.items():
                    data.write("{}\t{:.5f}\t{:.5f}\t# {}\t{}\t{}\t{}\n".format(
                        idx,
                        params[0],
                        params[1],
                        params[2],
                        params[3],
                        params[4],
                        params[5],
                    ))
            elif imp_dihedrals:
                # Improper dihedral coefficients
                sorted_imp_dihedral_types = {
                    k: v
                    for k, v in sorted(
                        unique_imp_dihedral_types.items(),
                        key=lambda item: item[1],
                    )
                }
                data.write("\nImproper Coeffs # cvff\n")
                data.write("#K, d, n\n")
                for params, idx in sorted_imp_dihedral_types.items():
                    data.write(
                        "{}\t{:.5f}\t{:d}\t{:d}\t# {}\t{}\t{}\t{}\n".format(
                            idx,
                            params[0],
                            params[1],
                            params[2],
                            params[5],
                            params[6],
                            params[7],
                            params[8],
                        ))

        # Atom data
        data.write("\nAtoms # {}\n\n".format(atom_style))
        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":
            if unit_style == "real":
                atom_line = "{index:d}\t{type_index:d}\t{charge:.6f}\t{x:.6f}\t{y:.6f}\t{z:.6f}\n"
            elif unit_style == "lj":
                atom_line = "{index:d}\t{type_index:d}\t{charge:.4ef}\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":
            if unit_style == "real":
                atom_line = "{index:d}\t{zero:d}\t{type_index:d}\t{charge:.6f}\t{x:.6f}\t{y:.6f}\t{z:.6f}\n"
            elif unit_style == "lj":
                atom_line = "{index:d}\t{zero:d}\t{type_index:d}\t{charge:.4e}\t{x:.6f}\t{y:.6f}\t{z:.6f}\n"

        for i, coords in enumerate(xyz):
            data.write(
                atom_line.format(
                    index=i + 1,
                    type_index=unique_types.index(types[i]) + 1,
                    zero=structure.atoms[i].residue.idx + moleculeID_offset,
                    charge=charges[i],
                    x=coords[0],
                    y=coords[1],
                    z=coords[2],
                ))

        if atom_style in ["full", "molecular"]:
            # Bond data
            if bonds:
                data.write("\nBonds\n\n")
                for i, bond in enumerate(bonds):
                    data.write("{:d}\t{:d}\t{:d}\t{:d}\n".format(
                        i + 1, bond_types[i], bond[0], bond[1]))

            # Angle data
            if angles:
                data.write("\nAngles\n\n")
                for i, angle in enumerate(angles):
                    data.write("{:d}\t{:d}\t{:d}\t{:d}\t{:d}\n".format(
                        i + 1, angle_types[i], angle[0], angle[1], angle[2]))

            # Dihedral data
            if dihedrals:
                data.write("\nDihedrals\n\n")
                for i, dihedral in enumerate(dihedrals):
                    data.write("{:d}\t{:d}\t{:d}\t{:d}\t{:d}\t{:d}\n".format(
                        i + 1,
                        dihedral_types[i],
                        dihedral[0],
                        dihedral[1],
                        dihedral[2],
                        dihedral[3],
                    ))
            # Dihedral data
            if impropers:
                data.write("\nImpropers\n\n")
                for i, improper in enumerate(impropers):
                    data.write("{:d}\t{:d}\t{:d}\t{:d}\t{:d}\t{:d}\n".format(
                        i + 1,
                        improper_types[i],
                        improper[2],
                        improper[1],
                        improper[0],
                        improper[3],
                    ))
            elif imp_dihedrals:
                data.write("\nImpropers\n\n")
                for i, improper in enumerate(imp_dihedrals):
                    # The atoms are written central-atom third in LAMMPS data file.
                    # This is correct for AMBER impropers even though
                    # LAMMPS documentation implies central-atom-first.
                    data.write("{:d}\t{:d}\t{:d}\t{:d}\t{:d}\t{:d}\n".format(
                        i + 1,
                        imp_dihedral_types[i],
                        improper[0],
                        improper[1],
                        improper[2],
                        improper[3],
                    ))