1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126
|
# fmt: off
from pathlib import Path
import pytest
from ase.build import bulk
from ase.calculators.emt import EMT
from ase.optimize import (
BFGS,
FIRE,
LBFGS,
BFGSLineSearch,
GoodOldQuasiNewton,
GPMin,
LBFGSLineSearch,
MDMin,
ODE12r,
)
from ase.optimize.precon import PreconFIRE, PreconLBFGS, PreconODE12r
from ase.optimize.sciopt import SciPyFminBFGS, SciPyFminCG
optclasses = [
MDMin,
FIRE,
LBFGS,
LBFGSLineSearch,
BFGSLineSearch,
BFGS,
GoodOldQuasiNewton,
GPMin,
SciPyFminCG,
SciPyFminBFGS,
PreconLBFGS,
PreconFIRE,
ODE12r,
PreconODE12r,
]
@pytest.fixture(name="ref_atoms")
def fixture_ref_atoms():
ref_atoms = bulk("Au")
ref_atoms.calc = EMT()
ref_atoms.get_potential_energy()
return ref_atoms
@pytest.fixture(name="atoms")
def fixture_atoms(ref_atoms):
atoms = ref_atoms * (2, 2, 2)
floor = 0.45
atoms.calc = EMT()
atoms.rattle(stdev=0.1, seed=7)
e_unopt = atoms.get_potential_energy()
assert e_unopt > floor
return atoms
@pytest.fixture(name="optcls", params=optclasses)
def fixture_optcls(request):
optcls = request.param
return optcls
@pytest.fixture(name="kwargs")
def fixture_kwargs(optcls):
kwargs = {}
if optcls is PreconLBFGS:
kwargs["precon"] = None
yield kwargs
kwargs = {}
@pytest.mark.optimize()
@pytest.mark.filterwarnings("ignore: estimate_mu")
def test_optimize(optcls, atoms, ref_atoms, kwargs):
"""Test if forces can be converged using the optimizer."""
fmax = 0.01
with optcls(atoms, **kwargs) as opt:
is_converged = opt.run(fmax=fmax)
assert is_converged # check if opt.run() returns True when converged
forces = atoms.get_forces()
final_fmax = max((forces**2).sum(axis=1) ** 0.5)
ref_energy = ref_atoms.get_potential_energy()
e_opt = atoms.get_potential_energy() * len(ref_atoms) / len(atoms)
e_err = abs(e_opt - ref_energy)
print(f"{optcls.__name__:>20}:", end=" ")
print(f"fmax={final_fmax:.05f} eopt={e_opt:.06f} err={e_err:06e}")
assert final_fmax < fmax
assert e_err < 1.75e-5 # (This tolerance is arbitrary)
@pytest.mark.optimize()
def test_unconverged(optcls, atoms, kwargs):
"""Test if things work properly when forces are not converged."""
fmax = 1e-9 # small value to not get converged
with optcls(atoms, **kwargs) as opt:
opt.run(fmax=fmax, steps=1) # only one step to not get converged
gradient = opt.optimizable.get_gradient()
assert not opt.converged(gradient)
assert opt.todict()["fmax"] == 1e-9
def test_run_twice(optcls, atoms, kwargs):
"""Test if `steps` increments `max_steps` when `run` is called twice."""
fmax = 1e-9 # small value to not get converged
steps = 5
with optcls(atoms, **kwargs) as opt:
opt.run(fmax=fmax, steps=steps)
opt.run(fmax=fmax, steps=steps)
assert opt.nsteps == 2 * steps
assert opt.max_steps == 2 * steps
@pytest.mark.optimize()
@pytest.mark.filterwarnings("ignore: estimate_mu")
def test_path(testdir, optcls, atoms, kwargs):
fmax = 0.01
traj, log = Path('trajectory.traj'), Path('relax.log')
with optcls(atoms, logfile=log, trajectory=traj, **kwargs) as opt:
is_converged = opt.run(fmax=fmax)
assert is_converged # check if opt.run() returns True when converged
|