diff --git a/elasticparser/__init__.py b/elasticparser/__init__.py index 018dcdb3e013c406b6a9c4885538a486e5f77377..c992b104149bb950c6749edaa49db86e3b53d02a 100644 --- a/elasticparser/__init__.py +++ b/elasticparser/__init__.py @@ -1,32 +1 @@ -# Copyright 2015-2018 Lauri Himanen, Fawzi Mohamed, Ankit Kariryaa -# -# 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. - -from .metainfo import m_env -from nomad.parsing.parser import FairdiParser -from elasticparser.elastic_parser import ElasticParserInterface - - -class ElasticParser(FairdiParser): - def __init__(self): - super().__init__( - name='parsers/elastic', code_name='elastic', code_homepage='http://exciting-code.org/elastic', - mainfile_contents_re=r'\s*Order of elastic constants\s*=\s*[0-9]+\s*', - mainfile_name_re=(r'.*/INFO_ElaStic')) - - def parse(self, filepath, archive, logger=None): - self._metainfo_env = m_env - - parser = ElasticParserInterface(filepath, archive, logger) - - parser.parse() +from elasticparser.elastic_parser import ElasticParser diff --git a/elasticparser/elastic_parser.py b/elasticparser/elastic_parser.py index ac236ad77a16cc08470dd65c61a9d9c79a333b9c..ceb78d4f64877fa6c72379b17a7da870cd3f437a 100644 --- a/elasticparser/elastic_parser.py +++ b/elasticparser/elastic_parser.py @@ -2,22 +2,26 @@ import os import numpy as np import logging -from nomad.datamodel.metainfo.public import section_run, section_system,\ - section_single_configuration_calculation, section_method, section_calculation_to_calculation_refs,\ - Workflow, Elastic +from nomad.datamodel.metainfo.common_dft import Run, System, SingleConfigurationCalculation,\ + Method, CalculationToCalculationRefs, Workflow, Elastic +from nomad.parsing.parser import FairdiParser from elasticparser.metainfo.elastic import x_elastic_section_strain_diagrams,\ x_elastic_section_fitting_parameters from elasticparser.elastic_properties import ElasticProperties +from .metainfo import m_env -class ElasticParserInterface: - def __init__(self, filepath, archive, logger=None): - self.filepath = os.path.abspath(filepath) - self.archive = archive - self.logger = logger if logger is not None else logging - self.properties = ElasticProperties(self.filepath) +class ElasticParser(FairdiParser): + def __init__(self): + super().__init__( + name='parsers/elastic', code_name='elastic', code_homepage='http://exciting-code.org/elastic', + mainfile_contents_re=r'\s*Order of elastic constants\s*=\s*[0-9]+\s*', + mainfile_name_re=(r'.*/INFO_ElaStic')) + + self._metainfo_env = m_env + self.properties = ElasticProperties() def parse_strain(self): sec_scc = self.archive.section_run[-1].section_single_configuration_calculation[-1] @@ -95,7 +99,6 @@ class ElasticParserInterface: if order == 2: matrices, moduli, eigenvalues = self.properties.get_elastic_constants_order2() - sec_scc.x_elastic_2nd_order_constants_notation_matrix = matrices['voigt'] sec_scc.x_elastic_2nd_order_constants_matrix = matrices['elastic_constant'] sec_scc.x_elastic_2nd_order_constants_compliance_matrix = matrices['compliance'] @@ -123,14 +126,23 @@ class ElasticParserInterface: sec_scc.x_elastic_3rd_order_constants_matrix = elastic_constant - def parse(self): + def _init_parsers(self): + self.properties.mainfile = self.filepath + self.properties.logger = self.logger + + def parse(self, filepath, archive, logger): + self.filepath = os.path.abspath(filepath) + self.archive = archive + self.logger = logger if logger is not None else logging + + self._init_parsers() - sec_run = self.archive.m_create(section_run) + sec_run = self.archive.m_create(Run) sec_run.program_name = 'elastic' sec_run.program_version = '1.0' - sec_system = sec_run.m_create(section_system) + sec_system = sec_run.m_create(System) symbols, positions, cell = self.properties.get_structure_info() volume = self.properties.info['equilibrium_volume'] @@ -142,7 +154,7 @@ class ElasticParserInterface: sec_system.x_elastic_space_group_number = self.properties.info['space_group_number'] sec_system.x_elastic_unit_cell_volume = volume - sec_method = sec_run.m_create(section_method) + sec_method = sec_run.m_create(Method) sec_method.x_elastic_elastic_constant_order = self.properties.info['order'] sec_method.x_elastic_calculation_method = self.properties.info['calculation_method'] sec_method.x_elastic_code = self.properties.info['code_name'] @@ -154,9 +166,9 @@ class ElasticParserInterface: sec_method.x_elastic_number_of_deformations = len(self.properties.deformation_dirs) references = self.properties.get_references_to_calculations() - sec_scc = sec_run.m_create(section_single_configuration_calculation) + sec_scc = sec_run.m_create(SingleConfigurationCalculation) for reference in references: - sec_calc_ref = sec_scc.m_create(section_calculation_to_calculation_refs) + sec_calc_ref = sec_scc.m_create(CalculationToCalculationRefs) sec_calc_ref.calculation_to_calculation_external_url = reference sec_calc_ref.calculation_to_calculation_kind = 'source_calculation' diff --git a/elasticparser/elastic_properties.py b/elasticparser/elastic_properties.py index 7e24c0735a6b384176798125cf63caa6d7defc57..1f1a3313ffcdfcd573583bdbbf16b86b291dcb66 100644 --- a/elasticparser/elastic_properties.py +++ b/elasticparser/elastic_properties.py @@ -3,16 +3,154 @@ import pint import numpy as np from ase import Atoms -from nomad.parsing.file_parser import Quantity, UnstructuredTextFileParser +from nomad.parsing.file_parser import Quantity, TextParser + + +class InfoParser(TextParser): + def __init__(self): + super().__init__(None) + + def init_quantities(self): + self._quantities = [ + Quantity( + 'order', r'\s*Order of elastic constants\s*=\s*([0-9]+)', repeats=False, + dtype=int), + Quantity( + 'calculation_method', r'\s*Method of calculation\s*=\s*([-a-zA-Z]+)\s*', + repeats=False), + Quantity( + 'code_name', r'\s*DFT code name\s*=\s*([-a-zA-Z]+)', repeats=False), + Quantity( + 'space_group_number', r'\s*Space-group number\s*=\s*([0-9]+)', repeats=False), + Quantity( + 'equilibrium_volume', r'\s*Volume of equilibrium unit cell\s*=\s*([0-9.]+)\s*', + unit='angstrom ** 3'), + Quantity( + 'max_strain', r'\s*Maximum Lagrangian strain\s*=\s*([0-9.]+)', repeats=False), + Quantity( + 'n_strains', r'\s*Number of distorted structures\s*=\s*([0-9]+)', repeats=False)] + + +class StructureParser(TextParser): + def __init__(self): + super().__init__(None) + + def init_quantities(self): + def get_sym_pos(val): + val = val.strip().replace('\n', '').split() + sym = [] + pos = [] + for i in range(0, len(val), 4): + sym.append(val[i + 3].strip()) + pos.append([float(val[j]) for j in range(i, i + 3)]) + sym_pos = dict(symbols=sym, positions=pos) + return sym_pos + + self._quantities = [ + Quantity( + 'cellpar', r'a\s*b\s*c\n([\d\.\s]+)\n\s*alpha\s*beta\s*gamma\n([\d\.\s]+)\n+', + repeats=False), + Quantity( + 'sym_pos', r'Atom positions:\n\n([\s\d\.A-Za-z]+)\n\n', + str_operation=get_sym_pos, repeats=False, convert=False)] + + +class DistortedParametersParser(TextParser): + def __init__(self): + super().__init__(None) + + def init_quantities(self): + self._quantities = [Quantity( + 'deformation', r'Lagrangian strain\s*=\s*\(([eta\s\d\.,]+)\)', + str_operation=lambda x: x.replace(',', '').split(), repeats=True, dtype=str)] + + +class FitParser(TextParser): + def __init__(self): + super().__init__(None) + + def init_quantities(self): + + def split_eta_val(val): + order, val = val.strip().split(' order fit.') + val = [float(v) for v in val.strip().split()] + return order, val[0::2], val[1::2] + + self._quantities = [Quantity( + 'fit', r'(\w+ order fit\.\n[\d.\s\neE\-\+]+)\n', repeats=True, convert=False, + str_operation=split_eta_val)] + + +class ElasticConstant2Parser(TextParser): + def __init__(self): + super().__init__(None) + + def init_quantities(self): + self._quantities = [ + Quantity( + 'voigt', r'Symmetry[\s\S]+\n\s*\n([C\d\s\n\(\)\-\+\/\*]+)\n', + shape=(6, 6), dtype=str, repeats=False), + Quantity( + 'elastic_constant', r'Elastic constant[\s\S]+in GPa\s*:\s*\n\n([\-\d\.\s\n]+)\n', + shape=(6, 6), dtype=float, unit='GPa', repeats=False), + Quantity( + 'compliance', r'Elastic compliance[\s\S]+in 1/GPa\s*:\s*\n\n([\-\d\.\s\n]+)\n', + shape=(6, 6), dtype=float, unit='1/GPa', repeats=False)] + + def str_to_modulus(val_in): + val_in = val_in.strip().split() + key = val_in[0] + unit = val_in[-1] if len(val_in) == 3 else None + val = float(val_in[1]) + val = pint.Quantity(val, unit) if unit is not None else val + return key, val + + self._quantities.append(Quantity( + 'modulus', r',\s*(\w+)\s*=\s*([\-\+\w\. ]+?)\n', str_operation=str_to_modulus, + repeats=True)) + + self._quantities.append(Quantity( + 'eigenvalues', + r'Eigenvalues of elastic constant \(stiffness\) matrix:\s*\n+([\-\d\.\n\s]+)\n', + unit='GPa', repeats=False)) + + +class ElasticConstant3Parser(TextParser): + def __init__(self): + super().__init__(None) + + def init_quantities(self): + def arrange_matrix(val): + val = val.strip().split('\n') + matrix = [v.strip().split() for v in val if v.strip()] + matrix = np.array(matrix).reshape((12, 18)) + arranged = [] + for i in range(2): + for j in range(3): + arranged.append( + matrix[i * 6: (i + 1) * 6, j * 6: (j + 1) * 6].tolist()) + return arranged + + self._quantities = [ + Quantity( + 'elastic_constant', r'\%\s*\n([\s0-6A-L]*)[\n\s\%1-6\-ij]*([\s0-6A-L]*)\n', + str_operation=arrange_matrix, dtype=str, repeats=False, convert=False), + Quantity( + 'cijk', r'(C\d\d\d)\s*=\s*([\-\d\.]+)\s*GPa', repeats=True, convert=False)] class ElasticProperties: - def __init__(self, mainfile): - self._mainfile = mainfile - self.maindir = os.path.dirname(mainfile) + def __init__(self): + self._mainfile = None + self.logger = None self._deform_dirs = None self._deform_dir_prefix = 'Dst' - self._info = None + self.info = InfoParser() + self.structure = StructureParser() + self.distorted_parameters = DistortedParametersParser() + self.fit = FitParser() + self.elastic_constant_2 = ElasticConstant2Parser() + self.elastic_constant_3 = ElasticConstant3Parser() @property def mainfile(self): @@ -20,31 +158,10 @@ class ElasticProperties: @mainfile.setter def mainfile(self, val): - self._info = None self._deform_dirs = None self._mainfile = val - - @property - def info(self): - if self._info is None: - - quantities = [ - Quantity('order', r'\s*Order of elastic constants\s*=\s*([0-9]+)'), - Quantity('calculation_method', r'\s*Method of calculation\s*=\s*([-a-zA-Z]+)\s*'), - Quantity('code_name', r'\s*DFT code name\s*=\s*([-a-zA-Z]+)'), - Quantity('space_group_number', r'\s*Space-group number\s*=\s*([0-9]+)'), - Quantity('equilibrium_volume', r'\s*Volume of equilibrium unit cell\s*=\s*([0-9.]+)\s*', unit='angstrom ** 3'), - Quantity('max_strain', r'\s*Maximum Lagrangian strain\s*=\s*([0-9.]+)'), - Quantity('n_strains', r'\s*Number of distorted structures\s*=\s*([0-9]+)') - ] - - parser = UnstructuredTextFileParser(self.mainfile, quantities) - - self._info = {} - for key, val in parser.items(): - self._info[key] = val[0] - - return self._info + self.maindir = os.path.dirname(val) if val is not None else None + self.info.mainfile = val @property def deformation_dirs(self): @@ -57,7 +174,7 @@ class ElasticProperties: def get_references_to_calculations(self): def output_file(dirname): - code = self.info['code_name'].lower() + code = self.info.get('code_name', '').lower() if code == 'exciting': return os.path.join(dirname, 'INFO.OUT') elif code == 'wien': @@ -83,28 +200,16 @@ class ElasticProperties: if not os.path.isfile(path): return - def get_sym_pos(val): - val = val.strip().replace('\n', '').split() - sym = [] - pos = [] - for i in range(0, len(val), 4): - sym.append(val[i + 3].strip()) - pos.append([float(val[j]) for j in range(i, i + 3)]) - sym_pos = dict(symbols=sym, positions=pos) - return sym_pos - - quantities = [ - Quantity('cellpar', r'a\s*b\s*c\n([\d\.\s]+)\n\s*alpha\s*beta\s*gamma\n([\d\.\s]+)\n+'), - Quantity('sym_pos', r'Atom positions:\n\n([\s\d\.A-Za-z]+)\n\n', str_operation=get_sym_pos) - ] + self.structure.mainfile = path - parser = UnstructuredTextFileParser(path, quantities) + cellpar = self.structure.get('cellpar', None) + sym_pos = self.structure.get('sym_pos', {}) - cellpar = list(parser['cellpar'])[0] - sym_pos = list(parser['sym_pos'])[0] + sym = sym_pos.get('symbols', None) + pos = sym_pos.get('positions', None) - sym = list(sym_pos['symbols']) - pos = list(sym_pos['positions']) + if cellpar is None or sym is None or pos is None: + return structure = Atoms(cell=cellpar, scaled_positions=pos, symbols=sym, pbc=True) @@ -125,13 +230,10 @@ class ElasticProperties: path = os.path.join(deform_dir, filenames[-1]) data = np.loadtxt(path).T - strains.append(data[0]) - energies.append(data[1]) - - energies = pint.Quantity(energies, 'hartree') - # the peculiarity of the x_elastic_strain_diagram_values metainfo that it does - # not have the energy unit - energies = energies.to('J').magnitude + strains.append(list(data[0])) + # the peculiarity of the x_elastic_strain_diagram_values metainfo that it does + # not have the energy unit + energies.append(list(pint.Quantity(data[1], 'hartree').to('J').magnitude)) return strains, energies @@ -167,18 +269,8 @@ class ElasticProperties: def get_deformation_types(self): path = os.path.join(self.maindir, 'Distorted_Parameters') - if not os.path.isfile(path): - return - - quantities = [ - Quantity( - 'deformation', r'Lagrangian strain\s*=\s*\(([eta\s\d\.,]+)\)', - str_operation=lambda x: x.replace(',', '').split(), dtype=str)] - parser = UnstructuredTextFileParser(path, quantities) - - deformation_types = [list(d) for d in list(parser['deformation'])] - - return deformation_types + self.distorted_parameters.mainfile = path + return self.distorted_parameters.get('deformation') def _get_fit(self, path_dir, file_ext): path_dir = os.path.join(self.maindir, path_dir) @@ -192,32 +284,17 @@ class ElasticProperties: if not paths: return - eta = {'2nd': [], '3rd': [], '4th': [], '5th': [], '6th': [], '7th': []} - val = {'2nd': [], '3rd': [], '4th': [], '5th': [], '6th': [], '7th': []} - - def split_eta_val(val): - val = val.strip().split() - return [val[0::2], val[1::2]] - - quantities = [ - Quantity( - order, r'%s order fit.\n([\d.\s\ne\-\+]+)\n' % order, str_operation=split_eta_val) - for order in eta.keys() - ] - - parser = UnstructuredTextFileParser(os.path.join(path_dir, paths[0]), quantities) - + eta, val = {}, {} for path in paths: - parser.mainfile = os.path.join(path_dir, path) - for key in eta.keys(): - if parser[key] is not None: - eta[key].append(list(parser[key][0][0])) - val[key].append(list(parser[key][0][1])) + self.fit.mainfile = os.path.join(path_dir, path) + fit_results = self.fit.get('fit', []) + for result in fit_results: + eta.setdefault(result[0], []) + val.setdefault(result[0], []) + eta[result[0]].append(result[1]) + val[result[0]].append(result[2]) - eta = {key: val for key, val in eta.items() if val} - val = {key: val for key, val in val.items() if val} - - return [eta, val] + return eta, val def get_energy_fit(self): energy_fit = dict() @@ -227,14 +304,18 @@ class ElasticProperties: if result is None: continue + result = list(result) result[1] = { - key: pint.Quantity(val, 'GPa').to('Pa').magnitude for key, val in result[1].items()} + key: pint.Quantity( + val, 'GPa').to('Pa').magnitude for key, val in result[1].items()} energy_fit['d2e'] = result result = self._get_fit('Energy-vs-Strain', 'CVe.dat') if result is not None: + result = list(result) result[1] = { - key: pint.Quantity(val, 'hartree').to('J').magnitude for key, val in result[1].items()} + key: pint.Quantity( + val, 'hartree').to('J').magnitude for key, val in result[1].items()} energy_fit['cross-validation'] = result return energy_fit @@ -260,7 +341,7 @@ class ElasticProperties: def get_input(self): paths = os.listdir(self.maindir) path = None - order = self.info['order'] + order = self.info.get('order', 2) for p in paths: if 'ElaStic_' in p and p.endswith('.in') and str(order) in p: path = p @@ -269,7 +350,7 @@ class ElasticProperties: if path is None: return - calc_method = self.info['calculation_method'] + calc_method = self.info.get('calculation_method') eta_ec = [] fit_ec = [] @@ -309,89 +390,33 @@ class ElasticProperties: def get_elastic_constants_order2(self): path = os.path.join(self.maindir, 'ElaStic_2nd.out') - def reshape(val): - return np.array(val.split()).reshape((6, 6)) + self.elastic_constant_2.mainfile = path - quantities = [ - Quantity( - 'voigt', r'Symmetry[\s\S]+\n\s*\n([C\d\s\n\(\)\-\+\/\*]+)\n', - str_operation=reshape, dtype=str), - Quantity( - 'elastic_constant', r'Elastic constant[\s\S]+in GPa\s*:\s*\n\n([\-\d\.\s\n]+)\n', - str_operation=reshape, dtype=float, unit='GPa'), - Quantity( - 'compliance', r'Elastic compliance[\s\S]+in 1/GPa\s*:\s*\n\n([\-\d\.\s\n]+)\n', - str_operation=reshape, dtype=float, unit='1/GPa')] - - modulus_names = [ - 'B_V', 'K_V', 'G_V', 'B_R', 'K_R', 'G_R', 'B_H', 'K_H', 'G_H', 'E_V', - 'nu_V', 'E_R', 'nu_R', 'E_H', 'nu_H', 'AVR'] - - for name in modulus_names: - unit = None - if name[0:2] in ('B_', 'K_', 'G_', 'E_'): - unit = 'GPa' - quantities.append( - Quantity(name, r'%s\s*=\s*([-+\d+\.]+)\s*' % name, unit=unit)) - - quantities += [ - Quantity( - 'eigenvalues', r'Eigenvalues of elastic constant \(stiffness\) matrix:\s*\n+([\-\d\.\n\s]+)\n', unit='GPa')] - - parser = UnstructuredTextFileParser(path, quantities) - - matrices = dict( - voigt=list(parser['voigt'])[0], elastic_constant=list(parser['elastic_constant'])[0], - compliance=list(parser['compliance'])[0]) + matrices = dict() + for key in ['voigt', 'elastic_constant', 'compliance']: + val = self.elastic_constant_2.get(key, None) + if val is not None: + matrices[key] = val moduli = dict() - for name in modulus_names: - if parser[name] is not None: - moduli[name] = parser[name][0] + for modulus in self.elastic_constant_2.get('modulus', []): + moduli[modulus[0]] = modulus[1] - eigenvalues = list(parser['eigenvalues'])[0] + eigenvalues = self.elastic_constant_2.get('eigenvalues') return matrices, moduli, eigenvalues def get_elastic_constants_order3(self): path = os.path.join(self.maindir, 'ElaStic_3rd.out') - elastic_constant = [] + self.elastic_constant_3.mainfile = path - def arrange_matrix(val): - val = val.strip().split('\n') - matrix = [v.strip().split() for v in val if v.strip()] - matrix = np.array(matrix).reshape((12, 18)) - arranged = [] - for i in range(2): - for j in range(3): - arranged.append( - matrix[i * 6: (i + 1) * 6, j * 6: (j + 1) * 6].tolist()) - return arranged - - def get_cijk(val): - val = val.replace('=', '').replace('GPa', '') - cijk = dict() - for v in val.split('\n'): - v = v.split() - if len(v) == 2: - cijk[v[0].strip()] = float(v[1]) - return cijk - - space_group_number = self.info['space_group_number'] - - quantities = [ - Quantity( - 'elastic_constant', r'\%\s*\n([\s0-6A-L]*)[\n\s\%1-6\-ij]*([\s0-6A-L]*)\n', - str_operation=arrange_matrix, dtype=str), - Quantity( - 'cijk', r'(C\d{3}\s*=\s*[C1-6\s=\n\-\.0-9GPa]*)\n\n', str_operation=get_cijk) - ] - - parsers = UnstructuredTextFileParser(path, quantities) - - elastic_constant_str = list(parsers['elastic_constant'])[0] + elastic_constant_str = self.elastic_constant_3.get('elastic_constant') + if elastic_constant_str is None: + return - cijk = list(parsers['cijk'])[0] + cijk = dict() + for element in self.elastic_constant_3.get('cijk', []): + cijk[element[0]] = float(element[1]) # formulas for the coefficients coeff_A = cijk.get('C111', 0) + cijk.get('C112', 0) - cijk.get('C222', 0) @@ -406,6 +431,8 @@ class ElasticProperties: coeff_J = (cijk.get('C113', 0) - cijk.get('C123', 0)) / 2 coeff_K = -(cijk.get('C144', 0) - cijk.get('C155', 0)) / 2 + space_group_number = self.info.get('space_group_number') + if space_group_number <= 148: # rhombohedral II coefficients = dict( A=coeff_A, B=coeff_B, C=coeff_C, D=coeff_D, E=coeff_E, F=coeff_F, G=coeff_G,