LCOV - code coverage report
Current view: top level - colors/private - linear_interp.f90 (source / functions) Coverage Total Hit
Test: coverage.info Lines: 0.0 % 163 0
Test Date: 2026-02-18 10:38:02 Functions: 0.0 % 6 0

            Line data    Source code
       1              : ! ***********************************************************************
       2              : !
       3              : !   Copyright (C) 2025  Niall Miller & The MESA Team
       4              : !
       5              : !   This program is free software: you can redistribute it and/or modify
       6              : !   it under the terms of the GNU Lesser General Public License
       7              : !   as published by the Free Software Foundation,
       8              : !   either version 3 of the License, or (at your option) any later version.
       9              : !
      10              : !   This program is distributed in the hope that it will be useful,
      11              : !   but WITHOUT ANY WARRANTY; without even the implied warranty of
      12              : !   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
      13              : !   See the GNU Lesser General Public License for more details.
      14              : !
      15              : !   You should have received a copy of the GNU Lesser General Public License
      16              : !   along with this program. If not, see <https://www.gnu.org/licenses/>.
      17              : !
      18              : ! ***********************************************************************
      19              : 
      20              : ! ***********************************************************************
      21              : ! Linear interpolation module for spectral energy distributions (SEDs)
      22              : ! ***********************************************************************
      23              : 
      24              : module linear_interp
      25              :    use const_def, only: dp
      26              :    use colors_utils, only: dilute_flux
      27              :    use utils_lib, only: mesa_error
      28              :    implicit none
      29              : 
      30              :    private
      31              :    public :: construct_sed_linear, trilinear_interp
      32              : 
      33              : contains
      34              : 
      35              :    !---------------------------------------------------------------------------
      36              :    ! Main entry point: Construct a SED using linear interpolation
      37              :    !---------------------------------------------------------------------------
      38              : 
      39            0 :    subroutine construct_sed_linear(teff, log_g, metallicity, R, d, file_names, &
      40              :                                    lu_teff, lu_logg, lu_meta, stellar_model_dir, &
      41              :                                    wavelengths, fluxes)
      42              : 
      43              :       real(dp), intent(in) :: teff, log_g, metallicity, R, d
      44              :       real(dp), intent(in) :: lu_teff(:), lu_logg(:), lu_meta(:)
      45              :       character(len=*), intent(in) :: stellar_model_dir
      46              :       character(len=100), intent(in) :: file_names(:)
      47              :       real(dp), dimension(:), allocatable, intent(out) :: wavelengths, fluxes
      48              : 
      49              :       integer :: i, n_lambda, status, n_teff, n_logg, n_meta
      50            0 :       real(dp), dimension(:), allocatable :: interp_flux, diluted_flux
      51            0 :       real(dp), dimension(:, :, :, :), allocatable :: precomputed_flux_cube
      52            0 :       real(dp), dimension(:, :, :), allocatable :: flux_cube_lambda
      53              :       real(dp) :: min_flux, max_flux, mean_flux, progress_pct
      54              : 
      55              :       ! Parameter grids
      56            0 :       real(dp), allocatable :: teff_grid(:), logg_grid(:), meta_grid(:)
      57              :       character(len=256) :: bin_filename, clean_path
      58              :       logical :: file_exists
      59              : 
      60              :       ! Clean up any double slashes in the path
      61            0 :       clean_path = trim(stellar_model_dir)
      62            0 :       if (clean_path(len_trim(clean_path):len_trim(clean_path)) == '/') then
      63            0 :          bin_filename = trim(clean_path)//'flux_cube.bin'
      64              :       else
      65            0 :          bin_filename = trim(clean_path)//'/flux_cube.bin'
      66              :       end if
      67              : 
      68              :       ! Check if file exists first
      69            0 :       INQUIRE (file=bin_filename, EXIST=file_exists)
      70              : 
      71            0 :       if (.not. file_exists) then
      72            0 :          print *, 'Missing required binary file for interpolation'
      73            0 :          call mesa_error(__FILE__, __LINE__)
      74              :       end if
      75              : 
      76              :       ! Load the data from binary file
      77              :       call load_binary_data(bin_filename, teff_grid, logg_grid, meta_grid, &
      78            0 :                             wavelengths, precomputed_flux_cube, status)
      79              : 
      80            0 :       if (status /= 0) then
      81            0 :          print *, 'Binary data loading error'
      82            0 :          call mesa_error(__FILE__, __LINE__)
      83              :       end if
      84              : 
      85            0 :       n_teff = size(teff_grid)
      86            0 :       n_logg = size(logg_grid)
      87            0 :       n_meta = size(meta_grid)
      88            0 :       n_lambda = size(wavelengths)
      89              : 
      90              :       ! Allocate space for interpolated flux
      91            0 :       allocate (interp_flux(n_lambda))
      92            0 :       allocate (flux_cube_lambda(n_teff, n_logg, n_meta))
      93              : 
      94              :       ! Perform trilinear interpolation for each wavelength
      95            0 :       do i = 1, n_lambda
      96              : 
      97              :          ! Extract the 3D grid for this wavelength
      98            0 :          flux_cube_lambda = precomputed_flux_cube(:, :, :, i)
      99              : 
     100              :          ! Simple trilinear interpolation at the target parameters
     101              :          interp_flux(i) = trilinear_interp(teff, log_g, metallicity, &
     102            0 :                                            teff_grid, logg_grid, meta_grid, flux_cube_lambda)
     103              :       end do
     104              : 
     105              : 
     106            0 :       deallocate(flux_cube_lambda)
     107              : 
     108              :       ! Calculate statistics for validation
     109              :       min_flux = minval(interp_flux)
     110              :       max_flux = maxval(interp_flux)
     111            0 :       mean_flux = sum(interp_flux)/n_lambda
     112              : 
     113              :       ! Apply distance dilution to get observed flux
     114            0 :       allocate (diluted_flux(n_lambda))
     115            0 :       call dilute_flux(interp_flux, R, d, diluted_flux)
     116            0 :       fluxes = diluted_flux
     117              : 
     118              :       ! Calculate statistics after dilution
     119              :       min_flux = minval(diluted_flux)
     120              :       max_flux = maxval(diluted_flux)
     121              :       mean_flux = sum(diluted_flux)/n_lambda
     122              : 
     123            0 :    end subroutine construct_sed_linear
     124              : 
     125              :    !---------------------------------------------------------------------------
     126              :    ! Load data from binary file
     127              :    !---------------------------------------------------------------------------
     128            0 :    subroutine load_binary_data(filename, teff_grid, logg_grid, meta_grid, &
     129              :                                wavelengths, flux_cube, status)
     130              :       character(len=*), intent(in) :: filename
     131              :       real(dp), allocatable, intent(out) :: teff_grid(:), logg_grid(:), meta_grid(:)
     132              :       real(dp), allocatable, intent(out) :: wavelengths(:)
     133              :       real(dp), allocatable, intent(out) :: flux_cube(:, :, :, :)
     134              :       integer, intent(out) :: status
     135              : 
     136              :       integer :: unit, n_teff, n_logg, n_meta, n_lambda
     137              : 
     138            0 :       unit = 99
     139            0 :       status = 0
     140              : 
     141              :       ! Open the binary file
     142            0 :       open (unit=unit, file=filename, status='OLD', ACCESS='STREAM', FORM='UNFORMATTED', iostat=status)
     143            0 :       if (status /= 0) then
     144              :          !print *, 'Error opening binary file:', trim(filename)
     145              :          return
     146              :       end if
     147              : 
     148              :       ! Read dimensions
     149            0 :       read (unit, iostat=status) n_teff, n_logg, n_meta, n_lambda
     150            0 :       if (status /= 0) then
     151              :          !print *, 'Error reading dimensions from binary file'
     152            0 :          close (unit)
     153            0 :          return
     154              :       end if
     155              : 
     156              :       ! Allocate arrays based on dimensions
     157            0 :       allocate (teff_grid(n_teff), STAT=status)
     158            0 :       if (status /= 0) then
     159              :          !print *, 'Error allocating teff_grid array'
     160            0 :          close (unit)
     161            0 :          return
     162              :       end if
     163              : 
     164            0 :       allocate (logg_grid(n_logg), STAT=status)
     165            0 :       if (status /= 0) then
     166              :          !print *, 'Error allocating logg_grid array'
     167            0 :          close (unit)
     168            0 :          return
     169              :       end if
     170              : 
     171            0 :       allocate (meta_grid(n_meta), STAT=status)
     172            0 :       if (status /= 0) then
     173              :          !print *, 'Error allocating meta_grid array'
     174            0 :          close (unit)
     175            0 :          return
     176              :       end if
     177              : 
     178            0 :       allocate (wavelengths(n_lambda), STAT=status)
     179            0 :       if (status /= 0) then
     180              :          !print *, 'Error allocating wavelengths array'
     181            0 :          close (unit)
     182            0 :          return
     183              :       end if
     184              : 
     185            0 :       allocate (flux_cube(n_teff, n_logg, n_meta, n_lambda), STAT=status)
     186            0 :       if (status /= 0) then
     187              :          !print *, 'Error allocating flux_cube array'
     188            0 :          close (unit)
     189            0 :          return
     190              :       end if
     191              : 
     192              :       ! Read grid arrays
     193            0 :       read (unit, iostat=status) teff_grid
     194            0 :       if (status /= 0) then
     195              :          !print *, 'Error reading teff_grid'
     196              :          GOTO 999  ! Cleanup and return
     197              :       end if
     198              : 
     199            0 :       read (unit, iostat=status) logg_grid
     200            0 :       if (status /= 0) then
     201              :          !print *, 'Error reading logg_grid'
     202              :          GOTO 999  ! Cleanup and return
     203              :       end if
     204              : 
     205            0 :       read (unit, iostat=status) meta_grid
     206            0 :       if (status /= 0) then
     207              :          !print *, 'Error reading meta_grid'
     208              :          GOTO 999  ! Cleanup and return
     209              :       end if
     210              : 
     211            0 :       read (unit, iostat=status) wavelengths
     212            0 :       if (status /= 0) then
     213              :          !print *, 'Error reading wavelengths'
     214              :          GOTO 999  ! Cleanup and return
     215              :       end if
     216              : 
     217              :       ! Read flux cube
     218            0 :       read (unit, iostat=status) flux_cube
     219            0 :       if (status /= 0) then
     220              :          !print *, 'Error reading flux_cube'
     221              :          GOTO 999  ! Cleanup and return
     222              :       end if
     223              : 
     224              :       ! Close file and return success
     225            0 :       close (unit)
     226            0 :       return
     227              : 
     228              : 999   CONTINUE
     229              :       ! Cleanup on error
     230            0 :       close (unit)
     231            0 :       return
     232              : 
     233              : ! After reading the grid arrays
     234              : !print *, 'Teff grid min/max:', minval(teff_grid), maxval(teff_grid)
     235              : !print *, 'logg grid min/max:', minval(logg_grid), maxval(logg_grid)
     236              : !print *, 'meta grid min/max:', minval(meta_grid), maxval(meta_grid)
     237              : 
     238              :    end subroutine load_binary_data
     239              : 
     240              :    !---------------------------------------------------------------------------
     241              :    ! Simple trilinear interpolation function
     242              :    !---------------------------------------------------------------------------
     243              : !---------------------------------------------------------------------------
     244              : ! Log-space trilinear interpolation function with normalization
     245              : !---------------------------------------------------------------------------
     246            0 :    function trilinear_interp(x_val, y_val, z_val, x_grid, y_grid, z_grid, f_values) result(f_interp)
     247              :       real(dp), intent(in) :: x_val, y_val, z_val
     248              :       real(dp), intent(in) :: x_grid(:), y_grid(:), z_grid(:)
     249              :       real(dp), intent(in) :: f_values(:, :, :)
     250              :       real(dp) :: f_interp
     251              :       ! Compute log-space result
     252              :       real(dp) :: log_result
     253              :       integer :: i_x, i_y, i_z
     254              :       real(dp) :: t_x, t_y, t_z
     255              :       real(dp) :: c000, c001, c010, c011, c100, c101, c110, c111
     256              :       real(dp) :: c00, c01, c10, c11, c0, c1
     257              :       real(dp), parameter :: tiny_value = 1.0e-10_dp
     258              : 
     259              :       ! Find containing cell and parameter values using binary search
     260              :       call find_containing_cell(x_val, y_val, z_val, x_grid, y_grid, z_grid, &
     261            0 :                                 i_x, i_y, i_z, t_x, t_y, t_z)
     262              : 
     263              :       ! Boundary safety check
     264            0 :       if (i_x < lbound(x_grid,1)) i_x = lbound(x_grid,1)
     265            0 :       if (i_y < lbound(y_grid,1)) i_y = lbound(y_grid,1)
     266            0 :       if (i_z < lbound(z_grid,1)) i_z = lbound(z_grid,1)
     267            0 :       if (i_x >= ubound(x_grid,1)) i_x = ubound(x_grid,1) - 1
     268            0 :       if (i_y >= ubound(y_grid,1)) i_y = ubound(y_grid,1) - 1
     269            0 :       if (i_z >= ubound(z_grid,1)) i_z = ubound(z_grid,1) - 1
     270              : 
     271              :       ! Force interpolation parameters to be in [0,1]
     272            0 :       t_x = max(0.0_dp, MIN(1.0_dp, t_x))
     273            0 :       t_y = max(0.0_dp, MIN(1.0_dp, t_y))
     274            0 :       t_z = max(0.0_dp, MIN(1.0_dp, t_z))
     275              : 
     276              :       ! Get the corners of the cube with safety checks
     277            0 :       c000 = max(tiny_value, f_values(i_x, i_y, i_z))
     278            0 :       c001 = max(tiny_value, f_values(i_x, i_y, i_z + 1))
     279            0 :       c010 = max(tiny_value, f_values(i_x, i_y + 1, i_z))
     280            0 :       c011 = max(tiny_value, f_values(i_x, i_y + 1, i_z + 1))
     281            0 :       c100 = max(tiny_value, f_values(i_x + 1, i_y, i_z))
     282            0 :       c101 = max(tiny_value, f_values(i_x + 1, i_y, i_z + 1))
     283            0 :       c110 = max(tiny_value, f_values(i_x + 1, i_y + 1, i_z))
     284            0 :       c111 = max(tiny_value, f_values(i_x + 1, i_y + 1, i_z + 1))
     285              : 
     286              :       ! Try standard linear interpolation first (safer)
     287            0 :       c00 = c000*(1.0_dp - t_x) + c100*t_x
     288            0 :       c01 = c001*(1.0_dp - t_x) + c101*t_x
     289            0 :       c10 = c010*(1.0_dp - t_x) + c110*t_x
     290            0 :       c11 = c011*(1.0_dp - t_x) + c111*t_x
     291              : 
     292            0 :       c0 = c00*(1.0_dp - t_y) + c10*t_y
     293            0 :       c1 = c01*(1.0_dp - t_y) + c11*t_y
     294              : 
     295            0 :       f_interp = c0*(1.0_dp - t_z) + c1*t_z
     296              : 
     297              :       ! If the linear result is valid and non-zero, try log space
     298            0 :       if (f_interp > tiny_value) then
     299              :          ! Perform log-space interpolation
     300            0 :          c00 = log(c000)*(1.0_dp - t_x) + log(c100)*t_x
     301            0 :          c01 = log(c001)*(1.0_dp - t_x) + log(c101)*t_x
     302            0 :          c10 = log(c010)*(1.0_dp - t_x) + log(c110)*t_x
     303            0 :          c11 = log(c011)*(1.0_dp - t_x) + log(c111)*t_x
     304              : 
     305            0 :          c0 = c00*(1.0_dp - t_y) + c10*t_y
     306            0 :          c1 = c01*(1.0_dp - t_y) + c11*t_y
     307              : 
     308            0 :          log_result = c0*(1.0_dp - t_z) + c1*t_z
     309              : 
     310              :          ! Only use the log-space result if it's valid
     311            0 :          if (log_result == log_result) then  ! NaN check
     312            0 :             f_interp = EXP(log_result)
     313              :          end if
     314              :       end if
     315              : 
     316              :       ! Final sanity check
     317            0 :       if (f_interp /= f_interp .or. f_interp <= 0.0_dp) then
     318              :          ! If we somehow still got an invalid result, use nearest neighbor
     319            0 :          call find_nearest_point(x_val, y_val, z_val, x_grid, y_grid, z_grid, i_x, i_y, i_z)
     320            0 :          f_interp = max(tiny_value, f_values(i_x, i_y, i_z))
     321              :       end if
     322            0 :    end function trilinear_interp
     323              : 
     324              :    !---------------------------------------------------------------------------
     325              :    ! Find the cell containing the interpolation point
     326              :    !---------------------------------------------------------------------------
     327            0 :    subroutine find_containing_cell(x_val, y_val, z_val, x_grid, y_grid, z_grid, &
     328              :                                    i_x, i_y, i_z, t_x, t_y, t_z)
     329              :       real(dp), intent(in) :: x_val, y_val, z_val
     330              :       real(dp), intent(in) :: x_grid(:), y_grid(:), z_grid(:)
     331              :       integer, intent(out) :: i_x, i_y, i_z
     332              :       real(dp), intent(out) :: t_x, t_y, t_z
     333              : 
     334              :       ! Find x interval
     335            0 :       call find_interval(x_grid, x_val, i_x, t_x)
     336              : 
     337              :       ! Find y interval
     338            0 :       call find_interval(y_grid, y_val, i_y, t_y)
     339              : 
     340              :       ! Find z interval
     341            0 :       call find_interval(z_grid, z_val, i_z, t_z)
     342            0 :    end subroutine find_containing_cell
     343              : 
     344              :    !---------------------------------------------------------------------------
     345              :    ! Find the interval in a sorted array containing a value
     346              :    !---------------------------------------------------------------------------
     347            0 :    subroutine find_interval(x, val, i, t)
     348              :       real(dp), intent(in) :: x(:), val
     349              :       integer, intent(out) :: i
     350              :       real(dp), intent(out) :: t
     351              : 
     352              :       integer :: n, lo, hi, mid
     353              :       logical :: dummy_axis
     354              : 
     355            0 :       n = size(x)
     356              : 
     357              :       ! Detect dummy axis
     358            0 :       dummy_axis = all(x == 0.0_dp) .or. all(x == 999.0_dp) .or. all(x == -999.0_dp)
     359              : 
     360              :       if (dummy_axis) then
     361              :          ! Collapse: use the first element of the axis, no interpolation
     362            0 :          i = 1
     363            0 :          t = 0.0_dp
     364            0 :          return
     365              :       end if
     366              : 
     367              :       ! --- ORIGINAL CODE BELOW ---
     368            0 :       if (val <= x(1)) then
     369            0 :          i = 1
     370            0 :          t = 0.0_dp
     371            0 :          return
     372            0 :       else if (val >= x(n)) then
     373            0 :          i = n - 1
     374            0 :          t = 1.0_dp
     375            0 :          return
     376              :       end if
     377              : 
     378              :       lo = 1
     379              :       hi = n
     380            0 :       do while (hi - lo > 1)
     381            0 :          mid = (lo + hi)/2
     382            0 :          if (val >= x(mid)) then
     383              :             lo = mid
     384              :          else
     385            0 :             hi = mid
     386              :          end if
     387              :       end do
     388              : 
     389            0 :       i = lo
     390            0 :       t = (val - x(i))/(x(i + 1) - x(i))
     391              :    end subroutine find_interval
     392              : 
     393              :    !---------------------------------------------------------------------------
     394              :    ! Find the nearest grid point
     395              :    !---------------------------------------------------------------------------
     396            0 :    subroutine find_nearest_point(x_val, y_val, z_val, x_grid, y_grid, z_grid, &
     397              :                                  i_x, i_y, i_z)
     398              :       real(dp), intent(in) :: x_val, y_val, z_val
     399              :       real(dp), intent(in) :: x_grid(:), y_grid(:), z_grid(:)
     400              :       integer, intent(out) :: i_x, i_y, i_z
     401              : 
     402              :       integer :: i
     403              :       real(dp) :: min_dist, dist
     404              : 
     405              :       ! Find nearest x grid point
     406            0 :       min_dist = abs(x_val - x_grid(1))
     407            0 :       i_x = 1
     408            0 :       do i = 2, size(x_grid)
     409            0 :          dist = abs(x_val - x_grid(i))
     410            0 :          if (dist < min_dist) then
     411            0 :             min_dist = dist
     412            0 :             i_x = i
     413              :          end if
     414              :       end do
     415              : 
     416              :       ! Find nearest y grid point
     417            0 :       min_dist = abs(y_val - y_grid(1))
     418            0 :       i_y = 1
     419            0 :       do i = 2, size(y_grid)
     420            0 :          dist = abs(y_val - y_grid(i))
     421            0 :          if (dist < min_dist) then
     422            0 :             min_dist = dist
     423            0 :             i_y = i
     424              :          end if
     425              :       end do
     426              : 
     427              :       ! Find nearest z grid point
     428            0 :       min_dist = abs(z_val - z_grid(1))
     429            0 :       i_z = 1
     430            0 :       do i = 2, size(z_grid)
     431            0 :          dist = abs(z_val - z_grid(i))
     432            0 :          if (dist < min_dist) then
     433            0 :             min_dist = dist
     434            0 :             i_z = i
     435              :          end if
     436              :       end do
     437            0 :    end subroutine find_nearest_point
     438              : 
     439              : end module linear_interp
        

Generated by: LCOV version 2.0-1