!=======================================================================
!
!  sderk90 module
!
!  Version 2.1
!
!  Main interface subroutine:
!
!     sderk (t, tout)
!                      
!  This subroutine generates strong solutions to Ito stochastic 
!  differential equations of the form
!    
!     dy = a(y, t) dt + b(y, t) dW
!
!  where y is a vector, a and b are vector-valued functions, and W is
!  a scalar Wiener process.  This routine implements Runge-Kutta
!  methods of order 1 and 1.5.
!  This routine also constructs a Brownian tree to realize the same
!  Brownian path, independent of the order of the method or the 
!  internal stepsize, as long as the routine is called with the same
!  output times.
!          
!  Random numbers are generated using an internal, portable generator,
!  so calling other generators between integration steps will not
!  interfere with the integrator.
!
!
!  References:
!    
!  Pamela Marion Burrage, "Runge-Kutta Methods for Stochastic
!    Differential Equations," Ph.D. thesis, University of Queensland
!    (1999).
!    
!  P. E. Kloeden and E. Platen, "Higher-Order Implicit Strong Numerical
!    Schemes for Stochastic Differential Equations," J. Stat. Phys. 66,
!    283 (1992).
!    
!  Peter E. Kloeden and Eckhard Platen, _Numerical Solution of
!    Stochastic Differential Equations_ (Springer, Berlin, 1992).
!    
!  J. G. Gaines and T. J. Lyons, "Variable Step Size Control in the
!    Numerical Solution of Stochastic Differential Equations,"
!    SIAM J. Appl. Math. 57, 1455 (1997).
!
!                      
!  Interface:
!
!  This module defines:
!
!    sderk_int_type -> (integer parameter) the integer type used
!                  by the internal random-number generator; this
!                  parameter is needed to declare some integer
!                  scalars and arrays of type integer(sderk_int_type)
!                  to inteface with the internal rng (e.g., to seed
!                  it or to save/restore its state).
!
!  Most of the communication with this integrator is through a
!    user-defined module called "sderk_support". A sample module
!    is included as a separate file with this software.  This module
!    should define several parameters, variables, and functions:
!                    
!    sderk_prec -> (default integer parameter) the real/complex kind
!                  for the precision type to use for all computations,
!                  e.g., this should be the output of selected_real_kind.
!
!    sderk_mf  -> (default integer) method flag to set which integration
!                 method to use (see below for available options).
!                      
!    sderk_rand_mf -> (default integer) method flag to set which
!                 method for generating random numbers to use.  The
!                 allowed values are documented in the random_pl
!                 (version 2.0) package by the same author, but 
!                 a reasonable choice is 301 (or 204, to switch
!                 methods to see if the random-number generator is
!                 causing funny results).
!
!    sderk_tol -> (real(sderk_prec)) tolerance for iterative solution,
!                 this is needed for implicit methods and ignored
!                 otherwise (but still needs to be defined). A  
!                 sensible number is 1e-10 for double precision.
!                    
!    sderk_y -> (real/complex array) the solution array, where
!               the type/dimension are set according to the
!               user's requirements.  Because the initial
!               value is set by the user, the user must control
!               the allocation for this array.  May be declared
!               as a pointer to a more sensible array name.
!    
!    sderk_a      ->  \
!    sderk_b      ->   |  Always allocate these
!    sderk_c      ->   |
!    sderk_ybar   ->  /
!    sderk_aybarp ->  \
!    sderk_aybarm ->   |
!    sderk_bybarp ->   |
!    sderk_bybarm ->   | For any order 1.5 stochastic method
!    sderk_aphip  ->   | Also any order 4 deterministic (phi's only)
!    sderk_aphim  ->   |  
!    sderk_bphip  ->   |
!    sderk_bphim  ->  /
!                   auxilliary storage arrays, of the same type and
!                   dimensions as sderk_solution.  These should be   
!                   declared and allocated by the user.  It is easiest
!                   to just define and allocate all of these, but in
!                   some cases, as marked above, not all of these are
!                   actually needed; the unneeded ones can be declared
!                   "allocatable" and not actually allocated, if the
!                   user is memory-starved.
!    sderk_func_a -> subroutine of the form
!                    subroutine sderk_func_a(t, y, ydot)
!                      real(sderk_prec), intent(in) :: t
!                      <solution type>, intent(in)  :: y
!                      <solution type>, intent(out) :: ydot
!                    which returns the derivative array a(y, t). Here,
!                    <solution type> is the array type/dimension
!                    defined above for the solution arrays, e.g., this
!                    could be "real(sderk_prec), dimension(100,3)".
!
!    sderk_func_b -> subroutine of the form
!                    subroutine sderk_func_b(t, y, ydot)
!                      real(sderk_prec), intent(in) :: t
!                      <solution type>, intent(in)  :: y
!                      <solution type>, intent(out) :: ydot
!                    which returns the derivative array b(y, t).
!
!
!  Calling interface:
!
!  The integrator is called to advance in time via
!      call sderk (t, tout)
!    where
!
!    t -> (real(sderk_prec)) initial value of the time parameter;
!       on successful integration, this is updated to tout
!
!    tout -> (real(sderk_prec)) the desired output time
!
!
!  Before calling this routine, the integrator must be initialized by
!    a call of the form
!      call init_sderk(nsteps, seed)
!    where
!
!    nsteps -> (default integer) number of steps to take from t to tout;
!            must be a power of 2 (1, 2, 4, 8, etc.).  Two runs with
!            the same t and tout but different values of nsteps will
!            be realizations of the same Brownian path.
!
!    seed -> (optional integer(sderk_int_type)) seed to initialize
!            random number generators; a default seed is used if
!            this seed is omitted.
!
!    Note that if this routine is called again with other values of
!    nsteps (i.e., nsteps is not constant over a long integration),
!    the results of several runs with different nsteps values
!    may not be on the same Brownian path.  The only way to
!    ensure that a consistent Brownian path is used is to keep
!    nsteps fixed over a given run.  In the future, an adaptive
!    driver will use this facility to modify nsteps, but saving
!    the integrator state and resynchronizing the various
!    random-number generators to enforce consistent Brownian paths.
!
!
!  Numerical methods:
!  
!      This package supports a number of integration methods, set by
!        the method flag mf, a two-digit integer.
!        The first digit is a flag (1 or 2), with 1 indicating
!        explicit and 2 implicit evolution of the deterministic
!        part of the equation (i.e. a(y, t)).
!        Solution of the implicit equations is accomplished by
!        simple functional iteration; setting this digit to
!        3 instead of two causes the integrator to report the
!        number of iterations needed to converge.
!        The second digit is the order of the Runge-Kutta solver
!        Legal values are
!          11 (first-order explicit deterministic,
!                first-order stochastic)
!          12 (fourth-order explicit deterministic,
!                first-order stochastic)
!          13 (explicit order 1.5 RK method in Kloeden/Platen)
!          21 (second-order implicit deterministic,
!                first-order stochastic)
!          22 (fourth-order implicit deterministic,
!                first-order stochastic)
!          23 (implicit order 1.5 RK method in Kloeden/Platen; order
!                2 deterministic step)
!          24 (modification of 23 to have 4th-order implicit
!                deterministic step, but has possibly worse
!                than order 1.5 convergence)
!          25 (modification of 23 to have 6th-order implicit
!                deterministic step, also with possibly worse
!                than order 1.5 convergence)
! 
!                
!  Random numbers:
!          
!  This package uses an embedded, internal random-number generator, 
!    which should not be disturbed during normal operation.  For
!    checkpointing and restarting between calls, three subroutines
!    for accessing the generator states are available:
!
!    subroutine sderk_get_rand_state_size(dim1, dim2) ->
!             gets dimensions of the istate array needed for the
!             call to sderk_get_rand_state
!
!    subroutine sderk_get_rand_state(istate) ->
!             gets the state of the rng, putting the result in istate;
!             istate is an integer(sderk_int_type), allocatable array,
!             which is allocated to the proper size automatically.
!
!    subroutine sderk_set_rand_state(istate) ->
!             initializes and resets the generator to the state when
!             istate was returned by a call to sderk_get_rand_state.
!
!    Thus, the procedure to get the state of the rng would be:
!      integer :: dim1, dim2
!      integer(sderk_int_kind), dimension(:,:), allocatable :: istate
!      call sderk_get_rand_state_size(dim1, dim2)
!      allocate( istate(dim1, dim2) )
!      call sderk_get_rand_state(istate)
!
!    and a later call sderk_set_rand_state(istate) would restore
!    the generator to that state.
!
!
!  Also, there are two interface routines for retrieving the random 
!    numbers used for the last call to sderk:
!    
!    sderk_get_I1() -> function returning a vector of real(sderk_prec)
!                      numbers and of length nsteps of the Wiener  
!                      increments I1 (i.e., dW) used for the internal
!                      integration steps.
!    
!    sderk_get_I10() -> similar function, but returning the I10
!                       integrals instead.
!
!=======================================================================


module sderk90

  use sderk_support, only :                                            &
           sderk_func_a, sderk_func_b,                                 &
           sderk_prec, sderk_tol, sderk_mf, sderk_y,                   &
           sderk_a, sderk_b, sderk_c, sderk_ybar,                      &
           sderk_aybarp, sderk_aybarm, sderk_bybarp, sderk_bybarm,     &
           sderk_aphip, sderk_aphim, sderk_bphip, sderk_bphim,         &
           sderk_rand_mf


  private
  public :: sderk_int_kind, sderk_get_rand_state_size
  public :: sderk_get_rand_state, sderk_set_rand_state
  public :: init_sderk, sderk, sderk_get_I1, sderk_get_I10


  !!! local storage for integrator stuff

  ! aliases
  integer, parameter  :: sderk_int_kind = selected_int_kind(9)
  integer, parameter  :: rpk = sderk_int_kind     
  integer, parameter  :: wp = sderk_prec
  integer, parameter  :: mf = sderk_mf

  ! maximum number of iterations before convergence fails    
  integer, parameter :: maxit = 50
  real(wp)           :: tol

  ! storage for random deviates
  real(wp), dimension(:),   allocatable :: I1, I10
  real(wp), dimension(:,:), allocatable :: I1raw
  real(wp), dimension(2) :: z

  ! Brownian tree stuff
  integer :: nsteps, nsteps_last = -1, nsteps_most = - 1, treedepth

  !!! length of Brownian tree base, also the number of random numbers
  !!!   used to calculate the Ito integral I_10.
  !!!   Must be at least 2.
  integer, parameter :: tree_base = 20

  !!! select Brownian tree refinement method
  !!!   choices are levyarea for Levy area method (just refine I_1's)
  !!!   or splitboth for transformation method by Mauthner/Burrage/Burrage
  integer, parameter :: levyarea = 1, splitboth = 2
  integer, parameter :: refinement_method = splitboth

  !!! local storage for embedded random-number generator

  ! default seeds
  integer(rpk), parameter :: dseed1 = 310952
  integer(rpk), parameter :: dseed2 = 314159
  integer(rpk), parameter :: dseed3 = 4357

  ! parse out digits of sderk_rand_mf
  integer, parameter :: rand_pl_mf = sderk_rand_mf
  integer, parameter :: rand_pl_mf_pri = sderk_rand_mf/100
  integer, parameter :: rand_pl_mf_mix = sderk_rand_mf/10 - rand_pl_mf_pri*10
  integer, parameter :: rand_pl_mf_scr = sderk_rand_mf - (sderk_rand_mf/10)*10
  
  ! temporary storage for fibonacci generator
  integer(rpk), dimension(1009) :: aa
  
  ! define storage size parameters
  integer, parameter :: scramble_stor_sz = 64
  integer, parameter :: fib_stor_sz = 100 
  integer, parameter :: lecuyer_stor_sz = 6
  integer, parameter :: mersenne_stor_sz = 624
  integer, parameter :: rand_pl_state_sz = &
                scramble_stor_sz+fib_stor_sz+lecuyer_stor_sz+mersenne_stor_sz+5
  
  ! define storage for multiple generators
  integer(rpk), allocatable, dimension(:,:), target :: lecuyer_stor_m
  integer(rpk), allocatable, dimension(:,:), target :: scramble_stor_m
  integer(rpk), allocatable, dimension(:,:), target :: fib_stor_m
  integer(rpk), allocatable, dimension(:,:), target :: mersenne_stor_m
  integer(rpk), allocatable, dimension(:), target :: initialized_m
  integer(rpk), allocatable, dimension(:), target :: fib_ctr_m, bd_lastout_m, &
                                                     mersenne_ptr_m

  ! define storage pointers
  integer(rpk), dimension(:), pointer :: lecuyer_stor
  integer(rpk), dimension(:), pointer :: scramble_stor
  integer(rpk), dimension(:), pointer :: fib_stor
  integer(rpk), dimension(:), pointer :: mersenne_stor
  integer(rpk), pointer :: initialized
  integer(rpk), pointer :: fib_ctr, bd_lastout, mersenne_ptr

  ! to keep track of where pointers are associated
  integer :: curr_ptr = -1


contains


!-----------------------------------------------------------------------
!
!  main subroutine: sderk
!
!-----------------------------------------------------------------------

subroutine sderk(t, tout)

  implicit none

  real(wp), intent(inout) :: t
  real(wp), intent(in)    :: tout

  integer      :: j, k, p, q
  real(wp)     :: bigstep, h
  real(wp), dimension(nsteps) :: A10, sumarr

  real(wp), dimension(tree_base) :: tmp1, tmp2
  real(wp) :: btmp1, btmp2, btmp3, btmp4


  !!! Make sure initializations already happened
  if ( .not. allocated( initialized_m ) ) then
    write(0,*) 'Error (SDERK): integrator called before initialized'
    stop
  end if

  if ( refinement_method .eq. levyarea ) then

  !!! Precompute time steps
  bigstep = tout - t
  h = bigstep / real(tree_base, kind=wp)

  !!! Generate base layer of random numbers; store in part of rwork
  I1raw(1,:) = nrand_pl_vec_multi(1, tree_base) * sqrt(h)

  !!! Descend Brownian tree, only retaining finest layer     
  do j = 1, treedepth

    ! Rearrange values to create space for new deviates
    do k = 2**(j-1), 2, -1
      I1raw(2*k-1, :) = I1raw(k, :)
    end do

    ! get new deviates
    h = bigstep/real(tree_base*2**j, kind=wp)
    if ( j .eq. 1 ) then
      I1raw(2, :) = nrand_pl_vec_multi(2, tree_base)
    else
      do k = 1, tree_base
        I1raw(2:(2**j):2, k) = nrand_pl_vec_multi(1+j, 2**(j-1))
      end do
    end if

    ! refine I1 onto smaller grid
    do k = 1, 2**j, 2
      tmp1 = I1raw(k, :)   * 0.5_wp
      tmp2 = I1raw(k+1, :) * sqrt(0.5_wp * h)
      I1raw(k, :)   = tmp1 + tmp2
      I1raw(k+1, :) = tmp1 - tmp2
    end do

  end do

  !!! Calculate I1's and I10's via Levy areas A10
  h = bigstep/nsteps
  I1raw = transpose(reshape(I1raw, (/ tree_base, nsteps /)))
  I1 = sum(I1raw, dim=2)

  sumarr = 0.0_wp
  A10 = 0.0_wp
  do p = 2, tree_base
    sumarr = sumarr + I1raw(:, p-1)
    A10 = A10 + sumarr
  end do
  do q = 2, tree_base
    A10 = A10 - I1raw(:, q) * real(q-1, kind=wp)
  end do
  A10 = A10 * h/real(tree_base, kind=wp) * 0.5_wp
  I10 = A10 + 0.5_wp * I1 * h

  else if ( refinement_method .eq. splitboth ) then

  !!! Precompute time steps
  bigstep = tout - t
  h = bigstep
                
  !!! Generate base layer of random numbers; store in part of rwork
  z = nrand_pl_vec_multi(1, 2)
  I1(1) = z(1) * sqrt(h)
  I10(1) = h*sqrt(h)/2 * (z(1) + z(2)/sqrt(3.0_wp))
  
  !!! Descend Brownian tree, only retaining finest layer     
  do j = 1, treedepth
  
    ! Rearrange values to create space for new deviates
    do k = 2**(j-1), 2, -1
      I1(2*k-1) = I1(k)
      I10(2*k-1) = I10(k)
    end do
  
    ! refine I1 onto smaller grid
    h = bigstep / 2**(j-1) ! h is coarser step here
    do k = 1, 2**j, 2
      z = nrand_pl_vec_multi(1+j, 2)
      btmp1 = -sqrt(h)*z(2)/4 - I1(k)/4 + 3*I10(k)/(2*h)
      btmp2 = h*sqrt(h)*z(1)/(8*sqrt(3.0_wp)) - h*I1(k)/8 + I10(k)/2
      btmp3 = sqrt(h)*z(2)/4 + 5*I1(k)/4 - 3*I10(k)/(2*h)
      btmp4 = -h*sqrt(h)*z(1)/(8*sqrt(3.0_wp)) &
               + h*sqrt(h)*z(2)/8 + h*I1(k)/4 -I10(k)/4
      I1(k) = btmp1
      I1(k+1) = btmp3
      I10(k) = btmp2
      I10(k+1) = btmp4
    end do

  end do

  h = bigstep/nsteps

  end if


  !!! Set iteration tolerance, if needed
  if ( mf/10 .eq. 2 .or. mf/10 .eq. 3 ) then
    if ( sderk_tol .lt. 100_wp * epsilon(1.0_wp) ) then
      tol = 100_wp * epsilon(1.0_wp)
      write(0,*) 'Warning (SDERK): resetting tolerance to ', tol
    else
      tol = sderk_tol
    end if
  end if


  !!! Now do integration by appropriate method
  if ( mf .eq. 11 .or. mf .eq. 12 ) then
    do k = 1, nsteps
      call rk1_ito_onestep(t, h, I1(k))
      t = t + h
    end do
  else if ( mf .eq. 13 ) then
    do k = 1, nsteps
      call rk15_ito_onestep(t, h, I1(k), I10(k))
      t = t + h
    end do
  else if ( mf .eq. 21 .or. mf .eq. 31 .or. mf .eq. 22 .or. mf .eq. 32 ) then
    do k = 1, nsteps
      call rk1_ito_imp_onestep(t, h, I1(k))
      t = t + h
    end do
  else if ( mf .eq. 23 .or. mf .eq. 33 .or. mf .eq. 24 .or. mf .eq. 34 .or. &
            mf .eq. 25 .or. mf .eq. 35 ) then
    do k = 1, nsteps
      call rk15_ito_imp_onestep(t, h, I1(k), I10(k))
      t = t + h
    end do
  else
    write(0,*) 'Error (SDERK): invalid mf'
    stop
  end if

  return

end subroutine sderk


!-----------------------------------------------------------------------
!
!  subroutine init_sderk(set_nsteps, optional seed)
!
!  Initializes random-number generators for sderk integrator, 
!  so that the integrator will use a number of substeps equal to
!  set_nsteps per call to sderk.  Calling this function again during
!  an evolution will reset the random number generators if
!  nsteps is larger than before.  The optional argument seed
!  is used to initialize the random number generators, with distinct
!  seeds producing distinct random sequences.  A default seed is 
!  used if this argument is omitted.
!
!  The integer argument set_nsteps must be a power of 2.
!  The integer(sderk_int_kind) argument seed must be in the range
!    [0, 2**30 - 3].
!
!-----------------------------------------------------------------------

subroutine init_sderk(set_nsteps, seed)

  implicit none

  integer, intent(in)                :: set_nsteps
  integer(rpk), optional, intent(in) :: seed

  nsteps = set_nsteps

  !!! Calculate depth of Brownian tree (treedepth = 0 means just base layer)
  treedepth = int( log(real(nsteps, kind=wp)) / log(2.0_wp) + 0.5_wp )
  if ( 2**treedepth .ne. nsteps ) then
    write(0,*) 'Error (INIT_SDERK): nsteps must be power of 2'
    stop
  end if

  !!! Manage storage for random deviates
  if ( allocated(I1) .and. ( nsteps .ne. nsteps_last ) ) then
    deallocate(I1, I10, I1raw)
  else if ( .not. allocated(I1) ) then
    allocate(I1(nsteps), I10(nsteps), I1raw(nsteps, tree_base))
    nsteps_last = nsteps
  end if

  !!! reallocate random number generators, if more are needed
  if ( nsteps .gt. nsteps_most ) then
    if ( present(seed) ) then
      call allocate_rand_pl_multi(treedepth+1, seed1=seed)
    else
      call allocate_rand_pl_multi(treedepth+1)
    end if
  end if

  nsteps_most = max(nsteps_most, nsteps)

end subroutine init_sderk


!-----------------------------------------------------------------------
!
!  subroutine sderk_get_rand_state_size(dim1, dim2)
!
!  Gets the dimensions of an array needed for a call to
!  sderk_get_rand_state.
!
!-----------------------------------------------------------------------

subroutine sderk_get_rand_state_size(dim1, dim2)
  implicit none
  integer, intent(out) :: dim1, dim2
  if ( .not. allocated( initialized_m ) ) then
    write(0,*) 'Error (SDERK_GET_RAND_STATE_SIZE): generators not yet ', &
               'initialized'
    stop
  end if
  dim1 = rand_pl_state_sz
  dim2 = size(initialized_m)
end subroutine sderk_get_rand_state_size


!-----------------------------------------------------------------------
!
!  subroutine sderk_get_rand_state(istate)
!
!  Gets the state of the random number generators, stored in the
!  allocatable integer(sderk_int_type) array istate.  This array
!  will be allocated by this subroutine.
!
!-----------------------------------------------------------------------

subroutine sderk_get_rand_state(istate)

  implicit none

  integer(rpk), dimension(:,:), intent(out) :: istate
  integer :: j, dim1, dim2

  call sderk_get_rand_state_size(dim1, dim2)
  if ( size(istate, 1) .ne. dim1 ) then
    write(0,*) 'Error (SDERK_GET_RAND_STATE): istate has wrong first dim'
    stop
  end if
  if ( size(istate, 2) .ne. dim2 ) then
    write(0,*) 'Error (SDERK_GET_RAND_STATE): istate has wrong second dim'
    stop
  end if

  do j = 1, size(istate, 2)
    call get_rand_pl_state_multi(j, istate(:,j))
  end do

end subroutine sderk_get_rand_state


!-----------------------------------------------------------------------
!
!  subroutine sderk_set_rand_state(istate)
!
!  Resets the state of the random number generators, stored in the
!  integer(sderk_int_type) array istate, which was retrieved from
!  a previous call to sderk_get_rand_state.  This subroutine
!  will also initialize the random number generators, so a prior
!  call to init_sderk is unnecessary.
!
!-----------------------------------------------------------------------

subroutine sderk_set_rand_state(istate)

  implicit none

  integer(rpk), dimension(:,:), intent(in) :: istate
  integer :: j

  if ( size(istate, 1) .ne. rand_pl_state_sz ) then
    write(0,*) 'Error (SDERK_SET_RAND_STATE): istate has wrong first dim'
    stop
  end if

  call init_sderk( 2**( size(istate, 2) - 1 ) )

  do j = 1, size(istate, 2)
    call set_rand_pl_state_multi(j, istate(:,j))
  end do

end subroutine sderk_set_rand_state


!-----------------------------------------------------------------------
!
!  subroutine rk1_ito_onestep(t, h, dW)
!
!  Calculates one order 1.0 stochastic Runge-Kutta iteration for an
!  Ito SDE. The parameter t is the time at the
!  beginning of the interval of length h.
!  dW is the Wiener increment during this interval.
!  This subroutine requires the user-defined arrays sderk_a, sderk_b,
!  and sderk_c.
!
!  The method here is that of Kloeden and Platen.
!
!-----------------------------------------------------------------------

subroutine rk1_ito_onestep(t, h, dW)

  implicit none     

  real(wp), intent(in) :: t, h, dW


  !!! Compute stochastic increment and store in sderk_c
  call sderk_func_a(t, sderk_y, sderk_a)
  call sderk_func_b(t, sderk_y, sderk_b)
  sderk_ybar = sderk_y + sderk_a * h + sderk_b * sqrt(h)

  call sderk_func_b(t, sderk_ybar, sderk_c)
  sderk_c = sderk_b * dW + (sderk_c - sderk_b) * (dW*dW-h)/(2.0_wp*sqrt(h))


  !!! Compute deterministic increment
  if ( mf .eq. 11 ) then
    sderk_y = sderk_y + sderk_a * h
  else if ( mf .eq. 12 ) then
    call rk4_onestep(t, h)
  end if

  sderk_y = sderk_y + sderk_c

  return

end subroutine rk1_ito_onestep


!-----------------------------------------------------------------------
!
!  subroutine rk1_ito_imp_onestep(t, h, dW)
!
!  Calculates one implicit order 1.0 stochastic Runge-Kutta iteration
!  for an Ito SDE. The parameter t is the time at the
!  beginning of the interval of length h.
!  dW is the Wiener increment during this interval.
!  This subroutine requires the user-defined storage arrays sderk_a,
!  sderk_b, sderk_c, sderk_ybar, sderk_aphip, sderk_aphim,
!  sderk_bphip, and sderk_bphim.
!
!  The method here is that of Kloeden and Platen, with degree of
!  implicitness 1/2.
!
!-----------------------------------------------------------------------

subroutine rk1_ito_imp_onestep(t, h, dW)

  implicit none

  real(wp), intent(in) :: t, h, dW


  !!! Compute stochastic increment and store in sderk_c
  call sderk_func_a(t, sderk_y, sderk_a)       
  call sderk_func_b(t, sderk_y, sderk_b)
  sderk_ybar = sderk_y + sderk_a * h + sderk_b * sqrt(h)

  call sderk_func_b(t, sderk_ybar, sderk_c)
  sderk_c = sderk_b * dW + (sderk_c - sderk_b) * (dW*dW-h)/(2.0_wp*sqrt(h))


  if ( (mf - 10*(mf/10)) .eq. 1 ) then
    sderk_c = sderk_c + 0.5_wp * sderk_a * h
    call ord_2_iterator(t, h)
  else
    call ord_4_iterator(t, h)
  end if

  return 

end subroutine rk1_ito_imp_onestep


!-----------------------------------------------------------------------
!
!  subroutine rk15_ito_onestep(t, h, dW, I10)
!
!  Calculates one order 1.5 stochastic Runge-Kutta iteration for an
!  Ito SDE. The parameter t is the time at the
!  beginning of the interval of length h.  dW and I10 are the
!  respective I1 and I10 integral values for this interval.
!  This routine requires the user-defined storage arrays
!  sderk_a, sderk_b, sderk_c, sderk_aybarp, sderk_aybarm,
!  sderk_bybarp, sderk_bybarm, sderk_aphip, sderk_aphim,
!  sderk_bphip, sderk_bphim, and sderk_ybar.
!
!  The method here is from Kloeden/Platen.
!
!-----------------------------------------------------------------------

subroutine rk15_ito_onestep(t, h, dW, I10)

  implicit none

  real(wp), intent(in) :: t, h, dW, I10


  ! compute a(y), b(y)
  call sderk_func_a(t, sderk_y, sderk_a)
  call sderk_func_b(t, sderk_y, sderk_b)

  ! compute the ybar(+) supporting value, and eval functions
  sderk_ybar = sderk_y + sderk_a * h + sderk_b * sqrt(h)
  call sderk_func_a(t + h, sderk_ybar, sderk_aybarp)
  call sderk_func_b(t + h, sderk_ybar, sderk_bybarp)

  ! compute the ybar(-) supporting value (store as c), and eval functions
  sderk_c = sderk_y + sderk_a * h - sderk_b * sqrt(h)
  call sderk_func_a(t - h, sderk_c, sderk_aybarm)
  call sderk_func_b(t - h, sderk_c, sderk_bybarm)

  ! compute the phi(+) supporting value (store as c), and eval functions
  sderk_c = sderk_ybar + sderk_bybarp * sqrt(h)
  call sderk_func_a(t + h, sderk_c, sderk_aphip)
  call sderk_func_b(t + h, sderk_c, sderk_bphip)

  ! compute the phi(-) supporting value (store as c), and eval functions
  sderk_c = sderk_ybar - sderk_bybarp * sqrt(h)
  call sderk_func_a(t + h, sderk_c, sderk_aphim)
  call sderk_func_b(t + h, sderk_c, sderk_bphim)

  ! compute the final result
  sderk_y = sderk_y + sderk_b * dW                                     &
           + (sderk_aybarp-sderk_aybarm)*I10/(2.0_wp*sqrt(h))          &
           + (sderk_aybarp+2.0_wp*sderk_a+sderk_aybarm)*h/4.0_wp       &
           + (sderk_bybarp-sderk_bybarm)*(dW*dW-h)/(4.0_wp*sqrt(h))    &
           + (sderk_bybarp-2.0_wp*sderk_b+sderk_bybarm)                &
             * (dW*h-I10)/(2.0_wp*h)                                   &
           + (sderk_bphip-sderk_bphim-sderk_bybarp+sderk_bybarm)       &
             * (dW*dW/3.0_wp-h)*dW/(4.0_wp*h)

  return

end subroutine rk15_ito_onestep


!-----------------------------------------------------------------------
!
!  subroutine rk15_ito_imp_onestep(t, h, dW, I10)
!
!  Calculates one (implicit) order 1.5 stochastic Runge-Kutta iteration
!  for an Ito SDE. The parameter t is the time at the
!  beginning of the interval of length h.  dW and I10 are the
!  respective I1 and I10 integral values for this interval.
!  This routine requires the user-defined arrays
!  sderk_a, sderk_b, sderk_c, sderk_aybarp, sderk_aybarm,
!  sderk_bybarp, sderk_bybarm, sderk_aphip, sderk_aphim,   
!  sderk_bphip, sderk_bphim, sderk_ybar.
!
!  The method here is from Kloeden/Platen, with degree of implicitness    
!  1/2.
!
!-----------------------------------------------------------------------

subroutine rk15_ito_imp_onestep(t, h, dW, I10)

  implicit none

  real(wp), intent(in) :: t, h, dW, I10


  call sderk_func_a(t, sderk_y, sderk_a)
  call sderk_func_b(t, sderk_y, sderk_b)

  ! compute the ybar(+) supporting value, and eval functions
  sderk_ybar = sderk_y + sderk_a * h + sderk_b * sqrt(h)
  call sderk_func_a(t + h, sderk_ybar, sderk_aybarp)
  call sderk_func_b(t + h, sderk_ybar, sderk_bybarp)

  ! compute the ybar(-) supporting value (store as c), and eval functions
  sderk_c = sderk_y + sderk_a * h - sderk_b * sqrt(h)
  call sderk_func_a(t - h, sderk_c, sderk_aybarm)
  call sderk_func_b(t - h, sderk_c, sderk_bybarm)

  ! compute the phi(+) supporting value (store as c), and eval functions
  sderk_c = sderk_ybar + sderk_bybarp * sqrt(h)
  call sderk_func_a(t + h, sderk_c, sderk_aphip)         
  call sderk_func_b(t + h, sderk_c, sderk_bphip)

  ! compute the phi(-) supporting value (store as c), and eval functions
  sderk_c = sderk_ybar - sderk_bybarp * sqrt(h)
  call sderk_func_a(t + h, sderk_c, sderk_aphim)
  call sderk_func_b(t + h, sderk_c, sderk_bphim)

  ! compute everything but the implicit part, store in c
  sderk_c = sderk_b * dW                                             &
           + (sderk_aybarp-sderk_aybarm)*(I10-0.5_wp*dW*h)           &
             / (2.0_wp*sqrt(h))                                      &
           + (sderk_bybarp-sderk_bybarm)*(dW*dW-h)/(4.0_wp*sqrt(h))  &
           + (sderk_bybarp-2.0_wp*sderk_b+sderk_bybarm)              &
             * (dW*h-I10)/(2.0_wp*h)                                 &
           + (sderk_bphip-sderk_bphim-sderk_bybarp+sderk_bybarm)     &
             * (dW*dW/3.0_wp-h)*dW/(4.0_wp*h)


  if ( (mf - 10*(mf/10)) .eq. 3 ) then
    sderk_c = sderk_c + 0.5_wp * sderk_a * h     
    call ord_2_iterator(t, h)
  else if ( (mf - 10*(mf/10)) .eq. 4 ) then
    call ord_4_iterator(t, h)
  else if ( (mf - 10*(mf/10)) .eq. 5 ) then
    call ord_6_iterator(t, h)
  end if

  return

end subroutine rk15_ito_imp_onestep


!----------------------------------------------------------------------
!
!  subroutine rk4_onestep(t, h)
!
!  Calculates one 4th-order deterministic Runge-Kutta iteration.
!  The parameter t is the time at the beginning of the interval of
!  length h. This subroutine requires the user-defined arrays
!  sderk_aphip, sderk_aphim, sderk_bphip, sderk_bphim, and sderk_ybar.
!
!-----------------------------------------------------------------------

subroutine rk4_onestep(t, h)

  implicit none

  real(wp), intent(in) :: t, h


  call sderk_func_a(t, sderk_y, sderk_aphip)
  sderk_ybar = sderk_y + 0.5_wp * h * sderk_aphip

  call sderk_func_a(t + 0.5_wp * h, sderk_ybar, sderk_aphim)
  sderk_ybar = sderk_y + 0.5_wp * h * sderk_aphim

  call sderk_func_a(t + 0.5_wp * h, sderk_ybar, sderk_bphip)
  sderk_ybar = sderk_y + h * sderk_bphip

  call sderk_func_a(t + h, sderk_ybar, sderk_bphim)
  sderk_y = sderk_y + (h/6.0_wp) * &
    (sderk_aphip + 2.0_wp * sderk_aphim + 2.0_wp * sderk_bphip + sderk_bphim)

  return

end subroutine rk4_onestep


!-----------------------------------------------------------------------
!
!  subroutine ord_2_iterator(t, h)
!
!  Performs iteration to convergence on the deterministic part of
!  the equations of motion for the implicit, order 2 deterministic
!  case.  Everything but the implicit part is stored in sderk_c,
!  and sderk_a stores the initial guess for the deterministic part.
!  Solution is by functional iteration; sderk_ybar contains the
!  updated solution and sderk_b is temporary storage to compute
!  residuals.
!
!-----------------------------------------------------------------------

subroutine ord_2_iterator(t, h)

  implicit none

  real(wp), intent(in) :: t, h

  integer  :: iter
  logical  :: stopflag
  real(wp) :: resid


  ! Make explicit guess for y_(n+1) and store as ybar
  sderk_ybar = sderk_y + 0.5_wp * sderk_a * h + sderk_c

  iter = 0
  stopflag = .false.
  do while ( .not. stopflag )
    iter = iter + 1
    call sderk_func_a(t + h, sderk_ybar, sderk_a)
    sderk_b = sderk_ybar
    sderk_ybar = sderk_y + 0.5_wp * sderk_a * h + sderk_c
    resid = sum(abs(sderk_b - sderk_ybar))

    if ( resid .lt. tol ) then
      stopflag = .true.
      if ( mf/10 .eq. 3 ) then
        write(0,*) 'Converged in ', iter, ' iterations at time t = ', t
      end if
    end if

    if ( ( .not. stopflag ) .and. ( iter .ge. maxit ) ) then
      write(0,*) 'Warning (SDERK): no convergence in ', iter, ' iterations'
      write(0,*) '  tol = ', tol, ', resid = ', resid
      stopflag = .true.
    end if      
  end do

  sderk_y = sderk_ybar

  return

end subroutine ord_2_iterator


!-----------------------------------------------------------------------
!
!  subroutine ord_4_iterator(t, h)
!
!  Performs iteration to convergence on the deterministic part of
!  the equations of motion for the implicit, order 4 deterministic      
!  case (see Iserles, A First Course in the Numerical Analysis of 
!  Differential Equations p. 47).  Everything but the implicit part 
!  is stored in sderk_c.  We will use sderk_a and sderk_b to store 
!  xi1 and xi2 arrays, and sderk_aphip, sderk_bphip, sderk_aphim, 
!  and sderk_bphim for temporary storage.
!  Solution is by functional iteration.
!
!-----------------------------------------------------------------------

subroutine ord_4_iterator(t, h)

  implicit none

  real(wp), intent(in) :: t, h

  integer  :: iter
  logical  :: stopflag
  real(wp) :: resid

  real(wp), dimension(2,2) :: ac
  real(wp), dimension(2)   :: bc, cc

  ac(:,1) = (/ 0.25_wp, 0.25_wp + sqrt(3.0_wp)/6.0_wp /)
  ac(:,2) = (/ 0.25_wp - sqrt(3.0_wp)/6.0_wp, 0.25_wp /)
  bc = 0.5_wp
  cc = (/ 0.5_wp - sqrt(3.0_wp)/6.0_wp, 0.5_wp + sqrt(3.0_wp)/6.0_wp /)


  ! initial guesses for xi1, xi2
  sderk_b = sderk_y + (sderk_a * h + sderk_c) * sum(ac(2,:))
  sderk_a = sderk_y + (sderk_a * h + sderk_c) * sum(ac(1,:))


  iter = 0
  stopflag = .false.
  do while ( .not. stopflag )
    iter = iter + 1
    call sderk_func_a(t + cc(1)*h, sderk_a, sderk_aphip)
    call sderk_func_a(t + cc(2)*h, sderk_b, sderk_bphip)
    sderk_aphim = sderk_a
    sderk_bphim = sderk_b
    sderk_a = sderk_y + h*(ac(1,1)*sderk_aphip+ac(1,2)*sderk_bphip) &
                        + sum(ac(1,:))*sderk_c
    sderk_b = sderk_y + h*(ac(2,1)*sderk_aphip+ac(2,2)*sderk_bphip) &
                        + sum(ac(2,:))*sderk_c
    resid = sum(abs(sderk_aphim-sderk_a)) + sum(abs(sderk_bphim-sderk_b))

    if ( resid .lt. tol ) then
      stopflag = .true.
      if ( mf/10 .eq. 3 ) then
        write(0,*) 'Converged in ', iter, ' iterations at time t = ', t
      end if
    end if

    if ( ( .not. stopflag ) .and. ( iter .ge. maxit ) ) then
      write(0,*) 'Warning (SDERK): no convergence in ', iter, ' iterations'
      write(0,*) '  tol = ', tol, ', resid = ', resid
      stopflag = .true.
    end if
  end do

  call sderk_func_a(t + cc(1)*h, sderk_a, sderk_aphip)
  call sderk_func_a(t + cc(2)*h, sderk_b, sderk_bphip)
  sderk_y = sderk_y + h*(bc(1)*sderk_aphip+bc(2)*sderk_bphip) + sderk_c

  return

end subroutine ord_4_iterator


!-----------------------------------------------------------------------
!
!  subroutine ord_6_iterator(t, h)
!
!  Performs iteration to convergence on the deterministic part of
!  the equations of motion for the implicit, order 6 deterministic
!  case (see Iserles, A First Course in the Numerical Analysis of
!  Differential Equations p. 47).  Everything but the implicit part
!  is stored in sderk_c.  We will use sderk_a, sderk_b, and sderk_ybar
!  to store the xi1, xi2, and xi3 arrays, and sderk_aybarp, 
!  sderk_bybarp, sderk_aphip, sderk_aybarm, sderk_bybarm, and
!  sderk_aphim for temporary storage.
!  Solution is by functional iteration.
!
!-----------------------------------------------------------------------

subroutine ord_6_iterator(t, h)

  implicit none

  real(wp), intent(in) :: t, h

  integer  :: iter
  logical  :: stopflag
  real(wp) :: resid

  real(wp), dimension(3,3) :: ac
  real(wp), dimension(3)   :: bc, cc

  ac(:,1) = (/ 5.0_wp/36.0_wp,                           & 
               5.0_wp/36.0_wp + sqrt(15.0_wp)/24.0_wp,   &
               5.0_wp/36.0_wp + sqrt(15.0_wp)/30.0_wp /)
  ac(:,2) = (/ 2.0_wp/9.0_wp - sqrt(15.0_wp)/15.0_wp,    &
               2.0_wp/9.0_wp,                            &
               2.0_wp/9.0_wp + sqrt(15.0_wp)/15.0_wp  /)
  ac(:,3) = (/ 5.0_wp/36.0_wp - sqrt(15.0_wp)/30.0_wp,   &
               5.0_wp/36.0_wp - sqrt(15.0_wp)/24.0_wp,   &
               5.0_wp/36.0_wp                         /)    
  bc = (/ 5.0_wp/18.0_wp, 4.0_wp/9.0_wp, 5.0_wp/18.0_wp /)     
  cc = (/ 0.5d0 - dsqrt(15.0d0)/10.0d0, 0.5d0,           &
          0.5d0 + dsqrt(15.0d0)/10.0d0                /)


  ! initial guesses for xi1, xi2, xi3
  sderk_ybar = sderk_y + (sderk_a * h + sderk_c) * sum(ac(3,:))
  sderk_b    = sderk_y + (sderk_a * h + sderk_c) * sum(ac(2,:))
  sderk_a    = sderk_y + (sderk_a * h + sderk_c) * sum(ac(1,:))


  iter = 0
  stopflag = .false.
  do while ( .not. stopflag )
    iter = iter + 1
    call sderk_func_a(t + cc(1)*h, sderk_a,    sderk_aybarp)
    call sderk_func_a(t + cc(2)*h, sderk_b,    sderk_bybarp)
    call sderk_func_a(t + cc(3)*h, sderk_ybar, sderk_aphip )
    sderk_aybarm = sderk_a
    sderk_bybarm = sderk_b
    sderk_aphim  = sderk_ybar
    sderk_a = sderk_y +                                              &
                h*(ac(1,1)*sderk_aybarp +                            &
                   ac(1,2)*sderk_bybarp +                            &
                   ac(1,3)*sderk_aphip   ) +                         &
                sum(ac(1,:)) * sderk_c
    sderk_b = sderk_y +                                              &
                h*(ac(2,1)*sderk_aybarp +                            &
                   ac(2,2)*sderk_bybarp +                            &
                   ac(2,3)*sderk_aphip   ) +                         &
                sum(ac(2,:)) * sderk_c
    sderk_ybar = sderk_y +                                           &
                h*(ac(3,1)*sderk_aybarp +                            &
                   ac(3,2)*sderk_bybarp +                            &
                   ac(3,3)*sderk_aphip   ) +                         &
                sum(ac(3,:)) * sderk_c
    resid = sum(abs(sderk_aybarm-sderk_a)) +                         &
            sum(abs(sderk_bybarm-sderk_b)) +                         &
            sum(abs(sderk_aphim-sderk_ybar))

    if ( resid .lt. tol ) then
      stopflag = .true.
      if ( mf/10 .eq. 3 ) then
        write(0,*) 'Converged in ', iter, ' iterations at time t = ', t
      end if
    end if

    if ( ( .not. stopflag ) .and. ( iter .ge. maxit ) ) then
      write(0,*) 'Warning (SDERK): no convergence in ', iter, ' iterations'
      write(0,*) '  tol = ', tol, ', resid = ', resid
      stopflag = .true.
    end if
  end do


  call sderk_func_a(t + cc(1)*h, sderk_a, sderk_aybarp)
  call sderk_func_a(t + cc(2)*h, sderk_b, sderk_bybarp)
  call sderk_func_a(t + cc(3)*h, sderk_ybar, sderk_aphip)
  sderk_y = sderk_y +                                                     &
            h*(bc(1)*sderk_aybarp+bc(2)*sderk_bybarp+bc(3)*sderk_aphip) + &
            sderk_c

  return

end subroutine ord_6_iterator


!-----------------------------------------------------------------------
!
!  function sderk_get_I1()
!
!  Returns a real vector containing the values of I1 used during the
!    last call to sderk.  The length is the value of nsteps used 
!    during the last call.
!
!-----------------------------------------------------------------------

function sderk_get_I1()
  implicit none
  real(wp), dimension(size(I1)) :: sderk_get_I1
  if ( .not. allocated(I1) ) then
    write(0,*) 'Error (SDERK_GET_I1): SDERK not yet called'
    stop
  end if
  sderk_get_I1 = I1
end function sderk_get_I1


!-----------------------------------------------------------------------
!
!  function sderk_get_I10()     
!
!  Returns a real vector containing the values of I10 used during the
!    last call to sderk.  The length is the value of nsteps used 
!    during the last call.
!
!-----------------------------------------------------------------------

function sderk_get_I10()
  implicit none
  real(wp), dimension(size(I10)) :: sderk_get_I10
  if ( .not. allocated(I10) ) then
    write(0,*) 'Error (SDERK_GET_I10): SDERK not yet called'
    stop
  end if
  sderk_get_I10 = I10
end function sderk_get_I10

  
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!
!  Now comes an embedded version of the random_pl random number
!  generator (multi routines only)
!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!


!=======================================================================
!
!  subroutine init_rand_pl_multi(m, optional seed1, optional seed2,
!                                optional seed3)
!
!  I: m          integer      selects which generator to initialize
!     seed1,2,3  int(rpk)     optional seeds for initializing generator
!
!  O: globals only
!
!  Analogous routine to init_rand_pl, to initialize one of the set
!  of multiple random number generators, specified by m.
!
!=======================================================================

subroutine init_rand_pl_multi(m, seed1, seed2, seed3)

  implicit none

  integer, intent(in) :: m
  integer(rpk), optional, intent(in) :: seed1, seed2, seed3
  integer(rpk) :: lseed1, lseed2, lseed3, firstseed

  ! set seeds
  if ( present(seed1) .or. present(seed2) .or. present(seed3) ) then
    if ( present(seed3) ) firstseed = seed3
    if ( present(seed2) ) firstseed = seed2
    if ( present(seed1) ) firstseed = seed1
    lseed1 = firstseed; lseed2 = firstseed; lseed3 = firstseed
    if ( present(seed1) ) lseed1 = seed1
    if ( present(seed2) ) lseed2 = seed2
    if ( present(seed3) ) lseed3 = seed3
  else
    lseed1 = dseed1+m; lseed2 = dseed2+m; lseed3 = dseed3+m
  end if

  call associate_multi(m)
  call init_rand_pl_main(lseed1, lseed2, lseed3)

end subroutine init_rand_pl_multi


!=======================================================================
!
!  subroutine allocate_rand_pl_multi(m, optional seed1, optional seed2,
!                              optional seed3)
!
!  I: m          integer      selects how many generators to allocate
!     seed1,2,3  int(rpk)     optional seeds for initializing generator
!
!  O: globals only
!
!  Allocate storage for m multiple random number generators.  Also
!  initializes all generators, as follows: 
!  the multi generators 1 through m are set using the specified seeds
!  incremented by the generator number.
!
!  Any existing set of multiple generators will be obliterated.
!  
!=======================================================================

subroutine allocate_rand_pl_multi(m, seed1, seed2, seed3)

  implicit none

  integer, intent(in) :: m
  integer(rpk), optional, intent(in) :: seed1, seed2, seed3
  integer(rpk) :: lseed1, lseed2, lseed3, firstseed
  integer :: j
  integer(rpk) :: jrpk
  
  ! set seeds
  if ( present(seed1) .or. present(seed2) .or. present(seed3) ) then
    if ( present(seed3) ) firstseed = seed3
    if ( present(seed2) ) firstseed = seed2
    if ( present(seed1) ) firstseed = seed1
    lseed1 = firstseed; lseed2 = firstseed; lseed3 = firstseed
    if ( present(seed1) ) lseed1 = seed1
    if ( present(seed2) ) lseed2 = seed2
    if ( present(seed3) ) lseed3 = seed3
  else 
    lseed1 = dseed1; lseed2 = dseed2; lseed3 = dseed3
  end if 
  
  if ( allocated( initialized_m ) ) then
    deallocate( lecuyer_stor_m, scramble_stor_m, fib_stor_m, mersenne_stor_m)
    deallocate( initialized_m, fib_ctr_m, bd_lastout_m, mersenne_ptr_m )
  end if
  allocate( lecuyer_stor_m(lecuyer_stor_sz, m) )
  allocate( scramble_stor_m(scramble_stor_sz, m) )
  allocate( fib_stor_m(fib_stor_sz, m), mersenne_stor_m(mersenne_stor_sz, m) )
  allocate( initialized_m(m), fib_ctr_m(m) )
  allocate( bd_lastout_m(m), mersenne_ptr_m(m) )

  do j = 1, m
    jrpk = j
    call associate_multi(j)
    call init_rand_pl_main(lseed1+jrpk, lseed2+jrpk, lseed3+jrpk)
    initialized = 1
  end do

end subroutine allocate_rand_pl_multi


!=======================================================================
!
!  real(kind=wp) function rand_pl_multi(m)
!
!  I: m              integer          selects which generator to use
!  
!  O: rand_pl_multi  real(kind=wp)    random number in (0,1)
!
!  This is a wrapper for rand_pl_main that associates pointers for
!  the multiple generators before calling the main routine. 
!
!=======================================================================

function rand_pl_multi(m) 
  implicit none
  integer, intent(in) :: m
  real(wp) :: rand_pl_multi
  if ( curr_ptr .ne. m ) call associate_multi(m)
  if ( initialized .ne. 1 ) call init_rand_pl_multi(m)
  rand_pl_multi = rand_pl_main()
end function rand_pl_multi


!=======================================================================
!
!  subroutine associate_multi(m)
!
!  I: m        integer        selects which of the set of multiple 
!                             generators to use
!
!  O: globals only
!
!  Associates pointers so that operations are on one of the multiple
!  generators, specified by the integer n.
!  
!=======================================================================
  
subroutine associate_multi(m)

  implicit none

  integer, intent(in) :: m

  if ( .not. allocated( initialized_m ) ) then
    write(0,*) 'Error (ASSOCIATE_MULTI): attempt to use multiple ', &
               'generators before initialization'
    stop
  end if

  if ( m .lt. 1 .or. m .gt. size(initialized_m) ) then
    write(0,*) 'Error (ASSOCIATE_MULTI): selector is out of allocated range'
    write(0,*) '  selector = ', m, ', max = ', size(initialized_m)
    stop
  end if
  
  curr_ptr = m
  lecuyer_stor  => lecuyer_stor_m(:,m)
  scramble_stor => scramble_stor_m(:,m)
  fib_stor      => fib_stor_m(:,m)
  mersenne_stor => mersenne_stor_m(:,m)
  initialized   => initialized_m(m)
  fib_ctr       => fib_ctr_m(m)
  bd_lastout    => bd_lastout_m(m)
  mersenne_ptr  => mersenne_ptr_m(m)

end subroutine associate_multi


!=======================================================================
!
!  integer(kind=rpk) function mersenne()
!
!  I: only through module globals
!
!  O: mersenne  int(rpk)    random integer, in the range
!                           [0, 2**31 - 1] inclusive
!
!  Implements the Mersenne twister generator, returning one random
!  integer.
!
!=======================================================================

function mersenne()

  implicit none

  integer(rpk) :: mersenne

  integer(rpk), parameter :: n = 624, m = 397
  integer(rpk), parameter :: unity      =           1_rpk  ! 00000001
  integer(rpk), parameter :: matrix_a   = -1727483681_rpk  ! 9908b0df
  integer(rpk), parameter :: upp_p1     = -2147483647_rpk  ! 80000001
  integer(rpk), parameter :: upp_mask   =    upp_p1-unity  ! 80000000
  integer(rpk), parameter :: low_mask   =  2147483647_rpk  ! 7fffffff
  integer(rpk), parameter :: tmp_mask_b = -1658038656_rpk  ! 9d2c5680
  integer(rpk), parameter :: tmp_mask_c =  -272236544_rpk  ! efc60000
  integer(rpk), dimension(0:1), parameter :: mag01 = (/ 0_rpk, matrix_a /)

  integer, parameter :: shift_u = -11
  integer, parameter :: shift_s =   7
  integer, parameter :: shift_t =  15
  integer, parameter :: shift_l = -18

  integer(rpk) :: y
  integer :: k

  if ( mersenne_ptr .ge. n+1 ) then
    do k = 1, n-m
      y = ior( iand(mersenne_stor(k),   upp_mask), &
               iand(mersenne_stor(k+1), low_mask) )
      mersenne_stor(k) = ieor( ieor( mersenne_stor(k+m), ishft(y,-1) ), &
                               mag01( iand(y, unity) ) )
    end do

    do k = n-m+1, n-1
      y = ior( iand(mersenne_stor(k),   upp_mask), &
               iand(mersenne_stor(k+1), low_mask) )
      mersenne_stor(k) = ieor( ieor( mersenne_stor(k+m-n), ishft(y,-1) ), &
                               mag01( iand(y, unity) ) )
    end do

    y = ior( iand(mersenne_stor(n), upp_mask), &
             iand(mersenne_stor(1), low_mask) )
    mersenne_stor(n) = ieor( ieor( mersenne_stor(m), ishft(y,-1) ), &
                             mag01( iand(y, unity) ) )

    mersenne_ptr = 1
  end if

  y = mersenne_stor(mersenne_ptr)
  mersenne_ptr = mersenne_ptr + 1
  y = ieor( y, ishft(y, shift_u) )
  y = ieor( y, iand(ishft(y, shift_s), tmp_mask_b) )
  y = ieor( y, iand(ishft(y, shift_t), tmp_mask_c) )
  y = ieor( y, ishft(y, shift_l) )

  ! shift off 1 bit, so we get a 31-bit integer
  mersenne = ishft(y, -1)

end function mersenne


!=======================================================================
!
!  subroutine init_mersenne(seed)
!
!  I: seed     int(rpk)       initialization seed for generator,
!                              any nonzero integer is ok
!
!  O: none
!
!  Initializes Mersenne twister generator as in the sample code by
!  Matsumoto and Nishimura, using Knuth's LCG.
!
!=======================================================================
  
subroutine init_mersenne(seed)
  
  implicit none 
  
  integer(rpk), intent(in) :: seed

  integer(rpk), parameter :: mask32 = -1_rpk ! ffffffff

  ! check seed
  if ( iand(seed, mask32) .eq. 0_rpk ) then
    write(0,*) 'Error (INIT_MERSENNE): nonzero seed required'
    stop
  end if

  ! just in case the storage uses more than 32 bits
  mersenne_stor(1) = iand( seed, mask32 )
  
  do mersenne_ptr = 2, mersenne_stor_sz
    mersenne_stor(mersenne_ptr) = 1812433253_rpk * &
      ieor( mersenne_stor(mersenne_ptr-1), &
            ishft( mersenne_stor(mersenne_ptr-1), -30 ) ) + mersenne_ptr - 1
    mersenne_stor(mersenne_ptr) = iand( mersenne_stor(mersenne_ptr), mask32 )
  end do

  mersenne_ptr = mersenne_stor_sz + 1

end subroutine init_mersenne
  

!=======================================================================
!  
!  integer(kind=rpk) function lecuyer()
!
!  I: only through module globals
!
!  O: lecuyer   int(rpk)    random integer, in the range
!                           [0, 2**31 - 2] inclusive
!
!  Implements one iteration of the L'Ecuyer generator.
!
!=======================================================================

function lecuyer()

  implicit none

  integer(rpk) :: lecuyer

  integer(rpk), parameter :: m1  = 2147483647_rpk ! 2**31 - 1
  integer(rpk), parameter :: m2  = 2145483479_rpk

  integer(rpk), parameter :: a12 =      63308_rpk
  integer(rpk), parameter :: a13 =    -183326_rpk
  integer(rpk), parameter :: a21 =      86098_rpk
  integer(rpk), parameter :: a23 =    -539608_rpk

  integer(rpk), parameter :: q12 =      33921_rpk
  integer(rpk), parameter :: q13 =      11714_rpk
  integer(rpk), parameter :: q21 =      24919_rpk
  integer(rpk), parameter :: q23 =       3976_rpk

  integer(rpk), parameter :: r12 =      12979_rpk
  integer(rpk), parameter :: r13 =       2883_rpk
  integer(rpk), parameter :: r21 =       7417_rpk
  integer(rpk), parameter :: r23 =       2071_rpk

  integer(rpk) :: h, p12, p13, p21, p23
  integer(rpk), pointer :: x10, x11, x12, x20, x21, x22
  x10 => lecuyer_stor(1); x11 => lecuyer_stor(2); x12 => lecuyer_stor(3)
  x20 => lecuyer_stor(4); x21 => lecuyer_stor(5); x22 => lecuyer_stor(6)

  ! Component 1
  h = x10 / q13
  p13 = -a13 * (x10 - h * q13) - h * r13
  h = x11 / q12
  p12 =  a12 * (x11 - h * q12) - h * r12
  if ( p13 .lt. 0 ) p13 = p13 + m1
  if ( p12 .lt. 0 ) p12 = p12 + m1
  x10 = x11
  x11 = x12
  x12 = p12 - p13
  if ( x12 .lt. 0 ) x12 = x12 + m1

  ! Component 2
  h = x20 / q23
  p23 = -a23 * (x20 - h * q23) - h * r23
  h = x22 / q21
  p21 =  a21 * (x22 - h * q21) - h * r21
  if ( p23 .lt. 0 ) p23 = p23 + m2
  if ( p21 .lt. 0 ) p21 = p21 + m2
  x20 = x21
  x21 = x22
  x22 = p21 - p23
  if ( x22 .lt. 0 ) x22 = x22 + m2

  ! Combination
  if ( x12 .le. x22 ) then
    lecuyer = x12 - x22 + m1 - 1
  else
    lecuyer = x12 - x22 - 1
  end if

end function lecuyer


!=======================================================================
!
!  subroutine init_lecuyer(seed)
!
!  I: seed     int(rpk)       initialization seed for generator,
!                              different seeds give distinct sequences
!                              by partitioning the full sequence;
!                              any number in [0, 2**31 - 2] is ok
!
!  O: none
!
!  Initializes lecuyer generator by setting the seeds from a single
!  integer, by partitioning the full sequence and jumping ahead
!  by an amount proportional to the seed.
!
!=======================================================================

subroutine init_lecuyer(seed)

  implicit none
  
  integer(rpk), intent(in) :: seed

  integer(rpk) :: lseed

  integer(rpk), parameter :: m1  = 2147483647_rpk ! 2**31 - 1
  integer(rpk), parameter :: m2  = 2145483479_rpk

  integer(rpk), parameter :: a12 =      63308_rpk
  integer(rpk), parameter :: a13 =    -183326_rpk + m1
  integer(rpk), parameter :: a21 =      86098_rpk
  integer(rpk), parameter :: a23 =    -539608_rpk + m2

  integer(rpk), parameter :: logk = 184 - 32
  integer(rpk), dimension(3,3) :: A1, A2, A1k, A2k, A1ks, A2ks
  integer(rpk), dimension(3) :: tmparr

  integer :: j

  
  ! check seed
  if ( seed .lt. 0_rpk .or. seed .gt. 2147483646_rpk ) then
    write(0,*) 'Error (INIT_LECUYER): seed outside acceptable range'
    stop
  end if
  lseed = seed + 1

  ! single-iteration matrices
  A1(1,:) = (/   0_rpk,   1_rpk,   0_rpk /)
  A1(2,:) = (/   0_rpk,   0_rpk,   1_rpk /)
  A1(3,:) = (/   0_rpk,     a12,     a13 /)
  A2(1,:) = (/   0_rpk,   1_rpk,   0_rpk /)
  A2(2,:) = (/   0_rpk,   0_rpk,   1_rpk /)
  A2(3,:) = (/     a21,   0_rpk,     a23 /)

  ! compute k-iteration matrices, to jump between different partitions
  A1k = A1
  A2k = A2
  do j = 1, logk
    A1k = modmatsq(A1k, m1)  ! A1k = A1k * A1k (mod m1)
    A2k = modmatsq(A2k, m2)  ! A2k = A2k * A2k (mod m2)
  end do

  ! compute (A**k)**lseed matrices, to jump ahead to the location 
  !   marked by lseed
  A1ks = divconq(A1k, lseed, m1)
  A2ks = divconq(A2k, lseed, m2)

  ! set default seeds
  lecuyer_stor = (/ 1852689663, 1962642687, 580869375, &
                    2039711750, 1671394257, 879888250  /)

  ! advance by k*seed iterations (tmparr used to work around ifc bug)
  tmparr = lecuyer_stor(1:3)
  lecuyer_stor(1:3) = modmvmul(A1ks, tmparr, m1)
  tmparr = lecuyer_stor(4:6)
  lecuyer_stor(4:6) = modmvmul(A2ks, tmparr, m2)

end subroutine init_lecuyer


  !!!!
  !!!! routines for integer modular matrix/vector/scalar arithmetic
  !!!!

  ! divide-and-conquer to calculate A**j mod m
  recursive function divconq(A, j, m) result (Aj)
    implicit none
    integer(rpk), dimension(:,:), intent(in) :: A
    integer(rpk), intent(in) :: j, m
    integer(rpk), dimension(size(A,1),size(A,2)) :: Aj
    if ( j .eq. 1 ) then
      Aj = A
    else if ( isodd(j) ) then
      Aj = modmatmul(A, divconq(A, j-1_rpk, m), m)
    else
      Aj = modmatsq(divconq(A, j/2_rpk, m), m)
    end if
  end function divconq
    
  ! check if integer scalar is odd
  function isodd(a)
    implicit none
    integer(rpk), intent(in) :: a
    logical :: isodd
    isodd = (iand(a, 1_rpk) .eq. 1_rpk)
  end function isodd

  ! wrapper for modmatmul to square a matrix
  function modmatsq(A, m)
    implicit none
    integer(rpk), dimension(:,:), intent(in) :: A
    integer(rpk), intent(in) :: m
    integer(rpk), dimension(size(A,1),size(A,2)) :: modmatsq
    modmatsq = modmatmul(A, A, m)
  end function modmatsq

  ! calculates the matrix product A * B mod m, assuming nonnegative
  ! entries; A and B are assumed square
  function modmatmul(A, B, m)
    implicit none
    integer(rpk), dimension(:,:), intent(in) :: A, B
    integer(rpk), intent(in) :: m
    integer(rpk), dimension(size(A,1),size(A,2)) :: modmatmul
    integer(rpk) :: n, j
    n = size(A,1)
    if ( n.ne.size(A,2) .or. n.ne.size(B,1) .or. n.ne.size(B,2) ) then
      write(0,*) 'Error (MODMATMUL): nonsquare or incompatible matrix sizes'
      stop
    end if
    do j = 1, n
      modmatmul(:,j) = modmvmul(A, B(:,j), m)
    end do
  end function modmatmul

  ! calculates the matrix-vector product A * x mod m, assuming 
  ! nonnegative entries; A is assumed square
  function modmvmul(A, x, m)
    implicit none
    integer(rpk), dimension(:,:), intent(in) :: A
    integer(rpk), dimension(:),   intent(in) :: x
    integer(rpk), intent(in) :: m
    integer(rpk), dimension(size(A,1)) :: modmvmul
    integer(rpk) :: n, j
    n = size(A,1)
    if ( n.ne.size(A,2) .or. n.ne.size(x) ) then
      write(0,*) 'Error (MODMVMUL): nonsquare matrix or incompatible vector'
      stop
    end if
    do j = 1, n
      modmvmul(j) = moddotprod(A(j,:), x, m)
    end do
  end function modmvmul

  ! calculates the vector dot product x * y mod m, assuming 
  ! nonnegative entries
  function moddotprod(x, y, m)
    implicit none
    integer(rpk), dimension(:),   intent(in) :: x, y
    integer(rpk), intent(in) :: m
    integer(rpk) :: moddotprod
    integer(rpk) :: n, j
    n = size(x)
    if ( n.ne.size(y) ) then
      write(0,*) 'Error (MODDOTPROD): incompatible vector sizes'
      stop
    end if
    moddotprod = modmult(x(1), y(1), m)
    do j = 2, n
      moddotprod = modadd( moddotprod, modmult(x(j), y(j), m), m )
    end do
  end function moddotprod

  ! calculates x+y mod m without overflow, assuming x, y >= 0
  function modadd(x, y, m)
    implicit none
    integer(rpk), intent(in) :: x, y, m
    integer(rpk) :: modadd
    modadd = (x - m) + y; if ( modadd .lt. 0 ) modadd = modadd + m
  end function modadd
    
  ! calculates x*y mod m without overflow via the decomposition
  ! algorithm; see L'Ecuyer and Cote, ACM Trans. Math. Soft. 17, 98 
  ! (1991). Assumes x, y >= 0.  Note the redundant p = p+m statements
  ! that were necessary to work around an intel compiler bug.
  function modmult(x, y, m)
    implicit none
    integer(rpk), intent(in) :: x, y, m
    integer(rpk) :: modmult
    integer(rpk), parameter :: h = 32768 ! for 32-bit integers
    integer(rpk) :: a, s, a0, a1, q, qh, rh, k, p
    if ( x .lt. 0 .or. y .lt. 0 ) then
      write(0,*) 'Error (MODMULT): inputs must be positive'
      stop
    end if
    if ( x .eq. 0 .or. y .eq. 0 ) then
      modmult = 0; return
    end if
    a = x; s = y
    if ( a .lt. h ) then
      a0 = a; p = 0
    else
      a1 = a / h; a0 = a - h*a1
      qh = m / h; rh = m - h*qh
      if ( a1 .ge. h ) then
        a1 = a1 - h; k = s / qh
        p = h * (s - k*qh) - k * rh
        if ( p .lt. 0 ) p = p + m
        do while ( p .lt. 0 ); p = p + m; end do
      else
        p = 0
      end if
      if ( a1 .ne. 0 ) then
        q = m / a1; k = s / q
        p = p - k * (m - a1*q)
        if ( p .gt. 0 ) p = p - m
        p = p + a1 * (s - k*q)
        if ( p .lt. 0 ) p = p + m
        do while ( p .lt. 0 ); p = p + m; end do
      end if
      k = p / qh; p = h * (p - k*qh) - k * rh
      if ( p .lt. 0 ) p = p + m
      do while ( p .lt. 0 ); p = p + m; end do
    end if
    if ( a0 .ne. 0 ) then
      q = m / a0; k = s / q
      p = p - k * (m - a0*q)
      if ( p .gt. 0 ) p = p - m
      p = p + a0 * (s - k*q)
      if ( p .lt. 0 ) p = p + m
      do while ( p .lt. 0 ); p = p + m; end do
    end if
    modmult = p
  end function modmult


!=======================================================================
!  
!  integer(kind=rpk) function fibonacci(optional force_init)
!
!  I: force_init  logical    (optional) force a new set of 1009 
!                            numbers to be generated
!
!  O: fibonacci   int(rpk)    random integer, in the range
!                           [0, 2**31 - 2] (note that the lsb is 
!                                           always 0)
!
!  Implements one iteration of Knuth's subtractive lagged Fibonacci
!    generator, with skipping to improve randomness.
!
!=======================================================================

function fibonacci(force_init)

  implicit none

  logical, optional :: force_init

  integer(rpk) :: fibonacci

  integer(rpk), parameter :: lag = 37, sbst = 100, total = 1009
  integer(rpk), parameter :: mdls = 1073741824_rpk ! 2**30
  logical :: reinit
  integer :: j

  ! check for forcing reinitialization
  reinit = .false.
  if ( present( force_init ) ) reinit = force_init

  ! generate 1009 numbers, if needed
  if ( fib_ctr .gt. sbst .or. reinit ) then
    aa(1:sbst) = fib_stor(1:sbst)
    do j = sbst+1, total
      aa(j) = iand(aa(j-sbst) - aa(j-lag), mdls-1_rpk)
    end do
    do j = 1, lag
      fib_stor(j) = iand(aa(total+j-sbst) - aa(total+j-lag), mdls-1_rpk)
    end do
    do j = lag+1, sbst
      fib_stor(j) = iand(aa(total+j-sbst) - fib_stor(j-lag), mdls-1_rpk)
    end do
    fib_ctr = 1
  end if

  fibonacci = 2 * fib_stor(fib_ctr)
  fib_ctr = fib_ctr + 1

end function fibonacci
  

!=======================================================================
!  
!  subroutine init_fibonacci(seed)
!
!  I: seed    int(rpk)   initialization seed, in the range
!                        [0, 2**30 - 3 = 1 073 741 821]
!
!  O: only through module globals
!
!  Initializes Knuth's Fibonacci generator, according to his
!    recommendations.
!
!=======================================================================

subroutine init_fibonacci(seed)

  implicit none

  integer(rpk), intent(in) :: seed

  integer(rpk), parameter :: tt = 70, lag = 37 
  integer(rpk), parameter :: mdls = 1073741824_rpk ! 2**30
  integer(rpk), parameter :: maxseed = mdls-3_rpk
  integer, parameter :: kk = fib_stor_sz, kkk = kk+kk-1
  integer(rpk), dimension(kkk) :: xtmp
  integer :: t, j, ss
  integer(rpk) :: dummy


  ! check seed
  if ( seed .lt. 1 .or. seed .gt. maxseed ) then
    write(0,*) 'Error (INIT_FIBONACCI): inappropriate seed'
  end if

  ! set pointer
  fib_ctr = 1

  ! do knuthian stuff
  ss = seed - mod(seed,2_rpk) + 2
  do j = 1, kk
    xtmp(j) = ss
    ss = ss + ss
    if ( ss .ge. mdls ) ss = ss - mdls + 2
  end do
  xtmp(2) = xtmp(2) + 1
  ss = seed
  t = tt - 1
10 continue
  do j = kk, 2, -1
    xtmp(j+j-1) = xtmp(j)
    xtmp(j+j-2) = 0
  end do
  do j = kkk, kk+1, -1
    xtmp(j-(kk-lag)) = xtmp(j-(kk-lag)) - xtmp(j)
    if ( xtmp(j-(kk-lag) ) .lt. 0)  xtmp(j-(kk-lag)) = xtmp(j-(kk-lag)) + mdls
    xtmp(j-kk) = xtmp(j-kk) - xtmp(j)
    if ( xtmp(j-kk) .lt. 0 )  xtmp(j-kk) = xtmp(j-kk) + mdls
  end do
  if ( mod(ss, 2) .eq. 1 ) then
    do j = kk, 1, -1
      xtmp(j+1) = xtmp(j)
    end do
    xtmp(1) = xtmp(kk+1)
    xtmp(lag+1) = xtmp(lag+1) - xtmp(kk+1)
    if ( xtmp(lag+1) .lt. 0 )  xtmp(lag+1) = xtmp(lag+1) + mdls
  end if
  if ( ss .ne. 0 ) then
    ss = ss / 2
  else
    t = t - 1
  end if
  if ( t .gt. 0 ) go to 10
  do j = 1, lag
    fib_stor(j+kk-lag) = xtmp(j)
  end do
  do j = lag+1, kk
    fib_stor(j-lag) = xtmp(j)
  end do

  ! discard the first 2020 or so numbers
  do j = 1, 2
    dummy = fibonacci(force_init=.true.)
  end do

end subroutine init_fibonacci


!=======================================================================
!
!  subroutine init_rand_pl_main(seed1, seed2, seed3)
!
!  I: seed1,2,3  int(rpk)     seeds for initializing generator
!
!  O: through module globals
!
!  Initializes all 3 random number generators, with the
!  specified seeds (1 for Fibonacci, 2 for L'Ecuyer, 3 for Mersenne).  
!
!=======================================================================

subroutine init_rand_pl_main(seed1, seed2, seed3)

  implicit none

  integer(rpk), intent(in) :: seed1, seed2, seed3
  integer :: j


  ! check method flag
  if ( rand_pl_mf_pri .lt. 1 .or. rand_pl_mf_pri .gt. 3 .or. &
       rand_pl_mf_mix .lt. 0 .or. rand_pl_mf_mix .gt. 3 .or. &
       rand_pl_mf_scr .lt. 0 .or. rand_pl_mf_scr .gt. 4 .or. &
       rand_pl_mf_pri .eq. rand_pl_mf_mix .or. &
       rand_pl_mf_pri .eq. rand_pl_mf_scr .or. &
       (rand_pl_mf_mix .ne. 0 .and. rand_pl_mf_mix .eq. rand_pl_mf_scr) ) then
    write(0,*) 'Error (INIT_RAND_PL): illegal rand_pl_mf'
    stop
  end if

  ! initialize all generators
  call init_fibonacci(seed1)
  call init_lecuyer(seed2)
  call init_mersenne(seed3)

  ! fill scramble table
  bd_lastout = 0
  scramble_stor = 0
  if ( rand_pl_mf_scr .ne. 0 ) then
    do j = 1, scramble_stor_sz
      scramble_stor(j) = rand_pl_raw()
    end do
    if ( rand_pl_mf_scr .eq. 4 ) bd_lastout = rand_pl_raw()
  end if

  initialized = 1

end subroutine init_rand_pl_main


!=======================================================================
!
!  real(kind=wp) function rand_pl_raw()
!
!  I: none
!
!  O: rand_pl     integer(rpk)         random integer 
!                              
!  Constructs a random integer from the proper generator(s) and
!  subtractively combines them, if appropriate.  Basically this
!  does everything short of shuffling the output numbers.
!
!=======================================================================

function rand_pl_raw() result(r)

  implicit none

  integer(rpk) :: r

  integer(rpk), parameter :: modmask = 2147483647_rpk  ! 7fffffff = 2**31-1


  if ( rand_pl_mf_pri .eq. 1 ) then
    r = fibonacci()
  else if ( rand_pl_mf_pri .eq. 2 ) then
    r = lecuyer()
  else if ( rand_pl_mf_pri .eq. 3 ) then
    r = mersenne()
  end if

  if ( rand_pl_mf_mix .ne. 0 ) then
    if ( rand_pl_mf_mix .eq. 1 ) then
      r = iand( r - fibonacci(), modmask )
    else if ( rand_pl_mf_mix .eq. 2 ) then
      r = iand( r - lecuyer(), modmask )
    else if ( rand_pl_mf_mix .eq. 3 ) then
      r = iand( r - mersenne(), modmask )
    end if
  end if

end function rand_pl_raw


!=======================================================================
!
!  real(kind=wp) function rand_pl_main()
!
!  I: none
!
!  O: rand_pl     real(kind=wp)         random number in (0,1)
!
!=======================================================================

function rand_pl_main()

  implicit none

  real(wp) :: rand_pl_main

  integer(rpk), parameter :: fibonacci_range = 1073741823_rpk ! 2**31 - 2
  real(wp), parameter :: fibonacci_mult = 4.65661287307739e-10_wp ! 1/(2**31)
  integer(rpk), parameter :: lecuyer_range   = 2147483646_rpk ! 2**31 - 2
  real(wp), parameter :: lecuyer_mult = 4.65661287307739e-10_wp ! 1/(2**31)
  integer(rpk), parameter :: mersenne_range  = 2147483647_rpk ! 2**31 - 1
  real(wp), parameter :: mersenne_mult = 4.65661287090899e-10_wp ! 1/(2**31 + 1)

  real(wp), parameter :: gen_mult = 4.65661287090899e-10_wp ! 1/(2**31 + 1)

  integer :: indx

 
  if ( rand_pl_mf_scr .eq. 0 ) then
    rand_pl_main = (real(rand_pl_raw(), kind=wp) + 1) * gen_mult

  else if ( rand_pl_mf_scr .eq. 1 ) then
    ! scramble output using Fibonacci output
    indx = ceiling(fibonacci() * gen_mult * scramble_stor_sz)
    rand_pl_main = (real(scramble_stor(indx), kind=wp) + 1) * gen_mult
    scramble_stor(indx) = rand_pl_raw()

  else if ( rand_pl_mf_scr .eq. 2 ) then
    ! scramble output using L'Ecuyer output
    indx = ceiling(lecuyer() * gen_mult * scramble_stor_sz)
    rand_pl_main = (real(scramble_stor(indx), kind=wp) + 1) * gen_mult
    scramble_stor(indx) = rand_pl_raw()

  else if ( rand_pl_mf_scr .eq. 3 ) then
    ! scramble output using Mersenne output
    indx = ceiling(mersenne() * gen_mult * scramble_stor_sz)
    rand_pl_main = (real(scramble_stor(indx), kind=wp) + 1) * gen_mult
    scramble_stor(indx) = rand_pl_raw()

  else if ( rand_pl_mf_scr .eq. 4 ) then
    ! scramble output using Bays-Durham scrambling
    indx = ceiling(bd_lastout * gen_mult * scramble_stor_sz)
    bd_lastout = scramble_stor(indx)
    rand_pl_main = (real(bd_lastout, kind=wp) + 1) * gen_mult
    scramble_stor(indx) = rand_pl_raw()

  end if

end function rand_pl_main


!=======================================================================
!
!  real(kind=wp) function rand_pl_vec_multi(m, n)
!
!  I: m            default integer      selects which generator to use
!     n            default integer      length of output vector
!
!  O: rand_pl_vec  real(kind=wp) array  array of random numbers in (0,1)
!
!  Single wrapper for rand_pl_vec_main()
!
!=======================================================================

function rand_pl_vec_multi(m, n)
  implicit none
  integer, intent(in) :: m, n
  real(wp), dimension(n) :: rand_pl_vec_multi
  if ( curr_ptr .ne. m ) call associate_multi(m)
  rand_pl_vec_multi = rand_pl_vec_main(n)
end function rand_pl_vec_multi


!=======================================================================
!
!  real(kind=wp) function rand_pl_vec_main(n)
!
!  I: n            default integer      length of output vector
!
!  O: rand_pl_vec_main  real(kind=wp) array  array of random numbers 
!                                            in (0,1)
!
!=======================================================================

function rand_pl_vec_main(n)
  implicit none
  integer, intent(in) :: n
  real(wp), dimension(n) :: rand_pl_vec_main
  integer :: j
  do j = 1, n
    rand_pl_vec_main(j) = rand_pl_main()
  end do
end function rand_pl_vec_main


!=======================================================================
!
!  real(kind=wp) function nrand_pl_vec_multi(m, n)
!
!  I: m            default integer      selects which generator to use
!     n            default integer      length of output vector, must
!                                         be even
!
!  O: nrand_pl_vec real(kind=wp) array  array of standard normal
!                                         deviates
!  
!  Multiple wrapper for nrand_pl_vec_main
!  
!=======================================================================

function nrand_pl_vec_multi(m, n)
  implicit none
  integer, intent(in) :: m, n
  real(wp), dimension(n) :: nrand_pl_vec_multi
  if ( curr_ptr .ne. m ) call associate_multi(m)
  nrand_pl_vec_multi = nrand_pl_vec_main(n)
end function nrand_pl_vec_multi


!=======================================================================
!
!  real(kind=wp) function nrand_pl_vec_main(n)
!
!  I: n            default integer      length of output vector, must
!                                         be even
!
!  O: nrand_pl_vec_main real(kind=wp) array  array of standard normal
!                                            deviates
!
!=======================================================================

function nrand_pl_vec_main(n) result(nd)

  implicit none

  integer, intent(in) :: n
  real(wp), dimension(n) :: nd

  real(wp), parameter :: pi = 3.141592653589793_wp, twopi = 2*pi
  integer :: j
  real(wp) :: tmp

  
  if ( (n/2)*2 .ne. n ) then
    write(0,*) 'Error (NRAND_PL_VEC): requested length not even'
    stop
  end if

  nd = rand_pl_vec_main(n)

  do j = 1, n, 2
    tmp = sqrt((-2.0_wp) * log(nd(j)))
    nd(j)   = cos(twopi * nd(j+1)) * tmp
    nd(j+1) = sin(twopi * nd(j+1)) * tmp
  end do

end function nrand_pl_vec_main


!=======================================================================
!
!  subroutine get_rand_pl_state_multi(m, ivec)
!
!  I: m      integer            selects which generator to use
!
!  O: ivec   int(rpk) array     contains sufficient information to
!                               restore the state of the generator
!
!  Single wrapper for get_rand_pl_state_main
!
!=======================================================================
    
subroutine get_rand_pl_state_multi(m, ivec)
  implicit none
  integer(rpk), dimension(rand_pl_state_sz), intent(out) :: ivec
  integer, intent(in) :: m
  if ( curr_ptr .ne. m ) call associate_multi(m)
  call get_rand_pl_state_main(ivec)
end subroutine get_rand_pl_state_multi


!=======================================================================
!
!  subroutine get_rand_pl_state_main(ivec)
!
!  I: only through globals
!
!  O: ivec   int(rpk) array     contains sufficient information to
!                               restore the state of the generator
!
!=======================================================================

subroutine get_rand_pl_state_main(ivec)

  implicit none

  integer(rpk), dimension(rand_pl_state_sz), intent(out) :: ivec
  integer :: ptr1, ptr2

  if ( initialized .eq. 0 ) then
    ivec = 0
    ivec(1) = rand_pl_mf
  else
    ivec(1) = rand_pl_mf
    ivec(2) = initialized
    ivec(3) = fib_ctr
    ivec(4) = bd_lastout
    ivec(5) = mersenne_ptr
    ptr1 = 6
    ptr2 = ptr1 + fib_stor_sz - 1
    ivec(ptr1:ptr2) = fib_stor
    ptr1 = ptr2 + 1
    ptr2 = ptr1 + lecuyer_stor_sz - 1
    ivec(ptr1:ptr2) = lecuyer_stor
    ptr1 = ptr2 + 1
    ptr2 = ptr1 + scramble_stor_sz - 1
    ivec(ptr1:ptr2) = scramble_stor
    ptr1 = ptr2 + 1
    ptr2 = ptr1 + mersenne_stor_sz - 1
    ivec(ptr1:ptr2) = mersenne_stor
  end if

end subroutine get_rand_pl_state_main


!=======================================================================
!
!  subroutine set_rand_pl_state_multi(m, ivec)
!  
!  I: ivec   int(rpk) array     contains sufficient information to
!                               restore the state of the generator,
!                               from previous call to get_rand_pl_state
!     m      integer            selects which generator to use
!                               
!  O: only through globals
!  
!  Multiple generator wrapper for set_rand_pl_state_main.
!  
!=======================================================================

subroutine set_rand_pl_state_multi(m, ivec)
  implicit none
  integer(rpk), dimension(rand_pl_state_sz), intent(in) :: ivec
  integer, intent(in) :: m
  if ( curr_ptr .ne. m ) call associate_multi(m)
  call set_rand_pl_state_main(ivec)
end subroutine set_rand_pl_state_multi


!=======================================================================
!
!  subroutine set_rand_pl_state_main(ivec)
!
!  I: ivec   int(rpk) array     contains sufficient information to
!                               restore the state of the generator,
!                               from previous call to get_rand_pl_state
!
!  O: only through globals
!
!=======================================================================

subroutine set_rand_pl_state_main(ivec)

  implicit none

  integer(rpk), dimension(rand_pl_state_sz), intent(in) :: ivec
  integer :: ptr1, ptr2

  if ( ivec(1) .ne. rand_pl_mf ) then
    write(0,*) 'Error (SET_RAND_PL_STATE): rand_pl_mf changed since ' &
               // 'state vector was taken'
    stop
  else
    initialized   = ivec(2)
    fib_ctr       = ivec(3)
    bd_lastout    = ivec(4)
    mersenne_ptr  = ivec(5)
    ptr1 = 6
    ptr2 = ptr1 + fib_stor_sz - 1
    fib_stor      = ivec(ptr1:ptr2)
    ptr1 = ptr2 + 1
    ptr2 = ptr1 + lecuyer_stor_sz - 1
    lecuyer_stor  = ivec(ptr1:ptr2)
    ptr1 = ptr2 + 1
    ptr2 = ptr1 + scramble_stor_sz - 1
    scramble_stor = ivec(ptr1:ptr2)
    ptr1 = ptr2 + 1
    ptr2 = ptr1 + mersenne_stor_sz - 1
    mersenne_stor = ivec(ptr1:ptr2)
  end if

end subroutine set_rand_pl_state_main


end module sderk90
