Skip to content
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

Allow explicitly specifying 'ibrav'. #503

Merged
merged 1 commit into from
Oct 8, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 25 additions & 5 deletions aiida_quantumespresso/calculations/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,12 @@
from aiida.common import datastructures, exceptions
from aiida.common.lang import classproperty

from qe_tools.converters import get_parameters_from_cell

from aiida_quantumespresso.utils.convert import convert_input_to_namelist_entry

from .base import CalcJob
from .helpers import QEInputValidationError


class BasePwCpInputGenerator(CalcJob):
Expand All @@ -23,6 +26,7 @@ class BasePwCpInputGenerator(CalcJob):
_DATAFILE_XML_PRE_6_2 = 'data-file.xml'
_DATAFILE_XML_POST_6_2 = 'data-file-schema.xml'
_ENVIRON_INPUT_FILE_NAME = 'environ.in'
_DEFAULT_IBRAV = 0

# Additional files that should always be retrieved for the specific plugin
_internal_retrieve_list = []
Expand Down Expand Up @@ -285,10 +289,15 @@ def _generate_PWCPinputdata(cls, parameters, settings, pseudos, structure, kpoin

# ============ I prepare the input site data =============
# ------------ CELL_PARAMETERS -----------
cell_parameters_card = 'CELL_PARAMETERS angstrom\n'
for vector in structure.cell:
cell_parameters_card += ('{0:18.10f} {1:18.10f} {2:18.10f}'
'\n'.format(*vector))

# Specify cell parameters only if 'ibrav' is zero.
if input_params.get('SYSTEM', {}).get('ibrav', cls._DEFAULT_IBRAV) == 0:
cell_parameters_card = 'CELL_PARAMETERS angstrom\n'
for vector in structure.cell:
cell_parameters_card += ('{0:18.10f} {1:18.10f} {2:18.10f}'
'\n'.format(*vector))
else:
cell_parameters_card = ''

# ------------- ATOMIC_SPECIES ------------
atomic_species_card_list = []
Expand Down Expand Up @@ -448,7 +457,18 @@ def _generate_PWCPinputdata(cls, parameters, settings, pseudos, structure, kpoin
# Set some variables (look out at the case! NAMELISTS should be
# uppercase, internal flag names must be lowercase)
input_params.setdefault('SYSTEM', {})
input_params['SYSTEM']['ibrav'] = 0
input_params['SYSTEM'].setdefault('ibrav', cls._DEFAULT_IBRAV)
ibrav = input_params['SYSTEM']['ibrav']
if ibrav != 0:
try:
structure_parameters = get_parameters_from_cell(
ibrav=ibrav,
cell=structure.get_attribute('cell'),
tolerance=settings.pop('IBRAV_CELL_TOLERANCE', 1e-6)
)
except ValueError as exc:
raise QEInputValidationError('Cannot get structure parameters from cell: {}'.format(exc)) from exc
input_params['SYSTEM'].update(structure_parameters)
input_params['SYSTEM']['nat'] = len(structure.sites)
input_params['SYSTEM']['ntyp'] = len(structure.kinds)

Expand Down
1 change: 0 additions & 1 deletion aiida_quantumespresso/calculations/cp.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,6 @@ class CpCalculation(BasePwCpInputGenerator):
('CONTROL', 'pseudo_dir'), # set later
('CONTROL', 'outdir'), # set later
('CONTROL', 'prefix'), # set later
('SYSTEM', 'ibrav'), # set later
('SYSTEM', 'celldm'),
('SYSTEM', 'nat'), # set later
('SYSTEM', 'ntyp'), # set later
Expand Down
1 change: 0 additions & 1 deletion aiida_quantumespresso/calculations/helpers/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,6 @@ def pw_input_helper(input_params, structure, stop_at_first_error=False, flat_mod
i.lower() for i in [
'pseudo_dir',
'outdir',
'ibrav',
'celldm',
'nat',
'ntyp',
Expand Down
1 change: 0 additions & 1 deletion aiida_quantumespresso/calculations/pw.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@ class PwCalculation(BasePwCpInputGenerator):
('CONTROL', 'pseudo_dir'),
('CONTROL', 'outdir'),
('CONTROL', 'prefix'),
('SYSTEM', 'ibrav'),
('SYSTEM', 'celldm'),
('SYSTEM', 'nat'),
('SYSTEM', 'ntyp'),
Expand Down
3 changes: 0 additions & 3 deletions aiida_quantumespresso/tools/pwinputparser.py
Original file line number Diff line number Diff line change
Expand Up @@ -96,9 +96,6 @@ def create_builder_from_file(input_folder, input_file_name, code, metadata, pseu
builder.structure = parsed_file.get_structuredata()
builder.kpoints = parsed_file.get_kpointsdata()

if parsed_file.namelists['SYSTEM']['ibrav'] != 0:
raise NotImplementedError('Found ibrav != 0: `aiida-quantumespresso` currently only supports ibrav = 0.')

# Then, strip the namelist items that the plugin doesn't allow or sets later.
# NOTE: If any of the position or cell units are in alat or crystal
# units, that will be taken care of by the input parsing tools, and
Expand Down
3 changes: 2 additions & 1 deletion docs/source/user_guide/calculation_plugins/cp.rst
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,6 @@ Inputs
'CONTROL', 'pseudo_dir': pseudopotential directory
'CONTROL', 'outdir': scratch directory
'CONTROL', 'prefix': file prefix
'SYSTEM', 'ibrav': cell shape
'SYSTEM', 'celldm': cell dm
'SYSTEM', 'nat': number of atoms
'SYSTEM', 'ntyp': number of species
Expand All @@ -48,6 +47,8 @@ Inputs

Those keywords should not be specified, otherwise the submission will fail.

The `SYSTEM`, `ibrav` keyword is optional. If it is not specified, `ibrav=0` is used. When a non-zero `ibrav` is given, `aiida-quantumespresso` automatically extracts the cell parameters. As a consistency check, the cell is re-constructed from these parameters and compared to the input cell. The input structure needs to match the convention detailed in the `pw.x documentation <https://www.quantum-espresso.org/Doc/INPUT_PW.html#idm199>`_. The tolerance used in this check can be adjusted with the `IBRAV_CELL_TOLERANCE` key in the `settings` dictionary. It defines the absolute tolerance on each element of the cell matrix.
sphuber marked this conversation as resolved.
Show resolved Hide resolved

* **structure**, class :py:class:`StructureData <aiida.orm.nodes.data.structure.StructureData>`
The initial ionic configuration of the CP molecular dynamics.
* **settings**, class :py:class:`Dict <aiida.orm.nodes.data.dict.Dict>` (optional)
Expand Down
3 changes: 2 additions & 1 deletion docs/source/user_guide/calculation_plugins/pw.rst
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,6 @@ This can then be used directly in the process builder of for example a ``PwCalcu
'CONTROL', 'pseudo_dir': pseudopotential directory
'CONTROL', 'outdir': scratch directory
'CONTROL', 'prefix': file prefix
'SYSTEM', 'ibrav': cell shape
'SYSTEM', 'celldm': cell dm
'SYSTEM', 'nat': number of atoms
'SYSTEM', 'ntyp': number of species
Expand All @@ -94,6 +93,8 @@ This can then be used directly in the process builder of for example a ``PwCalcu

Those keywords should not be specified, otherwise the submission will fail.

The `SYSTEM`, `ibrav` keyword is optional. If it is not specified, `ibrav=0` is used. When a non-zero `ibrav` is given, `aiida-quantumespresso` automatically extracts the cell parameters. As a consistency check, the cell is re-constructed from these parameters and compared to the input cell. The input structure needs to match the convention detailed in the `pw.x documentation <https://www.quantum-espresso.org/Doc/INPUT_PW.html#idm199>`_. The tolerance used in this check can be adjusted with the `IBRAV_CELL_TOLERANCE` key in the `settings` dictionary. It defines the absolute tolerance on each element of the cell matrix.

* **structure**, class :py:class:`StructureData <aiida.orm.nodes.data.structure.StructureData>`
* **settings**, class :py:class:`Dict <aiida.orm.nodes.data.dict.Dict>` (optional)
An optional dictionary that activates non-default operations. For a list of possible
Expand Down
3 changes: 2 additions & 1 deletion setup.json
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,8 @@
"aiida_core[atomic_tools]~=1.3",
"packaging",
"qe-tools~=2.0rc1",
"xmlschema~=1.2"
"xmlschema~=1.2",
"numpy"
],
"license": "MIT License",
"name": "aiida_quantumespresso",
Expand Down
125 changes: 125 additions & 0 deletions tests/calculations/test_pw.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
# -*- coding: utf-8 -*-
"""Tests for the `PwCalculation` class."""

import pytest

from aiida import orm
from aiida.common import datastructures
from aiida_quantumespresso.utils.resources import get_default_options
from aiida_quantumespresso.calculations.helpers import QEInputValidationError


def test_pw_default(fixture_sandbox, generate_calc_job, generate_inputs_pw, file_regression):
Expand Down Expand Up @@ -30,3 +36,122 @@ def test_pw_default(fixture_sandbox, generate_calc_job, generate_inputs_pw, file
# Checks on the files written to the sandbox folder as raw input
assert sorted(fixture_sandbox.get_content_list()) == sorted(['aiida.in', 'pseudo', 'out'])
file_regression.check(input_written, encoding='utf-8', extension='.in')


def test_pw_ibrav(
fixture_sandbox, generate_calc_job, fixture_code, generate_kpoints_mesh, generate_upf_data, file_regression
):
"""Test a `PwCalculation` where `ibrav` is explicitly specified."""
entry_point_name = 'quantumespresso.pw'

parameters = {'CONTROL': {'calculation': 'scf'}, 'SYSTEM': {'ecutrho': 240.0, 'ecutwfc': 30.0, 'ibrav': 2}}

# The structure needs to be rotated in the same way QE does it for ibrav=2.
param = 5.43
cell = [[-param / 2., 0, param / 2.], [0, param / 2., param / 2.], [-param / 2., param / 2., 0]]
structure = orm.StructureData(cell=cell)
structure.append_atom(position=(0., 0., 0.), symbols='Si', name='Si')
structure.append_atom(position=(param / 4., param / 4., param / 4.), symbols='Si', name='Si')

upf = generate_upf_data('Si')
inputs = {
'code': fixture_code(entry_point_name),
'structure': structure,
'kpoints': generate_kpoints_mesh(2),
'parameters': orm.Dict(dict=parameters),
'pseudos': {
'Si': upf
},
'metadata': {
'options': get_default_options()
}
}

calc_info = generate_calc_job(fixture_sandbox, entry_point_name, inputs)

cmdline_params = ['-in', 'aiida.in']
local_copy_list = [(upf.uuid, upf.filename, u'./pseudo/Si.upf')]
retrieve_list = ['aiida.out', './out/aiida.save/data-file-schema.xml', './out/aiida.save/data-file.xml']
retrieve_temporary_list = [['./out/aiida.save/K*[0-9]/eigenval*.xml', '.', 2]]

# Check the attributes of the returned `CalcInfo`
assert isinstance(calc_info, datastructures.CalcInfo)
assert sorted(calc_info.cmdline_params) == sorted(cmdline_params)
assert sorted(calc_info.local_copy_list) == sorted(local_copy_list)
assert sorted(calc_info.retrieve_list) == sorted(retrieve_list)
assert sorted(calc_info.retrieve_temporary_list) == sorted(retrieve_temporary_list)
assert sorted(calc_info.remote_symlink_list) == sorted([])

with fixture_sandbox.open('aiida.in') as handle:
input_written = handle.read()

# Checks on the files written to the sandbox folder as raw input
assert sorted(fixture_sandbox.get_content_list()) == sorted(['aiida.in', 'pseudo', 'out'])
file_regression.check(input_written, encoding='utf-8', extension='.in')


def test_pw_wrong_ibrav(fixture_sandbox, generate_calc_job, fixture_code, generate_kpoints_mesh, generate_upf_data):
greschd marked this conversation as resolved.
Show resolved Hide resolved
"""Test that a `PwCalculation` with an incorrect `ibrav` raises."""
entry_point_name = 'quantumespresso.pw'

parameters = {'CONTROL': {'calculation': 'scf'}, 'SYSTEM': {'ecutrho': 240.0, 'ecutwfc': 30.0, 'ibrav': 2}}

# Here we use the wrong order of unit cell vectors on purpose.
param = 5.43
cell = [[0, param / 2., param / 2.], [-param / 2., 0, param / 2.], [-param / 2., param / 2., 0]]
structure = orm.StructureData(cell=cell)
structure.append_atom(position=(0., 0., 0.), symbols='Si', name='Si')
structure.append_atom(position=(param / 4., param / 4., param / 4.), symbols='Si', name='Si')

upf = generate_upf_data('Si')
inputs = {
'code': fixture_code(entry_point_name),
'structure': structure,
'kpoints': generate_kpoints_mesh(2),
'parameters': orm.Dict(dict=parameters),
'pseudos': {
'Si': upf
},
'metadata': {
'options': get_default_options()
}
}

with pytest.raises(QEInputValidationError):
generate_calc_job(fixture_sandbox, entry_point_name, inputs)


def test_pw_ibrav_tol(fixture_sandbox, generate_calc_job, fixture_code, generate_kpoints_mesh, generate_upf_data):
"""Test that `IBRAV_TOLERANCE` controls the tolerance when checking cell consistency."""
entry_point_name = 'quantumespresso.pw'

parameters = {'CONTROL': {'calculation': 'scf'}, 'SYSTEM': {'ecutrho': 240.0, 'ecutwfc': 30.0, 'ibrav': 2}}

# The structure needs to be rotated in the same way QE does it for ibrav=2.
param = 5.43
eps = 0.1
cell = [[-param / 2., eps, param / 2.], [-eps, param / 2. + eps, param / 2.], [-param / 2., param / 2., 0]]
structure = orm.StructureData(cell=cell)
structure.append_atom(position=(0., 0., 0.), symbols='Si', name='Si')
structure.append_atom(position=(param / 4., param / 4., param / 4.), symbols='Si', name='Si')

upf = generate_upf_data('Si')
inputs = {
'code': fixture_code(entry_point_name),
'structure': structure,
'kpoints': generate_kpoints_mesh(2),
'parameters': orm.Dict(dict=parameters),
'pseudos': {
'Si': upf
},
'metadata': {
'options': get_default_options()
},
}
# Without adjusting the tolerance, the check fails.
with pytest.raises(QEInputValidationError):
generate_calc_job(fixture_sandbox, entry_point_name, inputs)

# After adjusting the tolerance, the input validation no longer fails.
inputs['settings'] = orm.Dict(dict={'ibrav_cell_tolerance': eps})
generate_calc_job(fixture_sandbox, entry_point_name, inputs)
24 changes: 24 additions & 0 deletions tests/calculations/test_pw/test_pw_ibrav.in
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
&CONTROL
calculation = 'scf'
outdir = './out/'
prefix = 'aiida'
pseudo_dir = './pseudo/'
verbosity = 'high'
/
&SYSTEM
a = 5.4300000000d+00
ecutrho = 2.4000000000d+02
ecutwfc = 3.0000000000d+01
ibrav = 2
nat = 2
ntyp = 1
/
&ELECTRONS
/
ATOMIC_SPECIES
Si 28.0855 Si.upf
ATOMIC_POSITIONS angstrom
Si 0.0000000000 0.0000000000 0.0000000000
Si 1.3575000000 1.3575000000 1.3575000000
K_POINTS automatic
2 2 2 0 0 0
62 changes: 61 additions & 1 deletion tests/tools/test_immigrate.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
"""Tests for immigrating `PwCalculation`s."""
import os

import numpy as np

from aiida_quantumespresso.tools.pwinputparser import create_builder_from_file


Expand Down Expand Up @@ -47,10 +49,68 @@ def test_create_builder(fixture_sandbox, fixture_code, generate_upf_data, genera
},
'SYSTEM': {
'ecutrho': 240.0,
'ecutwfc': 30.0
'ecutwfc': 30.0,
'ibrav': 0,
}
}
assert 'kpoints' in builder
assert 'structure' in builder

generate_calc_job(fixture_sandbox, entry_point_name, builder)


def test_create_builder_nonzero_ibrav(fixture_sandbox, fixture_code, generate_upf_data, generate_calc_job):
"""Test the `create_builder_from_file` method that parses an existing `pw.x` folder into a process builder.

The input file used is the one generated for `tests.calculations.test_pw.test_pw_ibrav`.
"""
entry_point_name = 'quantumespresso.pw'
code = fixture_code(entry_point_name)

metadata = {
'options': {
'resources': {
'num_machines': 1,
'num_mpiprocs_per_machine': 32,
},
'max_memory_kb': 1000,
'max_wallclock_seconds': 60 * 60 * 12,
'withmpi': True,
}
}

in_foldername = os.path.join('tests', 'calculations', 'test_pw')
in_folderpath = os.path.abspath(in_foldername)

upf_foldername = os.path.join('tests', 'fixtures', 'pseudos')
upf_folderpath = os.path.abspath(upf_foldername)
si_upf = generate_upf_data('Si')
si_upf.store()

builder = create_builder_from_file(
in_folderpath, 'test_pw_ibrav.in', code, metadata, upf_folderpath, use_first=True
)

assert builder['code'] == code
assert builder['metadata'] == metadata
pseudo_hash = si_upf.get_hash()
assert pseudo_hash is not None
assert builder['pseudos']['Si'].get_hash() == pseudo_hash
assert builder['parameters'].get_dict() == {
'CONTROL': {
'calculation': 'scf',
'verbosity': 'high'
},
'SYSTEM': {
'ecutrho': 240.0,
'ecutwfc': 30.0,
'ibrav': 2,
}
}
assert 'kpoints' in builder
assert 'structure' in builder
param = 5.43
np.testing.assert_allclose([[-param / 2., 0, param / 2.], [0, param / 2., param / 2.], [-param / 2., param / 2., 0]
], builder.structure.get_attribute('cell'))

generate_calc_job(fixture_sandbox, entry_point_name, builder)