test_encyclopedia.py 18.7 KB
Newer Older
1
2
3
4
5
6
7
8
9
10
11
12
13
14
# Copyright 2018 Markus Scheidgen
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#   http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an"AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

15
import pytest
16
import numpy as np
17
from ase import Atoms
18
import ase.build
19
from matid.symmetry.wyckoffset import WyckoffSet
20
from pint import UnitRegistry
21

22
from nomad.utils import hash
23
from nomad.parsing import LocalBackend
24
from nomad.normalizing import structure
25
from nomad.metainfo.encyclopedia import Encyclopedia
26
27
28
29
30
31
32
33
34
35
36
37
38
from tests.normalizing.conftest import (  # pylint: disable=unused-import
    run_normalize_for_structure,
    geometry_optimization,
    molecular_dynamics,
    phonon,
    two_d,
    bulk,
    bands_unpolarized_no_gap,
    bands_polarized_no_gap,
    bands_unpolarized_gap_indirect,
    bands_polarized_gap_indirect,
    dos_unpolarized_vasp,
    dos_polarized_vasp,
39
    hash_exciting,
40
    hash_vasp,
41
)
42
43

ureg = UnitRegistry()
44
45


Lauri Himanen's avatar
Lauri Himanen committed
46
47
48
49
def test_geometry_optimization(geometry_optimization: LocalBackend):
    """Tests that geometry optimizations are correctly processed."
    """
    enc = geometry_optimization.get_mi2_section(Encyclopedia.m_def)
Lauri Himanen's avatar
Lauri Himanen committed
50
    run_type = enc.run_type.run_type
Lauri Himanen's avatar
Lauri Himanen committed
51
52
53
54
55
56
57
    assert run_type == "geometry optimization"


def test_molecular_dynamics(molecular_dynamics: LocalBackend):
    """Tests that geometry optimizations are correctly processed."
    """
    enc = molecular_dynamics.get_mi2_section(Encyclopedia.m_def)
Lauri Himanen's avatar
Lauri Himanen committed
58
    run_type = enc.run_type.run_type
Lauri Himanen's avatar
Lauri Himanen committed
59
60
61
    assert run_type == "molecular dynamics"


62
63
64
65
66
67
68
# Disabled until the method information can be retrieved
# def test_phonon(phonon: LocalBackend):
    # """Tests that geometry optimizations are correctly processed."
    # """
    # enc = phonon.get_mi2_section(Encyclopedia.m_def)
    # run_type = enc.run_type.run_type
    # assert run_type == "phonon calculation"
Lauri Himanen's avatar
Lauri Himanen committed
69
70


71
72
73
74
def test_1d_metainfo(one_d: LocalBackend):
    """Tests that metainfo for 1D systems is correctly processed.
    """
    enc = one_d.get_mi2_section(Encyclopedia.m_def)
75
    # Material
76
77
78
79
    material = enc.material
    assert material.material_type == "1D"
    assert material.formula == "C6H4"
    assert material.formula_reduced == "C3H2"
80

81
82
83
84
85
86
87
88
    # Idealized structure
    ideal = enc.material.idealized_structure
    assert ideal.number_of_atoms == 10
    assert ideal.atom_labels == ["C", "C", "C", "C", "C", "C", "H", "H", "H", "H"]
    assert ideal.atom_positions is not None
    assert ideal.lattice_vectors is not None
    assert np.array_equal(ideal.periodicity, [True, False, False])
    assert np.allclose(ideal.lattice_parameters, [4.33793652e-10, 0, 0, 0, 0, 0], atol=0)
89
90
91
92
93
94


def test_2d_metainfo(two_d: LocalBackend):
    """Tests that metainfo for 2D systems is correctly processed.
    """
    enc = two_d.get_mi2_section(Encyclopedia.m_def)
95
    # Material
96
97
98
99
100
101
102
103
104
105
106
107
108
109
    material = enc.material
    assert material.material_type == "2D"
    assert material.formula == "C2"
    assert material.formula_reduced == "C"

    # Idealized structure
    ideal = enc.material.idealized_structure
    assert ideal.number_of_atoms == 2
    assert ideal.atom_labels == ["C", "C"]
    assert ideal.atom_positions is not None
    assert ideal.lattice_vectors is not None
    assert ideal.lattice_vectors_primitive is not None
    assert np.array_equal(ideal.periodicity, [True, True, False])
    assert np.allclose(ideal.lattice_parameters, [2.46559821e-10, 2.46559821e-10, 0, 120 / 180 * np.pi, 0, 0], atol=0)
110
111


Lauri Himanen's avatar
Lauri Himanen committed
112
113
114
115
116
def test_bulk_metainfo(bulk: LocalBackend):
    """Tests that metainfo for bulk systems is correctly processed.
    """
    enc = bulk.get_mi2_section(Encyclopedia.m_def)
    # Material
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
    material = enc.material
    assert material.material_type == "bulk"
    assert material.formula == "Si2"
    assert material.formula_reduced == "Si"
    assert material.material_name == "Silicon"

    # Bulk
    bulk = enc.material.bulk
    assert bulk.crystal_system == "cubic"
    assert bulk.bravais_lattice == "cF"
    assert bulk.has_free_wyckoff_parameters is False
    assert bulk.point_group == "m-3m"
    assert bulk.wyckoff_sets is not None
    assert bulk.space_group_number == 227
    assert bulk.structure_type == "diamond"
    assert bulk.structure_prototype == "C"
    assert bulk.strukturbericht_designation == "A4"
    assert bulk.space_group_international_short_symbol == "Fd-3m"

    # Idealized structure
    ideal = enc.material.idealized_structure
    assert ideal.number_of_atoms == 8
    assert ideal.atom_labels == ["Si", "Si", "Si", "Si", "Si", "Si", "Si", "Si"]
    assert ideal.atom_positions is not None
    assert ideal.lattice_vectors is not None
    assert ideal.lattice_vectors_primitive is not None
    assert np.array_equal(ideal.periodicity, [True, True, True])
    assert ideal.lattice_parameters is not None
    assert ideal.cell_volume == pytest.approx(5.431**3 * 1e-30)

    # Properties
    prop = enc.properties
    assert prop.atomic_density == pytest.approx(4.99402346512432e+28)
    assert prop.mass_density == pytest.approx(8 * 28.0855 * 1.6605389e-27 / (5.431**3 * 1e-30))  # Atomic mass in kg/m^3
Lauri Himanen's avatar
Lauri Himanen committed
151
152


153
154
155
156
157
158
159
160
161
162
163
164
165
166
def test_1d_material_identification():
    # Original nanotube
    nanotube1 = ase.build.nanotube(4, 4, vacuum=4)
    backend = run_normalize_for_structure(nanotube1)
    enc = backend.get_mi2_section(Encyclopedia.m_def)
    hash1 = enc.material.material_hash

    # Rotated copy
    nanotube2 = nanotube1.copy()
    nanotube2.rotate(90, "z", rotate_cell=True)
    backend = run_normalize_for_structure(nanotube2)
    enc = backend.get_mi2_section(Encyclopedia.m_def)
    hash2 = enc.material.material_hash
    assert hash2 == hash1
167

168
169
170
171
172
173
174
    # Longer copy
    nanotube3 = nanotube1.copy()
    nanotube3 *= [1, 1, 2]
    backend = run_normalize_for_structure(nanotube3)
    enc = backend.get_mi2_section(Encyclopedia.m_def)
    hash3 = enc.material.material_hash
    assert hash3 == hash1
175

176
177
    # Slightly distorted copies should match
    np.random.seed(4)
178
    for _ in range(10):
179
180
181
182
183
184
185
186
        nanotube4 = nanotube1.copy()
        pos = nanotube4.get_positions()
        pos += 0.2 * np.random.rand(pos.shape[0], pos.shape[1])
        nanotube4.set_positions(pos)
        backend = run_normalize_for_structure(nanotube4)
        enc = backend.get_mi2_section(Encyclopedia.m_def)
        hash4 = enc.material.material_hash
        assert hash4 == hash1
187

188
189
190
191
192
193
194
195
196
197
    # Too distorted copy should not match
    nanotube5 = nanotube1.copy()
    pos = nanotube5.get_positions()
    np.random.seed(4)
    pos += 1 * np.random.rand(pos.shape[0], pos.shape[1])
    nanotube5.set_positions(pos)
    backend = run_normalize_for_structure(nanotube5)
    enc = backend.get_mi2_section(Encyclopedia.m_def)
    hash5 = enc.material.material_hash
    assert hash5 != hash1
198

199
200
201
202

def test_2d_material_identification():
    # Expected information for graphene. Graphene is an example of completely
    # flat 2D material.
Lauri Himanen's avatar
Lauri Himanen committed
203
204
205
206
207
208
209
    wyckoff_sets = [WyckoffSet(
        wyckoff_letter="c",
        element="C",
        indices=[0, 1]
    )]
    space_group_number = 191
    norm_hash_string = structure.get_symmetry_string(space_group_number, wyckoff_sets)
210
    graphene_material_hash = hash(norm_hash_string)
Lauri Himanen's avatar
Lauri Himanen committed
211
212

    # Graphene orthogonal cell
213
    graphene = Atoms(
Lauri Himanen's avatar
Lauri Himanen committed
214
215
216
217
218
219
220
221
222
223
224
225
226
227
        symbols=["C", "C", "C", "C"],
        positions=[
            [2.84, 7.5, 6.148780366869514e-1],
            [3.55, 7.5, 1.8446341100608543],
            [7.1e-1, 7.5, 1.8446341100608543],
            [1.42, 7.5, 6.148780366869514e-1]
        ],
        cell=[
            [4.26, 0.0, 0.0],
            [0.0, 15, 0.0],
            [0.0, 0.0, 2.4595121467478055]
        ],
        pbc=True
    )
228
    backend = run_normalize_for_structure(graphene)
Lauri Himanen's avatar
Lauri Himanen committed
229
230
231
    enc = backend.get_mi2_section(Encyclopedia.m_def)
    assert enc.material.material_hash == graphene_material_hash

232
233
234
235
    # Graphene orthogonal supercell
    graphene2 = graphene.copy()
    graphene2 *= [2, 1, 2]
    backend = run_normalize_for_structure(graphene2)
Lauri Himanen's avatar
Lauri Himanen committed
236
237
238
239
    enc = backend.get_mi2_section(Encyclopedia.m_def)
    assert enc.material.material_hash == graphene_material_hash

    # Graphene primitive cell
240
    graphene3 = Atoms(
Lauri Himanen's avatar
Lauri Himanen committed
241
242
243
244
245
246
247
248
249
250
251
252
        symbols=["C", "C"],
        positions=[
            [0, 1.42, 6],
            [1.2297560733739028, 7.100000000000001e-1, 6]
        ],
        cell=[
            [2.4595121467478055, 0.0, 0.0],
            [-1.2297560733739028, 2.13, 0.0],
            [0.0, 0.0, 12]
        ],
        pbc=True
    )
253
    backend = run_normalize_for_structure(graphene3)
Lauri Himanen's avatar
Lauri Himanen committed
254
255
256
    enc = backend.get_mi2_section(Encyclopedia.m_def)
    assert enc.material.material_hash == graphene_material_hash

257
258
259
260
261
    # Slightly distorted system should match
    np.random.seed(4)
    for _ in range(10):
        graphene4 = graphene.copy()
        pos = graphene4.get_positions()
262
        pos += 0.05 * np.random.rand(pos.shape[0], pos.shape[1])
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
        graphene4.set_positions(pos)
        backend = run_normalize_for_structure(graphene4)
        enc = backend.get_mi2_section(Encyclopedia.m_def)
        hash4 = enc.material.material_hash
        assert hash4 == graphene_material_hash

    # Too distorted system should not match
    graphene5 = graphene.copy()
    pos = graphene5.get_positions()
    np.random.seed(4)
    pos += 1 * np.random.rand(pos.shape[0], pos.shape[1])
    graphene5.set_positions(pos)
    backend = run_normalize_for_structure(graphene5)
    enc = backend.get_mi2_section(Encyclopedia.m_def)
    hash5 = enc.material.material_hash
    assert hash5 != graphene_material_hash
Lauri Himanen's avatar
Lauri Himanen committed
279

280
281
282
    # Expected information for MoS2. MoS2 has finite thichkness unlike
    # graphene. The structure is thus treated differently and tested
    # separately.
Lauri Himanen's avatar
Lauri Himanen committed
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
    wyckoff_sets = [
        WyckoffSet(
            wyckoff_letter="e",
            element="S",
            indices=[2, 5]
        ),
        WyckoffSet(
            wyckoff_letter="e",
            element="S",
            indices=[3, 4]
        ),
        WyckoffSet(
            wyckoff_letter="e",
            element="Mo",
            indices=[0, 1]
        )
    ]
    space_group_number = 11
    norm_hash_string = structure.get_symmetry_string(space_group_number, wyckoff_sets)
302
    mos2_material_hash = hash(norm_hash_string)
Lauri Himanen's avatar
Lauri Himanen committed
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323

    # MoS2 orthogonal cell
    atoms = Atoms(
        symbols=["Mo", "Mo", "S", "S", "S", "S"],
        scaled_positions=[
            [0.000000, 0.022916, 0.630019],
            [0.500000, 0.418064, 0.635988],
            [0.500000, 0.795155, 0.68827],
            [0.000000, 0.299555, 0.70504],
            [0.500000, 0.141429, 0.56096],
            [0.000000, 0.645894, 0.57774],
        ],
        cell=[
            [3.193638, 0.000000, 0.000000],
            [0.000000, 5.738503, 0.110928],
            [0.000000, 0.021363, 24.194079],
        ],
        pbc=True
    )
    backend = run_normalize_for_structure(atoms)
    enc = backend.get_mi2_section(Encyclopedia.m_def)
324
    assert enc.material.material_hash == mos2_material_hash
Lauri Himanen's avatar
Lauri Himanen committed
325
326
327
328
329

    # MoS2 orthogonal supercell
    atoms *= [2, 3, 1]
    backend = run_normalize_for_structure(atoms)
    enc = backend.get_mi2_section(Encyclopedia.m_def)
330
    assert enc.material.material_hash == mos2_material_hash
Lauri Himanen's avatar
Lauri Himanen committed
331
332


333
334
335
336
def test_bulk_material_identification():
    # Original system
    wurtzite = ase.build.bulk("SiC", crystalstructure="wurtzite", a=3.086, c=10.053)
    backend = run_normalize_for_structure(wurtzite)
337
338
339
    enc = backend.get_mi2_section(Encyclopedia.m_def)
    hash1 = enc.material.material_hash

340
341
342
343
    # Rotated
    wurtzite2 = wurtzite.copy()
    wurtzite2.rotate(90, "z", rotate_cell=True)
    backend = run_normalize_for_structure(wurtzite2)
344
345
346
347
    enc = backend.get_mi2_section(Encyclopedia.m_def)
    hash2 = enc.material.material_hash
    assert hash2 == hash1

348
349
350
351
    # Supercell
    wurtzite3 = wurtzite.copy()
    wurtzite3 *= [2, 3, 1]
    backend = run_normalize_for_structure(wurtzite3)
352
353
354
355
    enc = backend.get_mi2_section(Encyclopedia.m_def)
    hash3 = enc.material.material_hash
    assert hash3 == hash1

356
    # Slightly distorted system should match
357
    np.random.seed(4)
358
359
360
    for _ in range(10):
        wurtzite4 = wurtzite.copy()
        pos = wurtzite4.get_positions()
361
        pos += 0.05 * np.random.rand(pos.shape[0], pos.shape[1])
362
363
        wurtzite4.set_positions(pos)
        backend = run_normalize_for_structure(wurtzite4)
364
365
366
367
        enc = backend.get_mi2_section(Encyclopedia.m_def)
        hash4 = enc.material.material_hash
        assert hash4 == hash1

368
369
370
    # Too distorted system should not match
    wurtzite5 = wurtzite.copy()
    pos = wurtzite5.get_positions()
371
372
    np.random.seed(4)
    pos += 1 * np.random.rand(pos.shape[0], pos.shape[1])
373
374
    wurtzite5.set_positions(pos)
    backend = run_normalize_for_structure(wurtzite5)
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
    enc = backend.get_mi2_section(Encyclopedia.m_def)
    hash5 = enc.material.material_hash
    assert hash5 != hash1


def test_1d_structure_structure_at_cell_boundary():
    """Tests that the visualization that is made for 1D systems has the
    correct form even if the cell boundary is at the middle of the
    structure.
    """
    atoms = Atoms(
        symbols=["H", "C"],
        positions=[
            [0.0, 0.0, 0],
            [1.0, 0.0, 10.0]
        ],
        cell=[
            [2, 0.0, 0.0],
            [0.0, 10, 0.0],
            [0.0, 0.0, 10]
        ],
        pbc=True
    )
    backend = run_normalize_for_structure(atoms)
    enc = backend.get_mi2_section(Encyclopedia.m_def)

    expected_cell = [
        [0, 0, 0],
        [0, 0, 0],
        [0, 0, 2e-10]
    ]
    expected_pos = [
        [0, 0, 0],
        [0, 0, 0.5],
    ]
    expected_labels = [
        "H",
        "C",
    ]

415
416
417
418
    ideal = enc.material.idealized_structure
    assert np.allclose(ideal.atom_positions, expected_pos)
    assert np.array_equal(ideal.atom_labels, expected_labels)
    assert np.allclose(ideal.lattice_vectors, expected_cell)
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455


def test_2d_structure_structure_at_cell_boundary():
    """Tests that the visualization that is made for 2D systems has the
    correct form even if the cell boundary is at the middle of the
    structure.
    """
    atoms = Atoms(
        symbols=["H", "C"],
        positions=[
            [0.0, 0.0, 0],
            [0.0, 0.0, 13.800000000000002]
        ],
        cell=[
            [2, 0.0, 0.0],
            [0.0, 2, 0.0],
            [0.0, 0.0, 15]
        ],
        pbc=True
    )
    backend = run_normalize_for_structure(atoms)
    enc = backend.get_mi2_section(Encyclopedia.m_def)

    expected_cell = [
        [2e-10, 0, 0],
        [0, 2e-10, 0],
        [0, 0, 1.2e-10]
    ]
    expected_pos = [
        [0, 0, 1],
        [0, 0, 0],
    ]
    expected_labels = [
        "H",
        "C",
    ]

456
457
458
459
    ideal = enc.material.idealized_structure
    assert np.allclose(ideal.atom_positions, expected_pos)
    assert np.array_equal(ideal.atom_labels, expected_labels)
    assert np.allclose(ideal.lattice_vectors, expected_cell)
460
461


462
def test_method_dft_metainfo(single_point):
463
    enc = single_point.get_mi2_section(Encyclopedia.m_def)
Lauri Himanen's avatar
Lauri Himanen committed
464
465
466
467
468
469
    assert enc.method.basis_set_type == "Numeric AOs"
    assert enc.method.core_electron_treatment == "full all electron"
    assert enc.method.code_name == "FHI-aims"
    assert enc.method.code_version == "010314"
    assert enc.method.functional_long_name == "GGA_C_PBE+GGA_X_PBE"
    assert enc.method.functional_type == "GGA"
470
471
472
473


def test_method_gw_metainfo(gw):
    enc = gw.get_mi2_section(Encyclopedia.m_def)
Lauri Himanen's avatar
Lauri Himanen committed
474
475
476
477
    assert enc.method.code_name == "FHI-aims"
    assert enc.method.code_version == "180607"
    assert enc.method.gw_type == "G0W0"
    assert enc.method.gw_starting_point == "GGA_C_PBE+0.75*GGA_X_PBE+0.25*HF_X"
478
479


480
def test_band_structure(bands_unpolarized_no_gap, bands_polarized_no_gap, bands_unpolarized_gap_indirect, bands_polarized_gap_indirect):
481
482
483
484
485
486

    def test_generic(bs, n_channels):
        """Generic tests for band structure data."""
        assert bs.brillouin_zone is not None
        assert bs.reciprocal_cell.shape == (3, 3)

487
488
489
490
    # Unpolarized, no gaps
    enc = bands_unpolarized_no_gap.get_mi2_section(Encyclopedia.m_def)
    properties = enc.properties
    bs = properties.electronic_band_structure
491
    test_generic(bs, n_channels=1)
492
493
494
495
496
497
498
499
    assert bs.band_gap is None
    assert bs.band_gap_spin_up is None
    assert bs.band_gap_spin_down is None

    # Polarized, no gaps
    enc = bands_polarized_no_gap.get_mi2_section(Encyclopedia.m_def)
    properties = enc.properties
    bs = properties.electronic_band_structure
500
    test_generic(bs, n_channels=2)
501
502
503
504
505
506
    assert bs.band_gap is None
    assert bs.band_gap_spin_up is None
    assert bs.band_gap_spin_down is None

    # Unpolarized, finite gap, indirect
    enc = bands_unpolarized_gap_indirect.get_mi2_section(Encyclopedia.m_def)
507
    properties = enc.properties
508
    bs = properties.electronic_band_structure
509
    test_generic(bs, n_channels=1)
510
    gap_ev = (bs.band_gap.value * ureg.J).to(ureg.eV).magnitude
511
    assert gap_ev == pytest.approx(0.62, 0.01)
512
    assert bs.band_gap.type == "indirect"
513
514
515
516
517
518
519
    assert bs.band_gap_spin_up is None
    assert bs.band_gap_spin_down is None

    # Polarized, finite gap, indirect
    enc = bands_polarized_gap_indirect.get_mi2_section(Encyclopedia.m_def)
    properties = enc.properties
    bs = properties.electronic_band_structure
520
    test_generic(bs, n_channels=2)
521
522
523
524
525
526
527
528
529
530
531
532
    gap = bs.band_gap
    gap_up = bs.band_gap_spin_up
    gap_down = bs.band_gap_spin_down
    gap_ev = (gap.value * ureg.J).to(ureg.eV).magnitude
    gap_up_ev = (gap_up.value * ureg.J).to(ureg.eV).magnitude
    gap_down_ev = (gap_down.value * ureg.J).to(ureg.eV).magnitude
    assert gap_up.type == "indirect"
    assert gap_down.type == "indirect"
    assert gap_up_ev != gap_down_ev
    assert gap_up_ev == gap_ev
    assert gap_up_ev == pytest.approx(0.956, 0.01)
    assert gap_down_ev == pytest.approx(1.230, 0.01)
533

534

535
def test_hashes_exciting(hash_exciting):
536
    """Tests that the hashes has been successfully created for calculations
537
538
539
540
541
542
543
544
545
    from exciting.
    """
    enc = hash_exciting.get_mi2_section(Encyclopedia.m_def)
    method_hash = enc.method.method_hash
    group_eos_hash = enc.method.group_eos_hash
    group_parametervariation_hash = enc.method.group_parametervariation_hash
    assert method_hash is not None
    assert group_eos_hash is not None
    assert group_parametervariation_hash is not None
546
547
548
549
550
551
552
553
554
555
556
557
558
559


def test_hashes_undefined(hash_vasp):
    """Tests that the hashes are not present when the method settings cannot be
    determined at a sufficient accuracy.
    """
    # VASP
    enc = hash_vasp.get_mi2_section(Encyclopedia.m_def)
    method_hash = enc.method.method_hash
    group_eos_hash = enc.method.group_eos_hash
    group_parametervariation_hash = enc.method.group_parametervariation_hash
    assert method_hash is None
    assert group_eos_hash is None
    assert group_parametervariation_hash is None