Cholla 3.0.1-dev
Cholla - Massively parallel hydro on GPUs
Loading...
Searching...
No Matches
dual_energy.h
Go to the documentation of this file.
1
20#ifndef DUAL_ENERGY_H
21#define DUAL_ENERGY_H
22
23#include "../global/global.h"
24#include "../grid/grid_enum.h"
25#include "../hydro/hydro_cuda.h"
26#include "../utils/basic_structs.h"
27
28namespace dual_energy
29{
30
52template <int NDim>
53__global__ void Select_Internal_Energy(Real *dev_conserved, hydro_utilities::VectorXYZ<int> grid_shape, int n_ghost,
54 int n_fields)
55{
56 static_assert((NDim == 1) || (NDim == 2) || (NDim == 3), "NDim must be 1, 2, or 3");
57
58#ifndef DE
59 printf("WARNING: this function isn't usable since Cholla wasn't compiled with Dual Energy Formalism!\n");
60
61#else
62 int id, n_cells;
63 int neighbor_ids[NDim * 2];
64 bool is_real_cell;
65
66 // Here we branch and determine some basic details based on the number of dimensions
67 // -> there are much more clever ways to do all of this more concisely
68 // -> for the uninitiated, the conditions of `constexpr if` statements (within templates) are evaluated
69 // at compile time. In other words, there is no branching
70 if constexpr (NDim == 1) {
71 n_cells = grid_shape.x();
72 id = threadIdx.x + blockIdx.x * blockDim.x;
73
74 int nx = grid_shape.x();
75
76 int xid = id;
77
78 int imo = max(xid - 1, n_ghost);
79 neighbor_ids[0] = imo;
80 int ipo = min(xid + 1, nx - n_ghost - 1);
81 neighbor_ids[1] = ipo;
82
83 is_real_cell = (xid > n_ghost - 1 && xid < nx - n_ghost);
84
85 } else if constexpr (NDim == 2) {
86 n_cells = grid_shape.x() * grid_shape.y();
87
88 // this difference in logic is left over from the distant past. when we
89 // we needed to use 2D grids to run really large 2D problems, because of
90 // CUDA limits on the 1D grid dimensions. This is reflected the name
91 // `dim2dgrid` for the grid dimensions in e.g. the VL_2D_cuda.cu functions,
92 // versus 'dim1dgrid' for the 3D version, even though then we set up a a 1D
93 // grid. In principle as long as we continue to only use 1D grids,
94 // Evan thinks that replacing this with blockIdx.x would be fine, since
95 // blockIdx.y will always be 0 for a 1D grid,
96 int blockId = blockIdx.x + blockIdx.y * gridDim.x;
97 id = threadIdx.x + blockId * blockDim.x;
98
99 int nx = grid_shape.x();
100 int ny = grid_shape.y();
101
102 int yid = id / nx;
103 int xid = id - yid * nx;
104
105 int imo = max(xid - 1, n_ghost);
106 neighbor_ids[0] = imo + yid * nx;
107 int ipo = min(xid + 1, nx - n_ghost - 1);
108 neighbor_ids[1] = ipo + yid * nx;
109 int jmo = max(yid - 1, n_ghost);
110 neighbor_ids[2] = xid + jmo * nx;
111 int jpo = min(yid + 1, ny - n_ghost - 1);
112 neighbor_ids[3] = xid + jpo * nx;
113
114 is_real_cell = (xid > n_ghost - 1 && xid < nx - n_ghost && yid > n_ghost - 1 && yid < ny - n_ghost);
115
116 } else { // NDim == 3
117 n_cells = grid_shape.x() * grid_shape.y() * grid_shape.z();
118 id = threadIdx.x + blockIdx.x * blockDim.x;
119
120 int nx = grid_shape.x();
121 int ny = grid_shape.y();
122 int nz = grid_shape.z();
123
124 int zid = id / (nx * ny);
125 int yid = (id - zid * nx * ny) / nx;
126 int xid = id - zid * nx * ny - yid * nx;
127
128 int imo = max(xid - 1, n_ghost);
129 neighbor_ids[0] = imo + yid * nx + zid * nx * ny;
130 int ipo = min(xid + 1, nx - n_ghost - 1);
131 neighbor_ids[1] = ipo + yid * nx + zid * nx * ny;
132 int jmo = max(yid - 1, n_ghost);
133 neighbor_ids[2] = xid + jmo * nx + zid * nx * ny;
134 int jpo = min(yid + 1, ny - n_ghost - 1);
135 neighbor_ids[3] = xid + jpo * nx + zid * nx * ny;
136 int kmo = max(zid - 1, n_ghost);
137 neighbor_ids[4] = xid + yid * nx + kmo * nx * ny;
138 int kpo = min(zid + 1, nz - n_ghost - 1);
139 neighbor_ids[5] = xid + yid * nx + kpo * nx * ny;
140
141 is_real_cell = (xid > n_ghost - 1 && xid < nx - n_ghost && yid > n_ghost - 1 && yid < ny - n_ghost &&
142 zid > n_ghost - 1 && zid < nz - n_ghost);
143 }
144
145 const Real eta_1 = DE_ETA_1;
146 const Real eta_2 = DE_ETA_2;
147
148 // threads corresponding to real cells do the calculation
149 if (is_real_cell) {
150 // every thread collects the conserved variables it needs from global memory
151 Real d = dev_conserved[id];
152 Real d_inv = 1.0 / d;
153 Real vx = dev_conserved[1 * n_cells + id] * d_inv;
154 Real vy = dev_conserved[2 * n_cells + id] * d_inv;
155 Real vz = dev_conserved[3 * n_cells + id] * d_inv;
156 Real E = dev_conserved[4 * n_cells + id];
157 Real U_advected = dev_conserved[(n_fields - 1) * n_cells + id];
158 Real U_total = E - 0.5 * d * (vx * vx + vy * vy + vz * vz);
159
160 // We will deal with this crashed cell later in a different kernel... (at the time of writing, a 1D
161 // simulation always uses floors for this purpose)
162 if (Cell_Is_Crashed(d, E)) return;
163
164 // find the max nearby total energy (from the local cell and any uncrashed neighbors)
165 // -> we take the stance that "crashed" neighbors are unreliable, even if total energy looks ok
166 // -> we're making use of a "range-based for loop"
167 Real Emax = E;
168 for (int neighbor_id : neighbor_ids) {
169 Real neighbor_d = dev_conserved[grid_enum::density * n_cells + neighbor_id];
170 Real neighbor_E = dev_conserved[grid_enum::Energy * n_cells + neighbor_id];
171 Emax = fmax(Emax, Cell_Is_Crashed(neighbor_d, neighbor_E) ? Emax : neighbor_E);
172 }
173
174 // Ordinarily, we only use the "advected" internal energy if both:
175 // - the thermal energy divided by total energy is a small fraction (smaller than eta_1)
176 // - AND we aren't masking shock heating (details controlled by Emax & eta_2)
177 // We also explicitly use the "advected" internal energy if the total energy is positive but doesn't
178 // exceed kinetic energy (i.e. U_total <= 0). This scenario comes up in simulations with particle-based
179 // feedback.
180 bool prefer_U_total = (U_total > E * eta_1) or (U_total > Emax * eta_2);
181 Real U = (prefer_U_total and (U_total > 0)) ? U_total : U_advected;
182
183 // Optional: Avoid Negative Internal Energies
184 U = fmax(U, (Real)TINY_NUMBER);
185
186 // Write Selected internal energy to the GasEnergy array ONLY
187 // to avoid mixing updated and non-updated values of E
188 // since the Dual Energy condition depends on the neighbour cells
189 dev_conserved[(n_fields - 1) * n_cells + id] = U;
190 }
191#endif /* DE */
192}
193
211template <int NDim>
212__global__ void Sync_Energies(Real *dev_conserved, hydro_utilities::VectorXYZ<int> grid_shape, int n_ghost,
213 int n_fields)
214{
215 static_assert((NDim == 1) || (NDim == 2) || (NDim == 3), "NDim must be 1, 2, or 3");
216
217#ifndef DE
218 printf("WARNING: this function isn't usable since Cholla wasn't compiled with Dual Energy Formalism!\n");
219
220#else
221 int id, n_cells;
222 bool is_real_cell;
223
224 // Here we branch and determine some basic details based on the number of dimensions
225 // -> for the uninitiated, the conditions of `constexpr if` statements (within templates) are evaluated
226 // at compile time. In other words, there is no branching
227 if constexpr (NDim == 1) {
228 n_cells = grid_shape.x();
229
230 // get a global thread ID
231 id = threadIdx.x + blockIdx.x * blockDim.x;
232 int xid = id;
233 is_real_cell = (xid > n_ghost - 1 && xid < grid_shape.x() - n_ghost);
234
235 } else if constexpr (NDim == 2) {
236 n_cells = grid_shape.x() * grid_shape.y();
237
238 int nx = grid_shape.x();
239 int ny = grid_shape.y();
240
241 // get a global thread ID
242 int blockId = blockIdx.x + blockIdx.y * gridDim.x;
243 id = threadIdx.x + blockId * blockDim.x;
244 int yid = id / nx;
245 int xid = id - yid * nx;
246 is_real_cell = (xid > n_ghost - 1 && xid < nx - n_ghost && yid > n_ghost - 1 && yid < ny - n_ghost);
247
248 } else { // NDim == 3
249 n_cells = grid_shape.x() * grid_shape.y() * grid_shape.z();
250
251 int nx = grid_shape.x();
252 int ny = grid_shape.y();
253 int nz = grid_shape.z();
254
255 // get a global thread ID
256 id = threadIdx.x + blockIdx.x * blockDim.x;
257 int zid = id / (nx * ny);
258 int yid = (id - zid * nx * ny) / nx;
259 int xid = id - zid * nx * ny - yid * nx;
260 is_real_cell = (xid > n_ghost - 1 && xid < nx - n_ghost && yid > n_ghost - 1 && yid < ny - n_ghost &&
261 zid > n_ghost - 1 && zid < nz - n_ghost);
262 }
263
264 // threads corresponding to real cells do the calculation
265 if (is_real_cell) {
266 // every thread collects the conserved variables it needs from global memory
267 Real d = dev_conserved[grid_enum::density * n_cells + id];
268 Real d_inv = 1.0 / d;
269 Real vx = dev_conserved[grid_enum::momentum_x * n_cells + id] * d_inv;
270 Real vy = dev_conserved[grid_enum::momentum_y * n_cells + id] * d_inv;
271 Real vz = dev_conserved[grid_enum::momentum_z * n_cells + id] * d_inv;
272 Real U = dev_conserved[(n_fields - 1) * n_cells + id];
273
274 // Use the previously selected Internal Energy to update the total energy
275 dev_conserved[grid_enum::Energy * n_cells + id] = 0.5 * d * (vx * vx + vy * vy + vz * vz) + U;
276 }
277
278#endif /* DE */
279}
280
281} // namespace dual_energy
282
283#endif /* DUAL_ENERGY_H */
__global__ void Sync_Energies(Real *dev_conserved, hydro_utilities::VectorXYZ< int > grid_shape, int n_ghost, int n_fields)
Definition dual_energy.h:212
__global__ void Select_Internal_Energy(Real *dev_conserved, hydro_utilities::VectorXYZ< int > grid_shape, int n_ghost, int n_fields)
Definition dual_energy.h:53
__device__ bool Cell_Is_Crashed(Real density, Real Etot_density)
Definition hydro_cuda.h:106
A data only struct that acts as a simple 3 element vector.
Definition basic_structs.h:32
__device__ __host__ T & x() noexcept
Directly access the x, y, and z elements. Const version is needed if the object instance is declared ...
Definition basic_structs.h:68