LCOV - code coverage report
Current view: top level - colors/private - colors_utils.f90 (source / functions) Coverage Total Hit
Test: coverage.info Lines: 8.0 % 276 22
Test Date: 2025-10-14 06:41:40 Functions: 14.3 % 14 2

            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 colors_utils
      21              :    use const_def, only: dp, strlen, mesa_dir
      22              :    use colors_def, only: Colors_General_Info
      23              : 
      24              :    implicit none
      25              : 
      26              :    public :: dilute_flux, trapezoidal_integration, romberg_integration, &
      27              :              simpson_integration, load_sed, load_filter, load_vega_sed, &
      28              :              load_lookup_table, remove_dat
      29              : contains
      30              : 
      31              :    !---------------------------------------------------------------------------
      32              :    ! Apply dilution factor to convert surface flux to observed flux
      33              :    !---------------------------------------------------------------------------
      34            0 :    subroutine dilute_flux(surface_flux, R, d, calibrated_flux)
      35              :       real(dp), intent(in)  :: surface_flux(:)
      36              :       real(dp), intent(in)  :: R, d  ! R = stellar radius, d = distance (both in the same units, e.g., cm)
      37              :       real(dp), intent(out) :: calibrated_flux(:)
      38              : 
      39              :       ! Check that the output array has the same size as the input
      40            0 :       if (size(calibrated_flux) /= size(surface_flux)) then
      41            0 :          print *, "Error in dilute_flux: Output array must have the same size as input array."
      42            0 :          stop 1
      43              :       end if
      44              : 
      45              :       ! Apply the dilution factor (R/d)^2 to each element
      46            0 :       calibrated_flux = surface_flux*((R/d)**2)
      47            0 :    end subroutine dilute_flux
      48              : 
      49              :    !###########################################################
      50              :    !## MATHS
      51              :    !###########################################################
      52              : 
      53              :    !****************************
      54              :    !Trapezoidal and Simpson Integration For Flux Calculation
      55              :    !****************************
      56              : 
      57            0 :    subroutine trapezoidal_integration(x, y, result)
      58              :       real(dp), dimension(:), intent(in) :: x, y
      59              :       real(dp), intent(out) :: result
      60              : 
      61              :       integer :: i, n
      62            0 :       REAL :: sum
      63              : 
      64            0 :       n = size(x)
      65            0 :       sum = 0.0
      66              : 
      67              :       ! Validate input sizes
      68            0 :       if (size(x) /= size(y)) then
      69            0 :          print *, "Error: x and y arrays must have the same size."
      70            0 :          stop
      71              :       end if
      72              : 
      73            0 :       if (size(x) < 2) then
      74            0 :          print *, "Error: x and y arrays must have at least 2 elements."
      75            0 :          stop
      76              :       end if
      77              : 
      78              :       ! Perform trapezoidal integration
      79            0 :       do i = 1, n - 1
      80            0 :          sum = sum + 0.5*(x(i + 1) - x(i))*(y(i + 1) + y(i))
      81              :       end do
      82              : 
      83            0 :       result = sum
      84            0 :    end subroutine trapezoidal_integration
      85              : 
      86            0 :    subroutine simpson_integration(x, y, result)
      87              :       real(dp), dimension(:), intent(in) :: x, y
      88              :       real(dp), intent(out) :: result
      89              : 
      90              :       integer :: i, n
      91            0 :       real(dp) :: sum, h1, h2, f1, f2, f0
      92              : 
      93            0 :       n = size(x)
      94            0 :       sum = 0.0_dp
      95              : 
      96              :       ! Validate input sizes
      97            0 :       if (size(x) /= size(y)) then
      98            0 :          print *, "Error: x and y arrays must have the same size."
      99            0 :          stop
     100              :       end if
     101              : 
     102            0 :       if (size(x) < 2) then
     103            0 :          print *, "Error: x and y arrays must have at least 2 elements."
     104            0 :          stop
     105              :       end if
     106              : 
     107              :       ! Perform adaptive Simpson’s rule
     108            0 :       do i = 1, n - 2, 2
     109            0 :          h1 = x(i + 1) - x(i)       ! Step size for first interval
     110            0 :          h2 = x(i + 2) - x(i + 1)     ! Step size for second interval
     111              : 
     112            0 :          f0 = y(i)
     113            0 :          f1 = y(i + 1)
     114            0 :          f2 = y(i + 2)
     115              : 
     116              :          ! Simpson's rule: (h/3) * (f0 + 4f1 + f2)
     117            0 :          sum = sum + (h1 + h2)/6.0_dp*(f0 + 4.0_dp*f1 + f2)
     118              :       end do
     119              : 
     120              :       ! Handle the case where n is odd (last interval)
     121            0 :       if (MOD(n, 2) == 0) then
     122            0 :          sum = sum + 0.5_dp*(x(n) - x(n - 1))*(y(n) + y(n - 1))
     123              :       end if
     124              : 
     125            0 :       result = sum
     126            0 :    end subroutine simpson_integration
     127              : 
     128            0 :    subroutine romberg_integration(x, y, result)
     129              :       real(dp), dimension(:), intent(in) :: x, y
     130              :       real(dp), intent(out) :: result
     131              : 
     132              :       integer :: i, j, k, n, m
     133            0 :       real(dp), dimension(:), allocatable :: R
     134            0 :       real(dp) :: h, sum, factor
     135              : 
     136            0 :       n = size(x)
     137            0 :       m = int(log(real(n, DP))/log(2.0_dp)) + 1  ! Number of refinement levels
     138              : 
     139              :       ! Validate input sizes
     140            0 :       if (size(x) /= size(y)) then
     141            0 :          print *, "Error: x and y arrays must have the same size."
     142            0 :          stop
     143              :       end if
     144              : 
     145            0 :       if (n < 2) then
     146            0 :          print *, "Error: x and y arrays must have at least 2 elements."
     147            0 :          stop
     148              :       end if
     149              : 
     150            0 :       allocate (R(m))
     151              : 
     152              :       ! Compute initial trapezoidal rule estimate
     153            0 :       h = x(n) - x(1)
     154            0 :       R(1) = 0.5_dp*h*(y(1) + y(n))
     155              : 
     156              :       ! Refinement using Romberg's method
     157            0 :       do j = 2, m
     158            0 :          sum = 0.0_dp
     159            0 :          do i = 1, 2**(j - 2)
     160            0 :             sum = sum + y(1 + (2*i - 1)*(n - 1)/(2**(j - 1)))
     161              :          end do
     162              : 
     163            0 :          h = h/2.0_dp
     164            0 :          R(j) = 0.5_dp*R(j - 1) + h*sum
     165              : 
     166              :          ! Richardson extrapolation
     167            0 :          factor = 4.0_dp
     168            0 :          do k = j, 2, -1
     169            0 :             R(k - 1) = (factor*R(k) - R(k - 1))/(factor - 1.0_dp)
     170            0 :             factor = factor*4.0_dp
     171              :          end do
     172              :       end do
     173              : 
     174            0 :       result = R(1)
     175            0 :    end subroutine romberg_integration
     176              : 
     177              :    !-----------------------------------------------------------------------
     178              :    ! File I/O functions
     179              :    !-----------------------------------------------------------------------
     180              : 
     181              :    !****************************
     182              :    ! Load Vega SED for Zero Point Calculation
     183              :    !****************************
     184            0 :    subroutine load_vega_sed(filepath, wavelengths, flux)
     185              :       character(len=*), intent(in) :: filepath
     186              :       real(dp), dimension(:), allocatable, intent(out) :: wavelengths, flux
     187              :       character(len=512) :: line
     188              :       integer :: unit, n_rows, status, i
     189            0 :       real(dp) :: temp_wave, temp_flux
     190              : 
     191            0 :       unit = 20
     192            0 :       open (unit, file=trim(filepath), status='OLD', action='READ', iostat=status)
     193            0 :       if (status /= 0) then
     194            0 :          print *, "Error: Could not open Vega SED file ", trim(filepath)
     195            0 :          stop
     196              :       end if
     197              : 
     198              :       ! Skip header line
     199            0 :       read (unit, '(A)', iostat=status) line
     200            0 :       if (status /= 0) then
     201            0 :          print *, "Error: Could not read header from Vega SED file ", trim(filepath)
     202            0 :          stop
     203              :       end if
     204              : 
     205              :       ! Count the number of data lines
     206              :       n_rows = 0
     207            0 :       do
     208            0 :          read (unit, '(A)', iostat=status) line
     209            0 :          if (status /= 0) exit
     210            0 :          n_rows = n_rows + 1
     211              :       end do
     212              : 
     213            0 :       rewind (unit)
     214            0 :       read (unit, '(A)', iostat=status) line  ! Skip header again
     215              : 
     216            0 :       allocate (wavelengths(n_rows))
     217            0 :       allocate (flux(n_rows))
     218              : 
     219            0 :       i = 0
     220            0 :       do
     221            0 :          read (unit, *, iostat=status) temp_wave, temp_flux  ! Ignore any extra columns
     222            0 :          if (status /= 0) exit
     223            0 :          i = i + 1
     224            0 :          wavelengths(i) = temp_wave
     225            0 :          flux(i) = temp_flux
     226              :       end do
     227              : 
     228            0 :       close (unit)
     229            0 :    end subroutine load_vega_sed
     230              : 
     231              :    !****************************
     232              :    ! Load Filter File
     233              :    !****************************
     234            0 :    subroutine load_filter(directory, filter_wavelengths, filter_trans)
     235              :       character(len=*), intent(in) :: directory
     236              :       real(dp), dimension(:), allocatable, intent(out) :: filter_wavelengths, filter_trans
     237              : 
     238              :       character(len=512) :: line
     239              :       integer :: unit, n_rows, status, i
     240            0 :       REAL :: temp_wavelength, temp_trans
     241              : 
     242              :       ! Open the file
     243            0 :       unit = 20
     244            0 :       open (unit, file=trim(directory), status='OLD', action='READ', iostat=status)
     245            0 :       if (status /= 0) then
     246            0 :          print *, "Error: Could not open file ", trim(directory)
     247            0 :          stop
     248              :       end if
     249              : 
     250              :       ! Skip header line
     251            0 :       read (unit, '(A)', iostat=status) line
     252            0 :       if (status /= 0) then
     253            0 :          print *, "Error: Could not read the file", trim(directory)
     254            0 :          stop
     255              :       end if
     256              : 
     257              :       ! Count rows in the file
     258              :       n_rows = 0
     259            0 :       do
     260            0 :          read (unit, '(A)', iostat=status) line
     261            0 :          if (status /= 0) exit
     262            0 :          n_rows = n_rows + 1
     263              :       end do
     264              : 
     265              :       ! Allocate arrays
     266            0 :       allocate (filter_wavelengths(n_rows))
     267            0 :       allocate (filter_trans(n_rows))
     268              : 
     269              :       ! Rewind to the first non-comment line
     270            0 :       rewind (unit)
     271              :       do
     272            0 :          read (unit, '(A)', iostat=status) line
     273            0 :          if (status /= 0) then
     274            0 :             print *, "Error: Could not rewind file", trim(directory)
     275            0 :             stop
     276              :          end if
     277            0 :          if (line(1:1) /= "#") exit
     278              :       end do
     279              : 
     280              :       ! Read and parse data
     281              :       i = 0
     282            0 :       do
     283            0 :          read (unit, *, iostat=status) temp_wavelength, temp_trans
     284            0 :          if (status /= 0) exit
     285            0 :          i = i + 1
     286              : 
     287            0 :          filter_wavelengths(i) = temp_wavelength
     288            0 :          filter_trans(i) = temp_trans
     289              :       end do
     290              : 
     291            0 :       close (unit)
     292            0 :    end subroutine load_filter
     293              : 
     294              :    !****************************
     295              :    ! Load Lookup Table For Identifying Stellar Atmosphere Models
     296              :    !****************************
     297            0 :    subroutine load_lookup_table(lookup_file, lookup_table, out_file_names, &
     298              :                                 out_logg, out_meta, out_teff)
     299              : 
     300              :       character(len=*), intent(in) :: lookup_file
     301              :       REAL, dimension(:, :), allocatable, intent(out) :: lookup_table
     302              :       character(len=100), allocatable, intent(inout) :: out_file_names(:)
     303              :       real(dp), allocatable, intent(inout) :: out_logg(:), out_meta(:), out_teff(:)
     304              : 
     305              :       integer :: i, n_rows, status, unit
     306              :       character(len=512) :: line
     307              :       character(len=*), parameter :: delimiter = ","
     308            0 :       character(len=100), allocatable :: columns(:), headers(:)
     309              :       integer :: logg_col, meta_col, teff_col
     310              : 
     311              :       ! Open the file
     312            0 :       unit = 10
     313            0 :       open (unit, file=lookup_file, status='old', action='read', iostat=status)
     314            0 :       if (status /= 0) then
     315            0 :          print *, "Error: Could not open file", lookup_file
     316            0 :          stop
     317              :       end if
     318              : 
     319              :       ! Read header line
     320            0 :       read (unit, '(A)', iostat=status) line
     321            0 :       if (status /= 0) then
     322            0 :          print *, "Error: Could not read header line"
     323            0 :          stop
     324              :       end if
     325              : 
     326              :       call split_line(line, delimiter, headers)
     327              : 
     328              :       ! Determine column indices for logg, meta, and teff
     329            0 :       logg_col = get_column_index(headers, "logg")
     330            0 :       teff_col = get_column_index(headers, "teff")
     331              : 
     332            0 :       meta_col = get_column_index(headers, "meta")
     333            0 :       if (meta_col < 0) then
     334            0 :          meta_col = get_column_index(headers, "feh")
     335              :       end if
     336              : 
     337            0 :       n_rows = 0
     338            0 :       do
     339            0 :          read (unit, '(A)', iostat=status) line
     340            0 :          if (status /= 0) exit
     341            0 :          n_rows = n_rows + 1
     342              :       end do
     343            0 :       rewind (unit)
     344              : 
     345              :       ! Skip header
     346            0 :       read (unit, '(A)', iostat=status) line
     347              : 
     348              :       ! Allocate output arrays
     349            0 :       allocate (out_file_names(n_rows))
     350            0 :       allocate (out_logg(n_rows), out_meta(n_rows), out_teff(n_rows))
     351              : 
     352              :       ! Read and parse the file
     353            0 :       i = 0
     354            0 :       do
     355            0 :          read (unit, '(A)', iostat=status) line
     356            0 :          if (status /= 0) exit
     357            0 :          i = i + 1
     358              : 
     359            0 :          call split_line(line, delimiter, columns)
     360              : 
     361              :          ! Populate arrays
     362            0 :          out_file_names(i) = columns(1)
     363              : 
     364            0 :          if (logg_col > 0) then
     365            0 :             if (columns(logg_col) /= "") then
     366            0 :                read (columns(logg_col), *) out_logg(i)
     367              :             else
     368            0 :                out_logg(i) = -999.0
     369              :             end if
     370              :          else
     371            0 :             out_logg(i) = -999.0
     372              :          end if
     373              : 
     374            0 :          if (meta_col > 0) then
     375            0 :             if (columns(meta_col) /= "") then
     376            0 :                read (columns(meta_col), *) out_meta(i)
     377              :             else
     378            0 :                out_meta(i) = 0.0
     379              :             end if
     380              :          else
     381            0 :             out_meta(i) = 0.0
     382              :          end if
     383              : 
     384            0 :          if (teff_col > 0) then
     385            0 :             if (columns(teff_col) /= "") then
     386            0 :                read (columns(teff_col), *) out_teff(i)
     387              :             else
     388            0 :                out_teff(i) = 0.0
     389              :             end if
     390              :          else
     391            0 :             out_teff(i) = 0.0
     392              :          end if
     393              :       end do
     394              : 
     395            0 :       close (unit)
     396              : 
     397              :    contains
     398              : 
     399            0 :       function get_column_index(headers, target) result(index)
     400              :          character(len=100), intent(in) :: headers(:)
     401              :          character(len=*), intent(in) :: target
     402              :          integer :: index, i
     403              :          character(len=100) :: clean_header, clean_target
     404              : 
     405            0 :          index = -1
     406            0 :          clean_target = trim(adjustl(target))  ! Clean the target string
     407              : 
     408            0 :          do i = 1, size(headers)
     409            0 :             clean_header = trim(adjustl(headers(i)))  ! Clean each header
     410            0 :             if (clean_header == clean_target) then
     411              :                index = i
     412              :                exit
     413              :             end if
     414              :          end do
     415            0 :       end function get_column_index
     416              : 
     417            0 :       subroutine split_line(line, delimiter, tokens)
     418              :          character(len=*), intent(in) :: line, delimiter
     419              :          character(len=100), allocatable, intent(out) :: tokens(:)
     420              :          integer :: num_tokens, pos, start, len_delim
     421              : 
     422            0 :          len_delim = len_trim(delimiter)
     423            0 :          start = 1
     424            0 :          num_tokens = 0
     425            0 :          if (allocated(tokens)) deallocate (tokens)
     426              : 
     427            0 :          do
     428            0 :             pos = index(line(start:), delimiter)
     429              : 
     430            0 :             if (pos == 0) exit
     431            0 :             num_tokens = num_tokens + 1
     432            0 :             call append_token(tokens, line(start:start + pos - 2))
     433            0 :             start = start + pos + len_delim - 1
     434              :          end do
     435              : 
     436            0 :          num_tokens = num_tokens + 1
     437            0 :          call append_token(tokens, line(start:))
     438            0 :       end subroutine split_line
     439              : 
     440            0 :       subroutine append_token(tokens, token)
     441              :          character(len=*), intent(in) :: token
     442              :          character(len=100), allocatable, intent(inout) :: tokens(:)
     443            0 :          character(len=100), allocatable :: temp(:)
     444              :          integer :: n
     445              : 
     446            0 :          if (.not. allocated(tokens)) then
     447            0 :             allocate (tokens(1))
     448            0 :             tokens(1) = token
     449              :          else
     450            0 :             n = size(tokens)
     451            0 :             allocate (temp(n))
     452            0 :             temp = tokens  ! Backup the current tokens
     453            0 :             deallocate (tokens)  ! Deallocate the old array
     454            0 :             allocate (tokens(n + 1))  ! Allocate with one extra space
     455            0 :             tokens(1:n) = temp  ! Restore old tokens
     456            0 :             tokens(n + 1) = token  ! Add the new token
     457            0 :             deallocate (temp)  ! unsure if this is till needed.
     458              :          end if
     459            0 :       end subroutine append_token
     460              : 
     461              :    end subroutine load_lookup_table
     462              : 
     463            0 :    subroutine load_sed(directory, index, wavelengths, flux)
     464              :       character(len=*), intent(in) :: directory
     465              :       integer, intent(in) :: index
     466              :       real(dp), dimension(:), allocatable, intent(out) :: wavelengths, flux
     467              : 
     468              :       character(len=512) :: line
     469              :       integer :: unit, n_rows, status, i
     470            0 :       real(dp) :: temp_wavelength, temp_flux
     471              : 
     472              :       ! Open the file
     473            0 :       unit = 20
     474            0 :       open (unit, file=trim(directory), status='OLD', action='READ', iostat=status)
     475            0 :       if (status /= 0) then
     476            0 :          print *, "Error: Could not open file ", trim(directory)
     477            0 :          stop
     478              :       end if
     479              : 
     480              :       ! Skip header lines
     481              :       do
     482            0 :          read (unit, '(A)', iostat=status) line
     483            0 :          if (status /= 0) then
     484            0 :             print *, "Error: Could not read the file", trim(directory)
     485            0 :             stop
     486              :          end if
     487            0 :          if (line(1:1) /= "#") exit
     488              :       end do
     489              : 
     490              :       ! Count rows in the file
     491              :       n_rows = 0
     492            0 :       do
     493            0 :          read (unit, '(A)', iostat=status) line
     494            0 :          if (status /= 0) exit
     495            0 :          n_rows = n_rows + 1
     496              :       end do
     497              : 
     498              :       ! Allocate arrays
     499            0 :       allocate (wavelengths(n_rows))
     500            0 :       allocate (flux(n_rows))
     501              : 
     502              :       ! Rewind to the first non-comment line
     503            0 :       rewind (unit)
     504              :       do
     505            0 :          read (unit, '(A)', iostat=status) line
     506            0 :          if (status /= 0) then
     507            0 :             print *, "Error: Could not rewind file", trim(directory)
     508            0 :             stop
     509              :          end if
     510            0 :          if (line(1:1) /= "#") exit
     511              :       end do
     512              : 
     513              :       ! Read and parse data
     514              :       i = 0
     515            0 :       do
     516            0 :          read (unit, *, iostat=status) temp_wavelength, temp_flux
     517            0 :          if (status /= 0) exit
     518            0 :          i = i + 1
     519              :          ! Convert f_lambda to f_nu
     520            0 :          wavelengths(i) = temp_wavelength
     521            0 :          flux(i) = temp_flux
     522              :       end do
     523              : 
     524            0 :       close (unit)
     525              : 
     526            0 :    end subroutine load_sed
     527              : 
     528              :    !-----------------------------------------------------------------------
     529              :    ! Helper function for file names
     530              :    !-----------------------------------------------------------------------
     531              : 
     532            0 :    function remove_dat(path) result(base)
     533              :       ! Extracts the portion of the string before the first dot
     534              :       character(len=*), intent(in) :: path
     535              :       character(len=strlen) :: base
     536              :       integer :: first_dot
     537              : 
     538              :       ! Find the position of the first dot
     539            0 :       first_dot = 0
     540            0 :       do while (first_dot < len_trim(path) .and. path(first_dot + 1:first_dot + 1) /= '.')
     541            0 :          first_dot = first_dot + 1
     542              :       end do
     543              : 
     544              :       ! Check if a dot was found
     545            0 :       if (first_dot < len_trim(path)) then
     546              :          ! Extract the part before the dot
     547            0 :          base = path(:first_dot)
     548              :       else
     549              :          ! No dot found, return the input string
     550            0 :          base = path
     551              :       end if
     552            0 :    end function remove_dat
     553              : 
     554            1 :    function basename(path) result(name)
     555              :       character(len=*), intent(in) :: path
     556              :       character(len=strlen) :: name
     557              :       integer :: i
     558            1 :       if (len_trim(path) == 0) then
     559            0 :          name = ''
     560            0 :          return
     561              :       end if
     562            1 :       i = index(path, '/', back=.true.)
     563            1 :       name = path(i + 1:)
     564              :    end function basename
     565              : 
     566            1 :    subroutine read_strings_from_file(colors_settings, strings, n, ierr)
     567              :       character(len=512) :: filename
     568              :       character(len=100), allocatable, intent(out) :: strings(:)
     569              :       integer, intent(out) :: n, ierr
     570              :       integer :: unit, i, status
     571              :       character(len=100) :: line
     572              :       type(Colors_General_Info), pointer :: colors_settings
     573              : 
     574            1 :       ierr = 0
     575              : 
     576              :       filename = trim(mesa_dir)//trim(colors_settings%instrument)//"/"// &
     577            1 :                  trim(basename(colors_settings%instrument))
     578            1 :       n = 0
     579            1 :       unit = 10
     580            1 :       open (unit, file=filename, status='old', action='read', iostat=status)
     581            1 :       if (status /= 0) then
     582            0 :          ierr = -1
     583            0 :          print *, "Error: Could not open file", filename
     584            0 :          stop
     585              :       end if
     586              : 
     587            6 :       do
     588            7 :          read (unit, '(A)', iostat=status) line
     589            7 :          if (status /= 0) exit
     590            6 :          n = n + 1
     591              :       end do
     592            1 :       rewind (unit)
     593            1 :       if (allocated(strings)) deallocate (strings)
     594            1 :       allocate (strings(n))
     595            7 :       do i = 1, n
     596            7 :          read (unit, '(A)') strings(i)
     597              :       end do
     598            1 :       close (unit)
     599            1 :    end subroutine read_strings_from_file
     600              : 
     601              : end module colors_utils
        

Generated by: LCOV version 2.0-1