"""Module to handle qcmaquis output files.
This module implements the Qcmaquis class which evaluates entropies and
mutual information from the qcmaquis hdf5 output.
"""
# -*- coding: utf-8 -*-
__copyright__ = """This code is licensed under the 3-clause BSD license.
Copyright ETH Zurich, Department of Chemistry and Applied Biosciences, Reiher Group.
See LICENSE.txt for details. """
import numpy as np
from scine_autocas.interfaces.qcmaquis.qcmaquis_hdf5_utils import Hdf5Converter
from scine_autocas.interfaces.qcmaquis.qcmaquis_orbital_rdm_builder import OrbitalRDMBuilder
[docs]class Qcmaquis:
    """Class to handle all qcmaquis output.
    This class purpose is to read qcmaquis output and evaluate entropies from
    the provided measurements.
    Attributes
    ----------
    hdf5_converter : Hdf5Converter
        reads the qcmaquis hdf5 output file
    orbital_rdm_builder : OrbitalRDMBuilder
        evaluates the one and two orbital rdm from data provided by the hdf5 converter
    s1_entropy : np.ndarray
        stores the sinlge orbital entropy
    s2_entropy : np.ndarray
        stores the two orbital two orbital entropy
    mutual_information : np.ndarray
        stores the mutual information
    n_orbitals : int
        the number of orbitals in the active space
    energy : float
        the energy of the qcmaquis calculation
    """
    __slots__ = (
        "hdf5_converter",
        "orbital_rdm_builder",
        "s1_entropy",
        "s2_entropy",
        "mutual_information",
        "n_orbitals",
        "energy",
    )
[docs]    def __init__(self):
        """Init."""
        self.hdf5_converter: Hdf5Converter = Hdf5Converter()
        """extracts one and two orbital RDM from QCMaquis results fil"""
        self.orbital_rdm_builder: OrbitalRDMBuilder = OrbitalRDMBuilder()
        """evaluates and stores the orbital RMDs"""
        self.s1_entropy: np.ndarray = np.array([])
        """single orbital entrop"""
        self.s2_entropy: np.ndarray = np.array([])
        """two orbital entrop"""
        self.mutual_information: np.ndarray = np.array([])
        """mutual information"""
        self.n_orbitals: int = 0
        """number of orbitals in CA"""
        self.energy: float = 0
        """DMRG energ""" 
[docs]    def make_s1(self, one_ordm: np.ndarray) -> np.ndarray:
        """Evaluate single orbital entropy from one orbital RDM.
        Parameters
        ----------
        one_ordm : np.ndarray
            one orbital RDM
        Returns
        -------
        s1 : np.ndarray
            single orbital entropy
        Raises
        ------
        AttributeError
            if hdf5_converter has not read the output yet
        """
        if not self.hdf5_converter.L:
            raise AttributeError("Read hdf5 file before, doing measurments")
        self.s1_entropy = np.zeros((self.n_orbitals))
        for site in range(self.n_orbitals):
            s1_entropy = 0
            for alpha in range(len(one_ordm[site])):
                eigenvalue = one_ordm[site, alpha]
                if eigenvalue > 0:
                    s1_entropy = s1_entropy - eigenvalue * np.log(eigenvalue)
            self.s1_entropy[site] = s1_entropy
        return self.s1_entropy 
[docs]    def make_s2(self, two_ordm: np.ndarray) -> np.ndarray:
        """Evaluate two orbital entropy from two orbital RDM.
        Parameters
        ----------
        two_ordm : np.ndarray
            two orbital RDM
        Returns
        -------
        s2 : np.ndarray
            two orbital entropy
        Raises
        ------
        AttributeError
            if hdf5_converter has not read the output yet
        """
        if self.hdf5_converter.L is None:
            raise AttributeError("Read hdf5 file before, doing measurments")
        self.s2_entropy = np.zeros((self.n_orbitals, self.n_orbitals))
        for site_1 in range(self.n_orbitals):
            for site_2 in range(site_1 + 1, self.n_orbitals):
                sub_matrix = two_ordm[site_1][site_2][:][:]
                eigenvalue, _ = np.linalg.eig(sub_matrix)  # type: ignore[attr-defined]
                s2_entropy = 0
                for alpha in range(16):
                    if eigenvalue[alpha] > 0:
                        s2_entropy = s2_entropy - (eigenvalue[alpha] * np.log(eigenvalue[alpha]))
                self.s2_entropy[site_1, site_2] = s2_entropy.real
                self.s2_entropy[site_2, site_1] = s2_entropy.real
        return self.s2_entropy 
[docs]    def make_diagnostics(self):
        """Generate orbital RDMs from QCMaquis output file and evaluate entropies.
        Raises
        ------
        AttributeError
            if hdf5 converter has not read the output yet
        """
        if self.hdf5_converter.L is None:
            raise AttributeError("Read hdf5 file before, doing measurments")
        self.n_orbitals = self.hdf5_converter.L
        self.energy = self.hdf5_converter.energy
        self.make_s1(self.orbital_rdm_builder.make_one_ordm(self.hdf5_converter))
        self.make_s2(self.orbital_rdm_builder.make_two_ordm(self.hdf5_converter))
        self.make_mutual_information()
        # handle fiedler ordering
        sort_key = self.hdf5_converter.orbital_order.argsort()
        self.s1_entropy = self.s1_entropy[sort_key]
        self.s2_entropy = self.s2_entropy[sort_key][:, sort_key]
        self.mutual_information = self.mutual_information[sort_key][:, sort_key] 
[docs]    def read_hdf5(self, file_name: str):
        """Read the hdf5 ouput file from qcmaquis.
        Parameters
        ----------
        file_name : str
            path to the qcmaquis hdf5 output file
        """
        self.hdf5_converter.read_hdf5(file_name)