<commit_before># -*- coding: utf-8 -*-
#
# This file is part of cclib (http://cclib.github.io), a library for parsing
# and interpreting the results of computational chemistry packages.
#
# Copyright (C) 2014-2015, the cclib development team
#
# The library is free software, distributed under the terms of
# the GNU Lesser General Public version 2.1 or later. You should have
# received a copy of the license along with cclib. You can also access
# the full license online at http://www.gnu.org/copyleft/lgpl.html.

"""Parser for Q-Chem output files"""

from __future__ import print_function

import re
import numpy
import itertools

from . import logfileparser
from . import utils


class QChem(logfileparser.Logfile):
    """A Q-Chem 4 log file."""

    def __init__(self, *args, **kwargs):

        # Call the __init__ method of the superclass
        super(QChem, self).__init__(logname="QChem", *args, **kwargs)
        self.package = "QChem"

    def __str__(self):
        """Return a string representation of the object."""
        return "QChem log file %s" % (self.filename)

    def __repr__(self):
        """Return a representation of the object."""
        return 'QChem("%s")' % (self.filename)

    def normalisesym(self, label):
        """Q-Chem does not require normalizing symmetry labels."""

    def before_parsing(self):

        # Keep track of whether or not we're performing an
        # (un)restricted calculation.
        self.unrestricted = False
        self.is_rohf = False

        # Compile the dashes-and-or-spaces-only regex.
        self.re_dashes_and_spaces = re.compile('^[\s-]+$')

        # Compile the regex for extracting the atomic index from an
        # aoname.
        self.re_atomindex = re.compile('(\d+)_')

        # A maximum of 6 columns per block when printing matrices.
        self.ncolsblock = 6

        # By default, when asked to print orbitals via
        # `scf_print`/`scf_final_print` and/or `print_orbitals`,
        # Q-Chem will print all occupieds and the first 5 virtuals.
        #
        # When the number is set for `print_orbitals`, that section of
        # the output will display (NOcc + that many virtual) MOs, but
        # any other sections present due to
        # `scf_print`/`scf_final_print` will still only display (NOcc
        # + 5) MOs.
        #
        # Note that the density matrix is always (NBasis * NBasis)!
        self.norbdisp_alpha = self.norbdisp_beta = 5
        self.norbdisp_alpha_aonames = self.norbdisp_beta_aonames = 5
        self.norbdisp_set = False

        self.alpha_mo_coefficient_headers = (
            'RESTRICTED (RHF) MOLECULAR ORBITAL COEFFICIENTS',
            'ALPHA MOLECULAR ORBITAL COEFFICIENTS'
        )

    def after_parsing(self):

        # If parsing a fragment job, each of the geometries appended to
        # `atomcoords` may be of different lengths, which will prevent
        # conversion from a list to NumPy array.
        # Take the length of the first geometry as correct, and remove
        # all others with different lengths.
        if len(self.atomcoords) > 1:
            correctlen = len(self.atomcoords[0])
            self.atomcoords[:] = [coords for coords in self.atomcoords
                                  if len(coords) == correctlen]
        # At the moment, there is no similar correction for other properties!

        # QChem does not print all MO coefficients by default, but rather
        # up to HOMO+5. So, fill up the missing values with NaNs. If there are
        # other cases where coefficient are missing, but different ones, this
        # general afterthought might not be appropriate and the fix will
        # need to be done while parsing.
        if hasattr(self, 'mocoeffs'):
            for im in range(len(self.mocoeffs)):
                _nmo, _nbasis = self.mocoeffs[im].shape
                if (_nmo, _nbasis) != (self.nmo, self.nbasis):
                    coeffs = numpy.empty((self.nmo, self.nbasis))
                    coeffs[:] = 0.0
                    coeffs[0:_nmo, 0:_nbasis] = self.mocoeffs[im]
                    self.mocoeffs[im] = coeffs

        # When parsing the 'MOLECULAR ORBITAL COEFFICIENTS' block for
        # `aonames`, Q-Chem doesn't print the principal quantum number
        # for each shell; this needs to be added.
        if hasattr(self, 'aonames') and hasattr(self, 'atombasis'):
            angmom = ('', 'S', 'P', 'D', 'F', 'G', 'H', 'I')
            for atom in self.atombasis:
                bfcounts = dict()
                for bfindex in atom:
                    atomname, bfname = self.aonames[bfindex].split('_')
                    # Keep track of how many times each shell type has
                    # appeared.
                    if bfname in bfcounts:
                        bfcounts[bfname] += 1
                    else:
                        # Make sure the starting number for type of
                        # angular momentum begins at the appropriate
                        # principal quantum number (1S, 2P, 3D, 4F,
                        # ...).
                        bfcounts[bfname] = angmom.index(bfname[0])
                    newbfname = '{}{}'.format(bfcounts[bfname], bfname)
                    self.aonames[bfindex] = '_'.join([atomname, newbfname])

    def extract(self, inputfile, line):
        """Extract information from the file object inputfile."""

        wfn_method = ['HF', 'HARTREE-FOCK', 'MP2', 'RI-MP2', 'LOCAL_MP2', 'MP4', \
                      'CCD', 'CCSD', 'CCSD(T)', 'QCISD', 'QCISD(T)']
        #extract the version number
        if 'Q-Chem,' in line:
            tversion = line.split()[1]
            self.version = tversion[:-1]
        # If the input section is repeated back, parse the $rem and
        # $molecule sections.
        if line[0:11] == 'User input:':
            self.skip_line(inputfile, 'd')
            while list(set(line.strip())) != ['-']:

                if '$rem' in line:
                    while '$end' not in line:
                        line = next(inputfile)
                        if 'method' in line.lower():
                            method = line.split()[-1].upper()
                            if method.upper() in wfn_method:
                                if method.upper() == 'HARTREE-FOCK':
                                    self.theory = 'HF'
                                else:
                                    self.theory = method
                            else:
                                self.theory = 'DFT'
                                self.functional = method
                        if 'exchange' in line.lower():
                            self.theory = 'DFT'
                            xfunctional = line.split()[1]
                            if xfunctional == 'B':
                                self.xfunctional = 'Becke88'
                            else:
                                self.functional = xfunctional
                        if 'correlation' in line.lower():
                            self.theory = 'DFT'
                            self.cfunctional = line.split()[1]
                        if 'print_orbitals' in line.lower():
                            # Stay with the default value if a number isn't
                            # specified.
                            if line.split()[-1].lower() in ('true', 'false'):
                                continue
                            else:
                                norbdisp_aonames = int(line.split()[-1])
                                self.norbdisp_alpha_aonames = norbdisp_aonames
                                self.norbdisp_beta_aonames = norbdisp_aonames
                                self.norbdisp_set = True

                # Charge and multiplicity are present in the input file, which is generally
                # printed once at the beginning. However, it is also prined for fragment
                # calculations, so make sure we parse only the first occurance.
                if '$molecule' in line :
                    line = next(inputfile)
                    if line.split()[0] == "read":
                        break
                    charge, mult = map(int, line.split())
                    if not hasattr(self, 'charge'):
                        self.set_attribute('charge', charge)
                    if not hasattr(self, 'mult'):
                        self.set_attribute('mult', mult)

                line = next(inputfile)
<commit_msg>fixed dft functional parsing for QChem<commit_after># -*- coding: utf-8 -*-
#
# This file is part of cclib (http://cclib.github.io), a library for parsing
# and interpreting the results of computational chemistry packages.
#
# Copyright (C) 2014-2015, the cclib development team
#
# The library is free software, distributed under the terms of
# the GNU Lesser General Public version 2.1 or later. You should have
# received a copy of the license along with cclib. You can also access
# the full license online at http://www.gnu.org/copyleft/lgpl.html.

"""Parser for Q-Chem output files"""

from __future__ import print_function

import re
import numpy
import itertools

from . import logfileparser
from . import utils


class QChem(logfileparser.Logfile):
    """A Q-Chem 4 log file."""

    def __init__(self, *args, **kwargs):

        # Call the __init__ method of the superclass
        super(QChem, self).__init__(logname="QChem", *args, **kwargs)
        self.package = "QChem"

    def __str__(self):
        """Return a string representation of the object."""
        return "QChem log file %s" % (self.filename)

    def __repr__(self):
        """Return a representation of the object."""
        return 'QChem("%s")' % (self.filename)

    def normalisesym(self, label):
        """Q-Chem does not require normalizing symmetry labels."""

    def before_parsing(self):

        # Keep track of whether or not we're performing an
        # (un)restricted calculation.
        self.unrestricted = False
        self.is_rohf = False

        # Compile the dashes-and-or-spaces-only regex.
        self.re_dashes_and_spaces = re.compile('^[\s-]+$')

        # Compile the regex for extracting the atomic index from an
        # aoname.
        self.re_atomindex = re.compile('(\d+)_')

        # A maximum of 6 columns per block when printing matrices.
        self.ncolsblock = 6

        # By default, when asked to print orbitals via
        # `scf_print`/`scf_final_print` and/or `print_orbitals`,
        # Q-Chem will print all occupieds and the first 5 virtuals.
        #
        # When the number is set for `print_orbitals`, that section of
        # the output will display (NOcc + that many virtual) MOs, but
        # any other sections present due to
        # `scf_print`/`scf_final_print` will still only display (NOcc
        # + 5) MOs.
        #
        # Note that the density matrix is always (NBasis * NBasis)!
        self.norbdisp_alpha = self.norbdisp_beta = 5
        self.norbdisp_alpha_aonames = self.norbdisp_beta_aonames = 5
        self.norbdisp_set = False

        self.alpha_mo_coefficient_headers = (
            'RESTRICTED (RHF) MOLECULAR ORBITAL COEFFICIENTS',
            'ALPHA MOLECULAR ORBITAL COEFFICIENTS'
        )

    def after_parsing(self):

        # If parsing a fragment job, each of the geometries appended to
        # `atomcoords` may be of different lengths, which will prevent
        # conversion from a list to NumPy array.
        # Take the length of the first geometry as correct, and remove
        # all others with different lengths.
        if len(self.atomcoords) > 1:
            correctlen = len(self.atomcoords[0])
            self.atomcoords[:] = [coords for coords in self.atomcoords
                                  if len(coords) == correctlen]
        # At the moment, there is no similar correction for other properties!

        # QChem does not print all MO coefficients by default, but rather
        # up to HOMO+5. So, fill up the missing values with NaNs. If there are
        # other cases where coefficient are missing, but different ones, this
        # general afterthought might not be appropriate and the fix will
        # need to be done while parsing.
        if hasattr(self, 'mocoeffs'):
            for im in range(len(self.mocoeffs)):
                _nmo, _nbasis = self.mocoeffs[im].shape
                if (_nmo, _nbasis) != (self.nmo, self.nbasis):
                    coeffs = numpy.empty((self.nmo, self.nbasis))
                    coeffs[:] = 0.0
                    coeffs[0:_nmo, 0:_nbasis] = self.mocoeffs[im]
                    self.mocoeffs[im] = coeffs

        # When parsing the 'MOLECULAR ORBITAL COEFFICIENTS' block for
        # `aonames`, Q-Chem doesn't print the principal quantum number
        # for each shell; this needs to be added.
        if hasattr(self, 'aonames') and hasattr(self, 'atombasis'):
            angmom = ('', 'S', 'P', 'D', 'F', 'G', 'H', 'I')
            for atom in self.atombasis:
                bfcounts = dict()
                for bfindex in atom:
                    atomname, bfname = self.aonames[bfindex].split('_')
                    # Keep track of how many times each shell type has
                    # appeared.
                    if bfname in bfcounts:
                        bfcounts[bfname] += 1
                    else:
                        # Make sure the starting number for type of
                        # angular momentum begins at the appropriate
                        # principal quantum number (1S, 2P, 3D, 4F,
                        # ...).
                        bfcounts[bfname] = angmom.index(bfname[0])
                    newbfname = '{}{}'.format(bfcounts[bfname], bfname)
                    self.aonames[bfindex] = '_'.join([atomname, newbfname])

    def extract(self, inputfile, line):
        """Extract information from the file object inputfile."""

        wfn_method = ['HF', 'HARTREE-FOCK', 'MP2', 'RI-MP2', 'LOCAL_MP2', 'MP4', \
                      'CCD', 'CCSD', 'CCSD(T)', 'QCISD', 'QCISD(T)']
        #extract the version number
        if 'Q-Chem,' in line:
            tversion = line.split()[1]
            self.version = tversion[:-1]
        # If the input section is repeated back, parse the $rem and
        # $molecule sections.
        if line[0:11] == 'User input:':
            self.skip_line(inputfile, 'd')
            while list(set(line.strip())) != ['-']:

                if '$rem' in line:
                    while '$end' not in line:
                        line = next(inputfile)
                        if 'method' in line.lower():
                            method = line.split()[-1].upper()
                            if method.upper() in wfn_method:
                                if method.upper() == 'HARTREE-FOCK':
                                    self.theory = 'HF'
                                else:
                                    self.theory = method
                            else:
                                self.theory = 'DFT'
                                self.functional = method.upper()
                        if 'exchange' in line.lower():
                            self.theory = 'DFT'
                            xfunctional = line.split()[1]
                            if xfunctional == 'B':
                                self.xfunctional = 'Becke88'
                            else:
                                self.functional = xfunctional.upper()
                        if 'correlation' in line.lower():
                            self.theory = 'DFT'
                            self.cfunctional = line.split()[1]
                        if 'print_orbitals' in line.lower():
                            # Stay with the default value if a number isn't
                            # specified.
                            if line.split()[-1].lower() in ('true', 'false'):
                                continue
                            else:
                                norbdisp_aonames = int(line.split()[-1])
                                self.norbdisp_alpha_aonames = norbdisp_aonames
                                self.norbdisp_beta_aonames = norbdisp_aonames
                                self.norbdisp_set = True

                # Charge and multiplicity are present in the input file, which is generally
                # printed once at the beginning. However, it is also prined for fragment
                # calculations, so make sure we parse only the first occurance.
                if '$molecule' in line :
                    line = next(inputfile)
                    if line.split()[0] == "read":
                        break
                    charge, mult = map(int, line.split())
                    if not hasattr(self, 'charge'):
                        self.set_attribute('charge', charge)
                    if not hasattr(self, 'mult'):
                        self.set_attribute('mult', mult)

                line = next(inputfile)

{'fraction_non_alphanumeric': 0.09415755307327914, 'fraction_numerical': 0.0056143634130650915, 'mean_word_length': 1.5507159904534606, 'pattern_counts': {'":': 2, '<': 3, '<?xml version=': 0, '>': 5, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 8, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '2207690', 'n_tokens_mistral': 4830, 'n_tokens_neox': 4493, 'n_words': 1465}