! Subroutine : run_orb_opt_trust






! Subroutine to optimize the MOs using a trust region algorithm:
! - choice of the method
! - initialization
! - optimization until convergence

! The optimization use the trust region algorithm, the different parts
! are explained in the corresponding subroutine files.

! qp_edit:
! | thresh_opt_max_elem_grad |
! | optimization_max_nb_iter |
! | optimization_method      |

! Provided:
! | mo_num                         | integer          | number of MOs                  |
! | ao_num                         | integer          | number of AOs                  |
! | N_states                       | integer          | number of states               |
! | ci_energy(N_states)            | double precision | CI energies                    |
! | state_average_weight(N_states) | double precision | Weight of the different states |

! Variables:
! | m                         | integer          | number of active MOs                               |
! | tmp_n                     | integer          | m*(m-1)/2, number of MO parameters                 |
! | tmp_n2                    | integer          | m*(m-1)/2 or 1 if the hessian is diagonal          |
! | v_grad(tmp_n)             | double precision | gradient                                           |
! | H(tmp_n,tmp_n)            | double precision | hessian (2D)                                       |
! | h_f(m,m,m,m)              | double precision | hessian (4D)                                       |
! | e_val(m)                  | double precision | eigenvalues of the hessian                         |
! | w(m,m)                    | double precision | eigenvectors of the hessian                        |
! | x(m)                      | double precision | step given by the trust region                     |
! | m_x(m,m)                  | double precision | step given by the trust region after               |
! | tmp_R(m,m)                | double precision | rotation matrix for active MOs                     |
! | R(mo_num,mo_num)          | double precision | full rotation matrix                               |
! | prev_mos(ao_num,mo_num)   | double precision | previous MOs (before the rotation)                 |
! | new_mos(ao_num,mo_num)    | double precision | new MOs (after the roration)                       |
! | delta                     | double precision | radius of the trust region                         |
! | rho                       | double precision | agreement between the model and the exact function |
! | max_elem                  | double precision | maximum element in the gradient                    |
! | i                         | integer          | index                                              |
! | tmp_i,tmp_j               | integer          | indexes in the subspace containing only            |
! |                           |                  | the active MOs                                     |
! | converged                 | logical          | convergence of the algorithm                       |
! | cancel_step               | logical          | if the step must be cancelled                      |
! | nb_iter                   | integer          | number of iterations (accepted)                    |
! | nb_diag                   | integer          | number of diagonalizations of the CI matrix        |
! | nb_cancel                 | integer          | number of cancelled steps for the actual iteration |
! | nb_cancel_tot             | integer          | total number of cancel steps                       |
! | info                      | integer          | if 0 ok, else problem in the diagonalization of    |
! |                           |                  | the hessian with the Lapack routine                |
! | criterion                 | double precision | energy at a given step                             |
! | prev_criterion            | double precision | energy before the rotation                         |
! | criterion_model           | double precision | estimated energy after the rotation using          |
! |                           |                  | a Taylor series                                    |
! | must_exit                 | logical          | To exit the trust region algorithm when            |
! |                           |                  | criterion - criterion_model is too small           |
! | enforce_step_cancellation | logical          | To force the cancellation of the step if the       |
! |                           |                  | error in the rotation matrix is too large          |


subroutine run_orb_opt_trust_v2

  include 'constants.h'

  implicit none

  BEGIN_DOC
  ! Orbital optimization
  END_DOC

  ! Variables

  double precision, allocatable :: R(:,:)
  double precision, allocatable :: H(:,:),h_f(:,:,:,:)
  double precision, allocatable :: v_grad(:)
  double precision, allocatable :: prev_mos(:,:),new_mos(:,:)
  integer                       :: info
  integer                       :: n
  integer                       :: i,j,p,q,k
  double precision              :: max_elem_grad, delta, rho, norm_grad, normalization_factor
  logical                       :: cancel_step
  integer                       :: nb_iter, nb_diag, nb_cancel, nb_cancel_tot, nb_sub_iter
  double precision              :: t1, t2, t3
  double precision              :: prev_criterion, criterion, criterion_model
  logical                       :: not_converged, must_exit, enforce_step_cancellation
  integer                       :: m, tmp_n, tmp_i, tmp_j, tmp_k, tmp_n2
  integer,allocatable           :: tmp_list(:), key(:)
  double precision, allocatable :: tmp_m_x(:,:),tmp_R(:,:), tmp_x(:), W(:,:), e_val(:)

  PROVIDE mo_two_e_integrals_in_map ci_energy psi_det psi_coef

! Allocation


allocate(R(mo_num,mo_num))  ! rotation matrix
allocate(prev_mos(ao_num,mo_num), new_mos(ao_num,mo_num)) ! old and new MOs

! Definition of m and tmp_n
m = dim_list_act_orb
tmp_n = m*(m-1)/2

allocate(tmp_list(m))
allocate(tmp_R(m,m), tmp_m_x(m,m), tmp_x(tmp_n))
allocate(e_val(tmp_n),key(tmp_n),v_grad(tmp_n))

! Method
!    There are three different methods : 
!    - the "full" hessian, which uses all the elements of the hessian
!      matrix"
!    - the "diagonal" hessian, which uses only the diagonal elements of the
!      hessian
!    - without the hessian (hessian = identity matrix) 


!Display the method
 print*, 'Method :', optimization_method
if (optimization_method == 'full') then 
  print*, 'Full hessian'
  allocate(H(tmp_n,tmp_n), h_f(m,m,m,m),W(tmp_n,tmp_n))
  tmp_n2 = tmp_n
elseif (optimization_method == 'diag') then
  print*,'Diagonal hessian'
  allocate(H(tmp_n,1),W(tmp_n,1))
  tmp_n2 = 1
elseif (optimization_method == 'none') then
  print*,'No hessian'
  allocate(H(tmp_n,1),W(tmp_n,1))
  tmp_n2 = 1
else
  print*,'Unknown optimization_method, please select full, diag or none'
  call abort
endif
print*, 'Absolute value of the hessian:', absolute_eig

! Algorithm

! Here is the main algorithm of the optimization:
! - First of all we initialize some parameters and we compute the
!   criterion (the ci energy) before doing any MO rotations
! - We compute the gradient and the hessian for the active MOs
! - We diagonalize the hessian
! - We compute a step and loop to reduce the radius of the
!   trust region (and the size of the step by the way) until the step is
!   accepted 
! - We repeat the process until the convergence 
!   NB: the convergence criterion can be changed


! Loop until the convergence of the optimization
! call diagonalize_ci 

!### Initialization ###
nb_iter = 0
rho = 0.5d0
not_converged = .True.
tmp_list = list_act ! Optimization of the active MOs
nb_cancel_tot = 0

! Renormalization of the weights of the states
call state_weight_normalization

! Compute the criterion before the loop
call state_average_energy(prev_criterion)

do while (not_converged)
  print*,''
  print*,'******************'
  print*,'Iteration', nb_iter
  print*,'******************'
  print*,''

  ! Gradient
  call gradient_list_opt(tmp_n, m, tmp_list, v_grad, max_elem_grad, norm_grad)

  ! Hessian
  if (optimization_method == 'full') then
    ! Full hessian
    call hessian_list_opt(tmp_n, m, tmp_list, H, h_f)

    ! Diagonalization of the hessian 
    call diagonalization_hessian(tmp_n, H, e_val, w)

  elseif (optimization_method == 'diag') then
    ! Diagonal hessian 
    call diag_hessian_list_opt(tmp_n, m, tmp_list, H)
  else
    ! Identity matrix 
    do tmp_i = 1, tmp_n
      H(tmp_i,1) = 1d0
    enddo
  endif

  if (optimization_method /= 'full') then
    ! Sort
    do tmp_i = 1, tmp_n
      key(tmp_i) = tmp_i
      e_val(tmp_i) = H(tmp_i,1)
    enddo
    call dsort(e_val,key,tmp_n)

    ! Eigenvalues and eigenvectors
    do tmp_i = 1, tmp_n
      w(tmp_i,1) = dble(key(tmp_i))
    enddo

  endif

  ! Init before the internal loop
  cancel_step = .True. ! To enter in the loop just after 
  nb_cancel = 0
  nb_sub_iter = 0

  ! Loop to reduce the trust radius until the criterion decreases and rho >= thresh_rho
  do while (cancel_step)
    print*,''
    print*,'-----------------------------'
    print*,'Iteration:    ', nb_iter
    print*,'Sub iteration:', nb_sub_iter
    print*,'Max elem grad:', max_elem_grad
    print*,'-----------------------------'

    ! Hessian,gradient,Criterion -> x 
    call trust_region_step_w_expected_e(tmp_n,tmp_n2,H,W,e_val,v_grad,prev_criterion,rho,nb_iter,delta,criterion_model,tmp_x,must_exit) 

    if (must_exit) then
      print*,'step_in_trust_region sends: Exit'
      exit
    endif

    ! 1D tmp -> 2D tmp 
    call vec_to_mat_v2(tmp_n, m, tmp_x, tmp_m_x)

    ! Rotation matrix for the active MOs
    call rotation_matrix(tmp_m_x, m, tmp_R, m, m, info, enforce_step_cancellation)

    ! Security to ensure an unitary transformation
    if (enforce_step_cancellation) then
      print*, 'Step cancellation, too large error in the rotation matrix'
      rho = 0d0
      cycle
    endif

    ! tmp_R to R, subspace to full space
    call sub_to_full_rotation_matrix(m, tmp_list, tmp_R, R)

    ! MO rotations
    call apply_mo_rotation(R, prev_mos)   

    ! Update of the energy before the diagonalization of the hamiltonian
    call clear_mo_map
    TOUCH mo_coef psi_det psi_coef ci_energy two_e_dm_mo 
    call state_average_energy(criterion)

    ! Criterion -> step accepted or rejected 
    call trust_region_is_step_cancelled(nb_iter, prev_criterion, criterion, criterion_model, rho, cancel_step)

    ! Cancellation of the step if necessary
    if (cancel_step) then
      mo_coef = prev_mos
      call save_mos()
      nb_cancel = nb_cancel + 1
      nb_cancel_tot = nb_cancel_tot + 1
    else
      ! Diagonalization of the hamiltonian
      FREE ci_energy! To enforce the recomputation
      call diagonalize_ci
      call save_wavefunction_unsorted

      ! Energy obtained after the diagonalization of the CI matrix
      call state_average_energy(prev_criterion)
    endif

    nb_sub_iter = nb_sub_iter + 1
  enddo
  call save_mos() !### depend of the time for 1 iteration

  ! To exit the external loop if must_exit = .True.
  if (must_exit) then
    exit
  endif 

  ! Step accepted, nb iteration + 1
  nb_iter = nb_iter + 1

  ! External loop exit conditions
  if (DABS(max_elem_grad) < thresh_opt_max_elem_grad) then
    print*,'Converged: DABS(max_elem_grad) < thresh_opt_max_elem_grad'
    not_converged = .False.
  endif
  if (nb_iter >= optimization_max_nb_iter) then
    print*,'Not converged: nb_iter >= optimization_max_nb_iter'
    not_converged = .False. 
  endif

  if (.not. not_converged) then
    print*,'#############################'
    print*,'   End of the optimization'
    print*,'#############################'
  endif
enddo

! Deallocation, end


deallocate(v_grad,H,R,W,e_val)
  deallocate(prev_mos,new_mos)
  if (optimization_method == 'full') then
    deallocate(h_f)
  endif

end
