Source code for atompy.physics.coltrims._coulomb_explode

import os
import pickle

import joblib
import numpy as np
import tqdm

from atompy import _vectors as vec
from atompy.physics import constants
from atompy.physics.particles import Molecule


def calc_coulomb_force(mol: Molecule, idx_probe: int) -> vec.Vector:
    """
    Calculate Coulomb force acting on atom `idx_probe` of `mol`.

    Parameters
    ----------
    mol : :class:`.Molecule`
        The molecule.

        It is assumed that all attributes of the molecule (positions, speeds, masses)
        are given in a.u.

    idx_probe : int
        The index of :attr:`.~Molecule.atoms` on which the calculated force acts.

    Returns
    -------
    :class:`.Vector`
        The vectorial force acting on atom `idx_probe` in a.u..
    """
    force = vec.Vector(0.0, 0.0, 0.0)

    for i in range(mol.size):
        if i == idx_probe:
            continue
        direction = mol.atoms[idx_probe].pos - mol.atoms[i].pos
        distance = direction.mag()
        force += direction.norm().scale(
            mol.atoms[idx_probe].charge * mol.atoms[i].charge / distance**2
        )

    return force


def _coulomb_explode_step(mol: Molecule, dt: float) -> Molecule:
    """
    Advance time of the Coulomb explosion by `dt`.

    Updates the positions and speeds of `mol`.

    `dt` in a.u.
    """
    updated_mol = mol.copy()
    for i in range(mol.size):
        atom = mol.atoms[i]
        force = calc_coulomb_force(mol, i)
        accel = force.scale(1.0 / atom.mass)
        new_pos = 0.5 * accel * dt**2 + atom.speed * dt + atom.pos
        new_speed = accel * dt + mol.atoms[i].speed
        updated_mol.atoms[i].pos = new_pos
        updated_mol.atoms[i].speed = new_speed
    return updated_mol


[docs] def coulomb_explode( mol: Molecule, time_end_fs: float = 5000.0, time_stepsize_fs: float = 1.0 ) -> Molecule: """ Coulomb explode a molecule. .. attention:: This function is slow as it uses native Python code for its core computations. Parameters ---------- mol : :class:`.Molecule` The initial state of the molecule. It is assumed that all attributes of the molecule (positions, speeds, masses) are given in a.u. time_end_fs : float, default 5000 fs The time up to which the Coulomb explosion is simulated (in fs). time_step_fs : float, default 1 fs The time steps in which the Coulomb explosion is simulated (in fs). Returns ------- :class:`.Molecule` A ``Molecule`` instance describing the state of the initial molecule after *time_end_fs*. """ t1 = time_end_fs * constants.AU_PER_FS dt = time_stepsize_fs * constants.AU_PER_FS steps = int(t1 // dt) final_mol = mol.copy() for _ in range(steps): final_mol = _coulomb_explode_step(final_mol, dt) return final_mol
[docs] def coulomb_explode_batch( molecules: np.ndarray[tuple[int], np.dtype[np.object_]], time_end_fs: float, time_stepsize_fs: float, pickle_fname: str | os.PathLike | None = None, n_jobs: int = -2, ) -> np.ndarray[tuple[int], np.dtype[np.object_]]: """ Coulomb explode a batch of molecules. .. attention:: This function is slow. It uses native Python code and inefficient memory layout for its core computations. Parameters ---------- molecules : ndarray of :class:`.Molecule` The molecules. It is assumed that all attributes of the molecules (positions, speeds, masses) are given in a.u. time_end_fs : float, default 5000 fs The time up to which the Coulomb explosion is simulated (in fs). time_step_fs : float, default 1 fs The time steps in which the Coulomb explosion is simulated (in fs). pickle_fname : str | PathLike, optional If provided, pickle output and save it. n_jobs : int, default -2 The number of jobs started. The default value creates *number-of-CPUs* minus 1 jobs. See *n_jobs* descriptions of `joblibs.Parallel <https://joblib.readthedocs.io/en/latest/generated/joblib.Parallel.html>`__ for more information. Returns ------- :class:`.Molecule` A :class:`.!Molecule` instance describing the state of the initial molecule after *time_end_fs*. """ final_molecules = joblib.Parallel(n_jobs=n_jobs)( joblib.delayed(coulomb_explode)(mol, time_end_fs, time_stepsize_fs) for mol in tqdm.tqdm(molecules, desc="Processing Molecules") ) final_molecules = np.array(final_molecules) if pickle_fname is not None: print(f"pickling data to {pickle_fname} ...", end="") with open(pickle_fname, "wb") as file: pickle.dump(final_molecules, file) print(" done") return final_molecules