Example #1
0
def get_linear_bend_inds(coords3d,
                         cbm,
                         bends,
                         min_deg=175,
                         max_bonds=4,
                         logger=None):
    linear_bends = list()
    complements = list()

    if min_deg is None:
        return linear_bends, complements

    bm = squareform(cbm)
    for bend in bends:
        deg = np.rad2deg(Bend._calculate(coords3d, bend))
        bonds = sum(bm[bend[1]])
        if (deg >= min_deg) and (bonds <= max_bonds):
            log(
                logger,
                f"Bend {bend}={deg:.1f}° is (close to) linear. "
                "Creating linear bend & complement.",
            )
            linear_bends.append(bend)
            complements.append(bend)
    return linear_bends, complements
Example #2
0
def get_hydrogen_bond_inds(atoms, coords3d, bond_inds, logger=None):
    tmp_sets = [frozenset(bi) for bi in bond_inds]
    # Check for hydrogen bonds as described in [1] A.1 .
    # Find hydrogens bonded to small electronegative atoms X = (N, O
    # F, P, S, Cl).
    hydrogen_inds = [i for i, a in enumerate(atoms) if a.lower() == "h"]
    x_inds = [
        i for i, a in enumerate(atoms) if a.lower() in "n o f p s cl".split()
    ]
    hydrogen_bond_inds = list()
    for h_ind, x_ind in it.product(hydrogen_inds, x_inds):
        as_set = set((h_ind, x_ind))
        if as_set not in tmp_sets:
            continue
        # Check if distance of H to another electronegative atom Y is
        # greater than the sum of their covalent radii but smaller than
        # the 0.9 times the sum of their van der Waals radii. If the
        # angle X-H-Y is greater than 90° a hydrogen bond is asigned.
        y_inds = set(x_inds) - set((x_ind, ))
        for y_ind in y_inds:
            y_atom = atoms[y_ind].lower()
            cov_rad_sum = CR["h"] + CR[y_atom]
            distance = Stretch._calculate(coords3d, (h_ind, y_ind))
            vdw = 0.9 * (VDW_RADII["h"] + VDW_RADII[y_atom])
            angle = Bend._calculate(coords3d, (x_ind, h_ind, y_ind))
            if (cov_rad_sum < distance < vdw) and (angle > np.pi / 2):
                hydrogen_bond_inds.append((h_ind, y_ind))
                log(
                    logger,
                    f"Detected hydrogen bond between atoms {h_ind} "
                    f"({atoms[h_ind]}) and {y_ind} ({atoms[y_ind]})",
                )

    return hydrogen_bond_inds
Example #3
0
def dihedrals_are_valid(coords3d, dihedral_inds, logger=None):
    valid = [dihedral_valid(coords3d, inds) for inds in dihedral_inds]
    invalid = [dihedral_ind for dihedral_ind, v in zip(dihedral_inds, valid) if not v]
    if invalid:
        log(logger, f"Invalid dihedrals: {invalid}")
    all_valid = all(valid)
    return all_valid
Example #4
0
def augment_bonds(geom, root=0, proj=False):
    assert geom.coord_type != "cart"
    log(logger, "Trying to augment bonds.")

    hessian = geom.cart_hessian
    try:
        energy = geom.energy
    except AttributeError:
        energy = None

    func = find_missing_bonds_by_projection if proj else find_missing_strong_bonds

    missing_bonds = func(geom, hessian, root=root)

    if missing_bonds:
        aux_bond_pt = PrimTypes.AUX_BOND
        missing_aux_bonds = [(aux_bond_pt, *mbond) for mbond in missing_bonds]
        print("\t@Missing bonds:", missing_bonds)
        new_geom = Geometry(
            geom.atoms,
            geom.cart_coords,
            coord_type=geom.coord_type,
            coord_kwargs={
                "define_prims": missing_aux_bonds,
            },
        )
        new_geom.set_calculator(geom.calculator)
        new_geom.energy = energy
        new_geom.cart_hessian = hessian
        return new_geom
    else:
        return geom
Example #5
0
def update_internals(new_coords3d, old_internals, primitives, dihedral_inds,
                     check_dihedrals=False, logger=None):
    prim_internals = eval_primitives(new_coords3d, primitives)
    new_internals = [prim_int.val for prim_int in prim_internals]
    internal_diffs = np.array(new_internals) - old_internals

    dihedrals = [prim_internals[i] for i in dihedral_inds]
    dihedral_num = len(dihedrals)
    dihedral_diffs = internal_diffs[-dihedral_num:]

    # Find differences that are shifted by 2*pi
    shifted_by_2pi = np.abs(np.abs(dihedral_diffs) - 2 * np.pi) < np.pi / 2
    new_dihedrals = np.array([dihed.val for dihed in dihedrals])
    if any(shifted_by_2pi):
        new_dihedrals[shifted_by_2pi] -= (
            2 * np.pi * np.sign(dihedral_diffs[shifted_by_2pi])
        )
        # Update values
        for dihed, new_val in zip(dihedrals, new_dihedrals):
            dihed.val = new_val
    if check_dihedrals and (
        not dihedrals_are_valid(new_coords3d, [prim_int.inds for prim_int in dihedrals])
    ):
        log(logger, "Dihedral(s) became invalid! Need new internal coordinates!")
        raise NeedNewInternalsException(new_coords3d)

    return prim_internals
    def do_line_search(e0, e1, g0, g1, prev_step, maximize, logger=None):
        poly_fit_kwargs = {
            "e0": e0,
            "e1": e1,
            "g0": g0,
            "g1": g1,
            "maximize": maximize,
        }
        if not maximize:
            poly_fit_kwargs.update({
                "g0": prev_step.dot(g0),
                "g1": prev_step.dot(g1),
            })
        prefix = "Max" if maximize else "Min"

        fit_result = poly_fit.quartic_fit(**poly_fit_kwargs)
        fit_energy = None
        fit_grad = None
        fit_step = None
        if fit_result and (0.0 < fit_result.x <= 2.0):
            x = fit_result.x
            log(logger,
                f"{prefix}-subpsace interpolation succeeded: x={x:.6f}")
            fit_energy = fit_result.y
            fit_step = (1 - x) * -prev_step
            fit_grad = (1 - x) * g0 + x * g1
        return fit_energy, fit_grad, fit_step
Example #7
0
def connect_fragments(cdm, fragments, max_aux=3.78, aux_factor=1.3, logger=None):
    """Determine the smallest interfragment bond for a list
    of fragments and a condensed distance matrix."""
    if len(fragments) > 1:
        log(
            logger,
            f"Detected {len(fragments)} fragments. Generating interfragment bonds.",
        )
    dist_mat = squareform(cdm)
    interfrag_inds = list()
    aux_interfrag_inds = list()
    for frag1, frag2 in it.combinations(fragments, 2):
        log(logger, f"\tConnecting {len(frag1)} atom and {len(frag2)} atom fragment")
        inds = [(i1, i2) for i1, i2 in it.product(frag1, frag2)]
        distances = np.array([dist_mat[ind] for ind in inds])

        # Determine minimum distance bond
        min_ind = distances.argmin()
        min_dist = distances[min_ind]
        interfrag_bond = tuple(inds[min_ind])
        interfrag_inds.append(interfrag_bond)
        log(logger, f"\tMinimum distance bond: {interfrag_bond}, {min_dist:.4f} au")

        # Determine auxiliary interfragment bonds that are either below max_aux
        # (default 2 Å, ≈ 3.78 au), or less than aux_factor (default 1.3) times the
        # minimum interfragment distance.
        below_max_aux = [
            ind for ind in inds if (dist_mat[ind] < max_aux) and (ind != interfrag_bond)
        ]
        if below_max_aux:
            log(
                logger,
                f"\tAux. interfrag bonds below {max_aux*BOHR2ANG:.2f} Å:\n"
                + "\n".join(
                    [f"\t\t{ind}: {dist_mat[ind]:.4f} au" for ind in below_max_aux]
                ),
            )
        scaled_min_dist = aux_factor * min_dist
        above_min_dist = [
            ind
            for ind in inds
            if (dist_mat[ind] < scaled_min_dist)
            and (ind != interfrag_bond)
            and (ind not in below_max_aux)
        ]
        if above_min_dist:
            log(
                logger,
                f"\tAux. interfrag bonds below {aux_factor:.2f} * min_dist:\n"
                + "\n".join(
                    [f"\t\t{ind}: {dist_mat[ind]:.4f} au" for ind in above_min_dist]
                ),
            )
        aux_interfrag_inds.extend(below_max_aux)
        aux_interfrag_inds.extend(above_min_dist)
    # Or as Philipp proposed: two loops over the fragments and only
    # generate interfragment distances. So we get a full matrix with
    # the original indices but only the required distances.
    return interfrag_inds, aux_interfrag_inds
Example #8
0
def get_primitives(coords3d, typed_prims, logger=None):
    primitives = list()
    for type_, *indices in typed_prims:
        cls = PrimMap[type_]
        primitives.append(cls(indices=indices))

    msg = ("Defined primitives\n" + "\n".join([
        f"\t{i:03d}: {str(p.indices): >14}" for i, p in enumerate(primitives)
    ]) + "\n")
    log(logger, msg)
    return primitives
Example #9
0
def get_bend_inds(coords3d, bond_inds, min_deg, max_deg, logger=None):
    bond_sets = {frozenset(bi) for bi in bond_inds}

    bend_inds = list()
    for bond_set1, bond_set2 in it.combinations(bond_sets, 2):
        union = bond_set1 | bond_set2
        if len(union) == 3:
            indices, _ = sort_by_central(bond_set1, bond_set2)
            if not bend_valid(coords3d, indices, min_deg, max_deg):
                log(logger, f"Bend {indices} is not valid!")
                continue
            bend_inds.append(indices)

    return bend_inds
Example #10
0
def get_lindh_precon(
    atoms,
    coords,
    bonds=None,
    bends=None,
    dihedrals=None,
    c_stab=0.0103,
    logger=None,
):
    """c_stab = 0.00103 hartree/bohr² corresponds to 0.1 eV/Ų as
    given in the paper."""

    if bonds is None:
        bonds = list()
    if bends is None:
        bends = list()
    if dihedrals is None:
        dihedrals = list()

    dim = coords.size
    c3d = coords.reshape(-1, 3)

    # Calculate Lindh force constants
    ks = get_lindh_k(atoms, c3d, bonds, bends)

    grad_funcs = {
        2: dq_b,  # Bond
        3: dq_a,  # Bend
        4: dq_d,  # Dihedral
    }

    P = dok_matrix((dim, dim))
    for inds, k in zip(it.chain(bonds, bends, dihedrals), ks):
        # First derivatives of internal coordinates w.r.t cartesian coordinates
        int_grad = grad_funcs[len(inds)](*c3d[inds].flatten())
        # Assign to the correct cartesian indices
        cart_inds = np.array(
            list(it.chain(*[range(3 * i, 3 * i + 3) for i in inds])))
        P[cart_inds[:, None],
          cart_inds[None, :]] += abs(k) * np.outer(int_grad, int_grad)

    # Add stabilization to diagonal
    P[np.diag_indices(dim)] += c_stab
    P = P.tocsc()
    filled = P.size / dim**2
    log(logger, f"Preconditioner P has {P.size} entries ({filled:.2%} filled)")

    return P
Example #11
0
def update_internals(
    new_coords3d,
    old_internals,
    primitives,
    dihedral_inds,
    check_dihedrals=False,
    logger=None,
):
    prim_internals = eval_primitives(new_coords3d, primitives)
    new_internals = [prim_int.val for prim_int in prim_internals]
    internal_diffs = np.array(new_internals) - old_internals

    dihedrals = [prim_internals[i] for i in dihedral_inds]
    dihedral_num = len(dihedrals)
    dihedral_diffs = internal_diffs[-dihedral_num:]

    # Find differences that are shifted by 2*pi
    shifted_by_2pi = np.abs(np.abs(dihedral_diffs) - 2 * np.pi) < np.pi / 2
    new_dihedrals = np.array([dihed.val for dihed in dihedrals])
    if any(shifted_by_2pi):
        new_dihedrals[shifted_by_2pi] -= (
            2 * np.pi * np.sign(dihedral_diffs[shifted_by_2pi]))
        # Update values
        for dihed, new_val in zip(dihedrals, new_dihedrals):
            dihed.val = new_val
    # See if dihedrals became invalid (collinear atoms)
    if check_dihedrals:
        are_valid = [
            dihedral_valid(new_coords3d, prim.inds) for prim in dihedrals
        ]
        try:
            first_dihedral = dihedral_inds[0]
        except IndexError:
            first_dihedral = 0
        invalid_inds = [
            i + first_dihedral for i, is_valid in enumerate(are_valid)
            if not is_valid
        ]
        if len(invalid_inds) > 0:
            invalid_prims = [primitives[i] for i in invalid_inds]
            log(logger,
                "Dihedral(s) became invalid! Need new internal coordinates!")
            raise NeedNewInternalsException(new_coords3d,
                                            invalid_inds=invalid_inds,
                                            invalid_prims=invalid_prims)

    return prim_internals
Example #12
0
def find_bonds_bends_dihedrals(geom, bond_factor=1.3, min_deg=15, max_deg=175):
    log(logger,
        f"Detecting bonds, bends and dihedrals for {len(geom.atoms)} atoms.")
    bonds, bends = find_bonds_bends(geom,
                                    bond_factor=bond_factor,
                                    min_deg=min_deg,
                                    max_deg=max_deg)
    proper_diheds, improper_diheds = get_dihedral_inds(geom.coords3d,
                                                       bonds,
                                                       bends,
                                                       max_deg=max_deg)
    log(
        logger,
        f"Found {len(proper_diheds)} proper and improper "
        f"{len(improper_diheds)} dihedrals.",
    )
    return bonds, bends, proper_diheds + improper_diheds
Example #13
0
def afir_closure(fragment_indices,
                 cov_radii,
                 gamma,
                 rho=1,
                 p=6,
                 prefactor=1.0,
                 logger=None):
    """rho=1 pushes fragments together, rho=-1 pulls fragments apart."""

    # See https://onlinelibrary.wiley.com/doi/full/10.1002/qua.24757
    # Eq. (9) for extension to 3 fragments.
    assert len(fragment_indices) == 2

    inds = np.array(list(it.product(*fragment_indices)))
    cov_rad_sums = cov_radii[inds].sum(axis=1)

    # 3.8164 Angstrom in Bohr
    R0 = 7.21195
    # 1.0061 kJ/mol to Hartree
    epsilon = 0.000383203368

    # Avoid division by zero for gamma = 0.
    if gamma == 0.0:
        alpha = 0.0
    else:
        alpha = gamma / ((2**(-1 / 6) -
                          (1 + (1 + gamma / epsilon)**0.5)**(-1 / 6)) * R0)

    rho_verbose = {1: ("pushing", "together"), -1: ("pulling", "apart")}
    w1, w2 = rho_verbose[rho]
    log(
        logger,
        f"Creating AFIR closure with α={alpha:.6f}, prefactor {prefactor:.6f}, "
        f"rho={rho}, {w1} framgents {w2}",
    )

    def afir_func(coords3d):
        diffs = anp.diff(coords3d[inds], axis=1).reshape(-1, 3)
        rs = anp.linalg.norm(diffs, axis=1)

        omegas = (cov_rad_sums / rs)**p

        f = prefactor * alpha * rho * (omegas * rs).sum() / omegas.sum()
        return f

    return afir_func
Example #14
0
def find_bonds_bends(geom, bond_factor=1.3, min_deg=15, max_deg=175):
    log(logger, "Starting detection of bonds and bends.")
    bonds = find_bonds(geom, bond_factor=bond_factor)
    log(logger, f"Found {len(bonds)} bonds.")
    bends = find_bends(geom.coords3d, bonds, min_deg=min_deg, max_deg=max_deg)
    log(logger, f"Found {len(bends)} bends.")
    return bonds, bends
Example #15
0
def precon_getter(geom, c_stab=0.0103, kind="full", logger=None):
    valid_kinds = ("full", "full_fast", "bonds", "bonds_bends")
    assert kind in valid_kinds, f"Invalid kind='{kind}'! Valid kinds are: {valid_kinds}"

    atoms = geom.atoms
    # Default empty lists for coordinates that may be skipped
    # for kind != "full".
    bends = list()
    dihedrals = list()
    if kind == "full":
        internal = RedundantCoords(atoms, geom.cart_coords)
        bonds = internal.bond_indices
        bends = internal.bending_indices
        dihedrals = internal.dihedrals
    elif kind == "full_fast":
        bonds, bends, dihedrals = find_bonds_bends_dihedrals(geom)
    elif kind == "bonds_bends":
        bonds, bends = find_bonds_bends(geom)
    elif kind == "bonds":
        bonds = find_bonds(geom)

    msg = (
        f"Constructing preconditioner from {len(bonds)} bonds, {len(bends)} bends "
        f"and {len(dihedrals)} dihedrals (kind='{kind}').")
    log(logger, msg)

    def wrapper(coords):
        P = get_lindh_precon(
            atoms,
            coords,
            bonds,
            bends,
            dihedrals,
            c_stab=c_stab,
            logger=logger,
        )
        return P

    return wrapper
Example #16
0
def check_typed_prims(
    coords3d,
    typed_prims,
    bend_min_deg,
    dihed_max_deg,
    lb_min_deg,
    logger=None,
    check_bends=True,
):
    if check_bends:
        bend_func = lambda indices: bend_still_valid(
            coords3d, indices, min_deg=bend_min_deg, max_deg=lb_min_deg
        )
    else:
        bend_func = lambda indices: True
    funcs = {
        PrimTypes.BEND: bend_func,
        PrimTypes.PROPER_DIHEDRAL: lambda indices: dihedral_valid(
            coords3d,
            indices,
            deg_thresh=dihed_max_deg,
        ),
        PrimTypes.IMPROPER_DIHEDRAL: lambda indices: dihedral_valid(
            coords3d,
            indices,
            deg_thresh=dihed_max_deg,
        ),
    }
    valid_typed_prims = list()
    for i, (type_, *indices) in enumerate(typed_prims):
        try:
            valid = funcs[type_](indices)
        except KeyError:
            valid = True
        if valid:
            valid_typed_prims.append((type_, *indices))
        else:
            log(logger, f"Primitive {(type_, *indices)} is invalid!")
    return valid_typed_prims
Example #17
0
def transform_int_step(
    int_step,
    old_cart_coords,
    cur_internals,
    B_prim,
    primitives,
    check_dihedrals=False,
    cart_rms_thresh=1e-6,
    rcond=1e-8,
    logger=None,
):
    """Transformation is done in primitive internals, so int_step must be given
    in primitive internals and not in DLC!"""

    new_cart_coords = old_cart_coords.copy()
    remaining_int_step = int_step
    target_internals = cur_internals + int_step

    Bt_inv_prim = np.linalg.pinv(B_prim.dot(B_prim.T), rcond=rcond).dot(B_prim)
    dihedral_inds = np.array(
        [i for i, primitive in enumerate(primitives) if isinstance(primitive, Torsion)]
    )

    last_rms = 9999
    old_internals = cur_internals
    backtransform_failed = True
    for i in range(25):
        cart_step = Bt_inv_prim.T.dot(remaining_int_step)
        cart_rms = np.sqrt(np.mean(cart_step ** 2))
        # Update cartesian coordinates
        new_cart_coords += cart_step
        # Determine new internal coordinates
        new_prim_ints = update_internals(
            new_cart_coords.reshape(-1, 3), old_internals, primitives, dihedral_inds,
            check_dihedrals=check_dihedrals, logger=logger,
        )
        new_internals = [prim.val for prim in new_prim_ints]
        remaining_int_step = target_internals - new_internals
        internal_rms = np.sqrt(np.mean(remaining_int_step ** 2))
        log(logger, f"Cycle {i}: rms(Δcart)={cart_rms:1.4e}, rms(Δint.) = {internal_rms:1.5e}")

        # This assumes the first cart_rms won't be > 9999 ;)
        if cart_rms < last_rms:
            # Store results of the conversion cycle for laster use, if
            # the internal-cartesian-transformation goes bad.
            best_cycle = (new_cart_coords.copy(), new_internals.copy())
            best_cycle_ind = i
        elif i != 0:
            # If the conversion somehow fails we fallback to the best previous step.
            log(logger, f"Backconversion failed! Falling back to step {best_cycle_ind}")
            new_cart_coords, new_internals = best_cycle
            break
        else:
            raise Exception(
                "Internal-cartesian back-transformation already "
                "failed in the first step. Aborting!"
            )
        old_internals = new_internals

        last_rms = cart_rms
        if cart_rms < cart_rms_thresh:
            log(logger, f"Internal->Cartesian transformation converged in {i+1} cycle(s)!")
            backtransform_failed = False
            break

    # if check_dihedrals and (
        # not dihedrals_are_valid(new_cart_coords.reshape(-1, 3), dihedral_inds)
    # ):
        # raise NeedNewInternalsException(new_cart_coords)

    log(logger, "")

    # Return the difference between the new cartesian coordinates that yield
    # the desired internal coordinates and the old cartesian coordinates.
    cart_step = new_cart_coords - old_cart_coords
    return new_prim_ints, cart_step, backtransform_failed
Example #18
0
def md(geom,
       v0,
       steps,
       dt,
       remove_com_v=True,
       thermostat=None,
       T=298.15,
       timecon=100,
       term_funcs=None,
       constraints=None,
       constraint_kwargs=None,
       verbose=True):
    """Velocity verlet integrator.

    Parameters
    ----------
    geom : Geometry
        The system for which the dynamics are to be run.
    v0 : np.array, floats
        Initial velocities in Bohr/fs.
    steps : float
        Number of simulation steps.
    dt : float
        Timestep in fs.
    remove_com_v : bool, default=True
        Remove center-of-mass velocity.
    thermostat : str, optional, default None
        Which and whether to use a thermostat.
    T : float, optional, default=None
        Desired temperature in thermostated runs.
    timecon : float
        Timeconsanst of the thermostat in fs.
    term_funcs : dict, optional
        Iterable of functions that are called with the atomic
        coordinates in every MD cycle and result in termination
    constraints : 2d iterable, optional
        2D iterable containing atom indices describing constrained
        bond lengths.
        of the MD integration when they evaluate to true.
    constraint_kwargs : dict, optional
        Keyword arguments for the constraint algorithm.
    verbose : bool, default=True
        Do additional printing when True.
    """

    assert geom.coord_type == "cart"

    if term_funcs is None:
        term_funcs = dict()

    if verbose:
        t_ps = steps * dt * 1e-3  # Total simulation time
        print(
            f"Doing {steps} steps of {dt:.4f} fs for a total of {t_ps:.2f} ps."
        )

    energy_forces_getter = energy_forces_getter_closure(geom)

    if constraint_kwargs is None:
        constraint_kwargs = dict()

    if remove_com_v and (not thermostat):
        print(
            "Center of mass velocity removal requested, but thermostat is disabled. "
            "Disabling velocity removal.")
        remove_com_v = False

    # Fixed degrees of freedom
    fixed_dof = 0
    if remove_com_v:
        fixed_dof += 3
    constrained_md = constraints is not None
    # Get RATTLE function from closure for constrained MD
    if constrained_md:
        fixed_dof += len(constraints)
        rattle = rattle_closure(geom,
                                constraints,
                                dt,
                                energy_forces_getter=energy_forces_getter,
                                **constraint_kwargs)

    if thermostat is not None:
        thermo_func = THERMOSTATS[thermostat]
        tau_t = dt / timecon
        sigma = kinetic_energy_for_temperature(len(geom.atoms),
                                               T,
                                               fixed_dof=fixed_dof)

    # In amu
    masses = geom.masses
    masses_rep = geom.masses_rep
    total_mass = masses.sum()

    x = geom.cart_coords
    # v is given in Bohr/fs
    v = v0
    a_prev = np.zeros_like(x)
    xs = list()
    Ts = list()
    E_tots = list()

    E_pot, forces = energy_forces_getter(geom.coords)

    t_cur = 0
    terminate = False
    terminate_key = None
    T_avg = 0
    log(logger, f"Running MD with Δt={dt:.2f} fs for {steps} steps.")
    for step in range(steps):
        xs.append(x.copy())

        E_kin = kinetic_energy_from_velocities(masses, v.reshape(-1, 3))
        T = temperature_for_kinetic_energy(len(masses),
                                           E_kin,
                                           fixed_dof=fixed_dof)
        T_avg += T
        Ts.append(T)
        E_tot = E_pot + E_kin
        E_tots.append(E_tot)

        status_msg = (
            f"Step {step:05d}  {t_cur*1e-3: >6.2f} ps  E={E_tot: >8.6f} E_h  "
            f"T={T: >8.2f} K <T>={T_avg/(step+1): >8.2f}")
        if (step % 25) == 0:
            log(logger, status_msg)
            if verbose: print(status_msg)

        if thermostat:
            E_kin_new = thermo_func(E_kin, sigma, v.size - fixed_dof, tau_t)
            scale = (E_kin_new / E_kin)**0.5
            v *= scale

        # RATTLE algorithm
        if constrained_md:
            x, v, E_pot, forces = rattle(x, v, forces)
        # Simple Velocity-Verlet integration
        else:
            E_pot, forces = energy_forces_getter(geom.coords)
            # Acceleration, convert from Hartree / (Bohr * amu) to Bohr/fs²
            a = forces / masses_rep * FORCE2ACC
            v += .5 * (a + a_prev) * dt
            if remove_com_v:
                v -= v * masses_rep / total_mass
            # v*dt = Bohr/fs * fs -> Bohr
            # a*dt**2 = Bohr/fs² * fs² -> Bohr
            x += v * dt + .5 * a * dt**2
            a_prev = a

        # Update coordinates
        geom.coords = x

        for name, func in term_funcs.items():
            if func(x.reshape(-1, 3)):
                terminate = True
                terminate_key = name
                break
        if terminate:
            log(logger,
                f"Termination function '{name}' evaluted to True. Breaking.")
            break

        if check_for_stop_sign():
            break

        # Advance time
        t_cur += dt
    log(logger, "")

    md_result = MDResult(
        coords=np.array(xs),
        t_ps=t_cur * 1e-3,
        step=step,
        terminated=terminate_key,
        T=np.array(Ts),
        E_tot=np.array(E_tots),
    )

    return md_result
Example #19
0
 def log_dihed_skip(inds):
     log(
         logger,
         f"Skipping generation of dihedral {inds} "
         "as some of the the atoms are (close too) linear.",
     )
Example #20
0
def get_dihedral_inds(coords3d, bond_inds, bend_inds, max_deg, logger=None):
    max_rad = np.deg2rad(max_deg)
    bond_dict = dict()
    for from_, to_ in bond_inds:
        bond_dict.setdefault(from_, list()).append(to_)
        bond_dict.setdefault(to_, list()).append(from_)
    proper_dihedral_inds = list()
    improper_candidates = list()
    improper_dihedral_inds = list()

    def log_dihed_skip(inds):
        log(
            logger,
            f"Skipping generation of dihedral {inds} "
            "as some of the the atoms are (close too) linear.",
        )

    def set_dihedral_index(dihedral_ind, proper=True):
        dihed = tuple(dihedral_ind)
        check_in = proper_dihedral_inds if proper else improper_dihedral_inds
        # Check if this dihedral is already present
        if (dihed in check_in) or (dihed[::-1] in check_in):
            return
        # Assure that the angles are below 175° (3.054326 rad)
        if not dihedral_valid(coords3d, dihedral_ind, deg_thresh=max_deg):
            log_dihed_skip(dihedral_ind)
            return
        if proper:
            proper_dihedral_inds.append(dihed)
        else:
            improper_dihedral_inds.append(dihed)

    for bond, bend in it.product(bond_inds, bend_inds):
        # print("bond", bond, "bend", bend)
        central = bend[1]
        bend_set = set(bend)
        bond_set = set(bond)
        # Check if the two sets share one common atom. If not continue.
        intersect = bend_set & bond_set
        # print("intersect", intersect)
        if len(intersect) != 1:
            continue
        # if bond == frozenset((0, 11)) and bend == (0, 3, 4):
        # import pdb; pdb.set_trace()
        # pass

        # TODO: check collinearity of bond and bend.

        # When the common atom between bond and bend is a terminal, and not a central atom
        # in the bend we create a proper dihedral. Improper dihedrals are only created
        # when no proper dihedrals have been found.
        if central not in bond_set:
            # The new terminal atom in the dihedral is the one, that doesn' intersect.
            terminal = tuple(bond_set - intersect)[0]
            intersecting_atom = tuple(intersect)[0]
            bend_terminal = tuple(bend_set - {central} - intersect)[0]

            bend_rad = Bend._calculate(coords3d, bend)
            # Bend atoms are nearly collinear. Check if we can skip the central bend atom
            # and use an atom that is conneced to the terminal atom of the bend or bond.
            if bend_rad >= max_rad:
                bend_terminal_bonds = set(bond_dict[bend_terminal]) - bend_set
                bond_terminal_bonds = set(bond_dict[terminal]) - bond_set
                set_dihedrals = [
                    (terminal, intersecting_atom, bend_terminal, betb)
                    for betb in bend_terminal_bonds
                ] + [(bend_terminal, intersecting_atom, terminal, botb)
                     for botb in bond_terminal_bonds]
                # Hardcoded for now ... look ahead to next shell of atoms
                if not any([
                        dihedral_valid(coords3d, inds, deg_thresh=max_deg)
                        for inds in set_dihedrals
                ]):
                    set_dihedrals = []
                    for betb in bend_terminal_bonds:
                        bend_terminal_bonds_v2 = set(
                            bond_dict[betb]) - bend_set - bond_set
                        set_dihedrals = [(terminal, intersecting_atom, betb,
                                          betb_v2)
                                         for betb_v2 in bend_terminal_bonds_v2]
                    for botb in bond_terminal_bonds:
                        bond_terminal_bonds_v2 = set(
                            bond_dict[botb]) - bend_set - bond_set
                        set_dihedrals = [(bend_terminal, intersecting_atom,
                                          botb, botb_v2)
                                         for botb_v2 in bond_terminal_bonds_v2]
            elif intersecting_atom == bend[0]:
                set_dihedrals = [[terminal] + list(bend)]
            else:
                set_dihedrals = [list(bend) + [terminal]]
            [set_dihedral_index(dihed) for dihed in set_dihedrals]
        # If the common atom is the central atom we try to form an out
        # of plane bend / improper torsion. They may be created later on.
        else:
            fourth_atom = list(bond_set - intersect)
            dihedral_ind = list(bend) + fourth_atom
            # This way dihedrals may be generated that contain linear
            # atoms and these would be undefinied. So we check for this.
            if dihedral_valid(coords3d, dihedral_ind, deg_thresh=max_deg):
                improper_candidates.append(dihedral_ind)
            else:
                log_dihed_skip(dihedral_ind)

    # Now try to create the remaining improper dihedrals.
    if (len(coords3d) >= 4) and (len(proper_dihedral_inds) == 0):
        log(
            logger,
            "Could not define any proper dihedrals! Generating improper dihedrals!",
        )
        for improp in improper_candidates:
            set_dihedral_index(improp, proper=False)
        log(
            logger,
            "Permutational symmetry not considerd in generation of "
            "improper dihedrals.",
        )

    return proper_dihedral_inds, improper_dihedral_inds
Example #21
0
def setup_redundant(
    atoms,
    coords3d,
    factor=1.3,
    define_prims=None,
    min_deg=15,
    dihed_max_deg=175.0,
    lb_min_deg=None,
    lb_max_bonds=4,
    min_weight=None,
    logger=None,
):
    if define_prims is None:
        define_prims = list()

    log(logger, f"Detecting primitive internals for {len(atoms)} atoms.")

    def keep_coord(prim_cls, prim_inds):
        return (True if (min_weight is None) else (
            prim_cls._weight(atoms, coords3d, prim_inds, 0.12) >= min_weight))

    def keep_coords(prims, prim_cls):
        return [prim for prim in prims if keep_coord(prim_cls, prim)]

    # Bonds
    bonds, cdm, cbm = get_bond_sets(
        atoms,
        coords3d,
        bond_factor=factor,
        return_cdm=True,
        return_cbm=True,
    )
    bonds = [tuple(bond) for bond in bonds]
    bonds = keep_coords(bonds, Stretch)

    # Fragments
    fragments = merge_sets(bonds)
    # Check for unbonded single atoms and create fragments for them.
    bonded_set = set(tuple(np.ravel(bonds)))
    unbonded_set = set(range(len(atoms))) - bonded_set
    fragments.extend([frozenset((atom, )) for atom in unbonded_set])

    # Check for disconnected fragments. If they are present, create interfragment
    # bonds between them.
    interfrag_bonds, aux_interfrag_bonds = connect_fragments(cdm,
                                                             fragments,
                                                             logger=logger)

    # Hydrogen bonds
    hydrogen_bonds = get_hydrogen_bond_inds(atoms,
                                            coords3d,
                                            bonds,
                                            logger=logger)

    hydrogen_set = [frozenset(bond) for bond in hydrogen_bonds]
    interfrag_bonds = [
        bond for bond in interfrag_bonds if set(bond) not in hydrogen_set
    ]
    aux_interfrag_bonds = [
        bond for bond in aux_interfrag_bonds if set(bond) not in hydrogen_set
    ]
    bonds = [bond for bond in bonds if set(bond) not in hydrogen_set]
    aux_bonds = list()

    # Don't use auxilary interfragment bonds for bend detection
    bonds_for_bends = set(
        [frozenset(bond) for bond in bonds + hydrogen_bonds + interfrag_bonds])

    # Bends
    bends = get_bend_inds(
        coords3d,
        bonds_for_bends,
        min_deg=min_deg,
        max_deg=180.0,
        logger=logger,
    )
    # All bends will be checked, for being linear bends and will be removed from
    # bend_inds, if needed.
    bends = keep_coords(bends, Bend)

    # Linear Bends and orthogonal complements
    linear_bends, linear_bend_complements = get_linear_bend_inds(
        coords3d,
        cbm,
        bends,
        min_deg=lb_min_deg,
        max_bonds=lb_max_bonds,
        logger=logger,
    )
    # Remove linear bends from bends
    bends = [bend for bend in bends if bend not in linear_bends]
    linear_bends = keep_coords(linear_bends, LinearBend)
    linear_bend_complements = keep_coords(linear_bend_complements, LinearBend)

    # Dihedrals
    bends_for_dihedrals = bends + linear_bends
    proper_dihedrals, improper_dihedrals = get_dihedral_inds(
        # coords3d, bonds_for_bends, bends, max_deg=dihed_max_deg, logger=logger
        coords3d,
        bonds_for_bends,
        bends_for_dihedrals,
        max_deg=dihed_max_deg,
        logger=logger)
    proper_dihedrals = keep_coords(proper_dihedrals, Torsion)
    improper_dihedrals = keep_coords(improper_dihedrals, Torsion)

    # Additional primitives to be defined.
    define_map = {
        PrimTypes.BOND: "bonds",
        PrimTypes.AUX_BOND: "aux_bonds",
        PrimTypes.HYDROGEN_BOND: "hydrogen_bonds",
        PrimTypes.INTERFRAG_BOND: "interfrag_bonds",
        PrimTypes.AUX_INTERFRAG_BOND: "aux_interfrag_bonds",
        PrimTypes.BEND: "bends",
        PrimTypes.LINEAR_BEND: "linear_bends",
        PrimTypes.LINEAR_BEND_COMPLEMENT: "linear_bend_complements",
        PrimTypes.PROPER_DIHEDRAL: "proper_dihedrals",
        PrimTypes.IMPROPER_DIHEDRAL: "improper_dihedrals",
    }
    for type_, *indices in define_prims:
        key = define_map[type_]
        locals()[key].append(tuple(indices))

    pt = PrimTypes
    typed_prims = (
        # Bonds, two indices
        [(pt.BOND, *bond) for bond in bonds] + [(pt.AUX_BOND, *abond)
                                                for abond in aux_bonds] +
        [(pt.HYDROGEN_BOND, *hbond) for hbond in hydrogen_bonds] +
        [(pt.INTERFRAG_BOND, *ifbond)
         for ifbond in interfrag_bonds] + [(pt.AUX_INTERFRAG_BOND, *aifbond)
                                           for aifbond in aux_interfrag_bonds]
        # Bends, three indices
        + [(pt.BEND, *bend) for bend in bends] + [(pt.LINEAR_BEND, *lbend)
                                                  for lbend in linear_bends] +
        [(pt.LINEAR_BEND_COMPLEMENT, *lbendc)
         for lbendc in linear_bend_complements]
        # Dihedral, four indices
        + [(pt.PROPER_DIHEDRAL, *pdihedral)
           for pdihedral in proper_dihedrals] +
        [(pt.IMPROPER_DIHEDRAL, *idihedral)
         for idihedral in improper_dihedrals])

    coord_info = CoordInfo(
        bonds=bonds,
        hydrogen_bonds=hydrogen_bonds,
        interfrag_bonds=interfrag_bonds,
        aux_interfrag_bonds=aux_interfrag_bonds,
        bends=bends,
        linear_bends=linear_bends,
        linear_bend_complements=linear_bend_complements,
        proper_dihedrals=proper_dihedrals,
        improper_dihedrals=improper_dihedrals,
        typed_prims=typed_prims,
        fragments=fragments,
    )
    return coord_info