Пример #1
0
def from_snapshot(snapshot, scale=1.0):
    """Convert a Snapshot to a Compound.

    Snapshot can be a hoomd.data.Snapshot or a gsd.hoomd.Snapshot.

    Parameters
    ----------
    snapshot : hoomd._hoomd.SnapshotSystemData_float or gsd.hoomd.Snapshot
        Snapshot from which to build the mbuild Compound.
    scale : float, optional, default 1.0
        Value by which to scale the length values

    Returns
    -------
    comp : Compound

    Note
    ----
    GSD and HOOMD snapshots center their boxes on the origin (0,0,0), so the
    compound is shifted by half the box lengths
    """
    comp = Compound()
    bond_array = snapshot.bonds.group
    n_atoms = snapshot.particles.N

    if "SnapshotSystemData_float" in dir(hoomd._hoomd) and isinstance(
        snapshot, hoomd._hoomd.SnapshotSystemData_float
    ):
        # hoomd v2
        box = snapshot.box
        comp.box = Box.from_lengths_tilt_factors(
            lengths=np.array([box.Lx, box.Ly, box.Lz]) * scale,
            tilt_factors=np.array([box.xy, box.xz, box.yz]),
        )
    else:
        # gsd / hoomd v3
        box = snapshot.configuration.box
        comp.box = Box.from_lengths_tilt_factors(
            lengths=box[:3] * scale, tilt_factors=box[3:]
        )

    # GSD and HOOMD snapshots center their boxes on the origin (0,0,0)
    shift = np.array(comp.box.lengths) / 2
    # Add particles
    for i in range(n_atoms):
        name = snapshot.particles.types[snapshot.particles.typeid[i]]
        xyz = snapshot.particles.position[i] * scale + shift
        charge = snapshot.particles.charge[i]

        atom = Particle(name=name, pos=xyz, charge=charge)
        comp.add(atom, label=str(i))

    # Add bonds
    particle_dict = {idx: p for idx, p in enumerate(comp.particles())}
    for i in range(bond_array.shape[0]):
        atom1 = int(bond_array[i][0])
        atom2 = int(bond_array[i][1])
        comp.add_bond([particle_dict[atom1], particle_dict[atom2]])
    return comp
Пример #2
0
def fill_box(
    compound,
    n_compounds=None,
    box=None,
    density=None,
    overlap=0.2,
    seed=12345,
    sidemax=100.0,
    edge=0.2,
    compound_ratio=None,
    aspect_ratio=None,
    fix_orientation=False,
    temp_file=None,
    update_port_locations=False,
):
    """Fill a box with a `mbuild.compound` or `Compound`s using PACKMOL.

   `fill_box` takes a single `mbuild.Compound` or a
   list of `mbuild.Compound`'s and return an `mbuild.Compound` that has
   been filled to the user's specifications to the best of PACKMOL's ability.

    When filling a system, two arguments of `n_compounds, box, and density`
    must be specified.

    If `n_compounds` and `box` are not None, the specified number of
    n_compounds will be inserted into a box of the specified size.

    If `n_compounds` and `density` are not None, the corresponding box
    size will be calculated internally. In this case, `n_compounds`
    must be an int and not a list of int.

    If `box` and `density` are not None, the corresponding number of
    compounds will be calculated internally.

    For the cases in which `box` is not specified but generated internally,
    the default behavior is to calculate a cubic box. Optionally,
    `aspect_ratio` can be passed to generate a non-cubic box.

    Parameters
    ----------
    compound : mb.Compound or list of mb.Compound
        Compound or list of compounds to fill in box.
    n_compounds : int or list of int
        Number of compounds to be filled in box.
    box : mb.Box
        Box to be filled by compounds.
    density : float, units kg/m^3, default=None
        Target density for the system in macroscale units. If not None, one of
        `n_compounds` or `box`, but not both, must be specified.
    overlap : float, units nm, default=0.2
        Minimum separation between atoms of different molecules.
    seed : int, default=12345
        Random seed to be passed to PACKMOL.
    sidemax : float, optional, default=100.0
        Needed to build an initial approximation of the molecule distribution in PACKMOL.
        All system coordinates must fit with in +/- sidemax,
        so increase sidemax accordingly to your final box size.
    edge : float, units nm, default=0.2
        Buffer at the edge of the box to not place molecules. This is necessary
        in some systems because PACKMOL does not account for periodic boundary
        conditions in its optimization.
    compound_ratio : list, default=None
        Ratio of number of each compound to be put in box. Only used in the
        case of `density` and `box` having been specified, `n_compounds` not
        specified, and more than one `compound`.
    aspect_ratio : list of float
        If a non-cubic box is desired, the ratio of box lengths in the x, y,
        and z directions.
    fix_orientation : bool or list of bools
        Specify that compounds should not be rotated when filling the box,
        default=False.
    temp_file : str, default=None
        File name to write PACKMOL's raw output to.
    update_port_locations : bool, default=False
        After packing, port locations can be updated, but since compounds
        can be rotated, port orientation may be incorrect.

    Returns
    -------
    filled : mb.Compound

    """
    # check that the user has the PACKMOL binary on their PATH
    _check_packmol(PACKMOL)

    arg_count = 3 - [n_compounds, box, density].count(None)
    if arg_count != 2:
        msg = ("Exactly 2 of `n_compounds`, `box`, and `density` "
               "must be specified. {} were given.".format(arg_count))
        raise ValueError(msg)

    if box is not None:
        box = _validate_box(box)
    if not isinstance(compound, (list, set)):
        compound = [compound]
    if n_compounds is not None and not isinstance(n_compounds, (list, set)):
        n_compounds = [n_compounds]
    if not isinstance(fix_orientation, (list, set)):
        fix_orientation = [fix_orientation] * len(compound)

    if compound is not None and n_compounds is not None:
        if len(compound) != len(n_compounds):
            msg = "`compound` and `n_compounds` must be of equal length."
            raise ValueError(msg)

    if compound is not None:
        if len(compound) != len(fix_orientation):
            msg = ("`compound`, `n_compounds`, and `fix_orientation` "
                   "must be of equal length.")
            raise ValueError(msg)

    if density is not None:
        if box is None and n_compounds is not None:
            total_mass = np.sum([
                n * np.sum([a.mass for a in c.to_parmed().atoms])
                for c, n in zip(compound, n_compounds)
            ])
            # Conversion from (amu/(kg/m^3))**(1/3) to nm
            L = (total_mass / density)**(1 / 3) * 1.1841763
            if aspect_ratio is None:
                box = _validate_box(Box(3 * [L]))
            else:
                L *= np.prod(aspect_ratio)**(-1 / 3)
                box = _validate_box(Box([val * L for val in aspect_ratio]))
        if n_compounds is None and box is not None:
            if len(compound) == 1:
                compound_mass = np.sum(
                    [a.mass for a in compound[0].to_parmed().atoms])
                # Conversion from kg/m^3 / amu * nm^3 to dimensionless units
                n_compounds = [
                    int(density / compound_mass * np.prod(box.lengths) *
                        0.60224)
                ]
            else:
                if compound_ratio is None:
                    msg = ("Determing `n_compounds` from `density` and `box` "
                           "for systems with more than one compound type "
                           "requires `compound_ratio`")
                    raise ValueError(msg)
                if len(compound) != len(compound_ratio):
                    msg = ("Length of `compound_ratio` must equal length of "
                           "`compound`")
                    raise ValueError(msg)
                prototype_mass = 0
                for c, r in zip(compound, compound_ratio):
                    prototype_mass += r * np.sum(
                        [a.mass for a in c.to_parmed().atoms])
                # Conversion from kg/m^3 / amu * nm^3 to dimensionless units
                n_prototypes = int(density / prototype_mass *
                                   np.prod(box.lengths) * 0.60224)
                n_compounds = list()
                for c in compound_ratio:
                    n_compounds.append(int(n_prototypes * c))

    # Convert nm to angstroms for PACKMOL.
    box_mins = box.mins * 10
    box_maxs = box.maxs * 10
    overlap *= 10

    # Apply 1nm edge buffer
    box_maxs -= edge * 10

    # Build the input file for each compound and call packmol.
    filled_xyz = _new_xyz_file()

    # create a list to contain the file handles for the compound temp files
    compound_xyz_list = list()
    try:
        input_text = PACKMOL_HEADER.format(overlap, filled_xyz.name, seed,
                                           sidemax * 10)
        for comp, m_compounds, rotate in zip(compound, n_compounds,
                                             fix_orientation):
            m_compounds = int(m_compounds)

            compound_xyz = _new_xyz_file()
            compound_xyz_list.append(compound_xyz)

            comp.save(compound_xyz.name, overwrite=True)
            input_text += PACKMOL_BOX.format(
                compound_xyz.name,
                m_compounds,
                box_mins[0],
                box_mins[1],
                box_mins[2],
                box_maxs[0],
                box_maxs[1],
                box_maxs[2],
                PACKMOL_CONSTRAIN if rotate else "",
            )
        _run_packmol(input_text, filled_xyz, temp_file)
        # Create the topology and update the coordinates.
        filled = Compound()
        filled = _create_topology(filled, compound, n_compounds)
        filled.update_coordinates(filled_xyz.name,
                                  update_port_locations=update_port_locations)
        filled.box = box
        filled.periodicity = np.asarray(box.lengths, dtype=np.float32)

    # ensure that the temporary files are removed from the machine after filling
    finally:
        for file_handle in compound_xyz_list:
            file_handle.close()
            os.unlink(file_handle.name)
        filled_xyz.close()
        os.unlink(filled_xyz.name)
    return filled