LCOV - code coverage report
Current view: top level - colors/private - bolometric.f90 (source / functions) Coverage Total Hit
Test: coverage.info Lines: 0.0 % 69 0
Test Date: 2026-01-06 18:03:11 Functions: 0.0 % 4 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              : module bolometric
      21              : 
      22              :    use const_def, only: dp
      23              :    use colors_utils, only: romberg_integration, load_lookup_table
      24              :    use hermite_interp, only: construct_sed_hermite
      25              :    use linear_interp,  only: construct_sed_linear
      26              :    use knn_interp,     only: construct_sed_knn
      27              : 
      28              :    implicit none
      29              : 
      30              :    private
      31              :    public :: calculate_bolometric
      32              : 
      33              : contains
      34              : 
      35              :    !****************************
      36              :    ! Calculate Bolometric Photometry Using Multiple SEDs
      37              :    !****************************
      38            0 :    subroutine calculate_bolometric(teff, log_g, metallicity, R, d, bolometric_magnitude, &
      39              :                                    bolometric_flux, wavelengths, fluxes, sed_filepath, interpolation_radius)
      40              :       real(dp), intent(in) :: teff, log_g, metallicity, R, d
      41              :       character(len=*), intent(in) :: sed_filepath
      42              :       real(dp), intent(out) :: bolometric_magnitude, bolometric_flux, interpolation_radius
      43              :       real(dp), dimension(:), allocatable, intent(out) :: wavelengths, fluxes
      44              : 
      45            0 :       real(dp), allocatable :: lu_logg(:), lu_meta(:), lu_teff(:)
      46              : 
      47            0 :       REAL, dimension(:, :), allocatable :: lookup_table
      48            0 :       character(len=100), allocatable :: file_names(:)
      49              :       character(len=256) :: lookup_file
      50              :       character(len=32) :: interpolation_method
      51              : 
      52              : 
      53              : 
      54            0 :       lookup_file = trim(sed_filepath)//'/lookup_table.csv'
      55              : 
      56              :       call load_lookup_table(lookup_file, lookup_table, file_names, lu_logg, lu_meta, lu_teff)
      57              : 
      58            0 :       interpolation_method = 'Hermite'   ! or 'Linear' / 'KNN' later
      59              : 
      60              :       ! Quantify how far (teff, log_g, metallicity) is from the grid points
      61              :       interpolation_radius = compute_interp_radius(teff, log_g, metallicity, &
      62            0 :                                             lu_teff, lu_logg, lu_meta)
      63              : 
      64            0 :       select case (interpolation_method)
      65              :       case ('Hermite','hermite','HERMITE')
      66              :          call construct_sed_hermite(teff, log_g, metallicity, R, d, file_names, &
      67              :                                     lu_teff, lu_logg, lu_meta, sed_filepath, &
      68            0 :                                     wavelengths, fluxes)
      69              : 
      70              :       case ('Linear','linear','LINEAR')
      71              :          call construct_sed_linear(teff, log_g, metallicity, R, d, file_names, &
      72              :                                    lu_teff, lu_logg, lu_meta, sed_filepath, &
      73            0 :                                    wavelengths, fluxes)
      74              : 
      75              :       case ('KNN','knn','Knn')
      76              :          call construct_sed_knn(teff, log_g, metallicity, R, d, file_names, &
      77              :                                 lu_teff, lu_logg, lu_meta, sed_filepath, &
      78            0 :                                 wavelengths, fluxes)
      79              : 
      80              :       case default
      81              :          ! Fallback: Hermite
      82              :          call construct_sed_hermite(teff, log_g, metallicity, R, d, file_names, &
      83              :                                     lu_teff, lu_logg, lu_meta, sed_filepath, &
      84            0 :                                     wavelengths, fluxes)
      85              :       end select
      86              : 
      87              :       ! Calculate bolometric flux and magnitude
      88            0 :       call calculate_bolometric_phot(wavelengths, fluxes, bolometric_magnitude, bolometric_flux)
      89            0 :    end subroutine calculate_bolometric
      90              : 
      91              : 
      92              :    !****************************
      93              :    ! Calculate Bolometric Magnitude and Flux
      94              :    !****************************
      95            0 :    subroutine calculate_bolometric_phot(wavelengths, fluxes, bolometric_magnitude, bolometric_flux)
      96              :       real(dp), dimension(:), intent(inout) :: wavelengths, fluxes
      97              :       real(dp), intent(out) :: bolometric_magnitude, bolometric_flux
      98              :       integer :: i
      99              : 
     100              :       ! Validate inputs and replace invalid wavelengths with 0
     101            0 :       do i = 1, size(wavelengths) - 1
     102            0 :          if (wavelengths(i) <= 0.0d0 .or. fluxes(i) < 0.0d0) then
     103            0 :             fluxes(i) = 0.0d0  ! Replace invalid wavelength with 0
     104              :          end if
     105              :       end do
     106              : 
     107              :       ! Call Romberg integration
     108            0 :       call romberg_integration(wavelengths, fluxes, bolometric_flux)
     109              : 
     110              :       ! Validate integration result
     111            0 :       if (bolometric_flux <= 0.0d0) then
     112            0 :          print *, "Error: Flux integration resulted in non-positive value."
     113            0 :          bolometric_magnitude = 99.0d0
     114            0 :          return
     115              :       end if
     116              : 
     117              :       ! Calculate bolometric magnitude
     118              :       if (bolometric_flux <= 0.0d0) then
     119              :          print *, "Error: Flux integration resulted in non-positive value."
     120              :          bolometric_magnitude = 99.0d0
     121              :          return
     122            0 :       else if (bolometric_flux < 1.0d-10) then
     123            0 :          print *, "Warning: Flux value is very small, precision might be affected."
     124              :       end if
     125              : 
     126            0 :       bolometric_magnitude = flux_to_magnitude(bolometric_flux)
     127              :    end subroutine calculate_bolometric_phot
     128              : 
     129              :    !****************************
     130              :    ! Convert Flux to Magnitude
     131              :    !****************************
     132            0 :    real(dp) function flux_to_magnitude(flux)
     133              :       real(dp), intent(in) :: flux
     134            0 :       if (flux <= 0.0d0) then
     135            0 :          print *, "Error: Flux must be positive to calculate magnitude."
     136            0 :          flux_to_magnitude = 99.0d0  ! Return an error value
     137              :       else
     138            0 :          flux_to_magnitude = -2.5d0*log10(flux)
     139              :       end if
     140            0 :    end function flux_to_magnitude
     141              : 
     142              : 
     143              : 
     144              : 
     145              : 
     146              : 
     147              : 
     148              : 
     149              :    !--------------------------------------------------------------------
     150              :    ! Scalar metric: distance to nearest grid point in normalized space
     151              :    !
     152              :    ! - Uses lu_teff, lu_logg, lu_meta as the available atmosphere grid.
     153              :    ! - Normalize each dimension to [0,1] using min/max of the grid.
     154              :    ! - Compute Euclidean distance to the nearest grid point in that
     155              :    !   normalized space.
     156              :    !
     157              :    ! interp_radius ~ 0  => sitting very close to an atmosphere point
     158              :    ! interp_radius ~ O(1) => deep in-between points / extrapolating
     159              :    !--------------------------------------------------------------------
     160              : 
     161            0 :    real(dp) function compute_interp_radius(teff, log_g, metallicity, &
     162            0 :                                           lu_teff, lu_logg, lu_meta)
     163              : 
     164              :       real(dp), intent(in) :: teff, log_g, metallicity
     165              :       real(dp), intent(in) :: lu_teff(:), lu_logg(:), lu_meta(:)
     166              : 
     167              :       real(dp) :: teff_min, teff_max, logg_min, logg_max, meta_min, meta_max
     168              :       real(dp) :: teff_range, logg_range, meta_range
     169              :       real(dp) :: norm_teff, norm_logg, norm_meta
     170              :       real(dp) :: grid_teff, grid_logg, grid_meta
     171              :       real(dp) :: d, d_min
     172              :       integer  :: i, n
     173              : 
     174              :       logical :: use_teff, use_logg, use_meta
     175              :       real(dp), parameter :: eps = 1.0d-12
     176              : 
     177              :       ! ---------------------------------------------------------
     178              :       ! Detect dummy columns (entire axis is 0 or 999 or -999)
     179              :       ! ---------------------------------------------------------
     180              :       use_teff = .not. ( all(lu_teff == 0.0d0)   .or. &
     181              :                          all(lu_teff == 999.0d0) .or. &
     182            0 :                          all(lu_teff == -999.0d0) )
     183              : 
     184              :       use_logg = .not. ( all(lu_logg == 0.0d0)   .or. &
     185              :                          all(lu_logg == 999.0d0) .or. &
     186            0 :                          all(lu_logg == -999.0d0) )
     187              : 
     188              :       use_meta = .not. ( all(lu_meta == 0.0d0)   .or. &
     189              :                          all(lu_meta == 999.0d0) .or. &
     190            0 :                          all(lu_meta == -999.0d0) )
     191              : 
     192              :       ! ---------------------------------------------------------
     193              :       ! Compute min/max only for VALID axes
     194              :       ! ---------------------------------------------------------
     195              : 
     196            0 :       if (use_teff) then
     197            0 :          teff_min = minval(lu_teff)
     198            0 :          teff_max = maxval(lu_teff)
     199            0 :          teff_range = max(teff_max - teff_min, eps)
     200            0 :          norm_teff = (teff - teff_min)/teff_range
     201              :       endif
     202              : 
     203            0 :       if (use_logg) then
     204            0 :          logg_min = minval(lu_logg)
     205            0 :          logg_max = maxval(lu_logg)
     206            0 :          logg_range = max(logg_max - logg_min, eps)
     207            0 :          norm_logg = (log_g - logg_min)/logg_range
     208              :       endif
     209              : 
     210            0 :       if (use_meta) then
     211            0 :          meta_min = minval(lu_meta)
     212            0 :          meta_max = maxval(lu_meta)
     213            0 :          meta_range = max(meta_max - meta_min, eps)
     214            0 :          norm_meta = (metallicity - meta_min)/meta_range
     215              :       endif
     216              : 
     217              :       ! ---------------------------------------------------------
     218              :       ! Compute minimum distance with dimension-dropping
     219              :       ! ---------------------------------------------------------
     220            0 :       d_min = huge(1.0d0)
     221            0 :       n    = size(lu_teff)
     222              : 
     223            0 :       do i = 1, n
     224              : 
     225            0 :          d = 0.0d0
     226              : 
     227            0 :          if (use_teff) then
     228            0 :             grid_teff = (lu_teff(i) - teff_min)/teff_range
     229            0 :             d = d + (norm_teff - grid_teff)**2
     230              :          end if
     231              : 
     232            0 :          if (use_logg) then
     233            0 :             grid_logg = (lu_logg(i) - logg_min)/logg_range
     234            0 :             d = d + (norm_logg - grid_logg)**2
     235              :          end if
     236              : 
     237            0 :          if (use_meta) then
     238            0 :             grid_meta = (lu_meta(i) - meta_min)/meta_range
     239            0 :             d = d + (norm_meta - grid_meta)**2
     240              :          end if
     241              : 
     242            0 :          d = sqrt(d)
     243              : 
     244            0 :          if (d < d_min) d_min = d
     245              :       end do
     246              : 
     247            0 :       compute_interp_radius = d_min
     248              : 
     249            0 :    end function compute_interp_radius
     250              : 
     251              : 
     252              : 
     253              : 
     254              : 
     255              : end module bolometric
        

Generated by: LCOV version 2.0-1