Commit 59ecd6cd authored by temok-mx's avatar temok-mx
Browse files

Merge branch 'master' of https://gitlab.mpcdf.mpg.de/nomad-lab/parser-phonopy

Updated the phonopy parser to the right commit-tmk
parents 2e120c5f 4ee1175a
# FHIaims.py - IO routines for phonopy-FHI-aims
# methods compatible with the corresponding ones from ase.io.aims
# only minimal subset of functionality required within phonopy context is implemented
#
# Copyright (C) 2009-2011 Joerg Meyer (jm)
# All rights reserved.
#
# This file is part of phonopy.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
# are met:
#
# * Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
#
# * Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in
# the documentation and/or other materials provided with the
# distribution.
#
# * Neither the name of the phonopy project nor the names of its
# contributors may be used to endorse or promote products derived
# from this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
# COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
from phonopy.structure.atoms import PhonopyAtoms
def read_aims(filename):
"""Method to read FHI-aims geometry files in phonopy context."""
lines = open(filename, 'r').readlines()
cell = []
is_frac = []
positions = []
symbols = []
magmoms = []
for line in lines:
fields = line.split()
if not len(fields):
continue
if fields[0] == "lattice_vector":
vec = [float(x) for x in fields[1:4]]
cell.append(vec)
elif fields[0][0:4] == "atom":
if fields[0] == "atom":
frac = False
elif fields[0] == "atom_frac":
frac = True
pos = [float(x) for x in fields[1:4]]
sym = fields[4]
is_frac.append(frac)
positions.append(pos)
symbols.append(sym)
magmoms.append(None)
# implicitly assuming that initial_moments line adhere to FHI-aims geometry.in specification,
# i.e. two subsequent initial_moments lines do not occur
# if they do, the value specified in the last line is taken here - without any warning
elif fields[0] == "initial_moment":
magmoms[-1] = float(fields[1])
for (n,frac) in enumerate(is_frac):
if frac:
pos = [ sum( [ positions[n][l] * cell[l][i] for l in range(3) ] ) for i in range(3) ]
positions[n] = pos
if None in magmoms:
atoms = PhonopyAtoms(cell=cell, symbols=symbols, positions=positions)
else:
atoms = PhonopyAtoms(cell=cell, symbols=symbols, positions=positions, magmoms=magmoms)
return atoms
def write_aims(filename, atoms):
"""Method to write FHI-aims geometry files in phonopy context."""
lines = ""
lines += "# geometry.in for FHI-aims \n"
lines += "# | generated by phonopy.FHIaims.write_aims() \n"
lattice_vector_line = "lattice_vector " + "%16.16f "*3 + "\n"
for vec in atoms.get_cell():
lines += lattice_vector_line % tuple(vec)
N = atoms.get_number_of_atoms()
atom_line = "atom " + "%16.16f "*3 + "%s \n"
positions = atoms.get_positions()
symbols = atoms.get_chemical_symbols()
initial_moment_line = "initial_moment %16.6f\n"
magmoms = atoms.get_magnetic_moments()
for n in range(N):
lines += atom_line % (tuple(positions[n]) + (symbols[n],))
if magmoms is not None:
lines += initial_moment_line % magmoms[n]
f = open(filename, 'w')
f.write(lines)
f.close()
class Atoms_with_forces(PhonopyAtoms):
""" Hack to phonopy.atoms to maintain ASE compatibility also for forces."""
def get_forces(self):
return self.forces
def read_aims_output(filename):
""" Read FHI-aims output and
return geometry, energy and forces from last self-consistency iteration"""
lines = open(filename, 'r').readlines()
l = 0
N = 0
while l < len(lines):
line = lines[l]
if "Number of atoms" in line:
N = int(line.split()[5])
elif "| Unit cell:" in line:
cell = []
for i in range(3):
l += 1
vec = [float(x) for x in lines[l].split()[1:4]]
cell.append(vec)
elif ("Atomic structure:" in line) or ("Updated atomic structure:" in line):
if "Atomic structure:" in line:
i_sym = 3
i_pos_min = 4 ; i_pos_max = 7
elif "Updated atomic structure:" in line:
i_sym = 4
i_pos_min = 1 ; i_pos_max = 4
l += 1
symbols = []
positions = []
for n in range(N):
l += 1
fields = lines[l].split()
sym = fields[i_sym]
pos = [float(x) for x in fields[i_pos_min:i_pos_max]]
symbols.append(sym)
positions.append(pos)
elif "Total atomic forces" in line:
forces = []
for i in range(N):
l += 1
force = [float(x) for x in lines[l].split()[2:5]]
forces.append(force)
l += 1
atoms = Atoms_with_forces(cell=cell, symbols=symbols, positions=positions)
atoms.forces = forces
return atoms
# Copyright 2016-2018 Fawzi Mohamed, Danio Brambila
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#### phonopy parser written by Hagen-Henrik Kowalski and based on the original work of Joerg Mayer on phonopy-FHI-aims
import numpy as np
from phonopyparser.PhononModulesNomad import *
from fnmatch import fnmatch
import sys
import math
import os
import argparse
import logging
from phonopy.interface.FHIaims import read_aims, write_aims, read_aims_output
from phonopyparser.con import Control
# Note this Phonopy is the NOMAD-lab version. Not the open source package on PYPI.
from phonopy import Phonopy, __version__
from phonopy.structure.symmetry import Symmetry
from phonopy.file_IO import write_FORCE_CONSTANTS
from phonopy.harmonic.forces import Forces
from phonopy.harmonic.force_constants import get_force_constants
from phonopy.units import *
from nomadcore.unit_conversion.unit_conversion import convert_unit_function
from nomadcore.parser_backend import *
from nomadcore.local_meta_info import loadJsonFile, InfoKindEl
from phonopy.structure.atoms import PhonopyAtoms as Atoms
import nomad.config
phonopy_version = __version__
parser_info = {"name": "parser_phonopy", "version": "1.0"}
class PhonopyParserWrapper():
""" A proper class envolop for running this parser using Noamd-FAIRD infra. """
def __init__(self, backend, **kwargs):
self.backend_factory = backend
def parse(self, mainfile):
logging.info('phonopy parser started')
logging.getLogger('nomadcore').setLevel(logging.WARNING)
backend = self.backend_factory("phonopy.nomadmetainfo.json")
# Call the old parser without a class.
mainDir = os.path.dirname(os.path.dirname(os.path.abspath(mainfile)))
cwd = os.getcwd()
os.chdir(mainDir)
try:
return parse_without_class(mainfile, backend)
finally:
os.chdir(cwd)
def parse_without_class(name, backend):
cell_obj = read_aims("geometry.in")
cell = cell_obj.get_cell()
positions = cell_obj.get_positions()
symbols = np.array(cell_obj.get_chemical_symbols())
control = Control()
if (len(control.phonon["supercell"]) == 3):
supercell_matrix = np.diag(control.phonon["supercell"])
elif (len(control.phonon["supercell"]) == 9):
supercell_matrix = np.array(control.phonon["supercell"]).reshape(3,3)
displacement = control.phonon["displacement"]
sym = control.phonon["symmetry_thresh"]
####
#### constructing FORCE_CONSTANTS
set_of_forces, phonopy_obj, Relative_Path = Collect_Forces_aims(cell_obj, supercell_matrix, displacement, sym)
Prep_Path = name.split("phonopy-FHI-aims-displacement-")
Whole_Path = []
# Try to resolve references as paths relative to the upload root.
try:
for Path in Relative_Path:
abs_path = "%s%s" % (Prep_Path[0], Path)
rel_path = abs_path.split(nomad.config.fs.staging + "/")[1].split("/", 3)[3]
Whole_Path.append(rel_path)
except Exception:
logging.warn("Could not resolve path to a referenced calculation within the upload.")
phonopy_obj.set_forces(set_of_forces)
phonopy_obj.produce_force_constants()
FC2 = phonopy_obj.get_force_constants()
####
#### obtaining information about supercell
super_c=phonopy_obj.supercell
s_cell = super_c.get_cell()
super_pos = super_c.get_positions()
super_sym = np.array(super_c.get_chemical_symbols())
####
#### Converting properties to Si unitis
converter_FC2 = convert_unit_function('eV*angstrom**-2', 'joules*meter**-2')
convert_angstrom = convert_unit_function('angstrom', 'meter')
FC2 = converter_FC2(FC2)
cell = convert_angstrom(cell)
s_cell = convert_angstrom(s_cell)
super_pos = convert_angstrom(super_pos)
positions = convert_angstrom(positions)
displacement = convert_angstrom(displacement)
#### parsing
pbc = np.array((1, 1, 1), bool)
Parse = backend
Parse.startedParsingSession(name, parser_info)
sRun = Parse.openSection("section_run")
Parse.addValue("program_name", "Phonopy")
Parse.addValue("program_version", phonopy_version)
Basesystem = Parse.openSection("section_system")
Parse.addArrayValues("configuration_periodic_dimensions", pbc)
Parse.addArrayValues("atom_labels", symbols)
Parse.addArrayValues("atom_positions", positions)
Parse.addArrayValues("simulation_cell", cell)
Parse.closeSection("section_system", Basesystem)
Supercellsystem = Parse.openSection("section_system")
sysrefs = Parse.openSection("section_system_to_system_refs")
Parse.addValue("system_to_system_kind", "subsystem")
Parse.addValue("system_to_system_ref", Basesystem)
Parse.closeSection("section_system_to_system_refs", sysrefs)
Parse.addArrayValues("configuration_periodic_dimensions", pbc)
Parse.addArrayValues("atom_labels", super_sym)
Parse.addArrayValues("atom_positions", super_pos)
Parse.addArrayValues("simulation_cell", s_cell)
Parse.addArrayValues("SC_matrix", supercell_matrix)
Parse.addValue("x_phonopy_original_system_ref", Basesystem)
Parse.closeSection("section_system", Supercellsystem)
method = Parse.openSection("section_method")
Parse.addValue("x_phonopy_symprec", sym)
Parse.addValue("x_phonopy_displacement", displacement)
Parse.closeSection("section_method", method)
results = Parse.openSection("section_single_configuration_calculation")
Parse.addValue("single_configuration_calculation_to_system_ref", Supercellsystem)
Parse.addValue("single_configuration_to_calculation_method_ref", method)
Parse.addArrayValues("hessian_matrix", FC2)
GP = Get_Properties(FC2, cell, positions, symbols, supercell_matrix, sym, displacement)
GP.prem_emit(Parse, results)
GP.prep_ref(Whole_Path, Parse)
Parse.closeSection("section_single_configuration_calculation", results)
Parse.closeSection("section_run", sRun)
Parse.finishedParsingSession("ParseSuccess", None)
return backend
# Copyright 2016-2018 Fawzi Mohamed, Danio Brambila
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#### phonopy parser based on the original work of Joerg Mayer on phonopy-FHI-aims
import numpy as np
from phonopyparser.PhononModulesNomad import *
from fnmatch import fnmatch
import sys
import math
import os, logging
from phonopy.interface.FHIaims import read_aims, write_aims, read_aims_output
from phonopyparser.con import Control
from phonopy import Phonopy
from phonopy.structure.symmetry import Symmetry
from phonopy.file_IO import parse_FORCE_CONSTANTS
from phonopy.harmonic.forces import Forces
from phonopy.harmonic.force_constants import get_force_constants
from phonopy.units import *
from phonopy.structure.atoms import Atoms
from nomadcore.unit_conversion.unit_conversion import convert_unit_function
from nomadcore.parser_backend import *
from nomadcore.local_meta_info import loadJsonFile, InfoKindEl
parser_info = {"name": "parser_phonopy", "version": "0.1"}
path = "../../../../nomad-meta-info/meta_info/nomad_meta_info/phonopy.nomadmetainfo.json"
metaInfoPath = os.path.normpath(
os.path.join(os.path.dirname(os.path.abspath(__file__)), path))
metaInfoEnv, warns = loadJsonFile(filePath=metaInfoPath,
dependencyLoader=None,
extraArgsHandling=InfoKindEl.ADD_EXTRA_ARGS,
uri=None)
if __name__ == "__main__":
import sys
name = sys.argv[1]
#### Reading basic properties from JSON
with open(name) as FORCES:
data = json.load(FORCES)
hessian= np.array(data["sections"]["section_single_configuration_calculation-0"]["hessian_matrix"])
SC_matrix = np.array(data["sections"]["section_system-1"]["SC_matrix"])
cell = np.array(data["sections"]["section_system-0"]["simulation_cell"])
symbols = np.array(data["sections"]["section_system-0"]["atom_labels"])
positions = np.array(data["sections"]["section_system-0"]["atom_positions"])
displacement = np.array(data["sections"]["section_method-0"]["x_phonopy_displacement"])
symmetry_thresh = np.array(data["sections"]["section_method-0"]["x_phonopy_symprec"])
####
#### omitting
get_properties = get_properties(hessian, cell, positions, symbols, SC_matrix, symmetry_thresh, displacement, file_name, metaInfoEnv, parser_info)
get_properties.omit_properties()
This diff is collapsed.
......@@ -12,4 +12,21 @@
# See the License for the specific language governing permissions and
# limitations under the License.
from phonopyparser.Get_Force_Constants import PhonopyParserWrapper
from phonopyparser.phonopy_calculators import PhonopyCalculatorInterface
from nomad.parsing.parser import FairdiParser
from .metainfo import m_env
class PhonopyParser(FairdiParser):
def __init__(self):
super().__init__(
name='parsers/phonopy', code_name='Phonopy', code_homepage='https://phonopy.github.io/phonopy/',
mainfile_name_re=(r'(.*/phonopy-FHI-aims-displacement-0*1/control.in$)|(.*/phonon.yaml)')
)
def parse(self, filepath, archive, logger=None):
self._metainfo_env = m_env
interface = PhonopyCalculatorInterface(filepath, archive, logger)
interface.parse()
# Copyright 2016-2018 Markus Scheidgen
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import sys
import json
from phonopyparser import PhonopyParser
if __name__ == "__main__":
archive = PhonopyParser.main(sys.argv[1])
json.dump(archive.m_to_dict(), sys.stdout, indent=2)
......@@ -12,19 +12,16 @@
# See the License for the specific language governing permissions and
# limitations under the License.
#### phonopy parser based on the original work of Joerg Mayer on phonopy-FHI-aims
# phonopy parser based on the original work of Joerg Mayer on phonopy-FHI-aims
import numpy as np
from phonopy.units import VaspToTHz as AimsToTHz, VaspToCm as AimsToCm, VaspToEv as AimsToEv, THzToCm, THzToEv
from phonopy.interface.FHIaims import read_aims, write_aims, read_aims_output
# from phonopy.structure.cells import get_equivalent_smallest_vectors
from phonopy.harmonic.dynamical_matrix import DynamicalMatrix, DynamicalMatrixNAC, get_equivalent_smallest_vectors
from phonopy.structure.cells import get_reduced_bases
import numpy as np
import math
import os
import logging
from fnmatch import fnmatch
from phonopy import Phonopy
from phonopy.structure.cells import Primitive
from phonopyparser.FHIaims import read_aims_output
class Control:
def __init__(self, file=None):
......@@ -53,7 +50,7 @@ class Control:
if (nfields >= 11):
s = map(int, fields[2:11])
s = list(s)
Smat = np.array(s).reshape(3,3)
Smat = np.array(s).reshape(3, 3)
elif (nfields >= 5):
s = map(int, fields[2:5])
s = list(s)
......@@ -66,7 +63,7 @@ class Control:
# this consistency check is not strictly necessary, since int function above used to transform the input
# already throws an exception when decimal numbers are encountered
# keep for consistency (and future changes to that spot?) nevertheless...
elif (abs(det_Smat-round(det_Smat)) > 1.0e-6):
elif (abs(det_Smat - round(det_Smat)) > 1.0e-6):
raise Exception("determinant of supercell differs from integer by more than numerical tolerance of 1.0e-6")
self.phonon["supercell"] = s
if (fields[1] == "displacement"):
......@@ -82,17 +79,89 @@ class Control:
delta = (0.1, 0.1, 0.1)
if (delta[0] == 0.0) and (delta[1] == 0.0) and (delta[2] == 0.0):
raise Exception("evaluation of frequencies with non-analytic corrections must be shifted by delta away from Gamma")
parameters = { "file" : fields[2],
"method" : fields[3].lower(),
"delta" : delta }
parameters = {
"file": fields[2], "method": fields[3].lower(), "delta": delta}
self.phonon["nac"].update(parameters)
except Exception:
#print (line,)
#print ("|-> line triggered exception: ") + str(Exception)
raise
except Exception as e:
raise(e)
# supercell is mandatory for all what follows
if not self.phonon["supercell"]:
raise Exception("no supercell specified in %s" % self.file)
f.close()
def clean_position(scaled_positions):
scaled_positions = list(scaled_positions)
for sp in range(len(scaled_positions)):
for i in range(len(scaled_positions[sp])):
if np.float(np.round(scaled_positions[sp][i], 7)) >= 1:
scaled_positions[sp][i] -= 1.0
elif scaled_positions[sp][i] <= -1e-5:
scaled_positions[sp][i] += 1.0
scaled_positions = np.array(scaled_positions)
return scaled_positions
def read_forces_aims(cell_obj, supercell_matrix, displacement, sym, tol=1e-6, logger=None):
if logger is None:
logger = logging
phonopy_obj = Phonopy(cell_obj, supercell_matrix, symprec=sym)
phonopy_obj.generate_displacements(distance=displacement)
supercells = phonopy_obj.get_supercells_with_displacements()
directories = []
digits = int(math.ceil(math.log(len(supercells) + 1, 10))) + 1
for i in range(len(supercells)):
directories.append(("phonopy-FHI-aims-displacement-%0" + str(digits) + "d") % (i + 1))
set_of_forces = []
Relative_Path = []
for directory, supercell in zip(directories, supercells):
aims_out = os.path.join(directory, directory + ".out")
if not os.path.isfile(aims_out):
logger.warn("!!! file not found: %s" % aims_out)
cwd = os.getcwd()
con_list = os.listdir(cwd)
check_var = False
for name in con_list:
if fnmatch(name, '*.out'):
aims_out = '%s/%s' % (directory, name)
logger.warn(
"Your file seems to have a wrong name proceeding with %s" % aims_out
)
check_var = True
break
if not check_var:
raise Exception("No phonon calculations found")
os.chdir("../")
Relative_Path.append(aims_out)
supercell_calculated = read_aims_output(aims_out)
if (
(supercell_calculated.get_number_of_atoms() == supercell.get_number_of_atoms()) and
(supercell_calculated.get_atomic_numbers() == supercell.get_atomic_numbers()).all() and
(abs(supercell_calculated.get_positions() - supercell.get_positions()) < tol).all() and
(abs(supercell_calculated.get_cell() - supercell.get_cell()) < tol).all()):
# read_aims_output reads in forces from FHI-aims output as list structure,
# but further processing below requires numpy array
forces = np.array(supercell_calculated.get_forces())
drift_force = forces.sum(axis=0)
for force in forces:
force -= drift_force / forces.shape[0]
set_of_forces.append(forces)
elif (
(supercell_calculated.get_number_of_atoms() == supercell.get_number_of_atoms()) and
(supercell_calculated.get_atomic_numbers() == supercell.get_atomic_numbers()).all() and
(abs(clean_position(supercell_calculated.get_scaled_positions()) - clean_position(supercell.get_scaled_positions())) < tol).all() and
(abs(supercell_calculated.get_cell() - supercell.get_cell()) < tol).all()):
logger.warn("!!! there seems to be a rounding error")
forces = np.array(supercell_calculated.get_forces())
drift_force = forces.sum(axis=0)
for force in forces:
force -= drift_force / forces.shape[0]
set_of_forces.append(forces)
else:
raise Exception("calculated varies from expected supercell in FHI-aims output %s" % aims_out)
return set_of_forces, phonopy_obj, Relative_Path
import os
import numpy as np
import phonopy
from phonopy.units import THzToEv
from phonopyparser.fhiaims_io import Control, read_forces_aims
from phonopyparser.phonopy_properties import PhononProperties
from phonopyparser.FHIaims import read_aims
from nomadcore.unit_conversion.unit_conversion import convert_unit_function
from nomad.datamodel.metainfo.public import section_run, section_system,\
section_system_to_system_refs, section_method, section_single_configuration_calculation,\
section_k_band, section_k_band_segment, section_dos, section_frame_sequence,\
section_thermodynamical_properties, section_sampling_method, Workflow, Phonon, \
section_calculation_to_calculation_refs
class PhonopyCalculatorInterface: