/*******************************************************************************
* This file is part of SWIFT.
* Copyright (c) 2016 Matthieu Schaller (schaller@strw.leidenuniv.nl)
*
* 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 .
*
******************************************************************************/
#ifndef SWIFT_EAGLE_STARS_IMF_H
#define SWIFT_EAGLE_STARS_IMF_H
/* Some standard headers. */
#include
/* Local includes. */
#include "exp10.h"
#include "inline.h"
#include "interpolate.h"
#include "minmax.h"
#include "yield_tables.h"
/**
* @brief the different weightings allowed for the IMF integration
*/
enum eagle_imf_integration_type {
eagle_imf_integration_no_weight, /* log10_max_mass)
error("Lower bound higher than larger bound.");
#endif
const int N_bins = eagle_feedback_N_imf_bins;
const double *const imf_bins_log10 = feedback_props->imf_mass_bin_log10;
/* Check whether lower mass is within the IMF mass bin range */
log10_min_mass = max(log10_min_mass, imf_bins_log10[0]);
log10_min_mass = min(log10_min_mass, imf_bins_log10[N_bins - 1]);
/* Check whether upper mass is within the IMF mass bin range */
log10_max_mass = max(log10_max_mass, imf_bins_log10[0]);
log10_max_mass = min(log10_max_mass, imf_bins_log10[N_bins - 1]);
*i_min = 0;
while ((*i_min < N_bins - 2) && imf_bins_log10[*i_min + 1] < log10_min_mass) {
(*i_min)++;
}
*i_max = 1;
while ((*i_max < N_bins - 1) && imf_bins_log10[*i_max] < log10_max_mass) {
(*i_max)++;
}
}
/**
* @brief Integrate the IMF between a minimum and maximum mass using the
* trapezoidal rule. The IMF may be weighted by various quantities, as specified
* by the variable, mode, including an input array, stellar_yields.
*
* @param log10_min_mass log10 mass lower integration bound
* @param log10_max_mass log10 mass upper integration bound
* @param mode Type of weighting for the IMF integration.
* @param stellar_yields Array of weights based on yields. Used only for
* yield-weighted integration.
* @param feedback_props the #feedback_props data structure
*/
INLINE static double integrate_imf(
const double log10_min_mass, const double log10_max_mass,
const enum eagle_imf_integration_type mode,
const double stellar_yields[eagle_feedback_N_imf_bins],
const struct feedback_props *feedback_props) {
/* Pull out some common terms */
const double *imf = feedback_props->imf;
const double *imf_mass_bin = feedback_props->imf_mass_bin;
const double *imf_mass_bin_log10 = feedback_props->imf_mass_bin_log10;
/* IMF mass bin spacing in log10 space. Assumes uniform spacing. */
const double imf_log10_mass_bin_size =
imf_mass_bin_log10[1] - imf_mass_bin_log10[0];
/* Determine bins to integrate over based on integration bounds */
int i_min, i_max;
determine_imf_bins(log10_min_mass, log10_max_mass, &i_min, &i_max,
feedback_props);
/* Array for the integrand */
double integrand[eagle_feedback_N_imf_bins];
/* Add up the contribution from each of the IMF mass bins */
switch (mode) {
case eagle_imf_integration_no_weight:
/* Integrate IMF on its own */
for (int i = i_min; i < i_max + 1; i++) {
integrand[i] = imf[i] * imf_mass_bin[i];
}
break;
case eagle_imf_integration_mass_weight:
/* Integrate IMF weighted by mass */
for (int i = i_min; i < i_max + 1; i++) {
integrand[i] = imf[i] * imf_mass_bin[i] * imf_mass_bin[i];
}
break;
case eagle_imf_integration_yield_weight:
#ifdef SWIFT_DEBUG_CHECKS
if (stellar_yields == NULL)
error(
"Yield array not passed in despite asking for yield-weighted IMf "
"integration.");
#endif
/* Integrate IMF weighted by yields */
for (int i = i_min; i < i_max + 1; i++) {
integrand[i] = stellar_yields[i] * imf[i] * imf_mass_bin[i];
}
break;
default:
error("Invalid mode for IMF integration");
}
/* Integrate using trapezoidal rule */
double result = 0.;
for (int i = i_min; i < i_max + 1; i++) {
result += integrand[i];
}
/* Update end bins since contribution was overcounted when summing up all
* entries */
result -= 0.5 * (integrand[i_min] + integrand[i_max]);
/* Correct first bin */
const double first_bin_offset =
(log10_min_mass - imf_mass_bin_log10[i_min]) / imf_log10_mass_bin_size;
if (first_bin_offset < 0.5) {
result -= first_bin_offset * integrand[i_min];
} else {
result -= 0.5 * integrand[i_min];
result -= (first_bin_offset - 0.5) * integrand[i_min + 1];
}
/* Correct last bin */
const double last_bin_offset =
(log10_max_mass - imf_mass_bin_log10[i_max - 1]) /
imf_log10_mass_bin_size;
if (last_bin_offset < 0.5) {
result -= 0.5 * integrand[i_max];
result -= (0.5 - last_bin_offset) * integrand[i_max - 1];
} else {
result -= (1.0 - last_bin_offset) * integrand[i_max];
}
/* The IMF is tabulated in log10, multiply by log10(mass bin size) to get
* result of integrating IMF */
return result * imf_log10_mass_bin_size * M_LN10;
}
/**
* @brief Allocate space for IMF table and compute values to populate this
* table.
*
* @param feedback_props #feedback_props data structure
*/
INLINE static void init_imf(struct feedback_props *feedback_props) {
/* Compute size of mass bins in log10 space */
const double imf_log10_mass_bin_size =
(feedback_props->log10_imf_max_mass_msun -
feedback_props->log10_imf_min_mass_msun) /
(double)(eagle_feedback_N_imf_bins - 1);
/* Allocate IMF array */
if (swift_memalign("imf-tables", (void **)&feedback_props->imf,
SWIFT_STRUCT_ALIGNMENT,
eagle_feedback_N_imf_bins * sizeof(double)) != 0)
error("Failed to allocate IMF bins table");
/* Allocate array to store IMF mass bins */
if (swift_memalign("imf-tables", (void **)&feedback_props->imf_mass_bin,
SWIFT_STRUCT_ALIGNMENT,
eagle_feedback_N_imf_bins * sizeof(double)) != 0)
error("Failed to allocate IMF bins table");
/* Allocate array to store IMF mass bins in log10 space */
if (swift_memalign("imf-tables", (void **)&feedback_props->imf_mass_bin_log10,
SWIFT_STRUCT_ALIGNMENT,
eagle_feedback_N_imf_bins * sizeof(double)) != 0)
error("Failed to allocate IMF bins table");
/* Set IMF from Chabrier 2003, PASP, 115, 763
* Eq. 17 with values from table 1 */
for (int i = 0; i < eagle_feedback_N_imf_bins; i++) {
/* Logarithmically-spaced bins in units of solar masses */
const double log10_mass_msun =
feedback_props->log10_imf_min_mass_msun + i * imf_log10_mass_bin_size;
const double mass_msun = exp10(log10_mass_msun);
feedback_props->imf_mass_bin[i] = mass_msun;
feedback_props->imf_mass_bin_log10[i] = log10_mass_msun;
if (mass_msun > 1.0) {
/* High-mass end */
feedback_props->imf[i] = 0.237912 * pow(mass_msun, -2.3);
} else {
/* Low-mass end */
feedback_props->imf[i] =
0.852464 *
exp((log10_mass_msun - log10(0.079)) *
(log10_mass_msun - log10(0.079)) / (-2.0 * 0.69 * 0.69)) /
mass_msun;
}
}
/* Normalize the IMF */
const float norm = integrate_imf(feedback_props->log10_imf_min_mass_msun,
feedback_props->log10_imf_max_mass_msun,
eagle_imf_integration_mass_weight,
/*(stellar_yields=)*/ NULL, feedback_props);
for (int i = 0; i < eagle_feedback_N_imf_bins; i++)
feedback_props->imf[i] /= norm;
}
/**
* @brief Calculate mass (in solar masses) of stars that died from the star
* particle's birth up to its current age (in Gyr).
*
* Calculation uses the tables of Portinari et al. 1998, A&A, 334, 505
*
* @param age_Gyr age of star in Gyr.
* @param Z Star's metallicity (metal mass fraction).
* @param feedback_props the #feedback_props data structure.
* @return Mass of stars died up to that age in solar masses.
*/
INLINE static double dying_mass_msun(
const double age_Gyr, const double Z,
const struct feedback_props *feedback_props) {
/* Pull out some common terms */
const double *lifetime_Z = feedback_props->lifetimes.metallicity;
const double *lifetime_m = feedback_props->lifetimes.mass;
double **const dying_times = feedback_props->lifetimes.dyingtime;
const int n_Z = eagle_feedback_lifetime_N_metals;
const int n_m = eagle_feedback_lifetime_N_masses;
/* Early abort? */
if (age_Gyr <= 0.) {
return feedback_props->imf_max_mass_msun;
}
const double log10_age_yr = log10(age_Gyr * 1.0e9);
/* Calculate index along the metallicity axis */
int Z_index;
double Z_offset;
if (Z <= lifetime_Z[0]) {
/* Before start of the table */
Z_index = 0;
Z_offset = 0.;
} else if (Z >= lifetime_Z[n_Z - 1]) {
/* After end of the table */
Z_index = n_Z - 2;
Z_offset = 1.;
} else {
/* Normal case: Somewhere inside the table */
Z_index = 0;
while (Z_index < n_Z - 1 && lifetime_Z[Z_index + 1] <= Z) {
Z_index++;
}
#ifdef SWIFT_DEBUG_CHECKS
if (Z_index >= n_Z) error("Z_index is beyond the range of the table");
#endif
Z_offset = (Z - lifetime_Z[Z_index]) /
(lifetime_Z[Z_index + 1] - lifetime_Z[Z_index]);
}
/* Check whether we are not beyond the age table for the low metallicity end
*/
int time_index_lowZ = -1;
double time_offset_lowZ = 0.;
if (log10_age_yr >= dying_times[Z_index][0]) {
/* Before start of the table */
time_index_lowZ = 0;
time_offset_lowZ = 0.;
} else if (log10_age_yr <= dying_times[Z_index][n_m - 1]) {
/* After end of the table */
time_index_lowZ = n_m - 2;
time_offset_lowZ = 1.;
}
/* Check whether we are not beyond the age table for the high metallicity end
*/
int time_index_highZ = -1;
double time_offset_highZ = 0.;
if (log10_age_yr >= dying_times[Z_index + 1][0]) {
/* Before start of the table */
time_index_highZ = 0;
time_offset_highZ = 0.;
} else if (log10_age_yr <= dying_times[Z_index + 1][n_m - 1]) {
/* After end of the table */
time_index_highZ = n_m - 2;
time_offset_highZ = 1.0;
}
/* Search the table starting from the largest times until we reach
a solution for the low-metallicity bound */
int i = n_m - 1;
while (i >= 0 && time_index_lowZ == -1) {
if (dying_times[Z_index][i] >= log10_age_yr && time_index_lowZ == -1) {
/* record index */
time_index_lowZ = i;
/* record distance from table element */
time_offset_lowZ =
(log10_age_yr - dying_times[Z_index][time_index_lowZ]) /
(dying_times[Z_index][time_index_lowZ + 1] -
dying_times[Z_index][time_index_lowZ]);
break;
}
i--;
}
#ifdef SWIFT_DEBUG_CHECKS
if (time_index_lowZ == -1) error("Could not find low-metallicity bound!");
#endif
/* Search the table starting from the largest times until we reach
a solution for the high-metallicity bound */
i = n_m - 1;
while (i >= 0 && time_index_highZ == -1) {
if (dying_times[Z_index + 1][i] >= log10_age_yr && time_index_highZ == -1) {
/* record index */
time_index_highZ = i;
/* record distance from table element */
time_offset_highZ =
(log10_age_yr - dying_times[Z_index + 1][time_index_highZ]) /
(dying_times[Z_index + 1][time_index_highZ + 1] -
dying_times[Z_index + 1][time_index_highZ]);
break;
}
i--;
}
#ifdef SWIFT_DEBUG_CHECKS
if (time_index_highZ == -1) error("Could not find high-metallicity bound!");
#endif
/* And now interpolate the solution */
const double mass_low_Z =
interpolate_1d(lifetime_m, time_index_lowZ, time_offset_lowZ);
const double mass_high_Z =
interpolate_1d(lifetime_m, time_index_highZ, time_offset_highZ);
double mass = (1. - Z_offset) * mass_low_Z + Z_offset * mass_high_Z;
/* Check that we haven't killed too many stars */
mass = min(mass, feedback_props->imf_max_mass_msun);
return mass;
}
/**
* @brief Calculate lifetime of stellar population in Gyr for a given mass.
*
* Calculation uses the tables of Portinari et al. 1998, A&A, 334, 505
*
* @param mass in solar masses.
* @param Z Metallicity (metal mass fraction).
* @param feedback_props the #feedback_props data structure.
* @return The life time in Giga-years.
*/
INLINE static float lifetime_in_Gyr(
const float mass, const float Z,
const struct feedback_props *feedback_props) {
/* Pull out some common terms */
const double *lifetime_Z = feedback_props->lifetimes.metallicity;
const double *lifetime_m = feedback_props->lifetimes.mass;
double **const dying_times = feedback_props->lifetimes.dyingtime;
const int n_Z = eagle_feedback_lifetime_N_metals;
const int n_m = eagle_feedback_lifetime_N_masses;
/* Calculate index along the mass axis */
int m_index;
float m_offset;
if (mass <= lifetime_m[0]) {
/* Before start of the table */
m_index = 0;
m_offset = 0.f;
} else if (mass >= lifetime_m[n_m - 1]) {
/* After end of the table */
m_index = n_m - 2;
m_offset = 1.f;
} else {
/* Normal case: Somewhere inside the table */
for (m_index = 0; m_index < n_m - 1; m_index++)
if (lifetime_m[m_index + 1] > mass) break;
m_offset = (mass - lifetime_m[m_index]) /
(lifetime_m[m_index + 1] - lifetime_m[m_index]);
}
/* Calculate index along the metallicity axis */
int Z_index;
float Z_offset;
if (Z <= lifetime_Z[0]) {
/* Before start of the table */
Z_index = 0;
Z_offset = 0.f;
} else if (Z >= lifetime_Z[n_Z - 1]) {
/* After end of the table */
Z_index = n_Z - 2;
Z_offset = 1.f;
} else {
for (Z_index = 0; Z_index < n_Z - 1; Z_index++)
if (lifetime_Z[Z_index + 1] > Z) break;
/* Normal case: Somewhere inside the table */
Z_offset = (Z - lifetime_Z[Z_index]) /
(lifetime_Z[Z_index + 1] - lifetime_Z[Z_index]);
}
/* Interpolation of the table to get the time */
const float log_time_years =
interpolate_2d(dying_times, Z_index, m_index, Z_offset, m_offset);
/* Convert to Giga-years */
const float time_Gyr = exp10f(log_time_years - 9.f);
return time_Gyr;
}
#endif