LCOV - code coverage report
Current view: top level - colors/private - synthetic.f90 (source / functions) Coverage Total Hit
Test: coverage.info Lines: 0.0 % 112 0
Test Date: 2026-01-06 18:03:11 Functions: 0.0 % 6 0

            Line data    Source code
       1              : ! ***********************************************************************
       2              : !
       3              : !   Copyright (C) 2025  Niall Miller & The MESA Team
       4              : !   Modified to include AB and ST magnitude systems.
       5              : !
       6              : ! ***********************************************************************
       7              : 
       8              : module synthetic
       9              : 
      10              :    ! Added clight for AB conversions
      11              :    use const_def, only: dp, clight
      12              :    use utils_lib, only: mkdir, folder_exists
      13              :    use colors_utils, only: remove_dat, romberg_integration, load_filter, load_vega_sed, load_lookup_table
      14              :    use knn_interp, only: interpolate_array
      15              : 
      16              :    implicit none
      17              : 
      18              :    private
      19              :    public :: calculate_synthetic
      20              : 
      21              : contains
      22              : 
      23              :    !****************************
      24              :    ! Calculate Synthetic Photometry Using SED and Filter
      25              :    !****************************
      26            0 :    real(dp) function calculate_synthetic(temperature, gravity, metallicity, ierr, &
      27            0 :                                          wavelengths, fluxes, filter_wavelengths, &
      28              :                                          filter_trans, &
      29              :                                          filter_filepath, vega_filepath, &
      30              :                                          filter_name, make_sed, colors_results_directory, &
      31              :                                          mag_system)
      32              :       ! Input arguments
      33              :       real(dp), intent(in) :: temperature, gravity, metallicity
      34              :       character(len=*), intent(in) :: filter_filepath, filter_name, vega_filepath, colors_results_directory
      35              :       character(len=*), intent(in) :: mag_system  ! NEW: Arguments for 'Vega', 'AB', or 'ST'
      36              :       integer, intent(out) :: ierr
      37              :       character(len=1000) :: line
      38              : 
      39              :       real(dp), dimension(:), intent(inout) :: wavelengths, fluxes
      40              :       real(dp), dimension(:), allocatable, intent(inout) :: filter_wavelengths, filter_trans
      41              :       logical, intent(in) :: make_sed
      42              : 
      43              :       ! Local variables
      44            0 :       real(dp), dimension(:), allocatable :: convolved_flux
      45              :       character(len=100) :: csv_file
      46              :       real(dp) :: synthetic_flux, zero_point_flux
      47              :       integer :: max_size, i
      48              :       real(dp) :: wv, fl, cf, fwv, ftr
      49              : 
      50            0 :       if (.not. folder_exists(trim(colors_results_directory))) call mkdir(trim(colors_results_directory))
      51            0 :       csv_file = trim(colors_results_directory)//'/'//trim(remove_dat(filter_name))//'_SED.csv'
      52            0 :       ierr = 0
      53              : 
      54              :       ! Load filter data
      55            0 :       call load_filter(filter_filepath, filter_wavelengths, filter_trans)
      56              : 
      57              :       ! Perform SED convolution (Source Object)
      58            0 :       allocate (convolved_flux(size(wavelengths)))
      59            0 :       call convolve_sed(wavelengths, fluxes, filter_wavelengths, filter_trans, convolved_flux)
      60              : 
      61              :       ! Write SED to CSV if requested
      62            0 :       if (make_sed) then
      63              :          max_size = max(size(wavelengths), size(filter_wavelengths), &
      64            0 :                         size(fluxes), size(convolved_flux), size(filter_trans))
      65              : 
      66            0 :          open (unit=10, file=csv_file, status='REPLACE', action='write', iostat=ierr)
      67            0 :          if (ierr /= 0) then
      68            0 :             print *, "Error opening file for writing"
      69            0 :             return
      70              :          end if
      71              : 
      72            0 :          write (10, '(A)') "wavelengths,fluxes,convolved_flux,filter_wavelengths,filter_trans"
      73              : 
      74            0 :          do i = 1, max_size
      75            0 :             wv = 0.0_dp; fl = 0.0_dp; cf = 0.0_dp; fwv = 0.0_dp; ftr = 0.0_dp
      76            0 :             if (i <= size(wavelengths)) wv = wavelengths(i)
      77            0 :             if (i <= size(fluxes)) fl = fluxes(i)
      78            0 :             if (i <= size(convolved_flux)) cf = convolved_flux(i)
      79            0 :             if (i <= size(filter_wavelengths)) fwv = filter_wavelengths(i)
      80            0 :             if (i <= size(filter_trans)) ftr = filter_trans(i)
      81              : 
      82              :             write (line, '(ES14.6, ",", ES14.6, ",", ES14.6, ",", ES14.6, ",", ES14.6)') &
      83            0 :                wv, fl, cf, fwv, ftr
      84            0 :             write (10, '(A)') trim(line)
      85              :          end do
      86            0 :          close (10)
      87              :       end if
      88              : 
      89              :       ! ------------------------------------------------------------------
      90              :       ! Calculate Zero Point Flux based on System Selection
      91              :       ! ------------------------------------------------------------------
      92            0 :       select case (trim(mag_system))
      93              :       case ('VEGA', 'Vega', 'vega')
      94              :          zero_point_flux = calculate_vega_flux(vega_filepath, filter_wavelengths, filter_trans, &
      95            0 :                                                filter_name, make_sed, colors_results_directory)
      96              :       case ('AB', 'ab')
      97            0 :          zero_point_flux = calculate_ab_zero_point(filter_wavelengths, filter_trans)
      98              :       case ('ST', 'st')
      99            0 :          zero_point_flux = calculate_st_zero_point(filter_wavelengths, filter_trans)
     100              :       case default
     101            0 :          print *, "Error: Unknown magnitude system: ", mag_system
     102            0 :          calculate_synthetic = huge(1.0_dp)
     103            0 :          return
     104              :       end select
     105              : 
     106              :       ! Calculate synthetic flux (Source Object)
     107              :       call calculate_synthetic_flux(wavelengths, convolved_flux, synthetic_flux, &
     108            0 :                                     filter_wavelengths, filter_trans)
     109              : 
     110              :       ! Calculate magnitude using the selected zero point
     111            0 :       if (zero_point_flux > 0.0_dp) then
     112            0 :          calculate_synthetic = -2.5d0*log10(synthetic_flux/zero_point_flux)
     113              :       else
     114            0 :          print *, "Error: Zero point flux is zero, magnitude calculation is invalid."
     115            0 :          calculate_synthetic = huge(1.0_dp)
     116              :       end if
     117              : 
     118              :       ! Clean up
     119            0 :       deallocate (convolved_flux)
     120            0 :    end function calculate_synthetic
     121              : 
     122              :    !-----------------------------------------------------------------------
     123              :    ! Internal functions for synthetic photometry
     124              :    !-----------------------------------------------------------------------
     125              : 
     126              :    !****************************
     127              :    ! Convolve SED With Filter
     128              :    !****************************
     129            0 :    subroutine convolve_sed(wavelengths, fluxes, filter_wavelengths, filter_trans, convolved_flux)
     130              :       real(dp), dimension(:), intent(inout) :: wavelengths, fluxes
     131              :       real(dp), dimension(:), intent(inout) :: filter_wavelengths, filter_trans
     132              :       real(dp), dimension(:), allocatable, intent(out) :: convolved_flux
     133              :       real(dp), dimension(:), allocatable :: interpolated_filter
     134              :       integer :: n
     135              : 
     136            0 :       n = size(wavelengths)
     137            0 :       allocate (interpolated_filter(n))
     138            0 :       call interpolate_array(filter_wavelengths, filter_trans, wavelengths, interpolated_filter)
     139            0 :       convolved_flux = fluxes*interpolated_filter
     140            0 :       deallocate (interpolated_filter)
     141            0 :    end subroutine convolve_sed
     142              : 
     143              :    !****************************
     144              :    ! Calculate Synthetic Flux (Integration)
     145              :    !****************************
     146            0 :    subroutine calculate_synthetic_flux(wavelengths, fluxes, synthetic_flux, &
     147            0 :                                        filter_wavelengths, filter_trans)
     148              : 
     149              :       real(dp), dimension(:), intent(in) :: wavelengths, fluxes
     150              :       real(dp), dimension(:), intent(inout) :: filter_wavelengths, filter_trans
     151              :       real(dp), intent(out) :: synthetic_flux
     152              :       integer :: i
     153              :       real(dp) :: integrated_flux, integrated_filter
     154              : 
     155              :       ! Validate inputs
     156            0 :       do i = 1, size(wavelengths) - 1
     157            0 :          if (wavelengths(i) <= 0.0_dp .or. fluxes(i) < 0.0_dp) then
     158            0 :             print *, "synthetic Invalid input at index", i, ":", wavelengths(i), fluxes(i)
     159            0 :             stop
     160              :          end if
     161              :       end do
     162              : 
     163            0 :       call romberg_integration(wavelengths, fluxes*wavelengths, integrated_flux)
     164              :       call romberg_integration(filter_wavelengths, &
     165            0 :                                filter_trans*filter_wavelengths, integrated_filter)
     166              : 
     167            0 :       if (integrated_filter > 0.0_dp) then
     168            0 :          synthetic_flux = integrated_flux/integrated_filter
     169              :       else
     170            0 :          print *, "Error: Integrated filter transmission is zero."
     171            0 :          synthetic_flux = -1.0_dp
     172            0 :          return
     173              :       end if
     174              :    end subroutine calculate_synthetic_flux
     175              : 
     176              :    !****************************
     177              :    ! Calculate Vega Flux for Zero Point
     178              :    !****************************
     179            0 :    function calculate_vega_flux(vega_filepath, filt_wave, filt_trans, &
     180              :                                 filter_name, make_sed, colors_results_directory) result(vega_flux)
     181              :       character(len=*), intent(in) :: vega_filepath, filter_name, colors_results_directory
     182              :       character(len=100) :: output_csv
     183              :       real(dp), dimension(:), intent(inout) :: filt_wave, filt_trans
     184              :       real(dp) :: vega_flux
     185              :       real(dp) :: int_flux, int_filter
     186            0 :       real(dp), allocatable :: vega_wave(:), vega_flux_arr(:), conv_flux(:)
     187              :       logical, intent(in) :: make_sed
     188              :       integer :: i, max_size, ierr
     189              :       real(dp) :: wv, fl, cf, fwv, ftr
     190              :       character(len=1000) :: line
     191              : 
     192              :       ! Load the Vega SED
     193              :       call load_vega_sed(vega_filepath, vega_wave, vega_flux_arr)
     194              : 
     195              :       ! Convolve the Vega SED with the filter transmission
     196            0 :       allocate (conv_flux(size(vega_wave)))
     197            0 :       call convolve_sed(vega_wave, vega_flux_arr, filt_wave, filt_trans, conv_flux)
     198              : 
     199              :       ! Integrate
     200            0 :       call romberg_integration(vega_wave, vega_wave*conv_flux, int_flux)
     201            0 :       call romberg_integration(filt_wave, filt_wave*filt_trans, int_filter)
     202              : 
     203            0 :       if (int_filter > 0.0_dp) then
     204            0 :          vega_flux = int_flux/int_filter
     205              :       else
     206              :          vega_flux = -1.0_dp
     207              :       end if
     208              : 
     209              :       ! Write Vega SED to CSV if requested
     210            0 :       if (make_sed) then
     211              :          max_size = max(size(vega_wave), size(vega_flux_arr), size(conv_flux), &
     212            0 :                         size(filt_wave), size(filt_trans))
     213              : 
     214            0 :          if (.not. folder_exists(trim(colors_results_directory))) call mkdir(trim(colors_results_directory))
     215            0 :          output_csv = trim(colors_results_directory)//'/VEGA_'//trim(remove_dat(filter_name))//'_SED.csv'
     216              : 
     217            0 :          open (unit=10, file=output_csv, status='REPLACE', action='write', iostat=ierr)
     218            0 :          if (ierr /= 0) then
     219            0 :             print *, "Error opening file for writing"
     220            0 :             return
     221              :          end if
     222              : 
     223            0 :          write (10, '(A)') "wavelengths,fluxes,convolved_flux,filter_wavelengths,filter_trans"
     224              : 
     225            0 :          do i = 1, max_size
     226            0 :             wv = 0.0_dp; fl = 0.0_dp; cf = 0.0_dp; fwv = 0.0_dp; ftr = 0.0_dp
     227            0 :             if (i <= size(vega_wave)) wv = vega_wave(i)
     228            0 :             if (i <= size(vega_flux_arr)) fl = vega_flux_arr(i)
     229            0 :             if (i <= size(conv_flux)) cf = conv_flux(i)
     230            0 :             if (i <= size(filt_wave)) fwv = filt_wave(i)
     231            0 :             if (i <= size(filt_trans)) ftr = filt_trans(i)
     232              : 
     233              :             write (line, '(ES14.6, ",", ES14.6, ",", ES14.6, ",", ES14.6, ",", ES14.6)') &
     234            0 :                wv, fl, cf, fwv, ftr
     235            0 :             write (10, '(A)') trim(line)
     236              :          end do
     237            0 :          close (10)
     238              :       end if
     239            0 :       deallocate (conv_flux, vega_wave, vega_flux_arr)
     240            0 :    end function calculate_vega_flux
     241              : 
     242              :    !****************************
     243              :    ! Calculate AB Zero Point Flux
     244              :    ! f_nu = 3631 Jy = 3.631e-20 erg/s/cm^2/Hz
     245              :    ! f_lambda = f_nu * c / lambda^2
     246              :    !****************************
     247            0 :    function calculate_ab_zero_point(filt_wave, filt_trans) result(ab_flux)
     248              :       real(dp), dimension(:), intent(inout) :: filt_wave, filt_trans
     249              :       real(dp) :: ab_flux
     250              :       real(dp) :: int_flux, int_filter
     251            0 :       real(dp), allocatable :: ab_sed_flux(:)
     252              :       integer :: i
     253              : 
     254            0 :       allocate(ab_sed_flux(size(filt_wave)))
     255              : 
     256              :       ! Construct AB Spectrum (f_lambda) on the filter wavelength grid
     257              :       ! Assumes wavelengths are in Angstroms and clight is in Angstroms/sec
     258              :       ! 3631 Jy = 3.631E-20 erg/s/cm^2/Hz
     259            0 :       do i = 1, size(filt_wave)
     260            0 :          if (filt_wave(i) > 0.0_dp) then
     261            0 :             ab_sed_flux(i) = 3.631d-20 * (clight / (filt_wave(i)**2))
     262              :          else
     263            0 :             ab_sed_flux(i) = 0.0_dp
     264              :          endif
     265              :       end do
     266              : 
     267              :       ! Integrate using same method as source (f_lambda * T * lambda)
     268              :       ! Note: We multiply by filt_wave inside the integration because the
     269              :       ! romberg helper expects (flux * lambda)
     270            0 :       call romberg_integration(filt_wave, ab_sed_flux * filt_trans * filt_wave, int_flux)
     271            0 :       call romberg_integration(filt_wave, filt_wave * filt_trans, int_filter)
     272              : 
     273            0 :       if (int_filter > 0.0_dp) then
     274            0 :          ab_flux = int_flux / int_filter
     275              :       else
     276              :          ab_flux = -1.0_dp
     277              :       end if
     278              : 
     279            0 :       deallocate(ab_sed_flux)
     280            0 :    end function calculate_ab_zero_point
     281              : 
     282              :    !****************************
     283              :    ! Calculate ST Zero Point Flux
     284              :    ! f_lambda = 3.63e-9 erg/s/cm^2/A (Constant)
     285              :    !****************************
     286            0 :    function calculate_st_zero_point(filt_wave, filt_trans) result(st_flux)
     287              :       real(dp), dimension(:), intent(inout) :: filt_wave, filt_trans
     288              :       real(dp) :: st_flux
     289              :       real(dp) :: int_flux, int_filter
     290            0 :       real(dp), allocatable :: st_sed_flux(:)
     291              : 
     292              :       ! For ST system, flux is constant in wavelength
     293              :       ! However, to maintain exact consistency with how the source is integrated
     294              :       ! (numerical integration over the filter grid), we integrate the constant array.
     295              : 
     296            0 :       allocate(st_sed_flux(size(filt_wave)))
     297            0 :       st_sed_flux = 3.63d-9
     298              : 
     299            0 :       call romberg_integration(filt_wave, st_sed_flux * filt_trans * filt_wave, int_flux)
     300            0 :       call romberg_integration(filt_wave, filt_wave * filt_trans, int_filter)
     301              : 
     302            0 :       if (int_filter > 0.0_dp) then
     303            0 :          st_flux = int_flux / int_filter
     304              :       else
     305              :          st_flux = -1.0_dp
     306              :       end if
     307              : 
     308            0 :       deallocate(st_sed_flux)
     309            0 :    end function calculate_st_zero_point
     310              : 
     311              : end module synthetic
        

Generated by: LCOV version 2.0-1