Exemple #1
0
def check_if_atoms_interacting_energy(model, symbols, etol):
    """
    First, get the energy of a single isolated atom of each species given in 'symbols'.
    Then, construct a dimer consisting of these two species and try to decrease its bond
    length until a discernible difference in the energy (from the sum of the isolated
    energy of each species) is detected.  The 'symbols' arg should be a list or tuple of
    length 2 indicating which species pair to check, e.g. to check if Al interacts with
    Al, one should specify ['Al', 'Al'].
    """
    if not isinstance(symbols, (list, tuple)) or len(symbols) != 2:
        raise ValueError(
            "Argument 'symbols' passed to check_if_atoms_interacting_energy "
            "must be a list of tuple of length 2 indicating the species pair to "
            "check"
        )

    isolated_energy_per_atom = {}
    isolated_energy_per_atom[symbols[0]] = get_isolated_energy_per_atom(
        model, symbols[0]
    )
    isolated_energy_per_atom[symbols[1]] = get_isolated_energy_per_atom(
        model, symbols[1]
    )

    dimer = Atoms(
        symbols,
        positions=[(0.1, 0.1, 0.1), (5.1, 0.1, 0.1)],
        cell=(20, 20, 20),
        pbc=(False, False, False),
    )
    calc = KIM(model)
    dimer.set_calculator(calc)
    try:
        rescale_to_get_nonzero_energy(dimer, isolated_energy_per_atom, etol)
        atoms_interacting = True
        return atoms_interacting
    except:  # noqa: E722
        atoms_interacting = False
        return atoms_interacting
    finally:
        if hasattr(calc, "__del__"):
            calc.__del__()
        del dimer
Exemple #2
0
from ase.lattice.cubic import FaceCenteredCubic
from ase.calculators.kim.kim import KIM

atoms = FaceCenteredCubic(symbol='Ar', latticeconstant=5.25, size=(1, 1, 1))
calc = KIM('LJ_ElliottAkerson_2015_Universal__MO_959249795837_003')
atoms.calc = calc

energy = atoms.get_potential_energy()
print('Potential energy: {} eV'.format(energy))
#!/usr/bin/env python3

from ase import Atom, Atoms
from ase.build import bulk, fcc100, add_adsorbate, add_vacuum
from ase.calculators.vasp import Vasp
from ase.calculators.kim.kim import KIM
from ase.calculators.qmmm import ForceQMMM, RescaledCalculator
from ase.constraints import StrainFilter
from ase.optimize import LBFGS
from ase.visualize import view

atoms = bulk("Pd", "fcc", a=3.5, cubic=True)
atoms.calc = KIM("MEAM_LAMMPS_JeongParkDo_2018_PdMo__MO_356501945107_000")
opt = LBFGS(StrainFilter(atoms), logfile=None)
opt.run(0.03, steps=30)
length = atoms.cell.cellpar()[0]

atoms = fcc100("Pd", (2,2,5), a=length, vacuum=10, periodic=True)
add_adsorbate(atoms, Atoms([Atom("Mo")]), 1.2)


qm_mask = [len(atoms)-1, len(atoms)-2]
qm_calc = Vasp(directory="./qmmm")
mm_calc = KIM("MEAM_LAMMPS_JeongParkDo_2018_PdMo__MO_356501945107_000")
mm_calc = RescaledCalculator(mm_calc, 1, 1, 1, 1)
qmmm = ForceQMMM(atoms, qm_mask, qm_calc, mm_calc, buffer_width=3)
qmmm.initialize_qm_buffer_mask(atoms)
atoms.pbc=True
atoms.calc = qmmm

print(atoms.get_forces())
Exemple #4
0
def get_model_energy_cutoff(
    model,
    symbols,
    xtol=1e-8,
    etol_coarse=1e-6,
    etol_fine=1e-15,
    max_bisect_iters=1000,
    max_upper_cutoff_bracket=20.0,
):
    """
    Compute the distance at which energy interactions become non-trival for a given
    model and a species pair it supports.  This is done by constructing a dimer composed
    of these species in a large finite box, increasing the separation if necessary until
    the total potential energy is within 'etol_fine' of the sum of the corresponding
    isolated energies, and then shrinking the separation until the energy differs from
    that value by more than 'etol_coarse'.  Using these two separations to bound the
    search range, bisection is used to refine in order to locate the cutoff.  The
    'symbols' arg should be a list or tuple of length 2 indicating which species pair to
    check, e.g. to get the energy cutoff of Al with Al, one should specify ['Al', 'Al'].

    This function is based on the content of the DimerContinuityC1__VC_303890932454_002
    Verification Check in OpenKIM [1-3].

    [1] Tadmor E. Verification Check of Dimer C1 Continuity v002. OpenKIM; 2018.
        doi:10.25950/43d2c6d5

    [2] Tadmor EB, Elliott RS, Sethna JP, Miller RE, Becker CA. The potential of
        atomistic simulations and the Knowledgebase of Interatomic Models. JOM.
        2011;63(7):17. doi:10.1007/s11837-011-0102-6

    [3] Elliott RS, Tadmor EB. Knowledgebase of Interatomic Models (KIM) Application
        Programming Interface (API). OpenKIM; 2011. doi:10.25950/ff8f563a
    """
    from scipy.optimize import bisect

    def get_dimer_positions(a, large_cell_len):
        """
        Generate positions for a dimer of length 'a' centered in a finite simulation box
        with side length 'large_cell_len'
        """
        half_cell = 0.5 * large_cell_len
        positions = [
            [half_cell - 0.5 * a, half_cell, half_cell],
            [half_cell + 0.5 * a, half_cell, half_cell],
        ]
        return positions

    def energy(a, dimer, large_cell_len, einf):
        dimer.set_positions(get_dimer_positions(a, large_cell_len))
        return dimer.get_potential_energy() - einf

    def energy_cheat(a, dimer, large_cell_len, offset, einf):
        dimer.set_positions(get_dimer_positions(a, large_cell_len))
        return (dimer.get_potential_energy() - einf) + offset

    if not isinstance(symbols, (list, tuple)) or len(symbols) != 2:
        raise ValueError(
            "Argument 'symbols' passed to check_if_atoms_interacting_energy "
            "must be a list of tuple of length 2 indicating the species pair to "
            "check"
        )

    isolated_energy_per_atom = {}
    isolated_energy_per_atom[symbols[0]] = get_isolated_energy_per_atom(
        model, symbols[0]
    )
    isolated_energy_per_atom[symbols[1]] = get_isolated_energy_per_atom(
        model, symbols[1]
    )
    einf = isolated_energy_per_atom[symbols[0]] + isolated_energy_per_atom[symbols[1]]

    # First, establish the upper bracket cutoff by starting at 'b_init' Angstroms and
    # incrementing by 'db' until
    b_init = 4.0

    # Create finite box of size large_cell_len
    large_cell_len = 50
    dimer = Atoms(
        symbols,
        positions=get_dimer_positions(b_init, large_cell_len),
        cell=(large_cell_len, large_cell_len, large_cell_len),
        pbc=(False, False, False),
    )
    calc = KIM(model)
    dimer.set_calculator(calc)

    db = 2.0
    b = b_init - db
    still_interacting = True
    while still_interacting:
        b += db
        if b > max_upper_cutoff_bracket:
            if hasattr(calc, "__del__"):
                calc.__del__()

            raise KIMASEError(
                "Exceeded limit on upper bracket when determining cutoff "
                "search range"
            )
        else:
            eb = energy(b, dimer, large_cell_len, einf)
            if abs(eb) < etol_fine:
                still_interacting = False

    a = b
    da = 0.01
    not_interacting = True
    while not_interacting:
        a -= da
        if a < 0:
            if hasattr(calc, "__del__"):
                calc.__del__()

            raise RuntimeError(
                "Failed to determine lower bracket for cutoff search using etol_coarse "
                "= {}.  This may mean that the species pair provided ({}) does not "
                "have a non-trivial energy interaction for the potential being "
                "used.".format(etol_coarse, symbols)
            )
        else:
            ea = energy(a, dimer, large_cell_len, einf)
            if abs(ea) > etol_coarse:
                not_interacting = False

    # NOTE: Some Simulator Models have a history dependence due to them maintaining
    #       charges from the previous energy evaluation to use as an initial guess
    #       for the next charge equilibration.  We therefore have to treat them not
    #       as single-valued functions but as distributions, i.e.  for a given
    #       configuration you might get any of a range of energy values depending on
    #       the history of your previous energy evaluations.  This is particularly
    #       problematic for this step, where we set up a bisection problem in order
    #       to determine the cutoff radius of the model.  Our solution for this
    #       specific case is to make a very crude estimate of the variance of that
    #       distribution with a 10% factor of safety on it.
    eb_new = energy(b, dimer, large_cell_len, einf)
    eb_error = abs(eb_new - eb)

    # compute offset to ensure that energy before and after cutoff have
    # different signs
    if ea < eb:
        offset = -eb + 1.1 * eb_error + np.finfo(float).eps
    else:
        offset = -eb - 1.1 * eb_error - np.finfo(float).eps

    rcut, results = bisect(
        energy_cheat,
        a,
        b,
        args=(dimer, large_cell_len, offset, einf),
        full_output=True,
        xtol=xtol,
        maxiter=max_bisect_iters,
    )

    # General clean-up
    if hasattr(calc, "__del__"):
        calc.__del__()

    if not results.converged:
        raise RuntimeError(
            "Bisection search to find cutoff distance did not converge "
            "within {} iterations with xtol = {}".format(max_bisect_iters, xtol)
        )
    else:
        return rcut
Exemple #5
0
def run_md(atoms, id, settings_fn):
    """The function does Molecular Dyanamic simulation (MD) on a material, given by argument atoms.

    Parameters:
    atoms (obj): an atoms object defined by class in ase. This is the material which MD
    will run on.
    id (int): an identifying number for the material.

    Returns:
    obj:atoms object defined in ase, is returned.
    """
    # Read settings
    settings = read_settings_file(settings_fn)
    initial_unitcell_atoms = copy.deepcopy(atoms)
    # Scale atoms object, cubic
    size = settings['supercell_size']
    atoms = atoms * size * (1, 1, 1)
    # atoms = atoms * (size,size,size)
    #print(atoms.get_chemical_symbols())
    N = len(atoms.get_chemical_symbols())

    # Use KIM for potentials from OpenKIM
    use_kim = settings['use_kim']

    # Use Asap for a huge performance increase if it is installed
    use_asap = True

    # Create a copy of the initial atoms object for future reference
    old_atoms = copy.deepcopy(atoms)

    # Describe the interatomic interactions with OpenKIM potential
    if use_kim:  # use KIM potential
        atoms.calc = KIM(
            "LJ_ElliottAkerson_2015_Universal__MO_959249795837_003")
    else:  # otherwise, default to asap3 LennardJones
        atoms.calc = LennardJones([18], [0.010323], [3.40],
                                  rCut=6.625,
                                  modified=True)

    # Set the momenta corresponding to temperature from settings file
    MaxwellBoltzmannDistribution(atoms, settings['temperature'] * units.kB)

    # Select integrator
    if settings['ensemble'] == "NVE":
        from ase.md.verlet import VelocityVerlet
        dyn = VelocityVerlet(atoms, settings['time_step'] * units.fs)

    elif settings['ensemble'] == "NVT":
        from ase.md.langevin import Langevin
        dyn = Langevin(atoms, settings['time_step'] * units.fs,
                       settings['temperature'] * units.kB,
                       settings['friction'])

    interval = settings['interval']

    # Creates trajectory files in directory trajectory_files
    traj = Trajectory("trajectory_files/" + id + ".traj", 'w', atoms)
    dyn.attach(traj.write, interval=interval)

    # Number of decimals for most calculated properties.
    decimals = settings['decimals']
    # Boolean indicating if the material is monoatomic.
    monoatomic = len(set(atoms.get_chemical_symbols())) == 1

    # Calculation and writing of properties
    properties.initialize_properties_file(atoms, initial_unitcell_atoms, id,
                                          decimals, monoatomic)
    dyn.attach(properties.calc_properties, 100, old_atoms, atoms, id, decimals,
               monoatomic)

    # unnecessary, used for logging md runs
    # we should write some kind of logger for the MD
    def logger(a=atoms):  # store a reference to atoms in the definition.
        """Function to print the potential, kinetic and total energy."""
        epot = a.get_potential_energy() / len(a)
        ekin = a.get_kinetic_energy() / len(a)
        t = ekin / (1.5 * units.kB)
        print('Energy per atom: Epot = %.3feV  Ekin = %.3feV (T=%3.0fK)  '
              'Etot = %.3feV' % (epot, ekin, t, epot + ekin))

    # Running the dynamics
    dyn.attach(logger, interval=interval)
    #logger()
    #dyn.run(settings['max_steps'])
    # check for thermal equilibrium
    counter = 0
    equilibrium = False
    for i in range(round(settings['max_steps'] /
                         settings['search_interval'])):  # hyperparameter
        epot, ekin_pre, etot, t = properties.energies_and_temp(atoms)
        # kör steg som motsvarar säg 5 fs
        dyn.run(settings['search_interval'])  # hyperparamter
        epot, ekin_post, etot, t = properties.energies_and_temp(atoms)
        #print(abs(ekin_pre-ekin_post) / math.sqrt(N))
        #print(counter)
        if (abs(ekin_pre - ekin_post) / math.sqrt(N)) < settings['tolerance']:
            counter += 1
        else:
            counter = 0
        if counter > settings['threshold']:  # hyperparameter
            print("reached equilibrium")
            equilibrium = True
            break

    if equilibrium:
        dyn.run(settings['max_steps'])
        properties.finalize_properties_file(atoms, id, decimals, monoatomic)
    else:
        properties.delete_properties_file(id)
        raise RuntimeError("MD did not find equilibrium")
    return atoms
Exemple #6
0
def run_md(atoms, id):
    # Read settings
    settings = read_settings_file()

    # Use KIM for potentials from OpenKIM
    use_kim = True

    # Use Asap for a huge performance increase if it is installed
    use_asap = True

    # Create a copy of the initial atoms object for future reference
    old_atoms = copy.deepcopy(atoms)

    # Describe the interatomic interactions with OpenKIM potential
    if use_kim:  # use KIM potential
        atoms.calc = KIM(
            "LJ_ElliottAkerson_2015_Universal__MO_959249795837_003")
    else:  # otherwise, default to asap3 LennardJones
        atoms.calc = LennardJones([18], [0.010323], [3.40],
                                  rCut=6.625,
                                  modified=True)

    # Set the momenta corresponding to temperature from settings file
    MaxwellBoltzmannDistribution(atoms, settings['temperature'] * units.kB)

    # Select integrator
    if settings['ensemble'] == "NVE":
        from ase.md.verlet import VelocityVerlet
        dyn = VelocityVerlet(atoms, settings['time_step'] * units.fs)

    elif settings['ensemble'] == "NVT":
        from ase.md.langevin import Langevin
        dyn = Langevin(atoms, settings['time_step'] * units.fs,
                       settings['temperature'] * units.kB,
                       settings['friction'])

    traj = Trajectory('ar.traj', 'w', atoms)
    dyn.attach(traj.write, interval=1000)

    # Identity number given as func. parameter to keep track of properties
    # Number of decimals for most calculated properties
    decimals = settings['decimals']
    # Calculation and writing of properties
    properties.initialize_properties_file(atoms, id, decimals)
    dyn.attach(properties.calc_properties, 100, old_atoms, atoms, id, decimals)

    # unnecessary, used for logging md runs
    # we should write some kind of logger for the MD
    def logger(a=atoms):  # store a reference to atoms in the definition.
        """Function to print the potential, kinetic and total energy."""
        epot = a.get_potential_energy() / len(a)
        ekin = a.get_kinetic_energy() / len(a)
        t = ekin / (1.5 * units.kB)
        print('Energy per atom: Epot = %.3feV  Ekin = %.3feV (T=%3.0fK)  '
              'Etot = %.3feV' % (epot, ekin, t, epot + ekin))

    # Running the dynamics
    dyn.attach(logger, interval=1000)
    logger()
    dyn.run(settings['max_steps'])

    return atoms