示例#1
0
def _validate_box(box):
    """Ensure that the box passed by the user can be formatted as an mbuild.Box.

    Parameters
    ----------
    box : mbuild.Box or a tuple or list thereof
        Box or inputs to `mbuild.Box` to generate a `mbuild.Box`.

    Returns
    -------
    box : mbuild.Box
    """
    if isinstance(box, (list, tuple)):
        if len(box) == 3:
            box = Box(lengths=box)
        elif len(box) == 6:
            box = Box(mins=box[:3], maxs=box[3:])

    if not isinstance(box, Box):
        raise MBuildError(
            "Unknown format for `box` parameter. Must pass a list/tuple of "
            "length 3 (box lengths) or length 6 (box mins and maxes) or an "
            "mbuild.Box object."
        )
    return box
示例#2
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
示例#3
0
def _validate_box(box):
    if isinstance(box, (list, tuple)):
        if len(box) == 3:
            box = Box(lengths=box)
        elif len(box) == 6:
            box = Box(mins=box[:3], maxs=box[3:])

    if not isinstance(box, Box):
        raise MBuildError('Unknown format for `box` parameter. Must pass a'
                          ' list/tuple of length 3 (box lengths) or length'
                          ' 6 (box mins and maxes) or an mbuild.Box object.')
    return box
示例#4
0
    def test_pass_box(self, mb_ethane):
        mb_box = Box(lengths=[3, 3, 3])

        top = from_mbuild(mb_ethane, box=mb_box)
        assert_allclose_units(top.box.lengths, [3, 3, 3] * u.nm,
                              rtol=1e-5,
                              atol=1e-8)
示例#5
0
def _validate_box(box):
    """Ensure that the box passed by the user can be formatted as an mbuild.Box.

    Parameters
    ----------
    box : mbuild.Box or a tuple or list thereof
        Box or inputs to `mbuild.Box` to generate a `mbuild.Box`.

    Returns
    -------
    box : mbuild.Box
    mins : list-like
    maxs : list-like
    """
    if isinstance(box, (list, tuple)):
        if len(box) == 3:
            mins = [0.0, 0.0, 0.0]
            maxs = box
            box = Box.from_mins_maxs_angles(mins=mins,
                                            maxs=maxs,
                                            angles=(90.0, 90.0, 90.0))
        elif len(box) == 6:
            mins = box[:3]
            maxs = box[3:]
            box = Box.from_mins_maxs_angles(mins=mins,
                                            maxs=maxs,
                                            angles=(90.0, 90.0, 90.0))
        else:
            raise MBuildError(
                "Unknown format for `box` parameter. Must pass a"
                " list/tuple of length 3 (box lengths) or length"
                " 6 (box mins and maxes) or an mbuild.Box object.")

    elif isinstance(box, Box):
        mins = [0.0, 0.0, 0.0]
        maxs = box.lengths
    else:
        raise MBuildError(
            "Unknown format for `box` parameter. Must pass a list/tuple of "
            "length 3 (box lengths) or length 6 (box mins and maxes) or an "
            "mbuild.Box object.")
    return (box, mins, maxs)
示例#6
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
示例#7
0
    def test_pass_box(self, ethane):
        mb_box = Box(lengths=[3, 3, 3])

        top = from_mbuild(ethane, box=mb_box)
        assert allclose(top.box.lengths, [3, 3, 3] * u.nm)
示例#8
0
def fill_box(compound, n_compounds=None, box=None, density=None, overlap=0.2,
             seed=12345, edge=0.2, compound_ratio=None,
             aspect_ratio=None, fix_orientation=False, temp_file=None):
    """Fill a box with a compound using packmol.

    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 be put in box.
    n_compounds : int or list of int
        Number of compounds to be put 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.
    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.

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

    """
    _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)*.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)*.60224)
                n_compounds = list()
                for c in compound_ratio:
                    n_compounds.append(int(n_prototypes * c))

    # In angstroms for packmol.
    box_mins = box.mins * 10
    box_maxs = box.maxs * 10
    overlap *= 10

    # Apply edge buffer
    box_maxs -= edge * 10

    # Build the input file for each compound and call packmol.
    filled_pdb = tempfile.mkstemp(suffix='.pdb')[1]
    input_text = PACKMOL_HEADER.format(overlap, filled_pdb, seed)

    for comp, m_compounds, rotate in zip(compound, n_compounds, fix_orientation):
        m_compounds = int(m_compounds)
        compound_pdb = tempfile.mkstemp(suffix='.pdb')[1]
        comp.save(compound_pdb, overwrite=True)
        input_text += PACKMOL_BOX.format(compound_pdb, 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_pdb, temp_file)

    # Create the topology and update the coordinates.
    filled = Compound()
    for comp, m_compounds in zip(compound, n_compounds):
        for _ in range(m_compounds):
            filled.add(clone(comp))
    filled.update_coordinates(filled_pdb)
    filled.periodicity = np.asarray(box.lengths, dtype=np.float32)
    return filled
示例#9
0
 def boundingbox(self):
     """Compute the bounding box of the compound. """
     xyz = self.xyz
     return Box(mins=xyz.min(axis=0), maxs=xyz.max(axis=0))
示例#10
0
def fill_box(compound,
             n_compounds=None,
             box=None,
             density=None,
             overlap=0.2,
             seed=12345):
    """Fill a box with a compound using packmol.

    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.

    Parameters
    ----------
    compound : mb.Compound or list of mb.Compound
    n_compounds : int or list of int
    box : mb.Box
    overlap : float, units nm
    density : float, units kg/m^3

    Returns
    -------
    filled : mb.Compound
    
    TODO : Support aspect ratios in generated boxes
    TODO : Support ratios of n_compounds
    """
    if not PACKMOL:
        msg = "Packmol not found."
        if sys.platform.startswith("win"):
            msg = (msg +
                   " If packmol is already installed, make sure that the "
                   "packmol.exe is on the path.")
        raise IOError(msg)

    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 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
            box = _validate_box(Box(3 * [L]))
        if n_compounds is None and box is not None:
            if len(compound) > 1:
                msg = ("Determing `n_compounds` from `density` and `box` "
                       "currently only supported for systems with one "
                       "compound type.")
                raise ValueError(msg)
            else:
                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) *
                        .60224)
                ]

    # In angstroms for packmol.
    box_mins = box.mins * 10
    box_maxs = box.maxs * 10
    overlap *= 10

    # Build the input file for each compound and call packmol.
    filled_pdb = tempfile.mkstemp(suffix='.pdb')[1]
    input_text = PACKMOL_HEADER.format(overlap, filled_pdb, seed)

    for comp, m_compounds in zip(compound, n_compounds):
        m_compounds = int(m_compounds)
        compound_pdb = tempfile.mkstemp(suffix='.pdb')[1]
        comp.save(compound_pdb, overwrite=True)
        input_text += PACKMOL_BOX.format(compound_pdb, m_compounds,
                                         box_mins[0], box_mins[1], box_mins[2],
                                         box_maxs[0], box_maxs[1], box_maxs[2])

    proc = Popen(PACKMOL,
                 stdin=PIPE,
                 stdout=PIPE,
                 stderr=PIPE,
                 universal_newlines=True)
    out, err = proc.communicate(input=input_text)
    if err:
        _packmol_error(out, err)

    # Create the topology and update the coordinates.
    filled = Compound()
    for comp, m_compounds in zip(compound, n_compounds):
        for _ in range(m_compounds):
            filled.add(clone(comp))
    filled.update_coordinates(filled_pdb)
    filled.periodicity = np.asarray(box.lengths, dtype=np.float32)
    return filled