module mod_multiroots
  use, intrinsic :: iso_c_binding
  use fgsl
  use mod_unit
  implicit none
contains
  function rosenbrock_f(x, params, f) bind(c)
    type(c_ptr), value :: x, params, f
    integer(c_int) :: rosenbrock_f
!
    type(fgsl_vector) :: fx, ff
    real(fgsl_double), pointer :: par(:), xv(:), yv(:)
    integer(fgsl_int) :: status
    call fgsl_obj_c_ptr(fx, x)
    call fgsl_obj_c_ptr(ff, f)
    call c_f_pointer(params, par, (/ 2 /))
    status = fgsl_vector_align(xv, fx)
    status = fgsl_vector_align(yv, ff)
    yv(1) = par(1) * (1.0_fgsl_double - xv(1))
    yv(2) = par(2) * (xv(2) - xv(1)*xv(1))
    rosenbrock_f = fgsl_success
  end function rosenbrock_f
  function rosenbrock_df(x, params, df) bind(c)
    type(c_ptr), value :: x, params, df
    integer(c_int) :: rosenbrock_df
!
    type(fgsl_vector) :: fx
    type(fgsl_matrix) :: dff
    real(fgsl_double), pointer :: par(:), xv(:), yv(:,:)
    integer(fgsl_int) :: status
    call fgsl_obj_c_ptr(fx, x)
    call fgsl_obj_c_ptr(dff, df)
    call c_f_pointer(params, par, (/ 2 /))
    status = fgsl_vector_align(xv, fx)
    status = fgsl_matrix_align(yv, dff)
    yv(1:2,1:2) = reshape( source = (/ -par(1), 0.0_fgsl_double , &
         -2.0_fgsl_double*par(2)*xv(1), par(2)  /), &
         shape = (/ 2, 2 /))
    rosenbrock_df = fgsl_success
  end function rosenbrock_df
  function rosenbrock_fdf(x, params, f, df) bind(c)
    type(c_ptr), value :: x, params, f, df
    integer(c_int) :: rosenbrock_fdf
!
    integer(c_int) :: status
    status = rosenbrock_f(x, params, f)
    status = rosenbrock_df(x, params, df)
    rosenbrock_fdf = fgsl_success
  end function rosenbrock_fdf
end module mod_multiroots
program multiroots
  use mod_multiroots
  implicit none
  real(fgsl_double), parameter :: eps5 = 1.0d-5
  integer(fgsl_int), parameter :: itmax_root = 50
  real(fgsl_double), target :: fpar(2), xv(2)
  real(fgsl_double) :: ri, ra, xlo, xhi
  type(c_ptr) :: ptr
  type(fgsl_function) :: stdfunc
  type(fgsl_function_fdf) :: stdfunc_fdf
  type(fgsl_multiroot_fsolver) :: mroot_fslv
  type(fgsl_multiroot_fdfsolver) :: mroot_fdfslv
  type(fgsl_multiroot_function) :: mroot_f
  type(fgsl_multiroot_function_fdf) :: mroot_fdf
  type(fgsl_vector) :: xvec, fvec
  integer(fgsl_int) :: status, i
  integer(fgsl_size_t) :: nrt
  character(kind=fgsl_char,len=fgsl_strmax) :: name
  real(fgsl_double), pointer :: fptr(:), xptr(:)
!
! Test multiroot finding routines
!
  call unit_init(20)
! fsolver
  nrt = 2
  mroot_fslv = fgsl_multiroot_fsolver_alloc(fgsl_multiroot_fsolver_hybrids,nrt)
  name = fgsl_multiroot_fsolver_name(mroot_fslv)
  call unit_assert_equal('fgsl_multiroot_fsolver_name','hybrids',trim(name))
! initialize function object
  fpar(1:2) = (/1.0_fgsl_double, 10.0_fgsl_double/)
  ptr = c_loc(fpar)
  mroot_f = fgsl_multiroot_function_init(rosenbrock_f,nrt,ptr)
  xv(1:2) = (/-10.0_fgsl_double, -5.0_fgsl_double/)
  xvec = fgsl_vector_init(1.0_fgsl_double)
  status = fgsl_vector_align(xv,nrt,xvec,nrt,0_fgsl_size_t,1_fgsl_size_t)
  status = fgsl_multiroot_fsolver_set(mroot_fslv, mroot_f, xvec)
  call unit_assert_equal('fgsl_multiroot_fsolver_set:status', &
       fgsl_success,status)
  fvec = fgsl_multiroot_fsolver_f(mroot_fslv)
  status = fgsl_vector_align(fptr, fvec)
  call fgsl_vector_free(xvec)
  xvec = fgsl_multiroot_fsolver_root(mroot_fslv)
  status = fgsl_vector_align(xptr, xvec)
  call unit_assert_true('fgsl_multiroot_fsolver_alloc', &
       fgsl_well_defined(mroot_fslv), .true.)
  i = 0
  do
     i = i + 1
     status = fgsl_multiroot_fsolver_iterate(mroot_fslv);
     if (status /= fgsl_success .or. i > itmax_root) then
        exit
     end if
     status = fgsl_multiroot_test_residual(fvec, 1.0d-7)
     if (status == fgsl_success) then 
!       write(6, '(1x,i2,1x,2(1f12.5),1x,2(1f12.5))') i, xptr(1:2), fptr(1:2)
        exit
     end if
  end do
  call unit_assert_equal('fgsl_multiroot_fsolver_iterate:status', &
       fgsl_success,status)
  call unit_assert_equal_within('fgsl_multiroot_fsolver',(/1.0d0, 1.0d0/), &
       xptr,eps5)
  call fgsl_multiroot_fsolver_free(mroot_fslv)
  call fgsl_multiroot_function_free(mroot_f)
! fdfsolver
  nrt = 2
  mroot_fdfslv = fgsl_multiroot_fdfsolver_alloc( &
       fgsl_multiroot_fdfsolver_gnewton,nrt)
  name = fgsl_multiroot_fdfsolver_name(mroot_fdfslv)
  call unit_assert_equal('fgsl_multiroot_fdfsolver_name','gnewton',trim(name))
! initialize function object
  fpar(1:2) = (/1.0_fgsl_double, 10.0_fgsl_double/)
  ptr = c_loc(fpar)
  mroot_fdf = fgsl_multiroot_function_fdf_init(rosenbrock_f,rosenbrock_df, &
       rosenbrock_fdf,nrt,ptr)
!
  xv(1:2) = (/-10.0_fgsl_double, -5.0_fgsl_double/)
  xvec = fgsl_vector_init(1.0_fgsl_double)
  status = fgsl_vector_align(xv,nrt,xvec,nrt,0_fgsl_size_t,1_fgsl_size_t)
  status = fgsl_multiroot_fdfsolver_set(mroot_fdfslv, mroot_fdf, xvec)
  call unit_assert_equal('fgsl_multiroot_fdfsolver_set:status', &
       fgsl_success,status)
  fvec = fgsl_multiroot_fdfsolver_f(mroot_fdfslv)
  status = fgsl_vector_align(fptr, fvec)
  call fgsl_vector_free(xvec)
  xvec = fgsl_multiroot_fdfsolver_root(mroot_fdfslv)
  status = fgsl_vector_align(xptr, xvec)
  call unit_assert_true('fgsl_multiroot_fdfsolver_alloc', &
       fgsl_well_defined(mroot_fdfslv), .true.)
  i = 0
  do
     i = i + 1
     status = fgsl_multiroot_fdfsolver_iterate(mroot_fdfslv);
     if (status /= fgsl_success .or. i > itmax_root) then
        exit
     end if
     status = fgsl_multiroot_test_residual(fvec, 1.0E-7_fgsl_double)
     if (status == fgsl_success) then 
!       write(6, '(1x,i2,1x,2(1f12.5),1x,2(1f12.5))') i, xptr(1:2), fptr(1:2)
        exit
     end if
  end do
  call unit_assert_equal('fgsl_multiroot_fdfsolver_iterate:status', &
       fgsl_success,status)
  call unit_assert_equal_within('fgsl_multiroot_fdfsolver',(/1.0d0, 1.0d0/), &
       xptr,eps5)

  call fgsl_multiroot_fdfsolver_free(mroot_fdfslv)
  call fgsl_multiroot_function_fdf_free(mroot_fdf)


!
! Done
!
  call unit_finalize() 
end program multiroots
