#
# @BEGIN LICENSE
#
# Psi4: an open-source quantum chemistry software package
#
# Copyright (c) 2007-2019 The Psi4 Developers.
#
# The copyrights for code used from other parties are included in
# the corresponding files.
#
# This file is part of Psi4.
#
# Psi4 is free software; you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation, version 3.
#
# Psi4 is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License along
# with Psi4; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
#
# @END LICENSE
#
"""Module with utility functions for use in input files."""

import os
import re
import sys
import math
import warnings

import numpy as np

from psi4 import core
from .exceptions import ValidationError, TestComparisonError


def oeprop(wfn, *args, **kwargs):
    """Evaluate one-electron properties.

    :returns: None

    :type wfn: :py:class:`~psi4.core.Wavefunction`
    :param wfn: set of molecule, basis, orbitals from which to compute properties

    How to specify args, which are actually the most important

    :type title: string
    :param title: label prepended to all psivars computed

    :examples:

    >>> # [1] Moments with specific label
    >>> E, wfn = energy('hf', return_wfn=True)
    >>> oeprop(wfn, 'DIPOLE', 'QUADRUPOLE', title='H3O+ SCF')

    """
    oe = core.OEProp(wfn)
    if 'title' in kwargs:
        oe.set_title(kwargs['title'])
    for prop in args:
        oe.add(prop)
    oe.compute()


def cubeprop(wfn, **kwargs):
    """Evaluate properties on a grid and generate cube files.

    .. versionadded:: 0.5
       *wfn* parameter passed explicitly

    :returns: None

    :type wfn: :py:class:`~psi4.core.Wavefunction`
    :param wfn: set of molecule, basis, orbitals from which to generate cube files

    :examples:

    >>> # [1] Cube files for all orbitals
    >>> E, wfn = energy('b3lyp', return_wfn=True)
    >>> cubeprop(wfn)

    >>> # [2] Cube files for density (alpha, beta, total, spin) and four orbitals
    >>> #     (two alpha, two beta)
    >>> set cubeprop_tasks ['orbitals', 'density']
    >>> set cubeprop_orbitals [5, 6, -5, -6]
    >>> E, wfn = energy('scf', return_wfn=True)
    >>> cubeprop(wfn)

    """
    # By default compute the orbitals
    if not core.has_global_option_changed('CUBEPROP_TASKS'):
        core.set_global_option('CUBEPROP_TASKS', ['ORBITALS'])

    if ((core.get_global_option('INTEGRAL_PACKAGE') == 'ERD') and ('ESP' in core.get_global_option('CUBEPROP_TASKS'))):
        raise ValidationError('INTEGRAL_PACKAGE ERD does not play nicely with electrostatic potential, so stopping.')

    cp = core.CubeProperties(wfn)
    cp.compute_properties()


def set_memory(inputval, execute=True):
    """Function to reset the total memory allocation. Takes memory value
    *inputval* as type int, float, or str; int and float are taken literally
    as bytes to be set, string taken as a unit-containing value (e.g., 30 mb)
    which is case-insensitive. Set *execute* to False to interpret *inputval*
    without setting in Psi4 core.

    :returns: *memory_amount* (float) Number of bytes of memory set

    :raises: ValidationError when <500MiB or disallowed type or misformatted

    :examples:

    >>> # [1] Passing absolute number of bytes
    >>> psi4.set_memory(600000000)
    >>> psi4.get_memory()
    Out[1]: 600000000L

    >>> # [2] Passing memory value as string with units
    >>> psi4.set_memory('30 GB')
    >>> psi4.get_memory()
    Out[2]: 30000000000L

    >>> # Good examples
    >>> psi4.set_memory(800000000)        # 800000000
    >>> psi4.set_memory(2004088624.9)     # 2004088624
    >>> psi4.set_memory(1.0e9)            # 1000000000
    >>> psi4.set_memory('600 mb')         # 600000000
    >>> psi4.set_memory('600.0 MiB')      # 629145600
    >>> psi4.set_memory('.6 Gb')          # 600000000
    >>> psi4.set_memory(' 100000000kB ')  # 100000000000
    >>> psi4.set_memory('2 eb')           # 2000000000000000000

    >>> # Bad examples
    >>> psi4.set_memory({})         # odd type
    >>> psi4.set_memory('')         # no info
    >>> psi4.set_memory("8 dimms")  # unacceptable units
    >>> psi4.set_memory("1e5 gb")   # string w/ exponent
    >>> psi4.set_memory("5e5")      # string w/o units
    >>> psi4.set_memory(2000)       # mem too small
    >>> psi4.set_memory(-5e5)       # negative (and too small)

    """
    # Handle memory given in bytes directly (int or float)
    if isinstance(inputval, (int, float)):
        val = inputval
        units = ''
    # Handle memory given as a string
    elif isinstance(inputval, str):
        memory_string = re.compile(r'^\s*(\d*\.?\d+)\s*([KMGTPBE]i?B)\s*$', re.IGNORECASE)
        matchobj = re.search(memory_string, inputval)
        if matchobj:
            val = float(matchobj.group(1))
            units = matchobj.group(2)
        else:
            raise ValidationError("""Invalid memory specification: {}. Try 5e9 or '5 gb'.""".format(repr(inputval)))
    else:
        raise ValidationError("""Invalid type {} in memory specification: {}. Try 5e9 or '5 gb'.""".format(
            type(inputval), repr(inputval)))

    # Units decimal or binary?
    multiplier = 1000
    if "i" in units.lower():
        multiplier = 1024
        units = units.lower().replace("i", "").upper()

    # Build conversion factor, convert units
    unit_list = ["", "KB", "MB", "GB", "TB", "PB", "EB"]
    mult = 1
    for unit in unit_list:
        if units.upper() == unit:
            break
        mult *= multiplier

    memory_amount = int(val * mult)

    # Check minimum memory requirement
    min_mem_allowed = 262144000
    if memory_amount < min_mem_allowed:
        raise ValidationError(
            """set_memory(): Requested {:.3} MiB ({:.3} MB); minimum 250 MiB (263 MB). Please, sir, I want some more."""
            .format(memory_amount / 1024**2, memory_amount / 1000**2))

    if execute:
        core.set_memory_bytes(memory_amount)
    return memory_amount


def get_memory():
    """Function to return the total memory allocation."""
    return core.get_memory()


def success(label):
    """Function to print a '*label*...PASSED' line to screen.
    Used by :py:func:`util.compare_values` family when functions pass.

    """
    msg = '\t{0:.<66}PASSED'.format(label)
    print(msg)
    sys.stdout.flush()
    core.print_out(msg + '\n')


# Test functions
def compare_values(expected, computed, digits, label, exitonfail=True):
    """Function to compare two values. Prints :py:func:`util.success`
    when value *computed* matches value *expected* to number of *digits*
    (or to *digits* itself when *digits* < 1 e.g. digits=0.04). Performs
    a system exit on failure unless *exitonfail* False, in which case
    returns error message. Used in input files in the test suite.

    """
    if digits > 1:
        thresh = 10**-digits
        message = """\t{}: computed value ({:.{digits1}f}) does not match ({:.{digits1}f}) to {digits} digits.""".format(
            label, computed, expected, digits1=int(digits) + 1, digits=digits)
    else:
        thresh = digits
        message = ("\t%s: computed value (%f) does not match (%f) to %f digits." % (label, computed, expected, digits))
    if abs(float(expected) - float(computed)) > thresh:
        # float cast handles decimal.Decimal vars
        print(message)
        if exitonfail:
            raise TestComparisonError(message)
    if math.isnan(computed):
        print(message)
        print("\tprobably because the computed value is nan.")
        if exitonfail:
            raise TestComparisonError(message)
    success(label)
    return True


def compare_integers(expected, computed, label):
    """Function to compare two integers. Prints :py:func:`util.success`
    when value *computed* matches value *expected*.
    Performs a system exit on failure. Used in input files in the test suite.

    """
    if (expected != computed):
        message = ("\t%s: computed value (%d) does not match (%d)." % (label, computed, expected))
        raise TestComparisonError(message)
    success(label)
    return True


def compare_strings(expected, computed, label):
    """Function to compare two strings. Prints :py:func:`util.success`
    when string *computed* exactly matches string *expected*.
    Performs a system exit on failure. Used in input files in the test suite.

    """
    if (expected != computed):
        message = ("\t%s: computed value (%s) does not match (%s)." % (label, computed, expected))
        raise TestComparisonError(message)
    success(label)
    return True


def compare_matrices(expected, computed, digits, label):
    """Function to compare two matrices. Prints :py:func:`util.success`
    when elements of matrix *computed* match elements of matrix *expected* to
    number of *digits*. Performs a system exit on failure to match symmetry
    structure, dimensions, or element values. Used in input files in the test suite.

    """
    if (expected.nirrep() != computed.nirrep()):
        message = ("\t%s has %d irreps, but %s has %d\n." % (expected.name(), expected.nirrep(), computed.name(),
                                                             computed.nirrep()))
        raise TestComparisonError(message)
    if (expected.symmetry() != computed.symmetry()):
        message = ("\t%s has %d symmetry, but %s has %d\n." % (expected.name(), expected.symmetry(), computed.name(),
                                                               computed.symmetry()))
        raise TestComparisonError(message)
    nirreps = expected.nirrep()
    symmetry = expected.symmetry()
    for irrep in range(nirreps):
        if (expected.rows(irrep) != computed.rows(irrep)):
            message = ("\t%s has %d rows in irrep %d, but %s has %d\n." %
                       (expected.name(), expected.rows(irrep), irrep, computed.name(), computed.rows(irrep)))
            raise TestComparisonError(message)
        if (expected.cols(irrep ^ symmetry) != computed.cols(irrep ^ symmetry)):
            message = ("\t%s has %d columns in irrep, but %s has %d\n." %
                       (expected.name(), expected.cols(irrep), irrep, computed.name(), computed.cols(irrep)))
            raise TestComparisonError(message)
        rows = expected.rows(irrep)
        cols = expected.cols(irrep ^ symmetry)
        failed = 0
        for row in range(rows):
            for col in range(cols):
                if (abs(expected.get(irrep, row, col) - computed.get(irrep, row, col)) > 10**(-digits)):
                    print("\t%s: computed value (%s) does not match (%s)." % (label, expected.get(irrep, row, col),
                                                                              computed.get(irrep, row, col)))
                    failed = 1
                    break

        if (failed):
            print("Check your output file for reporting of the matrices.")
            core.print_out("The Failed Test Matrices\n")
            core.print_out("Computed Matrix (2nd matrix passed in)\n")
            computed.print_out()
            core.print_out("Expected Matrix (1st matrix passed in)\n")
            expected.print_out()
            raise TestComparisonError("\n")
    success(label)
    return True


def compare_vectors(expected, computed, digits, label):
    """Function to compare two vectors. Prints :py:func:`util.success`
    when elements of vector *computed* match elements of vector *expected* to
    number of *digits*. Performs a system exit on failure to match symmetry
    structure, dimension, or element values. Used in input files in the test suite.

    """
    if (expected.nirrep() != computed.nirrep()):
        message = ("\t%s has %d irreps, but %s has %d\n." % (expected.name(), expected.nirrep(), computed.name(),
                                                             computed.nirrep()))
        raise TestComparisonError(message)
    nirreps = expected.nirrep()
    for irrep in range(nirreps):
        if (expected.dim(irrep) != computed.dim(irrep)):
            message = ("\tThe reference has %d entries in irrep %d, but the computed vector has %d\n." %
                       (expected.dim(irrep), irrep, computed.dim(irrep)))
            raise TestComparisonError(message)
        dim = expected.dim(irrep)
        failed = 0
        for entry in range(dim):
            if (abs(expected.get(irrep, entry) - computed.get(irrep, entry)) > 10**(-digits)):
                failed = 1
                break

        if (failed):
            core.print_out("The computed vector\n")
            computed.print_out()
            core.print_out("The reference vector\n")
            expected.print_out()
            message = ("\t%s: computed value (%s) does not match (%s)." % (label, computed.get(irrep, entry),
                                                                           expected.get(irrep, entry)))
            raise TestComparisonError(message)
    success(label)
    return True


def compare_arrays(expected, computed, digits, label, rtol=1.e-16):
    """Function to compare two numpy arrays. Prints :py:func:`util.success`
    when elements of vector *computed* match elements of vector *expected* to
    number of *digits*. Performs a system exit on failure to match symmetry
    structure, dimension, or element values. Used in input files in the test suite.

    Sets rtol to zero to match expected Psi4 behaviour, otherwise measured as:
        absolute(computed - expected) <= (atol + rtol * absolute(expected))
    """

    try:
        expected = np.asarray(expected)
        computed = np.asarray(computed)
        shape1 = expected.shape
        shape2 = computed.shape
    except:
        raise TestComparisonError("Input objects do not have a shape attribute.")

    if shape1 != shape2:
        TestComparisonError("Input shapes do not match.")

    tol = 10**(-digits)
    if not np.allclose(expected, computed, atol=tol, rtol=rtol):
        message = "\tArray difference norm is %12.6e." % np.linalg.norm(expected - computed)
        raise TestComparisonError(message)
    success(label)
    return True


def compare_cubes(expected, computed, label):
    """Function to compare two cube files. Prints :py:func:`util.success`
    when value *computed* matches value *expected*.
    Performs a system exit on failure. Used in input files in the test suite.

    """
    # Grab grid points. Skip the first nine lines and the last one
    evec = np.genfromtxt(expected, skip_header=9, skip_footer=1)
    cvec = np.genfromtxt(computed, skip_header=9, skip_footer=1)
    if evec.size == cvec.size:
        if not np.allclose(cvec, evec, rtol=5e-05, atol=1e-10):
            message = ("\t%s: computed cube file does not match expected cube file." % label)
            raise TestComparisonError(message)
    else:
        message = ("\t%s: computed cube file does not match size of expected cube file." % label)
        raise TestComparisonError(message)
    success(label)
    return True


def compare_wavefunctions(expected, computed, digits=9, label='Wavefunctions equal'):
    """Function to compare two wavefunctions. Prints :py:func:`util.success`
    when value *computed* matches value *expected*.
    Performs a system exit on failure. Used in input files in the test suite.

    """
    # yapf: disable
    if expected.Ca():          compare_matrices(expected.Ca(), computed.Ca(), digits, 'compare Ca')
    if expected.Cb():          compare_matrices(expected.Cb(), computed.Cb(), digits, 'compare Cb')
    if expected.Da():          compare_matrices(expected.Da(), computed.Da(), digits, 'compare Da')
    if expected.Db():          compare_matrices(expected.Db(), computed.Db(), digits, 'compare Db')
    if expected.Fa():          compare_matrices(expected.Fa(), computed.Fa(), digits, 'compare Fa')
    if expected.Fb():          compare_matrices(expected.Fb(), computed.Fb(), digits, 'compare Fb')
    if expected.H():           compare_matrices(expected.H(), computed.H(), digits, 'compare H')
    if expected.S():           compare_matrices(expected.S(), computed.S(), digits, 'compare S')
    if expected.X():           compare_matrices(expected.X(), computed.X(), digits, 'compare X')
    if expected.aotoso():      compare_matrices(expected.aotoso(), computed.aotoso(), digits, 'compare aotoso')
    if expected.gradient():    compare_matrices(expected.gradient(), computed.gradient(), digits, 'compare gradient')
    if expected.hessian():     compare_matrices(expected.hessian(), computed.hessian(), digits, 'compare hessian')
    if expected.epsilon_a():   compare_vectors(expected.epsilon_a(), computed.epsilon_a(), digits, 'compare epsilon_a')
    if expected.epsilon_b():   compare_vectors(expected.epsilon_b(), computed.epsilon_b(), digits, 'compare epsilon_b')
    if expected.frequencies(): compare_vectors(expected.frequencies(), computed.frequencies(), digits, 'compare frequencies')
    # yapf: enable
    compare_integers(expected.nalpha(), computed.nalpha(), 'compare nalpha')
    compare_integers(expected.nbeta(), computed.nbeta(), 'compare nbeta')
    compare_integers(expected.nfrzc(), computed.nfrzc(), 'compare nfrzc')
    compare_integers(expected.nirrep(), computed.nirrep(), 'compare nirrep')
    compare_integers(expected.nmo(), computed.nmo(), 'compare nmo')
    compare_integers(expected.nso(), computed.nso(), 'compare nso')
    compare_strings(expected.name(), computed.name(), 'compare name')
    compare_values(expected.energy(), computed.energy(), digits, 'compare energy')
    compare_values(expected.efzc(), computed.efzc(), digits, 'compare frozen core energy')
    compare_values(expected.get_dipole_field_strength()[0],
                   computed.get_dipole_field_strength()[0], digits, 'compare dipole field strength x')
    compare_values(expected.get_dipole_field_strength()[1],
                   computed.get_dipole_field_strength()[1], digits, 'compare dipole field strength y')
    compare_values(expected.get_dipole_field_strength()[2],
                   computed.get_dipole_field_strength()[2], digits, 'compare dipole field strength z')

    compare_strings(expected.basisset().name(), computed.basisset().name(), 'compare basis set name')
    compare_integers(expected.basisset().nbf(), computed.basisset().nbf(), 'compare number of basis functions in set')
    compare_integers(expected.basisset().nprimitive(),
                     computed.basisset().nprimitive(), 'compare total number of primitives in basis set')

    compare_strings(expected.molecule().name(), computed.molecule().name(), 'compare molecule name')
    compare_strings(expected.molecule().get_full_point_group(),
                    computed.molecule().get_full_point_group(), 'compare molecule point group')
    compare_matrices(expected.molecule().geometry(),
                     computed.molecule().geometry(), digits, 'compare molecule geometry')

    success(label)
    return True


# Uncomment and use if compare_arrays above is inadequate
#def compare_lists(expected, computed, digits, label):
#    """Function to compare two Python lists. Prints :py:func:`util.success`
#    when elements of vector *computed* match elements of vector *expected* to
#    number of *digits*. Performs a system exit on failure to match symmetry
#    structure, dimension, or element values. Used in input files in the test suite.
#
#    """
#    if len(expected) != len(computed):
#        message = ("\tThe reference has %d entries, but the computed vector has %d\n." % (len(expected), len(computed)))
#        raise TestComparisonError(message)
#    dim = len(expected)
#    failed = 0
#    for entry in range(dim):
#        if(abs(expected[entry] - computed[entry]) > 10 ** (-digits)):
#            print("\t%s: computed value (%s) does not match (%s)." % (label, computed[entry], expected[entry]))
#            failed = 1
#            break
#
#    if(failed):
#        core.print_out("The computed vector\n")
#        computed.print_out()
#        core.print_out("The reference vector\n")
#        expected.print_out()
#        message = ("\t%s: computed list does not match expected list." % (label, computed, expected))
#        raise TestComparisonError(message)
#    success(label)


def copy_file_to_scratch(filename, prefix, namespace, unit, move=False):
    """Function to move file into scratch with correct naming
    convention.

    Arguments:

    @arg filename  full path to file
    @arg prefix    computation prefix, usually 'psi'
    @arg namespace context namespace, usually molecule name
    @arg unit      unit number, e.g. 32
    @arg move      copy or move? (default copy)

    Example:

    Assume PID is 12345 and SCRATCH is /scratch/parrish/

    copy_file_to_scratch('temp', 'psi', 'h2o', 32):
        -cp ./temp /scratch/parrish/psi.12345.h2o.32
    copy_file_to_scratch('/tmp/temp', 'psi', 'h2o', 32):
        -cp /tmp/temp /scratch/parrish/psi.12345.h2o.32
    copy_file_to_scratch('/tmp/temp', 'psi', '', 32):
        -cp /tmp/temp /scratch/parrish/psi.12345.32
    copy_file_to_scratch('/tmp/temp', 'psi', '', 32, True):
        -mv /tmp/temp /scratch/parrish/psi.12345.32

    """

    pid = str(os.getpid())
    scratch = core.IOManager.shared_object().get_file_path(int(unit))

    cp = '/bin/cp'
    if move:
        cp = '/bin/mv'

    unit = str(unit)

    target = ''
    target += prefix
    target += '.'
    target += pid
    if len(namespace):
        target += '.'
        target += namespace
    target += '.'
    target += unit

    command = ('%s %s %s/%s' % (cp, filename, scratch, target))

    os.system(command)
    #print command


def copy_file_from_scratch(filename, prefix, namespace, unit, move=False):
    """Function to move file out of scratch with correct naming
    convention.

    Arguments:

    @arg filename  full path to target file
    @arg prefix    computation prefix, usually 'psi'
    @arg namespace context namespace, usually molecule name
    @arg unit      unit number, e.g. 32
    @arg move      copy or move? (default copy)

    Example:

    Assume PID is 12345 and SCRATCH is /scratch/parrish/

    copy_file_to_scratch('temp', 'psi', 'h2o', 32):
        -cp /scratch/parrish/psi.12345.h2o.32 .temp
    copy_file_to_scratch('/tmp/temp', 'psi', 'h2o', 32):
        -cp /scratch/parrish/psi.12345.h2o.32 /tmp/temp
    copy_file_to_scratch('/tmp/temp', 'psi', '', 32):
        -cp /scratch/parrish/psi.12345.32 /tmp/temp
    copy_file_to_scratch('/tmp/temp', 'psi', '', 32, True):
        -mv /scratch/parrish/psi.12345.32 /tmp/temp

    """

    pid = str(os.getpid())
    scratch = core.IOManager.shared_object().get_file_path(int(unit))

    cp = '/bin/cp'
    if move:
        cp = '/bin/mv'

    unit = str(unit)

    target = ''
    target += prefix
    target += '.'
    target += pid
    if len(namespace):
        target += '.'
        target += namespace
    target += '.'
    target += unit

    command = ('%s %s/%s %s' % (cp, scratch, target, filename))

    os.system(command)


def xml2dict(filename=None):
    """Read XML *filename* into nested OrderedDict-s. *filename* defaults to
    active CSX file.

    """
    warnings.warn(
        "Using `psi4.driver.p4util.xml2dict` is deprecated (silently in 1.2), and in 1.3 it will stop working\n",
        category=FutureWarning,
        stacklevel=2)

    import xmltodict as xd
    if filename is None:
        csx = os.path.splitext(core.outfile_name())[0] + '.csx'
    else:
        csx = filename
    with open(csx, 'r') as handle:
        csxdict = xd.parse(handle)

    return csxdict


def getFromDict(dataDict, mapList):
    warnings.warn(
        "Using `psi4.driver.p4util.getFromDict` is deprecated (silently in 1.2), and in 1.3 it will stop working\n",
        category=FutureWarning,
        stacklevel=2)

    return reduce(lambda d, k: d[k], mapList, dataDict)


def csx2endict():
    """Grabs the CSX file as a dictionary, encodes translation of PSI variables
    to XML blocks, gathers all available energies from CSX file into returned
    dictionary.

    """
    warnings.warn(
        "Using `psi4.driver.p4util.csx2endict` is deprecated (silently in 1.2), and in 1.3 it will stop working\n",
        category=FutureWarning,
        stacklevel=2)

    blockprefix = [
        'chemicalSemantics', 'molecularCalculation', 'quantumMechanics', 'singleReferenceState', 'singleDeterminant'
    ]
    blockmidfix = ['energies', 'energy']
    prefix = 'cs:'

    pv2xml = {
        'MP2 CORRELATION ENERGY': [['mp2'], 'correlation'],
        'MP2 SAME-SPIN CORRELATION ENERGY': [['mp2'], 'sameSpin correlation'],
        'HF TOTAL ENERGY': [['abinitioScf'], 'electronic'],
        'NUCLEAR REPULSION ENERGY': [['abinitioScf'], 'nuclearRepulsion'],
        'DFT FUNCTIONAL TOTAL ENERGY': [['dft'], 'dftFunctional'],
        'DFT TOTAL ENERGY': [['dft'], 'electronic'],
        'DOUBLE-HYBRID CORRECTION ENERGY': [['dft'], 'doubleHybrid correction'],
        'DISPERSION CORRECTION ENERGY': [['dft'], 'dispersion correction'],
    }

    csxdict = xml2dict()
    enedict = {}
    for pv, lpv in pv2xml.items():
        address = blockprefix + lpv[0] + blockmidfix
        indices = [prefix + bit for bit in address]
        try:
            qwer = getFromDict(csxdict, indices)
        except KeyError:
            continue
        for v in qwer:
            vv = v.values()
            if vv[0] == prefix + lpv[1]:
                enedict[pv] = float(vv[1])

    return enedict


def compare_csx():
    """Function to validate energies in CSX files against PSIvariables. Only
    active if write_csx flag on.

    """
    warnings.warn(
        "Using `psi4.driver.p4util.compare_csx` is deprecated (silently in 1.2), and in 1.3 it will stop working\n",
        category=FutureWarning,
        stacklevel=2)

    if 'csx4psi' in sys.modules.keys():
        if core.get_global_option('WRITE_CSX'):
            enedict = csx2endict()
            compare_integers(len(enedict) >= 2, True, 'CSX harvested')
            for pv, en in enedict.items():
                compare_values(core.variable(pv), en, 6, 'CSX ' + pv + ' ' + str(round(en, 4)))
