diff --git a/electronicparsers/openmx/parser.py b/electronicparsers/openmx/parser.py index f5d1b0dd..329dd1f9 100644 --- a/electronicparsers/openmx/parser.py +++ b/electronicparsers/openmx/parser.py @@ -54,6 +54,7 @@ Scf, BasisSetContainer, CoreHole, + KMesh, ) from nomad.parsing.file_parser import TextParser, Quantity from simulationworkflowschema import ( @@ -257,6 +258,11 @@ def convert_eigenvalues(string): r'scf.ElectronicTemperature\s+(\S+)', repeats=False, ), + Quantity( + 'scf.Kgrid', + r'scf.Kgrid\s+(\d+\s+\d+\s+\d+)', + repeats=False, + ) Quantity('scf.dftD', r'scf.dftD\s+([a-z]+)', repeats=False), Quantity('version.dftD', r'version.dftD\s+([23])', repeats=False), Quantity( @@ -726,8 +732,16 @@ def parse_method(self): else: sec_electronic.van_der_waals_method = '' - def parse_eigenvalues(self): + def parse_eigenvalues_and_kmesh(self): eigenvalues = BandEnergies() + + sec_k_mesh = KMesh() + self.archive.run[-1].method[-1].k_mesh = sec_k_mesh + + k_mesh_grid = mainfile_parser.get('scf.Kgrid') + if k_mesh_grid is not None: + sec_k_mesh.grid = k_mesh_grid + self.archive.run[-1].calculation[-1].eigenvalues.append(eigenvalues) values = mainfile_parser.get('eigenvalues') if values is not None: @@ -735,9 +749,22 @@ def parse_eigenvalues(self): if kpoints is not None: eigenvalues.kpoints = kpoints eigenvalues.n_kpoints = len(kpoints) + # OpenMX uses only inversion symmetry so every k-point except gamma-point has multiplicity of 2 + kpoints_multiplicities = [] + for kpoint in kpoints: + if np.allclose(kpoint, [[0.0, 0.0, 0.0]], rtol=0.0): + kpoints_multiplicities.append(1) + else: + kpoints_multiplicities.append(2) + sec_k_mesh.points = np.array(kpoints).astype(complex) + sec_k_mesh.multiplicities = kpoints_multiplicities + eigenvalues.kpoints_multiplicities = kpoints_multiplicities else: eigenvalues.kpoints = [[0, 0, 0]] eigenvalues.n_kpoints = 1 + eigenvalues.kpoints_multiplicities = [1] + sec_k_mesh.points = [[0, 0, 0]] + sec_k_mesh.multiplicities = [1] values = values.get('eigenvalues') if values is not None: if self.spinpolarized: @@ -915,4 +942,4 @@ def parse(self, mainfile: str, archive: EntryArchive, logger): except (IndexError, AttributeError): pass - self.parse_eigenvalues() + self.parse_eigenvalues_and_kmesh() diff --git a/electronicparsers/quantumespresso/parser.py b/electronicparsers/quantumespresso/parser.py index 624974e4..636e2461 100644 --- a/electronicparsers/quantumespresso/parser.py +++ b/electronicparsers/quantumespresso/parser.py @@ -36,6 +36,7 @@ BasisSetContainer, AtomParameters, KMesh, + Scf, ) from runschema.system import System, Atoms from runschema.calculation import ( @@ -3248,6 +3249,8 @@ def parse_method(self, run): sec_method.dft = sec_dft sec_electronic = Electronic() sec_method.electronic = sec_electronic + sec_scf = Scf() + sec_method.scf = sec_scf starting_magnetization = run.get_header('starting_magnetization') spin_orbit_mode = run.get_header('spin_orbit_mode') @@ -3260,6 +3263,8 @@ def parse_method(self, run): sec_kmesh.n_points = run.get_header('k_points', {}).get('nk', 1) sec_kmesh.points = run.get_header('k_points', {}).get('points', None) + sec_scf.threshold_energy_change = run.get_header('scf_threshold_energy_change') + g_vector_sticks = run.get_header('g_vector_sticks', {}).get('Sum', None) if g_vector_sticks is not None: names = [ @@ -3330,7 +3335,6 @@ def parse_method(self, run): # other method variables names = [ - 'scf_threshold_energy_change', 'x_qe_core_charge_realspace', 'x_qe_exact_exchange_fraction', 'x_qe_diagonalization_algorithm', diff --git a/tests/test_openmxparser.py b/tests/test_openmxparser.py index 15621ebb..58c97c83 100644 --- a/tests/test_openmxparser.py +++ b/tests/test_openmxparser.py @@ -79,6 +79,11 @@ def test_HfO2(parser): assert method.electronic.smearing.width == approx(K_to_J(300)) assert method.dft.xc_functional.correlation[0].name == 'GGA_C_PBE' assert method.dft.xc_functional.exchange[0].name == 'GGA_X_PBE' + assert (method.k_mesh.grid == [10, 10, 10]).all() + assert np.allclose(method.k_mesh.points[0], np.array([-0.45000, -0.45000, -0.45000]).astype(complex), rtol=0.0) + assert np.allclose(method.k_mesh.points[499], np.array([-0.05000, 0.45000, 0.45000]).astype(complex), rtol=0.0) + assert method.k_mesh.multiplicities[0] == approx(2.0) + assert method.k_mesh.multiplicities[0] == approx(2.0) system = run.system[0] assert system.atoms.periodic == [True, True, True] @@ -90,6 +95,9 @@ def test_HfO2(parser): assert len(system.atoms.labels) == 12 assert system.atoms.labels[9] == 'O' + scc = run.calculation + assert scc[-1].eigenvalues[0].kpoints_multiplicities[0] == approx(2.0) + def test_AlN(parser): """ @@ -121,6 +129,9 @@ def test_AlN(parser): scf = scc[3].scf_iteration assert len(scf) == 6 scf[5].energy.sum_eigenvalues.value.magnitude == approx(-3.4038520917173614e-17) + assert len(scc[-1].eigenvalues[0].kpoints_multiplicities) == 74 + assert scc[-1].eigenvalues[0].kpoints_multiplicities[-1] == approx(1.0) + assert scc[-1].eigenvalues[0].kpoints_multiplicities[0] == approx(2.0) method = run.method[0] assert method.electronic.n_spin_channels == 1 @@ -131,6 +142,11 @@ def test_AlN(parser): assert method.dft.xc_functional.exchange[0].name == 'GGA_X_PBE' assert method.scf.n_max_iteration == 100 assert method.scf.threshold_energy_change.magnitude == approx(Ha_to_J(1e-7)) + assert (method.k_mesh.grid == [7, 7, 3]).all() + assert np.allclose(method.k_mesh.points[0], np.array([ -0.42857, -0.42857, -0.33333]).astype(complex), rtol=0.0) + assert np.allclose(method.k_mesh.points[73], np.array([0.00000, -0.00000, 0.00000]).astype(complex), rtol=0.0) + assert np.allclose(method.k_mesh.multiplicities[0], np.array([2, 2, 2]).astype(complex), rtol=0.0) + assert method.k_mesh.multiplicities[73] == [1] workflow = archive.workflow2 assert workflow.method.method == 'steepest_descent' @@ -209,6 +225,7 @@ def test_C2N2(parser): assert np.shape(scc[0].forces.total.value) == (4, 3) assert scc[0].forces.total.value[0][0].magnitude == approx(HaB_to_N(0.10002)) assert scc[99].forces.total.value[2][2].magnitude == approx(HaB_to_N(-0.00989)) + assert scc[-1].eigenvalues[0].kpoints_multiplicities == approx(1.0) assert len(run.system) == 100 @@ -218,6 +235,8 @@ def test_C2N2(parser): assert method.electronic.smearing.width == approx(K_to_J(500)) assert method.dft.xc_functional.exchange[0].name == 'LDA_X' assert method.dft.xc_functional.correlation[0].name == 'LDA_C_PZ' + assert (method.k_mesh.grid == [1, 1, 1]).all() + assert method.k_mesh.multiplicities[0] == approx(1.0) workflow = archive.workflow2 assert workflow.method.thermodynamic_ensemble == 'NVT' @@ -284,6 +303,7 @@ def test_CrO2(parser): assert method.dft.xc_functional.correlation[0].name == 'LDA_C_PW' assert method.scf.n_max_iteration == 40 assert method.scf.threshold_energy_change.magnitude == approx(Ha_to_J(1e-7)) + assert (method.k_mesh.grid == [5, 5, 8]).all() eigenvalues = run.calculation[-1].eigenvalues[0] assert eigenvalues.n_kpoints == 100 @@ -316,6 +336,7 @@ def test_graphite(parser): assert method.electronic.van_der_waals_method == 'G10' assert method.scf.n_max_iteration == 100 assert method.scf.threshold_energy_change.magnitude == approx(Ha_to_J(1e-8)) + assert (method.k_mesh.grid == [12, 12, 4]).all() calculation_first = run.calculation[0] stress_tensor_first = calculation_first.stress.total.value diff --git a/tests/test_quantumespressoparser.py b/tests/test_quantumespressoparser.py index f90e9fba..14bd96aa 100644 --- a/tests/test_quantumespressoparser.py +++ b/tests/test_quantumespressoparser.py @@ -37,6 +37,10 @@ def RyB_to_N(value): return (value * ureg.rydberg / ureg.bohr).to_base_units().magnitude +def Ry_to_eV(value): + return (value * ureg.rydberg).to_base_units().magnitude + + def test_scf(parser): archive = EntryArchive() parser.parse('tests/data/quantumespresso/HO_scf/benchmark2.out', archive, None) @@ -73,6 +77,7 @@ def test_scf(parser): assert sec_method.dft.xc_functional.exchange[0].name == 'GGA_X_PBE' assert sec_method.electronic.n_electrons[0] == 8 assert sec_method.electronic.n_spin_channels == 1 + assert sec_method.scf.threshold_energy_change.magnitude == approx(Ry_to_eV(1e-6)) sec_atoms = sec_method.atom_parameters assert len(sec_atoms) == 2 assert sec_atoms[1].label == 'H' @@ -157,6 +162,7 @@ def test_multirun(parser): assert sec_runs[2].calculation[0].scf_iteration[ 11 ].time_physical.magnitude == approx(1189.6) + assert sec_method.scf.threshold_energy_change.magnitude == approx(Ry_to_eV(1e-5)) def test_md(parser): @@ -168,6 +174,7 @@ def test_md(parser): sec_method = sec_run.method[0] assert len(sec_method.k_mesh.points) == 1 assert sec_method.electronic.n_spin_channels == 1 + assert sec_method.scf.threshold_energy_change.magnitude == approx(Ry_to_eV(1e-8)) sec_sccs = sec_run.calculation assert len(sec_sccs) == 50 assert archive.run[0].system[6].atoms.positions[1][2].magnitude == approx( @@ -217,6 +224,7 @@ def test_vcrelax(parser): assert sec_method.electronic.smearing.kind == 'tetrahedra' assert sec_method.electronic.smearing.width is None assert sec_method.electronic.n_spin_channels == 1 + assert sec_method.scf.threshold_energy_change.magnitude == approx(Ry_to_eV(1e-8)) def test_noncolmag(parser): @@ -243,3 +251,4 @@ def test_noncolmag(parser): == (0.01 * ureg.rydberg).to_base_units().magnitude ) assert sec_method.electronic.n_spin_channels is None + assert sec_method.scf.threshold_energy_change.magnitude == approx(Ry_to_eV(1e-8))