Skip to content

Commit

Permalink
A large refactoring.
Browse files Browse the repository at this point in the history
Many things were written in wrong ways. What were done:

- Compute force constants only without phonon properties
- Replace POSCAR and phonopy.conf by phonopy_cells.yaml and command options. This aims to support non VASP calculators easily in the future
- Support type-II dataset. This is not tested yet.
  • Loading branch information
atztogo committed Jul 14, 2020
1 parent 0873de6 commit 12fa782
Show file tree
Hide file tree
Showing 10 changed files with 356 additions and 371 deletions.
115 changes: 52 additions & 63 deletions aiida_phonopy/calcs/base.py
Original file line number Diff line number Diff line change
@@ -1,43 +1,54 @@
from aiida.engine import CalcJob
from aiida.common import CalcInfo, CodeInfo
from aiida.plugins import DataFactory
from aiida_phonopy.common.raw_parsers import (get_BORN_txt,
get_phonopy_conf_file_txt,
get_poscar_txt)
from aiida.orm import Bool
from aiida_phonopy.common.file_generators import get_BORN_txt

Dict = DataFactory('dict')
StructureData = DataFactory('structure')
ArrayData = DataFactory('array')


class BasePhonopyCalculation(object):
class BasePhonopyCalculation(CalcJob):
"""
A basic plugin for calculating force constants using Phonopy.
Requirement: the node should be able to import phonopy if NAC is used
"""

_INPUT_CELL = 'POSCAR'
_INPUT_FORCE_SETS = 'FORCE_SETS'
_INPUT_NAC = 'BORN'

@classmethod
def _baseclass_use_methods(cls, spec):
spec.input('settings', valid_type=Dict, required=True,
help=('Use a node that specifies the phonopy '
'parameters for the namelists'))
spec.input('structure', valid_type=StructureData, required=True,
help=('Use a node for the structure'))
def define(cls, spec):
"""
dataset : Dict, ArrayData, optional
In Dict, this is similar to phonopy.dataset. This can be either
type-I or type-II. In ArrayData, this can contain arrays of forces
and displacements like type-II phonopy dataset, for which the array
names are 'forces' and 'displacements'.
"""

super().define(spec)

spec.input('settings', valid_type=Dict,
help='Phonopy parameters')
spec.input('structure', valid_type=StructureData,
help='Unit cell structure')
spec.input('fc_only', valid_type=Bool,
help='Only force constants are calculated.',
default=lambda: Bool(False))
spec.input('force_sets', valid_type=ArrayData, required=False,
help=('Use a node that specifies the force_sets '
'array for the namelists'))
help='Sets of forces in supercells')
spec.input('dataset', valid_type=ArrayData, required=False,
help='Sets of displacements and forces in supercells')
spec.input('nac_params', valid_type=ArrayData, required=False,
help=('Use a node for the Non-analitical '
'corrections parameters arrays'))
spec.input('primitive', valid_type=StructureData,
required=False, help=('Use a node for the structure'))
spec.input('metadata.options.withmpi', valid_type=bool, default=False)

def _create_additional_files(self, folder):
pass
help='NAC parameters')
spec.input('primitive', valid_type=StructureData, required=False,
help='Primitive cell structure')
spec.input('dataset', valid_type=(Dict, ArrayData), required=False,
help='Displacements and forces dataset')
spec.inputs['metadata']['options']['withmpi'].default = False

def prepare_for_submission(self, folder):
"""Create the input files from the input nodes passed to this instance of the `CalcJob`.
Expand All @@ -48,44 +59,25 @@ def prepare_for_submission(self, folder):

self.logger.info("prepare_for_submission")

# These three lists are updated in self._create_additional_files(folder)
self._internal_retrieve_list = []
self._additional_cmd_params = []
self._calculation_cmd = []

settings = self.inputs.settings
structure = self.inputs.structure
code = self.inputs.code

##############################
# END OF INITIAL INPUT CHECK #
##############################

# ================= prepare the python input files =================

self._create_additional_files(folder)

cell_txt = get_poscar_txt(structure)
input_txt = get_phonopy_conf_file_txt(settings)

input_filename = folder.get_abs_path(
self.inputs.metadata.options.input_filename)
with open(input_filename, 'w') as infile:
infile.write(input_txt)

cell_filename = folder.get_abs_path(self._INPUT_CELL)
with open(cell_filename, 'w') as infile:
infile.write(cell_txt)
# ================= prepare the python input files =================

if ('nac_params' in self.inputs and
'primitive' in self.inputs):
# BORN
if (not self.inputs.fc_only and
'nac_params' in self.inputs and
'primitive' in self.inputs and
'symmetry_tolerance' in self.inputs.settings.attributes):
born_txt = get_BORN_txt(
self.inputs.nac_params,
self.inputs.primitive,
settings['symmetry_tolerance'])

nac_filename = folder.get_abs_path(self._INPUT_NAC)
with open(nac_filename, 'w') as infile:
infile.write(born_txt)
self.inputs.settings['symmetry_tolerance'])
with folder.open(self._INPUT_NAC, 'w', encoding='utf8') as handle:
handle.write(born_txt)
for params in self._additional_cmd_params:
params.append('--nac')

Expand All @@ -95,26 +87,23 @@ def prepare_for_submission(self, folder):
remote_copy_list = []

calcinfo = CalcInfo()

calcinfo.uuid = self.uuid

# Empty command line by default
calcinfo.local_copy_list = local_copy_list
calcinfo.remote_copy_list = remote_copy_list

# Retrieve files
calcinfo.retrieve_list = self._internal_retrieve_list

calcinfo.codes_info = []
for default_params, additional_params in zip(
self._calculation_cmd, self._additional_cmd_params):
for i, (default_params, additional_params) in enumerate(zip(
self._calculation_cmd, self._additional_cmd_params)):
codeinfo = CodeInfo()
codeinfo.cmdline_params = (
[self.inputs.metadata.options.input_filename, ]
+ default_params + additional_params)
codeinfo.code_uuid = code.uuid
codeinfo.stdout_name = self.inputs.metadata.options.output_filename
codeinfo.cmdline_params = default_params + additional_params
codeinfo.code_uuid = self.inputs.code.uuid
codeinfo.stdout_name = "%s%d" % (
self.options.output_filename, i + 1)
codeinfo.withmpi = False
calcinfo.codes_info.append(codeinfo)

return calcinfo

def _create_additional_files(self, folder):
raise NotImplementedError()
7 changes: 3 additions & 4 deletions aiida_phonopy/calcs/phono3py.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,9 @@
from aiida.common.utils import classproperty
from aiida.plugins import DataFactory
from aiida_phonopy.calcs.base import BasePhonopyCalculation
from aiida_phonopy.common.raw_parsers import (get_disp_fc3_txt, get_forces_txt,
write_fc2_to_hdf5_file,
write_fc3_to_hdf5_file,
write_kappa_to_hdf5_file)
from aiida_phonopy.common.file_generators import (
get_disp_fc3_txt, get_forces_txt, write_fc2_to_hdf5_file,
write_fc3_to_hdf5_file, write_kappa_to_hdf5_file)

BandStructureData = DataFactory('phonopy.band_structure')
KpointsData = DataFactory('array.kpoints')
Expand Down
150 changes: 82 additions & 68 deletions aiida_phonopy/calcs/phonopy.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
from aiida.plugins import DataFactory
from aiida_phonopy.calcs.base import BasePhonopyCalculation
from aiida.engine import CalcJob, ExitCode
from aiida.orm import Str
from aiida.common import InputValidationError
from aiida_phonopy.common.raw_parsers import get_FORCE_SETS_txt
import six
from aiida_phonopy.common.file_generators import (
get_FORCE_SETS_txt, get_phonopy_options, get_phonopy_yaml_txt)


BandsData = DataFactory('array.bands')
Expand All @@ -13,7 +12,7 @@
Dict = DataFactory('dict')


class PhonopyCalculation(BasePhonopyCalculation, CalcJob):
class PhonopyCalculation(BasePhonopyCalculation):
"""
A basic plugin for calculating phonon properties using Phonopy.
"""
Expand All @@ -23,87 +22,102 @@ class PhonopyCalculation(BasePhonopyCalculation, CalcJob):
_OUTPUT_THERMAL_PROPERTIES = 'thermal_properties.yaml'
_OUTPUT_BAND_STRUCTURE = 'band.yaml'
_INOUT_FORCE_CONSTANTS = 'FORCE_CONSTANTS'
_INPUT_CELL = 'phonopy_cells.yaml'
_INPUT_FORCE_SETS = 'FORCE_SETS'

@classmethod
def define(cls, spec):
super(PhonopyCalculation, cls).define(spec)
spec.input('projected_dos_filename',
valid_type=Str, default=Str(cls._OUTPUT_PROJECTED_DOS))
spec.input('total_dos_filename',
valid_type=Str, default=Str(cls._OUTPUT_TOTAL_DOS))
spec.input('thermal_properties_filename',
valid_type=Str, default=Str(cls._OUTPUT_THERMAL_PROPERTIES))
spec.input('band_structure_filename',
valid_type=Str, default=Str(cls._OUTPUT_BAND_STRUCTURE))
spec.input('force_constants_filename',
valid_type=Str, default=Str(cls._INOUT_FORCE_CONSTANTS))
spec.input('metadata.options.parser_name',
valid_type=six.string_types, default='phonopy')
spec.input('metadata.options.input_filename',
valid_type=six.string_types, default='phonopy.conf')
spec.input('metadata.options.output_filename',
valid_type=six.string_types, default='phonopy.stdout')

super(PhonopyCalculation, cls)._baseclass_use_methods(spec)

spec.output('force_constants', valid_type=ArrayData,
required=False,
super().define(spec)

spec.input('projected_dos_filename', valid_type=Str,
default=lambda: Str(cls._OUTPUT_PROJECTED_DOS))
spec.input('total_dos_filename', valid_type=Str,
default=lambda: Str(cls._OUTPUT_TOTAL_DOS))
spec.input('thermal_properties_filename', valid_type=Str,
default=lambda: Str(cls._OUTPUT_THERMAL_PROPERTIES))
spec.input('band_structure_filename', valid_type=Str,
default=lambda: Str(cls._OUTPUT_BAND_STRUCTURE))
spec.input('force_constants_filename', valid_type=Str,
default=lambda: Str(cls._INOUT_FORCE_CONSTANTS))
# parser_name has to be set to invoke parsing.
spec.inputs['metadata']['options']['parser_name'].default = 'phonopy'
spec.inputs['metadata']['options']['output_filename'].default = 'phonopy.out'

spec.output('force_constants', valid_type=ArrayData, required=False,
help='Calculated force constants')
spec.output('dos', valid_type=XyData,
required=False,
spec.output('dos', valid_type=XyData, required=False,
help='Calculated total DOS')
spec.output('pdos', valid_type=XyData,
required=False,
spec.output('pdos', valid_type=XyData, required=False,
help='Calculated projected DOS')
spec.output('thermal_properties', valid_type=XyData,
required=False,
spec.output('thermal_properties', valid_type=XyData, required=False,
help='Calculated thermal properties')
spec.output('band_structure', valid_type=BandsData,
required=False,
spec.output('band_structure', valid_type=BandsData, required=False,
help='Calculated phonon band structure')
spec.exit_code(100, 'ERROR_NO_RETRIEVED_FOLDER',
message=('The retrieved folder data node could not '
'be accessed.'))
spec.exit_code(200, 'ERROR_MISSING_FILE',
message='An important file is missing.')
spec.exit_code(300, 'ERROR_PARSING_FILE_FAILED',
message='Parsing a file has failed.')

def prepare_for_submission(self, folder):
return super().prepare_for_submission(folder)

def _create_additional_files(self, folder):
self.logger.info("create_additional_files")

self._internal_retrieve_list = [self._OUTPUT_TOTAL_DOS,
self._OUTPUT_PROJECTED_DOS,
self._OUTPUT_THERMAL_PROPERTIES,
self._OUTPUT_BAND_STRUCTURE]
self._calculation_cmd = [
['--pdos=auto'],
['-t', '--dos'],
['--band=auto', '--band-points=101', '--band-const-interval']
]
self._create_phonopy_yaml(folder)
self._create_FORCE_SETS(folder)
mesh_opts, fc_opts = get_phonopy_options(
self.inputs.settings.get_dict())

self._internal_retrieve_list = [self._INOUT_FORCE_CONSTANTS, ]
self._additional_cmd_params = [['--writefc', ] + fc_opts, ]
self._calculation_cmd = [['-c', self._INPUT_CELL], ]

# First run with --writefc, and with --readfc for remaining runs
if not self.inputs.fc_only:

self._calculation_cmd = [
['-c', self._INPUT_CELL, '--pdos=auto'] + mesh_opts,
['-c', self._INPUT_CELL, '-t', '--dos'] + mesh_opts,
['-c', self._INPUT_CELL, '--band=auto', '--band-points=101',
'--band-const-interval']
]
N = len(self._calculation_cmd) - 1
self._additional_cmd_params += [['--readfc', ] for i in range(N)]
self._internal_retrieve_list += [self._OUTPUT_TOTAL_DOS,
self._OUTPUT_PROJECTED_DOS,
self._OUTPUT_THERMAL_PROPERTIES,
self._OUTPUT_BAND_STRUCTURE]

def _create_phonopy_yaml(self, folder):
phpy_yaml_txt = get_phonopy_yaml_txt(
self.inputs.structure,
self.inputs.settings.get_dict())
with folder.open(self._INPUT_CELL, 'w', encoding='utf8') as handle:
handle.write(phpy_yaml_txt)

def _create_FORCE_SETS(self, folder):
if 'force_sets' in self.inputs:
force_sets = self.inputs.force_sets
else:
force_sets = None
if 'displacement_dataset' in self.inputs.settings.attributes:
displacement_dataset = self.inputs.settings[
'displacement_dataset']
else:
displacement_dataset = None

if force_sets is not None and displacement_dataset is not None:
force_sets_txt = get_FORCE_SETS_txt(
force_sets, displacement_dataset)
force_sets_filename = folder.get_abs_path(self._INPUT_FORCE_SETS)
with open(force_sets_filename, 'w') as infile:
infile.write(force_sets_txt)
# First run with --writefc, and with --readfc for remaining runs
self._additional_cmd_params = [
['--readfc'] for i in range(len(self._calculation_cmd) - 1)]
self._additional_cmd_params.insert(0, ['--writefc'])
self._internal_retrieve_list.append(self._INOUT_FORCE_CONSTANTS)
dataset = self.inputs.settings['displacement_dataset']
elif 'dataset' in self.inputs.settings.attributes:
dataset = self.inputs.settings['dataset']
elif ('dataset' in self.inputs and
'displacements' in self.inputs.dataset.get_arraynames()):
dataset = {'displacements':
self.inputs.dataset.get_array('displacements')}
if 'forces' in self.inputs.dataset.get_arraynames():
dataset['forces'] = self.inputs.dataset.get_array('forces')
if force_sets is not None:
force_sets = None
else:
msg = ("no force_sets nor force_constants are specified for "
"this calculation")
dataset = None

# can work both for type-I and type-II
force_sets_txt = get_FORCE_SETS_txt(dataset, force_sets=force_sets)
if force_sets_txt is None:
msg = ("Displacements or forces were not found.")
raise InputValidationError(msg)

with folder.open(
self._INPUT_FORCE_SETS, 'w', encoding='utf8') as handle:
handle.write(force_sets_txt)
File renamed without changes.

0 comments on commit 12fa782

Please sign in to comment.