! Code

subroutine diis_cc(all_err,all_t,sze,m,iter,t)

  implicit none

  BEGIN_DOC
  ! DIIS. Take the error vectors and the amplitudes of the previous
  ! iterations to compute the new amplitudes
  END_DOC
  
  ! {err_i}_{i=1}^{m_it} -> B -> c
  ! {t_i}_{i=1}^{m_it}, c, {err_i}_{i=1}^{m_it} -> t_{m_it+1}

  integer, intent(in)             :: m,iter,sze
  double precision, intent(in)    :: all_err(sze,m)
  double precision, intent(in)    :: all_t(sze,m)
  
  double precision, intent(out)   :: t(sze)
  
  double precision, allocatable   :: B(:,:), c(:), zero(:)
  integer                         :: m_iter
  integer                         :: i,j,k
  integer                         :: info
  integer, allocatable            :: ipiv(:)
  double precision                :: accu
  
  m_iter = min(m,iter)
  !print*,'m_iter',m_iter
  allocate(B(m_iter+1,m_iter+1), c(m_iter), zero(m_iter+1))
  allocate(ipiv(m+1))

  ! B(i,j) =  < err(iter-m_iter+j),err(iter-m_iter+i) > ! iter-m_iter will be zero for us
  B = 0d0
  !$OMP PARALLEL &
  !$OMP SHARED(B,m,m_iter,sze,all_err) &
  !$OMP PRIVATE(i,j,k,accu) &
  !$OMP DEFAULT(NONE)
  do j = 1, m_iter
    do i = 1, m_iter
      accu = 0d0
      !$OMP DO
      do k = 1, sze
        ! the errors of the ith iteration are in all_err(:,m+1-i)
        accu = accu + all_err(k,m+1-i) * all_err(k,m+1-j)
      enddo
      !$OMP END DO NOWAIT
      !$OMP CRITICAL
      B(i,j) = B(i,j) + accu
      !$OMP END CRITICAL
    enddo
  enddo
  !$OMP END PARALLEL
  
  do i = 1, m_iter
    B(i,m_iter+1) = -1
  enddo
  do j = 1, m_iter
    B(m_iter+1,j) = -1
  enddo
  ! Debug
  !print*,'B'
  !do i = 1, m_iter+1
  !  write(*,'(100(F10.6))') B(i,:)
  !enddo

  ! (0 0 .... 0 -1)
  zero = 0d0
  zero(m_iter+1) = -1d0

  ! Solve B.c = zero
  call dgesv(m_iter+1, 1, B, size(B,1), ipiv, zero, size(zero,1), info)
  if (info /= 0) then
    print*,'DIIS error in dgesv:', info
    call abort
  endif
  ! c corresponds to the m_iter first solutions
  c = zero(1:m_iter)
  ! Debug
  !print*,'c',c
  !print*,'all_t' 
  !do i = 1, m
  !  write(*,'(100(F10.6))') all_t(:,i)
  !enddo
  !print*,'all_err' 
  !do i = 1, m
  !  write(*,'(100(F10.6))') all_err(:,i)
  !enddo

  ! update T
  !$OMP PARALLEL &
  !$OMP SHARED(t,c,m,all_err,all_t,sze,m_iter) &
  !$OMP PRIVATE(i,j,accu) &
  !$OMP DEFAULT(NONE)
  !$OMP DO
  do i = 1, sze
    t(i) = 0d0
  enddo
  !$OMP END DO
  do i = 1, m_iter
    !$OMP DO
    do j = 1, sze
      t(j) = t(j) + c(i) * (all_t(j,m+1-i) + all_err(j,m+1-i))
    enddo
    !$OMP END DO
  enddo
  !$OMP END PARALLEL

  !print*,'new t',t

  deallocate(ipiv,B,c,zero)

end

! Update all err

subroutine update_all_err(err,all_err,sze,m,iter)

  implicit none

  BEGIN_DOC
  ! Shift all the err vectors of the previous iterations to add the new one
  ! The last err vector is placed in the last position and all the others are
  ! moved toward the first one.
  END_DOC

  integer, intent(in)             :: m, iter, sze
  double precision, intent(in)    :: err(sze)
  double precision, intent(inout) :: all_err(sze,m)
  integer                         :: i,j
  integer                         :: m_iter

  m_iter = min(m,iter)

  ! Shift
  !$OMP PARALLEL &
  !$OMP SHARED(m,all_err,err,sze) &
  !$OMP PRIVATE(i,j) &
  !$OMP DEFAULT(NONE)
  do i = 1, m-1
    !$OMP DO
    do j = 1, sze
      all_err(j,i) = all_err(j,i+1)
    enddo
    !$OMP END DO
  enddo
  
  ! Debug
  !print*,'shift err'
  !do i = 1, m
  !  print*,i, all_err(:,i)
  !enddo

  ! New
  !$OMP DO
  do i = 1, sze
    all_err(i,m) = err(i)
  enddo
  !$OMP END DO
  !$OMP END PARALLEL

  ! Debug
  !print*,'Updated err'
  !do i = 1, m
  !  print*,i, all_err(:,i)
  !enddo

end

! Update all t

subroutine update_all_t(t,all_t,sze,m,iter)

  implicit none

  BEGIN_DOC
  ! Shift all the t vectors of the previous iterations to add the new one
  ! The last t vector is placed in the last position and all the others are
  ! moved toward the first one.
  END_DOC

  integer, intent(in)             :: m, iter, sze
  double precision, intent(in)    :: t(sze)
  double precision, intent(inout) :: all_t(sze,m)
  integer                         :: i,j
  integer                         :: m_iter

  m_iter = min(m,iter)

  ! Shift
  !$OMP PARALLEL &
  !$OMP SHARED(m,all_t,t,sze) &
  !$OMP PRIVATE(i,j) &
  !$OMP DEFAULT(NONE)
  do i = 1, m-1
    !$OMP DO
    do j = 1, sze
      all_t(j,i) = all_t(j,i+1)
    enddo
    !$OMP END DO
  enddo

  ! New
  !$OMP DO
  do i = 1, sze
    all_t(i,m) = t(i)
  enddo
  !$OMP END DO
  !$OMP END PARALLEL

  ! Debug
  !print*,'Updated t'
  !do i = 1, m
  !  print*,i, all_t(:,i)
  !enddo

end

! Err1

subroutine compute_err1(nO,nV,f_o,f_v,r1,err1)

  implicit none

  BEGIN_DOC
  ! Compute the error vector for the t1
  END_DOC

  integer, intent(in)           :: nO, nV
  double precision, intent(in)  :: f_o(nO), f_v(nV), r1(nO,nV)
  
  double precision, intent(out) :: err1(nO,nV)

  integer                       :: i,a
  
  !$OMP PARALLEL &
  !$OMP SHARED(err1,r1,f_o,f_v,nO,nV,cc_level_shift) &
  !$OMP PRIVATE(i,a) &
  !$OMP DEFAULT(NONE)
  !$OMP DO
  do a = 1, nV
    do i = 1, nO
      err1(i,a) = - r1(i,a) / (f_o(i) - f_v(a) - cc_level_shift)
    enddo
  enddo
  !$OMP END DO
  !$OMP END PARALLEL

end

! Err2

subroutine compute_err2(nO,nV,f_o,f_v,r2,err2)

  implicit none

  BEGIN_DOC
  ! Compute the error vector for the t2
  END_DOC

  integer, intent(in)           :: nO, nV
  double precision, intent(in)  :: f_o(nO), f_v(nV), r2(nO,nO,nV,nV)
  
  double precision, intent(out) :: err2(nO,nO,nV,nV)

  integer                       :: i,j,a,b

  !$OMP PARALLEL &
  !$OMP SHARED(err2,r2,f_o,f_v,nO,nV,cc_level_shift) &
  !$OMP PRIVATE(i,j,a,b) &
  !$OMP DEFAULT(NONE)
  !$OMP DO collapse(3)
  do b = 1, nV
    do a = 1, nV
      do j = 1, nO
        do i = 1, nO       
          err2(i,j,a,b) = - r2(i,j,a,b) / (f_o(i) + f_o(j) - f_v(a) - f_v(b) - cc_level_shift)
        enddo
      enddo
    enddo
  enddo
  !$OMP END DO
  !$OMP END PARALLEL

end

! Update t

subroutine update_t_ccsd(nO,nV,nb_iter,f_o,f_v,r1,r2,t1,t2,all_err1,all_err2,all_t1,all_t2)

  implicit none

  integer, intent(in)             :: nO,nV,nb_iter
  double precision, intent(in)    :: f_o(nO), f_v(nV)
  double precision, intent(in)    :: r1(nO,nV), r2(nO,nO,nV,nV)
  
  double precision, intent(inout) :: t1(nO,nV), t2(nO,nO,nV,nV)
  double precision, intent(inout) :: all_err1(nO*nV, cc_diis_depth), all_err2(nO*nO*nV*nV, cc_diis_depth)
  double precision, intent(inout) :: all_t1(nO*nV, cc_diis_depth), all_t2(nO*nO*nV*nV, cc_diis_depth)

  double precision, allocatable   :: err1(:,:), err2(:,:,:,:)
  double precision, allocatable   :: tmp_err1(:), tmp_err2(:)
  double precision, allocatable   :: tmp_t1(:), tmp_t2(:)
  
  if (cc_update_method == 'diis') then

    allocate(err1(nO,nV), err2(nO,nO,nV,nV))
    allocate(tmp_err1(nO*nV), tmp_err2(nO*nO*nV*nV))
    allocate(tmp_t1(nO*nV), tmp_t2(nO*nO*nV*nV))

    ! DIIS T1, it is not always good since the t1 can be small
    ! That's why there is a call to update the t1 in the standard way
    ! T1 error tensor
    !call compute_err1(nO,nV,f_o,f_v,r1,err1)
    ! Transfo errors and parameters in vectors
    !tmp_err1 = reshape(err1,(/nO*nV/))
    !tmp_t1   = reshape(t1  ,(/nO*nV/))
    ! Add the error and parameter vectors with those of the previous iterations
    !call update_all_err(tmp_err1,all_err1,nO*nV,cc_diis_depth,nb_iter+1)
    !call update_all_t  (tmp_t1  ,all_t1  ,nO*nV,cc_diis_depth,nb_iter+1)
    ! Diis and reshape T as a tensor
    !call diis_cc(all_err1,all_t1,nO*nV,cc_diis_depth,nb_iter+1,tmp_t1)
    !t1 = reshape(tmp_t1  ,(/nO,nV/))
    call update_t1(nO,nV,f_o,f_v,r1,t1)

    ! DIIS T2
    ! T2 error tensor
    call compute_err2(nO,nV,f_o,f_v,r2,err2)
    ! Transfo errors and parameters in vectors
    tmp_err2 = reshape(err2,(/nO*nO*nV*nV/))
    tmp_t2   = reshape(t2  ,(/nO*nO*nV*nV/))
    ! Add the error and parameter vectors with those of the previous iterations
    call update_all_err(tmp_err2,all_err2,nO*nO*nV*nV,cc_diis_depth,nb_iter+1)
    call update_all_t  (tmp_t2  ,all_t2  ,nO*nO*nV*nV,cc_diis_depth,nb_iter+1)
    ! Diis and reshape T as a tensor
    call diis_cc(all_err2,all_t2,nO*nO*nV*nV,cc_diis_depth,nb_iter+1,tmp_t2)
    t2 = reshape(tmp_t2  ,(/nO,nO,nV,nV/))

    deallocate(tmp_t1,tmp_t2,tmp_err1,tmp_err2,err1,err2)

  ! Standard update as T = T - Delta
  elseif (cc_update_method == 'none') then
     
    call update_t1(nO,nV,f_o,f_v,r1,t1)
    call update_t2(nO,nV,f_o,f_v,r2,t2)
    
  else
    print*,'Unkonw cc_method_method: '//cc_update_method
  endif
  
end

! Update t v2

subroutine update_t_ccsd_diis(nO,nV,nb_iter,f_o,f_v,r1,r2,t1,t2,all_err1,all_err2,all_t1,all_t2)

  implicit none

  integer, intent(in)             :: nO,nV,nb_iter
  double precision, intent(in)    :: f_o(nO), f_v(nV)
  double precision, intent(in)    :: r1(nO,nV), r2(nO,nO,nV,nV)
  
  double precision, intent(inout) :: t1(nO,nV), t2(nO,nO,nV,nV)
  double precision, intent(inout) :: all_err1(nO*nV, cc_diis_depth), all_err2(nO*nO*nV*nV, cc_diis_depth)
  double precision, intent(inout) :: all_t1(nO*nV, cc_diis_depth), all_t2(nO*nO*nV*nV, cc_diis_depth)

  double precision, allocatable   :: all_t(:,:), all_err(:,:), tmp_t(:)
  double precision, allocatable   :: err1(:,:), err2(:,:,:,:)
  double precision, allocatable   :: tmp_err1(:), tmp_err2(:)
  double precision, allocatable   :: tmp_t1(:), tmp_t2(:)

  integer                         :: i,j
  
  ! Allocate
  allocate(all_err(nO*nV+nO*nO*nV*nV,cc_diis_depth), all_t(nO*nV+nO*nO*nV*nV,cc_diis_depth))
  allocate(tmp_t(nO*nV+nO*nO*nV*nV))
  allocate(err1(nO,nV), err2(nO,nO,nV,nV))
  allocate(tmp_err1(nO*nV), tmp_err2(nO*nO*nV*nV))
  allocate(tmp_t1(nO*nV), tmp_t2(nO*nO*nV*nV))

  ! Compute the errors and reshape them as vector
  call compute_err1(nO,nV,f_o,f_v,r1,err1)
  call compute_err2(nO,nV,f_o,f_v,r2,err2)
  tmp_err1 = reshape(err1,(/nO*nV/))
  tmp_err2 = reshape(err2,(/nO*nO*nV*nV/))
  tmp_t1   = reshape(t1  ,(/nO*nV/))
  tmp_t2   = reshape(t2  ,(/nO*nO*nV*nV/))
  
  ! Update the errors and parameters for the diis
  call update_all_err(tmp_err1,all_err1,nO*nV,cc_diis_depth,nb_iter+1)
  call update_all_t  (tmp_t1  ,all_t1  ,nO*nV,cc_diis_depth,nb_iter+1)
  call update_all_err(tmp_err2,all_err2,nO*nO*nV*nV,cc_diis_depth,nb_iter+1)
  call update_all_t  (tmp_t2  ,all_t2  ,nO*nO*nV*nV,cc_diis_depth,nb_iter+1)

  ! Gather the different parameters and errors
  !$OMP PARALLEL &
  !$OMP SHARED(nO,nV,all_err,all_err1,all_err2,cc_diis_depth,&
  !$OMP all_t,all_t1,all_t2) &
  !$OMP PRIVATE(i,j) &
  !$OMP DEFAULT(NONE)
  do j = 1, cc_diis_depth
    !$OMP DO 
    do i = 1, nO*nV
      all_err(i,j) = all_err1(i,j)
    enddo
    !$OMP END DO NOWAIT
  enddo
  do j = 1, cc_diis_depth
    !$OMP DO
    do i = 1, nO*nO*nV*nV
      all_err(i+nO*nV,j) = all_err2(i,j)
    enddo
    !$OMP END DO NOWAIT
  enddo
  do j = 1, cc_diis_depth
    !$OMP DO 
    do i = 1, nO*nV
      all_t(i,j) = all_t1(i,j)
    enddo
    !$OMP END DO NOWAIT
  enddo
  do j = 1, cc_diis_depth
    !$OMP DO 
    do i = 1, nO*nO*nV*nV
      all_t(i+nO*nV,j) = all_t2(i,j)
    enddo
    !$OMP END DO
  enddo
  !$OMP END PARALLEL
  
  ! Diis
  call diis_cc(all_err,all_t,nO*nV+nO*nO*nV*nV,cc_diis_depth,nb_iter+1,tmp_t)

  ! Split the resulting vector
  !$OMP PARALLEL &
  !$OMP SHARED(nO,nV,tmp_t,tmp_t1,tmp_t2) &
  !$OMP PRIVATE(i) &
  !$OMP DEFAULT(NONE)
  !$OMP DO
  do i = 1, nO*nV
    tmp_t1(i) = tmp_t(i)
  enddo
  !$OMP END DO NOWAIT
  !$OMP DO
  do i = 1, nO*nO*nV*nV
    tmp_t2(i) = tmp_t(i+nO*nV) 
  enddo
  !$OMP END DO
  !$OMP END PARALLEL

  ! Reshape as tensors
  t1 = reshape(tmp_t1 ,(/nO,nV/))
  t2 = reshape(tmp_t2 ,(/nO,nO,nV,nV/))

  ! Deallocate
  deallocate(tmp_t1,tmp_t2,tmp_err1,tmp_err2,err1,err2,all_t,all_err)

end

! Update t v3

subroutine update_t_ccsd_diis_v3(nO,nV,nb_iter,f_o,f_v,r1,r2,t1,t2,all_err,all_t)

  implicit none

  integer, intent(in)             :: nO,nV,nb_iter
  double precision, intent(in)    :: f_o(nO), f_v(nV)
  double precision, intent(in)    :: r1(nO,nV), r2(nO,nO,nV,nV)
  
  double precision, intent(inout) :: t1(nO*nV), t2(nO*nO*nV*nV)
  double precision, intent(inout) :: all_err(nO*nV+nO*nO*nV*nV, cc_diis_depth)
  double precision, intent(inout) :: all_t(nO*nV+nO*nO*nV*nV, cc_diis_depth)

  double precision, allocatable   :: tmp(:)

  integer                         :: i,j
  
  ! Allocate
  allocate(tmp(nO*nV+nO*nO*nV*nV))

  ! Compute the errors
  call compute_err1(nO,nV,f_o,f_v,r1,tmp(1:nO*nV))
  call compute_err2(nO,nV,f_o,f_v,r2,tmp(nO*nV+1:nO*nV+nO*nO*nV*nV))
  
  ! Update the errors and parameters for the diis
  call update_all_err(tmp,all_err,nO*nV+nO*nO*nV*nV,cc_diis_depth,nb_iter+1)

  !$OMP PARALLEL &
  !$OMP SHARED(nO,nV,tmp,t1,t2) &
  !$OMP PRIVATE(i) &
  !$OMP DEFAULT(NONE)
  !$OMP DO
  do i = 1, nO*nV
    tmp(i) = t1(i)
  enddo
  !$OMP END DO NOWAIT
  !$OMP DO
  do i = 1, nO*nO*nV*nV
    tmp(i+nO*nV) = t2(i)
  enddo
  !$OMP END DO
  !$OMP END PARALLEL
   
  call update_all_t(tmp,all_t,nO*nV+nO*nO*nV*nV,cc_diis_depth,nb_iter+1)

  ! Diis
  call diis_cc(all_err,all_t,nO*nV+nO*nO*nV*nV,cc_diis_depth,nb_iter+1,tmp)

  ! Split the resulting vector
  !$OMP PARALLEL &
  !$OMP SHARED(nO,nV,tmp,t1,t2) &
  !$OMP PRIVATE(i) &
  !$OMP DEFAULT(NONE)
  !$OMP DO
  do i = 1, nO*nV
    t1(i) = tmp(i)
  enddo
  !$OMP END DO NOWAIT
  !$OMP DO
  do i = 1, nO*nO*nV*nV
    t2(i) = tmp(i+nO*nV) 
  enddo
  !$OMP END DO
  !$OMP END PARALLEL

  ! Deallocate
  deallocate(tmp)

end
