-
Notifications
You must be signed in to change notification settings - Fork 32
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Implement CommonBandsWorkChain for CASTEP #258
base: master
Are you sure you want to change the base?
Changes from 11 commits
1a90ef6
a47cfc8
60f0ac5
34ff533
5ff96f8
ccfbd43
c0a6e5e
c0f8b62
dc4d663
8d01573
b1c7020
34060a3
82e96b5
f279031
0495428
ecc4432
d48a79b
a581750
e7c2dba
82bf2f5
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,71 @@ | ||
# -*- coding: utf-8 -*- | ||
"""Implementation of `aiida_common_workflows.common.bands.generator.CommonBandsInputGenerator` for CASTEP.""" | ||
from aiida import engine, orm | ||
|
||
from aiida_common_workflows.generators import CodeType | ||
|
||
from ..generator import CommonBandsInputGenerator | ||
|
||
__all__ = ('CastepCommonBandsInputGenerator',) | ||
|
||
|
||
class CastepCommonBandsInputGenerator(CommonBandsInputGenerator): | ||
"""Generator of inputs for the CastepCommonBandsWorkChain""" | ||
|
||
@classmethod | ||
def define(cls, spec): | ||
"""Define the specification of the input generator. | ||
|
||
The ports defined on the specification are the inputs that will be accepted by the ``get_builder`` method. | ||
""" | ||
super().define(spec) | ||
spec.inputs['engines']['bands']['code'].valid_type = CodeType('castep.castep') | ||
|
||
def _construct_builder(self, **kwargs) -> engine.ProcessBuilder: | ||
"""Construct a process builder based on the provided keyword arguments. | ||
|
||
The keyword arguments will have been validated against the input generator specification. | ||
""" | ||
# pylint: disable=too-many-branches,too-many-statements,too-many-locals | ||
engines = kwargs.get('engines', None) | ||
parent_folder = kwargs['parent_folder'] | ||
bands_kpoints = kwargs['bands_kpoints'] | ||
|
||
# From the parent folder, we retrieve the calculation that created it. Note | ||
# that we are sure it exists (it wouldn't be the same for WorkChains). We then check | ||
# that it is a CastepCalculation and create the builder. | ||
parent_castep_calc = parent_folder.creator | ||
if parent_castep_calc.process_type != 'aiida.calculations:castep.castep': | ||
raise ValueError('The `parent_folder` has not been created by a CastepCalculation') | ||
builder_castep_calc = parent_castep_calc.get_builder_restart() | ||
|
||
# Construct the builder of the `common_bands_wc` from the builder of a CastepCalculation. | ||
builder_common_bands_wc = self.process_class.get_builder() | ||
builder_common_bands_wc.scf.calc_options = orm.Dict(dict=dict(builder_castep_calc.metadata.options)) | ||
# Ensure we use castep_bin for restart, instead of the check file | ||
#builder_common_bands_wc.scf.options = orm.Dict(dict={'use_castep_bin': True}) | ||
|
||
builder_castep_calc.metadata = {} | ||
|
||
# Attach inputs of the calculation | ||
for key, value in builder_castep_calc.items(): | ||
if value and key not in ['metadata', 'structure']: | ||
builder_common_bands_wc.scf.calc[key] = value | ||
|
||
# Updated the structure (in case we have one in output) | ||
if 'output_structure' in parent_castep_calc.outputs: | ||
builder_common_bands_wc.structure = parent_castep_calc.outputs.output_structure | ||
else: | ||
builder_common_bands_wc.structure = parent_castep_calc.inputs.structure | ||
|
||
engb = engines['bands'] | ||
builder_common_bands_wc.scf.calc.code = engines['bands']['code'] | ||
if 'options' in engb: | ||
builder_common_bands_wc.scf.calc_options = orm.Dict(dict=engines['bands']['options']) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think you may have just copied this from the Siesta implementation. Does the Castep implementation also expect the options as a There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. CASTEP actually work with I just updated the code to make it work with Which way do you think is better? For the plain There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I see the problem with the options not being stored, but I am personally not a fan of closing of the original port and replacing it with a custom There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I see, I will just keep both ways acceptable by |
||
|
||
# Set the `bandskpoints` and the `parent_calc_folder` for restart | ||
builder_common_bands_wc.bands_kpoints = bands_kpoints | ||
builder_common_bands_wc.scf.continuation_folder = parent_folder | ||
builder_common_bands_wc.run_separate_scf = orm.Bool(False) | ||
|
||
return builder_common_bands_wc |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
# -*- coding: utf-8 -*- | ||
"""Implementation of `aiida_common_workflows.common.relax.workchain.CommonRelaxWorkChain` for CASTEP.""" | ||
from logging import getLogger | ||
|
||
from aiida.engine import calcfunction | ||
from aiida.orm import Float | ||
from aiida.plugins import WorkflowFactory | ||
|
||
from ..workchain import CommonBandsWorkChain | ||
from .generator import CastepCommonBandsInputGenerator | ||
|
||
__all__ = ('CastepCommonBandsWorkChain',) | ||
|
||
|
||
@calcfunction | ||
def get_fermi_energy(bands): | ||
"""Extract the Fermi energy from the BandsData output""" | ||
efermi = bands.get_attribute('efermi') | ||
if isinstance(efermi, list): | ||
efermi = efermi[0] | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is a good point. Should we discuss whether the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I will put in the list for discussion tomorrow There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. An additional issue is that some code may not have a well defined single efermi energy if there are two spin channels (is such quantity itself well-defined at all?). Not sure if this is a limitation across the board or not. I know CASTEP always give two if there are two spin channels. VASP gives ones fermi energy with spin polarised calculation I think, but it may give a wrong value if spin is constrained. |
||
logger = getLogger(__name__) | ||
logger.warning('Spin polarised calculation - using the efermi energy of the first spin channel.') | ||
return Float(efermi) | ||
|
||
|
||
class CastepCommonBandsWorkChain(CommonBandsWorkChain): | ||
"""Implementation of `aiida_common_workflows.common.bands.workchain.CommonBandsWorkChain` for CASTEP.""" | ||
|
||
_process_class = WorkflowFactory('castep.bands') | ||
_generator_class = CastepCommonBandsInputGenerator | ||
|
||
def convert_outputs(self): | ||
"""Convert the outputs of the sub workchain to the common output specification.""" | ||
if 'band_structure' not in self.ctx.workchain.outputs: | ||
self.report('CastepBandsWorkChain concluded without returning bands!') | ||
return self.exit_codes.ERROR_SUB_PROCESS_FAILED | ||
|
||
self.out('fermi_energy', get_fermi_energy(self.ctx.workchain.outputs['band_structure'])) | ||
|
||
self.out('bands', self.ctx.workchain.outputs['band_structure']) | ||
return None |
Original file line number | Diff line number | Diff line change | ||
---|---|---|---|---|
|
@@ -32,7 +32,6 @@ def define(cls, spec): | |||
) | ||||
spec.input( | ||||
'protocol', | ||||
valid_type=ChoiceType(('fast', 'moderate', 'precise')), | ||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good to discuss whether this should be limited, but this explicit definition did have the advantage that it made it discoverable exactly which protocols are available. If we remove this, we should reintroduce it in some other way. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is an interesting point. The valid protocols can be always extended (actually changed) inside the code implementations of the
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. good point @bosonie. I should be able to just override it in |
||||
default='moderate', | ||||
help='The protocol to use for the automated input generation. This value indicates the level of precision ' | ||||
'of the results and computational cost that the input parameters will be selected for.', | ||||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This will except if
engines
was not specified inkwargs