Find the Ground State of an EON Structure ----------------------------------------- Atomistic workflows minimize. ASE relaxes structures with L-BFGS and FIRE; eOn (TheochemUI/eOn on GitHub) runs its own L-BFGS and conjugate-gradient minimizers at the end of every min-mode and nudged-elastic-band search, over structures stored as EON ``.con`` files and driven by a potential rgpot serves over RPC. The atomistic-cookbook example lab- cosmo/atomistic-cookbook runs exactly this stack: a PET-MAD model, a NEB path, eOn's saddle search. All of these minimizers are *local*: they walk downhill to the nearest minimum. The nearest minimum is often not the one you want. This tutorial finds the minimum a local search misses, and shows that ``anneal`` reaches it where ASE's optimizer and scipy's simulated annealing do not improve on it. The numbers you will reproduce, from one disordered start on a 7-atom Lennard- Jones cluster (reduced units, global minimum ``-16.505384``): .. table:: +-----------------------------------+--------------+-------------+ | Minimizer | Final energy | Evaluations | +===================================+==============+=============+ | local L-BFGS (ASE, eOn) | -3.13 | 500 | +-----------------------------------+--------------+-------------+ | scipy ``dual_annealing`` (global) | -16.505384 | 17006 | +-----------------------------------+--------------+-------------+ | ``anneal`` portfolio (global) | -16.505384 | 14920 | +-----------------------------------+--------------+-------------+ Local L-BFGS stops at the nearest minimum. Both global methods reach the ground state; ``anneal`` reaches it in fewer evaluations than scipy. What you will need ~~~~~~~~~~~~~~~~~~ - `pixi `_ (manages the toolchain) - The ``rgpot``, ``anneal``, and ``readcon-core`` checkouts side by side - About 10 minutes One command builds everything and runs the comparison: .. code:: bash cd rgpot pixi run -e rpctest ase-anneal-compare It builds the ``potserv`` potential server, builds the ``anneal`` and ``readcon`` Python modules, and runs the two scripts below. Step 1: drive an rgpot potential from Python ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ rgpot exposes every potential through one ASE calculator, ``RgpotCalculator``. It talks to the same ``potserv`` RPC server the eOn client connects to, so Python and eOn evaluate the same energies and forces. Point it at a potential and it launches the server: .. code:: python import numpy as np from ase import Atoms from ase_calculator import RgpotCalculator # CppCore/rgpot/rpc on sys.path calc = RgpotCalculator.spawn(server_bin="path/to/potserv", potential="LJ") start = np.random.RandomState(1).uniform(-2.3, 2.3, size=(7, 3)) atoms = Atoms(numbers=[0] * 7, positions=start, cell=[40, 40, 40]) atoms.calc = calc print("start energy:", atoms.get_potential_energy()) # about +20 Use ``spawn`` as a context manager (``with RgpotCalculator.spawn(...) as calc:``) to stop the managed server on exit. Step 2: local relaxation stops at the nearest minimum ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ .. code:: python from ase.optimize import LBFGS LBFGS(atoms).run(fmax=1e-3) print("local L-BFGS energy:", atoms.get_potential_energy()) # about -3.13 L-BFGS converges to a minimum, just not the global one. The LJ7 global minimum, the pentagonal bipyramid at ``-16.505384``, sits in a different basin; from a random start a local minimizer cannot cross into it. eOn's L-BFGS, run on the same surface, stops in the same place -- this is the wall a local search hits when a relaxation lands in a metastable basin instead of the product state. Step 3: find the global minimum ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ``AnnealOptimizer`` wraps ``anneal``'s portfolio optimizer (the method of the INFORMS Journal on Computing paper: Thompson-allocated basin hopping, differential evolution, generalized simulated annealing, parallel tempering, GLE-Langevin) behind the same ``run()``. It uses the rgpot forces as the gradient: .. code:: python from ase_helpers import AnnealOptimizer atoms.set_positions(start) # back to the disordered start opt = AnnealOptimizer(atoms, budget=15000, seed=11, pad=3.0) opt.run() print("anneal energy:", opt.get_potential_energy()) # -16.505384 print("evaluations:", opt.nevals) The geometry ends at the global minimum. ``scipy.optimize.dual_annealing`` reaches the same value on this surface; ``anneal`` reaches it in fewer evaluations, and both pass the value the local search returns. ``tests/ase_vs_anneal.py`` runs all three side by side. Which minimizer for which job ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - Local L-BFGS / FIRE / conjugate gradient (ASE or eOn): refine a structure inside the basin you want -- an eOn saddle endpoint, a perturbed crystal, an MD snapshot. - ``AnnealOptimizer``: find the minimum when the basin is unknown -- cluster ground states, conformer search, potential-parameter fitting. Chain them: an ``anneal`` search to locate the basin, then a local polish. Step 4: start from a ``.con`` file ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ EON workflows start from a structure on disk. ``atoms_from_con`` reads an EON ``.con`` file into ``ase.Atoms`` through ``readcon- core`` -- the files eOn reads and writes: .. code:: python from ase_helpers import atoms_from_con calc = RgpotCalculator.spawn(server_bin="path/to/potserv", potential="CuH2") atoms = atoms_from_con("tiny_cuh2.con", calc=calc) print(atoms.get_chemical_symbols()) # ['Cu', 'Cu', 'H', 'H'] print("energy:", atoms.get_potential_energy()) # The cell comes from the .con; override it with a larger box when needed. atoms = atoms_from_con("tiny_cuh2.con", cell=[15, 20, 30], pbc=False) Relaxing it is the same ``run()``: rattle the structure, then walk it back to the minimum the ``.con`` recorded: .. code:: python atoms.calc = calc atoms.rattle(stdev=0.15, seed=1) LBFGS(atoms).run(fmax=0.02) print("relaxed energy:", atoms.get_potential_energy()) # back to -6.474708 ``tests/con_relax_demo.py`` runs this round-trip. Visualize the minimization on the (s, d) landscape ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Two complementary views. First, sample the surface densely: a probe atom moved through the x-y plane near a lone atom (a shallow well) and a bound pair (a deeper well) gives a 2D Lennard-Jones double well. ASE LBFGS started in the shallow basin stops there; anneal's portfolio reaches the deep one. .. code:: bash pixi run -e rpctest pes-double-well .. figure:: ../_static/pes_double_well.png rgpot LJ double well (densely evaluated 2D PES). ASE LBFGS (red) stops in the shallow left basin at -2.0; anneal's global portfolio (orange star) reaches the deep right basin at -3.0. Second, for an EON minimization, \`\`rgpycrumbs eon plt-min\`\` renders the actual trajectory on a gradient-enhanced 2D landscape: progress \`\`s\`\` toward the minimum against lateral deviation \`\`d\`\`, with the energy surface fitted from the path. \`\`tests/eon\ :sub:`min`\ \ :sub:`landscape.py`\\`\` runs an \`\`eonclient\`\` LJ7 minimization and plots it (energies capped with \`\`plt-min --energy-cap-window\`\` so the colorbar resolves the well): .. code:: bash pixi run -e eonviz eon-min-landscape .. figure:: ../_static/eon_min_landscape.png An EON LJ7 minimization on the (s, d) landscape: the path descends from the start (Init) to the global minimum at -16.505 (Min), and the surface energy drops monotonically along progress s. The \`\`eonviz\`\` environment is separate from the C++ build env: \`\`eon\`\` pins \`\`fmt>=12\`\` (clashing with rgpot's C++ \`\`fmt<12\`\`), so it carries no default feature and pulls \`\`eon\`\`, \`\`ira\`\`, \`\`jax\`\`, and the dev \`\`chemparseplot\`\` / \`\`rgpycrumbs\`\` (the published releases lack the embedded-metadata trajectory parser and \`\`plt-min\`\`). Where to go next ~~~~~~~~~~~~~~~~ - Swap ``potential``"LJ"= for ``CuH2``, ``XTB``, ``TBLite``, or ``Metatomic:`` (the PET-MAD model the cookbook uses); the calculator stays the same, only the server backend changes, and the same server serves eOn. - Read `External Integration Guide <../howto/integration.rst>`_ for how eOn links rgpot's RPC server from C++. - Read `rgpot potentials as eindir objectives <../howto/eindir-anneal.rst>`_ for how an rgpot potential becomes an ``anneal`` objective with no glue code.