Skip to content

Commit

Permalink
Allow explicitly specifying 'ibrav'.
Browse files Browse the repository at this point in the history
If an 'ibrav' value other than zero is specified in the
SYSTEM inputs, the cell of the input structure is converted
into the appropriate A, B, C, cosAB, cosAC, cosBC values. As a
check, the cell is reconstructed using qe_tools. The cells are
compared element-wise, with an absolute tolerance controlled by
the 'IBRAV_CELL_TOLERANCE' setting.
  • Loading branch information
Dominik Gresch committed Oct 8, 2020
1 parent 7427398 commit bda0881
Show file tree
Hide file tree
Showing 11 changed files with 206 additions and 15 deletions.
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 Down Expand Up @@ -285,10 +288,16 @@ 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 either zero or
# unspecified.
if input_params.get('SYSTEM', {}).get('ibrav', 0) == 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', 0)
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.

* **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
4 changes: 3 additions & 1 deletion setup.json
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,9 @@
"aiida_core[atomic_tools]~=1.3",
"packaging",
"qe-tools~=2.0rc1",
"xmlschema~=1.2"
"xmlschema~=1.2",
"numpy",
"scipy"
],
"license": "MIT License",
"name": "aiida_quantumespresso",
Expand Down
89 changes: 89 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,86 @@ 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):
"""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)
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)

0 comments on commit bda0881

Please sign in to comment.