Skip to content

Commit d7e927f

Browse files
t-reentssphuber
authored andcommitted
🐛 PdosWorkChain: Enable energy range around the Fermi level
This PR fixes the existing `align_to_fermi` input. It is implemented as a top level argument, however, it was only expected in the parameter dictionaries of `dos` and `projwfc` in the previous version. Hence, it was never used as intended. * Further bugs in the input generation were fixed * The existing `align_to_fermi` input of type `Bool` is replaced by `energy_range_vs_fermi`, which is a `List` that can optionally specify the energy range around the Fermi level that should be covered by the DOS and PDOS. Co-authored-by: Sebastiaan Huber <mail@sphuber.net>
1 parent c1aa823 commit d7e927f

File tree

3 files changed

+83
-47
lines changed

3 files changed

+83
-47
lines changed

src/aiida_quantumespresso/workflows/pdos.py

Lines changed: 42 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,10 @@
1010
1111
Additional functionality:
1212
13-
- Setting ``'align_to_fermi': True`` in the inputs will ensure that the energy range is centred around the Fermi
14-
energy when `Emin` and `Emax` are provided for both the `dos` and `projwfc` inputs. This is useful when you are only
15-
interested in a certain energy range around the Fermi energy. By default the energy range is extracted from the
16-
NSCF calculation.
13+
- Setting ``'energy_range_vs_fermi'`` in the inputs allows to specify an energy range around the Fermi level that should
14+
be covered by the DOS and PDOS. This is useful when you are only interested in a certain energy range around the
15+
Fermi energy. By default, this is not specified and the energy range given in the `dos.x` and `projwfc.x`
16+
inputs will be used.
1717
1818
Storage memory management:
1919
@@ -97,7 +97,6 @@ def validate_inputs(value, _):
9797
9898
- Check that either the `scf` or `nscf.pw.parent_folder` inputs is provided.
9999
- Check that the `Emin`, `Emax` and `DeltaE` inputs are the same for the `dos` and `projwfc` namespaces.
100-
- Check that `Emin` and `Emax` are provided in case `align_to_fermi` is set to `True`.
101100
"""
102101
# Check that either the `scf` input or `nscf.pw.parent_folder` is provided.
103102
import warnings
@@ -113,10 +112,13 @@ def validate_inputs(value, _):
113112
if value['dos']['parameters']['DOS'].get(par, None) != value['projwfc']['parameters']['PROJWFC'].get(par, None):
114113
return f'The `{par}`` parameter has to be equal for the `dos` and `projwfc` inputs.'
115114

116-
if value.get('align_to_fermi', False):
115+
if value.get('energy_range_vs_fermi', False):
117116
for par in ['Emin', 'Emax']:
118-
if value['dos']['parameters']['DOS'].get(par, None) is None:
119-
return f'The `{par}`` parameter must be set in case `align_to_fermi` is set to `True`.'
117+
if value['dos']['parameters']['DOS'].get(par, None):
118+
warnings.warn(
119+
f'The `{par}` parameter and `energy_range_vs_fermi` were specified.'
120+
'The value in `energy_range_vs_fermi` will be used.'
121+
)
120122

121123
if 'nbands_factor' in value and 'nbnd' in value['nscf']['pw']['parameters'].base.attributes.get('SYSTEM', {}):
122124
return PdosWorkChain.exit_codes.ERROR_INVALID_INPUT_NUMBER_OF_BANDS.message
@@ -160,6 +162,17 @@ def validate_projwfc(value, _):
160162
jsonschema.validate(value['parameters'].get_dict()['PROJWFC'], get_parameter_schema())
161163

162164

165+
def validate_energy_range_vs_fermi(value, _):
166+
"""Validate specified energy_range_vs_fermi.
167+
168+
- List needs to consist of two float values.
169+
"""
170+
if len(value) != 2:
171+
return f'`energy_range_vs_fermi` should be a `List` of length two, but got: {value}'
172+
if not all(isinstance(val, (float, int)) for val in value):
173+
return f'`energy_range_vs_fermi` should be a `List` of floats, but got: {value}'
174+
175+
163176
def clean_calcjob_remote(node):
164177
"""Clean the remote directory of a ``CalcJobNode``."""
165178
cleaned = False
@@ -220,14 +233,15 @@ def define(cls, spec):
220233
help='Terminate workchain steps before submitting calculations (test purposes only).'
221234
)
222235
spec.input(
223-
'align_to_fermi',
224-
valid_type=orm.Bool,
236+
'energy_range_vs_fermi',
237+
valid_type=orm.List,
238+
required=False,
225239
serializer=to_aiida_type,
226-
default=lambda: orm.Bool(False),
240+
validator=validate_energy_range_vs_fermi,
227241
help=(
228-
'If true, Emin=>Emin-Efermi & Emax=>Emax-Efermi, where Efermi is taken from the `nscf` calculation. '
229-
'Note that it only makes sense to align `Emax` and `Emin` to the fermi level in case they are actually '
230-
'provided by in the `dos` and `projwfc` inputs, since otherwise the '
242+
'Energy range with respect to the Fermi level that should be covered in DOS and PROJWFC calculation.'
243+
'If not specified but Emin and Emax are specified in the input parameters, these values will be used.'
244+
'Otherwise, the default values are extracted from the NSCF calculation.'
231245
)
232246
)
233247
spec.input('nbands_factor', valid_type=orm.Float, required=False,
@@ -490,10 +504,14 @@ def _generate_dos_inputs(self):
490504
dos_inputs = AttributeDict(self.exposed_inputs(DosCalculation, 'dos'))
491505
dos_inputs.parent_folder = self.ctx.nscf_parent_folder
492506
dos_parameters = self.inputs.dos.parameters.get_dict()
507+
energy_range_vs_fermi = self.inputs.get('energy_range_vs_fermi')
493508

494-
if dos_parameters.pop('align_to_fermi', False):
495-
dos_parameters['DOS']['Emin'] = dos_parameters['Emin'] + self.ctx.nscf_fermi
496-
dos_parameters['DOS']['Emax'] = dos_parameters['Emax'] + self.ctx.nscf_fermi
509+
if energy_range_vs_fermi:
510+
dos_parameters['DOS']['Emin'] = energy_range_vs_fermi[0] + self.ctx.nscf_fermi
511+
dos_parameters['DOS']['Emax'] = energy_range_vs_fermi[1] + self.ctx.nscf_fermi
512+
else:
513+
dos_parameters['DOS'].setdefault('Emin', self.ctx.nscf_emin)
514+
dos_parameters['DOS'].setdefault('Emax', self.ctx.nscf_emax)
497515

498516
dos_inputs.parameters = orm.Dict(dos_parameters)
499517
dos_inputs['metadata']['call_link_label'] = 'dos'
@@ -504,10 +522,14 @@ def _generate_projwfc_inputs(self):
504522
projwfc_inputs = AttributeDict(self.exposed_inputs(ProjwfcCalculation, 'projwfc'))
505523
projwfc_inputs.parent_folder = self.ctx.nscf_parent_folder
506524
projwfc_parameters = self.inputs.projwfc.parameters.get_dict()
525+
energy_range_vs_fermi = self.inputs.get('energy_range_vs_fermi')
507526

508-
if projwfc_parameters.pop('align_to_fermi', False):
509-
projwfc_parameters['PROJWFC']['Emin'] = projwfc_parameters['Emin'] + self.ctx.nscf_fermi
510-
projwfc_parameters['PROJWFC']['Emax'] = projwfc_parameters['Emax'] + self.ctx.nscf_fermi
527+
if energy_range_vs_fermi:
528+
projwfc_parameters['PROJWFC']['Emin'] = energy_range_vs_fermi[0] + self.ctx.nscf_fermi
529+
projwfc_parameters['PROJWFC']['Emax'] = energy_range_vs_fermi[1] + self.ctx.nscf_fermi
530+
else:
531+
projwfc_parameters['PROJWFC'].setdefault('Emin', self.ctx.nscf_emin)
532+
projwfc_parameters['PROJWFC'].setdefault('Emax', self.ctx.nscf_emax)
511533

512534
projwfc_inputs.parameters = orm.Dict(projwfc_parameters)
513535
projwfc_inputs['metadata']['call_link_label'] = 'projwfc'

tests/conftest.py

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -879,8 +879,8 @@ def _generate_workchain_ph(exit_code=None, inputs=None, return_inputs=False):
879879
def generate_workchain_pdos(generate_workchain, generate_inputs_pw, fixture_code):
880880
"""Generate an instance of a `PdosWorkChain`."""
881881

882-
def _generate_workchain_pdos():
883-
from aiida.orm import Bool, Dict
882+
def _generate_workchain_pdos(emin=None, emax=None, energy_range_vs_fermi=None):
883+
from aiida.orm import Bool, Dict, List
884884

885885
from aiida_quantumespresso.utils.resources import get_default_options
886886

@@ -902,12 +902,15 @@ def _generate_workchain_pdos():
902902

903903
dos_params = {
904904
'DOS': {
905-
'Emin': -10,
906-
'Emax': 10,
907905
'DeltaE': 0.01,
908906
}
909907
}
910-
projwfc_params = {'PROJWFC': {'Emin': -10, 'Emax': 10, 'DeltaE': 0.01, 'ngauss': 0, 'degauss': 0.01}}
908+
projwfc_params = {'PROJWFC': {'DeltaE': 0.01, 'ngauss': 0, 'degauss': 0.01}}
909+
910+
if emin and emax:
911+
dos_params['DOS'].update({'Emin': emin, 'Emax': emax})
912+
projwfc_params['PROJWFC'].update({'Emin': emin, 'Emax': emax})
913+
911914
dos = {
912915
'code': fixture_code('quantumespresso.dos'),
913916
'parameters': Dict(dos_params),
@@ -928,9 +931,10 @@ def _generate_workchain_pdos():
928931
'nscf': nscf,
929932
'dos': dos,
930933
'projwfc': projwfc,
931-
'align_to_fermi': Bool(True),
932934
'dry_run': Bool(True)
933935
}
936+
if energy_range_vs_fermi:
937+
inputs.update({'energy_range_vs_fermi': List(energy_range_vs_fermi)})
934938

935939
return generate_workchain(entry_point, inputs)
936940

tests/workflows/test_pdos.py

Lines changed: 31 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
from aiida.common import LinkType
88
from aiida.engine.utils import instantiate_process
99
from aiida.manage.manager import get_manager
10+
import numpy as np
1011
from plumpy import ProcessState
1112
import pytest
1213

@@ -27,31 +28,38 @@ def instantiate_process_cls(process_cls, inputs):
2728
return instantiate_process(runner, process_cls, **inputs)
2829

2930

31+
def check_pdos_energy_range(dos_inputs, projwfc_inputs, expected_p_dos_inputs):
32+
"""Check the energy range of the pdos calculation."""
33+
# check generated inputs
34+
dos_params = dos_inputs.parameters.get_dict()
35+
projwfc_params = projwfc_inputs.parameters.get_dict()
36+
37+
assert np.isclose(dos_params['DOS']['Emin'], expected_p_dos_inputs[0])
38+
assert np.isclose(dos_params['DOS']['Emax'], expected_p_dos_inputs[1])
39+
assert np.isclose(projwfc_params['PROJWFC']['Emin'], expected_p_dos_inputs[0])
40+
assert np.isclose(projwfc_params['PROJWFC']['Emax'], expected_p_dos_inputs[1])
41+
42+
3043
@pytest.mark.parametrize(
31-
'nscf_output_parameters', [
32-
{
33-
'fermi_energy': 6.9
34-
},
35-
{
36-
'fermi_energy_down': 5.9,
37-
'fermi_energy_up': 6.9
38-
},
39-
]
44+
'nscf_output_parameters,energy_range_inputs,expected_p_dos_inputs',
45+
[({
46+
'fermi_energy': 6.9
47+
}, (-10, 10, None), (-10, 10)),
48+
({
49+
'fermi_energy_down': 5.9,
50+
'fermi_energy_up': 6.9
51+
}, (None, None, [-10, 10]), (-3.1, 16.9)), ({
52+
'fermi_energy': 6.9
53+
}, (None, None, None), (-5.64024889, 8.91047649))]
4054
)
4155
def test_default(
42-
generate_workchain_pdos,
43-
generate_workchain_pw,
44-
fixture_localhost,
45-
generate_remote_data,
46-
generate_calc_job,
47-
generate_calc_job_node,
48-
fixture_sandbox,
49-
generate_bands_data,
50-
nscf_output_parameters,
56+
generate_workchain_pdos, generate_workchain_pw, fixture_localhost, generate_remote_data, generate_calc_job,
57+
generate_calc_job_node, fixture_sandbox, generate_bands_data, nscf_output_parameters, energy_range_inputs,
58+
expected_p_dos_inputs
5159
):
5260
"""Test instantiating the WorkChain, then mock its process, by calling methods in the ``spec.outline``."""
5361

54-
wkchain = generate_workchain_pdos()
62+
wkchain = generate_workchain_pdos(*energy_range_inputs)
5563
assert wkchain.setup() is None
5664
assert wkchain.serial_clean() is False
5765

@@ -93,8 +101,7 @@ def test_default(
93101
result.store()
94102
result.base.links.add_incoming(mock_wknode, link_type=LinkType.RETURN, link_label='output_parameters')
95103

96-
bands_data = generate_bands_data()
97-
bands_data.store()
104+
bands_data = generate_bands_data().store()
98105
bands_data.base.links.add_incoming(mock_wknode, link_type=LinkType.RETURN, link_label='output_band')
99106

100107
wkchain.ctx.workchain_nscf = mock_wknode
@@ -104,6 +111,9 @@ def test_default(
104111

105112
# mock run dos and projwfc, and check that their inputs are acceptable
106113
dos_inputs, projwfc_inputs = wkchain.run_pdos_parallel()
114+
115+
check_pdos_energy_range(dos_inputs, projwfc_inputs, expected_p_dos_inputs)
116+
107117
generate_calc_job(fixture_sandbox, 'quantumespresso.dos', dos_inputs)
108118
generate_calc_job(fixture_sandbox, 'quantumespresso.projwfc', projwfc_inputs)
109119

0 commit comments

Comments
 (0)