/******************************************************************************* * This file is part of SWIFT. * Copyright (c) 2021 Mladen Ivkovic (mladen.ivkovic@hotmail.com) * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published * by the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see . * ******************************************************************************/ #include "rt_interaction_cross_sections.h" #include "error.h" #include "rt_blackbody.h" #include "rt_properties.h" #include "rt_species.h" #include /** * @brief compute the chosen spectrum for a given frequency nu. * This function is intended to be used to integrate the spectra * over a frequency range in order to obtain averaged ionization * cross sections. * * @param nu frequency at which to evaluate spectrum * @param params spectrum integration params. Needs to be of type * void* for GSL integrators. */ __attribute__((always_inline)) INLINE static double rt_interaction_rates_get_spectrum(const double nu, void *params) { /* Keep this function in the .c file so you don't have to * include the spectra .h files like rt_blackbody.h anywhere * else */ struct rt_spectrum_integration_params *pars = (struct rt_spectrum_integration_params *)params; if (pars->spectrum_type == 0) { /* Constant spectrum */ if (nu <= pars->const_stellar_spectrum_frequency_max) { return 1.; } else { return 0.; } } else if (pars->spectrum_type == 1) { /* Blackbody spectrum */ const double T = pars->T; const double kB = pars->kB; const double h_planck = pars->h_planck; const double c = pars->c; return blackbody_spectrum_intensity(nu, T, kB, h_planck, c); } else { error("Unknown stellar spectrum type selected: %d", pars->spectrum_type); return 0.; } } /** * Spectrum function to be integrated. * This function is called by the GSL integrator. * * @param nu frequency at which to evaluate spectrum * @param params spectrum integration params. Needs to be of type * void* for GSL integrators. */ static double spectrum_integrand(double nu, void *params) { return rt_interaction_rates_get_spectrum(nu, params); } /** * Spectrum function divided by photon energy h*nu to be integrated. * This function is called by the GSL integrator. * * @param nu frequency at which to evaluate spectrum * @param params spectrum integration params. Needs to be of type * void* for GSL integrators. */ static double spectrum_over_hnu_integrand(double nu, void *params) { struct rt_spectrum_integration_params *p = (struct rt_spectrum_integration_params *)params; const double E = nu * p->h_planck; const double J = rt_interaction_rates_get_spectrum(nu, params); if (E > 0.) { return J / E; } else { return 0.; } } /** * Spectrum times cross section function to be integrated. * This function is called by the GSL integrator. * * @param nu frequency at which to evaluate spectrum * @param params spectrum integration params. Needs to be of type * void* for GSL integrators. */ static double spectrum_times_sigma_integrand(double nu, void *params) { struct rt_spectrum_integration_params *p = (struct rt_spectrum_integration_params *)params; const double E = nu * p->h_planck; const double sigma = photoionization_cross_section(E, p->species, p->cs_params); const double J = rt_interaction_rates_get_spectrum(nu, params); return J * sigma; } /** * Spectrum times cross section divided by h*nu function to be integrated. * This function is called by the GSL integrator. * * @param nu frequency at which to evaluate spectrum * @param params spectrum integration params. Needs to be of type * void* for GSL integrators. */ static double spectrum_times_sigma_over_hnu_integrand(double nu, void *params) { struct rt_spectrum_integration_params *p = (struct rt_spectrum_integration_params *)params; const double E = nu * p->h_planck; const double sigma = photoionization_cross_section(E, p->species, p->cs_params); const double J = rt_interaction_rates_get_spectrum(nu, params); return J * sigma / E; } /** * Integrate a function from nu_start to nu_stop with GSL routines * * @param function function to integrate * @param nu_start lower boundary of the integral * @param nu_stop upper boundary of the integral * @param params spectrum integration params. * */ static double rt_cross_sections_integrate_gsl( double (*function)(double, void *), double nu_start, double nu_stop, int npoints, struct rt_spectrum_integration_params *params) { gsl_function F; gsl_integration_workspace *w = gsl_integration_workspace_alloc(npoints); double result, error; F.function = function; F.params = (void *)params; /* NOTE: there is an option to use the integrator with an upper limit * of infinity, but this is accurate enough for now when setting a * high enough maximal frequency. */ gsl_integration_qags(&F, nu_start, nu_stop, /*espabs=*/0., /*epsrel=*/1e-7, npoints, w, &result, &error); /* Clean up after yourself. */ gsl_integration_workspace_free(w); return result; } /** * @brief allocate and pre-compute the averaged cross sections * for each photon group and ionizing species. * * @param rt_props RT properties struct * @param phys_const physical constants struct * @param us internal units struct **/ void rt_cross_sections_init(struct rt_props *restrict rt_props, const struct phys_const *restrict phys_const, const struct unit_system *restrict us) { /* Allocate the space to store the (cross section) integrals */ /* --------------------------------------------------------- */ double **cse = malloc(RT_NGROUPS * sizeof(double *)); double **csn = malloc(RT_NGROUPS * sizeof(double *)); double *av_energy = rt_props->average_photon_energy; double *photon_number_integral = rt_props->photon_number_integral; for (int group = 0; group < RT_NGROUPS; group++) { cse[group] = malloc(rt_ionizing_species_count * sizeof(double)); csn[group] = malloc(rt_ionizing_species_count * sizeof(double)); av_energy[group] = 0.; photon_number_integral[group] = 0.; } double integral_E[RT_NGROUPS]; double integral_N[RT_NGROUPS]; double integral_sigma_E[RT_NGROUPS][rt_ionizing_species_count]; double integral_sigma_E_over_hnu[RT_NGROUPS][rt_ionizing_species_count]; /* Grab constants and conversions in cgs */ /* ------------------------------------- */ const double T_cgs = units_cgs_conversion_factor(us, UNIT_CONV_TEMPERATURE); const float dimension_kB[5] = {1, 2, -2, 0, -1}; /* [g cm^2 s^-2 K^-1] */ const double kB_to_cgs = units_general_cgs_conversion_factor(us, dimension_kB); const double kB_cgs = phys_const->const_boltzmann_k * kB_to_cgs; const float dimension_h[5] = {1, 2, -1, 0, 0}; /* [g cm^2 s^-1] */ const double h_to_cgs = units_general_cgs_conversion_factor(us, dimension_h); const double h_planck_cgs = phys_const->const_planck_h * h_to_cgs; const double c_cgs = phys_const->const_speed_light_c * units_cgs_conversion_factor(us, UNIT_CONV_VELOCITY); /* Prepare parameter struct for integration functions */ /* -------------------------------------------------- */ const int spectype = rt_props->stellar_spectrum_type; const double maxfreq_const_spectrum = rt_props->const_stellar_spectrum_max_frequency; const double T_bb = rt_props->stellar_spectrum_blackbody_T * T_cgs; struct rt_photoion_cs_parameters cs_params = rt_init_photoion_cs_params_cgs(); struct rt_spectrum_integration_params integration_params = { /*species=*/0, /*spectrum_type=*/spectype, /*freq_max for const spectrum=*/maxfreq_const_spectrum, /*T=*/T_bb, /*kB=*/kB_cgs, /*h_planck=*/h_planck_cgs, /*c=*/c_cgs, /*cross section params=*/&cs_params}; /* Set up Integration Limits */ /* ------------------------- */ /* Get start and end points of the integrals */ double nu_start[RT_NGROUPS]; double nu_stop[RT_NGROUPS]; for (int group = 0; group < RT_NGROUPS; group++) nu_start[group] = rt_props->photon_groups[group]; for (int group = 0; group < RT_NGROUPS - 1; group++) nu_stop[group] = rt_props->photon_groups[group + 1]; if (RT_NGROUPS == 1) { /* If we only have one group, start integrating from the Hydrogen * ionization frequency, not from zero. The reasoning here is that * typically you define the *ionizing* radiation as stellar emission * rates, not the *total* radiation. */ nu_start[0] = cs_params.E_ion[rt_ionizing_species_HI] / h_planck_cgs; if (engine_rank == 0) message( "WARNING: with only 1 photon group, I'll start integrating" " the cross sections at the first ionizing frequency %.3g", nu_start[0]); } else { /* don't start at exactly 0 to avoid unlucky divisions */ if (nu_start[0] == 0.) nu_start[0] = min(1e-20, 1e-12 * nu_start[1]); } /* Get frequency at which we stop integrating */ double nu_stop_final; if (rt_props->stellar_spectrum_type == 0) { nu_stop_final = rt_props->const_stellar_spectrum_max_frequency; } else if (rt_props->stellar_spectrum_type == 1) { nu_stop_final = 10. * blackbody_peak_frequency(T_bb, kB_cgs, h_planck_cgs); } else { nu_stop_final = -1.; error("Unknown stellar spectrum type %d", rt_props->stellar_spectrum_type); } nu_stop[RT_NGROUPS - 1] = nu_stop_final; /* Compute Integrals */ /* ----------------- */ for (int g = 0; g < RT_NGROUPS; g++) { /* This is independent of species. */ integral_E[g] = rt_cross_sections_integrate_gsl( spectrum_integrand, nu_start[g], nu_stop[g], RT_INTEGRAL_NPOINTS, &integration_params); integral_N[g] = rt_cross_sections_integrate_gsl( spectrum_over_hnu_integrand, nu_start[g], nu_stop[g], RT_INTEGRAL_NPOINTS, &integration_params); for (int s = 0; s < rt_ionizing_species_count; s++) { integration_params.species = s; integral_sigma_E[g][s] = rt_cross_sections_integrate_gsl( spectrum_times_sigma_integrand, nu_start[g], nu_stop[g], RT_INTEGRAL_NPOINTS, &integration_params); integral_sigma_E_over_hnu[g][s] = rt_cross_sections_integrate_gsl( spectrum_times_sigma_over_hnu_integrand, nu_start[g], nu_stop[g], RT_INTEGRAL_NPOINTS, &integration_params); } } /* Now compute the actual average cross sections */ /* --------------------------------------------- */ for (int g = 0; g < RT_NGROUPS; g++) { photon_number_integral[g] = integral_N[g]; if (integral_N[g] > 0.) { av_energy[g] = integral_E[g] / integral_N[g]; } else { av_energy[g] = 0.; } for (int s = 0; s < rt_ionizing_species_count; s++) { if (integral_E[g] > 0.) { cse[g][s] = integral_sigma_E[g][s] / integral_E[g]; csn[g][s] = integral_sigma_E_over_hnu[g][s] / integral_N[g]; } else { /* No radiation = no interaction */ cse[g][s] = 0.; csn[g][s] = 0.; } } } /* for (int g = 0; g < RT_NGROUPS; g++) { */ /* printf("\nGroup %d\n", g); */ /* printf("nu_start: %12.6g\n", nu_start[g]); */ /* printf("nu_end: %12.6g\n", nu_stop[g]); */ /* printf("spectrum energy integral: %12.6g\n", integral_E[g]); */ /* printf("spectrum number integral: %12.6g\n", integral_N[g]); */ /* printf("average photon energy: %12.6g\n", av_energy[g]); */ /* */ /* printf("species: "); */ /* for (int s = 0; s < rt_ionizing_species_count; s++) printf("%12d ", s); */ /* printf("\n"); */ /* */ /* printf("integral sigma * E: "); */ /* for (int s = 0; s < rt_ionizing_species_count; s++) printf("%12.6g ", */ /* integral_sigma_E[g][s]); */ /* printf("\n"); */ /* */ /* printf("integral sigma * E / h nu: "); */ /* for (int s = 0; s < rt_ionizing_species_count; s++) printf("%12.6g ", */ /* integral_sigma_E_over_hnu[g][s]); */ /* printf("\n"); */ /* */ /* printf("energy weighted c.section: "); */ /* for (int s = 0; s < rt_ionizing_species_count; s++) printf("%12.6g ", */ /* cse[g][s]); */ /* printf("\n"); */ /* */ /* printf("number weighted c.section: "); */ /* for (int s = 0; s < rt_ionizing_species_count; s++) printf("%12.6g ", */ /* csn[g][s]); */ /* printf("\n"); */ /* } */ /* Store the results */ /* ----------------- */ if (rt_props->energy_weighted_cross_sections != NULL) { for (int g = 0; g < RT_NGROUPS; g++) { if (rt_props->energy_weighted_cross_sections[g] != NULL) free(rt_props->energy_weighted_cross_sections[g]); } free(rt_props->energy_weighted_cross_sections); } rt_props->energy_weighted_cross_sections = cse; if (rt_props->number_weighted_cross_sections != NULL) { for (int g = 0; g < RT_NGROUPS; g++) { if (rt_props->number_weighted_cross_sections[g] != NULL) free(rt_props->number_weighted_cross_sections[g]); } free(rt_props->number_weighted_cross_sections); } rt_props->number_weighted_cross_sections = csn; }