-
Notifications
You must be signed in to change notification settings - Fork 32
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
PySCF is a Python-based Simulations of Chemistry Framework and the implementation is provided through the `aiida-pyscf` plugin.
- Loading branch information
Showing
6 changed files
with
195 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
"""Module with the implementations of the common structure relaxation workchain for pyscf.""" | ||
from .generator import * | ||
from .workchain import * | ||
|
||
__all__ = generator.__all__ + workchain.__all__ |
105 changes: 105 additions & 0 deletions
105
src/aiida_common_workflows/workflows/relax/pyscf/generator.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,105 @@ | ||
"""Implementation of `aiida_common_workflows.common.relax.generator.CommonRelaxInputGenerator` for pyscf.""" | ||
import pathlib | ||
import warnings | ||
|
||
import yaml | ||
from aiida import engine, orm, plugins | ||
|
||
from aiida_common_workflows.common import ElectronicType, RelaxType, SpinType | ||
from aiida_common_workflows.generators import ChoiceType, CodeType | ||
|
||
from ..generator import CommonRelaxInputGenerator | ||
|
||
__all__ = ('PyscfCommonRelaxInputGenerator',) | ||
|
||
StructureData = plugins.DataFactory('structure') | ||
|
||
|
||
class PyscfCommonRelaxInputGenerator(CommonRelaxInputGenerator): | ||
"""Input generator for the common relax workflow implementation of pyscf.""" | ||
|
||
def __init__(self, *args, **kwargs): | ||
"""Construct an instance of the input generator, validating the class attributes.""" | ||
process_class = kwargs.get('process_class', None) | ||
super().__init__(*args, **kwargs) | ||
self._initialize_protocols() | ||
|
||
def _initialize_protocols(self): | ||
"""Initialize the protocols class attribute by parsing them from the configuration file.""" | ||
with (pathlib.Path(__file__).parent / 'protocol.yml').open() as handle: | ||
self._protocols = yaml.safe_load(handle) | ||
self._default_protocol = 'moderate' | ||
|
||
@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['spin_type'].valid_type = ChoiceType((SpinType.NONE, SpinType.COLLINEAR)) | ||
spec.inputs['relax_type'].valid_type = ChoiceType((RelaxType.NONE, RelaxType.POSITIONS)) | ||
spec.inputs['electronic_type'].valid_type = ChoiceType((ElectronicType.METAL, ElectronicType.INSULATOR)) | ||
spec.inputs['engines']['relax']['code'].valid_type = CodeType('pyscf.base') | ||
|
||
def _construct_builder( | ||
self, | ||
structure, | ||
engines, | ||
protocol, | ||
spin_type, | ||
relax_type, | ||
electronic_type, | ||
magnetization_per_site=None, | ||
**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. | ||
""" | ||
if not self.is_valid_protocol(protocol): | ||
raise ValueError( | ||
f'selected protocol {protocol} is not valid, please choose from: {", ".join(self.get_protocol_names())}' | ||
) | ||
|
||
protocol_inputs = self.get_protocol(protocol) | ||
parameters = protocol_inputs.pop('parameters') | ||
|
||
if relax_type == RelaxType.NONE: | ||
parameters.pop('optimizer') | ||
|
||
if spin_type == SpinType.COLLINEAR: | ||
parameters['mean_field']['method'] = 'DKS' | ||
parameters['mean_field']['collinear'] = 'mcol' | ||
|
||
num_electrons = structure.get_pymatgen_molecule().nelectrons | ||
|
||
if spin_type == SpinType.NONE and num_electrons % 2 == 1: | ||
raise ValueError('structure has odd number of electrons, please select `spin_type = SpinType.COLLINEAR`') | ||
|
||
if spin_type == SpinType.COLLINEAR: | ||
if magnetization_per_site is None: | ||
multiplicity = 1 | ||
else: | ||
warnings.warn('magnetization_per_site site-resolved info is disregarded, only total spin is processed.') | ||
# ``magnetization_per_site`` is in units of Bohr magnetons, multiple by 0.5 to get atomic units | ||
total_spin = 0.5 * abs(sum(magnetization_per_site)) | ||
multiplicity = 2 * total_spin + 1 | ||
|
||
# In case of even/odd electrons, find closest odd/even multiplicity | ||
if num_electrons % 2 == 0: | ||
# round guess to nearest odd integer | ||
spin_multiplicity = int(round((multiplicity - 1) / 2) * 2 + 1) | ||
else: | ||
# round guess to nearest even integer; 0 goes to 2 | ||
spin_multiplicity = max([int(round(multiplicity / 2) * 2), 2]) | ||
|
||
parameters['structure']['spin'] = int((spin_multiplicity - 1) / 2) | ||
|
||
builder = self.process_class.get_builder() | ||
builder.pyscf.code = engines['relax']['code'] | ||
builder.pyscf.structure = structure | ||
builder.pyscf.parameters = orm.Dict(parameters) | ||
builder.pyscf.metadata.options = engines['relax']['options'] | ||
|
||
return builder |
35 changes: 35 additions & 0 deletions
35
src/aiida_common_workflows/workflows/relax/pyscf/protocol.yml
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
fast: | ||
description: Protocol to relax a structure with low precision at minimal computational cost for testing purposes. | ||
parameters: | ||
mean_field: | ||
method: UKS | ||
structure: | ||
basis: def2-svp | ||
optimizer: | ||
solver: geomeTRIC | ||
convergence_parameters: | ||
convergence_energy: 1E-6 | ||
moderate: | ||
description: Protocol to relax a structure with normal precision at moderate computational cost. | ||
parameters: | ||
mean_field: | ||
method: UKS | ||
xc: pbe | ||
structure: | ||
basis: def2-tzvp | ||
optimizer: | ||
solver: geomeTRIC | ||
# convergence_parameters: | ||
# convergence_energy: 1E-6 | ||
precise: | ||
description: Protocol to relax a structure with high precision at higher computational cost. | ||
parameters: | ||
mean_field: | ||
method: UHF | ||
# xc: 'pbe' | ||
structure: | ||
basis: def2-qzvp | ||
optimizer: | ||
solver: geomeTRIC | ||
# convergence_parameters: | ||
# convergence_energy: 1E-6 |
44 changes: 44 additions & 0 deletions
44
src/aiida_common_workflows/workflows/relax/pyscf/workchain.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
"""Implementation of `aiida_common_workflows.common.relax.workchain.CommonRelaxWorkChain` for pyscf.""" | ||
import numpy | ||
from aiida import orm | ||
from aiida.engine import calcfunction | ||
from aiida.plugins import WorkflowFactory | ||
|
||
from ..workchain import CommonRelaxWorkChain | ||
from .generator import PyscfCommonRelaxInputGenerator | ||
|
||
__all__ = ('PyscfCommonRelaxWorkChain',) | ||
|
||
|
||
@calcfunction | ||
def extract_energy_from_parameters(parameters): | ||
"""Return the total energy from the given parameters node.""" | ||
total_energy = parameters.get_attribute('total_energy') | ||
return {'total_energy': orm.Float(total_energy)} | ||
|
||
|
||
@calcfunction | ||
def extract_forces_from_parameters(parameters): | ||
"""Return the forces from the given parameters node.""" | ||
forces = orm.ArrayData() | ||
forces.set_array('forces', numpy.array(parameters.get_attribute('forces'))) | ||
return {'forces': forces} | ||
|
||
|
||
class PyscfCommonRelaxWorkChain(CommonRelaxWorkChain): | ||
"""Implementation of `aiida_common_workflows.common.relax.workchain.CommonRelaxWorkChain` for pyscf.""" | ||
|
||
_process_class = WorkflowFactory('pyscf.base') | ||
_generator_class = PyscfCommonRelaxInputGenerator | ||
|
||
def convert_outputs(self): | ||
"""Convert the outputs of the sub workchain to the common output specification.""" | ||
outputs = self.ctx.workchain.outputs | ||
total_energy = extract_energy_from_parameters(outputs.parameters)['total_energy'] | ||
forces = extract_forces_from_parameters(outputs.parameters)['forces'] | ||
|
||
if 'structure' in outputs: | ||
self.out('relaxed_structure', outputs.structure) | ||
|
||
self.out('total_energy', total_energy) | ||
self.out('forces', forces) |