Skip to content

Commit e061acb

Browse files
authored
Fixed data race condition in few modes IFT (no practical implication) (#172)
* Remove incorrect enforced symmetry with race condition and add explicit checks * Remove complex conjugate modes from sample input * Make REQUIRE a WARN for backwards compatibility * Remove Hermitian modes from gen fmturb modes * Add Changelog * Update reference data given new sample vectors * use ref data created on CI machine * Remove phase factor from hermitian symmetric modes and exclude nyquist ones * Python formatting * Adjust tolerance
1 parent bffa62a commit e061acb

7 files changed

Lines changed: 110 additions & 76 deletions

File tree

CHANGELOG.md

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,12 @@
44

55
### General notes
66

7-
### Fixed (not changing behavior/API/variables/...)
8-
97
### Added (new features/APIs/variables/...)
108

119
### Changed (changing behavior/API/variables/...)
1210

1311
### Fixed (not changing behavior/API/variables/...)
12+
- [[PR 172]](https://github.com/parthenon-hpc-lab/athenapk/pull/172) Fixed data race condition in few modes IFT (no practical implication)
1413

1514
### Infrastructure
1615
- [[PR 168]](https://github.com/parthenon-hpc-lab/athenapk/pull/168) Document agentic coding guidelines (and add PR template)
@@ -44,14 +43,14 @@ To enable, set `do_coalesced_comms=true` in the `<parthenon/mesh>` block of the
4443
- Input parameters can now be [automatically documented](https://github.com/parthenon-hpc-lab/parthenon/pull/1283)
4544
by adding an optional string as last argument to any `ParameterInput` `Get` or `GetOrAdd` call.
4645

47-
### Fixed (not changing behavior/API/variables/...)
48-
- [[PR 163]](https://github.com/parthenon-hpc-lab/athenapk/pull/163) Add normalization by volume for relative B field divergence in history file
49-
5046
### Added (new features/APIs/variables/...)
5147
- [[PR 162]](https://github.com/parthenon-hpc-lab/athenapk/pull/162) Add pgen for cloud shattering setup
5248
- [[PR 158]](https://github.com/parthenon-hpc-lab/athenapk/pull/158) Update particle id handling (now automated `uint64`). Extend particle history lookback in turbulence pgen and include in turbulence test
5349
- [[PR 157]](https://github.com/parthenon-hpc-lab/athenapk/pull/157) Support injection of blobs with density/temp contrast in turbulence simulations
5450

51+
### Changed (changing behavior/API/variables/...)
52+
- [[PR 163]](https://github.com/parthenon-hpc-lab/athenapk/pull/163) Add normalization by volume for relative B field divergence in history file
53+
5554
### Fixed (not changing behavior/API/variables/...)
5655
- [[PR 160]](https://github.com/parthenon-hpc-lab/athenapk/pull/160) Backport HLLD degeneracy check from Athena++
5756

inputs/generate_fmturb_modes.py

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,16 +26,26 @@
2626

2727
all_vec = []
2828

29-
for i in range(k_high):
30-
for j in range(-k_high, k_high):
31-
for k in range(-k_high, k_high):
29+
for i in range(k_high + 1):
30+
for j in range(-k_high, k_high + 1):
31+
for k in range(-k_high, k_high + 1):
3232

3333
k_mag = np.sqrt(i**2 + j**2 + k**2)
3434
if k_mag > k_high or k_mag < k_low:
3535
continue
3636
# this is the spectral shape of the implemented forcing function
3737
if (k_mag / k_peak) ** 2.0 * (2.0 - (k_mag / k_peak) ** 2.0) < 0:
3838
continue
39+
40+
# skip Hermitian partner
41+
if i == 0:
42+
skip = False
43+
for vec in all_vec:
44+
if j == -vec[1] and k == -vec[2]:
45+
skip = True
46+
if skip:
47+
continue
48+
3949
all_vec.append((i, j, k))
4050

4151
# select all wavevectors

inputs/turbulence.in

Lines changed: 7 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ corr_time = 1.0 # autocorrelation time of the OU forcing process
7272
rseed = 20190729 # random seed of the OU forcing process
7373
sol_weight = 1.0 # solenoidal weight of the acceleration field
7474
accel_rms = 0.5 # root mean square value of the acceleration field
75-
num_modes = 30 # number of wavemodes
75+
num_modes = 20 # number of wavemodes
7676

7777
<modes>
7878
k_1_0 = +2
@@ -93,9 +93,9 @@ k_5_2 = -1
9393
k_6_0 = +1
9494
k_6_1 = -1
9595
k_6_2 = -2
96-
k_7_0 = +0
97-
k_7_1 = +0
98-
k_7_2 = -2
96+
k_7_0 = +1
97+
k_7_1 = +2
98+
k_7_2 = -1
9999
k_8_0 = +1
100100
k_8_1 = +0
101101
k_8_2 = -1
@@ -117,9 +117,9 @@ k_13_2 = +1
117117
k_14_0 = +1
118118
k_14_1 = -1
119119
k_14_2 = +0
120-
k_15_0 = +0
121-
k_15_1 = -1
122-
k_15_2 = -1
120+
k_15_0 = +1
121+
k_15_1 = +1
122+
k_15_2 = +2
123123
k_16_0 = +1
124124
k_16_1 = +0
125125
k_16_2 = +1
@@ -135,34 +135,3 @@ k_19_2 = +1
135135
k_20_0 = +2
136136
k_20_1 = +1
137137
k_20_2 = -1
138-
k_21_0 = +0
139-
k_21_1 = -1
140-
k_21_2 = -2
141-
k_22_0 = +2
142-
k_22_1 = -1
143-
k_22_2 = +1
144-
k_23_0 = +0
145-
k_23_1 = +1
146-
k_23_2 = +1
147-
k_24_0 = +1
148-
k_24_1 = -2
149-
k_24_2 = +1
150-
k_25_0 = +1
151-
k_25_1 = -2
152-
k_25_2 = +0
153-
k_26_0 = +1
154-
k_26_1 = +2
155-
k_26_2 = +0
156-
k_27_0 = +1
157-
k_27_1 = -2
158-
k_27_2 = -1
159-
k_28_0 = +2
160-
k_28_1 = -1
161-
k_28_2 = -1
162-
k_29_0 = +1
163-
k_29_1 = +2
164-
k_29_2 = -1
165-
k_30_0 = +1
166-
k_30_1 = +1
167-
k_30_2 = +2
168-

src/pgen/turbulence.cpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
#include "globals.hpp"
2020
#include "interface/metadata.hpp"
2121
#include "kokkos_abstraction.hpp"
22+
#include "kokkos_types.hpp"
2223
#include "mesh/mesh.hpp"
2324
#include <iomanip>
2425
#include <ios>
@@ -359,7 +360,7 @@ void ProblemInitTracerData(ParameterInput *pin, parthenon::StateDescriptor *trac
359360
PARTHENON_THROW(
360361
"This line should not be reached as invalid n_lookback are caught above.");
361362
}
362-
auto dncycles_d =
363+
parthenon::ParArray1D<int> dncycles_d =
363364
Kokkos::create_mirror_view_and_copy(parthenon::DevMemSpace(), dncycles_h);
364365
tracer_pkg->AddParam("turbulence/n_lookback", n_lookback);
365366
tracer_pkg->AddParam("turbulence/dncycles_h", dncycles_h);

src/utils/few_modes_ft.cpp

Lines changed: 61 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99

1010
// C++ headers
1111
#include <random>
12+
#include <string>
1213

1314
// Parthenon headers
1415
#include "basic_types.hpp"
@@ -47,9 +48,50 @@ FewModesFT::FewModesFT(parthenon::ParameterInput *pin, parthenon::StateDescripto
4748
// lambda cannot live in the constructor of an object.
4849
auto k_vec_host = k_vec.GetHostMirrorAndCopy();
4950
for (int i = 0; i < num_modes; i++) {
50-
PARTHENON_REQUIRE(std::abs(k_vec_host(0, i)) <= gnx1 / 2, "k_vec x1 mode too large");
51-
PARTHENON_REQUIRE(std::abs(k_vec_host(1, i)) <= gnx2 / 2, "k_vec x2 mode too large");
52-
PARTHENON_REQUIRE(std::abs(k_vec_host(2, i)) <= gnx3 / 2, "k_vec x3 mode too large");
51+
PARTHENON_REQUIRE(
52+
k_vec_host(0, i) >= 0,
53+
"k_x mode must be positive as explicit Hermitian symmetry is assumed.");
54+
PARTHENON_REQUIRE(
55+
!(k_vec_host(0, i) == 0 && k_vec_host(1, i) == 0 && k_vec_host(2, i) == 0),
56+
"Forcing normalization is handled separately so do not include the 0 mode.");
57+
PARTHENON_REQUIRE(std::abs(k_vec_host(0, i)) < gnx1 / 2, "k_vec x1 mode too large");
58+
PARTHENON_REQUIRE(std::abs(k_vec_host(1, i)) < gnx2 / 2, "k_vec x2 mode too large");
59+
PARTHENON_REQUIRE(std::abs(k_vec_host(2, i)) < gnx3 / 2, "k_vec x3 mode too large");
60+
}
61+
62+
// Ensure that the provided k_vec do not include their conjugate and/or itself again as
63+
// otherwise that mode would be counted twice
64+
for (int i = 0; i < num_modes; i++) {
65+
for (int j = 0; j < num_modes; j++) {
66+
if (i == j) {
67+
continue;
68+
}
69+
70+
// The sample input file shipped for a couple of years
71+
// unforunately came with complex conjugate modes...
72+
// So in order to not crash old/existing sims upon restart we allow this and just
73+
// issue a warning
74+
if (k_vec_host(0, i) == 0 && k_vec_host(0, i) == k_vec_host(0, j) &&
75+
k_vec_host(1, i) == -k_vec_host(1, j) &&
76+
k_vec_host(2, i) == -k_vec_host(2, j)) {
77+
PARTHENON_WARN(
78+
"The given set of k_vec include complex conjugate partners for mode " +
79+
std::to_string(i) + " with components " + std::to_string(k_vec_host(0, i)) +
80+
" " + std::to_string(k_vec_host(1, i)) + " " +
81+
std::to_string(k_vec_host(2, i)) +
82+
"."
83+
"In theory, this results in these modes being counted double. In practice, "
84+
"this will have little to no effect as the normalization is done separate "
85+
"for the real space field rather than the spectral field. However, if this "
86+
"is a fresh simulation (and not a restarted one with given/fixed k_vec) it "
87+
"is recommended to update the set of k_vec in the input file.");
88+
}
89+
PARTHENON_REQUIRE_THROWS(!(k_vec_host(0, i) == k_vec_host(0, j) &&
90+
k_vec_host(1, i) == k_vec_host(1, j) &&
91+
k_vec_host(2, i) == k_vec_host(2, j)),
92+
"The given set of k_vec include identical modes. "
93+
"Please provide a set without dublicates.");
94+
}
5395
}
5496

5597
const auto nx1 = pin->GetInteger("parthenon/meshblock", "nx1");
@@ -161,12 +203,7 @@ void FewModesFT::SetPhases(MeshBlock *pmb, ParameterInput *pin) {
161203

162204
for (int m = 0; m < num_modes; m++) {
163205
w_kx = k_vec(0, m) * 2. * M_PI / static_cast<Real>(gnx1);
164-
// adjust phase factor to Complex->Real IFT: u_hat*(k) = u_hat(-k)
165-
if (k_vec(0, m) == 0.0) {
166-
phase = 0.5 * Kokkos::exp(I * w_kx * gi);
167-
} else {
168-
phase = Kokkos::exp(I * w_kx * gi);
169-
}
206+
phase = Kokkos::exp(I * w_kx * gi);
170207
phases_i(i, m, 0) = phase.real();
171208
phases_i(i, m, 1) = phase.imag();
172209
}
@@ -255,19 +292,6 @@ void FewModesFT::Generate(MeshData<Real> *md, const Real dt,
255292
Complex(tmp * norm * random_num(n, m, 0), tmp * norm * random_num(n, m, 1));
256293
});
257294

258-
// enforce symmetry of complex to real transform
259-
pmb->par_for(
260-
"forcing: enforce symmetry", 0, 2, 0, num_modes - 1,
261-
KOKKOS_LAMBDA(const int n, const int m) {
262-
if (k_vec(0, m) == 0.) {
263-
for (int m2 = 0; m2 < m; m2++) {
264-
if (k_vec(1, m) == -k_vec(1, m2) && k_vec(2, m) == -k_vec(2, m2))
265-
var_hat_new(n, m) =
266-
Complex(var_hat_new(n, m2).real(), -var_hat_new(n, m2).imag());
267-
}
268-
}
269-
});
270-
271295
const auto sol_weight = sol_weight_;
272296
if (sol_weight_ >= 0.0) {
273297
// project
@@ -372,6 +396,7 @@ ParArray2D<Real> MakeRandomModes(const int num_modes, const Real k_peak,
372396
constexpr int max_attempts = 1000000;
373397
Real kx1, kx2, kx3, k_mag, ampl;
374398
bool mode_exists = false;
399+
bool hermitian_exists = false;
375400
while (n_mode < num_modes && n_attempt < max_attempts) {
376401
n_attempt += 1;
377402

@@ -393,8 +418,20 @@ ParArray2D<Real> MakeRandomModes(const int num_modes, const Real k_peak,
393418
}
394419
}
395420

421+
// Check is Hermitian symmetric partner already exist
422+
hermitian_exists = false;
423+
if (kx1 == 0) {
424+
for (int n_mode_exsist = 0; n_mode_exsist < n_mode; n_mode_exsist++) {
425+
if (k_vec_h(0, n_mode_exsist) == kx1 && -k_vec_h(1, n_mode_exsist) == kx2 &&
426+
-k_vec_h(2, n_mode_exsist) == kx3) {
427+
hermitian_exists = true;
428+
}
429+
}
430+
}
431+
396432
// kx1 < 0.0 because we use a explicit symmetric Complex to Real transform
397-
if (ampl < 0 || k_mag < k_low || k_mag > k_high || mode_exists || kx1 < 0.0) {
433+
if (ampl < 0 || k_mag < k_low || k_mag > k_high || mode_exists || hermitian_exists ||
434+
kx1 < 0.0) {
398435
continue;
399436
}
400437
k_vec_h(0, n_mode) = kx1;
@@ -409,4 +446,4 @@ ParArray2D<Real> MakeRandomModes(const int num_modes, const Real k_peak,
409446

410447
return k_vec;
411448
}
412-
} // namespace utils::few_modes_ft
449+
} // namespace utils::few_modes_ft
0 Bytes
Binary file not shown.

tst/regression/test_suites/turbulence/turbulence.py

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -45,17 +45,37 @@ def Analyse(self, parameters):
4545
data = np.genfromtxt(data_filename)
4646

4747
# Check Ms
48-
if not (data[-1, -3] > 0.45 and data[-1, -3] < 0.50):
48+
if data[-1, -3] != 4.70153e-01:
4949
print(f"ERROR: Mismatch in Ms={data[-1, -3]}")
5050
success = False
5151

5252
# Check Ma
53-
if not (data[-1, -2] > 12.8 and data[-1, -2] < 13.6):
53+
if data[-1, -2] != 1.37253e01:
5454
print(f"ERROR: Mismatch in Ma={data[-1, -2]}")
5555
success = False
5656

5757
# Loading the data
5858
data = phdf.phdf(f"{parameters.output_path}/parthenon.restart.final.rhdf")
59+
60+
components = data.GetComponents(data.Info["ComponentNames"], flatten=False)
61+
density_sum = components["cons_density"].sum()
62+
try:
63+
np.testing.assert_array_max_ulp(density_sum, 64**3, maxulp=2)
64+
except AssertionError as ar:
65+
print(
66+
f"TEST FAIL: incorrect density sum\n"
67+
f"Got {density_sum} and expected {64**3}."
68+
)
69+
print(ar)
70+
success = False
71+
energy_sum = components["cons_total_energy_density"].sum()
72+
if energy_sum != 2621560462.960244:
73+
print(
74+
f"TEST FAIL: incorrect energy sum\n"
75+
f"Got {energy_sum} and expected {2621560462.960244}."
76+
)
77+
success = False
78+
5979
tracers = data.GetSwarm("tracers")
6080
ids = tracers.id
6181

@@ -76,13 +96,11 @@ def Analyse(self, parameters):
7696
# all_var_data = {}
7797
# for var in tracers.variables:
7898
# var_data = tracers.Get(var)
79-
# all_var_data[var] = var_data[order]
8099
# if len(var_data.shape) > 1:
81100
# out_data = var_data[np.arange(var_data.shape[0])[:, None], order]
82101
# else:
83102
# out_data = var_data[order]
84103
# all_var_data[var] = out_data
85-
86104
# with open("ref_data.pkl", "wb") as outfile:
87105
# pickle.dump(all_var_data, outfile)
88106

@@ -119,7 +137,7 @@ def Analyse(self, parameters):
119137
)
120138
else:
121139
np.testing.assert_allclose(
122-
var_data_sorted, ref_data[var], rtol=4e-8, strict=True
140+
var_data_sorted, ref_data[var], rtol=8.1e-7, strict=True
123141
)
124142

125143
except AssertionError as ar:

0 commit comments

Comments
 (0)