Exemple #1
0
def _perform_sanity_check(json_dict):
    """Perform Sanity Check on the JSON File."""
    from warnings import warn

    warning_msg = (
        "This Json was written using {0}, current mbuild version is {1}.")
    this_version = mb.__version__
    json_mbuild_version = json_dict.get("mbuild-version", None)

    if not json_mbuild_version:
        raise MBuildError(
            "The uploaded JSON file doesn't isn't correctly formatted")
    json_mb_type = json_dict.get("type", None)

    if (not json_mb_type) or (json_mb_type != "Compound"):
        raise MBuildError(
            "Error. Cannot convert JSON of type: {}".format(json_mb_type))

    [major, minor, patch] = json_mbuild_version.split(".")
    [this_major, this_minor, this_patch] = this_version.split(".")
    if major != this_major:
        raise MBuildError(
            warning_msg.format(json_mbuild_version, this_version) +
            " Cannot Convert JSON to compound")
    if minor != this_minor:
        warn(
            warning_msg.format(json_mbuild_version, this_version) +
            " Will Proceed.")
Exemple #2
0
def read_xyz(filename, compound=None):
    """Read an XYZ file. The expected format is as follows:
    The first line contains the number of atoms in the file The second line
    contains a comment, which is not read.  Remaining lines, one for each
    atom in the file, include an elemental symbol followed by X, Y, and Z
    coordinates in Angstroms. Columns are expected tbe separated by
    whitespace. See https://openbabel.org/wiki/XYZ_(format).

    Parameters
    ----------
    filename : str
        Path of the input file

    Returns
    -------
    compound : mb.Compound

    Notes
    -----
    The XYZ file format neglects many important details, notably as bonds,
    residues, and box information.

    There are some other flavors of the XYZ file format and not all are
    guaranteed to be compatible with this reader. For example, the TINKER
    XYZ format is not expected to be properly read.
    """

    if compound is None:
        compound = mb.Compound()

    with open(filename, 'r') as xyz_file:
        n_atoms = int(xyz_file.readline())
        xyz_file.readline()
        coords = np.zeros(shape=(n_atoms, 3), dtype=np.float64)
        for row, _ in enumerate(coords):
            line = xyz_file.readline().split()
            if not line:
                msg = (
                    'Incorrect number of lines in input file. Based on the '
                    'number in the first line of the file, {} rows of atoms '
                    'were expected, but at least one fewer was found.')
                raise MBuildError(msg.format(n_atoms))
            coords[row] = line[1:4]
            coords[row] *= 0.1
            particle = mb.Compound(pos=coords[row], name=line[0])
            compound.add(particle)

        # Verify we have read the last line by ensuring the next line in blank
        line = xyz_file.readline().split()
        if line:
            msg = ('Incorrect number of lines in input file. Based on the '
                   'number in the first line of the file, {} rows of atoms '
                   'were expected, but at least one more was found.')
            raise MBuildError(msg.format(n_atoms))

    return compound
Exemple #3
0
def _validate_mass(compound, n_compounds):
    """Check the mass of the compounds passed into the packing functions.

    Raises an error if the total mass is zero, and density cannot be used to
    find box size or number of compounds.
    Returns a warning of any subcompound in compound has a mass of zero.
    """
    if n_compounds is None:
        n_compounds = [1] * len(compound)
    found_zero_mass = False
    total_mass = 0
    for c, n in zip(compound, n_compounds):
        comp_masses = [c._particle_mass(p) for p in c.particles()]
        if 0.0 in comp_masses:
            found_zero_mass = True
        total_mass += np.sum(comp_masses) * n

    if total_mass == 0:
        raise MBuildError(
            "The total mass of your compound(s) is zero "
            "In order to use density when packing a box, the mass of "
            "the compounds must be set. See the doc strings of the "
            "Compound() class in compound.py for more information "
            "on how mass is handled.")
    if found_zero_mass:
        warnings.warn("Some of the compounds or subcompounds in `compound` "
                      "have a mass of zero. This may have an effect on "
                      "density calculations")
    return total_mass
Exemple #4
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
Exemple #5
0
def _normalize_box(vectors):
    """Align the box matrix into a right-handed coordinate frame.

    NOTE: This assumes that the matrix is in a row-major format.

    NOTE: Inspiration and logic are from the Glotzer group package, Garnett;
    which is provided under a BSD 3-clause License.
    For additional information, refer to the License file provided with this
    package.
    """
    det = np.linalg.det(vectors)
    if np.isclose(det, 0.0, atol=1e-5):
        raise MBuildError(
            "The vectors to define the box are co-linear, this does not form a "
            f"3D region in space.\n Box vectors evaluated: {vectors}")
    if det < 0.0:
        warn("Box vectors provided for a left-handed basis, these will be "
             "transformed into a right-handed basis automatically.")

    # transpose to column-major for the time being
    Q, R = np.linalg.qr(vectors.T)

    # left or right handed: det<0 left, >0, right
    sign = np.linalg.det(Q)
    R = R * sign

    signs = np.diag(
        np.diag(np.where(R < 0, -np.ones(R.shape), np.ones(R.shape))))
    transformed_vecs = R.dot(signs)
    return _reduced_form_vectors(transformed_vecs.T)
Exemple #6
0
 def _clone_bonds(self, clone_of=None):
     newone = clone_of[self]
     for c1, c2 in self.bonds():
         try:
             newone.add_bond((clone_of[c1], clone_of[c2]))
         except KeyError:
             raise MBuildError(
                 "Cloning failed. Compound contains bonds to "
                 "Particles outside of its containment hierarchy.")
Exemple #7
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)
Exemple #8
0
    def _find_particle_image(self, query, match, all_particles):
        """Find particle with the same index as match in a neighboring tile. """
        _, idxs = self.particle_kdtree.query(query.pos, k=10)

        neighbors = all_particles[idxs]

        for particle in neighbors:
            if particle.index == match.index:
                return particle
        raise MBuildError('Unable to find matching particle image while'
                          ' stitching bonds.')
Exemple #9
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
Exemple #10
0
    def from_mbuild(cls, compound):
        """
        Instantiates a CG_Compound and follows mb.Compound.deep_copy
        to copy particles and bonds to CG_Compound

        Parameters
        ----------
        compound : mb.Compound to be compied

        Returns
        -------
        CG_Compound
        """

        comp = cls()

        clone_dict = {}
        comp.name = deepcopy(compound.name)
        comp.periodicity = deepcopy(compound.periodicity)
        comp._pos = deepcopy(compound._pos)
        comp.port_particle = deepcopy(compound.port_particle)
        comp._check_if_contains_rigid_bodies = deepcopy(
            compound._check_if_contains_rigid_bodies
        )
        comp._contains_rigid = deepcopy(compound._contains_rigid)
        comp._rigid_id = deepcopy(compound._rigid_id)
        comp._charge = deepcopy(compound._charge)

        if compound.children is None:
            comp.children = None
        else:
            comp.children = OrderedSet()
        # Parent should be None initially.
        comp.parent = None
        comp.labels = OrderedDict()
        comp.referrers = set()
        comp.bond_graph = None
        for p in compound.particles():
            new_particle = mb.Particle(name=p.name, pos=p.xyz.flatten())
            comp.add(new_particle)
            clone_dict[p] = new_particle

        for c1, c2 in compound.bonds():
            try:
                comp.add_bond((clone_dict[c1], clone_dict[c2]))
            except KeyError:
                raise MBuildError(
                    "Cloning failed. Compound contains bonds to "
                    "Particles outside of its containment hierarchy."
                )
        return comp
Exemple #11
0
def _init_hoomd_dihedrals(structure, ref_energy=1.0):
    """ Periodic dihedrals (dubbed harmonic dihedrals in HOOMD) """
    # Identify the unique dihedral types before setting
    # need Hoomd 2.8.0 to use proper dihedral implemtnation
    # from this PR https://github.com/glotzerlab/hoomd-blue/pull/492
    version_numbers = _check_hoomd_version()
    if float(version_numbers[0]) < 2 or float(version_numbers[1]) < 8:
        from mbuild.exceptions import MBuildError

        raise MBuildError("Please upgrade Hoomd to at least 2.8.0")

    dihedral_type_params = {}
    for dihedral in structure.dihedrals:
        t1, t2 = dihedral.atom1.type, dihedral.atom2.type
        t3, t4 = dihedral.atom3.type, dihedral.atom4.type
        if [t2, t3] == sorted([t2, t3], key=natural_sort):
            dihedral_type = "-".join((t1, t2, t3, t4))
        else:
            dihedral_type = "-".join((t4, t3, t2, t1))
        if dihedral_type not in dihedral_type_params:
            if isinstance(dihedral.type, pmd.DihedralType):
                dihedral_type_params[dihedral_type] = dihedral.type
            elif isinstance(dihedral.type, pmd.DihedralTypeList):
                if len(dihedral.type) > 1:
                    warnings.warn(
                        "Multiple dihedral types detected" +
                        " for single dihedral, will ignore all except " +
                        " first dihedral type." +
                        "First dihedral type: {}".format(dihedral.type[0]))
                dihedral_type_params[dihedral_type] = dihedral.type[0]

    # Set the hoomd parameters
    # These are periodic torsions
    periodic_torsion = hoomd.md.dihedral.harmonic()
    for name, dihedral_type in dihedral_type_params.items():
        periodic_torsion.dihedral_coeff.set(
            name,
            k=2 * dihedral_type.phi_k / ref_energy,
            d=1,
            n=dihedral_type.per,
            phi_0=np.deg2rad(dihedral_type.phase),
        )

    return periodic_torsion
Exemple #12
0
    def from_uvec_lengths(cls, uvec, lengths, precision=None):
        """Generate a box from unit vectors and lengths."""
        uvec = np.asarray(uvec)
        uvec.reshape(3, 3)

        if not np.allclose(np.linalg.norm(uvec, axis=1), 1.0):
            raise MBuildError(
                "Unit vector magnitudes provided are not close to 1.0, "
                f"magnitudes: {np.linalg.norm(uvec, axis=1)}")

        lengths = np.asarray(lengths)
        lengths.reshape(1, 3)
        _validate_box_vectors(uvec)
        scaled_vec = (uvec.T * lengths).T
        (alpha, beta, gamma) = _calc_angles(scaled_vec)

        return cls(lengths=lengths,
                   angles=(alpha, beta, gamma),
                   precision=precision)
Exemple #13
0
def read_xyz(filename, compound=None):
    """Read an XYZ file.

    The expected format is as follows:
    The first line contains the number of atoms in the file. The second line
    contains a comment, which is not read. Remaining lines, one for each
    atom in the file, include an elemental symbol followed by X, Y, and Z
    coordinates in Angstroms. Columns are expected tbe separated by
    whitespace. See https://openbabel.org/wiki/XYZ_(format).

    Parameters
    ----------
    filename : str
        Path of the input file

    Returns
    -------
    compound : Compound

    Notes
    -----
    The XYZ file format neglects many important details, including bonds,
    residues, and box information.

    There are some other flavors of the XYZ file format and not all are
    guaranteed to be compatible with this reader. For example, the TINKER
    XYZ format is not expected to be properly read.
    """
    if compound is None:
        compound = mb.Compound()

    guessed_elements = set()

    with open(filename, "r") as xyz_file:
        n_atoms = int(xyz_file.readline())
        xyz_file.readline()
        coords = np.zeros(shape=(n_atoms, 3), dtype=np.float64)
        for row, _ in enumerate(coords):
            line = xyz_file.readline().split()
            if not line:
                msg = (
                    "Incorrect number of lines in input file. Based on the "
                    "number in the first line of the file, {} rows of atoms "
                    "were expected, but at least one fewer was found.")
                raise MBuildError(msg.format(n_atoms))
            coords[row] = line[1:4]
            coords[row] *= 0.1
            name = line[0]
            try:
                element = name.capitalize()
                element = element_from_symbol(element)
            except ElementError:
                if name not in guessed_elements:
                    warn("No matching element found for {}; the particle will "
                         "be added to the compound without an element "
                         "attribute.".format(name))
                    guessed_elements.add(name)
                element = None

            particle = mb.Compound(pos=coords[row], name=name, element=element)
            compound.add(particle)

        # Verify we have read the last line by ensuring the next line in blank
        line = xyz_file.readline().split()
        if line:
            msg = ("Incorrect number of lines in input file. Based on the "
                   "number in the first line of the file, {} rows of atoms "
                   "were expected, but at least one more was found.")
            raise MBuildError(msg.format(n_atoms))

    return compound
Exemple #14
0
 def pos(self, value):
     if not self.children:
         self._pos = value
     else:
         raise MBuildError('Cannot set position on a Compound that has'
                           ' children.')
Exemple #15
0
    def add(self,
            new_child,
            label=None,
            containment=True,
            replace=False,
            inherit_periodicity=True):
        """Add a part to the Compound.

        Note:
            This does not necessarily add the part to self.children but may
            instead be used to add a reference to the part to self.labels. See
            'containment' argument.

        Parameters
        ----------
        new_child : mb.Compound or list-like of mb.Compound
            The object(s) to be added to this Compound.
        label : str, optional
            A descriptive string for the part.
        containment : bool, optional, default=True
            Add the part to self.children.
        replace : bool, optional, default=True
            Replace the label if it already exists.

        """
        # Support batch add via lists, tuples and sets.
        if (isinstance(new_child, collections.Iterable)
                and not isinstance(new_child, string_types)):
            for child in new_child:
                self.add(child)
            return

        if not isinstance(new_child, Compound):
            raise ValueError('Only objects that inherit from mbuild.Compound '
                             'can be added to Compounds. You tried to add '
                             '"{}".'.format(new_child))

        # Create children and labels on the first add operation
        if self.children is None:
            self.children = OrderedSet()
        if self.labels is None:
            self.labels = OrderedDict()

        if containment:
            if new_child.parent is not None:
                raise MBuildError('Part {} already has a parent: {}'.format(
                    new_child, new_child.parent))
            self.children.add(new_child)
            new_child.parent = self

            if new_child.bond_graph is not None:
                if self.root.bond_graph is None:
                    self.root.bond_graph = new_child.bond_graph
                else:
                    self.root.bond_graph.compose(new_child.bond_graph)

                new_child.bond_graph = None

        # Add new_part to labels. Does not currently support batch add.
        if label is None:
            label = '{0}[$]'.format(new_child.__class__.__name__)

        if label.endswith('[$]'):
            label = label[:-3]
            if label not in self.labels:
                self.labels[label] = []
            label_pattern = label + '[{}]'

            count = len(self.labels[label])
            self.labels[label].append(new_child)
            label = label_pattern.format(count)

        if not replace and label in self.labels:
            raise MBuildError('Label "{0}" already exists in {1}.'.format(
                label, self))
        else:
            self.labels[label] = new_child
        new_child.referrers.add(self)

        if (inherit_periodicity and isinstance(new_child, Compound)
                and new_child.periodicity.any()):
            self.periodicity = new_child.periodicity