LCOV - code coverage report
Current view: top level - colors/private - synthetic.f90 (source / functions) Coverage Total Hit
Test: coverage.info Lines: 43.0 % 79 34
Test Date: 2026-01-29 18:28:55 Functions: 60.0 % 5 3

            Line data    Source code
       1              : ! ***********************************************************************
       2              : !
       3              : !   Copyright (C) 2025  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 synthetic
      21              : 
      22              :    use const_def, only: dp, clight
      23              :    use utils_lib, only: mkdir, folder_exists
      24              :    use colors_utils, only: remove_dat, romberg_integration
      25              :    use knn_interp, only: interpolate_array
      26              : 
      27              :    implicit none
      28              : 
      29              :    private
      30              :    public :: calculate_synthetic
      31              :    ! Export zero-point computation functions for precomputation at initialization
      32              :    public :: compute_vega_zero_point, compute_ab_zero_point, compute_st_zero_point
      33              : 
      34              : contains
      35              : 
      36              :    !****************************
      37              :    ! Calculate Synthetic Photometry Using SED and Filter
      38              :    ! Uses precomputed zero-point from filter data
      39              :    !****************************
      40            0 :    real(dp) function calculate_synthetic(temperature, gravity, metallicity, ierr, &
      41            0 :                                          wavelengths, fluxes, &
      42            0 :                                          filter_wavelengths, filter_trans, &
      43              :                                          zero_point_flux, &
      44              :                                          filter_name, make_sed, colors_results_directory)
      45              :       ! Input arguments
      46              :       real(dp), intent(in) :: temperature, gravity, metallicity
      47              :       character(len=*), intent(in) :: filter_name, colors_results_directory
      48              :       integer, intent(out) :: ierr
      49              : 
      50              :       real(dp), dimension(:), intent(in) :: wavelengths, fluxes
      51              :       real(dp), dimension(:), intent(in) :: filter_wavelengths, filter_trans
      52              :       real(dp), intent(in) :: zero_point_flux  ! precomputed at initialization
      53              :       logical, intent(in) :: make_sed
      54              : 
      55              :       ! Local variables
      56              :       real(dp), dimension(:), allocatable :: convolved_flux, filter_on_sed_grid
      57              :       character(len=256) :: csv_file
      58              :       character(len=1000) :: line
      59              :       real(dp) :: synthetic_flux
      60              :       integer :: max_size, i
      61              :       real(dp) :: wv, fl, cf, fwv, ftr
      62              : 
      63            0 :       ierr = 0
      64              : 
      65              :       ! Allocate working arrays
      66            0 :       allocate(convolved_flux(size(wavelengths)))
      67            0 :       allocate(filter_on_sed_grid(size(wavelengths)))
      68              : 
      69              :       ! Interpolate filter onto SED wavelength grid
      70            0 :       call interpolate_array(filter_wavelengths, filter_trans, wavelengths, filter_on_sed_grid)
      71              : 
      72              :       ! Convolve SED with filter
      73            0 :       convolved_flux = fluxes * filter_on_sed_grid
      74              : 
      75              :       ! Write SED to CSV if requested
      76            0 :       if (make_sed) then
      77            0 :          if (.not. folder_exists(trim(colors_results_directory))) call mkdir(trim(colors_results_directory))
      78            0 :          csv_file = trim(colors_results_directory)//'/'//trim(remove_dat(filter_name))//'_SED.csv'
      79              : 
      80            0 :          max_size = max(size(wavelengths), size(filter_wavelengths))
      81              : 
      82            0 :          open (unit=10, file=csv_file, status='REPLACE', action='write', iostat=ierr)
      83            0 :          if (ierr /= 0) then
      84            0 :             print *, "Error opening file for writing"
      85            0 :             deallocate(convolved_flux, filter_on_sed_grid)
      86              :             return
      87              :          end if
      88              : 
      89            0 :          write (10, '(A)') "wavelengths,fluxes,convolved_flux,filter_wavelengths,filter_trans"
      90              : 
      91            0 :          do i = 1, max_size
      92            0 :             wv = 0.0_dp; fl = 0.0_dp; cf = 0.0_dp; fwv = 0.0_dp; ftr = 0.0_dp
      93            0 :             if (i <= size(wavelengths)) wv = wavelengths(i)
      94            0 :             if (i <= size(fluxes)) fl = fluxes(i)
      95            0 :             if (i <= size(convolved_flux)) cf = convolved_flux(i)
      96            0 :             if (i <= size(filter_wavelengths)) fwv = filter_wavelengths(i)
      97            0 :             if (i <= size(filter_trans)) ftr = filter_trans(i)
      98              : 
      99              :             write (line, '(ES14.6, ",", ES14.6, ",", ES14.6, ",", ES14.6, ",", ES14.6)') &
     100            0 :                wv, fl, cf, fwv, ftr
     101            0 :             write (10, '(A)') trim(line)
     102              :          end do
     103            0 :          close (10)
     104              :       end if
     105              : 
     106              :       ! Calculate synthetic flux using photon-counting integration
     107            0 :       call calculate_synthetic_flux(wavelengths, convolved_flux, filter_on_sed_grid, synthetic_flux)
     108              : 
     109              :       ! Calculate magnitude
     110            0 :       if (zero_point_flux > 0.0_dp .and. synthetic_flux > 0.0_dp) then
     111            0 :          calculate_synthetic = -2.5d0 * log10(synthetic_flux / zero_point_flux)
     112              :       else
     113            0 :          if (zero_point_flux <= 0.0_dp) then
     114            0 :             print *, "Error: Zero point flux is zero or negative for filter ", trim(filter_name)
     115              :          end if
     116            0 :          if (synthetic_flux <= 0.0_dp) then
     117            0 :             print *, "Error: Synthetic flux is zero or negative for filter ", trim(filter_name)
     118              :          end if
     119              :          calculate_synthetic = huge(1.0_dp)
     120              :       end if
     121              : 
     122            0 :       deallocate(convolved_flux, filter_on_sed_grid)
     123            0 :    end function calculate_synthetic
     124              : 
     125              :    !****************************
     126              :    ! Calculate Synthetic Flux (photon-counting integration)
     127              :    !****************************
     128            0 :    subroutine calculate_synthetic_flux(wavelengths, convolved_flux, filter_on_sed_grid, synthetic_flux)
     129              :       real(dp), dimension(:), intent(in) :: wavelengths, convolved_flux, filter_on_sed_grid
     130              :       real(dp), intent(out) :: synthetic_flux
     131              : 
     132              :       real(dp) :: integrated_flux, integrated_filter
     133              : 
     134              :       ! Photon-counting: weight by wavelength
     135            0 :       call romberg_integration(wavelengths, convolved_flux * wavelengths, integrated_flux)
     136            0 :       call romberg_integration(wavelengths, filter_on_sed_grid * wavelengths, integrated_filter)
     137              : 
     138            0 :       if (integrated_filter > 0.0_dp) then
     139            0 :          synthetic_flux = integrated_flux / integrated_filter
     140              :       else
     141            0 :          print *, "Error: Integrated filter transmission is zero."
     142            0 :          synthetic_flux = -1.0_dp
     143              :       end if
     144            0 :    end subroutine calculate_synthetic_flux
     145              : 
     146              :    !****************************
     147              :    ! Compute Vega Zero Point Flux
     148              :    ! Called once at initialization, result cached in filter_data
     149              :    !****************************
     150            7 :    real(dp) function compute_vega_zero_point(vega_wave, vega_flux, filt_wave, filt_trans)
     151              :       real(dp), dimension(:), intent(in) :: vega_wave, vega_flux
     152              :       real(dp), dimension(:), intent(in) :: filt_wave, filt_trans
     153              : 
     154              :       real(dp) :: int_flux, int_filter
     155            7 :       real(dp), allocatable :: filt_on_vega_grid(:), conv_flux(:)
     156              : 
     157            7 :       allocate(filt_on_vega_grid(size(vega_wave)))
     158            7 :       allocate(conv_flux(size(vega_wave)))
     159              : 
     160              :       ! Interpolate filter onto Vega wavelength grid
     161            7 :       call interpolate_array(filt_wave, filt_trans, vega_wave, filt_on_vega_grid)
     162              : 
     163              :       ! Convolve Vega with filter
     164        56672 :       conv_flux = vega_flux * filt_on_vega_grid
     165              : 
     166              :       ! Photon-counting integration
     167        56665 :       call romberg_integration(vega_wave, vega_wave * conv_flux, int_flux)
     168        56665 :       call romberg_integration(vega_wave, vega_wave * filt_on_vega_grid, int_filter)
     169              : 
     170            7 :       if (int_filter > 0.0_dp) then
     171            7 :          compute_vega_zero_point = int_flux / int_filter
     172              :       else
     173              :          compute_vega_zero_point = -1.0_dp
     174              :       end if
     175              : 
     176            7 :       deallocate(filt_on_vega_grid, conv_flux)
     177            7 :    end function compute_vega_zero_point
     178              : 
     179              :    !****************************
     180              :    ! Compute AB Zero Point Flux
     181              :    ! f_nu = 3631 Jy = 3.631e-20 erg/s/cm^2/Hz
     182              :    ! f_lambda = f_nu * c / lambda^2
     183              :    ! Called once at initialization, result cached in filter_data
     184              :    !****************************
     185            7 :    real(dp) function compute_ab_zero_point(filt_wave, filt_trans)
     186              :       real(dp), dimension(:), intent(in) :: filt_wave, filt_trans
     187              : 
     188              :       real(dp) :: int_flux, int_filter
     189            7 :       real(dp), allocatable :: ab_sed_flux(:)
     190              :       integer :: i
     191              : 
     192            7 :       allocate(ab_sed_flux(size(filt_wave)))
     193              : 
     194              :       ! Construct AB spectrum (f_lambda) on the filter wavelength grid
     195              :       ! 3631 Jy = 3.631E-20 erg/s/cm^2/Hz
     196              :       ! clight in cm/s, wavelength in Angstroms, need to convert
     197          149 :       do i = 1, size(filt_wave)
     198          149 :          if (filt_wave(i) > 0.0_dp) then
     199          142 :             ab_sed_flux(i) = 3.631d-20 * ((clight * 1.0d8) / (filt_wave(i)**2))
     200              :          else
     201            0 :             ab_sed_flux(i) = 0.0_dp
     202              :          end if
     203              :       end do
     204              : 
     205              :       ! Photon-counting integration
     206          149 :       call romberg_integration(filt_wave, ab_sed_flux * filt_trans * filt_wave, int_flux)
     207          149 :       call romberg_integration(filt_wave, filt_wave * filt_trans, int_filter)
     208              : 
     209            7 :       if (int_filter > 0.0_dp) then
     210            7 :          compute_ab_zero_point = int_flux / int_filter
     211              :       else
     212              :          compute_ab_zero_point = -1.0_dp
     213              :       end if
     214              : 
     215            7 :       deallocate(ab_sed_flux)
     216            7 :    end function compute_ab_zero_point
     217              : 
     218              :    !****************************
     219              :    ! Compute ST Zero Point Flux
     220              :    ! f_lambda = 3.63e-9 erg/s/cm^2/A (Constant)
     221              :    ! Called once at initialization, result cached in filter_data
     222              :    !****************************
     223            7 :    real(dp) function compute_st_zero_point(filt_wave, filt_trans)
     224              :       real(dp), dimension(:), intent(in) :: filt_wave, filt_trans
     225              : 
     226              :       real(dp) :: int_flux, int_filter
     227            7 :       real(dp), allocatable :: st_sed_flux(:)
     228              : 
     229            7 :       allocate(st_sed_flux(size(filt_wave)))
     230          149 :       st_sed_flux = 3.63d-9
     231              : 
     232              :       ! Photon-counting integration
     233          149 :       call romberg_integration(filt_wave, st_sed_flux * filt_trans * filt_wave, int_flux)
     234          149 :       call romberg_integration(filt_wave, filt_wave * filt_trans, int_filter)
     235              : 
     236            7 :       if (int_filter > 0.0_dp) then
     237            7 :          compute_st_zero_point = int_flux / int_filter
     238              :       else
     239              :          compute_st_zero_point = -1.0_dp
     240              :       end if
     241              : 
     242            7 :       deallocate(st_sed_flux)
     243            7 :    end function compute_st_zero_point
     244              : 
     245              : end module synthetic
        

Generated by: LCOV version 2.0-1