File: test_lammpslib_simple.py

package info (click to toggle)
python-ase 3.26.0-3
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 15,484 kB
  • sloc: python: 148,112; xml: 2,728; makefile: 110; javascript: 47
file content (208 lines) | stat: -rw-r--r-- 5,716 bytes parent folder | download
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
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
# fmt: off
import numpy as np
import pytest

import ase.io
from ase import Atom, Atoms, units
from ase.build import bulk
from ase.md.verlet import VelocityVerlet


@pytest.fixture()
def atoms_fcc_Ni_with_H_at_center():
    atoms = bulk("Ni", cubic=True)
    atoms += Atom("H", position=atoms.cell.diagonal() / 2)
    return atoms


@pytest.fixture()
def lammps_data_file_Fe(datadir):
    return datadir / "lammpslib_simple_input.data"


@pytest.fixture()
def calc_params_Fe(lammps_data_file_Fe):
    calc_params = {}
    calc_params["lammps_header"] = [
        "units           real",
        "atom_style      full",
        "boundary        p p p",
        "box tilt        large",
        "pair_style      lj/cut/coul/long 12.500",
        "bond_style      harmonic",
        "angle_style     harmonic",
        "kspace_style    ewald 0.0001",
        "kspace_modify   gewald 0.01",
        f"read_data      {lammps_data_file_Fe}",
    ]
    calc_params["lmpcmds"] = []
    calc_params["atom_types"] = {"Fe": 1}
    calc_params["create_atoms"] = False
    calc_params["create_box"] = False
    calc_params["boundary"] = False
    calc_params["log_file"] = "test.log"
    return calc_params


@pytest.fixture()
def atoms_Fe(lammps_data_file_Fe):
    return ase.io.read(
        lammps_data_file_Fe,
        format="lammps-data",
        Z_of_type={1: 26},
        units="real",
    )


@pytest.mark.calculator_lite()
@pytest.mark.calculator("lammpslib")
def test_lammpslib_simple(
    factory,
    calc_params_NiH,
    atoms_fcc_Ni_with_H_at_center: Atoms,
):
    # TODO: Split the test in a rational way.
    NiH = atoms_fcc_Ni_with_H_at_center

    # Add a bit of distortion to the cell
    NiH.set_cell(
        NiH.cell + [[0.1, 0.2, 0.4], [0.3, 0.2, 0.0], [0.1, 0.1, 0.1]],
        scale_atoms=True,
    )

    calc = factory.calc(**calc_params_NiH)
    NiH.calc = calc

    E = NiH.get_potential_energy()
    F = NiH.get_forces()
    S = NiH.get_stress()

    print("Energy: ", E)
    print("Forces:", F)
    print("Stress: ", S)
    print()

    E = NiH.get_potential_energy()
    F = NiH.get_forces()
    S = NiH.get_stress()

    calc = factory.calc(**calc_params_NiH)
    NiH.calc = calc

    E2 = NiH.get_potential_energy()
    F2 = NiH.get_forces()
    S2 = NiH.get_stress()

    assert E == pytest.approx(E2, rel=1e-4)
    assert F == pytest.approx(F2, rel=1e-4)
    assert S == pytest.approx(S2, rel=1e-4)

    NiH.rattle(stdev=0.2)
    E3 = NiH.get_potential_energy()
    F3 = NiH.get_forces()
    S3 = NiH.get_stress()

    print("rattled atoms")
    print("Energy: ", E3)
    print("Forces:", F3)
    print("Stress: ", S3)
    print()

    assert not np.allclose(E, E3)
    assert not np.allclose(F, F3)
    assert not np.allclose(S, S3)

    # Add another H
    NiH += Atom("H", position=NiH.cell.diagonal() / 4)
    E4 = NiH.get_potential_energy()
    F4 = NiH.get_forces()
    S4 = NiH.get_stress()

    assert not np.allclose(E4, E3)
    assert not np.allclose(F4[:-1, :], F3)
    assert not np.allclose(S4, S3)

    # the example from the docstring

    NiH = atoms_fcc_Ni_with_H_at_center
    calc = factory.calc(**calc_params_NiH)
    NiH.calc = calc
    print("Energy ", NiH.get_potential_energy())


@pytest.mark.parametrize("keep_alive", [False, True])
@pytest.mark.calculator_lite()
@pytest.mark.calculator("lammpslib")
def test_read_data(
    factory,
    calc_params_Fe,
    atoms_Fe: Atoms,
    keep_alive: bool,
):
    """Test `read_data` and `keep_alive`.

    Test if a LAMMPS data file can be read and if both `keep_alive=False` and
    `keep_alive=True` work together with `Dynamics`.

    Get energy from a LAMMPS calculation of an uncharged system.
    This was written to run with the 30 Apr 2019 version of LAMMPS,
    for which uncharged systems require the use of 'kspace_modify gewald'.
    """
    calc = factory.calc(keep_alive=keep_alive, **calc_params_Fe)
    atoms_Fe.calc = calc
    with VelocityVerlet(atoms_Fe, 1 * units.fs) as dyn:
        energy = atoms_Fe.get_potential_energy()
        assert energy == pytest.approx(2041.411982950972, rel=1e-4)

        dyn.run(10)
        energy = atoms_Fe.get_potential_energy()
        assert energy == pytest.approx(312.4315854721744, rel=1e-4)


@pytest.mark.calculator_lite()
@pytest.mark.calculator('lammpslib')
def test_charges_atomic(
    factory,
    calc_params_NiH: dict,
    atoms_fcc_Ni_with_H_at_center: Atoms,
):
    """Test charges for `atom_style atomic`.

    Since `atom_style atomic` does not include atomic charges, LAMMPS raises
    an error "Cannot set attribute charge for atom style atomic" if we set a
    command like 'set atom 1 charge 1.0'.
    The above command must therefore be set only when `atom_style` include
    atomic charges, and otherwise the command should be skipped.
    This test checks if the skipping is properly done.
    """
    atoms = atoms_fcc_Ni_with_H_at_center
    atoms.set_initial_charges(len(atoms) * [1.0])
    atoms.calc = factory.calc(**calc_params_NiH)
    atoms.get_potential_energy()


@pytest.mark.calculator_lite()
@pytest.mark.calculator('lammpslib')
def test_charges_full(
    factory,
    calc_params_Fe: dict,
    atoms_Fe: Atoms,
):
    """Test charges for `atom_style full`.

    Test if

    1. Setting initial charges triggers a new calculation.
    2. The energy with charges is different from the energy without charges.

    """
    calc = factory.calc(**calc_params_Fe)
    atoms_Fe.calc = calc

    energy_without_charges = atoms_Fe.get_potential_energy()

    atoms_Fe.set_initial_charges(len(atoms_Fe) * [1.0])

    energy_with_charges = atoms_Fe.get_potential_energy()

    assert energy_with_charges != pytest.approx(energy_without_charges)