LCOV - code coverage report
Current view: top level - colors/private - hermite_interp.f90 (source / functions) Coverage Total Hit
Test: coverage.info Lines: 63.1 % 244 154
Test Date: 2026-11-11 22:04:34 Functions: 90.0 % 10 9

            Line data    Source code
       1              : ! ***********************************************************************
       2              : !   Copyright (C) 2026  Niall Miller & The MESA Team
       3              : ! ***********************************************************************
       4              : 
       5              : ! ***********************************************************************
       6              : ! Hermite interpolation module for spectral energy distributions (SEDs)
       7              : ! ***********************************************************************
       8              : 
       9              : module hermite_interp
      10              :    use const_def, only: dp
      11              :    use colors_def, only: Colors_General_Info
      12              :    use colors_utils, only: dilute_flux, find_containing_cell, &
      13              :                            find_nearest_point, find_bracket_index, load_stencil
      14              :    implicit none
      15              : 
      16              :    private
      17              :    public :: construct_sed_hermite, hermite_tensor_interp3d
      18              : 
      19              : contains
      20              : 
      21              :    !---------------------------------------------------------------------------
      22              :    ! Main entry point: Construct a SED using Hermite tensor interpolation.
      23              :    ! Data loading strategy is determined by rq%cube_loaded (set at init):
      24              :    !   cube_loaded = .true.  -> use the preloaded 4-D cube on the handle
      25              :    !   cube_loaded = .false. -> load individual SED files via the lookup table
      26              :    !---------------------------------------------------------------------------
      27            7 :    subroutine construct_sed_hermite(rq, teff, log_g, metallicity, R, d, &
      28              :                                     stellar_model_dir, wavelengths, fluxes)
      29              :       type(Colors_General_Info), intent(inout) :: rq
      30              :       real(dp), intent(in) :: teff, log_g, metallicity, R, d
      31              :       character(len=*), intent(in) :: stellar_model_dir
      32              :       real(dp), dimension(:), allocatable, intent(out) :: wavelengths, fluxes
      33              : 
      34              :       integer :: n_lambda
      35            7 :       real(dp), dimension(:), allocatable :: interp_flux
      36              : 
      37            7 :       if (rq%cube_loaded) then
      38              :          ! ---- Fast path: use preloaded cube from handle ----
      39            7 :          n_lambda = size(rq%cube_wavelengths)
      40              : 
      41              :          ! Copy wavelengths to output
      42           21 :          allocate (wavelengths(n_lambda))
      43         4819 :          wavelengths = rq%cube_wavelengths
      44              : 
      45              :          ! Vectorised interpolation over all wavelengths in one pass —
      46              :          ! cell location is computed once and reused, no per-wavelength
      47              :          ! allocation or 3-D slice extraction needed.
      48           14 :          allocate (interp_flux(n_lambda))
      49              :          call hermite_interp_vector(teff, log_g, metallicity, &
      50              :                                     rq%cube_teff_grid, rq%cube_logg_grid, &
      51              :                                     rq%cube_meta_grid, &
      52            7 :                                     rq%cube_flux, n_lambda, interp_flux)
      53              :       else
      54              :          ! ---- Fallback path: load individual SED files from lookup table ----
      55              :          call construct_sed_from_files(rq, teff, log_g, metallicity, &
      56            0 :                                        stellar_model_dir, interp_flux, wavelengths)
      57            0 :          n_lambda = size(wavelengths)
      58              :       end if
      59              : 
      60              :       ! Apply distance dilution to get observed flux
      61           21 :       allocate (fluxes(n_lambda))
      62            7 :       call dilute_flux(interp_flux, R, d, fluxes)
      63              : 
      64            7 :    end subroutine construct_sed_hermite
      65              : 
      66              :    !---------------------------------------------------------------------------
      67              :    ! Fallback: Build a local sub-cube from individual SED files with enough
      68              :    ! context for Hermite derivative computation, then interpolate all
      69              :    ! wavelengths in a single pass.
      70              :    !---------------------------------------------------------------------------
      71            0 :    subroutine construct_sed_from_files(rq, teff, log_g, metallicity, &
      72              :                                        stellar_model_dir, interp_flux, wavelengths)
      73              :       use colors_utils, only: resolve_path, build_grid_to_lu_map
      74              :       type(Colors_General_Info), intent(inout) :: rq
      75              :       real(dp), intent(in) :: teff, log_g, metallicity
      76              :       character(len=*), intent(in) :: stellar_model_dir
      77              :       real(dp), dimension(:), allocatable, intent(out) :: interp_flux, wavelengths
      78              : 
      79              :       integer :: i_t, i_g, i_m   ! bracketing indices in unique grids
      80              :       integer :: lo_t, hi_t, lo_g, hi_g, lo_m, hi_m   ! stencil bounds
      81              :       integer :: nt, ng, nm, n_lambda
      82              :       character(len=512) :: resolved_dir
      83              :       logical :: need_reload
      84              : 
      85            0 :       resolved_dir = trim(resolve_path(stellar_model_dir))
      86              : 
      87              :       ! Ensure the grid-to-lu mapping exists (built once, then reused)
      88            0 :       if (.not. rq%grid_map_built) call build_grid_to_lu_map(rq)
      89              : 
      90              :       ! Find bracketing cell in the unique grids
      91            0 :       call find_bracket_index(rq%u_teff, teff, i_t)
      92            0 :       call find_bracket_index(rq%u_logg, log_g, i_g)
      93            0 :       call find_bracket_index(rq%u_meta, metallicity, i_m)
      94              : 
      95              :       ! Check if the stencil cache is still valid for this cell
      96            0 :       need_reload = .true.
      97              :       if (rq%stencil_valid .and. &
      98              :           i_t == rq%stencil_i_t .and. &
      99            0 :           i_g == rq%stencil_i_g .and. &
     100              :           i_m == rq%stencil_i_m) then
     101              :          need_reload = .false.
     102              :       end if
     103              : 
     104              :       if (need_reload) then
     105              :          ! Determine the extended stencil bounds:
     106              :          ! For each axis, include one point before and after the cell
     107              :          ! when available, so that centred differences match the cube.
     108            0 :          nt = size(rq%u_teff)
     109            0 :          ng = size(rq%u_logg)
     110            0 :          nm = size(rq%u_meta)
     111              : 
     112            0 :          if (nt < 2) then
     113            0 :             lo_t = 1; hi_t = 1
     114              :          else
     115            0 :             lo_t = max(1, i_t - 1)
     116            0 :             hi_t = min(nt, i_t + 2)
     117              :          end if
     118              : 
     119            0 :          if (ng < 2) then
     120            0 :             lo_g = 1; hi_g = 1
     121              :          else
     122            0 :             lo_g = max(1, i_g - 1)
     123            0 :             hi_g = min(ng, i_g + 2)
     124              :          end if
     125              : 
     126            0 :          if (nm < 2) then
     127            0 :             lo_m = 1; hi_m = 1
     128              :          else
     129            0 :             lo_m = max(1, i_m - 1)
     130            0 :             hi_m = min(nm, i_m + 2)
     131              :          end if
     132              : 
     133              :          ! Load SEDs for every stencil point (using memory cache)
     134            0 :          call load_stencil(rq, resolved_dir, lo_t, hi_t, lo_g, hi_g, lo_m, hi_m)
     135              : 
     136              :          ! Store subgrid arrays on the handle
     137            0 :          if (allocated(rq%stencil_teff)) deallocate (rq%stencil_teff)
     138            0 :          if (allocated(rq%stencil_logg)) deallocate (rq%stencil_logg)
     139            0 :          if (allocated(rq%stencil_meta)) deallocate (rq%stencil_meta)
     140              : 
     141            0 :          allocate (rq%stencil_teff(hi_t - lo_t + 1))
     142            0 :          allocate (rq%stencil_logg(hi_g - lo_g + 1))
     143            0 :          allocate (rq%stencil_meta(hi_m - lo_m + 1))
     144            0 :          rq%stencil_teff = rq%u_teff(lo_t:hi_t)
     145            0 :          rq%stencil_logg = rq%u_logg(lo_g:hi_g)
     146            0 :          rq%stencil_meta = rq%u_meta(lo_m:hi_m)
     147              : 
     148            0 :          rq%stencil_i_t = i_t
     149            0 :          rq%stencil_i_g = i_g
     150            0 :          rq%stencil_i_m = i_m
     151            0 :          rq%stencil_valid = .true.
     152              :       end if
     153              : 
     154              :       ! Copy wavelengths to output
     155            0 :       n_lambda = size(rq%stencil_wavelengths)
     156            0 :       allocate (wavelengths(n_lambda))
     157            0 :       wavelengths = rq%stencil_wavelengths
     158              : 
     159              :       ! Interpolate all wavelengths using precomputed stencil
     160            0 :       allocate (interp_flux(n_lambda))
     161              :       call hermite_interp_vector(teff, log_g, metallicity, &
     162              :                                  rq%stencil_teff, rq%stencil_logg, rq%stencil_meta, &
     163            0 :                                  rq%stencil_fluxes, n_lambda, interp_flux)
     164              : 
     165            0 :    end subroutine construct_sed_from_files
     166              : 
     167              :    !---------------------------------------------------------------------------
     168              :    ! Vectorised Hermite interpolation over all wavelengths.
     169              :    !
     170              :    ! The cell location (i_x, i_y, i_z, t_x, t_y, t_z) depends only on
     171              :    ! (teff, logg, meta) and the sub-grids — not on wavelength.  Computing
     172              :    ! it once and reusing across all n_lambda samples eliminates redundant
     173              :    ! binary searches and basis-function evaluations.
     174              :    !---------------------------------------------------------------------------
     175            7 :    subroutine hermite_interp_vector(x_val, y_val, z_val, &
     176            7 :                                     x_grid, y_grid, z_grid, &
     177            7 :                                     f_values_4d, n_lambda, result_flux)
     178              :       real(dp), intent(in) :: x_val, y_val, z_val
     179              :       real(dp), intent(in) :: x_grid(:), y_grid(:), z_grid(:)
     180              :       real(dp), intent(in) :: f_values_4d(:, :, :, :)   ! (nx, ny, nz, n_lambda)
     181              :       integer, intent(in) :: n_lambda
     182              :       real(dp), intent(out) :: result_flux(n_lambda)
     183              : 
     184              :       integer :: i_x, i_y, i_z
     185              :       integer :: ix_max, iy_max, iz_max
     186              :       real(dp) :: t_x, t_y, t_z
     187              :       real(dp) :: dx, dy, dz
     188              :       real(dp) :: val, df_dx, df_dy, df_dz
     189              :       integer :: nx, ny, nz
     190              :       integer :: ix, iy, iz, lam
     191              :       real(dp) :: h_x(2), h_y(2), h_z(2)
     192              :       real(dp) :: hx_d(2), hy_d(2), hz_d(2)
     193              :       real(dp) :: wx, wy, wz, wxd, wyd, wzd
     194              : 
     195            7 :       nx = size(x_grid)
     196            7 :       ny = size(y_grid)
     197            7 :       nz = size(z_grid)
     198              : 
     199              :       ! Find containing cell (done once for all wavelengths).  Singleton axes
     200              :       ! are valid degenerate dimensions, not an out-of-grid condition.
     201              :       call find_containing_cell(x_val, y_val, z_val, x_grid, y_grid, z_grid, &
     202            7 :                                 i_x, i_y, i_z, t_x, t_y, t_z)
     203              : 
     204            7 :       ix_max = 1
     205            7 :       iy_max = 1
     206            7 :       iz_max = 1
     207              : 
     208            7 :       if (nx == 1) then
     209            0 :          i_x = 1
     210            0 :          t_x = 0.0_dp
     211            0 :          ix_max = 0
     212              :       end if
     213            7 :       if (ny == 1) then
     214            3 :          i_y = 1
     215            3 :          t_y = 0.0_dp
     216            3 :          iy_max = 0
     217              :       end if
     218            7 :       if (nz == 1) then
     219            3 :          i_z = 1
     220            3 :          t_z = 0.0_dp
     221            3 :          iz_max = 0
     222              :       end if
     223              : 
     224              :       ! If outside a non-degenerate grid dimension, use nearest point for all wavelengths.
     225              :       if ((nx > 1 .and. (i_x < 1 .or. i_x >= nx)) .or. &
     226            7 :           (ny > 1 .and. (i_y < 1 .or. i_y >= ny)) .or. &
     227              :           (nz > 1 .and. (i_z < 1 .or. i_z >= nz))) then
     228              : 
     229              :          call find_nearest_point(x_val, y_val, z_val, x_grid, y_grid, z_grid, &
     230            0 :                                  i_x, i_y, i_z)
     231            0 :          do lam = 1, n_lambda
     232            0 :             result_flux(lam) = f_values_4d(i_x, i_y, i_z, lam)
     233              :          end do
     234            0 :          return
     235              :       end if
     236              : 
     237              :       ! Grid cell spacing.  Degenerate axes have zero derivative contribution.
     238            7 :       if (nx > 1) then
     239            7 :          dx = x_grid(i_x + 1) - x_grid(i_x)
     240              :       else
     241              :          dx = 0.0_dp
     242              :       end if
     243            7 :       if (ny > 1) then
     244            4 :          dy = y_grid(i_y + 1) - y_grid(i_y)
     245              :       else
     246              :          dy = 0.0_dp
     247              :       end if
     248            7 :       if (nz > 1) then
     249            4 :          dz = z_grid(i_z + 1) - z_grid(i_z)
     250              :       else
     251              :          dz = 0.0_dp
     252              :       end if
     253              : 
     254              :       ! Precompute Hermite basis functions (same for all wavelengths)
     255           21 :       h_x = [h00(t_x), h01(t_x)]
     256           21 :       hx_d = [h10(t_x), h11(t_x)]
     257           21 :       h_y = [h00(t_y), h01(t_y)]
     258           21 :       hy_d = [h10(t_y), h11(t_y)]
     259           21 :       h_z = [h00(t_z), h01(t_z)]
     260           21 :       hz_d = [h10(t_z), h11(t_z)]
     261              : 
     262              :       ! stencil loop -- weights are invariant over lambda, so lambda is innermost
     263         4812 :       result_flux = 0.0_dp
     264           18 :       do iz = 0, iz_max
     265           11 :          wz = h_z(iz + 1)
     266           11 :          wzd = hz_d(iz + 1)
     267           37 :          do iy = 0, iy_max
     268           19 :             wy = h_y(iy + 1)
     269           19 :             wyd = hy_d(iy + 1)
     270           68 :             do ix = 0, ix_max
     271           38 :                wx = h_x(ix + 1)
     272           38 :                wxd = hx_d(ix + 1)
     273        38443 :                do lam = 1, n_lambda
     274        38386 :                   val = f_values_4d(i_x + ix, i_y + iy, i_z + iz, lam)
     275              : 
     276              :                   call compute_derivatives_at_point_4d( &
     277              :                      f_values_4d, i_x + ix, i_y + iy, i_z + iz, lam, &
     278        38386 :                      nx, ny, nz, x_grid, y_grid, z_grid, df_dx, df_dy, df_dz)
     279              : 
     280              :                   result_flux(lam) = result_flux(lam) &
     281              :                                      + wx*wy*wz*val &
     282              :                                      + wxd*wy*wz*dx*df_dx &
     283              :                                      + wx*wyd*wz*dy*df_dy &
     284        38424 :                                      + wx*wy*wzd*dz*df_dz
     285              :                end do
     286              :             end do
     287              :          end do
     288              :       end do
     289              : 
     290              :    end subroutine hermite_interp_vector
     291              : 
     292              : 
     293       115158 :    subroutine compute_derivatives_at_point_4d(f4d, i, j, k, lam, nx, ny, nz, &
     294        38386 :                                               x_grid, y_grid, z_grid, df_dx, df_dy, df_dz)
     295              :       real(dp), intent(in) :: f4d(:, :, :, :)
     296              :       integer, intent(in) :: i, j, k, lam, nx, ny, nz
     297              :       real(dp), intent(in) :: x_grid(:), y_grid(:), z_grid(:)
     298              :       real(dp), intent(out) :: df_dx, df_dy, df_dz
     299              :       real(dp) :: h_minus, h_plus, d_minus, d_plus
     300              : 
     301              :       ! x derivative
     302        38386 :       if (nx == 1) then
     303            0 :          df_dx = 0.0_dp
     304        38386 :       else if (i > 1 .and. i < nx) then
     305        38380 :          h_minus = x_grid(i) - x_grid(i - 1)
     306        38380 :          h_plus = x_grid(i + 1) - x_grid(i)
     307        38380 :          d_minus = (f4d(i, j, k, lam) - f4d(i - 1, j, k, lam))/h_minus
     308        38380 :          d_plus = (f4d(i + 1, j, k, lam) - f4d(i, j, k, lam))/h_plus
     309        38380 :          df_dx = (h_plus*d_minus + h_minus*d_plus)/(h_minus + h_plus)
     310            6 :       else if (i == 1) then
     311              :          df_dx = (f4d(i + 1, j, k, lam) - f4d(i, j, k, lam))/ &
     312            6 :                  (x_grid(i + 1) - x_grid(i))
     313              :       else
     314              :          df_dx = (f4d(i, j, k, lam) - f4d(i - 1, j, k, lam))/ &
     315            0 :                  (x_grid(i) - x_grid(i - 1))
     316              :       end if
     317              : 
     318              :       ! y derivative
     319        38386 :       if (ny == 1) then
     320           18 :          df_dy = 0.0_dp
     321        38368 :       else if (j > 1 .and. j < ny) then
     322        38368 :          h_minus = y_grid(j) - y_grid(j - 1)
     323        38368 :          h_plus = y_grid(j + 1) - y_grid(j)
     324        38368 :          d_minus = (f4d(i, j, k, lam) - f4d(i, j - 1, k, lam))/h_minus
     325        38368 :          d_plus = (f4d(i, j + 1, k, lam) - f4d(i, j, k, lam))/h_plus
     326        38368 :          df_dy = (h_plus*d_minus + h_minus*d_plus)/(h_minus + h_plus)
     327            0 :       else if (j == 1) then
     328              :          df_dy = (f4d(i, j + 1, k, lam) - f4d(i, j, k, lam))/ &
     329            0 :                  (y_grid(j + 1) - y_grid(j))
     330              :       else
     331              :          df_dy = (f4d(i, j, k, lam) - f4d(i, j - 1, k, lam))/ &
     332            0 :                  (y_grid(j) - y_grid(j - 1))
     333              :       end if
     334              : 
     335              :       ! z derivative
     336        38386 :       if (nz == 1) then
     337           18 :          df_dz = 0.0_dp
     338        38368 :       else if (k > 1 .and. k < nz) then
     339        38368 :          h_minus = z_grid(k) - z_grid(k - 1)
     340        38368 :          h_plus = z_grid(k + 1) - z_grid(k)
     341        38368 :          d_minus = (f4d(i, j, k, lam) - f4d(i, j, k - 1, lam))/h_minus
     342        38368 :          d_plus = (f4d(i, j, k + 1, lam) - f4d(i, j, k, lam))/h_plus
     343        38368 :          df_dz = (h_plus*d_minus + h_minus*d_plus)/(h_minus + h_plus)
     344            0 :       else if (k == 1) then
     345              :          df_dz = (f4d(i, j, k + 1, lam) - f4d(i, j, k, lam))/ &
     346            0 :                  (z_grid(k + 1) - z_grid(k))
     347              :       else
     348              :          df_dz = (f4d(i, j, k, lam) - f4d(i, j, k - 1, lam))/ &
     349            0 :                  (z_grid(k) - z_grid(k - 1))
     350              :       end if
     351              : 
     352        38386 :    end subroutine compute_derivatives_at_point_4d
     353              : 
     354              : 
     355            1 :    function hermite_tensor_interp3d(x_val, y_val, z_val, x_grid, y_grid, &
     356            1 :                                     z_grid, f_values) result(f_interp)
     357              :       real(dp), intent(in) :: x_val, y_val, z_val
     358              :       real(dp), intent(in) :: x_grid(:), y_grid(:), z_grid(:)
     359              :       real(dp), intent(in) :: f_values(:, :, :)
     360              :       real(dp) :: f_interp
     361              : 
     362              :       integer :: i_x, i_y, i_z, ix, iy, iz
     363              :       integer :: ix_max, iy_max, iz_max
     364              :       integer :: nx, ny, nz
     365              :       real(dp) :: t_x, t_y, t_z, dx, dy, dz, f_sum
     366              :       real(dp) :: dx_values(2, 2, 2), dy_values(2, 2, 2), dz_values(2, 2, 2)
     367              :       real(dp) :: values(2, 2, 2)
     368              :       real(dp) :: h_x(2), h_y(2), h_z(2)
     369              :       real(dp) :: hx_d(2), hy_d(2), hz_d(2)
     370              : 
     371            1 :       nx = size(x_grid)
     372            1 :       ny = size(y_grid)
     373            1 :       nz = size(z_grid)
     374              : 
     375              :       call find_containing_cell(x_val, y_val, z_val, x_grid, y_grid, z_grid, &
     376            1 :                                 i_x, i_y, i_z, t_x, t_y, t_z)
     377              : 
     378            1 :       ix_max = 1
     379            1 :       iy_max = 1
     380            1 :       iz_max = 1
     381              : 
     382            1 :       if (nx == 1) then
     383            0 :          i_x = 1
     384            0 :          t_x = 0.0_dp
     385            0 :          ix_max = 0
     386              :       end if
     387            1 :       if (ny == 1) then
     388            1 :          i_y = 1
     389            1 :          t_y = 0.0_dp
     390            1 :          iy_max = 0
     391              :       end if
     392            1 :       if (nz == 1) then
     393            1 :          i_z = 1
     394            1 :          t_z = 0.0_dp
     395            1 :          iz_max = 0
     396              :       end if
     397              : 
     398              :       if ((nx > 1 .and. (i_x < 1 .or. i_x >= nx)) .or. &
     399            1 :           (ny > 1 .and. (i_y < 1 .or. i_y >= ny)) .or. &
     400              :           (nz > 1 .and. (i_z < 1 .or. i_z >= nz))) then
     401              :          call find_nearest_point(x_val, y_val, z_val, x_grid, y_grid, z_grid, &
     402            0 :                                  i_x, i_y, i_z)
     403            0 :          f_interp = f_values(i_x, i_y, i_z)
     404            0 :          return
     405              :       end if
     406              : 
     407            1 :       if (nx > 1) then
     408            1 :          dx = x_grid(i_x + 1) - x_grid(i_x)
     409              :       else
     410              :          dx = 0.0_dp
     411              :       end if
     412            1 :       if (ny > 1) then
     413            0 :          dy = y_grid(i_y + 1) - y_grid(i_y)
     414              :       else
     415              :          dy = 0.0_dp
     416              :       end if
     417            1 :       if (nz > 1) then
     418            0 :          dz = z_grid(i_z + 1) - z_grid(i_z)
     419              :       else
     420              :          dz = 0.0_dp
     421              :       end if
     422              : 
     423            2 :       do iz = 0, iz_max
     424            3 :          do iy = 0, iy_max
     425            4 :             do ix = 0, ix_max
     426            2 :                values(ix + 1, iy + 1, iz + 1) = f_values(i_x + ix, i_y + iy, i_z + iz)
     427              :                call compute_derivatives_at_point(f_values, i_x + ix, i_y + iy, i_z + iz, &
     428              :                                                  nx, ny, nz, x_grid, y_grid, z_grid, &
     429              :                                                  dx_values(ix + 1, iy + 1, iz + 1), &
     430              :                                                  dy_values(ix + 1, iy + 1, iz + 1), &
     431            3 :                                                  dz_values(ix + 1, iy + 1, iz + 1))
     432              :             end do
     433              :          end do
     434              :       end do
     435              : 
     436            3 :       h_x = [h00(t_x), h01(t_x)]
     437            3 :       hx_d = [h10(t_x), h11(t_x)]
     438            3 :       h_y = [h00(t_y), h01(t_y)]
     439            3 :       hy_d = [h10(t_y), h11(t_y)]
     440            3 :       h_z = [h00(t_z), h01(t_z)]
     441            3 :       hz_d = [h10(t_z), h11(t_z)]
     442              : 
     443            1 :       f_sum = 0.0_dp
     444            2 :       do iz = 1, iz_max + 1
     445            3 :          do iy = 1, iy_max + 1
     446            4 :             do ix = 1, ix_max + 1
     447            2 :                f_sum = f_sum + h_x(ix)*h_y(iy)*h_z(iz)*values(ix, iy, iz)
     448            2 :                f_sum = f_sum + hx_d(ix)*h_y(iy)*h_z(iz)*dx*dx_values(ix, iy, iz)
     449            2 :                f_sum = f_sum + h_x(ix)*hy_d(iy)*h_z(iz)*dy*dy_values(ix, iy, iz)
     450            3 :                f_sum = f_sum + h_x(ix)*h_y(iy)*hz_d(iz)*dz*dz_values(ix, iy, iz)
     451              :             end do
     452              :          end do
     453              :       end do
     454              : 
     455            1 :       f_interp = f_sum
     456              :    end function hermite_tensor_interp3d
     457              : 
     458            6 :    subroutine compute_derivatives_at_point(f, i, j, k, nx, ny, nz, &
     459            2 :                                            x_grid, y_grid, z_grid, &
     460              :                                            df_dx, df_dy, df_dz)
     461              :       real(dp), intent(in) :: f(:, :, :)
     462              :       integer, intent(in) :: i, j, k, nx, ny, nz
     463              :       real(dp), intent(in) :: x_grid(:), y_grid(:), z_grid(:)
     464              :       real(dp), intent(out) :: df_dx, df_dy, df_dz
     465              :       real(dp) :: h_minus, h_plus, d_minus, d_plus
     466              : 
     467            2 :       if (nx == 1) then
     468            0 :          df_dx = 0.0_dp
     469            2 :       else if (i > 1 .and. i < nx) then
     470            2 :          h_minus = x_grid(i) - x_grid(i - 1)
     471            2 :          h_plus = x_grid(i + 1) - x_grid(i)
     472            2 :          d_minus = (f(i, j, k) - f(i - 1, j, k))/h_minus
     473            2 :          d_plus = (f(i + 1, j, k) - f(i, j, k))/h_plus
     474            2 :          df_dx = (h_plus*d_minus + h_minus*d_plus)/(h_minus + h_plus)
     475            0 :       else if (i == 1) then
     476              :          df_dx = (f(i + 1, j, k) - f(i, j, k))/ &
     477            0 :                  (x_grid(i + 1) - x_grid(i))
     478              :       else
     479              :          df_dx = (f(i, j, k) - f(i - 1, j, k))/ &
     480            0 :                  (x_grid(i) - x_grid(i - 1))
     481              :       end if
     482              : 
     483            2 :       if (ny == 1) then
     484            2 :          df_dy = 0.0_dp
     485            0 :       else if (j > 1 .and. j < ny) then
     486            0 :          h_minus = y_grid(j) - y_grid(j - 1)
     487            0 :          h_plus = y_grid(j + 1) - y_grid(j)
     488            0 :          d_minus = (f(i, j, k) - f(i, j - 1, k))/h_minus
     489            0 :          d_plus = (f(i, j + 1, k) - f(i, j, k))/h_plus
     490            0 :          df_dy = (h_plus*d_minus + h_minus*d_plus)/(h_minus + h_plus)
     491            0 :       else if (j == 1) then
     492              :          df_dy = (f(i, j + 1, k) - f(i, j, k))/ &
     493            0 :                  (y_grid(j + 1) - y_grid(j))
     494              :       else
     495              :          df_dy = (f(i, j, k) - f(i, j - 1, k))/ &
     496            0 :                  (y_grid(j) - y_grid(j - 1))
     497              :       end if
     498              : 
     499            2 :       if (nz == 1) then
     500            2 :          df_dz = 0.0_dp
     501            0 :       else if (k > 1 .and. k < nz) then
     502            0 :          h_minus = z_grid(k) - z_grid(k - 1)
     503            0 :          h_plus = z_grid(k + 1) - z_grid(k)
     504            0 :          d_minus = (f(i, j, k) - f(i, j, k - 1))/h_minus
     505            0 :          d_plus = (f(i, j, k + 1) - f(i, j, k))/h_plus
     506            0 :          df_dz = (h_plus*d_minus + h_minus*d_plus)/(h_minus + h_plus)
     507            0 :       else if (k == 1) then
     508              :          df_dz = (f(i, j, k + 1) - f(i, j, k))/ &
     509            0 :                  (z_grid(k + 1) - z_grid(k))
     510              :       else
     511              :          df_dz = (f(i, j, k) - f(i, j, k - 1))/ &
     512            0 :                  (z_grid(k) - z_grid(k - 1))
     513              :       end if
     514              : 
     515            2 :    end subroutine compute_derivatives_at_point
     516              : 
     517              :    !---------------------------------------------------------------------------
     518              :    ! Hermite basis functions
     519              :    !---------------------------------------------------------------------------
     520           24 :    function h00(t) result(h)
     521              :       real(dp), intent(in) :: t
     522              :       real(dp) :: h
     523           24 :       h = 2.0_dp*t**3 - 3.0_dp*t**2 + 1.0_dp
     524           24 :    end function h00
     525              : 
     526           24 :    function h10(t) result(h)
     527              :       real(dp), intent(in) :: t
     528              :       real(dp) :: h
     529           24 :       h = t**3 - 2.0_dp*t**2 + t
     530           24 :    end function h10
     531              : 
     532           24 :    function h01(t) result(h)
     533              :       real(dp), intent(in) :: t
     534              :       real(dp) :: h
     535           24 :       h = -2.0_dp*t**3 + 3.0_dp*t**2
     536           24 :    end function h01
     537              : 
     538           24 :    function h11(t) result(h)
     539              :       real(dp), intent(in) :: t
     540              :       real(dp) :: h
     541           24 :       h = t**3 - t**2
     542           24 :    end function h11
     543              : 
     544              : end module hermite_interp
        

Generated by: LCOV version 2.0-1