Ejemplo n.º 1
0
def check_cml_files(paths, linker, num_linkers, position_check_indices=[]):
    """ checks if more coordinates were changed in functionalized linker than just the newly added
    functional group atoms
    """
    for fnlinker_path in paths:
        fnlinker = Atoms.from_cml(fnlinker_path)
        match_pairs = find_unchanged_atom_pairs(linker, fnlinker)
        if len(match_pairs) == (len(linker) - num_linkers):
            marker = ""
        else:
            marker = "*"
            bad_indices = set(
                np.argwhere(fnlinker.positions[0:len(linker), :] !=
                            linker.positions[0:len(linker), :])[:, 0])
            marker += ".".join([str(i + 1) for i in bad_indices])

        if (fnlinker[position_check_indices].positions !=
                linker[position_check_indices].positions).any():
            position_marker = "P"
        else:
            position_marker = ""
        print("%s%s %s: unchanged %d/%d" %
              (marker, position_marker, fnlinker_path, len(match_pairs),
               len(fnlinker)))
Ejemplo n.º 2
0
def test_find_unchanged_atom_pairs__different_position_is_changed(linear_cnnc):
    offset_linear_cnns = linear_cnnc.copy()
    offset_linear_cnns.positions[2] += 0.5
    assert find_unchanged_atom_pairs(linear_cnnc,
                                     offset_linear_cnns) == [(0, 0), (1, 1),
                                                             (3, 3)]
Ejemplo n.º 3
0
def test_find_unchanged_atom_pairs__different_atom_type_is_changed(
        linear_cnnc):
    cncc = linear_cnnc.copy()
    cncc.atom_types[2] = cncc.atom_type_elements.index("C")
    assert find_unchanged_atom_pairs(linear_cnnc, cncc) == [(0, 0), (1, 1),
                                                            (3, 3)]
Ejemplo n.º 4
0
def test_find_unchanged_atom_pairs__same_structure_is_unchanged(linear_cnnc):
    assert find_unchanged_atom_pairs(linear_cnnc, linear_cnnc) == [(0, 0),
                                                                   (1, 1),
                                                                   (2, 2),
                                                                   (3, 3)]
Ejemplo n.º 5
0
def cml2lmpdat_typed_parameterized_for_new_atoms(fnlinker_path, linker_path=None, outpath="-"):

    uff_rules = {
        "H": [
            ("H_b", dict(n=2)),
            ("H_", {})
        ],
        "N": [
            ("N_R", dict(n=3, aromatic=True)),
            ("N_1", dict(neighbors=("N","N")))
        ],
        "O": [("O_1", dict(n=1))],
        "C": [("C_R", dict(n=3, aromatic=True))]
    }
    bond_order_rules = [({'N_1'}, 2), ({'N_1', 'N_2'}, 2)]

    linker = None
    if linker_path is not None:
        linker = Atoms.from_cml(linker_path)

    fnlinker = Atoms.from_cml(fnlinker_path)

    # assign uff atom types using mofun.rough_uff
    g = nx.Graph()
    g.add_edges_from(fnlinker.bonds)
    uff_types = ruff.assign_uff_atom_types(g, fnlinker.elements, override_rules=uff_rules)
    fnlinker.retype_atoms_from_uff_types(uff_types)

    # calculate all possible many-body terms
    fnlinker.calc_angles()
    fnlinker.calc_dihedrals()


    if linker is not None:
        # remove any dihedrals, angles and bonds that are unchanged from the original linker,
        # as we are only going to relax the new functional group, leaving everything else fixed.
        print("Num dihedrals, angles, bonds: %d, %d, %d" % (len(fnlinker.dihedrals), len(fnlinker.angles), len(fnlinker.bonds)))
        match_pairs = find_unchanged_atom_pairs(linker, fnlinker)
        unchanged_atom_indices = set()
        if len(match_pairs) > 0:
            unchanged_atom_indices = set(list(zip(*match_pairs))[1])

        # assign atoms to molecules where 0 is original linker, 1 is for new functional group atoms
        fnlinker.atom_groups = [0 if i in unchanged_atom_indices else 1 for i in range(len(fnlinker))]

        fnlinker.bonds = delete_if_all_in_set(fnlinker.bonds, unchanged_atom_indices)
        fnlinker.angles = delete_if_all_in_set(fnlinker.angles, unchanged_atom_indices)


    # calculate potential parameters and assign type #s to linker
    fnlinker.pair_params = ['%10.6f %10.6f # %s' % (*ruff.pair_params(a1), a1) for a1 in fnlinker.atom_type_labels]

    bond_types = [order_types([uff_types[b1], uff_types[b2]]) for b1, b2 in fnlinker.bonds]
    unique_bond_types = list(dict.fromkeys(bond_types).keys())
    fnlinker.bond_types = [unique_bond_types.index(bt) for bt in bond_types]
    bond_params = [(*ruff.bond_params(a1, a2, bond_order_rules=bond_order_rules), "%s %s" % (a1, a2)) for (a1, a2) in unique_bond_types]
    fnlinker.bond_type_params = ['%10.6f %10.6f # %s' % params for params in bond_params]

    angle_types = [order_types([uff_types[a] for a in atoms]) for atoms in fnlinker.angles]
    unique_angle_types = list(dict.fromkeys(angle_types).keys())
    fnlinker.angle_types = [unique_angle_types.index(a) for a in angle_types]
    angle_params = [(*ruff.angle_params(*a_ids, bond_order_rules=bond_order_rules), "%s %s %s" % a_ids) for a_ids in unique_angle_types]
    fnlinker.angle_type_params = [angle2lammpsdat(a) for a in angle_params]

    num_dihedrals_per_bond = Counter([order_types([a2, a3]) for _, a2, a3, _ in fnlinker.dihedrals])
    if linker is not None:
        fnlinker.dihedrals = delete_if_all_in_set(fnlinker.dihedrals, unchanged_atom_indices)

    dihedral_types = [(*order_types([uff_types[a] for a in atoms]),
                            num_dihedrals_per_bond[order_types([atoms[1], atoms[2]])])
                        for atoms in fnlinker.dihedrals]
    unique_dihedral_types = list(dict.fromkeys(dihedral_types).keys())
    dihedral_params = [ruff.dihedral_params(*a_ids, bond_order_rules=bond_order_rules) for a_ids in unique_dihedral_types]

    # delete any dihedrals when the params come back None (i.e. for *_1)
    for i in reversed(range(len(dihedral_params))):
        if dihedral_params[i] is None:
            none_dihedral = unique_dihedral_types[i]
            print(len(fnlinker.dihedrals), len(dihedral_types), len(unique_dihedral_types), len(dihedral_params))

            fnlinker.dihedrals = [d for j, d in enumerate(fnlinker.dihedrals) if dihedral_types[j] != none_dihedral]
            dihedral_types = [d for d in dihedral_types if d != none_dihedral]

            del(unique_dihedral_types[i])
            del(dihedral_params[i])
            print(len(fnlinker.dihedrals), len(dihedral_types), len(unique_dihedral_types), len(dihedral_params))

    # assign dihedral types
    fnlinker.dihedral_types = [unique_dihedral_types.index(a) for a in dihedral_types]
    dihedral_params = [(*ruff.dihedral_params(*a_ids, bond_order_rules=bond_order_rules), "%s %s %s %s M=%d" % a_ids) for a_ids in unique_dihedral_types]
    fnlinker.dihedral_type_params = ['%s %10.6f %d %d # %s' % params for params in dihedral_params]

    print("Num dihedrals, angles, bonds: %d, %d, %d" % (len(fnlinker.dihedrals), len(fnlinker.angles), len(fnlinker.bonds)))

    # output lammps-data file
    with open(outpath, "w") as f:
        fnlinker.to_lammps_data(f)
Ejemplo n.º 6
0
def replace_pattern_in_structure(
        structure,
        search_pattern,
        replace_pattern,
        replace_fraction=1.0,
        axis1a_idx=0,
        axis1b_idx=-1,
        axis2_idx=None,
        return_num_matches=False,
        replace_all=False,
        verbose=False,
        ignore_positions_check=False,
        positions_check_max_delta=0.1,
        ignore_atoms_should_not_be_deleted_twice=False):
    """Replaces all instances of `pattern` in `structure` with the `replace_pattern`.

    Works across periodic boundary conditions.

    WARNING: the replace pattern _MUST_ be on the same coordinate system as the search_pattern. If
    there are atoms that remain the same between the search and replace patterns, they must have the
    exact same coordinates. If these were to be offset, or moved, then when the replacement pattern
    gets inserted into the structure, then the replacement will also be offset.

    Args:
        structure (Atoms): an Atoms object to search in.
        search_pattern (Atoms): an Atoms object to search for.
        replace_pattern (Atoms): an Atoms object to search for.
        replace_fraction (float): how many instances of the search_pattern found in the structure get replaced by the replace pattern.
        axis1a_idx (float): index in search_pattern of first point defining the directional axis of the search_pattern. Necessary for handling symmetric patterns.
        axis1b_idx (float): index in search_pattern of second point defining the directional axis of the search_pattern. Necessary for handling symmetric patterns.
        axis2_idx (float): index in search_pattern of third point defining the orientational axis of the search_pattern. Necessary for handling symmetric patterns.
        replace_all (bool): replaces all atoms even if positions and elements match exactly
        verbose (bool): print debugging info.
        ignore_positions_check (bool): do not raise an error when the search pattern cannot be rotated to overlap with
            the atoms of the match pattern. In general, this should not be turned off, but there may be cases where it
            is helpful, such as when deleting all atoms in a symmetric search pattern. In that case, it doesn't matter
            what rotations would be performed on the replacement pattern, since the search pattern is being deleted.
        positions_check_max_delta (float): maximum allowed difference in position between each atom in the search and
            match patterns, before an error gets raised.
        ignore_atoms_should_not_be_deleted_twice (bool): don't raise an AtomsShouldNotBeDeletedTwice exception when
            two matches would delete the same atoms.
    Returns:
        Atoms: the structure after search_pattern is replaced by replace_pattern.
    """
    search_pattern = search_pattern.copy()
    replace_pattern = replace_pattern.copy()

    match_indices, match_positions = find_pattern_in_structure(
        structure, search_pattern, return_positions=True)

    if replace_fraction < 1.0:
        replace_indices = random.sample(list(range(len(match_positions))),
                                        k=round(replace_fraction *
                                                len(match_positions)))
        match_indices = [match_indices[i] for i in replace_indices]
        match_positions = match_positions[replace_indices]

    if verbose:
        print("match_indices / positions: ", match_indices, match_positions)

    # translate both search and replace patterns so that first atom of search pattern is at the origin
    replace_pattern.translate(-search_pattern.positions[axis1a_idx])
    search_pattern.translate(-search_pattern.positions[axis1a_idx])
    search_axis = search_pattern.positions[axis1b_idx]
    if verbose: print("search pattern axis: ", search_axis)

    replace2search_pattern_map = {
        k: v
        for (k,
             v) in find_unchanged_atom_pairs(replace_pattern, search_pattern)
    }

    if len(search_pattern) > 2:
        # note that we find an orientation point if there are nore than two atoms in the search pattern, but it is still
        # possible that all the atoms like on the search axis. In that, case a point on the axis will be returned and
        # there is an unnecessary final rotation to "align" that point.
        if axis2_idx is None:
            search_orientation_point_idx = position_index_farthest_from_axis(
                search_axis, search_pattern)
        else:
            search_orientation_point_idx = axis2_idx

        search_orientation_point = search_pattern.positions[
            search_orientation_point_idx]
        search_orientation_axis = search_orientation_point - (
            np.dot(search_orientation_point, search_axis) /
            np.dot(search_axis, search_axis)) * search_axis
        if verbose:
            print("search pattern orientation point index: ",
                  search_orientation_point_idx)
            print("search pattern orientation axis: ", search_orientation_axis)

    new_structure = structure.copy()
    to_delete = set()
    if len(replace_pattern) == 0:
        to_delete |= set([idx for match in match_indices for idx in match])
    else:
        offsets = new_structure.extend_types(replace_pattern)
        for m_i, atom_positions in enumerate(match_positions):
            new_atoms = replace_pattern.copy()
            if not ignore_positions_check:
                chk_search_pattern = search_pattern.copy()
            if verbose:
                print("--------------")
                print(m_i)
                print("average position: ", np.average(atom_positions, axis=0))

            if len(atom_positions) > 1:
                match_axis = atom_positions[axis1b_idx] - atom_positions[
                    axis1a_idx]
                if verbose: print("match axis: ", match_axis)

                # the first quaternion aligns the search pattern axis points with the axis points
                # found in the structure and is used to rotate the replacement pattern to match
                q1 = quaternion_from_two_vectors(search_axis, match_axis)
                new_atoms.positions = q1.apply(new_atoms.positions)
                if not ignore_positions_check:
                    chk_search_pattern.positions = q1.apply(
                        chk_search_pattern.positions)
                if verbose:
                    print("q1: ", q1.as_quat())

                if len(atom_positions) > 2:
                    match_orientation_point = atom_positions[
                        search_orientation_point_idx] - atom_positions[
                            axis1a_idx]
                    match_orientation_axis = match_orientation_point - (
                        np.dot(match_orientation_point, match_axis) /
                        np.dot(match_axis, match_axis)) * match_axis
                    if verbose:
                        print("match orientation axis: ",
                              match_orientation_axis)
                    q1_o_axis = q1.apply(search_orientation_axis)

                    # the second quaternion is a rotation around the found axis in the structure and
                    # aligns the orientation axis point to its placement in the structure.
                    q2 = quaternion_from_two_vectors_around_axis(
                        match_orientation_axis, q1_o_axis, match_axis)
                    if verbose:
                        print("orienting using match orientation point: ",
                              match_orientation_point)
                        print("from match orientation axis: ",
                              match_orientation_axis)
                        print("to (rotated) search pattern orientation axis: ",
                              q1_o_axis)
                        print("q2: ", q2.as_quat())

                    new_atoms.positions = q2.apply(new_atoms.positions)
                    if not ignore_positions_check:
                        chk_search_pattern.positions = q2.apply(
                            chk_search_pattern.positions)

            # move replacement atoms into correct position
            new_atoms.translate(atom_positions[axis1a_idx])

            if not ignore_positions_check:
                chk_search_pattern.translate(atom_positions[axis1a_idx])

            if not ignore_positions_check:
                assert_positions_are_unchanged(
                    atom_positions,
                    chk_search_pattern.positions,
                    max_delta=positions_check_max_delta,
                    verbose=verbose,
                    raise_exception=True)

            new_atoms.positions %= np.diag(new_structure.cell)

            if verbose:
                print("new atoms after translate:\n", new_atoms.positions)

            structure_index_map = {}
            if not replace_all:
                structure_index_map = {
                    k: match_indices[m_i][v]
                    for k, v in replace2search_pattern_map.items()
                }
                new_structure.extend(new_atoms,
                                     offsets=offsets,
                                     structure_index_map=structure_index_map)
            else:
                new_structure.extend(new_atoms, offsets=offsets)

            to_delete_linker = set(match_indices[m_i]) - set(
                structure_index_map.values())
            if (to_delete.isdisjoint(to_delete_linker)
                    or ignore_atoms_should_not_be_deleted_twice):
                to_delete |= set(to_delete_linker)
            else:
                raise AtomsShouldNotBeDeletedTwice()

    del (new_structure[list(to_delete)])

    if return_num_matches:
        return new_structure, len(match_indices)
    else:
        return new_structure