/*******************************************************************************
* This file is part of SWIFT.
* Copyright (c) 2016 Matthieu Schaller (schaller@strw.leidenuniv.nl).
* 2018 Jacob Kegerreis (jacob.kegerreis@durham.ac.uk).
*
* 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_TILLOTSON_EQUATION_OF_STATE_H
#define SWIFT_TILLOTSON_EQUATION_OF_STATE_H
/**
* @file equation_of_state/planetary/tillotson.h
*
* Contains the Tillotson EOS functions for
* equation_of_state/planetary/equation_of_state.h
*
*/
/* Some standard headers. */
#include
/* Local headers. */
#include "adiabatic_index.h"
#include "common_io.h"
#include "equation_of_state.h"
#include "inline.h"
#include "physical_constants.h"
#include "sesame.h"
#include "units.h"
// Tillotson parameters
struct Til_params {
float rho_0, a, b, A, B, u_0, u_iv, u_cv, alpha, beta;
float eta_min, eta_zero, P_min, C_V;
float *A1_u_cold;
float rho_cold_min, rho_cold_max;
float rho_min, rho_max;
enum eos_planetary_material_id mat_id;
};
// Parameter values for each material (SI units)
INLINE static void set_Til_iron(struct Til_params *mat,
enum eos_planetary_material_id mat_id) {
mat->mat_id = mat_id;
mat->rho_0 = 7800.0f;
mat->a = 0.5f;
mat->b = 1.5f;
mat->A = 1.28e11f;
mat->B = 1.05e11f;
mat->u_0 = 9.5e6f;
mat->u_iv = 2.4e6f;
mat->u_cv = 8.67e6f;
mat->alpha = 5.0f;
mat->beta = 5.0f;
mat->eta_min = 0.0f;
mat->eta_zero = 0.0f;
mat->P_min = 0.01f;
mat->C_V = 449.0f;
mat->rho_cold_min = 100.0f;
mat->rho_cold_max = 1.0e5f;
mat->rho_min = 1.0f;
mat->rho_max = 1.0e5f;
}
INLINE static void set_Til_granite(struct Til_params *mat,
enum eos_planetary_material_id mat_id) {
mat->mat_id = mat_id;
mat->rho_0 = 2680.0f;
mat->a = 0.5f;
mat->b = 1.3f;
mat->A = 1.8e10f;
mat->B = 1.8e10f;
mat->u_0 = 1.6e7f;
mat->u_iv = 3.5e6f;
mat->u_cv = 1.8e7f;
mat->alpha = 5.0f;
mat->beta = 5.0f;
mat->eta_min = 0.0f;
mat->eta_zero = 0.0f;
mat->P_min = 0.01f;
mat->C_V = 790.0f;
mat->rho_cold_min = 100.0f;
mat->rho_cold_max = 1.0e5f;
mat->rho_min = 1.0f;
mat->rho_max = 1.0e5f;
}
INLINE static void set_Til_basalt(struct Til_params *mat,
enum eos_planetary_material_id mat_id) {
mat->mat_id = mat_id;
mat->rho_0 = 2700.0f;
mat->a = 0.5f;
mat->b = 1.5f;
mat->A = 2.67e10f;
mat->B = 2.67e10f;
mat->u_0 = 4.87e8f;
mat->u_iv = 4.72e6f;
mat->u_cv = 1.82e7f;
mat->alpha = 5.0f;
mat->beta = 5.0f;
mat->eta_min = 0.0f;
mat->eta_zero = 0.0f;
mat->P_min = 0.01f;
mat->C_V = 790.0f;
mat->rho_cold_min = 100.0f;
mat->rho_cold_max = 1.0e5f;
mat->rho_min = 1.0f;
mat->rho_max = 1.0e5f;
}
INLINE static void set_Til_water(struct Til_params *mat,
enum eos_planetary_material_id mat_id) {
mat->mat_id = mat_id;
mat->rho_0 = 998.0f;
mat->a = 0.7f;
mat->b = 0.15f;
mat->A = 2.18e9f;
mat->B = 1.325e10f;
mat->u_0 = 7.0e6f;
mat->u_iv = 4.19e5f;
mat->u_cv = 2.69e6f;
mat->alpha = 10.0f;
mat->beta = 5.0f;
mat->eta_min = 0.925f;
mat->eta_zero = 0.875f;
mat->P_min = 0.01f;
mat->C_V = 4186.0f;
mat->rho_cold_min = 100.0f;
mat->rho_cold_max = 1.0e5f;
mat->rho_min = 1.0f;
mat->rho_max = 1.0e5f;
}
INLINE static void set_Til_ice(struct Til_params *mat,
enum eos_planetary_material_id mat_id) {
mat->mat_id = mat_id;
mat->rho_0 = 1293.0f;
mat->a = 0.3f;
mat->b = 0.1f;
mat->A = 1.07e10f;
mat->B = 6.5e10f;
mat->u_0 = 1.0e7f;
mat->u_iv = 7.73e5f;
mat->u_cv = 3.04e6f;
mat->alpha = 10.0f;
mat->beta = 5.0f;
mat->eta_min = 0.925f;
mat->eta_zero = 0.875f;
mat->P_min = 0.0f;
mat->C_V = 2093.0f;
mat->rho_cold_min = 100.0f;
mat->rho_cold_max = 1.0e5f;
mat->rho_min = 1.0f;
mat->rho_max = 1.0e5f;
}
/*
Read the parameters from a file.
File contents
-------------
# header (5 lines)
rho_0 (kg/m3) a (-) b (-) A (Pa) B (Pa)
u_0 (J) u_iv (J) u_cv (J) alpha (-) beta (-)
eta_min (-) eta_zero (-) P_min (Pa) C_V (J kg^-1 K^-1)
rho_cold_min (kg/m3) rho_cold_max (kg/m3) rho_min (kg/m3) rho_max (kg/m3)
*/
INLINE static void set_Til_custom(struct Til_params *mat,
enum eos_planetary_material_id mat_id,
char *param_file) {
mat->mat_id = mat_id;
// Load table contents from file
FILE *f = fopen(param_file, "r");
if (f == NULL)
error("Failed to open the Tillotson EoS file '%s'", param_file);
// Skip header lines
skip_lines(f, 5);
// Read parameters (SI)
int c;
c = fscanf(f, "%f %f %f %f %f", &mat->rho_0, &mat->a, &mat->b, &mat->A,
&mat->B);
if (c != 5) error("Failed to read the Tillotson EoS file %s", param_file);
c = fscanf(f, "%f %f %f %f %f", &mat->u_0, &mat->u_iv, &mat->u_cv,
&mat->alpha, &mat->beta);
if (c != 5) error("Failed to read the Tillotson EoS file %s", param_file);
c = fscanf(f, "%f %f %f %f", &mat->eta_min, &mat->eta_zero, &mat->P_min,
&mat->C_V);
if (c != 4) error("Failed to read the Tillotson EoS file %s", param_file);
c = fscanf(f, "%f %f %f %f", &mat->rho_cold_min, &mat->rho_cold_max,
&mat->rho_min, &mat->rho_max);
if (c != 4) error("Failed to read the Tillotson EoS file %s", param_file);
}
// Convert to internal units
INLINE static void convert_units_Til(struct Til_params *mat,
const struct unit_system *us) {
struct unit_system si;
units_init_si(&si);
const int N = 10000;
// SI to cgs
mat->rho_0 *= units_cgs_conversion_factor(&si, UNIT_CONV_DENSITY);
mat->A *= units_cgs_conversion_factor(&si, UNIT_CONV_PRESSURE);
mat->B *= units_cgs_conversion_factor(&si, UNIT_CONV_PRESSURE);
mat->u_0 *= units_cgs_conversion_factor(&si, UNIT_CONV_ENERGY_PER_UNIT_MASS);
mat->u_iv *= units_cgs_conversion_factor(&si, UNIT_CONV_ENERGY_PER_UNIT_MASS);
mat->u_cv *= units_cgs_conversion_factor(&si, UNIT_CONV_ENERGY_PER_UNIT_MASS);
mat->P_min *= units_cgs_conversion_factor(&si, UNIT_CONV_PRESSURE);
for (int i = 0; i < N; i++) {
mat->A1_u_cold[i] *=
units_cgs_conversion_factor(&si, UNIT_CONV_ENERGY_PER_UNIT_MASS);
}
mat->C_V *= units_cgs_conversion_factor(&si, UNIT_CONV_ENERGY_PER_UNIT_MASS);
// Entropy units don't work? using internal kelvin
mat->rho_cold_min *= units_cgs_conversion_factor(&si, UNIT_CONV_DENSITY);
mat->rho_cold_max *= units_cgs_conversion_factor(&si, UNIT_CONV_DENSITY);
mat->rho_min *= units_cgs_conversion_factor(&si, UNIT_CONV_DENSITY);
mat->rho_max *= units_cgs_conversion_factor(&si, UNIT_CONV_DENSITY);
// cgs to internal
mat->rho_0 /= units_cgs_conversion_factor(us, UNIT_CONV_DENSITY);
mat->A /= units_cgs_conversion_factor(us, UNIT_CONV_PRESSURE);
mat->B /= units_cgs_conversion_factor(us, UNIT_CONV_PRESSURE);
mat->u_0 /= units_cgs_conversion_factor(us, UNIT_CONV_ENERGY_PER_UNIT_MASS);
mat->u_iv /= units_cgs_conversion_factor(us, UNIT_CONV_ENERGY_PER_UNIT_MASS);
mat->u_cv /= units_cgs_conversion_factor(us, UNIT_CONV_ENERGY_PER_UNIT_MASS);
mat->P_min /= units_cgs_conversion_factor(us, UNIT_CONV_PRESSURE);
for (int i = 0; i < N; i++) {
mat->A1_u_cold[i] /=
units_cgs_conversion_factor(us, UNIT_CONV_ENERGY_PER_UNIT_MASS);
}
mat->C_V /= units_cgs_conversion_factor(us, UNIT_CONV_ENERGY_PER_UNIT_MASS);
// Entropy units don't work? using internal kelvin
mat->rho_cold_min /= units_cgs_conversion_factor(us, UNIT_CONV_DENSITY);
mat->rho_cold_max /= units_cgs_conversion_factor(us, UNIT_CONV_DENSITY);
mat->rho_min /= units_cgs_conversion_factor(us, UNIT_CONV_DENSITY);
mat->rho_max /= units_cgs_conversion_factor(us, UNIT_CONV_DENSITY);
}
// gas_internal_energy_from_entropy
INLINE static float Til_internal_energy_from_entropy(
const float density, const float entropy, const struct Til_params *mat) {
error("This EOS function is not yet implemented!");
return 0.f;
}
// gas_pressure_from_entropy
INLINE static float Til_pressure_from_entropy(const float density,
const float entropy,
const struct Til_params *mat) {
error("This EOS function is not yet implemented!");
return 0.f;
}
// gas_entropy_from_pressure
INLINE static float Til_entropy_from_pressure(const float density,
const float pressure,
const struct Til_params *mat) {
error("This EOS function is not yet implemented!");
return 0.f;
}
// gas_soundspeed_from_entropy
INLINE static float Til_soundspeed_from_entropy(const float density,
const float entropy,
const struct Til_params *mat) {
error("This EOS function is not yet implemented!");
return 0.f;
}
// gas_entropy_from_internal_energy
INLINE static float Til_entropy_from_internal_energy(
const float density, const float u, const struct Til_params *mat) {
return 0.f;
}
// gas_pressure_from_internal_energy
INLINE static float Til_pressure_from_internal_energy(
const float density, const float u, const struct Til_params *mat) {
const float eta = density / mat->rho_0;
const float eta_sq = eta * eta;
const float mu = eta - 1.f;
const float nu = 1.f / eta - 1.f;
const float w = u / (mat->u_0 * eta_sq) + 1.f;
const float w_inv = 1.f / w;
// Condensed or cold
float P_c =
(mat->a + mat->b * w_inv) * density * u + mat->A * mu + mat->B * mu * mu;
if (eta < mat->eta_zero) {
P_c = 0.f;
} else if (eta < mat->eta_min) {
P_c *= (eta - mat->eta_zero) / (mat->eta_min - mat->eta_zero);
}
// Expanded and hot
float P_e = mat->a * density * u + (mat->b * density * u * w_inv +
mat->A * mu * expf(-mat->beta * nu)) *
expf(-mat->alpha * nu * nu);
// Condensed or cold state
float P;
if ((1.f < eta) || (u < mat->u_iv)) {
P = P_c;
}
// Expanded and hot state
else if ((eta < 1.f) && (mat->u_cv < u)) {
P = P_e;
}
// Hybrid state
else {
P = ((u - mat->u_iv) * P_e + (mat->u_cv - u) * P_c) /
(mat->u_cv - mat->u_iv);
}
// Minimum pressure
if (P < mat->P_min) {
P = mat->P_min;
}
return P;
}
// gas_internal_energy_from_pressure
INLINE static float Til_internal_energy_from_pressure(
const float density, const float P, const struct Til_params *mat) {
error("This EOS function is not yet implemented!");
return 0.f;
}
// gas_soundspeed_from_internal_energy
INLINE static float Til_soundspeed_from_internal_energy(
const float density, const float u, const struct Til_params *mat) {
const float rho_0_inv = 1.f / mat->rho_0;
const float eta = density * rho_0_inv;
const float rho_inv = 1.f / density;
const float eta_sq = eta * eta;
const float mu = eta - 1.f;
const float nu = 1.f / eta - 1.f;
const float w = u / (mat->u_0 * eta_sq) + 1.f;
const float w_inv = 1.f / w;
const float w_inv_sq = w_inv * w_inv;
const float exp_beta = expf(-mat->beta * nu);
const float exp_alpha = expf(-mat->alpha * nu * nu);
// Condensed or cold
float P_c =
(mat->a + mat->b * w_inv) * density * u + mat->A * mu + mat->B * mu * mu;
if (eta < mat->eta_zero) {
P_c = 0.f;
} else if (eta < mat->eta_min) {
P_c *= (eta - mat->eta_zero) / (mat->eta_min - mat->eta_zero);
}
float c_sq_c = P_c * rho_inv * (1.f + mat->a + mat->b * w_inv) +
mat->b * (w - 1.f) * w_inv_sq * (2.f * u - P_c * rho_inv) +
rho_inv * (mat->A + mat->B * (eta_sq - 1.f));
// Expanded and hot
float P_e =
mat->a * density * u +
(mat->b * density * u * w_inv + mat->A * mu * exp_beta) * exp_alpha;
float c_sq_e =
P_e * rho_inv * (1.f + mat->a + mat->b * w_inv * exp_alpha) +
(mat->b * density * u * w_inv_sq / eta_sq *
(rho_inv / mat->u_0 * (2.f * u - P_e * rho_inv) +
2.f * mat->alpha * nu * w * rho_0_inv) +
mat->A * rho_0_inv *
(1.f + mu / eta_sq * (mat->beta + 2.f * mat->alpha * nu - eta)) *
exp_beta) *
exp_alpha;
// Condensed or cold state
float c_sq;
if ((1.f < eta) || (u < mat->u_iv)) {
c_sq = c_sq_c;
}
// Expanded and hot state
else if ((eta < 1.f) && (mat->u_cv < u)) {
c_sq = c_sq_e;
}
// Hybrid state
else {
c_sq = ((u - mat->u_iv) * c_sq_e + (mat->u_cv - u) * c_sq_c) /
(mat->u_cv - mat->u_iv);
}
c_sq = fmaxf(c_sq, mat->A * rho_0_inv);
return sqrtf(c_sq);
}
// gas_soundspeed_from_pressure
INLINE static float Til_soundspeed_from_pressure(const float density,
const float P,
const struct Til_params *mat) {
error("This EOS function is not yet implemented!");
return 0.f;
}
// Compute u cold
INLINE static float compute_u_cold(
const float density, const struct Til_params *mat,
const enum eos_planetary_material_id mat_id) {
const int N = 10000;
const float rho_0 = mat->rho_0;
const float drho = (density - rho_0) / N;
float x = rho_0;
float u_cold = 1e-9;
for (int i = 0; i < N; i++) {
x += drho;
u_cold +=
Til_pressure_from_internal_energy(x, u_cold, mat) * drho / (x * x);
}
return u_cold;
}
// Compute A1_u_cold
INLINE static void set_Til_u_cold(struct Til_params *mat,
enum eos_planetary_material_id mat_id) {
const int N = 10000;
const float rho_min = 100.f;
const float rho_max = 100000.f;
// Allocate table memory
mat->A1_u_cold = (float *)malloc(N * sizeof(float));
float rho = rho_min;
const float drho = (rho_max - rho_min) / (N - 1);
for (int i = 0; i < N; i++) {
mat->A1_u_cold[i] = compute_u_cold(rho, mat, mat_id);
rho += drho;
}
}
// Compute u cold fast from precomputed values
INLINE static float compute_fast_u_cold(const float density,
const struct Til_params *mat) {
const int N = 10000;
const float rho_min = mat->rho_cold_min;
const float rho_max = mat->rho_cold_max;
const float drho = (rho_max - rho_min) / (N - 1);
const int a = (int)((density - rho_min) / drho);
const int b = a + 1;
float u_cold;
if (a >= 0 && a < (N - 1)) {
u_cold = mat->A1_u_cold[a];
u_cold += ((mat->A1_u_cold[b] - mat->A1_u_cold[a]) / drho) *
(density - rho_min - a * drho);
} else if (density < rho_min) {
u_cold = mat->A1_u_cold[0];
} else {
u_cold = mat->A1_u_cold[N - 1];
u_cold += ((mat->A1_u_cold[N - 1] - mat->A1_u_cold[N - 2]) / drho) *
(density - rho_max);
}
return u_cold;
}
// gas_temperature_from_internal_energy
INLINE static float Til_temperature_from_internal_energy(
const float density, const float u, const struct Til_params *mat) {
const float u_cold = compute_fast_u_cold(density, mat);
float T = (u - u_cold) / (mat->C_V);
if (T < 0.f) {
T = 0.f;
}
return T;
}
// gas_pressure_from_density_and_temperature
INLINE static float Til_pressure_from_temperature(
const float density, const float T, const struct Til_params *mat) {
const float u = compute_fast_u_cold(density, mat) + mat->C_V * T;
const float P = Til_pressure_from_internal_energy(density, u, mat);
return P;
}
// gas_density_from_pressure_and_temperature
INLINE static float Til_density_from_pressure_and_temperature(
const float P, const float T, const struct Til_params *mat) {
float rho_min = mat->rho_min;
float rho_max = mat->rho_max;
float rho_mid = (rho_min + rho_max) / 2.f;
// Check for P == 0 or T == 0
float P_des;
if (P <= mat->P_min) {
P_des = mat->P_min;
} else {
P_des = P;
}
float P_min = Til_pressure_from_temperature(rho_min, T, mat);
float P_mid = Til_pressure_from_temperature(rho_mid, T, mat);
float P_max = Til_pressure_from_temperature(rho_max, T, mat);
// quick fix?
if (P_des < P_min) {
P_des = P_min;
}
const float tolerance = 0.001 * rho_min;
int counter = 0;
const int max_counter = 200;
if (P_des >= P_min && P_des <= P_max) {
while ((rho_max - rho_min) > tolerance && counter < max_counter) {
P_min = Til_pressure_from_temperature(rho_min, T, mat);
P_mid = Til_pressure_from_temperature(rho_mid, T, mat);
P_max = Til_pressure_from_temperature(rho_max, T, mat);
const float f0 = P_des - P_min;
const float f2 = P_des - P_mid;
if ((f0 * f2) > 0) {
rho_min = rho_mid;
} else {
rho_max = rho_mid;
}
rho_mid = (rho_min + rho_max) / 2.f;
counter += 1;
}
} else {
error("Error in Til_density_from_pressure_and_temperature");
return 0.f;
}
return rho_mid;
}
// gas_density_from_pressure_and_internal_energy
INLINE static float Til_density_from_pressure_and_internal_energy(
const float P, const float u, const float rho_ref, const float rho_sph,
const struct Til_params *mat) {
if (P <= mat->P_min || u == 0) {
return rho_sph;
}
// We start search on the same curve as rho_ref, since this is most likely
// curve to find rho on
float eta_ref = rho_ref / mat->rho_0;
/*
There are 5 possible curves:
1: cold_min
2: cold
3: hybrid_min
4: hybrid
5: hot
These curves cover different eta ranges within three different regions of u:
u REGION 1 (u < u_iv):
eta < eta_min: cold_min
eta_min < eta: cold
u REGION 2 (u_iv < u < u_cv):
eta < eta_min: hybrid_min
eta_min < eta < 1: hybrid
1 < eta: cold
u REGION 3 (u_cv < u):
eta < 1: hot
1 < eta: cold
NOTE: for a lot of EoS, eta_min = 0, so search this region last if given the
option to save time for most EoS
*/
enum Til_region {
Til_region_none,
Til_region_cold_min,
Til_region_cold,
Til_region_hybrid_min,
Til_region_hybrid,
Til_region_hot
};
// Based on our u region, what possible curves can rho be on? Ordered based on
// order we search for roots. Numbers correspond to curves in order given
// above. 0 is a dummy which breaks the loop.
enum Til_region possible_curves[3];
// u REGION 1
if (u <= mat->u_iv) {
if (eta_ref <= mat->eta_min) {
possible_curves[0] = Til_region_cold_min;
possible_curves[1] = Til_region_cold;
possible_curves[2] = Til_region_none;
} else {
possible_curves[0] = Til_region_cold;
possible_curves[1] = Til_region_cold_min;
possible_curves[2] = Til_region_none;
}
// u REGION 2
} else if (u <= mat->u_cv) {
if (eta_ref <= mat->eta_min) {
possible_curves[0] = Til_region_hybrid_min;
possible_curves[1] = Til_region_hybrid;
possible_curves[2] = Til_region_cold;
} else if (eta_ref <= 1) {
possible_curves[0] = Til_region_hybrid;
possible_curves[1] = Til_region_cold;
possible_curves[2] = Til_region_hybrid_min;
} else {
possible_curves[0] = Til_region_cold;
possible_curves[1] = Til_region_hybrid;
possible_curves[2] = Til_region_hybrid_min;
}
// u REGION 3
} else {
if (eta_ref <= 1) {
possible_curves[0] = Til_region_hot;
possible_curves[1] = Til_region_cold;
possible_curves[2] = Til_region_none;
} else {
possible_curves[0] = Til_region_cold;
possible_curves[1] = Til_region_hot;
possible_curves[2] = Til_region_none;
}
}
// Newton-Raphson
const int max_iter = 10;
const float tol = 1e-5;
// loops over possible curves
for (int i = 0; i < 3; i++) {
enum Til_region curve = possible_curves[i];
// if there are only two possible curves, break when we get to three and
// haven't found a root
if (curve == Til_region_none) {
break;
}
// Start iteration at reference value
float rho_iter = rho_ref;
// Constrain our initial guess to be on the curve we're currently looking at
// in the first loop, this is already satisfied.
if (i > 0) {
// u REGION 1
if (u <= mat->u_iv) {
if (curve == Til_region_cold_min) {
if (rho_iter > mat->eta_min * mat->rho_0) {
rho_iter = mat->eta_min * mat->rho_0;
}
} else if (curve == Til_region_cold) {
if (rho_iter < mat->eta_min * mat->rho_0) {
rho_iter = mat->eta_min * mat->rho_0;
}
} else {
error("Error in Til_density_from_pressure_and_internal_energy");
}
// u REGION 2
} else if (u <= mat->u_cv) {
if (curve == Til_region_hybrid_min) {
if (rho_iter > mat->eta_min * mat->rho_0) {
rho_iter = mat->eta_min * mat->rho_0;
}
} else if (curve == Til_region_hybrid) {
if (rho_iter < mat->eta_min * mat->rho_0) {
rho_iter = mat->eta_min * mat->rho_0;
}
if (rho_iter > mat->rho_0) {
rho_iter = mat->rho_0;
}
} else if (curve == Til_region_cold) {
if (rho_iter < mat->rho_0) {
rho_iter = mat->rho_0;
}
} else {
error("Error in Til_density_from_pressure_and_internal_energy");
}
// u REGION 3
} else {
if (curve == Til_region_hot) {
if (rho_iter > mat->rho_0) {
rho_iter = mat->rho_0;
}
} else if (curve == Til_region_cold) {
if (rho_iter < mat->rho_0) {
rho_iter = mat->rho_0;
}
} else {
error("Error in Til_density_from_pressure_and_internal_energy");
}
}
}
// Set this to an arbitrary number so we definitely dont't think we converge
// straigt away
float last_rho_iter = -1e5;
// Now iterate
for (int j = 0; j < max_iter; j++) {
const float eta_iter = rho_iter / mat->rho_0;
const float eta_iter_sq = eta_iter * eta_iter;
const float mu_iter = eta_iter - 1.0f;
const float nu_iter = 1.0f / eta_iter - 1.0f;
const float w_iter = u / (mat->u_0 * eta_iter_sq) + 1.0f;
const float w_iter_inv = 1.0f / w_iter;
const float exp1 = expf(-mat->beta * nu_iter);
const float exp2 = expf(-mat->alpha * nu_iter * nu_iter);
// Derivatives
const float dw_inv_drho_iter =
(2.f * mat->u_0 * u * eta_iter / mat->rho_0) /
((u + mat->u_0 * eta_iter_sq) * (u + mat->u_0 * eta_iter_sq));
const float dmu_drho_iter = 1.f / mat->rho_0;
const float dmu_sq_drho_iter =
2.f * rho_iter / (mat->rho_0 * mat->rho_0) - 2.f / mat->rho_0;
const float dexp1_drho_iter =
mat->beta * mat->rho_0 * exp1 / (rho_iter * rho_iter);
const float dexp2_drho_iter = 2.f * mat->alpha * mat->rho_0 *
(mat->rho_0 - rho_iter) * exp2 /
(rho_iter * rho_iter * rho_iter);
// Use P_fraction to determine whether we've converged on a root
float P_fraction;
// Newton-Raphson
float P_c_iter, dP_c_drho_iter, P_h_iter, dP_h_drho_iter;
// if "cold" or "hybrid"
if (curve == Til_region_cold || curve == Til_region_hybrid) {
P_c_iter = (mat->a + mat->b * w_iter_inv) * rho_iter * u +
mat->A * mu_iter + mat->B * mu_iter * mu_iter - P;
dP_c_drho_iter = (mat->a + mat->b * w_iter_inv) * u +
mat->b * u * rho_iter * dw_inv_drho_iter +
mat->A * dmu_drho_iter + mat->B * dmu_sq_drho_iter;
P_fraction = P_c_iter / P;
// if curve is cold then we've got everything we need
if (curve == Til_region_cold) {
rho_iter -= P_c_iter / dP_c_drho_iter;
// Don't use these:
P_h_iter = 0.f;
dP_h_drho_iter = 0.f;
}
// if "cold_min" or "hybrid_min"
// Can only have one version of the cold curve, therefore either use the
// min version or the normal version hence "else if"
} else if (curve == Til_region_cold_min ||
curve == Til_region_hybrid_min) {
P_c_iter = ((mat->a + mat->b * w_iter_inv) * rho_iter * u +
mat->A * mu_iter + mat->B * mu_iter * mu_iter) *
(eta_iter - mat->eta_zero) /
(mat->eta_min - mat->eta_zero) -
P;
dP_c_drho_iter =
((mat->a + mat->b * w_iter_inv) * u +
mat->b * u * rho_iter * dw_inv_drho_iter + mat->A * dmu_drho_iter +
mat->B * dmu_sq_drho_iter) *
(eta_iter - mat->eta_zero) / (mat->eta_min - mat->eta_zero) +
((mat->a + mat->b * w_iter_inv) * rho_iter * u + mat->A * mu_iter +
mat->B * mu_iter * mu_iter) *
(1 / (mat->rho_0 * (mat->eta_min - mat->eta_zero)));
P_fraction = P_c_iter / P;
// if curve is cold_min then we've got everything we need
if (curve == Til_region_cold_min) {
rho_iter -= P_c_iter / dP_c_drho_iter;
// Don't use these:
P_c_iter = 0.f;
dP_c_drho_iter = 0.f;
}
}
// if "hybrid_min" or "hybrid" or "hot"
if (curve == Til_region_hybrid_min || curve == Til_region_hybrid ||
curve == Til_region_hot) {
P_h_iter =
mat->a * rho_iter * u +
(mat->b * rho_iter * u * w_iter_inv + mat->A * mu_iter * exp1) *
exp2 -
P;
dP_h_drho_iter =
mat->a * u +
(mat->b * u * w_iter_inv +
mat->b * u * rho_iter * dw_inv_drho_iter +
mat->A * mu_iter * dexp1_drho_iter +
mat->A * exp1 * dmu_drho_iter) *
exp2 +
(mat->b * rho_iter * u * w_iter_inv + mat->A * mu_iter * exp1) *
dexp2_drho_iter;
P_fraction = P_h_iter / P;
// if curve is hot then we've got everything we need
if (curve == Til_region_hot) {
rho_iter -= P_h_iter / dP_h_drho_iter;
}
}
// If we are on a hybrid or hybrid_min curve, we combie hot and cold
// curves
if (curve == Til_region_hybrid_min || curve == Til_region_hybrid) {
const float P_hybrid_iter =
((u - mat->u_iv) * P_h_iter + (mat->u_cv - u) * P_c_iter) /
(mat->u_cv - mat->u_iv);
const float dP_hybrid_drho_iter = ((u - mat->u_iv) * dP_h_drho_iter +
(mat->u_cv - u) * dP_c_drho_iter) /
(mat->u_cv - mat->u_iv);
rho_iter -= P_hybrid_iter / dP_hybrid_drho_iter;
P_fraction = P_hybrid_iter / P;
}
// Now we have to constrain the new rho_iter to the curve we're on
// u REGION 1
if (u <= mat->u_iv) {
if (curve == Til_region_cold_min) {
if (rho_iter > mat->eta_min * mat->rho_0) {
rho_iter = mat->eta_min * mat->rho_0;
}
} else if (curve == Til_region_cold) {
if (rho_iter < mat->eta_min * mat->rho_0) {
rho_iter = mat->eta_min * mat->rho_0;
}
} else {
error("Error in Til_density_from_pressure_and_internal_energy");
}
// u REGION 2
} else if (u <= mat->u_cv) {
if (curve == Til_region_hybrid_min) {
if (rho_iter > mat->eta_min * mat->rho_0) {
rho_iter = mat->eta_min * mat->rho_0;
}
} else if (curve == Til_region_hybrid) {
if (rho_iter < mat->eta_min * mat->rho_0) {
rho_iter = mat->eta_min * mat->rho_0;
}
if (rho_iter > mat->rho_0) {
rho_iter = mat->rho_0;
}
} else if (curve == Til_region_cold) {
if (rho_iter < mat->rho_0) {
rho_iter = mat->rho_0;
}
} else {
error("Error in Til_density_from_pressure_and_internal_energy");
}
// u REGION 3
} else {
if (curve == Til_region_hot) {
if (rho_iter > mat->rho_0) {
rho_iter = mat->rho_0;
}
} else if (curve == Til_region_cold) {
if (rho_iter < mat->rho_0) {
rho_iter = mat->rho_0;
}
} else {
error("Error in Til_density_from_pressure_and_internal_energy");
}
}
// Either we've converged ...
if (fabsf(P_fraction) < tol) {
return rho_iter;
// ... or we're stuck at the boundary ...
} else if (rho_iter == last_rho_iter) {
break;
}
// ... or we loop again
last_rho_iter = rho_iter;
}
}
return rho_sph;
}
#endif /* SWIFT_TILLOTSON_EQUATION_OF_STATE_H */