Skip to content

Commit d2b200d

Browse files
authored
Initialize anelastic with isentropic hydrostatic equilibrium (Exawind#1484)
1 parent c6a54a7 commit d2b200d

File tree

9 files changed

+233
-33
lines changed

9 files changed

+233
-33
lines changed

amr-wind/CMakeLists.txt

+1
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ add_subdirectory(overset)
3333
add_subdirectory(immersed_boundary)
3434
add_subdirectory(mesh_mapping_models)
3535
add_subdirectory(ocean_waves)
36+
add_subdirectory(eos_models)
3637

3738
include(AMReXBuildInfo)
3839
generate_buildinfo(${amr_wind_lib_name} ${CMAKE_SOURCE_DIR})

amr-wind/eos_models/CMakeLists.txt

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+

amr-wind/eos_models/EOSModel.H

+87
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
#ifndef EOSMODEL_H
2+
#define EOSMODEL_H
3+
4+
#include "AMReX_REAL.H"
5+
#include "amr-wind/utilities/constants.H"
6+
7+
namespace amr_wind::eos {
8+
9+
/**
10+
* \defgroup eos Equation of state models
11+
*
12+
* AMR-Wind representation of equation of state models. These
13+
* expression are adapted from those found in ERF
14+
* https://github.com/erf-model/ERF.
15+
*
16+
*/
17+
18+
/** Gamma law equation of state
19+
* \ingroup eos
20+
*/
21+
struct GammaLaw
22+
{
23+
using eos_type = GammaLaw;
24+
static std::string identifier() { return "GammaLaw"; }
25+
26+
/**
27+
* Return pressure given density and potential temperature
28+
*
29+
* \param [in] rho density
30+
* \param [in] theta potential temperature
31+
* \param [in] qv water vapor
32+
* \return pressure
33+
*/
34+
AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE amrex::Real p_rth(
35+
const amrex::Real rho,
36+
const amrex::Real theta,
37+
const amrex::Real qv = 0.0) const
38+
{
39+
const auto rhotheta = rho * theta;
40+
return m_p0 * std::pow(
41+
m_air_gas_constant * rhotheta *
42+
(1.0 + (m_water_vapor_gas_constant /
43+
m_air_gas_constant) *
44+
qv) *
45+
m_ip0,
46+
m_gamma);
47+
}
48+
49+
/**
50+
* Return dP/drho at constant theta
51+
*
52+
* \param [in] rho density
53+
* \param [in] theta potential temperature
54+
* \param [in] qv water vapor
55+
* \return pressure
56+
*/
57+
AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE amrex::Real dp_constanttheta(
58+
const amrex::Real rho,
59+
const amrex::Real theta,
60+
const amrex::Real qv = 0.0) const
61+
{
62+
return m_gamma * m_p0 *
63+
std::pow(
64+
(m_air_gas_constant * theta *
65+
(1.0 +
66+
m_water_vapor_gas_constant / m_air_gas_constant * qv) *
67+
m_ip0),
68+
m_gamma) *
69+
std::pow(rho, m_gamma - 1.0);
70+
}
71+
72+
template <class... Args>
73+
AMREX_GPU_HOST_DEVICE explicit GammaLaw(const amrex::Real p0 = 1e-5)
74+
: m_p0{p0}, m_ip0{1.0 / p0}
75+
{}
76+
77+
const amrex::Real m_gamma{constants::HEAT_CAPACITY_RATIO};
78+
const amrex::Real m_p0{1e5};
79+
const amrex::Real m_ip0{1e-5};
80+
const amrex::Real m_air_gas_constant{
81+
constants::UNIVERSAL_GAS_CONSTANT / constants::MOLAR_MASS_AIR};
82+
const amrex::Real m_water_vapor_gas_constant{
83+
constants::UNIVERSAL_GAS_CONSTANT / constants::MOLAR_MASS_WATER_VAPOR};
84+
};
85+
} // namespace amr_wind::eos
86+
87+
#endif /* EOSMODEL_H */

amr-wind/utilities/constants.H

+9
Original file line numberDiff line numberDiff line change
@@ -46,5 +46,14 @@ static constexpr amrex::Real AVOGADRO_CONSTANT = 6.02214076 * 1e23;
4646
static constexpr amrex::Real UNIVERSAL_GAS_CONSTANT =
4747
AVOGADRO_CONSTANT * BOLTZMANN_CONSTANT;
4848

49+
//! Heat capacity ratio
50+
static constexpr amrex::Real HEAT_CAPACITY_RATIO = 1.4;
51+
52+
//! Molar mass of air (kg/mol)
53+
static constexpr amrex::Real MOLAR_MASS_AIR = 0.02896492;
54+
55+
//! Molar mass of water vapor (kg/mol)
56+
static constexpr amrex::Real MOLAR_MASS_WATER_VAPOR = 0.01801528;
57+
4958
} // namespace amr_wind::constants
5059
#endif

amr-wind/wind_energy/ABLAnelastic.H

+8-2
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
#ifndef ABLANELASTIC_H
22
#define ABLANELASTIC_H
33
#include "amr-wind/CFDSim.H"
4+
#include "amr-wind/transport_models/TransportModel.H"
45
#include "amr-wind/utilities/constants.H"
56
#include "amr-wind/utilities/MultiLevelVector.H"
7+
#include "amr-wind/eos_models/EOSModel.H"
68
#include "AMReX_ParmParse.H"
79

810
namespace amr_wind {
@@ -26,20 +28,24 @@ public:
2628
bool is_anelastic() const { return m_is_anelastic; }
2729

2830
private:
31+
//! Function to calculate the hydrostatic density and pressure
32+
void initialize_isentropic_hse();
33+
2934
const CFDSim& m_sim;
3035

3136
bool m_is_anelastic{false};
3237

3338
amrex::Vector<amrex::Real> m_gravity{0.0, 0.0, -9.81};
3439

35-
amrex::Real m_rho0_const{1.0};
40+
amrex::Real m_reference_density_constant{1.0};
3641

37-
amrex::Real m_bottom_reference_pressure{1.01325e5};
42+
amrex::Real m_bottom_reference_pressure{1.0e5};
3843

3944
const int m_axis{2};
4045

4146
MultiLevelVector m_density{FieldLoc::CELL};
4247
MultiLevelVector m_pressure{FieldLoc::NODE};
48+
MultiLevelVector m_theta{FieldLoc::CELL};
4349
};
4450
} // namespace amr_wind
4551
#endif

amr-wind/wind_energy/ABLAnelastic.cpp

+94-31
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ ABLAnelastic::ABLAnelastic(CFDSim& sim) : m_sim(sim)
1414
{
1515
amrex::ParmParse pp("incflo");
1616
pp.queryarr("gravity", m_gravity);
17-
pp.query("density", m_rho0_const);
17+
pp.query("density", m_reference_density_constant);
1818
pp.query("godunov_type", godunov_type);
1919
pp.query("icns_conserv", conserv);
2020
}
@@ -35,8 +35,8 @@ ABLAnelastic::ABLAnelastic(CFDSim& sim) : m_sim(sim)
3535
"reference_density", 1, density.num_grow()[0], 1);
3636
ref_density.set_default_fillpatch_bc(m_sim.time());
3737
m_sim.repo().declare_nd_field("reference_pressure", 1, 0, 1);
38-
auto& ref_theta = m_sim.repo().declare_field(
39-
"reference_temperature", 1, density.num_grow()[0], 1);
38+
auto& ref_theta =
39+
m_sim.repo().declare_field("reference_temperature", 1, 1, 1);
4040
ref_theta.set_default_fillpatch_bc(m_sim.time());
4141
}
4242
}
@@ -62,45 +62,108 @@ void ABLAnelastic::initialize_data()
6262

6363
m_density.resize(m_axis, m_sim.mesh().Geom());
6464
m_pressure.resize(m_axis, m_sim.mesh().Geom());
65+
m_theta.resize(m_axis, m_sim.mesh().Geom());
6566

6667
AMREX_ALWAYS_ASSERT(m_sim.repo().num_active_levels() == m_density.size());
6768
AMREX_ALWAYS_ASSERT(m_sim.repo().num_active_levels() == m_pressure.size());
69+
AMREX_ALWAYS_ASSERT(m_sim.repo().num_active_levels() == m_theta.size());
70+
71+
initialize_isentropic_hse();
72+
73+
auto& density0_field = m_sim.repo().get_field("reference_density");
74+
auto& p0 = m_sim.repo().get_field("reference_pressure");
75+
auto& temp0 = m_sim.repo().get_field("reference_temperature");
76+
m_density.copy_to_field(density0_field);
77+
m_pressure.copy_to_field(p0);
78+
m_theta.copy_to_field(temp0);
79+
density0_field.fillpatch(m_sim.time().current_time());
80+
temp0.fillpatch(m_sim.time().current_time());
81+
}
82+
83+
void ABLAnelastic::initialize_isentropic_hse()
84+
{
85+
const int max_iterations = 10;
86+
const auto ref_theta = m_sim.transport_model().reference_temperature();
87+
const auto eos = amr_wind::eos::GammaLaw(m_bottom_reference_pressure);
6888

6989
for (int lev = 0; lev < m_density.size(); lev++) {
7090
auto& dens = m_density.host_data(lev);
7191
auto& pres = m_pressure.host_data(lev);
72-
const auto& dx = m_sim.mesh().Geom(lev).CellSizeArray();
73-
dens.assign(dens.size(), m_rho0_const);
74-
pres[0] = m_bottom_reference_pressure;
75-
for (int k = 0; k < pres.size() - 1; k++) {
76-
pres[k + 1] = pres[k] - dens[k] * m_gravity[m_axis] * dx[m_axis];
92+
auto& theta = m_theta.host_data(lev);
93+
theta.assign(theta.size(), ref_theta);
94+
95+
const auto& dx = m_sim.mesh().Geom(lev).CellSizeArray()[m_axis];
96+
const amrex::Real half_dx = 0.5 * dx;
97+
98+
// Initial guess
99+
dens[0] = m_reference_density_constant;
100+
pres[0] =
101+
m_bottom_reference_pressure + half_dx * dens[0] * m_gravity[m_axis];
102+
103+
// We do a Newton iteration to satisfy the EOS & HSE (with constant
104+
// theta) at the surface
105+
bool converged_hse = false;
106+
amrex::Real p_hse = 0.0;
107+
amrex::Real p_eos = 0.0;
108+
109+
for (int iter = 0; (iter < max_iterations) && (!converged_hse);
110+
iter++) {
111+
p_hse = m_bottom_reference_pressure +
112+
half_dx * dens[0] * m_gravity[m_axis];
113+
p_eos = eos.p_rth(dens[0], ref_theta);
114+
115+
const amrex::Real p_diff = p_hse - p_eos;
116+
const amrex::Real dpdr = eos.dp_constanttheta(dens[0], ref_theta);
117+
const amrex::Real ddens =
118+
p_diff / (dpdr - half_dx * m_gravity[m_axis]);
119+
120+
dens[0] = dens[0] + ddens;
121+
pres[0] = eos.p_rth(dens[0], ref_theta);
122+
123+
if (std::abs(ddens) < constants::LOOSE_TOL) {
124+
converged_hse = true;
125+
break;
126+
}
127+
}
128+
if (!converged_hse) {
129+
amrex::Abort("Initializing with hydrostatic equilibrium failed");
77130
}
78-
}
79-
m_density.copy_host_to_device();
80-
m_pressure.copy_host_to_device();
81131

82-
auto& rho0 = m_sim.repo().get_field("reference_density");
83-
auto& p0 = m_sim.repo().get_field("reference_pressure");
84-
m_density.copy_to_field(rho0);
85-
m_pressure.copy_to_field(p0);
132+
// To get values at k > 0 we do a Newton iteration to satisfy the EOS
133+
// (with constant theta)
134+
for (int k = 1; k < dens.size(); k++) {
135+
converged_hse = false;
86136

87-
rho0.fillpatch(m_sim.time().current_time());
137+
dens[k] = dens[k - 1];
138+
for (int iter = 0; (iter < max_iterations) && (!converged_hse);
139+
iter++) {
140+
const amrex::Real dens_avg = 0.5 * (dens[k - 1] + dens[k]);
141+
p_hse = pres[k - 1] + dx * dens_avg * m_gravity[m_axis];
142+
p_eos = eos.p_rth(dens[k], ref_theta);
88143

89-
auto& temp0 = m_sim.repo().get_field("reference_temperature");
90-
const amrex::Real air_molar_mass = 0.02896492; // kg/mol
91-
const amrex::Real Rair = constants::UNIVERSAL_GAS_CONSTANT / air_molar_mass;
92-
for (int lev = 0; lev < m_sim.repo().num_active_levels(); ++lev) {
93-
const auto& rho0_arrs = rho0(lev).const_arrays();
94-
const auto& p0_arrs = p0(lev).const_arrays();
95-
const auto& temp0_arrs = temp0(lev).arrays();
96-
amrex::ParallelFor(
97-
temp0(lev), amrex::IntVect(0),
98-
[=] AMREX_GPU_DEVICE(int nbx, int i, int j, int k) noexcept {
99-
temp0_arrs[nbx](i, j, k) =
100-
p0_arrs[nbx](i, j, k) / (Rair * rho0_arrs[nbx](i, j, k));
101-
});
144+
const amrex::Real p_diff = p_hse - p_eos;
145+
const amrex::Real dpdr =
146+
eos.dp_constanttheta(dens[k], ref_theta);
147+
const amrex::Real ddens =
148+
p_diff / (dpdr - dx * m_gravity[m_axis]);
149+
150+
dens[k] = dens[k] + ddens;
151+
pres[k] = eos.p_rth(dens[k], ref_theta);
152+
153+
if (std::abs(ddens) < constants::LOOSE_TOL * dens[k - 1]) {
154+
converged_hse = true;
155+
break;
156+
}
157+
}
158+
if (!converged_hse) {
159+
amrex::Abort(
160+
"Initializing with hydrostatic equilibrium failed");
161+
}
162+
}
102163
}
103-
amrex::Gpu::synchronize();
104-
temp0.fillpatch(m_sim.time().current_time());
164+
m_density.copy_host_to_device();
165+
m_pressure.copy_host_to_device();
166+
m_theta.copy_host_to_device();
105167
}
168+
106169
} // namespace amr_wind

unit_tests/CMakeLists.txt

+1
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ add_subdirectory(multiphase)
1616
add_subdirectory(ocean_waves)
1717
add_subdirectory(projection)
1818
add_subdirectory(boundary_conditions)
19+
add_subdirectory(eos_models)
1920

2021
if(AMR_WIND_ENABLE_MASA)
2122
add_subdirectory(mms)

unit_tests/eos_models/CMakeLists.txt

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
target_sources(${amr_wind_unit_test_exe_name}
2+
PRIVATE
3+
4+
test_eos.cpp
5+
)

unit_tests/eos_models/test_eos.cpp

+27
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
#include <AMReX_Gpu.H>
2+
#include "aw_test_utils/AmrexTest.H"
3+
#include "amr-wind/eos_models/EOSModel.H"
4+
5+
namespace amr_wind_tests {
6+
7+
void test_eos_impl()
8+
{
9+
const auto eos = amr_wind::eos::GammaLaw(1.01325e5);
10+
amrex::Gpu::DeviceScalar<amrex::Real> val(0.0);
11+
auto* d_val = val.dataPtr();
12+
13+
amrex::ParallelFor(1, [=] AMREX_GPU_DEVICE(int /*unused*/) {
14+
d_val[0] = eos.p_rth(1.225, 300.0, 0.5);
15+
});
16+
EXPECT_NEAR(
17+
val.dataValue(), 244859.65251771925, amr_wind::constants::LOOSE_TOL);
18+
19+
amrex::ParallelFor(1, [=] AMREX_GPU_DEVICE(int /*unused*/) {
20+
d_val[0] = eos.dp_constanttheta(1.225, 300.0, 0.5);
21+
});
22+
EXPECT_NEAR(
23+
val.dataValue(), 279839.60287739342, amr_wind::constants::LOOSE_TOL);
24+
}
25+
26+
TEST(EOSModelTest, test_eos) { test_eos_impl(); }
27+
} // namespace amr_wind_tests

0 commit comments

Comments
 (0)