import numpy as np
import time
import sys
import matplotlib.pyplot as plt
from hamiltonian import *

class EigRNN:
    """
    Finds the lowest eigenvalue of a symmetric matrix
    by minimizing the Rayleigh quotient with gradient
    descent
    """
    def __init__(self,A,eps=1e-4,maxiter=1000):
        """
        A - input matrix
        eps - stops iterating when eigenvalue is not changing more than eps
        maxiter - maximum number of iterations
        """
        self.A = A
        self.maxiter = maxiter
        self.eps = eps
        self.x = np.random.rand(self.A.shape[0])

    def dR(self):
        """
        Returns the gradient of the Rayleigh quotient
        """
        x = self.x
        self.Ax = self.A@x
        self.xTx = x.T@x
        self.eig = x.T@self.Ax/self.xTx
        self.grad = 2*((self.xTx)*self.Ax - (x.T@self.Ax)*x)/((self.xTx)*(self.xTx))
        return(self.grad)

    def optStep(self):
        """
        Returns the optimal step length
        """
        dR = self.grad
        x = self.x
        xTA = self.Ax.T
        a = xTA@x
        b = xTA@dR
        c = dR.T@self.A@dR
        e = self.xTx
        f = x.T@dR
        g = dR.T@dR
        return( ( (a*g - e*c ) + np.sqrt( (e*c - a*g)**2 - 4*(a*f - e*b)*(b*g - c*f) ) )/(2*(b*g - c*f)))

    def solve(self):
        """
        Returns the lowest eigenvalue of A.
        To be called after initialization.
        """
        convergence = False
        val = 0
        for i in range(self.maxiter):
            grad = self.dR()
            self.x = self.x - self.optStep()*grad
            if np.sum(np.abs(grad)) < self.eps*(np.log(self.x.shape[0])):
                convergence = True
                break

        if not convergence:
            print('WARNING: Did not converge. Try increasing maxiter or a smaller eps.')    

        return(self.eig,self.x)



if __name__ == '__main__':
    n_pairs = int(sys.argv[1])
    n_basis = int(sys.argv[2])
    print('System with {} pairs and {} basis states'.format(n_pairs,n_basis))
    delta = float(sys.argv[3])
    g = float(sys.argv[4])
    epsilon = float(sys.argv[5])
    H,Eref = hamiltonian(n_pairs,n_basis,delta,g)
    RNN = EigRNN(H,eps=epsilon)
    eigval,eigvec = RNN.solve()
    print('Energy: {}'.format(eigval))

import numpy as np
import time
import sys
import matplotlib.pyplot as plt
from hamiltonian import *

class EigRNN:
    """
    Finds the lowest eigenvalue of a symmetric matrix
    by minimizing the Rayleigh quotient with gradient
    descent
    """
    def __init__(self,A,eps=1e-4,maxiter=1000):
        """
        A - input matrix
        eps - stops iterating when eigenvalue is not changing more than eps
        maxiter - maximum number of iterations
        """
        self.A = A
        self.maxiter = maxiter
        self.eps = eps
        self.x = np.random.rand(self.A.shape[0])

    def dR(self):
        """
        Returns the gradient of the Rayleigh quotient
        """
        x = self.x
        self.Ax = self.A@x
        self.xTx = x.T@x
        self.eig = x.T@self.Ax/self.xTx
        self.grad = 2*((self.xTx)*self.Ax - (x.T@self.Ax)*x)/((self.xTx)*(self.xTx))
        return(self.grad)

    def optStep(self):
        """
        Returns the optimal step length
        """
        dR = self.grad
        x = self.x
        xTA = self.Ax.T
        a = xTA@x
        b = xTA@dR
        c = dR.T@self.A@dR
        e = self.xTx
        f = x.T@dR
        g = dR.T@dR
        return( ( (a*g - e*c ) + np.sqrt( (e*c - a*g)**2 - 4*(a*f - e*b)*(b*g - c*f) ) )/(2*(b*g - c*f)))

    def solve(self):
        """
        Returns the lowest eigenvalue of A.
        To be called after initialization.
        """
        convergence = False
        val = 0
        for i in range(self.maxiter):
            grad = self.dR()
            self.x = self.x - self.optStep()*grad
            if np.sum(np.abs(grad)) < self.eps*(np.log(self.x.shape[0])):
                convergence = True
                break

        if not convergence:
            print('WARNING: Did not converge. Try increasing maxiter or a smaller eps.')    

        return(self.eig,self.x)



if __name__ == '__main__':
    n_pairs = int(sys.argv[1])
    n_basis = int(sys.argv[2])
    print('System with {} pairs and {} basis states'.format(n_pairs,n_basis))
    delta = float(sys.argv[3])
    g = float(sys.argv[4])
    epsilon = float(sys.argv[5])
    H,Eref = hamiltonian(n_pairs,n_basis,delta,g)
    RNN = EigRNN(H,eps=epsilon)
    eigval,eigvec = RNN.solve()
    print('Energy: {}'.format(eigval))

Finds the lowest eigenvalue of a symmetric matrix     by minimizing the Rayleigh quotient with gradient     descent A - input matrix         eps - stops iterating when eigenvalue is not changing more than eps         maxiter - maximum number of iterations Returns the gradient of the Rayleigh quotient Returns the optimal step length Returns the lowest eigenvalue of A.         To be called after initialization.