fix(weakRates): major progress in resolving bugs
bigs were introduced by the interface change from accepting raw molar abundance vectors to using the composition vector. This commit resolves many of these, including preformant ways to report that a species is not present in the composition and unified index lookups using composition object tooling. BREAKING CHANGE:
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
#define GRIDFIRE_WEAK_REACTION_LIB_SENTINEL -60.0
|
||||
#define GRIDFIRE_WEAK_REACTION_LIB_SENTINEL (-60.0)
|
||||
|
||||
#include "gridfire/reaction/reaction.h"
|
||||
#include "gridfire/reaction/weak/weak_types.h"
|
||||
@@ -541,21 +541,74 @@ namespace gridfire::rates::weak {
|
||||
m_atomic(ax, ay);
|
||||
rateConstant = static_cast<T>(ay[0]);
|
||||
} else { // The case where T is of type double
|
||||
const std::expected<WeakRatePayload, InterpolationError> result = m_interpolator.get_rates(
|
||||
std::expected<WeakRatePayload, InterpolationError> result = m_interpolator.get_rates(
|
||||
static_cast<uint16_t>(m_reactant_a),
|
||||
static_cast<uint8_t>(m_reactant_z),
|
||||
T9,
|
||||
log_rhoYe,
|
||||
mue
|
||||
log_rhoYe
|
||||
);
|
||||
|
||||
// TODO: Clean this up. When a bit of code needs this many comments to make it clear it is bad code
|
||||
if (!result.has_value()) {
|
||||
const InterpolationErrorType type = result.error().type;
|
||||
const std::string msg = std::format(
|
||||
"Failed to interpolate weak rate for (A={}, Z={}) at T9={}, log10(rho*Ye)={}, mu_e={} with error: {}",
|
||||
m_reactant.name(), m_reactant_a, m_reactant_z, T9, log_rhoYe, mue, InterpolationErrorTypeMap.at(type)
|
||||
bool okayToClamp = true;
|
||||
const auto&[errorType, boundsErrorInfo] = result.error();
|
||||
|
||||
// The logic here is
|
||||
// 1. If there is any bounds error in T9 then we do not allow clamping. T9 should be a large enough grid
|
||||
// that the user should not be asking for values outside the grid.
|
||||
// 2. If there is no bounds error in T9, but there is a bounds error in log_rhoYe, then we only allow
|
||||
// clamping if the query value is below the minimum of the grid. If it is above the maximum
|
||||
// of the grid, then we do not allow clamping. The reason for this is that at high density,
|
||||
// screening and other effects can make a significant difference to the rates, and
|
||||
// the user should be aware that they are asking for a value outside the grid.
|
||||
|
||||
// There are a couple of safety asserts in here that are only active in debug builds. These are to
|
||||
// ensure that our assumptions about the error information are correct. These should really never
|
||||
// be triggered, but if they are, they will help us to identify any issues.
|
||||
if (errorType == InterpolationErrorType::BOUNDS_ERROR) {
|
||||
assert(boundsErrorInfo.has_value()); // must be true if type is BOUNDS_ERROR, removed in release builds
|
||||
|
||||
if (boundsErrorInfo->contains(TableAxes::T9)) {
|
||||
okayToClamp = false;
|
||||
} else {
|
||||
assert(boundsErrorInfo->contains(TableAxes::LOG_RHOYE)); // must be true if T9 is not, removed in release builds
|
||||
const BoundsErrorInfo& boundsError = boundsErrorInfo->at(TableAxes::LOG_RHOYE);
|
||||
|
||||
if (boundsError.queryValue > boundsError.axisMaxValue) {
|
||||
okayToClamp = false;
|
||||
}
|
||||
|
||||
assert(boundsError.queryValue < boundsError.axisMinValue); // Given the above logic, this must be true, removed in release builds
|
||||
}
|
||||
}
|
||||
|
||||
if (!okayToClamp) {
|
||||
const InterpolationErrorType type = result.error().type;
|
||||
const std::string msg = std::format(
|
||||
"Failed to interpolate weak rate for {} (A={}, Z={}) at T9={}, log10(rho*Ye)={}, with error: {}. Clamping disallowed due to either query value being out of bounds in T9 or being above the maximum in log10(rho*Ye).",
|
||||
m_reactant.name(), m_reactant_a, m_reactant_z, T9, log_rhoYe, InterpolationErrorTypeMap.at(type)
|
||||
);
|
||||
throw std::runtime_error(msg);
|
||||
}
|
||||
|
||||
// In the case we get here the error was a bounds error in log_rhoYe and the query value was below the minimum of the grid
|
||||
// so the solution is to clamp the query value to the minimum of the grid and try again.
|
||||
result = m_interpolator.get_rates(
|
||||
static_cast<uint16_t>(m_reactant_a),
|
||||
static_cast<uint8_t>(m_reactant_z),
|
||||
T9,
|
||||
boundsErrorInfo->at(TableAxes::LOG_RHOYE).axisMinValue
|
||||
);
|
||||
throw std::runtime_error(msg);
|
||||
|
||||
// Check the result again. If it fails this time then we have a real problem and we throw.
|
||||
if (!result.has_value()) {
|
||||
const InterpolationErrorType type = result.error().type;
|
||||
const std::string msg = std::format(
|
||||
"After clamping, failed to interpolate weak rate for {} (A={}, Z={}) at T9={}, log10(rho*Ye)={}, with error: {}",
|
||||
m_reactant.name(), m_reactant_a, m_reactant_z, T9, log_rhoYe, InterpolationErrorTypeMap.at(type)
|
||||
);
|
||||
throw std::runtime_error(msg);
|
||||
}
|
||||
}
|
||||
const WeakRatePayload payload = result.value();
|
||||
const double logRate = get_log_rate_from_payload(payload);
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
#include "gridfire/reaction/weak/weak_types.h"
|
||||
#include "fourdst/composition/atomicSpecies.h"
|
||||
#include "fourdst/logging/logging.h"
|
||||
|
||||
#include <unordered_map>
|
||||
#include <cstdint>
|
||||
@@ -66,7 +67,6 @@ namespace gridfire::rates::weak {
|
||||
* @param Z Proton number of the isotope.
|
||||
* @param t9 Temperature in GK (10^9 K).
|
||||
* @param log_rhoYe Log10 of rho*Ye (cgs density times electron fraction).
|
||||
* @param mu_e Electron chemical potential (MeV).
|
||||
* @return expected<WeakRatePayload, InterpolationError>: payload on success;
|
||||
* InterpolationError::UNKNOWN_SPECIES_ERROR if (A,Z) not present; or
|
||||
* InterpolationError::BOUNDS_ERROR if any coordinate is outside the table
|
||||
@@ -84,8 +84,7 @@ namespace gridfire::rates::weak {
|
||||
uint16_t A,
|
||||
uint8_t Z,
|
||||
double t9,
|
||||
double log_rhoYe,
|
||||
double mu_e
|
||||
double log_rhoYe
|
||||
) const;
|
||||
|
||||
/**
|
||||
@@ -100,7 +99,6 @@ namespace gridfire::rates::weak {
|
||||
* @param Z Proton number of the isotope.
|
||||
* @param t9 Temperature in GK (10^9 K).
|
||||
* @param log_rhoYe Log10 of rho*Ye (cgs density times electron fraction).
|
||||
* @param mu_e Electron chemical potential (MeV).
|
||||
* @return expected<WeakRateDerivatives, InterpolationError>: derivative payload on success;
|
||||
* otherwise an InterpolationError as described above.
|
||||
* @par Example
|
||||
@@ -114,10 +112,10 @@ namespace gridfire::rates::weak {
|
||||
uint16_t A,
|
||||
uint8_t Z,
|
||||
double t9,
|
||||
double log_rhoYe,
|
||||
double mu_e
|
||||
double log_rhoYe
|
||||
) const;
|
||||
private:
|
||||
quill::Logger* m_logger = fourdst::logging::LogManager::getInstance().getLogger("log");
|
||||
/**
|
||||
* @brief Pack (A,Z) into a 32-bit key used for the internal map.
|
||||
*
|
||||
|
||||
@@ -93,18 +93,18 @@ namespace gridfire::rates::weak {
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Partial derivatives of the log10() fields w.r.t. (T9, log10(rho*Ye), mu_e).
|
||||
* @brief Partial derivatives of the log10() fields w.r.t. (T9, log10(rho*Ye)).
|
||||
*
|
||||
* Array ordering is [d/dT9, d/dlogRhoYe, d/dMuE] for each corresponding field.
|
||||
* Array ordering is [d/dT9, d/dlogRhoYe] for each corresponding field.
|
||||
*/
|
||||
struct WeakRateDerivatives {
|
||||
// Each array holds [d/dT9, d/dlogRhoYe, d/dMuE]
|
||||
std::array<double, 3> d_log_beta_plus;
|
||||
std::array<double, 3> d_log_electron_capture;
|
||||
std::array<double, 3> d_log_neutrino_loss_ec;
|
||||
std::array<double, 3> d_log_beta_minus;
|
||||
std::array<double, 3> d_log_positron_capture;
|
||||
std::array<double, 3> d_log_antineutrino_loss_bd;
|
||||
std::array<double, 2> d_log_beta_plus;
|
||||
std::array<double, 2> d_log_electron_capture;
|
||||
std::array<double, 2> d_log_neutrino_loss_ec;
|
||||
std::array<double, 2> d_log_beta_minus;
|
||||
std::array<double, 2> d_log_positron_capture;
|
||||
std::array<double, 2> d_log_antineutrino_loss_bd;
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -133,6 +133,19 @@ namespace gridfire::rates::weak {
|
||||
LOG_RHOYE, ///< log10(rho*Ye).
|
||||
MUE ///< Electron chemical potential (MeV).
|
||||
};
|
||||
}
|
||||
|
||||
// This need to be here to avoid compiler issues related to the order of specialization
|
||||
namespace std {
|
||||
template <>
|
||||
struct hash<gridfire::rates::weak::TableAxes> {
|
||||
std::size_t operator()(gridfire::rates::weak::TableAxes t) const noexcept {
|
||||
return std::hash<int>()(static_cast<int>(t));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
namespace gridfire::rates::weak {
|
||||
|
||||
/**
|
||||
* @brief Detailed bounds information for a BOUNDS_ERROR.
|
||||
@@ -154,20 +167,20 @@ namespace gridfire::rates::weak {
|
||||
std::optional<std::unordered_map<TableAxes, BoundsErrorInfo>> boundsErrorInfo = std::nullopt;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @brief Regular 3D grid and payloads for a single isotope (A,Z).
|
||||
* @brief Regular 2D grid and payloads for a single isotope (A,Z).
|
||||
*
|
||||
* Axes are monotonically increasing per dimension. Data vector is laid out in
|
||||
* row-major order with index computed as:
|
||||
* index = ((i_t9 * rhoYe_axis.size() + j_rhoYe) * mue_axis.size()) + k_mue
|
||||
*
|
||||
* index = i_t9 * N_rhoYe + j_rhoYe
|
||||
*
|
||||
*/
|
||||
struct IsotopeGrid {
|
||||
std::vector<double> t9_axis; ///< Unique sorted T9 grid.
|
||||
std::vector<double> rhoYe_axis;///< Unique sorted log10(rho*Ye) grid.
|
||||
std::vector<double> mue_axis; ///< Unique sorted mu_e grid.
|
||||
|
||||
// index = ((i_t9 * rhoYe_axis.size() + j_rhoYe) * mue_axis.size()) + k_mue
|
||||
std::vector<WeakRatePayload> data; ///< Payloads at each grid node.
|
||||
std::vector<double> t9_axis; ///< Unique sorted T9 grid.
|
||||
std::vector<double> rhoYe_axis; ///< Unique sorted log10(rho*Ye) grid.
|
||||
std::vector<WeakRatePayload> data; ///< MuE axis for each (T9, log_rhoYe) pair (the table is ragged in mu_e). This is also where the payloads are stored.
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -216,4 +229,5 @@ namespace gridfire::rates::weak {
|
||||
return os;
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user