module mod_cooling_curves use mod_global_variables, only: dp, str_len use mod_settings, only: settings_t use mod_background, only: background_t use mod_logging, only: logger use mod_interpolation, only: lookup_table_value, get_numerical_derivative use mod_cooling_curve_names use mod_data_rosner implicit none private real(dp), allocatable :: curve_T(:) real(dp), allocatable :: curve_lambda(:) real(dp), allocatable :: curve_dlambdadT(:) real(dp) :: minT, maxT public :: interpolate_cooling_curves public :: is_valid_cooling_curve public :: get_rosner_lambdaT, get_rosner_dlambdadT public :: get_interpolated_lambdaT, get_interpolated_dlambdadT public :: get_cooling_table public :: deallocate_cooling_curves contains subroutine interpolate_cooling_curves(settings) use mod_interpolation, only: interpolate_table, get_numerical_derivative type(settings_t), intent(in) :: settings real(dp), allocatable :: table_T(:) real(dp), allocatable :: table_lambda(:) real(dp) :: unit_temperature, unit_lambdaT, unit_dlambdadT integer :: ncool call get_cooling_table( & name=settings%physics%cooling%get_cooling_curve(), & table_T=table_T, & table_lambda=table_lambda & ) unit_temperature = settings%units%get_unit_temperature() unit_lambdaT = settings%units%get_unit_lambdaT() unit_dlambdadT = unit_lambdaT / unit_temperature !> @note The cooling tables contain dimensionfull values on a logarithmic scale. !! To avoid resampling the table on an unequally spaced temperature grid by doing !! 10**T, we interpolate the logarithmic table values on an equally spaced !! T grid in log scale, so we get log10(lambda(T)) and log10(T) values. @endnote ncool = settings%physics%cooling%get_interpolation_points() allocate(curve_T(ncool), curve_lambda(ncool), curve_dlambdadT(ncool)) call interpolate_table( & n_interp=ncool, & x_table=table_T, & y_table=table_lambda, & x_interp=curve_T, & y_interp=curve_lambda & ) ! rescale to L(T) and normalise curve_lambda = 10.0_dp**curve_lambda / unit_lambdaT ! normalise logT values: log10(T/Tunit) = log10(T) - log10(Tunit) curve_T = curve_T - log10(unit_temperature) ! get dlambda(T)/dlogT, hence chain rule: dL(T)/dlogT = (dL(T)/dT) * T call get_numerical_derivative(x=curve_T, y=curve_lambda, dy=curve_dlambdadT) ! rescale to T (already normalised) curve_T = 10.0_dp**curve_T ! finally, dL(T)/dT = dL(T)/dlogT * (1 / T) curve_dlambdadT = curve_dlambdadT / curve_T ! set min/max temperature minT = minval(curve_T) maxT = maxval(curve_T) ! LCOV_EXCL_START ! save these curves to a file if we're in debug mode if (logger%get_logging_level() >= 3) then open( & unit=1002, & file="debug_coolingcurves", & access="stream", & status="unknown", & action="write" & ) write(1002) size(table_T) write(1002) 10.0d0**table_T write(1002) 10.0d0**table_lambda write(1002) size(curve_T) write(1002) curve_T * unit_temperature write(1002) curve_lambda * unit_lambdaT write(1002) curve_dlambdadT * unit_dlambdadT close(1002) call logger%debug("cooling curves saved to file 'debug_coolingcurves'") end if ! LCOV_EXCL_STOP deallocate(table_T) deallocate(table_lambda) end subroutine interpolate_cooling_curves pure integer function get_rosner_index(logT0) !> dimensionfull log10(T0) value real(dp), intent(in) :: logT0 integer :: j get_rosner_index = 1 if (logT0 > logT_rosner(8)) then get_rosner_index = 9 else do j = 1, size(logT_rosner) if (logT0 < logT_rosner(j)) then get_rosner_index = j exit end if end do end if end function get_rosner_index real(dp) function get_rosner_lambdaT(x, settings, background) real(dp), intent(in) :: x type(settings_t), intent(in) :: settings type(background_t), intent(in) :: background real(dp) :: logT0, logxi, alpha real(dp) :: unit_temperature, unit_lambdaT integer :: idx unit_temperature = settings%units%get_unit_temperature() unit_lambdaT = settings%units%get_unit_lambdaT() logT0 = log10(background%temperature%T0(x) * unit_temperature) idx = get_rosner_index(logT0) logxi = logxi_rosner(idx) alpha = alpha_rosner(idx) ! lambdaT = xi * T**alpha, so log10(lambdaT) = log10(xi) + alpha * log10(T) get_rosner_lambdaT = 10.0_dp**(logxi + alpha * logT0) / unit_lambdaT end function get_rosner_lambdaT real(dp) function get_rosner_dlambdadT(x, settings, background) real(dp), intent(in) :: x type(settings_t), intent(in) :: settings type(background_t), intent(in) :: background real(dp) :: logT0, logxi, alpha real(dp) :: unit_temperature, unit_lambdaT integer :: idx unit_temperature = settings%units%get_unit_temperature() unit_lambdaT = settings%units%get_unit_lambdaT() logT0 = log10(background%temperature%T0(x) * unit_temperature) idx = get_rosner_index(logT0) logxi = logxi_rosner(idx) alpha = alpha_rosner(idx) ! dlambdadT = alpha * xi * T**(alpha - 1), and so ! = alpha * 10**(logxi + (alpha - 1) * logT0) get_rosner_dlambdadT = ( & alpha * 10.0_dp**(logxi + (alpha - 1.0_dp) * logT0) & ) / (unit_lambdaT / unit_temperature) end function get_rosner_dlambdadT real(dp) function get_interpolated_lambdaT(x, settings, background) real(dp), intent(in) :: x type(settings_t), intent(in) :: settings type(background_t), intent(in) :: background real(dp) :: lambdaT, T0 integer :: pts pts = size(curve_T) T0 = background%temperature%T0(x) if (T0 <= minT) then lambdaT = 0.0_dp else if (T0 >= maxT) then lambdaT = curve_lambda(pts) * sqrt(T0 / maxT) else lambdaT = lookup_table_value(T0, curve_T, curve_lambda) end if get_interpolated_lambdaT = lambdaT end function get_interpolated_lambdaT real(dp) function get_interpolated_dlambdadT(x, settings, background) real(dp), intent(in) :: x type(settings_t), intent(in) :: settings type(background_t), intent(in) :: background real(dp) :: dlambdadT, T0 integer :: pts pts = size(curve_T) T0 = background%temperature%T0(x) if (T0 <= minT) then dlambdadT = 0.0_dp else if (T0 >= maxT) then dlambdadT = 0.5_dp * curve_lambda(pts) / sqrt(T0 * maxT) else dlambdadT = lookup_table_value(T0, curve_T, curve_dlambdadT) end if get_interpolated_dlambdadT = dlambdadT end function get_interpolated_dlambdadT logical function is_valid_cooling_curve(name) character(len=*), intent(in) :: name is_valid_cooling_curve = any(name == KNOWN_CURVES) if (.not. is_valid_cooling_curve) then call logger%error("unknown cooling curve: " // name) end if end function is_valid_cooling_curve subroutine get_cooling_table(name, table_T, table_lambda) use mod_cooling_curve_names use mod_data_dalgarno use mod_data_dalgarno2 use mod_data_jccorona use mod_data_mlsolar use mod_data_rosner use mod_data_spex use mod_data_spex_enh use mod_data_colgan character(len=*), intent(in) :: name real(dp), intent(out), allocatable :: table_T(:) real(dp), intent(out), allocatable :: table_lambda(:) integer :: table_n select case(name) case(JC_CORONA) table_n = n_jccorona table_T = logT_jccorona table_lambda = logL_jccorona case(DALGARNO) table_n = n_dalgarno table_T = logT_dalgarno table_lambda = logL_dalgarno case(DALGARNO2) table_n = n_dalgarno2 table_T = logT_dalgarno2 table_lambda = logL_dalgarno2 case(ML_SOLAR) table_n = n_mlsolar table_T = logT_mlsolar table_lambda = logL_mlsolar case(SPEX) table_n = n_spex table_T = logT_spex table_lambda = logL_spex case(SPEX_DALGARNO) table_n = n_spex + n_spex_enh_dalgarno - 6 allocate(table_T(table_n)) allocate(table_lambda(table_n)) table_T(1:n_spex_enh_dalgarno - 1) = logT_spex_enh_dalgarno( & 1:n_spex_enh_dalgarno - 1 & ) table_T(n_spex_enh_dalgarno:) = logT_spex(6:n_spex) table_lambda(1:n_spex_enh_dalgarno - 1) = logL_spex_enh_dalgarno( & 1:n_spex_enh_dalgarno - 1 & ) table_lambda(n_spex_enh_dalgarno:) = ( & logL_spex(6:n_spex) + log10(L_spex_enh(6:n_spex)) & ) case(COLGAN) table_n = n_colgan table_T = logT_colgan table_lambda = logL_colgan case(COLGAN_DM) table_n = n_colgan + n_dalgarno2 allocate(table_T(table_n)) allocate(table_lambda(table_n)) table_T(1:n_dalgarno2) = logT_dalgarno2(1:n_dalgarno2) table_T(n_dalgarno2+1:) = logT_colgan(1:n_colgan) table_lambda(1:n_dalgarno2) = logL_dalgarno2(1:n_dalgarno2) table_lambda(n_dalgarno2+1:) = logL_colgan(1:n_colgan) end select end subroutine get_cooling_table subroutine deallocate_cooling_curves() if (allocated(curve_T)) deallocate(curve_T) if (allocated(curve_lambda)) deallocate(curve_lambda) if (allocated(curve_dlambdadT)) deallocate(curve_dlambdadT) end subroutine deallocate_cooling_curves end module mod_cooling_curves