Run the consistency check.
162 """!
163 @brief Run the consistency check.
164 @param[in] argv Optional argument override for tests.
165 @return Process exit code.
166 """
167 numpy = require_numpy()
168 args =
list(sys.argv[1:]
if argv
is None else argv)
169 if len(args) != 2:
170 print(USAGE, file=sys.stderr)
171 return 2
172 run_dir, window = args
173
174
175
176 viz_dir = sole_recipe_dir(run_dir, os.path.join("output", "visualization"))
177 stats_dir = sole_recipe_dir(run_dir, os.path.join("output", "analysis", "statistics"))
178
179 try:
180 vts_path = newest_window_vts(viz_dir, window)
181 nodes, arrays = read_vts_point_arrays(vts_path)
182
183 periodic = read_checkpoint_periodicity(run_dir)
184 if periodic and any(periodic):
185 if check_periodic_wrap(arrays, nodes, periodic) != 0:
186 return 1
187
188 tke_name = next((n for n in arrays if n.endswith("_tke")), None)
189 if tke_name is None:
190 print(f"[SKIP] {os.path.basename(vts_path)} carries no turbulent kinetic energy field.")
191 return 0
192 field = arrays[tke_name].reshape(nodes[2], nodes[1], nodes[0])
193
194 csv_path = next(
195 (os.path.join(stats_dir, n) for n in sorted(os.listdir(stats_dir))
196 if n.endswith(f"_statistics_{window}.csv")), None
197 )
198 if csv_path is None:
199 raise ValueError(f"no convergence-history CSV for window '{window}' in {stats_dir}.")
200 rows =
list(csv.DictReader(open(csv_path,
"r", encoding=
"utf-8")))
201 if not rows or "mean_tke" not in rows[0]:
202 print(f"[SKIP] {os.path.basename(csv_path)} records no mean_tke column.")
203 return 0
204 reference = float(rows[-1]["mean_tke"])
205 if reference <= 0.0:
206 print(f"[SKIP] window '{window}' has no accumulated energy to compare.")
207 return 0
208
209
210
211 interior = field[1:-1, 1:-1, 1:-1].mean()
212 deviation = abs(interior / reference - 1.0)
213 tolerance = 0.05
214 print(f"[INFO] {window}: nodal interior mean {interior:.6e} vs CSV mean_tke "
215 f"{reference:.6e} ({100*deviation:+.2f}%)")
216 if deviation > tolerance:
217 print(
218 f"[FAIL] derived statistics VTK and convergence CSV disagree by "
219 f"{100*deviation:.2f}%, above the {100*tolerance:.0f}% interpolation "
220 f"allowance. A layout boundary left unwritten by an interior-only "
221 f"producer is the usual cause; see SynchronizePeriodicCellFields().",
222 file=sys.stderr,
223 )
224 return 1
225
226 if periodic and all(periodic):
227
228
229
230
231 whole = field.mean()
232 whole_deviation = abs(whole / reference - 1.0)
233 print(f"[INFO] {window}: whole-domain nodal mean {whole:.6e} "
234 f"({100*whole_deviation:+.2f}%)")
235 if whole_deviation > tolerance:
236 print(
237 f"[FAIL] every boundary node is defined on a fully periodic layout, "
238 f"but the whole-domain nodal mean is off by {100*whole_deviation:.2f}%. "
239 f"The layout boundary was left unwritten; see SynchronizePeriodicCellFields().",
240 file=sys.stderr,
241 )
242 return 1
243
244 print("[PASS] derived statistics VTK is consistent with the convergence CSV.")
245 return 0
246 except Exception as exc:
247 print(f"[ERROR] statistics nodal consistency check failed: {exc}", file=sys.stderr)
248 return 1
249
250
int main(int argc, char **argv)
Entry point for the postprocessor executable.
Head of a generic C-style linked list.