From 4758eb321532365b014821711c85037c7e6a35fd Mon Sep 17 00:00:00 2001 From: Frank14f <1515444314@qq.com> Date: Wed, 27 May 2026 19:19:51 +0800 Subject: [PATCH] Add streakline post-processing and exp control-matrix batch runners (vorticity + streakline) --- README.md | 25 + src/CelerisLab/common/__init__.py | 3 +- src/CelerisLab/common/streakline.py | 778 +++++++++++ .../config_lbm_three_cylinder_triangle.json | 8 +- src/CelerisLab/lbm/field.py | 50 +- .../lbm/kernels/config/config_grid.h | 4 +- .../lbm/kernels/config/config_method.h | 2 +- .../lbm/kernels/config/config_objects.h | 2 +- .../lbm/kernels/config/config_physics.h | 4 +- src/CelerisLab/lbm/stepper.py | 5 +- src/CelerisLab/simulation.py | 5 +- tests/Kan99b_validation.md | 420 +++--- tests/Sah04_validation.md | 292 ++-- tests/exp_ctrl_matrix.md | 181 +++ tests/experiment.ipynb | 1238 ++++------------- tests/inlet_plan.md | 75 - tests/run_exp_ctrl_matrix_streakline.py | 233 ++++ tests/run_exp_ctrl_matrix_vorticity.py | 421 ++++++ tests/run_inlet_channel_diagnostic.py | 574 -------- tests/run_inlet_ghost_timing_experiment.py | 460 ------ tests/run_inlet_scenario_fields.py | 587 -------- tests/run_kan99b_rotating_cylinder.py | 645 ++++----- tests/run_kan99b_streakline.py | 309 ++++ tests/run_perf_baseline.py | 269 ++++ tests/run_sah04_case9_flow_diagnostics.py | 238 ---- .../run_sah04_case9_grid_blockage_compare.py | 452 ------ tests/run_sah04_st_matrix.py | 955 ++++++------- tests/streakline_notes.md | 493 +++++++ tests/streakline_triangle_clean.png | Bin 0 -> 323486 bytes tests/vorticity_triangle_clean.png | Bin 0 -> 137686 bytes 30 files changed, 4128 insertions(+), 4600 deletions(-) create mode 100644 src/CelerisLab/common/streakline.py create mode 100644 tests/exp_ctrl_matrix.md delete mode 100644 tests/inlet_plan.md create mode 100644 tests/run_exp_ctrl_matrix_streakline.py create mode 100644 tests/run_exp_ctrl_matrix_vorticity.py delete mode 100644 tests/run_inlet_channel_diagnostic.py delete mode 100644 tests/run_inlet_ghost_timing_experiment.py delete mode 100644 tests/run_inlet_scenario_fields.py create mode 100644 tests/run_kan99b_streakline.py create mode 100644 tests/run_perf_baseline.py delete mode 100644 tests/run_sah04_case9_flow_diagnostics.py delete mode 100644 tests/run_sah04_case9_grid_blockage_compare.py create mode 100644 tests/streakline_notes.md create mode 100644 tests/streakline_triangle_clean.png create mode 100644 tests/vorticity_triangle_clean.png diff --git a/README.md b/README.md index cc8bed3..0c9f219 100644 --- a/README.md +++ b/README.md @@ -217,6 +217,31 @@ Tested on Tesla V100-SXM2-16GB (CUDA 12.4): | Re100 EsoPull SRT | 384×192 | ~3900 | | Re3000 MRT+LES | 384×192 | ~4360 | +### Performance methodology + +For a "kernel-dominant" baseline (closest to FluidX3D-style throughput testing), +use the dedicated script: + +```bash +conda run -n pycuda_3_10 python tests/run_perf_baseline.py \ + --lattice-model D2Q9 --nx 384 --ny 192 --nz 1 \ + --steps 4000 --warmup-steps 400 --batch-steps 100 +``` + +This path times GPU stepping (`stepper.step`) and reports MLUPS and batch latency +percentiles. By default it avoids host readbacks inside the timed loop. + +APIs that trigger device-to-host transfers (DTOH) and can reduce MLUPS: + +- `Simulation.get_macroscopic()` / `LBMField.get_macroscopic()` (downloads full DDF) +- `Simulation.get_ddf()` / `LBMField.download_ddf()` +- `Simulation.save_checkpoint()` (downloads field/state buffers) +- Body observation downloads (e.g. `ObjectManager.download_obs_full_async(...)`) + +Use `tests/run_perf_baseline.py` switches (`--macro-every`, `--ddf-every`, +`--checkpoint-every`, `--obs-every`) to quantify each overhead path against the +pure-step baseline. + ## Citation If you use CelerisLab in your research, please cite: diff --git a/src/CelerisLab/common/__init__.py b/src/CelerisLab/common/__init__.py index 0aea2aa..6d76a64 100644 --- a/src/CelerisLab/common/__init__.py +++ b/src/CelerisLab/common/__init__.py @@ -4,5 +4,6 @@ Common utilities and preprocessing functions. """ from . import preprocess +from . import streakline -__all__ = ['preprocess'] +__all__ = ["preprocess", "streakline"] diff --git a/src/CelerisLab/common/streakline.py b/src/CelerisLab/common/streakline.py new file mode 100644 index 0000000..12f12e1 --- /dev/null +++ b/src/CelerisLab/common/streakline.py @@ -0,0 +1,778 @@ +# CelerisLab/common/streakline.py +"""Streakline utilities for online/offline particle-based flow visualization. + +Responsibilities: + - Build dense release seeds (point/line/strip). + - Integrate particles with RK4 and space-time interpolated velocity fields. + - Support both online (in-memory frames from Simulation) and offline playback. + - Render density-like streakline images or accumulated path trails. +""" + +from __future__ import annotations + +import math +import os +from concurrent.futures import ThreadPoolExecutor +from dataclasses import dataclass, field +from typing import Any, List, Optional, Sequence, Tuple + +import numpy as np + +CylinderSpec = Tuple[Tuple[float, float], float] + + +@dataclass(frozen=True) +class FlowFrame: + """One sampled velocity frame.""" + + step: int + ux: np.ndarray + uy: np.ndarray + + +@dataclass(frozen=True) +class ReleaseConfig: + """Release strategy configuration for dense streakline seeding.""" + + mode: str = "strip" # point | line | strip + line_span: float = 12.0 + line_count: int = 5 + downstream_count: int = 4 + downstream_spacing: float = 3.0 + inject_per_seed: int = 2 + + +@dataclass(frozen=True) +class IntegratorConfig: + """Particle integration and filtering settings.""" + + alpha_t: float = 0.2 + alpha_x: float = 0.4 + age_decay_steps: float = 20000.0 + max_particle_age: Optional[float] = None + diffusion_coeff: float = 0.0 + random_seed: int = 1234 + num_threads: int = 0 + + +@dataclass +class ParticleTrailSet: + """Growing polylines per particle (pathline history — not physical streakline).""" + + trails: List[np.ndarray] = field(default_factory=list) + active: List[bool] = field(default_factory=list) + + def inject(self, points: np.ndarray) -> None: + pts = np.asarray(points, dtype=np.float64) + if pts.ndim != 2 or pts.shape[1] != 2: + raise ValueError("points must be [N,2].") + for p in pts: + self.trails.append(np.asarray([[p[0], p[1]]], dtype=np.float64)) + self.active.append(True) + + def keep_indices(self, survived_mask: np.ndarray) -> None: + """Deactivate trails whose particle did not survive the last advance.""" + mask = np.asarray(survived_mask, dtype=bool).ravel() + j = 0 + for i, is_active in enumerate(self.active): + if not is_active: + continue + if j >= mask.size: + break + if not bool(mask[j]): + self.active[i] = False + j += 1 + + def keep_mask(self, survived_mask: np.ndarray) -> None: + """Alias for keep_indices (boolean mask over active particles).""" + self.keep_indices(survived_mask) + + def append_positions(self, particles: np.ndarray) -> None: + pts = np.asarray(particles, dtype=np.float64) + j = 0 + for i, is_active in enumerate(self.active): + if not is_active: + continue + if j >= pts.shape[0]: + break + self.trails[i] = np.vstack([self.trails[i], pts[j]]) + j += 1 + + +def configure_compute_threads(num_threads: int = 0) -> int: + """Set BLAS/OpenMP thread env vars for host-side numpy work.""" + if num_threads <= 0: + num_threads = min(32, os.cpu_count() or 4) + for var in ( + "OMP_NUM_THREADS", + "OPENBLAS_NUM_THREADS", + "MKL_NUM_THREADS", + "NUMEXPR_NUM_THREADS", + "VECLIB_MAXIMUM_THREADS", + ): + os.environ[var] = str(int(num_threads)) + return int(num_threads) + + +def cylinders_from_triangle_layout(layout: dict) -> List[CylinderSpec]: + """Build cylinder list from triangle-layout dict used in experiment notebook.""" + radius = float(layout["radius_lb"]) + return [ + ((float(layout["x_apex"]), float(layout["y_center"])), radius), + ((float(layout["x_rear"]), float(layout["y_lower"])), radius), + ((float(layout["x_rear"]), float(layout["y_upper"])), radius), + ] + + +def estimate_sampling_plan( + *, + st_ref: float, + diameter: float, + u_ref: float, + snapshots_per_period: float = 24.0, + periods: int = 5, +) -> dict: + """Estimate save interval and snapshot count from nominal shedding scale.""" + period_steps = float(diameter) / (float(st_ref) * float(u_ref)) + save_every = int(max(20, round(period_steps / snapshots_per_period / 10.0) * 10)) + n_snapshots = int(max(20, round(float(periods) * float(snapshots_per_period)))) + return { + "st_ref": float(st_ref), + "period_steps_est": float(period_steps), + "snapshots_per_period_target": float(snapshots_per_period), + "save_every_recommended": int(save_every), + "snapshot_count_recommended": int(n_snapshots), + } + + +def build_release_points( + base_points: np.ndarray, + release_cfg: ReleaseConfig, +) -> np.ndarray: + """Expand base release points to dense points by line/strip policy.""" + pts = np.asarray(base_points, dtype=np.float64) + if pts.ndim != 2 or pts.shape[1] != 2: + raise ValueError("base_points must be [N,2].") + mode = str(release_cfg.mode).lower() + if mode not in ("point", "line", "strip"): + raise ValueError("release mode must be point|line|strip.") + + line_count = max(1, int(release_cfg.line_count)) + ds_count = max(1, int(release_cfg.downstream_count)) + line_offsets = np.linspace( + -0.5 * float(release_cfg.line_span), + 0.5 * float(release_cfg.line_span), + line_count, + ) + ds_offsets = np.arange(ds_count, dtype=np.float64) * float(release_cfg.downstream_spacing) + + out: list[list[float]] = [] + for x0, y0 in pts: + line_iter = [0.0] if mode == "point" else line_offsets + ds_iter = [0.0] if mode in ("point", "line") else ds_offsets + for dy in line_iter: + for dx in ds_iter: + out.append([float(x0 + dx), float(y0 + dy)]) + return np.asarray(out, dtype=np.float64) + + +def _bilinear_sample(field: np.ndarray, x: np.ndarray, y: np.ndarray) -> np.ndarray: + ny, nx = field.shape + x0 = np.floor(x).astype(np.int64) + y0 = np.floor(y).astype(np.int64) + x1 = x0 + 1 + y1 = y0 + 1 + inside = (x0 >= 0) & (x1 < nx) & (y0 >= 0) & (y1 < ny) + out = np.full_like(x, np.nan, dtype=np.float64) + if not np.any(inside): + return out + + xi = x[inside] - x0[inside] + yi = y[inside] - y0[inside] + f00 = field[y0[inside], x0[inside]] + f10 = field[y0[inside], x1[inside]] + f01 = field[y1[inside], x0[inside]] + f11 = field[y1[inside], x1[inside]] + out[inside] = ( + (1.0 - xi) * (1.0 - yi) * f00 + + xi * (1.0 - yi) * f10 + + (1.0 - xi) * yi * f01 + + xi * yi * f11 + ) + return out + + +def _velocity_at(pos: np.ndarray, theta: float, f0: FlowFrame, f1: FlowFrame) -> np.ndarray: + x = pos[:, 0] + y = pos[:, 1] + vx0 = _bilinear_sample(f0.ux, x, y) + vy0 = _bilinear_sample(f0.uy, x, y) + vx1 = _bilinear_sample(f1.ux, x, y) + vy1 = _bilinear_sample(f1.uy, x, y) + vx = (1.0 - theta) * vx0 + theta * vx1 + vy = (1.0 - theta) * vy0 + theta * vy1 + return np.stack([vx, vy], axis=1) + + +def _particles_valid_mask( + p_new: np.ndarray, + *, + nx: int, + ny: int, + solid_center: Optional[Tuple[float, float]] = None, + solid_radius: Optional[float] = None, + cylinders: Optional[Sequence[CylinderSpec]] = None, +) -> np.ndarray: + valid = np.all(np.isfinite(p_new), axis=1) + valid &= (p_new[:, 0] >= 0.0) & (p_new[:, 0] <= nx - 1) + valid &= (p_new[:, 1] >= 0.0) & (p_new[:, 1] <= ny - 1) + + specs: List[CylinderSpec] = [] + if cylinders: + specs.extend(cylinders) + elif solid_center is not None and solid_radius is not None: + specs.append((solid_center, float(solid_radius))) + + for (cx, cy), radius in specs: + dx = p_new[:, 0] - float(cx) + dy = p_new[:, 1] - float(cy) + valid &= (dx * dx + dy * dy) > (float(radius) * float(radius)) + return valid + + +def _inject_particles( + particles: np.ndarray, + ages: np.ndarray, + release_points: np.ndarray, + *, + inject_per_seed: int, +) -> Tuple[np.ndarray, np.ndarray]: + new_pts = np.repeat(release_points, max(1, int(inject_per_seed)), axis=0) + if particles.size == 0: + return new_pts.copy(), np.zeros((new_pts.shape[0],), dtype=np.float64) + return ( + np.vstack([particles, new_pts]), + np.concatenate([ages, np.zeros((new_pts.shape[0],), dtype=np.float64)]), + ) + + +def advance_particles_between_frames( + particles: np.ndarray, + ages: np.ndarray, + frame0: FlowFrame, + frame1: FlowFrame, + *, + nx: int, + ny: int, + cfg: IntegratorConfig, + solid_center: Optional[Tuple[float, float]] = None, + solid_radius: Optional[float] = None, + cylinders: Optional[Sequence[CylinderSpec]] = None, + rng: Optional[np.random.Generator] = None, +) -> Tuple[np.ndarray, np.ndarray, dict, np.ndarray]: + """Advance particles from frame0.step to frame1.step with RK4. + + Returns: + particles, ages, diagnostics, survived_mask (bool, length = input count). + """ + if particles.size == 0: + return particles, ages, {"n_substeps": 0, "max_speed": 0.0}, np.zeros((0,), dtype=bool) + + dt_save = float(frame1.step - frame0.step) + max_speed = float( + max( + np.nanmax(np.sqrt(frame0.ux * frame0.ux + frame0.uy * frame0.uy)), + np.nanmax(np.sqrt(frame1.ux * frame1.ux + frame1.uy * frame1.uy)), + ) + ) + umax = max(max_speed, 1e-8) + dt_trace = min(float(cfg.alpha_t) * dt_save, float(cfg.alpha_x) / umax) + n_sub = max(1, int(math.ceil(dt_save / dt_trace))) + dt_sub = dt_save / float(n_sub) + + p = np.asarray(particles, dtype=np.float64) + a = np.asarray(ages, dtype=np.float64) + track = np.ones((p.shape[0],), dtype=bool) + local_rng = rng or np.random.default_rng(int(cfg.random_seed)) + diffusion = float(cfg.diffusion_coeff) + + for sub_idx in range(n_sub): + if p.size == 0: + break + theta = float(sub_idx) / float(n_sub) + k1 = _velocity_at(p, theta, frame0, frame1) + k2 = _velocity_at(p + 0.5 * dt_sub * k1, min(1.0, theta + 0.5 / n_sub), frame0, frame1) + k3 = _velocity_at(p + 0.5 * dt_sub * k2, min(1.0, theta + 0.5 / n_sub), frame0, frame1) + k4 = _velocity_at(p + dt_sub * k3, min(1.0, theta + 1.0 / n_sub), frame0, frame1) + p_new = p + (k1 + 2.0 * k2 + 2.0 * k3 + k4) * (dt_sub / 6.0) + + if diffusion > 0.0: + sigma = math.sqrt(max(0.0, 2.0 * diffusion * dt_sub)) + p_new += local_rng.normal(0.0, sigma, size=p_new.shape) + + valid = _particles_valid_mask( + p_new, + nx=nx, + ny=ny, + solid_center=solid_center, + solid_radius=solid_radius, + cylinders=cylinders, + ) + track[track] = valid + p = p_new[valid] + a = a[valid] + dt_sub + if cfg.max_particle_age is not None: + age_keep = a <= float(cfg.max_particle_age) + track[track] = age_keep + p = p[age_keep] + a = a[age_keep] + + survived_mask = track + return p, a, {"dt_trace": float(dt_sub), "n_substeps": int(n_sub), "max_speed": float(max_speed)}, survived_mask + + +def run_streakline_offline( + frames: Sequence[FlowFrame], + *, + nx: int, + ny: int, + release_points: np.ndarray, + release_cfg: ReleaseConfig, + integrator_cfg: IntegratorConfig, + solid_center: Optional[Tuple[float, float]] = None, + solid_radius: Optional[float] = None, + cylinders: Optional[Sequence[CylinderSpec]] = None, +) -> Tuple[np.ndarray, np.ndarray, dict]: + """Reconstruct streakline from pre-collected velocity frames.""" + if len(frames) < 2: + raise ValueError("At least two frames are required.") + pts = build_release_points(release_points, release_cfg) + particles = np.zeros((0, 2), dtype=np.float64) + ages = np.zeros((0,), dtype=np.float64) + rng = np.random.default_rng(int(integrator_cfg.random_seed)) + max_speed = 0.0 + last_step = int(frames[0].step) + total_substeps = 0 + + for i in range(len(frames) - 1): + particles, ages = _inject_particles( + particles, + ages, + pts, + inject_per_seed=release_cfg.inject_per_seed, + ) + particles, ages, diag, _ = advance_particles_between_frames( + particles, + ages, + frames[i], + frames[i + 1], + nx=nx, + ny=ny, + cfg=integrator_cfg, + solid_center=solid_center, + solid_radius=solid_radius, + cylinders=cylinders, + rng=rng, + ) + max_speed = max(max_speed, float(diag["max_speed"])) + total_substeps += int(diag["n_substeps"]) + last_step = int(frames[i + 1].step) + + return particles, ages, { + "frames_used": int(len(frames)), + "last_step": int(last_step), + "n_particles_alive": int(particles.shape[0]), + "max_speed_seen": float(max_speed), + "total_substeps": int(total_substeps), + } + + +def run_streakline_online( + sim: Any, + *, + start_step: int, + sample_every: int, + n_samples: int, + release_points: np.ndarray, + release_cfg: ReleaseConfig, + integrator_cfg: IntegratorConfig, + solid_center: Optional[Tuple[float, float]] = None, + solid_radius: Optional[float] = None, + cylinders: Optional[Sequence[CylinderSpec]] = None, +) -> Tuple[np.ndarray, np.ndarray, dict]: + """Compute streakline online from a running Simulation without disk snapshots.""" + if sample_every < 1 or n_samples < 2: + raise ValueError("sample_every must be >=1 and n_samples must be >=2.") + if not getattr(sim, "_initialized", False): + raise RuntimeError("Simulation must be initialized before online streakline run.") + + nx = int(sim.lbm_cfg.nx) + ny = int(sim.lbm_cfg.ny) + frames: list[FlowFrame] = [] + target_last = int(start_step + sample_every * (n_samples - 1)) + + while int(sim.stepper.step_count) < target_last: + sim.step(1) + step = int(sim.stepper.step_count) + if step < int(start_step): + continue + if (step - int(start_step)) % int(sample_every) != 0: + continue + macro = sim.get_macroscopic() + frames.append( + FlowFrame( + step=step, + ux=np.asarray(macro["ux"], dtype=np.float64), + uy=np.asarray(macro["uy"], dtype=np.float64), + ) + ) + if len(frames) >= int(n_samples): + break + + if len(frames) < 2: + raise RuntimeError("Online run did not collect enough frames.") + + particles, ages, diag = run_streakline_offline( + frames, + nx=nx, + ny=ny, + release_points=release_points, + release_cfg=release_cfg, + integrator_cfg=integrator_cfg, + solid_center=solid_center, + solid_radius=solid_radius, + cylinders=cylinders, + ) + diag["mode"] = "online" + diag["sample_every"] = int(sample_every) + diag["start_step"] = int(start_step) + return particles, ages, diag + + +def compute_vorticity(ux: np.ndarray, uy: np.ndarray) -> np.ndarray: + """Central-difference vorticity ω = ∂uy/∂x − ∂ux/∂y on interior cells.""" + ux_arr = np.asarray(ux, dtype=np.float64) + uy_arr = np.asarray(uy, dtype=np.float64) + vort = np.zeros_like(ux_arr, dtype=np.float64) + dudy = 0.5 * (ux_arr[2:, 1:-1] - ux_arr[:-2, 1:-1]) + dvdx = 0.5 * (uy_arr[1:-1, 2:] - uy_arr[1:-1, :-2]) + vort[1:-1, 1:-1] = dvdx - dudy + return vort + + +def render_vorticity_field( + vort: np.ndarray, + *, + nx: int, + ny: int, + out_path: str, + cylinders: Optional[Sequence[CylinderSpec]] = None, + vmin: Optional[float] = None, + vmax: Optional[float] = None, + percentile: float = 99.0, + minimal_axes: bool = True, +) -> dict: + """Render vorticity with white at zero, green negative, purple positive.""" + try: + import matplotlib + + matplotlib.use("Agg") + import matplotlib.pyplot as plt + from matplotlib.colors import LinearSegmentedColormap, TwoSlopeNorm + except ImportError as exc: + raise RuntimeError("render_vorticity_field requires matplotlib.") from exc + + field = np.asarray(vort, dtype=np.float64) + vmax_field = float(np.nanmax(np.abs(field))) + abs_pct = float(np.nanpercentile(np.abs(field), percentile)) + # Freestream dominates |ω|; do not let a near-zero percentile wash out wake structure. + abs_max = max(abs_pct, 0.35 * vmax_field, 1e-6) + if vmin is None: + vmin = -abs_max + if vmax is None: + vmax = abs_max + if vmin >= 0.0 or vmax <= 0.0: + raise ValueError(f"vmin/vmax must bracket zero: vmin={vmin}, vmax={vmax}") + + cmap = LinearSegmentedColormap.from_list( + "vort_green_white_purple", + ["#1a9850", "#f7f7f7", "#762a83"], + N=256, + ) + norm = TwoSlopeNorm(vmin=float(vmin), vcenter=0.0, vmax=float(vmax)) + + rgba = cmap(norm(field)) + if cylinders: + yy, xx = np.mgrid[0:ny, 0:nx] + for (cx, cy), radius in cylinders: + mask = (xx - float(cx)) ** 2 + (yy - float(cy)) ** 2 <= float(radius) ** 2 + rgba[mask] = (0.0, 0.0, 0.0, 1.0) + + os.makedirs(os.path.dirname(os.path.abspath(out_path)), exist_ok=True) + fig = plt.figure(figsize=(nx / 100.0, ny / 100.0), dpi=100) + ax = fig.add_axes([0.0, 0.0, 1.0, 1.0]) + ax.imshow(rgba, origin="lower", interpolation="nearest", aspect="auto") + if minimal_axes: + ax.axis("off") + fig.savefig(out_path, dpi=100, bbox_inches="tight", pad_inches=0) + plt.close(fig) + + return { + "image_path": out_path, + "vmin": float(vmin), + "vmax": float(vmax), + "vort_min": float(np.nanmin(field)), + "vort_max": float(np.nanmax(field)), + } + + +def gaussian_blur2d(img: np.ndarray, sigma: float = 1.0) -> np.ndarray: + """Separable Gaussian blur helper.""" + if float(sigma) <= 0.0: + return np.asarray(img, dtype=np.float64) + radius = max(1, int(math.ceil(3.0 * float(sigma)))) + x = np.arange(-radius, radius + 1, dtype=np.float64) + k = np.exp(-(x**2) / (2.0 * sigma * sigma)) + k /= np.sum(k) + arr = np.asarray(img, dtype=np.float64) + arr = np.apply_along_axis(lambda m: np.convolve(m, k, mode="same"), 1, arr) + arr = np.apply_along_axis(lambda m: np.convolve(m, k, mode="same"), 0, arr) + return arr + + +def _rasterize_segment( + intensity: np.ndarray, + x0: float, + y0: float, + x1: float, + y1: float, + weight: float, +) -> None: + """Stamp one segment into a scalar intensity map (for white-bg red compose).""" + ny, nx = intensity.shape + n = int(max(abs(x1 - x0), abs(y1 - y0), 1.0)) + 1 + xs = np.linspace(x0, x1, n) + ys = np.linspace(y0, y1, n) + xi = np.clip(np.rint(xs).astype(np.int64), 0, nx - 1) + yi = np.clip(np.rint(ys).astype(np.int64), 0, ny - 1) + w = float(max(0.0, min(1.0, weight))) + np.add.at(intensity, (yi, xi), w) + + +def _draw_trail( + intensity: np.ndarray, + trail: np.ndarray, + *, + fade_along_trail: bool, +) -> None: + if trail.shape[0] < 2: + return + nseg = trail.shape[0] - 1 + for i in range(nseg): + if fade_along_trail: + t = float(i + 1) / float(nseg) + weight = 0.15 + 0.85 * t + else: + weight = 1.0 + _rasterize_segment( + intensity, + float(trail[i, 0]), + float(trail[i, 1]), + float(trail[i + 1, 0]), + float(trail[i + 1, 1]), + weight, + ) + + +def _draw_cylinders(rgb: np.ndarray, cylinders: Sequence[CylinderSpec]) -> None: + ny, nx = rgb.shape[0], rgb.shape[1] + yy, xx = np.mgrid[0:ny, 0:nx] + for (cx, cy), radius in cylinders: + mask = (xx - float(cx)) ** 2 + (yy - float(cy)) ** 2 <= float(radius) ** 2 + rgb[mask] = 0.0 + + +def render_snapshot_trails( + trail_set: ParticleTrailSet, + *, + nx: int, + ny: int, + out_path: str, + cylinders: Optional[Sequence[CylinderSpec]] = None, + blur_sigma: float = 0.0, + fade_along_trail: bool = True, + num_threads: int = 0, + background_color: Tuple[float, float, float] = (1.0, 1.0, 1.0), + streak_color: Tuple[float, float, float] = (1.0, 0.0, 0.0), +) -> dict: + """Render accumulated path trails (pathline collage — debug only, not streakline).""" + n_threads = configure_compute_threads(num_threads) + intensity = np.zeros((ny, nx), dtype=np.float64) + + trails = trail_set.trails + if not trails: + density_max = 0.0 + else: + chunk = max(1, (len(trails) + n_threads - 1) // n_threads) + + def _worker(batch: List[np.ndarray]) -> np.ndarray: + local = np.zeros((ny, nx), dtype=np.float64) + for tr in batch: + _draw_trail(local, tr, fade_along_trail=fade_along_trail) + return local + + batches = [trails[i : i + chunk] for i in range(0, len(trails), chunk)] + if len(batches) == 1: + intensity = _worker(batches[0]) + else: + with ThreadPoolExecutor(max_workers=n_threads) as pool: + parts = list(pool.map(_worker, batches)) + for part in parts: + intensity = np.maximum(intensity, part) + + if np.any(intensity > 0): + vmax = float(np.percentile(intensity[intensity > 0], 99.0)) + intensity = np.clip(intensity / max(vmax, 1e-12), 0.0, 1.0) + density_max = float(np.max(intensity)) + + rgb = np.ones((ny, nx, 3), dtype=np.float64) + rgb[:, :, 0] = background_color[0] + rgb[:, :, 1] = background_color[1] * (1.0 - intensity) + streak_color[1] * intensity + rgb[:, :, 2] = background_color[2] * (1.0 - intensity) + streak_color[2] * intensity + if streak_color[0] < 0.999: + rgb[:, :, 0] = background_color[0] * (1.0 - intensity) + streak_color[0] * intensity + + if cylinders: + _draw_cylinders(rgb, cylinders) + + if float(blur_sigma) > 0.0: + for c in range(3): + rgb[:, :, c] = gaussian_blur2d(rgb[:, :, c], sigma=float(blur_sigma)) + + rgb = np.clip(rgb, 0.0, 1.0) + os.makedirs(os.path.dirname(os.path.abspath(out_path)), exist_ok=True) + try: + import matplotlib + + matplotlib.use("Agg") + import matplotlib.pyplot as plt + + fig = plt.figure(figsize=(nx / 100.0, ny / 100.0), dpi=100) + ax = fig.add_axes([0.0, 0.0, 1.0, 1.0]) + ax.axis("off") + ax.imshow(rgb, origin="lower", interpolation="nearest", aspect="auto") + fig.savefig(out_path, dpi=100, bbox_inches="tight", pad_inches=0) + plt.close(fig) + except ImportError: + from PIL import Image + + Image.fromarray((np.clip(rgb, 0.0, 1.0) * 255.0).astype(np.uint8)).save(out_path) + + return { + "image_path": out_path, + "trail_count": int(len(trails)), + "density_max": density_max, + "num_threads": int(n_threads), + } + + +def render_streakline_density( + positions: np.ndarray, + ages: np.ndarray, + *, + nx: int, + ny: int, + out_path: str, + release_points: Optional[np.ndarray] = None, + solid_center: Optional[Tuple[float, float]] = None, + solid_radius: Optional[float] = None, + cylinders: Optional[Sequence[CylinderSpec]] = None, + age_decay_steps: float = 20000.0, + blur_sigma: float = 1.2, + title: str = "Streakline", + minimal_axes: bool = False, + background_color: Tuple[float, float, float] = (0.0, 0.0, 0.0), + streak_color: Tuple[float, float, float] = (1.0, 0.5, 0.0), + show_release_points: bool = True, +) -> dict: + """Render streakline as current particle positions (density cloud at observation time).""" + try: + import matplotlib + + matplotlib.use("Agg") + import matplotlib.pyplot as plt + from matplotlib.colors import LinearSegmentedColormap + except ImportError as exc: + raise RuntimeError("render_streakline_density requires matplotlib.") from exc + + if positions.size == 0: + density = np.zeros((ny, nx), dtype=np.float64) + else: + w = np.exp(-np.asarray(ages, dtype=np.float64) / max(1.0, float(age_decay_steps))) + y_idx = np.clip(np.floor(positions[:, 1]).astype(np.int64), 0, ny - 1) + x_idx = np.clip(np.floor(positions[:, 0]).astype(np.int64), 0, nx - 1) + density = np.zeros((ny, nx), dtype=np.float64) + np.add.at(density, (y_idx, x_idx), w) + density = gaussian_blur2d(density, sigma=float(blur_sigma)) + + if minimal_axes: + rgb = np.ones((ny, nx, 3), dtype=np.float64) + rgb[:, :, 0] = background_color[0] + rgb[:, :, 1] = background_color[1] + rgb[:, :, 2] = background_color[2] + if np.any(density > 0): + vmax = float(np.percentile(density[density > 0], 99.0)) + alpha = np.clip(density / max(vmax, 1e-12), 0.0, 1.0) + for c, val in enumerate(streak_color): + rgb[:, :, c] = np.clip(rgb[:, :, c] * (1.0 - alpha) + val * alpha, 0.0, 1.0) + specs = list(cylinders or []) + if solid_center is not None and solid_radius is not None: + specs.append((solid_center, float(solid_radius))) + if specs: + _draw_cylinders(rgb, specs) + os.makedirs(os.path.dirname(os.path.abspath(out_path)), exist_ok=True) + fig = plt.figure(figsize=(nx / 100.0, ny / 100.0), dpi=100) + ax = fig.add_axes([0.0, 0.0, 1.0, 1.0]) + ax.axis("off") + ax.imshow(rgb, origin="lower", interpolation="nearest", aspect="auto") + fig.savefig(out_path, dpi=100, bbox_inches="tight", pad_inches=0) + plt.close(fig) + return { + "image_path": out_path, + "n_particles": int(positions.shape[0]) if positions.ndim == 2 else 0, + "density_max": float(np.max(density)) if density.size else 0.0, + } + + os.makedirs(os.path.dirname(os.path.abspath(out_path)), exist_ok=True) + fig, ax = plt.subplots(figsize=(12, 5)) + vmax = np.percentile(density[density > 0], 99.0) if np.any(density > 0) else 1.0 + cmap = LinearSegmentedColormap.from_list("streak", [(0.0, 0.0, 0.0), streak_color]) + ax.imshow( + density, + origin="lower", + cmap=cmap, + extent=(0, nx - 1, 0, ny - 1), + vmin=0.0, + vmax=max(vmax, 1e-12), + aspect="equal", + ) + if solid_center is not None and solid_radius is not None: + circ = plt.Circle(solid_center, radius=solid_radius, fill=False, color="cyan", linewidth=1.4) + ax.add_patch(circ) + if show_release_points and release_points is not None and np.asarray(release_points).size: + rp = np.asarray(release_points) + ax.scatter(rp[:, 0], rp[:, 1], c="white", s=10, marker="x") + ax.set_title(title) + ax.set_xlabel("x (lattice)") + ax.set_ylabel("y (lattice)") + ax.set_xlim(0, nx - 1) + ax.set_ylim(0, ny - 1) + fig.tight_layout() + fig.savefig(out_path, dpi=170, bbox_inches="tight") + plt.close(fig) + + return { + "image_path": out_path, + "n_particles": int(positions.shape[0]) if positions.ndim == 2 else 0, + "density_max": float(np.max(density)) if density.size else 0.0, + } diff --git a/src/CelerisLab/configs/config_lbm_three_cylinder_triangle.json b/src/CelerisLab/configs/config_lbm_three_cylinder_triangle.json index 940f65f..0af0509 100644 --- a/src/CelerisLab/configs/config_lbm_three_cylinder_triangle.json +++ b/src/CelerisLab/configs/config_lbm_three_cylinder_triangle.json @@ -2,8 +2,8 @@ "_doc": "Three rotating cylinders in confined channel (triangle layout, free-slip walls).", "grid": { "lattice_model": "D2Q9", - "nx": 6000, - "ny": 1200, + "nx": 3000, + "ny": 300, "nz": 1 }, "physics": { @@ -27,7 +27,9 @@ }, "inlet": { "profile": "uniform", - "trt_neq_damp": 0.5 + "scheme": "regularized", + "trt_neq_damp": 0.5, + "regularized_neq_damp": 0.5 }, "outlet": { "mode": "neq_extrap", diff --git a/src/CelerisLab/lbm/field.py b/src/CelerisLab/lbm/field.py index 19f847c..e57e5b1 100644 --- a/src/CelerisLab/lbm/field.py +++ b/src/CelerisLab/lbm/field.py @@ -69,6 +69,12 @@ class LBMField: # Snapshot self._ddf_snap: np.ndarray | None = None + self._host_ddf_step: int | None = None + + # Cached lattice constants mirrored from CUDA __constant__ memory. + self._cached_w: np.ndarray | None = None + self._cached_vectors_2d: tuple[np.ndarray, np.ndarray] | None = None + self._cached_vectors_3d: tuple[np.ndarray, np.ndarray, np.ndarray] | None = None # Host mirror of runtime parameters so partial updates preserve state. self._runtime_params = { @@ -82,6 +88,16 @@ class LBMField: # Upload d_params immediately self._upload_params() + def invalidate_host_ddf_cache(self) -> None: + """Mark host DDF mirror as stale after GPU-side updates.""" + self._host_ddf_step = None + + def invalidate_module_constant_cache(self) -> None: + """Drop cached __constant__ mirrors after module replacement.""" + self._cached_w = None + self._cached_vectors_2d = None + self._cached_vectors_3d = None + def build_channel_flags(self) -> np.ndarray: """Return a fresh host flag array for the base channel domain. @@ -123,9 +139,15 @@ class LBMField: return self.sensors.count # -- DDF download with precision decode ---------------------------------- - def download_ddf(self): + def download_ddf(self, *, step_id: int | None = None, force: bool = False): """Copy DDF from GPU to host self.ddf (float32), decoding FP16S if needed. If DDF shifting is enabled, un-shifts by adding back lattice weights.""" + # Fast path for repeated host queries within one solver step. + # Callers pass step_id from LBMStepper; force=True bypasses cache + # for snapshot-style semantics. + if not force and step_id is not None and self._host_ddf_step == int(step_id): + return + if self.store_bytes == 2: buf = np.empty(self.n * self.nq, dtype=self.store_dtype) cuda.memcpy_dtoh(buf, self.ddf_gpu) @@ -143,13 +165,17 @@ class LBMField: ddf_2d = self.ddf.reshape(self.nq, -1) for i in range(self.nq): ddf_2d[i] += w[i] + self._host_ddf_step = int(step_id) if step_id is not None else None def _read_lattice_weights(self): """Read d_w[] from CUDA __constant__ memory (single source of truth in descriptors.cuh).""" + if self._cached_w is not None: + return self._cached_w ptr, size = self.module.get_global("d_w") w = np.empty(self.nq, dtype=np.float32) cuda.memcpy_dtoh(w, ptr) - return w + self._cached_w = w + return self._cached_w # -- d_params upload ----------------------------------------------------- def _upload_params(self): @@ -209,6 +235,7 @@ class LBMField: else: cuda.memcpy_htod(self.ddf_gpu, self.ddf) cuda.memcpy_htod(self.temp_gpu, self.ddf) + self.invalidate_host_ddf_cache() # The canonical download_ddf is defined above in the class body. @@ -223,6 +250,11 @@ class LBMField: # -- Read lattice descriptors from CUDA module --------------------------- def _read_lattice_vectors(self): """Read d_cx, d_cy [, d_cz] from CUDA __constant__ memory.""" + if self.cfg.is_d2q9 and self._cached_vectors_2d is not None: + return self._cached_vectors_2d + if self.cfg.is_d3q19 and self._cached_vectors_3d is not None: + return self._cached_vectors_3d + nq = self.nq cx = np.empty(nq, dtype=np.int32) cy = np.empty(nq, dtype=np.int32) @@ -234,13 +266,17 @@ class LBMField: cz = np.empty(nq, dtype=np.int32) ptr_cz, _ = self.module.get_global("d_cz") cuda.memcpy_dtoh(cz, ptr_cz) - return cx, cy, cz - return cx, cy + self._cached_vectors_3d = (cx, cy, cz) + return self._cached_vectors_3d + self._cached_vectors_2d = (cx, cy) + return self._cached_vectors_2d # -- Macroscopic field extraction ---------------------------------------- - def get_macroscopic(self): + def get_macroscopic(self, *, step_id: int | None = None): """Download DDF and compute rho, ux, uy [, uz] on host.""" - self.download_ddf() + # Reuse the DDF host mirror when step_id is unchanged to avoid + # duplicate DTOH in repeated diagnostics of the same timestep. + self.download_ddf(step_id=step_id) nq = self.nq if self.cfg.is_d2q9: @@ -266,7 +302,7 @@ class LBMField: # -- Snapshots ----------------------------------------------------------- def snapshot(self): - self.download_ddf() + self.download_ddf(force=True) self._ddf_snap = self.ddf.copy() def restore(self): diff --git a/src/CelerisLab/lbm/kernels/config/config_grid.h b/src/CelerisLab/lbm/kernels/config/config_grid.h index bc7e935..dfdb62e 100644 --- a/src/CelerisLab/lbm/kernels/config/config_grid.h +++ b/src/CelerisLab/lbm/kernels/config/config_grid.h @@ -6,8 +6,8 @@ #define NT 256 #define MULT_GPU 0 -#define NX 401 -#define NY 201 +#define NX 3000 +#define NY 300 #define NZ 1 // ---- Lattice model (single source of truth) ---- diff --git a/src/CelerisLab/lbm/kernels/config/config_method.h b/src/CelerisLab/lbm/kernels/config/config_method.h index e07cec1..6a10352 100644 --- a/src/CelerisLab/lbm/kernels/config/config_method.h +++ b/src/CelerisLab/lbm/kernels/config/config_method.h @@ -17,7 +17,7 @@ #define OUTLET_MODE 0 #define OUTLET_BLEND_ALPHA 0.700f #define OUTLET_BACKFLOW_CLAMP 1 -#define Y_WALL_BC 0 +#define Y_WALL_BC 1 #define OMEGA_COLLISION_MIN 0.01f #define OMEGA_COLLISION_MAX 1.960f diff --git a/src/CelerisLab/lbm/kernels/config/config_objects.h b/src/CelerisLab/lbm/kernels/config/config_objects.h index e7cad65..ae6277e 100644 --- a/src/CelerisLab/lbm/kernels/config/config_objects.h +++ b/src/CelerisLab/lbm/kernels/config/config_objects.h @@ -3,6 +3,6 @@ #ifndef CELERIS_CONFIG_OBJECTS_H #define CELERIS_CONFIG_OBJECTS_H -#define N_OBJS 0 +#define N_OBJS 3 #endif diff --git a/src/CelerisLab/lbm/kernels/config/config_physics.h b/src/CelerisLab/lbm/kernels/config/config_physics.h index a08cc9b..43c6164 100644 --- a/src/CelerisLab/lbm/kernels/config/config_physics.h +++ b/src/CelerisLab/lbm/kernels/config/config_physics.h @@ -4,9 +4,9 @@ #define CELERIS_CONFIG_PHYSICS_H #define LBtype float -#define VIS 0.0090000000 +#define VIS 0.0040000000 #define RHO 1.0 -#define U0 0.03 +#define U0 0.04 #define PI 3.141592653589793238 diff --git a/src/CelerisLab/lbm/stepper.py b/src/CelerisLab/lbm/stepper.py index 0cec404..364bf3f 100644 --- a/src/CelerisLab/lbm/stepper.py +++ b/src/CelerisLab/lbm/stepper.py @@ -51,7 +51,7 @@ class LBMStepper: _gpu_bytes = f.n * f.nq * f.store_bytes cuda.memcpy_dtod(f.temp_gpu, f.ddf_gpu, _gpu_bytes) cuda.memcpy_dtoh(f.flag, f.flag_gpu) - f.download_ddf() + f.download_ddf(step_id=self._step_count, force=True) def step( self, @@ -88,6 +88,9 @@ class LBMStepper: self._launch_sensor(obs_gpu, **launch_kw) self._step_count += 1 + if n > 0: + # Any completed GPU step invalidates host DDF mirror. + f.invalidate_host_ddf_cache() def _launch_curved(self, action_gpu, obs_gpu, **launch_kw): f = self.field diff --git a/src/CelerisLab/simulation.py b/src/CelerisLab/simulation.py index 99ccbeb..ba5db9c 100644 --- a/src/CelerisLab/simulation.py +++ b/src/CelerisLab/simulation.py @@ -125,6 +125,7 @@ class Simulation: self._module = compiler.load_module(self._ptx_path) # Reconnect field and stepper to new module self.field.module = self._module + self.field.invalidate_module_constant_cache() self.field._upload_params() _prev_step_count = self.stepper._step_count self.stepper = LBMStepper( @@ -194,10 +195,10 @@ class Simulation: # -- Data access --------------------------------------------------------- def get_macroscopic(self) -> Dict[str, np.ndarray]: """Download DDF and return rho, ux, uy [, uz].""" - return self.field.get_macroscopic() + return self.field.get_macroscopic(step_id=self.stepper.step_count) def get_ddf(self) -> np.ndarray: - self.field.download_ddf() + self.field.download_ddf(step_id=self.stepper.step_count) return self.field.ddf.copy() def get_flags(self) -> np.ndarray: diff --git a/tests/Kan99b_validation.md b/tests/Kan99b_validation.md index fe0aca2..df9aafb 100644 --- a/tests/Kan99b_validation.md +++ b/tests/Kan99b_validation.md @@ -1,64 +1,52 @@ -# Rotating cylinder validation plan +# Rotating cylinder validation against [Kan99b] -##### [**Undermind**](https://undermind.ai) +## Goal ---- +This validation should stay small, direct, and defensible. -## Rotating cylinder validation against \[Kan99b\] +The main design rules are: -This plan defines a practical validation campaign for 2D flow past a rotating circular cylinder using the current CelerisLab solver. The reference is the rotating cylinder study of Kang, Choi, and Lee \[Kan99b\]. The goal is not to reproduce their O type far field mesh exactly. The goal is to show that the present rectangular domain with uniform inflow, slip top and bottom boundaries, curved moving wall treatment, and open outlet can recover the same force, shedding, and suppression trends with controlled boundary sensitivity. +- use the paper's direct numeric anchor at `Re = 100, alpha = 1.0` as the main hard benchmark +- use a low-rotation case to test the lift trend +- use suppression cases to test flow classification, not exact threshold fitting +- do not treat values read from figures near `alpha_L` as tight amplitude targets -The validation focuses on three questions: +This keeps the matrix representative without overfitting to sensitive threshold points. -- whether the moving curved wall treatment gives the correct mean force trend under rotation -- whether the wake frequency and fluctuation amplitudes are credible before shedding is suppressed -- whether the predicted critical rotation rate for shedding suppression is close to the literature value +## Strong numeric anchors from [Kan99b] -## Reference targets from \[Kan99b\] +The strongest exact benchmark in the paper is the convergence case at `Re = 100, alpha = 1.0`. -The paper defines the Reynolds number and spin ratio as +| Quantity | Reference value | +|---|---:| +| `St` | 0.1655 | +| `mean C_L` | -2.4881 | +| `mean C_D` | 1.1040 | +| `C'_L` | 0.3631 | +| `C'_D` | 0.0993 | -``` math -Re = \frac{U_\infty D}{\nu}, \qquad \alpha = \frac{\omega_{body} D}{2 U_\infty} -``` +For low rotation at `Re = 100`, the paper also gives the mean lift trend -and uses the standard force coefficients +\[ +\overline{C_L} \approx -2.48\alpha +\] -``` math -C_D = \frac{2 F_x}{\rho U_\infty^2 D}, \qquad C_L = \frac{2 F_y}{\rho U_\infty^2 D} -``` +which is a good secondary benchmark for small `alpha`. -The fluctuation amplitudes are half peak to peak values over one shedding period, not RMS values \[Kan99b\]. +The suppression thresholds are given as trends: -The most useful exact anchor point is the tabulated convergence case at $`Re=100`$ and $`\alpha=1.0`$ \[Kan99b\]. +| Reynolds number | Expected `alpha_L` | +|---|---:| +| 60 | about 1.4 | +| 100 | about 1.8 | +| 160 | about 1.9 | -| Quantity | Reference value | -|:-------------------|----------------:| -| $`St`$ | 0.1655 | -| $`\overline{C_L}`$ | -2.4881 | -| $`\overline{C_D}`$ | 1.1040 | -| $`C'_L`$ | 0.3631 | -| $`C'_D`$ | 0.0993 | +These threshold values should be used as regime guides, not as tight one-point numeric targets. In the suppression curve from [Kan99b] shown above, the boundary is exactly the kind of place where a small solver difference can change the observed state. -The same work gives the most important trend targets for the broader campaign \[Kan99b\]. - -| Reynolds number | Expected trend | Practical target | -|:---|:---|---:| -| 60 | shedding suppressed near $`\alpha_L`$ | about 1.4 | -| 100 | shedding suppressed near $`\alpha_L`$ | about 1.8 | -| 160 | shedding suppressed near $`\alpha_L`$ | about 1.9 | -| 60 | low rotation mean lift slope | $`\overline{C_L} \approx -2.50\alpha`$ | -| 100 | low rotation mean lift slope | $`\overline{C_L} \approx -2.48\alpha`$ | -| 160 | low rotation mean lift slope | $`\overline{C_L} \approx -2.46\alpha`$ | - -The paper also reports that its outer boundary sensitivity is already very small at far field radius $`R_D=50`$, and that increasing the radius to $`R_D=100`$ changes the main outputs by less than about half a percent for the anchor case \[Kan99b\]. That result motivates an explicit boundary independence check in the present rectangular setup. - -## Solver setup for this campaign - -The campaign assumes the present code path and keeps all nonessential choices fixed. +## Fixed solver setup | Item | Setting | -|:---|:---| +|---|---| | Dimension | 2D | | Lattice | D2Q9 | | Streaming | double buffer | @@ -68,286 +56,192 @@ The campaign assumes the present code path and keeps all nonessential choices fi | Outlet | neq extrapolation | | LES | off | | Precision | FP32 | -| Cylinder diameter | $`D=30`$ lattice units | -| Cylinder radius | $`R=15`$ lattice units | +| Cylinder diameter | `D = 30` lattice units | +| Cylinder radius | `R = 15` lattice units | | Rotation input | update body omega only | -| Output cadence for force history | every 100 steps | -| Sensors | none | -| Flow diagnosis | saved flow fields and derived images | -This campaign evaluates SRT, TRT, and MRT separately. The same geometry, flow scales, and post processing rules should be used for all three collision models. +The baseline domain remains the current medium far field unless a later boundary sensitivity check shows otherwise. -## Parameter mapping in lattice units +## Inlet recommendation by collision model -The recommended inflow speed is +Kan99b is an open-flow validation, not a confined-channel benchmark. -``` math +| Collision | Recommended inlet | Secondary choice | Avoid as primary | +|---|---|---|---| +| SRT | `equilibrium` | `regularized` | `zou_he_local` | +| TRT | `regularized` | `equilibrium` | `zou_he_local` until the anchor is stable | +| MRT | `regularized` or `zou_he_local` | `equilibrium` | `channel_stabilized` | + +Keep one inlet family per collision model across the primary matrix. + +## Lattice-unit mapping + +Use + +\[ U_\infty = 0.03 -``` +\] -This keeps the Mach number low while leaving enough dynamic range for the force signals. +With `D = 30`, -With $`D=30`$, the required viscosity for each Reynolds number is - -``` math +\[ \nu = \frac{U_\infty D}{Re} = \frac{0.9}{Re} -``` +\] -which gives the following values. +| `Re` | `nu` | SRT equivalent `omega` | +|---|---:|---:| +| 60 | 0.015000 | 1.83486 | +| 100 | 0.009000 | 1.89753 | +| 160 | 0.005625 | 1.93470 | -| $`Re`$ | $`\nu`$ | SRT equivalent $`\omega = 1 \mathbin{/} (3\nu + 0.5)`$ | -|:-------|---------:|-------------------------------------------------------:| -| 60 | 0.015000 | 1.83486 | -| 100 | 0.009000 | 1.89753 | -| 160 | 0.005625 | 1.93470 | +The body rotation rate is -The body rotation rate in lattice units is - -``` math +\[ \omega_{body} = \frac{2 \alpha U_\infty}{D} = 0.002\alpha -``` +\] -which gives the following direct conversion table. +| `alpha` | body omega | +|---|---:| +| 0.5 | 0.0010 | +| 1.0 | 0.0020 | +| 1.6 | 0.0032 | +| 2.0 | 0.0040 | -| $`\alpha`$ | body omega | -|:-----------|-----------:| -| 0.0 | 0.0000 | -| 0.5 | 0.0010 | -| 1.0 | 0.0020 | -| 1.4 | 0.0028 | -| 1.8 | 0.0036 | -| 1.9 | 0.0038 | -| 2.0 | 0.0040 | +## Primary matrix -## Computational domain family +This is the recommended main validation set. -Because the current solver uses a rectangular box rather than the O type far field mesh of \[Kan99b\], boundary sensitivity must be checked explicitly. With free slip top and bottom boundaries, the lateral confinement error should be much weaker than with no slip walls, so a moderate family of domain sizes is sufficient for the first pass. +| Case | `Re` | `alpha` | Role | +|---|---:|---:|---| +| K1 | 100 | 0.5 | low-rotation lift trend check | +| K2 | 100 | 1.0 | strongest hard anchor | +| K3 | 60 | 1.6 | low-Re suppression classification | +| K4 | 100 | 2.0 | mid-Re suppression classification | +| K5 | 160 | 2.0 | high-Re suppression classification | -The recommended domain family is defined in cylinder diameters. +Optional baseline if needed for debugging or plots: -| Domain | Upstream distance | Downstream distance | Height | Integer grid for $`D=30`$ | -|:---|---:|---:|---:|---:| -| S | 12D | 24D | 16D | 1081 by 481 | -| M | 15D | 30D | 20D | 1351 by 601 | -| L | 20D | 40D | 24D | 1801 by 721 | +| Case | `Re` | `alpha` | Status | +|---|---:|---:|---| +| K0 | 100 | 0.0 | optional | -The corresponding cylinder centers are +This matrix covers: -| Domain | Cylinder center | -|:-------|:----------------| -| S | 360, 240 | -| M | 450, 300 | -| L | 600, 360 | +- one periodic low-rotation trend point +- one exact hard anchor with full force data +- suppression behavior at low, medium, and high Reynolds number -These sizes are measured from the cylinder center to the inlet, outlet, and slip boundaries. The baseline domain should be M. Domain S and L are used only to establish boundary independence. +## How to judge each case -## Test phases +### K1 -## Phase A +Use K1 to check the low-rotation lift law. -The first phase chooses a domain that is large enough for the rest of the campaign. +Target: -Run only the anchor case +\[ +\overline{C_L} \approx -2.48 \times 0.5 \approx -1.24 +\] -``` math -Re = 100, \qquad \alpha = 1.0 -``` +This is a trend check, not a strict fluctuation-amplitude benchmark. -with MRT on domains S, M, and L. +### K2 -For each run, compute +Use K2 as the hard benchmark case. -- $`\overline{C_D}`$ -- $`\overline{C_L}`$ -- $`C'_D`$ -- $`C'_L`$ -- $`St`$ +Preferred agreement band: -Pick the smallest domain for which the change from the next larger domain is small enough. +| Quantity | Preferred band | +|---|---:| +| `St` | within 3 percent | +| `mean C_L` | within 4 percent | +| `mean C_D` | within 5 percent | +| `C'_L` | within 8 percent | +| `C'_D` | within 10 percent | -| Quantity | Domain independence target | -|:-------------------|---------------------------:| -| $`St`$ | less than 1 percent | -| $`\overline{C_L}`$ | less than 2 percent | -| $`\overline{C_D}`$ | less than 2 percent | -| $`C'_L`$ | less than 3 percent | -| $`C'_D`$ | less than 3 percent | +### K3 to K5 -If M satisfies these limits against L, the rest of the campaign should use M. If not, use L. +Use K3 to K5 as suppression classification cases. -## Phase B +Primary success signature: -Once the domain is fixed, run the same anchor case with all three collision models. +- `C'_L` collapses toward zero in the final window +- no sustained alternating wake remains +- flow classification agrees with the expected suppressed regime -| Collision model | Required run | -|:----------------|:-----------------------| -| SRT | $`Re=100, \alpha=1.0`$ | -| TRT | $`Re=100, \alpha=1.0`$ | -| MRT | $`Re=100, \alpha=1.0`$ | +These are not exact threshold-fitting cases. Do not over-interpret a small residual fluctuation if the wake is otherwise clearly in the suppressed class. -Compare each run to the exact anchor values from \[Kan99b\]. +## Optional threshold bracket check -| Quantity | Preferred agreement band | -|:-------------------|-------------------------:| -| $`St`$ | within 3 percent | -| $`\overline{C_L}`$ | within 4 percent | -| $`\overline{C_D}`$ | within 5 percent | -| $`C'_L`$ | within 8 percent | -| $`C'_D`$ | within 10 percent | +If later you want a more explicit threshold study, use pairs around `alpha_L` rather than a single point on the boundary. -This phase also checks collision model consistency. SRT, TRT, and MRT should not disagree strongly if the moving wall and force extraction are implemented correctly. +Recommended pairs: -## Phase C +| `Re` | Lower point | Upper point | +|---|---:|---:| +| 60 | 1.3 | 1.5 | +| 100 | 1.7 | 1.9 | +| 160 | 1.8 | 2.0 | -After the anchor case is established, run the main rotation scan at $`Re=100`$ for all three collision models. +These should still be treated as regime-location checks, not hard force targets. -| Case | $`\alpha`$ | body omega | -|:-----|-----------:|-----------:| -| A0 | 0.0 | 0.0000 | -| A1 | 0.5 | 0.0010 | -| A2 | 1.0 | 0.0020 | -| A3 | 1.5 | 0.0030 | -| A4 | 1.7 | 0.0034 | -| A5 | 1.8 | 0.0036 | -| A6 | 1.9 | 0.0038 | -| A7 | 2.0 | 0.0040 | - -The expected signatures from the paper are \[Kan99b\]: - -- mean lift becomes more negative roughly linearly at low $`\alpha`$ -- mean drag decreases as $`\alpha`$ increases -- Strouhal number stays nearly constant while periodic shedding exists -- shedding disappears near $`\alpha \approx 1.8`$ - -The low rotation linear fit gives a strong consistency check. - -``` math -\overline{C_L} \approx -2.48\alpha \qquad Re=100 -``` - -## Phase D - -The final phase checks whether the code recovers the Reynolds number dependence of the suppression threshold. - -Run the following reduced sets for all three collision models. - -| Reynolds number | $`\alpha`$ values | -|:----------------|:----------------------------------| -| 60 | 0.0, 0.5, 1.0, 1.2, 1.4, 1.6 | -| 160 | 0.0, 0.5, 1.0, 1.5, 1.8, 1.9, 2.0 | - -The expected signatures are \[Kan99b\]: - -- $`Re=60`$ should lose periodic shedding near $`\alpha \approx 1.4`$ -- $`Re=160`$ should lose periodic shedding near $`\alpha \approx 1.9`$ -- the low rotation lift slopes should be about $`-2.50\alpha`$ at $`Re=60`$ and about $`-2.46\alpha`$ at $`Re=160`$ - -## Run length and output policy - -The lattice time step is very small, so dense output is unnecessary. Integrated quantities should be written every 100 steps. Full field dumps should be much less frequent. - -The recommended policy is - -| Output type | Cadence | -|:---|:---| -| force and torque history | every 100 steps | -| full field dump during warmup | every 2000 steps | -| full field dump during statistics window | every 1000 steps | -| final image export for report | selected representative times only | - -The minimum full field content is - -- density -- $`u_x`$ -- $`u_y`$ - -Derived images should be generated in post processing. - -| Image | Purpose | -|:---|:---| -| vorticity | identify alternating vortex shedding and its suppression | -| velocity magnitude | show wake width and accelerated side of the rotating cylinder | -| streamwise velocity | show wake recovery and reverse flow region | - -No sensors are required for this campaign. Flow images plus the integrated force history are sufficient. - -## Statistics window - -The force history should be split into a warmup window and a statistics window. Near the suppression threshold, transients decay slowly, so longer runs are needed. +## Run policy | Case type | Total steps | Warmup | Statistics | -|:---|---:|---:|---:| -| anchor case | 180000 to 220000 | first 40 percent | last 60 percent | -| near critical $`\alpha`$ | 220000 to 280000 | first 50 percent | last 50 percent | -| clearly periodic or clearly steady case | 140000 to 180000 | first 40 percent | last 60 percent | +|---|---:|---:|---:| +| K1 and K2 | 180000 to 220000 | first 40 percent | last 60 percent | +| K3 to K5 | 220000 to 280000 | first 50 percent | last 50 percent | -The final statistics window should contain at least 20 shedding periods whenever the case is periodic. +The final statistics window should contain at least 20 shedding periods whenever the case remains periodic. -## Post processing rules +## TRT re-entry rule -Compute the force coefficients from the saved force history using +Bring TRT back in this order: -``` math -C_D = \frac{2 F_x}{\rho U_\infty^2 D}, \qquad C_L = \frac{2 F_y}{\rho U_\infty^2 D} -``` +1. K2 only +2. if K2 is stable and credible, run K1 +3. only then run K3 to K5 -For periodic cases, compute +This prevents TRT from expanding the matrix before the hard anchor is trustworthy. -- $`\overline{C_D}`$ and $`\overline{C_L}`$ as time averages over the statistics window -- $`C'_D`$ and $`C'_L`$ as half peak to peak values over each period, then average those amplitudes over the statistics window -- $`St`$ from the dominant frequency of $`C_L`$ +## Deliverables -The preferred frequency estimate is FFT of $`C_L`$. A zero crossing estimate may be used as a cross check. +For each collision model, deliver: -A case should be classified as steady when both conditions hold: +- one table of run settings including collision, inlet scheme, wall type, `Re`, `alpha`, `nu`, and body omega +- one CSV per run with force history +- selected field images for wake classification +- one summary table with `mean C_D`, `mean C_L`, `C'_D`, `C'_L`, and `St` +- one short note stating whether suppression behavior matches [Kan99b] -- the last part of the $`C_L`$ signal no longer shows a sustained periodic component above noise level -- the vorticity images no longer show an alternating Karman street +## Recommended primary settings summary -A practical threshold for suppression is that the measured $`C'_L`$ in the final window falls below 5 percent of the $`\alpha=0`$ value at the same Reynolds number and collision model. +| Collision | Wall | Inlet | Status | +|---|---|---|---| +| SRT | free slip | `equilibrium` | primary | +| TRT | free slip | `regularized` | primary if K2 is stable | +| MRT | free slip | `regularized` or `zou_he_local` | primary | -## Deliverables for the coder +## MRT-only runner mapping -The coder should deliver the following for each completed phase. +The current executable entrypoint is `tests/run_kan99b_rotating_cylinder.py`, and this round uses MRT-only scheduling: -- one configuration table listing domain, collision model, Reynolds number, viscosity, $`\alpha`$, body omega, and total step count -- one CSV file per run with force history sampled every 100 steps -- one folder of selected field snapshots -- one post processing table with $`\overline{C_D}`$, $`\overline{C_L}`$, $`C'_D`$, $`C'_L`$, and $`St`$ -- one summary plot of $`\overline{C_L}`$ versus $`\alpha`$ -- one summary plot of $`\overline{C_D}`$ versus $`\alpha`$ -- one summary plot of $`C'_L`$ versus $`\alpha`$ -- one summary plot of $`St`$ versus $`\alpha`$ -- one domain sensitivity comparison for the anchor case -- one short note stating whether shedding suppression occurs near the expected $`\alpha_L`$ - -Executable driver for this deliverable set: - -- [tests/run_kan99b_rotating_cylinder.py](tests/run_kan99b_rotating_cylinder.py) +- primary matrix is `K1-K5` with `MRT + regularized` inlet +- one extra control run is added at K2 with `MRT + zou_he_local` +- all runs keep `uniform` inlet profile, `free_slip` y-wall, `neq_extrap` outlet +- output rows include `case_id`, `variant`, `collision`, `inlet_scheme`, `grid`, `steps`, `burn_in`, `St`, `St_error_pct` (for K2), and force metrics +- K2 gate uses this document's per-metric tolerances for `St`, `mean C_L`, `mean C_D`, `C'_L`, `C'_D` Example commands: ```bash -conda run -n pycuda_3_10 python tests/run_kan99b_rotating_cylinder.py --phase a -conda run -n pycuda_3_10 python tests/run_kan99b_rotating_cylinder.py --phase b --domain M -conda run -n pycuda_3_10 python tests/run_kan99b_rotating_cylinder.py --phase all --minimal --domain M +conda run -n pycuda_3_10 python tests/run_kan99b_rotating_cylinder.py \ + --json-out tests/output/kan99b_validation/summary_runs.json + +conda run -n pycuda_3_10 python tests/run_kan99b_rotating_cylinder.py \ + --case K2 --save-vorticity ``` -## Minimum run set +## Reference -If compute budget is tight, the minimum useful run set is the following. - -| Phase | Runs | -|:---|:---| -| domain choice | MRT on S, M, L for $`Re=100, \alpha=1.0`$ | -| anchor comparison | SRT, TRT, MRT on the chosen domain for $`Re=100, \alpha=1.0`$ | -| main trend check | SRT, TRT, MRT on the chosen domain for $`Re=100`$ with $`\alpha = 0.0, 1.0, 1.5, 1.8, 2.0`$ | -| threshold check | SRT, TRT, MRT on the chosen domain for $`Re=60, \alpha=1.4`$ and $`Re=160, \alpha=1.9`$ | - -This reduced set is enough to answer the main validation question. A larger scan can be added only after the anchor and threshold cases behave correctly. - ---- - -## References - -\[Kan99b\] S. Kang, H. Choi, and S. Lee, “Laminar flow past a rotating circular cylinder,” Oct. 07, 1999. doi: [10.1063/1.870190](https://doi.org/10.1063/1.870190). +[Kan99b] S. Kang, H. Choi, and S. Lee, “Laminar flow past a rotating circular cylinder,” 1999. diff --git a/tests/Sah04_validation.md b/tests/Sah04_validation.md index a1437b1..4d1c9a1 100644 --- a/tests/Sah04_validation.md +++ b/tests/Sah04_validation.md @@ -1,147 +1,243 @@ -# Sah04 St validation matrix +# Sah04 confined-cylinder validation against [Sah04] -##### [**Undermind**](https://undermind.ai) +## Goal ---- +This validation should use a small set of direct periodic anchors from [Sah04], plus a small secondary block for near-onset flow-state checks. -## Sah04 St validation matrix +The main design rules are: -A Strouhal based matrix should lean on cases where \[Sah04\] gives an explicit frequency target, or a target that can be derived directly from a stated shedding period. With cylinder diameter fixed at D = 30, that rules out a strict low to mid to high blockage Cartesian grid if every cell is meant to carry a hard 5 percent St gate. The paper simply does not publish a dense 3 by 3 table of supercritical confined flow frequencies. The most defensible 27 run plan is therefore a nine case matrix chosen to maximize paper anchored St targets, then run with SRT, TRT, and MRT on each case. +- do not use interpolated values from figures as hard targets +- do not use points on or extremely near critical curves as primary pass fail anchors +- report realized blockage and realized Reynolds number, not just nominal inputs +- use finer grids for high blockage so the narrow wall gaps are not under-resolved -## Fixed setup +This keeps the primary matrix small but still representative across moderate and high confinement. -- Geometry follows \[Sah04\] -- Upstream length is 40D -- Downstream length is 40D -- Total fluid length is 80D -- With D = 30, use nx = 80D + 2 = 2402 -- Cylinder radius is 15 -- Cylinder x center is 40D + 0.5 = 1200.5 -- Inlet profile is parabolic -- Reynolds number is defined with U_max and D -- Strouhal number is defined as St = fD U_max^-1 -- For all cases, keep U_max fixed and set viscosity from Re = U_max D nu^-1 +## What counts as a hard benchmark from [Sah04] -## Blockage levels +The strongest periodic-flow anchors are the direct DNS values stated in the paper for developed unsteady states. -The table below gives the three blockage levels to use with D fixed at 30. +| Case | Blockage `beta` | Reynolds number | `St` target | Why it is a hard anchor | +|---|---:|---:|---:|---| +| S1 | 0.3 | 100 | 0.2115 | direct periodic DNS value | +| S2 | 0.5 | 200 | 0.3513 | direct periodic DNS value | +| S3 | 0.8 | 160 | `T ≈ 1.806`, so `St ≈ 0.5537` | direct case given in the paper | +| S4 | 0.9 | 200 | 0.5314 | direct periodic DNS value | -| Blockage tier | Nominal beta | H | ny | Realized beta | Cylinder center | -|:--------------|-------------:|----:|----:|--------------:|:----------------| -| Low | 0.5 | 60 | 62 | 0.5000 | 1200.5, 30.5 | -| Mid | 0.8 | 38 | 40 | 0.7895 | 1200.5, 19.5 | -| High | 0.9 | 33 | 35 | 0.9091 | 1200.5, 17.0 | +These should be the primary Sah04 validation anchors. -The low blockage tier is exact on the D = 30 grid. The two higher blockage tiers use the nearest integer channel heights. This keeps the matrix practical while staying close to the \[Sah04\] confined flow regime where supercritical St values are actually reported. +## What should not be a hard target -## Nine Sah04 cases +The following are useful for qualitative or secondary checks, but not for the main validation gate: -Run every row below with SRT, TRT, and MRT. That gives 27 total runs. +- critical onset values from Table IV +- any `St` or force value obtained by reading or interpolating a plot +- cases chosen only to complete a rectangular parameter grid +- points too close to the codimension two region or nearby neutral stability boundaries -| Case | Blockage tier | Re | Target St | Target class | -|:-----|:--------------|-------:|----------:|:-----------------------------------------| -| 1 | Low beta 0.5 | 124.09 | 0.3393 | Direct from Table IV | -| 2 | Low beta 0.5 | 160 | 0.3450 | Interpolated between Table IV and Re 200 | -| 3 | Low beta 0.5 | 200 | 0.3513 | Direct from Section IV.B | -| 4 | Mid beta 0.8 | 110.24 | 0.5363 | Direct from Table IV | -| 5 | Mid beta 0.8 | 160 | 0.5537 | Derived from stated period T ≈ 1.806 | -| 6 | Mid beta 0.8 | 200 | 0.5510 | Derived from stated period T ≈ 1.815 | -| 7 | High beta 0.9 | 162.82 | 0.5202 | Direct from Table IV | -| 8 | High beta 0.9 | 180 | 0.5254 | Interpolated between Table IV and Re 200 | -| 9 | High beta 0.9 | 200 | 0.5314 | Direct from Section IV.B | +This matters because the paper's stability map has several sensitive regions, especially at high blockage and near symmetry breaking. In the stability figure from [Sah04] shown above, those boundaries are exactly where a small setup difference can change the observed state. -## How to use the targets +## Geometry and blockage mapping -The nine cases are not equal in evidential weight. +Keep the confined-channel layout and no-slip walls. -- Hard pass fail cases +The validation table must always report both nominal and realized blockage. - - Case 1 - - Case 3 - - Case 4 - - Case 5 - - Case 6 - - Case 7 - - Case 9 +With `D = 30` lattice units, the recommended realizations are: - These have targets stated directly in \[Sah04\], or obtained directly from a shedding period stated in the paper. A 5 percent St gate is reasonable here. +| Case | `beta_nominal` | Suggested `H` | `beta_real` | Notes | +|---|---:|---:|---:|---| +| S1 | 0.3 | 100 | 0.3000 | exact | +| S2 | 0.5 | 60 | 0.5000 | exact | +| S3 | 0.8 | 38 or 37 | 0.7895 or 0.8108 | pick one and report it explicitly | +| S4 | 0.9 | 33 | 0.9091 | use this, not `H = 35` | -- Soft trend cases +Do not silently rename `beta_real` as the paper blockage. - - Case 2 - - Case 8 +## Grid density policy - These are useful to see whether each collision model follows the same St trend between two paper anchored points. They should not carry the same weight as the hard cases. +High blockage cases need more wall-normal resolution than the base grid. -## Recommended run settings +Minimum rule: -Use one sampling and averaging policy across the whole matrix so the comparisons stay clean. +- for `beta < 0.8`, the baseline grid with `D = 30` is acceptable for the first pass +- for `beta >= 0.8`, increase grid density by at least 2 times in each spatial direction before treating the result as validation quality -- D = 30 for all runs -- record_every = 5 -- double buffer streaming -- same inlet formulation and force extraction for all collisions -- same FFT windowing and same St extraction routine for all runs +A practical way to do this is to keep geometry similarity while doubling the characteristic resolution: - The automated runner ``tests/run_sah04_st_matrix.py`` uses a band around the - paper target shedding frequency ``f0 = St_target * U_max / D`` and reports - **two** Strouhal numbers: **raw** (plain band-limited dominant peak) and - **guided** (same band with a mild Gaussian weight toward ``f0`` to reduce - harmonic ambiguity in narrow channels). The **5% / 10%** hard-case rules - apply to **guided** ``St``; **raw** is for diagnosing pick ambiguity (e.g. - supercritical mid/high cases). +- base cases: `D = 30` +- high blockage validation cases: at least `D = 60` -For the lower Re onset cases, the frequency estimate is more sensitive to burn in and window length. A conservative default is: +Recommended high blockage realizations on the refined grid: -- Cases 1, 4, 7 +| Case | `beta_nominal` | Suggested refined `D` | Suggested refined `H` | `beta_real` | +|---|---:|---:|---:|---:| +| S3 | 0.8 | 60 | 75 | 0.8000 | +| S4 | 0.9 | 60 | 67 | 0.8955 | - - 80k total steps - - 30k burn +The point of this refinement is not only bulk accuracy. It is also to resolve the narrow cylinder wall gaps and reduce the risk that blockage effects are dominated by lattice geometry error. -- Cases 2, 3, 5, 6, 8, 9 +## Primary matrix - - 60k total steps - - 20k burn +This is the main Sah04 validation set. -If TRT is visibly noisier on the onset cases, extend TRT alone to the longer window rather than changing the matrix. +| Case | `beta_nominal` | Primary target | Role | +|---|---:|---|---| +| S1 | 0.3 | `Re = 100`, `St = 0.2115` | moderate blockage periodic anchor | +| S2 | 0.5 | `Re = 200`, `St = 0.3513` | medium blockage periodic anchor | +| S3 | 0.8 | `Re = 160`, `St ≈ 0.5537` | high blockage periodic anchor | +| S4 | 0.9 | `Re = 200`, `St = 0.5314` | very high blockage periodic anchor | -**Runner (repo):** ``tests/run_sah04_st_matrix.py`` +This matrix is smaller than the older grid, but it covers: -- Full MRT sweep: ``conda run -n pycuda_3_10 python tests/run_sah04_st_matrix.py --collision MRT`` -- All collisions (27 runs): ``... --collision all`` -- Quick wiring check: add ``--smoke`` (short steps; St only qualitative) -- Single case: ``--case 4`` ; JSON: ``--json-out path.json`` -- Override length: ``--steps`` / ``--burn`` (ignored with ``--smoke``) -- Long archive: ``--dump-npz-dir DIR`` writes ``case{id}_{COLL}.npz`` (lift, drag samples, LBM step index per sample, post-burn ``freqs_hz`` / power, band mask, guided Gaussian weight) plus ``.meta.json`` +- moderate confinement +- stronger confinement +- high blockage periodic shedding +- very high blockage periodic shedding -## Long runs (cases 6 and 9) +## Secondary onset block -For spectrum stability checks (not the 9-case matrix gate), a **minimal** set is six runs: MRT/TRT/SRT × case 6 and case 9. Suggested first budget: **steps 200000**, **burn 80000**, ``record_every=5`` (same as matrix), with ``--dump-npz-dir`` so raw vs guided can be recomputed offline from ``power_post_burn`` and the saved band weights. +These cases are recommended as flow-state checks, not hard `St` benchmarks. + +| Case | `beta_nominal` | Suggested `Re` | Why it is useful | How to judge it | +|---|---:|---:|---|---| +| SO1 | 0.5 | about 130 | safely above first onset without sitting on the boundary | confirm sustained periodic state | +| SO2 | 0.7 | about 120 | tests near-onset behavior in a more sensitive blockage range | confirm sustained periodic state | + +These points exist to answer a different question from the primary matrix: + +- does the solver enter and maintain the right flow regime once slightly above onset + +For SO1 and SO2, judge by: + +- persistent nonzero `C'_L` +- a clean dominant spectral peak +- repeatable periodic wake structure + +Do not fail these runs because the measured `St` differs slightly from a value read from a nearby figure. + +## Inlet and wall policy + +Sah04 is a confined-channel benchmark, so inlet consistency matters more than inlet variety. + +| Collision | Wall | Inlet | Status | +|---|---|---|---| +| SRT | no slip | `channel_stabilized` | primary | +| TRT | no slip | `channel_stabilized` | primary | +| MRT | no slip | `channel_stabilized` | primary | + +Keep the inlet family fixed across collision models in the primary matrix. + +Secondary inlet comparison, only after the primary set is working: + +| Collision | Optional inlet | Status | +|---|---|---| +| MRT | `regularized` or `zou_he_local` | exploratory | +| SRT or TRT | `equilibrium` or `regularized` | exploratory | + +## Realized Reynolds number check + +This is mandatory for Sah04. + +For each run, record: + +- nominal inlet definition +- developed downstream velocity profile +- measured `U_max,real` +- measured bulk velocity if available +- `beta_nominal` +- `beta_real` +- `Re_nominal` +- `Re_real` + +Use the paper-consistent label + +\[ +Re_{real} = \frac{U_{max,real} D}{\nu} +\] + +for the final comparison table. + +If `Re_real` drifts materially from the intended target, treat that as a setup problem before treating it as a Strouhal miss. + +## Run policy + +| Case block | Total steps | Burn | Statistics | +|---|---:|---:|---:| +| S1 and S2 | 100000 to 160000 | first 35 to 40 percent | last 60 to 65 percent | +| S3 and S4 | 180000 to 260000 | first 45 percent | last 55 percent | +| SO1 and SO2 | 140000 to 220000 | first 45 percent | last 55 percent | + +For high blockage refined runs, prefer the longer end of the window. ## Evaluation rule -Judge each collision model on the hard cases first. +Use a two-layer rule. -- Primary rule +### Primary periodic anchors - - At least 5 of the 7 hard cases within 5 percent in St - - No hard case worse than 10 percent +| Case | Hard target use | +|---|---| +| S1 to S4 | hard periodic benchmark anchors | -- Secondary rule +Preferred agreement band: - - Cases 2 and 8 should lie between their neighboring hard target values in the correct order - - SRT, TRT, and MRT should preserve the same beta and Re trend even when their absolute errors differ slightly +- within 5 percent when `Re_real` is close to target and the spectrum is clean +- within 10 percent still acceptable if the run is clearly periodic and the residual mismatch is explainable by `Re_real` drift or geometry realization -## Why this matrix and not a strict Cartesian 3 by 3 +### Secondary onset block -\[Sah04\] gives systematic confined flow St targets at the onset of periodic shedding in Table IV, plus a small number of direct supercritical DNS values in Section IV.B. It does not provide a full Cartesian table of supercritical St values for low, mid, and high blockage at three common Reynolds numbers. The matrix above is therefore built to maximize direct paper anchors rather than to force a mathematically neat but weakly supported grid. +| Case | Hard target use | +|---|---| +| SO1 and SO2 | no hard `St` gate | -## Exact beta alternative +Success means: -If exact blockage ratios on the D = 30 grid matter more than supercritical St anchor density, use beta = 0.1, 0.3, and 0.5 instead. That gives exact channel heights H = 300, 100, and 60. The tradeoff is that \[Sah04\] supplies far fewer direct supercritical St targets in that lower blockage band, so the resulting matrix is less suitable for a clean 5 percent St gate. +- the flow is clearly unsteady and periodic +- the dominant frequency is stable over long windows +- the wake classification is consistent with being above onset ---- +## Deliverables -## References +For each run, deliver: -\[Sah04\] M. Sahin and R. G. Owens, “A numerical investigation of wall effects up to high blockage ratios on two-dimensional flow past a confined circular cylinder,” Apr. 02, 2004. doi: [10.1063/1.1668285](https://doi.org/10.1063/1.1668285). +- one row with `beta_nominal`, `beta_real`, `Re_nominal`, `Re_real`, `nu`, collision, wall type, inlet scheme, and grid resolution +- one downstream velocity-profile plot +- one force-history CSV +- one `St` estimate with the exact analysis window stated +- selected wake images for flow classification + +## Recommended minimum set + +If compute budget is tight, run this order first: + +| Priority | Runs | +|---|---| +| 1 | MRT on S1 to S4 | +| 2 | SRT on S2 and S4 | +| 3 | TRT on S2 and S4 | +| 4 | SO1 and SO2 only after the primary anchors are behaving | + +## MRT-only runner mapping + +The current executable entrypoint is `tests/run_sah04_st_matrix.py`, and it is now aligned to this document's primary S1-S4 matrix: + +- collision is fixed to `MRT` +- inlet is fixed to `parabolic + channel_stabilized` +- case set is `S1-S4` only +- output rows include `case_id`, `collision`, `inlet_scheme`, `grid`, `steps`, `burn_in`, `St`, `St_error_pct`, `Re_real`, `beta_real` +- default hard gate is 5% (`--gate-pct` can relax it to 10%) + +Example commands: + +```bash +conda run -n pycuda_3_10 python tests/run_sah04_st_matrix.py \ + --json-out tests/output/sah04_mrt/summary.json + +conda run -n pycuda_3_10 python tests/run_sah04_st_matrix.py \ + --case S3 --gate-pct 10 --final-vorticity-dir tests/output/sah04_mrt/vorticity +``` + +## Reference + +[Sah04] M. Sahin and R. G. Owens, “A numerical investigation of wall effects up to high blockage ratios on two-dimensional flow past a confined circular cylinder,” 2004. diff --git a/tests/exp_ctrl_matrix.md b/tests/exp_ctrl_matrix.md new file mode 100644 index 0000000..942c18a --- /dev/null +++ b/tests/exp_ctrl_matrix.md @@ -0,0 +1,181 @@ +229:无控制: +``` +SIGNAL_FEATURES0 = { + 'action1': { + 'mean': 0.0, + 'components': [ + (0.1354, 0.0, 1.600), + ] + }, + 'action2': { + 'mean': -0.0, + 'components': [ + (0.1354, 0.0, 2.099), + ] + }, + 'action3': { + 'mean': 0.0, + 'components': [ + (0.1354, 0.0, 1.639), + ] + } +} +``` +234:隐身: +``` +SIGNAL_FEATURES1 = { + 'action1': { + 'mean': 0.0, + 'components': [ + (0.1354, 0.0, 1.600), + ] + }, + 'action2': { + 'mean': -0.01806, + 'components': [ + (0.1354, 0.0, 2.099), + ] + }, + 'action3': { + 'mean': 0.01806, + 'components': [ + (0.1354, 0.0, 1.639), + ] + } +} +``` +欺骗: +237 238 253: +``` +SIGNAL_FEATURES2 = { + 'action1': { + 'mean': 0.0, + 'components': [ + (0.1354, 0.0026, 1.600), # 主频 + ] + }, + 'action2': { + 'mean': -0.008730, + 'components': [ + (0.1354, 0.0045, 2.099), # 主频 + (0.2708, 0.0010, 0.612), + ] + }, + 'action3': { + 'mean': 0.008730, + 'components': [ + (0.1354, 0.0045, 1.639), # 主频 + (0.2708, 0.0010, -2.962), + ] + } +} +``` +257: +``` +SIGNAL_FEATURES3 = { + 'action1': { + 'mean': 0.0, + 'components': [ + (0.1354, 0.0029, -2.619), # 主频 + (0.2708, 0.0008, 2.856), + ] + }, + 'action2': { + 'mean': -0.0140, + 'components': [ + (0.1354, 0.0050, -0.933), # 主频 + (0.2708, 0.0010, 0.801), + (0.1806, 0.0003, 1.854), + ] + }, + 'action3': { + 'mean': 0.014, + 'components': [ + (0.1354, 0.0050, -1.398), # 主频 + (0.2708, 0.0010, 2.208), + (0.1806, 0.0003, 1.810), + ] + } +} +``` +260 (f*1.5): +``` +SIGNAL_FEATURES4 = { + 'action1': { + 'mean': 0.0, + 'components': [ + (0.2031, 0.0026, 1.600), # 主频 + ] + }, + 'action2': { + 'mean': -0.008730, + 'components': [ + (0.2031, 0.0045, 2.099), # 主频 + (0.4062, 0.0010, 0.612), + ] + }, + 'action3': { + 'mean': 0.008730, + 'components': [ + (0.2031, 0.0045, 1.639), # 主频 + (0.4062, 0.0010, -2.962), + ] + } +} +``` +262: (f*1.5): +``` +SIGNAL_FEATURES5 = { + 'action1': { + 'mean': 0.0, + 'components': [ + (0.2031, 0.0029, -2.619), # 主频 + (0.4062, 0.0008, 2.856), + ] + }, + 'action2': { + 'mean': -0.0140, + 'components': [ + (0.2031, 0.0050, -0.933), # 主频 + (0.4062, 0.0010, 0.801), + (0.2709, 0.0003, 1.854), + ] + }, + 'action3': { + 'mean': 0.014, + 'components': [ + (0.2031, 0.0050, -1.398), # 主频 + (0.4062, 0.0010, 2.208), + (0.2709, 0.0003, 1.810), + ] + } +} +``` +270 (f*2): +``` +SIGNAL_FEATURES6 = { + 'action1': { + 'mean': 0.0, + 'components': [ + (0.2708, 0.0044, -2.619), # 主频 + (0.8124, 0.0012, 2.856), + ] + }, + 'action2': { + 'mean': -0.014, + 'components': [ + (0.2708, 0.0075, -0.933), # 主频 + (0.8124, 0.0015, 0.801), + (0.5418, 0.0005, 1.854), + ] + }, + 'action3': { + 'mean': 0.014, + 'components': [ + (0.2708, 0.0075, -1.398), # 主频 + (0.8124, 0.0015, 2.208), + (0.5418, 0.0005, 1.810), + ] + } +} +``` \ No newline at end of file diff --git a/tests/experiment.ipynb b/tests/experiment.ipynb index 9351d06..040f39c 100644 --- a/tests/experiment.ipynb +++ b/tests/experiment.ipynb @@ -1,872 +1,5 @@ { "cells": [ - { - "cell_type": "code", - "execution_count": 1, - "id": "b2533afe", - "metadata": {}, - "outputs": [], - "source": [ - "# ==================== 1. 配置参数 ====================\n", - "import time\n", - "import math\n", - "\n", - "# 控制间隔 (秒)\n", - "DT = 0.443 # 每0.5秒生成一次信号\n", - "\n", - "# 缓起时间 (秒)\n", - "RAMP_TIME = 5\n", - "\n", - "# 初始定常信号值 (m/s)\n", - "INITIAL_VALUES = [0.0, 0.0, 0.0] # [action1, action2, action3]\n", - "\n", - "# 信号特征 (从FFT分析提取)\n", - "# 格式: {'mean': 均值, 'components': [(频率Hz, 幅值, 相位rad), ...]}\n", - "SIGNAL_FEATURES = {\n", - " 'action1': {\n", - " 'mean': 0.0,\n", - " 'components': [\n", - " (0.2708, 0.0044, -2.619), # 主频\n", - " (0.8124, 0.0012, 2.856),\n", - " ]\n", - " },\n", - " 'action2': {\n", - " 'mean': -0.014,\n", - " 'components': [\n", - " (0.2708, 0.0075, -0.933), # 主频\n", - " (0.8124, 0.0015, 0.801),\n", - " (0.5418, 0.0005, 1.854),\n", - " ]\n", - " },\n", - " 'action3': {\n", - " 'mean': 0.014,\n", - " 'components': [\n", - " (0.2708, 0.0075, -1.398), # 主频\n", - " (0.8124, 0.0015, 2.208),\n", - " (0.5418, 0.0005, 1.810),\n", - " ]\n", - " }\n", - "}\n", - "\n", - "# ==================== 2. 信号生成函数 ====================\n", - "\n", - "def generate_signal(t, feature):\n", - " \"\"\"\n", - " 生成单个周期信号\n", - " t: 时间(秒)\n", - " feature: 信号特征字典\n", - " \"\"\"\n", - " value = feature['mean']\n", - " for freq, amp, phase in feature['components']:\n", - " value += amp * math.cos(2 * math.pi * freq * t + phase)\n", - " return value\n", - "\n", - "\n", - "def generate_all_signals(t, ramp_factor=1.0):\n", - " \"\"\"\n", - " 生成三个信号,带缓起功能\n", - " \n", - " 参数:\n", - " t: 当前时间(秒)\n", - " ramp_factor: 缓起系数 0~1 (0=初始定常, 1=完全周期)\n", - " \n", - " 返回:\n", - " (action1, action2, action3) 三个信号值\n", - " \"\"\"\n", - " # 生成周期信号\n", - " sig1 = generate_signal(t, SIGNAL_FEATURES['action1'])\n", - " sig2 = generate_signal(t, SIGNAL_FEATURES['action2'])\n", - " sig3 = generate_signal(t, SIGNAL_FEATURES['action3'])\n", - " \n", - " # 缓起混合: output = initial * (1-ramp) + periodic * ramp\n", - " action1 = INITIAL_VALUES[0] * (1 - ramp_factor) + sig1 * ramp_factor\n", - " action2 = INITIAL_VALUES[1] * (1 - ramp_factor) + sig2 * ramp_factor\n", - " action3 = INITIAL_VALUES[2] * (1 - ramp_factor) + sig3 * ramp_factor\n", - " \n", - " return action1, action2, action3\n", - "\n", - "\n", - "def get_ramp_factor(elapsed_time):\n", - " \"\"\"\n", - " 计算缓起系数 (平滑S曲线)\n", - " elapsed_time: 从启动开始经过的时间(秒)\n", - " \"\"\"\n", - " if elapsed_time >= RAMP_TIME:\n", - " return 1.0\n", - " elif elapsed_time <= 0:\n", - " return 0.0\n", - " else:\n", - " # 平滑S曲线: 0.5 * (1 - cos(pi * t / T))\n", - " x = elapsed_time / RAMP_TIME\n", - " return 0.5 * (1 - math.cos(math.pi * x))" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "id": "1c3da26c", - "metadata": {}, - "outputs": [], - "source": [ - "# ==================== 4. 主控制循环 ====================\n", - "from drv_encodermotor import EncoderMotorDriver, NORMAL_MODE, SPEED_MODE, POSITION_MODE\n", - "def run_control_loop(duration=30.0):\n", - " t_start = time.time() # 记录启动时间\n", - " t_next = t_start # 下次执行时间\n", - " step = 0\n", - " \n", - " try:\n", - " while True:\n", - " t_now = time.time()\n", - " elapsed = t_now - t_start\n", - " \n", - " # 检查是否到达运行时长\n", - " if duration is not None and elapsed >= duration:\n", - " print(f\"\\n运行完成, 共 {step} 步\")\n", - " break\n", - " \n", - " # 检查是否到达下次执行时间\n", - " if t_now >= t_next:\n", - " # 计算缓起系数\n", - " ramp = get_ramp_factor(elapsed)\n", - " \n", - " # 生成信号\n", - " a1, a2, a3 = generate_all_signals(elapsed, ramp)\n", - " \n", - " # 发送到设备\n", - " with EncoderMotorDriver(bus=1) as driver:\n", - " driver.set_linear_speed_m_s(3, a1)\n", - " time.sleep(0.1)\n", - " driver.set_linear_speed_m_s(2, a3)\n", - " time.sleep(0.1)\n", - " driver.set_linear_speed_m_s(0, a2)\n", - " print(\"a1: {:.5f}, a2: {:.5f}, a3: {:.5f}\".format(a1, a2, a3))\n", - " step += 1\n", - " t_next += DT\n", - " \n", - " # 短暂休眠以减少CPU占用\n", - " time.sleep(0.001)\n", - " \n", - " except KeyboardInterrupt:\n", - " print(f\"\\n用户中断, 共运行 {step} 步, {elapsed:.2f} 秒\")" - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "id": "7fa42c68", - "metadata": {}, - "outputs": [], - "source": [ - "from drv_encodermotor import EncoderMotorDriver, NORMAL_MODE, SPEED_MODE, POSITION_MODE\n", - "with EncoderMotorDriver(bus=1) as driver:\n", - " driver.set_mode(3, SPEED_MODE)\n", - " driver.set_mode(2, SPEED_MODE)\n", - " driver.set_mode(0, SPEED_MODE)\n", - " driver.set_speed_pid(3, kp=1, ki=100, kd=1)\n", - " driver.set_speed_pid(2, kp=1, ki=100, kd=1)\n", - " driver.set_speed_pid(0, kp=1, ki=100, kd=1)\n", - " driver.set_soft_start_and_stop(3, True)\n", - " driver.set_soft_start_and_stop(2, True)\n", - " driver.set_soft_start_and_stop(0, True)" - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "id": "0d393152", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "a1: -0.00000, a2: -0.00000, a3: 0.00000\n", - "a1: -0.00002, a2: -0.00017, a3: 0.00037\n", - "a1: 0.00019, a2: -0.00051, a3: 0.00173\n", - "a1: 0.00048, a2: -0.00186, a3: 0.00296\n", - "a1: 0.00140, a2: -0.00532, a3: 0.00392\n", - "a1: 0.00072, a2: -0.00835, a3: 0.00369\n", - "a1: -0.00129, a2: -0.01162, a3: 0.00271\n", - "a1: -0.00186, a2: -0.01171, a3: 0.00682\n", - "a1: -0.00390, a2: -0.00771, a3: 0.01104\n", - "a1: -0.00244, a2: -0.00783, a3: 0.01529\n", - "a1: 0.00197, a2: -0.00704, a3: 0.02194\n", - "a1: 0.00257, a2: -0.00868, a3: 0.02001\n", - "a1: 0.00453, a2: -0.01711, a3: 0.01506\n", - "a1: 0.00354, a2: -0.02027, a3: 0.01114\n", - "a1: -0.00155, a2: -0.02088, a3: 0.00532\n", - "a1: -0.00253, a2: -0.01944, a3: 0.00776\n", - "a1: -0.00412, a2: -0.01155, a3: 0.01305\n", - "a1: -0.00423, a2: -0.00851, a3: 0.01526\n", - "a1: 0.00089, a2: -0.00807, a3: 0.02125\n", - "a1: 0.00254, a2: -0.00712, a3: 0.02187\n", - "a1: 0.00369, a2: -0.01433, a3: 0.01632\n", - "a1: 0.00471, a2: -0.02001, a3: 0.01285\n", - "a1: -0.00008, a2: -0.02049, a3: 0.00686\n", - "a1: -0.00255, a2: -0.02080, a3: 0.00585\n", - "a1: -0.00327, a2: -0.01429, a3: 0.01182\n", - "a1: -0.00497, a2: -0.00864, a3: 0.01426\n", - "a1: -0.00083, a2: -0.00861, a3: 0.01911\n", - "a1: 0.00250, a2: -0.00676, a3: 0.02288\n", - "a1: 0.00294, a2: -0.01126, a3: 0.01804\n", - "a1: 0.00500, a2: -0.01903, a3: 0.01406\n", - "a1: 0.00181, a2: -0.02031, a3: 0.00904\n", - "a1: -0.00233, a2: -0.02114, a3: 0.00494\n", - "a1: -0.00270, a2: -0.01717, a3: 0.00992\n", - "a1: -0.00483, a2: -0.00961, a3: 0.01369\n", - "a1: -0.00274, a2: -0.00867, a3: 0.01690\n", - "a1: 0.00202, a2: -0.00728, a3: 0.02265\n", - "a1: 0.00257, a2: -0.00864, a3: 0.02009\n", - "a1: 0.00452, a2: -0.01706, a3: 0.01508\n", - "a1: 0.00358, a2: -0.02027, a3: 0.01118\n", - "a1: -0.00153, a2: -0.02087, a3: 0.00534\n", - "a1: -0.00253, a2: -0.01949, a3: 0.00770\n", - "a1: -0.00410, a2: -0.01161, a3: 0.01303\n", - "a1: -0.00425, a2: -0.00851, a3: 0.01522\n", - "a1: 0.00086, a2: -0.00808, a3: 0.02122\n", - "a1: 0.00254, a2: -0.00710, a3: 0.02190\n", - "a1: 0.00366, a2: -0.01425, a3: 0.01635\n", - "a1: 0.00473, a2: -0.02000, a3: 0.01288\n", - "a1: -0.00004, a2: -0.02048, a3: 0.00691\n", - "a1: -0.00255, a2: -0.02081, a3: 0.00582\n", - "a1: -0.00326, a2: -0.01436, a3: 0.01179\n", - "a1: -0.00498, a2: -0.00866, a3: 0.01424\n", - "a1: -0.00088, a2: -0.00861, a3: 0.01905\n", - "a1: 0.00249, a2: -0.00676, a3: 0.02289\n", - "a1: 0.00293, a2: -0.01119, a3: 0.01809\n", - "a1: 0.00500, a2: -0.01901, a3: 0.01407\n", - "a1: 0.00185, a2: -0.02030, a3: 0.00909\n", - "a1: -0.00232, a2: -0.02114, a3: 0.00494\n", - "a1: -0.00270, a2: -0.01722, a3: 0.00989\n", - "a1: -0.00482, a2: -0.00965, a3: 0.01368\n", - "a1: -0.00279, a2: -0.00867, a3: 0.01685\n", - "a1: 0.00200, a2: -0.00729, a3: 0.02264\n", - "a1: 0.00257, a2: -0.00860, a3: 0.02013\n", - "a1: 0.00450, a2: -0.01701, a3: 0.01510\n", - "a1: 0.00362, a2: -0.02028, a3: 0.01123\n", - "a1: -0.00150, a2: -0.02085, a3: 0.00537\n", - "a1: -0.00253, a2: -0.01952, a3: 0.00767\n", - "a1: -0.00409, a2: -0.01166, a3: 0.01301\n", - "a1: -0.00428, a2: -0.00851, a3: 0.01519\n", - "a1: 0.00083, a2: -0.00809, a3: 0.02118\n", - "a1: 0.00254, a2: -0.00707, a3: 0.02195\n", - "a1: 0.00365, a2: -0.01420, a3: 0.01638\n", - "a1: 0.00475, a2: -0.02000, a3: 0.01290\n", - "a1: -0.00000, a2: -0.02047, a3: 0.00696\n", - "a1: -0.00255, a2: -0.02082, a3: 0.00580\n", - "a1: -0.00324, a2: -0.01441, a3: 0.01176\n", - "a1: -0.00498, a2: -0.00868, a3: 0.01421\n", - "a1: -0.00092, a2: -0.00862, a3: 0.01900\n", - "a1: 0.00249, a2: -0.00676, a3: 0.02291\n", - "a1: 0.00291, a2: -0.01112, a3: 0.01813\n", - "a1: 0.00499, a2: -0.01898, a3: 0.01409\n", - "a1: 0.00189, a2: -0.02030, a3: 0.00913\n", - "a1: -0.00231, a2: -0.02113, a3: 0.00494\n", - "a1: -0.00269, a2: -0.01729, a3: 0.00983\n", - "a1: -0.00481, a2: -0.00969, a3: 0.01366\n", - "a1: -0.00282, a2: -0.00867, a3: 0.01681\n", - "a1: 0.00198, a2: -0.00731, a3: 0.02262\n", - "a1: 0.00257, a2: -0.00854, a3: 0.02018\n", - "a1: 0.00448, a2: -0.01696, a3: 0.01512\n", - "a1: 0.00365, a2: -0.02028, a3: 0.01127\n", - "a1: -0.00148, a2: -0.02084, a3: 0.00540\n", - "a1: -0.00253, a2: -0.01956, a3: 0.00761\n", - "a1: -0.00407, a2: -0.01173, a3: 0.01298\n", - "a1: -0.00431, a2: -0.00852, a3: 0.01515\n", - "a1: 0.00080, a2: -0.00810, a3: 0.02114\n", - "a1: 0.00254, a2: -0.00705, a3: 0.02198\n", - "a1: 0.00363, a2: -0.01413, a3: 0.01641\n", - "a1: 0.00476, a2: -0.01999, a3: 0.01292\n", - "a1: 0.00004, a2: -0.02046, a3: 0.00701\n", - "a1: -0.00255, a2: -0.02083, a3: 0.00577\n", - "a1: -0.00322, a2: -0.01449, a3: 0.01171\n", - "a1: -0.00499, a2: -0.00870, a3: 0.01420\n", - "a1: -0.00096, a2: -0.00862, a3: 0.01896\n", - "a1: 0.00248, a2: -0.00675, a3: 0.02293\n", - "a1: 0.00290, a2: -0.01105, a3: 0.01818\n", - "a1: 0.00499, a2: -0.01896, a3: 0.01410\n", - "a1: 0.00193, a2: -0.02030, a3: 0.00919\n", - "a1: -0.00230, a2: -0.02112, a3: 0.00494\n", - "a1: -0.00268, a2: -0.01734, a3: 0.00979\n", - "a1: -0.00480, a2: -0.00973, a3: 0.01364\n", - "a1: -0.00285, a2: -0.00867, a3: 0.01677\n", - "a1: 0.00196, a2: -0.00732, a3: 0.02261\n", - "a1: 0.00256, a2: -0.00849, a3: 0.02023\n", - "a1: 0.00446, a2: -0.01691, a3: 0.01513\n", - "a1: 0.00368, a2: -0.02028, a3: 0.01131\n", - "a1: -0.00145, a2: -0.02083, a3: 0.00543\n", - "a1: -0.00253, a2: -0.01960, a3: 0.00757\n", - "a1: -0.00405, a2: -0.01179, a3: 0.01295\n", - "a1: -0.00433, a2: -0.00852, a3: 0.01512\n", - "a1: 0.00076, a2: -0.00812, a3: 0.02111\n", - "a1: 0.00254, a2: -0.00702, a3: 0.02202\n", - "a1: 0.00361, a2: -0.01407, a3: 0.01644\n", - "a1: 0.00477, a2: -0.01999, a3: 0.01295\n", - "a1: 0.00007, a2: -0.02045, a3: 0.00705\n", - "a1: -0.00254, a2: -0.02084, a3: 0.00575\n", - "a1: -0.00321, a2: -0.01456, a3: 0.01168\n", - "a1: -0.00499, a2: -0.00872, a3: 0.01417\n", - "a1: -0.00100, a2: -0.00863, a3: 0.01891\n", - "a1: 0.00248, a2: -0.00675, a3: 0.02294\n", - "a1: 0.00289, a2: -0.01099, a3: 0.01822\n", - "a1: 0.00498, a2: -0.01893, a3: 0.01412\n", - "a1: 0.00197, a2: -0.02030, a3: 0.00923\n", - "a1: -0.00229, a2: -0.02111, a3: 0.00495\n", - "a1: -0.00267, a2: -0.01741, a3: 0.00973\n", - "a1: -0.00479, a2: -0.00978, a3: 0.01362\n", - "a1: -0.00290, a2: -0.00867, a3: 0.01672\n", - "a1: 0.00195, a2: -0.00732, a3: 0.02260\n", - "a1: 0.00256, a2: -0.00846, a3: 0.02027\n", - "a1: 0.00445, a2: -0.01687, a3: 0.01515\n", - "a1: 0.00371, a2: -0.02028, a3: 0.01134\n", - "a1: -0.00142, a2: -0.02081, a3: 0.00547\n", - "a1: -0.00253, a2: -0.01963, a3: 0.00754\n", - "a1: -0.00403, a2: -0.01185, a3: 0.01293\n", - "a1: -0.00436, a2: -0.00852, a3: 0.01509\n", - "a1: 0.00072, a2: -0.00813, a3: 0.02106\n", - "a1: 0.00254, a2: -0.00700, a3: 0.02206\n", - "a1: 0.00359, a2: -0.01400, a3: 0.01647\n", - "a1: 0.00479, a2: -0.01998, a3: 0.01297\n", - "a1: 0.00011, a2: -0.02045, a3: 0.00709\n", - "a1: -0.00254, a2: -0.02085, a3: 0.00572\n", - "a1: -0.00319, a2: -0.01462, a3: 0.01164\n", - "a1: -0.00499, a2: -0.00874, a3: 0.01415\n", - "a1: -0.00106, a2: -0.00863, a3: 0.01885\n", - "a1: 0.00247, a2: -0.00675, a3: 0.02295\n", - "a1: 0.00288, a2: -0.01092, a3: 0.01827\n", - "a1: 0.00498, a2: -0.01890, a3: 0.01413\n", - "a1: 0.00201, a2: -0.02030, a3: 0.00928\n", - "a1: -0.00228, a2: -0.02110, a3: 0.00495\n", - "a1: -0.00267, a2: -0.01746, a3: 0.00969\n", - "a1: -0.00478, a2: -0.00981, a3: 0.01360\n", - "a1: -0.00294, a2: -0.00867, a3: 0.01667\n", - "a1: 0.00193, a2: -0.00734, a3: 0.02258\n", - "a1: 0.00256, a2: -0.00840, a3: 0.02033\n", - "a1: 0.00443, a2: -0.01682, a3: 0.01517\n", - "a1: 0.00374, a2: -0.02028, a3: 0.01138\n", - "a1: -0.00140, a2: -0.02080, a3: 0.00549\n", - "a1: -0.00253, a2: -0.01967, a3: 0.00748\n", - "a1: -0.00401, a2: -0.01191, a3: 0.01290\n", - "a1: -0.00438, a2: -0.00853, a3: 0.01505\n", - "a1: 0.00069, a2: -0.00814, a3: 0.02102\n", - "a1: 0.00254, a2: -0.00697, a3: 0.02210\n", - "a1: 0.00357, a2: -0.01394, a3: 0.01650\n", - "a1: 0.00480, a2: -0.01997, a3: 0.01300\n", - "a1: 0.00015, a2: -0.02044, a3: 0.00715\n", - "a1: -0.00254, a2: -0.02085, a3: 0.00570\n", - "a1: -0.00318, a2: -0.01470, a3: 0.01160\n", - "a1: -0.00500, a2: -0.00876, a3: 0.01413\n", - "a1: -0.00109, a2: -0.00863, a3: 0.01881\n", - "a1: 0.00247, a2: -0.00675, a3: 0.02296\n", - "a1: 0.00287, a2: -0.01086, a3: 0.01831\n", - "a1: 0.00497, a2: -0.01887, a3: 0.01415\n", - "a1: 0.00206, a2: -0.02030, a3: 0.00934\n", - "a1: -0.00227, a2: -0.02109, a3: 0.00495\n", - "a1: -0.00266, a2: -0.01752, a3: 0.00965\n", - "a1: -0.00476, a2: -0.00986, a3: 0.01359\n", - "a1: -0.00297, a2: -0.00867, a3: 0.01663\n", - "a1: 0.00190, a2: -0.00735, a3: 0.02256\n", - "a1: 0.00256, a2: -0.00836, a3: 0.02037\n", - "a1: 0.00441, a2: -0.01677, a3: 0.01519\n", - "a1: 0.00377, a2: -0.02029, a3: 0.01141\n", - "a1: -0.00137, a2: -0.02078, a3: 0.00552\n", - "a1: -0.00253, a2: -0.01969, a3: 0.00745\n", - "a1: -0.00399, a2: -0.01197, a3: 0.01287\n", - "a1: -0.00441, a2: -0.00853, a3: 0.01502\n", - "a1: 0.00065, a2: -0.00815, a3: 0.02098\n", - "a1: 0.00255, a2: -0.00695, a3: 0.02214\n", - "a1: 0.00355, a2: -0.01387, a3: 0.01653\n", - "a1: 0.00482, a2: -0.01996, a3: 0.01302\n", - "a1: 0.00020, a2: -0.02043, a3: 0.00719\n", - "a1: -0.00254, a2: -0.02086, a3: 0.00567\n", - "a1: -0.00316, a2: -0.01476, a3: 0.01156\n", - "a1: -0.00500, a2: -0.00878, a3: 0.01411\n", - "a1: -0.00113, a2: -0.00864, a3: 0.01876\n", - "a1: 0.00246, a2: -0.00675, a3: 0.02298\n", - "a1: 0.00285, a2: -0.01080, a3: 0.01835\n", - "a1: 0.00497, a2: -0.01884, a3: 0.01417\n", - "a1: 0.00210, a2: -0.02030, a3: 0.00938\n", - "a1: -0.00225, a2: -0.02108, a3: 0.00496\n", - "a1: -0.00265, a2: -0.01756, a3: 0.00961\n", - "a1: -0.00475, a2: -0.00991, a3: 0.01356\n", - "a1: -0.00302, a2: -0.00867, a3: 0.01658\n", - "a1: 0.00189, a2: -0.00736, a3: 0.02255\n", - "a1: 0.00255, a2: -0.00831, a3: 0.02042\n", - "a1: 0.00440, a2: -0.01672, a3: 0.01521\n", - "a1: 0.00381, a2: -0.02029, a3: 0.01146\n", - "a1: -0.00134, a2: -0.02077, a3: 0.00556\n", - "a1: -0.00253, a2: -0.01973, a3: 0.00741\n", - "a1: -0.00397, a2: -0.01203, a3: 0.01285\n", - "a1: -0.00443, a2: -0.00853, a3: 0.01498\n", - "a1: 0.00062, a2: -0.00816, a3: 0.02094\n", - "a1: 0.00255, a2: -0.00693, a3: 0.02217\n", - "a1: 0.00353, a2: -0.01380, a3: 0.01657\n", - "a1: 0.00483, a2: -0.01995, a3: 0.01305\n", - "a1: 0.00023, a2: -0.02042, a3: 0.00724\n", - "a1: -0.00254, a2: -0.02087, a3: 0.00566\n", - "a1: -0.00315, a2: -0.01482, a3: 0.01153\n", - "a1: -0.00501, a2: -0.00880, a3: 0.01409\n", - "a1: -0.00118, a2: -0.00864, a3: 0.01870\n", - "a1: 0.00245, a2: -0.00675, a3: 0.02299\n", - "a1: 0.00284, a2: -0.01074, a3: 0.01840\n", - "a1: 0.00496, a2: -0.01881, a3: 0.01418\n", - "a1: 0.00214, a2: -0.02030, a3: 0.00943\n", - "a1: -0.00224, a2: -0.02107, a3: 0.00497\n", - "a1: -0.00265, a2: -0.01763, a3: 0.00955\n", - "a1: -0.00474, a2: -0.00995, a3: 0.01355\n", - "a1: -0.00305, a2: -0.00866, a3: 0.01653\n", - "a1: 0.00186, a2: -0.00737, a3: 0.02253\n", - "a1: 0.00255, a2: -0.00826, a3: 0.02047\n", - "a1: 0.00438, a2: -0.01666, a3: 0.01523\n", - "a1: 0.00384, a2: -0.02029, a3: 0.01150\n", - "a1: -0.00131, a2: -0.02075, a3: 0.00559\n", - "a1: -0.00253, a2: -0.01976, a3: 0.00736\n", - "a1: -0.00395, a2: -0.01209, a3: 0.01282\n", - "a1: -0.00445, a2: -0.00854, a3: 0.01496\n", - "a1: 0.00059, a2: -0.00817, a3: 0.02090\n", - "a1: 0.00255, a2: -0.00691, a3: 0.02220\n", - "a1: 0.00351, a2: -0.01373, a3: 0.01660\n", - "a1: 0.00484, a2: -0.01994, a3: 0.01306\n", - "a1: 0.00028, a2: -0.02041, a3: 0.00729\n", - "a1: -0.00254, a2: -0.02088, a3: 0.00563\n", - "a1: -0.00313, a2: -0.01489, a3: 0.01148\n", - "a1: -0.00501, a2: -0.00882, a3: 0.01407\n", - "a1: -0.00123, a2: -0.00865, a3: 0.01865\n", - "a1: 0.00245, a2: -0.00675, a3: 0.02300\n", - "a1: 0.00283, a2: -0.01068, a3: 0.01843\n", - "a1: 0.00496, a2: -0.01878, a3: 0.01420\n", - "a1: 0.00218, a2: -0.02030, a3: 0.00948\n", - "a1: -0.00223, a2: -0.02106, a3: 0.00497\n", - "a1: -0.00264, a2: -0.01767, a3: 0.00951\n", - "a1: -0.00472, a2: -0.00999, a3: 0.01353\n", - "a1: -0.00309, a2: -0.00866, a3: 0.01649\n", - "a1: 0.00185, a2: -0.00738, a3: 0.02251\n", - "a1: 0.00255, a2: -0.00821, a3: 0.02052\n", - "a1: 0.00436, a2: -0.01662, a3: 0.01525\n", - "a1: 0.00387, a2: -0.02029, a3: 0.01153\n", - "a1: -0.00129, a2: -0.02074, a3: 0.00562\n", - "a1: -0.00253, a2: -0.01979, a3: 0.00733\n", - "a1: -0.00393, a2: -0.01216, a3: 0.01279\n", - "a1: -0.00447, a2: -0.00854, a3: 0.01492\n", - "a1: 0.00055, a2: -0.00818, a3: 0.02086\n", - "a1: 0.00255, a2: -0.00689, a3: 0.02224\n", - "a1: 0.00350, a2: -0.01368, a3: 0.01662\n", - "a1: 0.00485, a2: -0.01993, a3: 0.01309\n", - "a1: 0.00032, a2: -0.02040, a3: 0.00734\n", - "a1: -0.00253, a2: -0.02089, a3: 0.00561\n", - "a1: -0.00312, a2: -0.01495, a3: 0.01145\n", - "a1: -0.00501, a2: -0.00884, a3: 0.01405\n", - "a1: -0.00127, a2: -0.00865, a3: 0.01860\n", - "a1: 0.00244, a2: -0.00675, a3: 0.02301\n", - "a1: 0.00282, a2: -0.01061, a3: 0.01848\n", - "a1: 0.00495, a2: -0.01875, a3: 0.01421\n", - "a1: 0.00223, a2: -0.02030, a3: 0.00953\n", - "a1: -0.00222, a2: -0.02105, a3: 0.00498\n", - "a1: -0.00263, a2: -0.01774, a3: 0.00946\n", - "a1: -0.00471, a2: -0.01003, a3: 0.01351\n", - "a1: -0.00312, a2: -0.00866, a3: 0.01645\n", - "a1: 0.00182, a2: -0.00739, a3: 0.02249\n", - "a1: 0.00255, a2: -0.00817, a3: 0.02056\n", - "a1: 0.00434, a2: -0.01657, a3: 0.01526\n", - "a1: 0.00390, a2: -0.02029, a3: 0.01157\n", - "a1: -0.00125, a2: -0.02072, a3: 0.00566\n", - "a1: -0.00253, a2: -0.01982, a3: 0.00728\n", - "a1: -0.00391, a2: -0.01222, a3: 0.01276\n", - "a1: -0.00449, a2: -0.00855, a3: 0.01489\n", - "a1: 0.00052, a2: -0.00819, a3: 0.02083\n", - "a1: 0.00255, a2: -0.00687, a3: 0.02228\n", - "a1: 0.00348, a2: -0.01362, a3: 0.01665\n", - "a1: 0.00487, a2: -0.01992, a3: 0.01311\n", - "a1: 0.00036, a2: -0.02039, a3: 0.00739\n", - "a1: -0.00253, a2: -0.02089, a3: 0.00559\n", - "a1: -0.00310, a2: -0.01502, a3: 0.01141\n", - "a1: -0.00501, a2: -0.00886, a3: 0.01403\n", - "a1: -0.00131, a2: -0.00865, a3: 0.01856\n", - "a1: 0.00244, a2: -0.00675, a3: 0.02302\n", - "a1: 0.00281, a2: -0.01054, a3: 0.01853\n", - "a1: 0.00494, a2: -0.01873, a3: 0.01423\n", - "a1: 0.00227, a2: -0.02030, a3: 0.00958\n", - "a1: -0.00220, a2: -0.02104, a3: 0.00499\n", - "a1: -0.00263, a2: -0.01780, a3: 0.00941\n", - "a1: -0.00470, a2: -0.01008, a3: 0.01349\n", - "a1: -0.00316, a2: -0.00866, a3: 0.01641\n", - "a1: 0.00181, a2: -0.00740, a3: 0.02247\n", - "a1: 0.00255, a2: -0.00813, a3: 0.02060\n", - "a1: 0.00432, a2: -0.01650, a3: 0.01529\n", - "a1: 0.00394, a2: -0.02029, a3: 0.01161\n", - "a1: -0.00122, a2: -0.02071, a3: 0.00570\n", - "a1: -0.00253, a2: -0.01986, a3: 0.00723\n", - "a1: -0.00389, a2: -0.01228, a3: 0.01274\n", - "a1: -0.00451, a2: -0.00855, a3: 0.01486\n", - "a1: 0.00048, a2: -0.00820, a3: 0.02078\n", - "a1: 0.00255, a2: -0.00685, a3: 0.02231\n", - "a1: 0.00346, a2: -0.01354, a3: 0.01670\n", - "a1: 0.00488, a2: -0.01991, a3: 0.01314\n", - "a1: 0.00039, a2: -0.02038, a3: 0.00743\n", - "a1: -0.00253, a2: -0.02090, a3: 0.00557\n", - "a1: -0.00309, a2: -0.01508, a3: 0.01137\n", - "a1: -0.00501, a2: -0.00889, a3: 0.01400\n", - "a1: -0.00135, a2: -0.00865, a3: 0.01851\n", - "a1: 0.00243, a2: -0.00675, a3: 0.02303\n", - "a1: 0.00280, a2: -0.01049, a3: 0.01857\n", - "a1: 0.00493, a2: -0.01869, a3: 0.01424\n", - "a1: 0.00231, a2: -0.02030, a3: 0.00962\n", - "a1: -0.00219, a2: -0.02103, a3: 0.00500\n", - "a1: -0.00262, a2: -0.01785, a3: 0.00936\n", - "a1: -0.00468, a2: -0.01012, a3: 0.01347\n", - "a1: -0.00321, a2: -0.00866, a3: 0.01635\n", - "a1: 0.00178, a2: -0.00742, a3: 0.02245\n", - "a1: 0.00254, a2: -0.00808, a3: 0.02066\n", - "a1: 0.00431, a2: -0.01645, a3: 0.01531\n", - "a1: 0.00396, a2: -0.02029, a3: 0.01164\n", - "a1: -0.00120, a2: -0.02069, a3: 0.00573\n", - "a1: -0.00253, a2: -0.01989, a3: 0.00720\n", - "a1: -0.00387, a2: -0.01234, a3: 0.01271\n", - "a1: -0.00453, a2: -0.00856, a3: 0.01483\n", - "a1: 0.00045, a2: -0.00821, a3: 0.02074\n", - "a1: 0.00255, a2: -0.00683, a3: 0.02234\n", - "a1: 0.00344, a2: -0.01349, a3: 0.01672\n", - "a1: 0.00489, a2: -0.01990, a3: 0.01315\n", - "a1: 0.00044, a2: -0.02038, a3: 0.00749\n", - "a1: -0.00253, a2: -0.02090, a3: 0.00555\n", - "a1: -0.00307, a2: -0.01516, a3: 0.01132\n", - "a1: -0.00501, a2: -0.00891, a3: 0.01399\n", - "a1: -0.00140, a2: -0.00866, a3: 0.01845\n", - "a1: 0.00242, a2: -0.00675, a3: 0.02304\n", - "a1: 0.00279, a2: -0.01042, a3: 0.01862\n", - "a1: 0.00493, a2: -0.01866, a3: 0.01426\n", - "a1: 0.00236, a2: -0.02030, a3: 0.00968\n", - "a1: -0.00217, a2: -0.02102, a3: 0.00501\n", - "a1: -0.00262, a2: -0.01789, a3: 0.00932\n", - "a1: -0.00467, a2: -0.01016, a3: 0.01345\n", - "a1: -0.00324, a2: -0.00866, a3: 0.01631\n", - "a1: 0.00176, a2: -0.00743, a3: 0.02243\n", - "a1: 0.00254, a2: -0.00804, a3: 0.02070\n", - "a1: 0.00429, a2: -0.01641, a3: 0.01532\n", - "a1: 0.00399, a2: -0.02029, a3: 0.01167\n", - "a1: -0.00116, a2: -0.02068, a3: 0.00577\n", - "a1: -0.00254, a2: -0.01992, a3: 0.00716\n", - "a1: -0.00385, a2: -0.01240, a3: 0.01268\n", - "a1: -0.00456, a2: -0.00856, a3: 0.01480\n", - "a1: 0.00041, a2: -0.00822, a3: 0.02069\n", - "a1: 0.00255, a2: -0.00681, a3: 0.02238\n", - "a1: 0.00342, a2: -0.01341, a3: 0.01676\n", - "a1: 0.00490, a2: -0.01989, a3: 0.01318\n", - "a1: 0.00048, a2: -0.02037, a3: 0.00754\n", - "a1: -0.00253, a2: -0.02091, a3: 0.00553\n", - "a1: -0.00306, a2: -0.01522, a3: 0.01129\n", - "a1: -0.00501, a2: -0.00893, a3: 0.01396\n", - "a1: -0.00143, a2: -0.00866, a3: 0.01841\n", - "a1: 0.00241, a2: -0.00676, a3: 0.02305\n", - "a1: 0.00278, a2: -0.01037, a3: 0.01866\n", - "a1: 0.00492, a2: -0.01864, a3: 0.01427\n", - "a1: 0.00239, a2: -0.02030, a3: 0.00972\n", - "a1: -0.00216, a2: -0.02100, a3: 0.00502\n", - "a1: -0.00261, a2: -0.01795, a3: 0.00927\n", - "a1: -0.00465, a2: -0.01021, a3: 0.01344\n", - "a1: -0.00327, a2: -0.00866, a3: 0.01628\n", - "a1: 0.00174, a2: -0.00744, a3: 0.02241\n", - "a1: 0.00254, a2: -0.00800, a3: 0.02074\n", - "a1: 0.00427, a2: -0.01635, a3: 0.01535\n", - "a1: 0.00402, a2: -0.02029, a3: 0.01171\n", - "a1: -0.00114, a2: -0.02067, a3: 0.00579\n", - "a1: -0.00254, a2: -0.01995, a3: 0.00712\n", - "a1: -0.00383, a2: -0.01247, a3: 0.01265\n", - "a1: -0.00458, a2: -0.00857, a3: 0.01477\n", - "a1: 0.00037, a2: -0.00823, a3: 0.02065\n", - "a1: 0.00255, a2: -0.00679, a3: 0.02241\n", - "a1: 0.00341, a2: -0.01336, a3: 0.01679\n", - "a1: 0.00491, a2: -0.01988, a3: 0.01320\n", - "a1: 0.00053, a2: -0.02036, a3: 0.00759\n", - "a1: -0.00252, a2: -0.02091, a3: 0.00551\n", - "a1: -0.00304, a2: -0.01529, a3: 0.01124\n", - "a1: -0.00501, a2: -0.00895, a3: 0.01395\n", - "a1: -0.00147, a2: -0.00866, a3: 0.01836\n", - "a1: 0.00240, a2: -0.00676, a3: 0.02305\n", - "a1: 0.00277, a2: -0.01031, a3: 0.01871\n", - "a1: 0.00491, a2: -0.01860, a3: 0.01429\n", - "a1: 0.00243, a2: -0.02030, a3: 0.00977\n", - "a1: -0.00215, a2: -0.02099, a3: 0.00502\n", - "a1: -0.00261, a2: -0.01800, a3: 0.00923\n", - "a1: -0.00464, a2: -0.01026, a3: 0.01341\n", - "a1: -0.00331, a2: -0.00866, a3: 0.01623\n", - "a1: 0.00171, a2: -0.00745, a3: 0.02239\n", - "a1: 0.00254, a2: -0.00796, a3: 0.02079\n", - "a1: 0.00425, a2: -0.01629, a3: 0.01537\n", - "a1: 0.00405, a2: -0.02029, a3: 0.01175\n", - "a1: -0.00110, a2: -0.02065, a3: 0.00583\n", - "a1: -0.00254, a2: -0.01998, a3: 0.00708\n", - "a1: -0.00382, a2: -0.01253, a3: 0.01263\n", - "a1: -0.00460, a2: -0.00857, a3: 0.01474\n", - "a1: 0.00033, a2: -0.00825, a3: 0.02060\n", - "a1: 0.00255, a2: -0.00677, a3: 0.02245\n", - "a1: 0.00339, a2: -0.01328, a3: 0.01683\n", - "a1: 0.00492, a2: -0.01987, a3: 0.01322\n", - "a1: 0.00057, a2: -0.02035, a3: 0.00764\n", - "a1: -0.00252, a2: -0.02092, a3: 0.00549\n", - "a1: -0.00303, a2: -0.01536, a3: 0.01120\n", - "a1: -0.00501, a2: -0.00898, a3: 0.01393\n", - "a1: -0.00152, a2: -0.00866, a3: 0.01831\n", - "a1: 0.00240, a2: -0.00676, a3: 0.02306\n", - "a1: 0.00276, a2: -0.01024, a3: 0.01876\n", - "\n", - "运行完成, 共 429 步\n" - ] - } - ], - "source": [ - "run_control_loop(duration=190.0)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "1686547c", - "metadata": {}, - "outputs": [], - "source": [ - "from drv_encodermotor import EncoderMotorDriver, NORMAL_MODE, SPEED_MODE, POSITION_MODE\n", - "import time\n", - "# time.sleep(20)\n", - "motor_channel = 0\n", - "with EncoderMotorDriver(bus=1) as driver:\n", - " driver.set_mode(motor_channel, SPEED_MODE)\n", - " driver.set_speed_pid(motor_channel, kp=1, ki=100, kd=1)\n", - " # driver.set_position_pid(motor_channel, kp=1, ki=100, kd=1)\n", - " driver.set_soft_start_and_stop(motor_channel, True)\n", - " # driver.set_linear_speed_m_s(motor_channel, 0.004514*9*2*5)\n", - " # driver.set_encoder_value(motor_channel, 0)\n", - " # driver.set_position_pid_max_speed(motor_channel, 10)\n", - " # driver.set_position_point(motor_channel, 100)" - ] - }, - { - "cell_type": "code", - "execution_count": 7, - "id": "e352526c", - "metadata": {}, - "outputs": [], - "source": [ - "with EncoderMotorDriver(bus=1) as driver:\n", - " # driver.set_position_point(motor_channel, 570)\n", - " # time.sleep(10)\n", - " # driver.set_position_point(motor_channel, 0)\n", - " driver.set_linear_speed_m_s(3, 0.004514*0)\n", - " driver.set_linear_speed_m_s(2, 0.004514*0*4)\n", - " driver.set_linear_speed_m_s(0, -0.004514*0*4)" - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "id": "88cc25ce", - "metadata": {}, - "outputs": [], - "source": [ - "from drv_stepmotor import StepMotorDriver\n", - "drv = StepMotorDriver()\n", - "drv.enable_motor()\n", - "for i in range(3):\n", - " drv.reset_motor(i, True)\n", - "drv.set_dir(True)\n", - "drv.start(0.004514*2)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "13ebac6f", - "metadata": {}, - "outputs": [], - "source": [ - "drv.change_speed(0.004514*2)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "1bdb147f", - "metadata": {}, - "outputs": [], - "source": [ - "drv.set_dir(True)\n", - "drv.start(0.004514*2)" - ] - }, - { - "cell_type": "code", - "execution_count": 6, - "id": "09304592", - "metadata": {}, - "outputs": [], - "source": [ - "drv.stop()\n", - "drv.set_dir(False)\n", - "drv.start(0.004514*1)" - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "id": "f4b1ccc4", - "metadata": {}, - "outputs": [], - "source": [ - "drv.stop()\n", - "drv.close()" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "0fb7a008", - "metadata": {}, - "outputs": [], - "source": [ - "from rpi_hardware_pwm import HardwarePWM\n", - "\n", - "pwm = HardwarePWM(pwm_channel=0, hz=60, chip=0)\n", - "pwm.start(50)\n", - "pwm.change_frequency(25_000)\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "c1d37a4a", - "metadata": {}, - "outputs": [], - "source": [ - "\n", - "pwm.stop()" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "fef25185", - "metadata": {}, - "outputs": [], - "source": [ - "# Step motor test: run all 3 motors together using set_speeds\n", - "from drv_stepmotor import StepMotorDriver\n", - "import time\n", - "import math\n", - "from drv_motor import MotorDriver, NORMAL_MODE, SPEED_MODE, POSITION_MODE\n", - "from drv_adc import ADS124S08\n", - "from matplotlib import pyplot as plt\n", - "motor_channel = 3 # test channel, can be 0~3\n", - "period = 10.0 # seconds for direction change\n", - "duration = 40 # total run time in seconds\n", - "steps_per_sec = 100\n", - "total_steps = 100\n", - "amplitude = 10 # max speed (-127~127)\n", - "adc = ADS124S08()\n", - "adc.set_crc(True)\n", - "adc.set_datarate(dr = 8, mode = 1)\n", - "adc.set_pga(pga_en=1, gain=5)\n", - "adc.set_input_mux(pos_channel=1, neg_channel=12)\n", - "# time.sleep(5)\n", - "drv = StepMotorDriver()\n", - "# drv.enable_motor()\n", - "for i in range(3):\n", - " drv.reset_motor(i, False)\n", - "\n", - "with MotorDriver(bus=1) as driver:\n", - " driver.set_mode(motor_channel, SPEED_MODE)\n", - " driver.set_speed_pid(motor_channel, kp=1, ki=100, kd=1)\n", - " driver.set_soft_start_and_stop(motor_channel, True)\n", - " \n", - " # drv.set_speeds(0.04/3)\n", - " time.sleep(10.1)\n", - " time_series = []\n", - " start_time = time.time()\n", - " for i in range(total_steps):\n", - " # t = time.time() - start_time\n", - " # direction = 1 if int(t // period) % 2 == 0 else -1\n", - " # speed = int(amplitude * math.sin(2 * math.pi * (i) / steps_per_sec))\n", - " # driver.set_speed_point(motor_channel, speed)\n", - " ave_values = [0,0,0]\n", - " for j in range(10):\n", - " values = adc.request_channels(channels=[0,1,2])\n", - " # if abs(values[0]) > 10_000_000 or abs(values[1]) > 10_000_000:\n", - " # values = [0,0,0]\n", - " ave_values[0] += values[0]\n", - " ave_values[1] += values[1]\n", - " ave_values[2] += values[2]\n", - " ave_values[0] /= 10\n", - " ave_values[1] /= 10\n", - " ave_values[2] /= 10\n", - "\n", - " # time.sleep(0.1)\n", - " time_series.append(ave_values)\n", - " drv.stop_all(emergency=False)\n", - " time.sleep(3.1)\n", - " end_time = time.time()\n", - " \n", - " print('All motors test complete')\n", - "\n", - " # Reset and disable all motors\n", - " for i in range(3):\n", - " drv.reset_motor(i, True)\n", - " drv.disable_motor()\n", - " drv.close()\n", - " driver.set_speed_point(motor_channel, 0)\n", - " driver.set_soft_start_and_stop(motor_channel, False)\n", - " adc.close()\n", - " average = [sum([v[0] for v in time_series])/len(time_series), \n", - " sum([v[1] for v in time_series])/len(time_series),\n", - " sum([v[2] for v in time_series])/len(time_series)]\n", - " de_averaged_series = [[v[0]-average[0] for v in time_series],\n", - " [v[1]-average[1] for v in time_series],\n", - " [v[2]-average[2] for v in time_series]]\n", - " plt.plot(de_averaged_series[0], label='Channel 0')\n", - " plt.plot(de_averaged_series[1], label='Channel 1')\n", - " plt.plot(de_averaged_series[2], label='Channel 2')\n", - " # plt.plot(time_series)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "66a9eb00", - "metadata": {}, - "outputs": [], - "source": [ - "# 保存原始ADC信号到CSV(带时间戳,从0开始,单位秒)\n", - "import csv\n", - "import os\n", - "\n", - "# 校验采集变量是否存在\n", - "try:\n", - " _ts = time_series\n", - " _start = start_time\n", - " _end = end_time\n", - "except NameError as e:\n", - " raise RuntimeError(\"请先运行采集数据的单元格,确保存在 time_series、start_time、end_time。\") from e\n", - "\n", - "num_samples = len(_ts)\n", - "if num_samples == 0:\n", - " raise RuntimeError(\"time_series 为空,未采集到数据。\")\n", - "\n", - "# 根据起止时间生成均匀时间戳(最后一个点贴合总时长)\n", - "duration = max(0.0, _end - _start)\n", - "if num_samples > 1 and duration > 0:\n", - " timestamps = [i * duration / (num_samples - 1) for i in range(num_samples)]\n", - "else:\n", - " timestamps = [0.0 for _ in range(num_samples)]\n", - "\n", - "out_path = os.path.join(os.getcwd(), \"stealth_no_ctrl_still.csv\")\n", - "with open(out_path, \"w\", newline=\"\") as f:\n", - " writer = csv.writer(f)\n", - " writer.writerow([\"time_s\", \"ch0\", \"ch1\", \"ch2\"])\n", - " for t, v in zip(timestamps, _ts):\n", - " writer.writerow([t, v[0], v[1], v[2]])\n", - "\n", - "print(f\"已写入 {num_samples} 行到 {out_path},采集时长约 {duration:.3f} 秒。\")" - ] - }, { "cell_type": "markdown", "id": "e9ca5704", @@ -896,13 +29,26 @@ "# ==================== CLEAN 1. Imports ====================\n", "from pathlib import Path\n", "import math\n", + "import json\n", + "import tempfile\n", "\n", "import numpy as np\n", "import matplotlib.pyplot as plt\n", - "from matplotlib.colors import BoundaryNorm, LinearSegmentedColormap\n", "import pycuda.driver as cuda\n", "\n", "from CelerisLab import Simulation\n", + "from CelerisLab.common.streakline import (\n", + " FlowFrame,\n", + " ReleaseConfig,\n", + " IntegratorConfig,\n", + " build_release_points,\n", + " advance_particles_between_frames,\n", + " cylinders_from_triangle_layout,\n", + " configure_compute_threads,\n", + " compute_vorticity,\n", + " render_streakline_density,\n", + " render_vorticity_field,\n", + ")\n", "\n", "INLET_U_PHYS_M_S = 0.009028\n", "CYLINDER_DIAMETER_M = 0.010\n", @@ -936,24 +82,31 @@ "\n", "CFD_PARAMS = {\n", " \"config_path\": str((cwd / \"src/CelerisLab/configs/config_lbm_three_cylinder_triangle.json\") if (cwd / \"src\").exists() else Path(\"../src/CelerisLab/configs/config_lbm_three_cylinder_triangle.json\")),\n", + " \"inlet_scheme\": \"regularized\",\n", " \"checkpoint_path\": str(WORKDIR / \"triangle_state_clean.h5\"),\n", - " \"cache_npz\": str(WORKDIR / \"triangle_latest_fields_clean.npz\"),\n", + " \"output_streak_png\": str(WORKDIR / \"streakline_triangle_clean.png\"),\n", + " \"output_streak_meta\": str(WORKDIR / \"streakline_triangle_clean.json\"),\n", " \"output_vort_png\": str(WORKDIR / \"vorticity_triangle_clean.png\"),\n", " \"report_every\": 10000,\n", "}\n", "\n", "RUN_PARAMS = {\n", " \"steady\": False,\n", - " \"steps_this_chunk\": 0, # 0 => nx / U_lb\n", + " \"steps_this_chunk\": 0, # 0 => 2 * nx / (3 * U_lb)\n", " \"load_checkpoint\": False,\n", "}\n", "\n", - "PLOT_PARAMS = {\n", - " \"vmin\": None,\n", - " \"vmax\": None,\n", - " \"bins\": 20,\n", - " \"figsize\": (14, 4),\n", - " \"dpi\": 150,\n", + "STREAK_PARAMS = {\n", + " \"sample_every\": 200,\n", + " \"inject_per_seed\": 1,\n", + " # Per seed: 5 particles along +x with small spacing (strip mode).\n", + " \"release_mode\": \"strip\",\n", + " \"downstream_count\": 5,\n", + " \"downstream_spacing\": 1.0,\n", + " \"max_particle_age\": None,\n", + " \"age_decay_steps\": 80000.0,\n", + " \"blur_sigma\": 0.8,\n", + " \"num_threads\": 0,\n", "}\n", "\n", "ACTION_FEATURES = {\n", @@ -1049,8 +202,44 @@ " cuda.memcpy_htod(bodies.action_gpu, bodies.action)\n", "\n", "\n", - "def add_triangle_cylinders(sim):\n", - " cfg = sim.lbm_cfg\n", + "def ensure_compat_config(config_path, preferred_scheme=\"regularized\"):\n", + " config_file = Path(config_path)\n", + " with config_file.open(\"r\", encoding=\"utf-8\") as f:\n", + " cfg = json.load(f)\n", + "\n", + " method = cfg.setdefault(\"method\", {})\n", + " inlet = method.setdefault(\"inlet\", {})\n", + " outlet = method.setdefault(\"outlet\", {})\n", + "\n", + " changed = False\n", + " if \"scheme\" not in inlet:\n", + " inlet[\"scheme\"] = preferred_scheme\n", + " changed = True\n", + " if \"regularized_neq_damp\" not in inlet:\n", + " inlet[\"regularized_neq_damp\"] = 0.5\n", + " changed = True\n", + " if \"blend_alpha\" not in outlet:\n", + " outlet[\"blend_alpha\"] = 0.7\n", + " changed = True\n", + " if \"backflow_clamp\" not in outlet:\n", + " outlet[\"backflow_clamp\"] = True\n", + " changed = True\n", + "\n", + " if not changed:\n", + " return str(config_file)\n", + "\n", + " tmp = tempfile.NamedTemporaryFile(\n", + " mode=\"w\",\n", + " suffix=\"_compat_config_lbm.json\",\n", + " delete=False,\n", + " encoding=\"utf-8\",\n", + " )\n", + " with tmp:\n", + " json.dump(cfg, tmp, indent=4)\n", + " return tmp.name\n", + "\n", + "\n", + "def triangle_layout(cfg):\n", " dx_phys = CYLINDER_DIAMETER_M / DIAMETER_CELLS\n", " spacing_lb = CENTER_SPACING_M / dx_phys\n", " radius_lb = DIAMETER_CELLS / 2.0\n", @@ -1062,17 +251,46 @@ " y_upper = y_center + 0.5 * spacing_lb\n", " y_lower = y_center - 0.5 * spacing_lb\n", "\n", - " sim.add_cylinder(center=(x_apex, y_center), radius=radius_lb)\n", - " sim.add_cylinder(center=(x_rear, y_lower), radius=radius_lb)\n", - " sim.add_cylinder(center=(x_rear, y_upper), radius=radius_lb)\n", + " return {\n", + " \"x_apex\": x_apex,\n", + " \"x_rear\": x_rear,\n", + " \"y_center\": y_center,\n", + " \"y_upper\": y_upper,\n", + " \"y_lower\": y_lower,\n", + " \"radius_lb\": radius_lb,\n", + " }\n", "\n", "\n", - "def compute_vorticity(ux, uy):\n", - " vort = np.zeros_like(ux, dtype=np.float32)\n", - " dudy = 0.5 * (ux[2:, 1:-1] - ux[:-2, 1:-1])\n", - " dvdx = 0.5 * (uy[1:-1, 2:] - uy[1:-1, :-2])\n", - " vort[1:-1, 1:-1] = dvdx - dudy\n", - " return vort\n" + "def add_triangle_cylinders(sim):\n", + " layout = triangle_layout(sim.lbm_cfg)\n", + " sim.add_cylinder(center=(layout[\"x_apex\"], layout[\"y_center\"]), radius=layout[\"radius_lb\"])\n", + " sim.add_cylinder(center=(layout[\"x_rear\"], layout[\"y_lower\"]), radius=layout[\"radius_lb\"])\n", + " sim.add_cylinder(center=(layout[\"x_rear\"], layout[\"y_upper\"]), radius=layout[\"radius_lb\"])\n", + " return layout\n", + "\n", + "\n", + "def build_release_points_for_triangle(layout):\n", + " release_x = min(layout[\"x_apex\"], layout[\"x_rear\"]) - 4.0 * DIAMETER_CELLS\n", + " y_low_edge = layout[\"y_lower\"] - layout[\"radius_lb\"]\n", + " y_high_edge = layout[\"y_upper\"] + layout[\"radius_lb\"]\n", + " ys = np.linspace(y_low_edge, y_high_edge, 4, dtype=np.float32)\n", + " return np.column_stack([np.full(4, release_x, dtype=np.float32), ys]).astype(np.float32)\n", + "\n", + "\n", + "def filter_particles_outside_cylinders(points, layout):\n", + " centers = np.array(\n", + " [\n", + " [layout[\"x_apex\"], layout[\"y_center\"]],\n", + " [layout[\"x_rear\"], layout[\"y_lower\"]],\n", + " [layout[\"x_rear\"], layout[\"y_upper\"]],\n", + " ],\n", + " dtype=np.float32,\n", + " )\n", + " dist2_min = np.full(points.shape[0], np.inf, dtype=np.float32)\n", + " for center in centers:\n", + " d2 = (points[:, 0] - center[0]) ** 2 + (points[:, 1] - center[1]) ** 2\n", + " dist2_min = np.minimum(dist2_min, d2)\n", + " return dist2_min > (layout[\"radius_lb\"] ** 2)\n" ] }, { @@ -1096,8 +314,11 @@ " CLEAN_STATE = {\"sim\": None, \"steps_done\": 0, \"u_lb\": None, \"dt_phys\": None}\n", "\n", "if CLEAN_STATE[\"sim\"] is None:\n", - " sim = Simulation(CFD_PARAMS[\"config_path\"])\n", - " add_triangle_cylinders(sim)\n", + " compat_config_path = ensure_compat_config(\n", + " CFD_PARAMS[\"config_path\"], preferred_scheme=CFD_PARAMS[\"inlet_scheme\"]\n", + " )\n", + " sim = Simulation(compat_config_path)\n", + " layout = add_triangle_cylinders(sim)\n", " sim.initialize()\n", "\n", " u_lb = float(sim.lbm_cfg.velocity)\n", @@ -1105,6 +326,7 @@ " dt_phys = dx_phys * (u_lb / INLET_U_PHYS_M_S)\n", "\n", " CLEAN_STATE[\"sim\"] = sim\n", + " CLEAN_STATE[\"layout\"] = layout\n", " CLEAN_STATE[\"u_lb\"] = u_lb\n", " CLEAN_STATE[\"dt_phys\"] = dt_phys\n", " CLEAN_STATE[\"steps_done\"] = 0\n", @@ -1116,6 +338,9 @@ " print(f\"Restored checkpoint: {ckpt}, step={CLEAN_STATE['steps_done']}\")\n", " else:\n", " print(\"Initialized from scratch.\")\n", + "\n", + " print(f\"Using config: {compat_config_path}\")\n", + " print(f\"Inlet scheme: {sim.lbm_cfg.inlet_scheme}\")\n", "else:\n", " print(\"Session already initialized.\")\n", "\n", @@ -1155,47 +380,165 @@ "if sim is None:\n", " raise RuntimeError(\"Run CLEAN 4 first.\")\n", "\n", + "layout = CLEAN_STATE[\"layout\"]\n", "steps = int(RUN_PARAMS[\"steps_this_chunk\"])\n", "if steps <= 0:\n", - " steps = int(round(sim.lbm_cfg.nx / CLEAN_STATE[\"u_lb\"]/3*2))\n", + " steps = int(round(2.0 * sim.lbm_cfg.nx / (3.0 * CLEAN_STATE[\"u_lb\"])))\n", "\n", "steady = bool(RUN_PARAMS[\"steady\"])\n", "report_every = max(1, int(CFD_PARAMS[\"report_every\"]))\n", + "sample_every = max(1, int(STREAK_PARAMS[\"sample_every\"]))\n", + "n_threads = configure_compute_threads(int(STREAK_PARAMS.get(\"num_threads\", 0)))\n", + "print(f\"Host compute threads (BLAS/blur): {n_threads}\")\n", + "\n", + "release_cfg = ReleaseConfig(\n", + " mode=str(STREAK_PARAMS.get(\"release_mode\", \"strip\")),\n", + " line_count=1,\n", + " line_span=0.0,\n", + " downstream_count=max(1, int(STREAK_PARAMS.get(\"downstream_count\", 5))),\n", + " downstream_spacing=float(STREAK_PARAMS.get(\"downstream_spacing\", 1.0)),\n", + " inject_per_seed=max(1, int(STREAK_PARAMS[\"inject_per_seed\"])),\n", + ")\n", + "max_age = STREAK_PARAMS.get(\"max_particle_age\")\n", + "integrator_cfg = IntegratorConfig(\n", + " alpha_t=0.25,\n", + " alpha_x=0.40,\n", + " max_particle_age=None if max_age is None else float(max_age),\n", + " num_threads=n_threads,\n", + ")\n", + "\n", + "base_release = build_release_points_for_triangle(layout)\n", + "release_points = build_release_points(base_release, release_cfg).astype(np.float64)\n", + "cylinders = cylinders_from_triangle_layout(layout)\n", + "print(\n", + " f\"Release seeds: {base_release.shape[0]} base -> {release_points.shape[0]} \"\n", + " f\"(+x x{release_cfg.downstream_count}, spacing={release_cfg.downstream_spacing})\"\n", + ")\n", + "\n", + "# Streakline = continuous release at fixed seeds + current particle cloud at end (not path history).\n", + "particles = np.empty((0, 2), dtype=np.float64)\n", + "ages = np.empty((0,), dtype=np.float64)\n", + "prev_frame = None\n", + "chunk_start_step = int(CLEAN_STATE[\"steps_done\"])\n", "\n", "for i in range(steps):\n", " t_phys = CLEAN_STATE[\"steps_done\"] * CLEAN_STATE[\"dt_phys\"]\n", " a1, a2, a3 = actions_m_s_at_time(t_phys, steady=steady)\n", " w0 = action_to_omega_lb(a1, CLEAN_STATE[\"u_lb\"])\n", - " w1 = action_to_omega_lb(a2, CLEAN_STATE[\"u_lb\"])\n", - " w2 = action_to_omega_lb(a3, CLEAN_STATE[\"u_lb\"])\n", - " set_body_omegas(sim, w0, w1, w2)\n", + " w2 = action_to_omega_lb(a2, CLEAN_STATE[\"u_lb\"])\n", + " w3 = action_to_omega_lb(a3, CLEAN_STATE[\"u_lb\"])\n", + " # action2 -> rear-upper, action3 -> rear-lower (swap vs body add order)\n", + " set_body_omegas(sim, w0, w3, w2)\n", " sim.run(1)\n", " CLEAN_STATE[\"steps_done\"] += 1\n", "\n", + " if CLEAN_STATE[\"steps_done\"] % sample_every == 0:\n", + " macro = sim.get_macroscopic()\n", + " curr_frame = FlowFrame(\n", + " step=int(CLEAN_STATE[\"steps_done\"]),\n", + " ux=macro[\"ux\"].astype(np.float64, copy=False),\n", + " uy=macro[\"uy\"].astype(np.float64, copy=False),\n", + " )\n", + "\n", + " # Continuous dye injection: new particles always born at upstream seeds.\n", + " new_particles = np.repeat(release_points, release_cfg.inject_per_seed, axis=0).astype(np.float64)\n", + " if particles.size:\n", + " particles = np.vstack([particles, new_particles])\n", + " ages = np.concatenate([ages, np.zeros(new_particles.shape[0], dtype=np.float64)])\n", + " else:\n", + " particles = new_particles.copy()\n", + " ages = np.zeros(new_particles.shape[0], dtype=np.float64)\n", + "\n", + " if prev_frame is not None and particles.size:\n", + " particles, ages, _, _ = advance_particles_between_frames(\n", + " particles,\n", + " ages,\n", + " prev_frame,\n", + " curr_frame,\n", + " nx=sim.lbm_cfg.nx,\n", + " ny=sim.lbm_cfg.ny,\n", + " cfg=integrator_cfg,\n", + " cylinders=cylinders,\n", + " )\n", + "\n", + " prev_frame = curr_frame\n", + "\n", " if (i + 1) % report_every == 0:\n", " print(\n", " f\"chunk_step={i+1}/{steps} global={CLEAN_STATE['steps_done']} \"\n", - " f\"a=({a1:+.5f},{a2:+.5f},{a3:+.5f})\"\n", + " f\"a=({a1:+.5f},{a2:+.5f},{a3:+.5f}) particles={particles.shape[0]}\"\n", " )\n", "\n", - "macro = sim.get_macroscopic()\n", - "ux_clean = macro[\"ux\"].astype(np.float32, copy=False)\n", - "uy_clean = macro[\"uy\"].astype(np.float32, copy=False)\n", - "vort_clean = compute_vorticity(ux_clean, uy_clean)\n", - "\n", - "np.savez_compressed(\n", - " CFD_PARAMS[\"cache_npz\"],\n", - " ux=ux_clean,\n", - " uy=uy_clean,\n", - " vort=vort_clean,\n", - " step=np.int64(CLEAN_STATE[\"steps_done\"]),\n", - ")\n", "sim.save_checkpoint(CFD_PARAMS[\"checkpoint_path\"])\n", "\n", + "if particles.size == 0:\n", + " raise RuntimeError(\"No alive particles at end. Increase steps or lower sample_every.\")\n", + "\n", + "streak_png = Path(CFD_PARAMS[\"output_streak_png\"])\n", + "render_info = render_streakline_density(\n", + " particles,\n", + " ages,\n", + " nx=sim.lbm_cfg.nx,\n", + " ny=sim.lbm_cfg.ny,\n", + " out_path=str(streak_png),\n", + " cylinders=cylinders,\n", + " age_decay_steps=float(STREAK_PARAMS[\"age_decay_steps\"]),\n", + " blur_sigma=float(STREAK_PARAMS[\"blur_sigma\"]),\n", + " minimal_axes=True,\n", + " background_color=(1.0, 1.0, 1.0),\n", + " streak_color=(1.0, 0.0, 0.0),\n", + " show_release_points=False,\n", + ")\n", + "\n", + "streak_meta = {\n", + " \"chunk_start_step\": chunk_start_step,\n", + " \"chunk_end_step\": int(CLEAN_STATE[\"steps_done\"]),\n", + " \"sample_every\": sample_every,\n", + " \"release_points_base\": base_release.tolist(),\n", + " \"release_points_dense\": release_points.tolist(),\n", + " \"release_x_4D_upstream\": float(base_release[0, 0]),\n", + " \"particle_count\": int(particles.shape[0]),\n", + " \"max_particle_age\": max_age,\n", + " \"age_decay_steps\": float(STREAK_PARAMS[\"age_decay_steps\"]),\n", + " \"render_mode\": \"particle_cloud_streakline\",\n", + " \"render\": render_info,\n", + "}\n", + "meta_path = Path(CFD_PARAMS[\"output_streak_meta\"])\n", + "meta_path.parent.mkdir(parents=True, exist_ok=True)\n", + "\n", + "macro_end = sim.get_macroscopic()\n", + "ux_end = macro_end[\"ux\"].astype(np.float32, copy=False)\n", + "uy_end = macro_end[\"uy\"].astype(np.float32, copy=False)\n", + "vort_end = compute_vorticity(ux_end, uy_end)\n", + "vort_png = Path(CFD_PARAMS[\"output_vort_png\"])\n", + "vort_info = render_vorticity_field(\n", + " vort_end,\n", + " nx=sim.lbm_cfg.nx,\n", + " ny=sim.lbm_cfg.ny,\n", + " out_path=str(vort_png),\n", + " cylinders=cylinders,\n", + " minimal_axes=True,\n", + ")\n", + "streak_meta[\"vorticity\"] = vort_info\n", + "\n", + "CLEAN_STATE[\"streak_particles\"] = particles\n", + "CLEAN_STATE[\"streak_age\"] = ages\n", + "CLEAN_STATE[\"streak_image\"] = plt.imread(streak_png)\n", + "CLEAN_STATE[\"vort\"] = vort_end\n", + "CLEAN_STATE[\"vort_image\"] = plt.imread(vort_png)\n", + "\n", + "with meta_path.open(\"w\", encoding=\"utf-8\") as f:\n", + " json.dump(streak_meta, f, indent=2)\n", + "\n", "print(f\"Compute done. step={CLEAN_STATE['steps_done']}\")\n", - "print(f\"Vorticity stats: min={vort_clean.min():+.6e}, max={vort_clean.max():+.6e}\")\n", - "print(f\"Saved cache: {CFD_PARAMS['cache_npz']}\")\n", - "print(f\"Saved checkpoint: {CFD_PARAMS['checkpoint_path']}\")" + "print(f\"Saved streakline image: {streak_png}\")\n", + "print(f\"Saved vorticity image: {vort_png}\")\n", + "print(\n", + " f\"Vorticity stats: min={vort_end.min():+.6e}, max={vort_end.max():+.6e} \"\n", + " f\"(plot range [{vort_info['vmin']:+.3e}, {vort_info['vmax']:+.3e}])\"\n", + ")\n", + "print(f\"Saved streakline meta: {meta_path}\")\n", + "print(f\"Saved checkpoint: {CFD_PARAMS['checkpoint_path']}\")" ] }, { @@ -1224,57 +567,40 @@ } ], "source": [ - "# ==================== CLEAN 6. Plot only (repeat this cell) ====================\n", - "if \"vort_clean\" not in globals():\n", - " cache_path = Path(CFD_PARAMS[\"cache_npz\"])\n", - " if not cache_path.exists():\n", - " raise RuntimeError(\"No cached field. Run CLEAN 5 first.\")\n", - " d = np.load(cache_path)\n", - " vort = d[\"vort\"]\n", - " step = int(d[\"step\"])\n", + "# ==================== CLEAN 6. Plot only (repeat) ====================\n", + "# Streakline\n", + "img_path = Path(CFD_PARAMS[\"output_streak_png\"])\n", + "if \"streak_image\" in CLEAN_STATE:\n", + " img = CLEAN_STATE[\"streak_image\"]\n", "else:\n", - " vort = vort_clean\n", - " step = int(CLEAN_STATE.get(\"steps_done\", -1))\n", + " if not img_path.exists():\n", + " raise RuntimeError(\"No streakline image. Run CLEAN 5 first.\")\n", + " img = plt.imread(img_path)\n", "\n", - "print(f\"Plotting step={step}, vort min={vort.min():+.6e}, max={vort.max():+.6e}\")\n", + "step = int(CLEAN_STATE.get(\"steps_done\", -1))\n", + "sim = CLEAN_STATE.get(\"sim\")\n", + "if sim is None:\n", + " raise RuntimeError(\"Simulation state not found. Run CLEAN 4 first.\")\n", + "print(f\"Plotting streakline at step={step}, image={img_path}\")\n", "\n", - "vmin = -2e-3\n", - "vmax = 2e-3\n", - "bins = int(PLOT_PARAMS[\"bins\"])\n", - "if bins < 3:\n", - " raise ValueError(\"PLOT_PARAMS['bins'] must be >= 3\")\n", + "fig = plt.figure(figsize=(sim.lbm_cfg.nx / 100.0, sim.lbm_cfg.ny / 100.0), dpi=100)\n", + "ax = fig.add_axes([0.0, 0.0, 1.0, 1.0])\n", + "ax.axis(\"off\")\n", + "ax.imshow(img, origin=\"lower\", interpolation=\"nearest\", aspect=\"auto\")\n", + "plt.show()\n", "\n", - "if vmin is None or vmax is None:\n", - " abs_max = max(float(np.nanmax(np.abs(vort))), 1e-6)\n", - " if vmin is None:\n", - " vmin = -abs_max\n", - " if vmax is None:\n", - " vmax = abs_max\n", - "\n", - "if vmin >= vmax:\n", - " raise ValueError(f\"Invalid range: vmin={vmin}, vmax={vmax}\")\n", - "\n", - "levels = np.linspace(vmin, vmax, bins + 1, dtype=np.float32)\n", - "cmap = LinearSegmentedColormap.from_list(\"bwr_white_mid\", [\"#2166ac\", \"#ffffff\", \"#b2182b\"])\n", - "norm = BoundaryNorm(levels, ncolors=cmap.N, clip=True)\n", - "\n", - "# Figure matches grid, no axis, no colorbar, image occupies entire output, one pixel per grid cell\n", - "ny, nx = vort.shape\n", - "fig = plt.figure(figsize=(nx / 100, ny / 100), dpi=100) # Ensures 1 pixel per grid cell\n", - "plt.subplots_adjust(left=0, right=1, bottom=0, top=1)\n", - "plt.axis(\"off\")\n", - "plt.imshow(\n", - " vort,\n", - " origin=\"lower\",\n", - " cmap=cmap,\n", - " norm=norm,\n", - " interpolation=\"nearest\",\n", - " aspect=\"auto\"\n", - ")\n", - "out = Path(CFD_PARAMS[\"output_vort_png\"])\n", - "out.parent.mkdir(parents=True, exist_ok=True)\n", - "fig.savefig(out, bbox_inches=\"tight\", pad_inches=0)\n", - "print(f\"Saved figure: {out}\")\n", + "vort_path = Path(CFD_PARAMS[\"output_vort_png\"])\n", + "if \"vort_image\" in CLEAN_STATE:\n", + " vort_img = CLEAN_STATE[\"vort_image\"]\n", + "elif vort_path.exists():\n", + " vort_img = plt.imread(vort_path)\n", + "else:\n", + " raise RuntimeError(\"No vorticity image. Run CLEAN 5 first.\")\n", + "print(f\"Vorticity image: {vort_path}\")\n", + "fig2 = plt.figure(figsize=(sim.lbm_cfg.nx / 100.0, sim.lbm_cfg.ny / 100.0), dpi=100)\n", + "ax2 = fig2.add_axes([0.0, 0.0, 1.0, 1.0])\n", + "ax2.axis(\"off\")\n", + "ax2.imshow(vort_img, origin=\"lower\", interpolation=\"nearest\", aspect=\"auto\")\n", "plt.show()" ] }, diff --git a/tests/inlet_plan.md b/tests/inlet_plan.md deleted file mode 100644 index cbfd430..0000000 --- a/tests/inlet_plan.md +++ /dev/null @@ -1,75 +0,0 @@ -## Inlet module refactor plan - -The inlet module should be treated as a source-state generator for west ghost nodes, not as a grab-bag of formulas inside one boundary file. In the current solver, inlet cells are `SOLID | BC_INLET` ghost nodes whose state is later pulled by the first interior column. That semantic should stay explicit in the code structure. - -## Current target structure - -- `boundary/inlet/common` - - shared profile logic such as `inlet_target_u(y)` - - shared rho-closure helpers for west velocity inlet - - helper stating whether a scheme requires post-BC ghost collision -- `boundary/inlet/zou_he_local` - - local on-site Zou-He source-state reconstruction - - D2Q9 and D3Q19 west inlet versions -- `boundary/inlet/channel_stabilized` - - donor-based stabilized inlet for high blockage or conservative production runs - - D2Q9 and D3Q19 west inlet versions -- `boundary/inlet/equilibrium` - - full `feq` source-state construction from local rho closure and target velocity - - D2Q9 and D3Q19 versions -- `boundary/inlet/regularized` - - local macro state plus damped donor NEQ on incoming directions - - D2Q9 and D3Q19 versions -- `boundary/outlet/pressure_neq` - - pressure outlet and zero-gradient outlet implementations -- `boundary/inlet_outlet` - - compile-time dispatch only - - no long method bodies - - streaming-specific donor assembly and method selection - -## Design rule for each inlet scheme - -Each scheme should answer the same question: - -- given a west ghost node after pull loading, what source state should be stored there for the next interior pull - -That makes the interface stable even when methods differ in how much donor information they use. - -## Scheme meanings - -| Scheme | Main idea | Best fit | Main caution | -|---|---|---|---| -| `zou_he_local` | textbook local algebraic closure | MRT, research comparisons, clean local baseline | in ghost-source semantics it requires post-BC ghost collision and can be noisy for high-omega SRT | -| `channel_stabilized` | donor-based stabilized inlet | high blockage, production robustness, conservative benchmark work | less pure as a local boundary method | -| `equilibrium` | write full `feq` source state | robust SRT baseline, simple ghost-source compatibility | may suppress inlet NEQ too aggressively for some validation targets | -| `regularized` | local macro state plus damped incoming donor NEQ | middle ground between `equilibrium` and donor-heavy methods | still an experimental family and may need tuning | - -## Collision policy - -Post-BC ghost collision must be owned by the scheme, not hard-coded as a general inlet rule. - -Current policy: - -- `zou_he_local` requires post-BC ghost collision -- `channel_stabilized` does not -- `equilibrium` does not -- `regularized` does not - -This should remain encoded through a helper such as `inlet_scheme_uses_post_collision_ghost()` rather than scattered `INLET_SCHEME == ...` checks. - -## Why this split matters - -The earlier instability work showed that the main difficulty was not a single formula error. The real issue was mixing methods that assume different node semantics: - -- local fluid-boundary formulas such as Zou-He -- ghost-source node architecture in the solver -- different collision-model tolerances, especially SRT versus MRT - -Keeping each inlet method in its own file makes those assumptions visible and lowers the chance of mixing donor and ghost semantics by accident. - -## Next cleanups worth doing later - -1. Split outlet schemes into separate files as more outlet variants are added. -2. If inlet junction handling grows, move row-specific or corner-specific logic into dedicated helpers instead of embedding it inside the main schemes. -3. When validation settles, add a small test matrix document mapping recommended schemes to benchmark families and collision models. -4. If the solver later moves away from ghost-source inlet nodes, keep this folder layout but replace the per-scheme internals rather than rebuilding the whole dispatch layer. diff --git a/tests/run_exp_ctrl_matrix_streakline.py b/tests/run_exp_ctrl_matrix_streakline.py new file mode 100644 index 0000000..259cb01 --- /dev/null +++ b/tests/run_exp_ctrl_matrix_streakline.py @@ -0,0 +1,233 @@ +# CelerisLab/tests/run_exp_ctrl_matrix_streakline.py +"""Streakline (final-step particle cloud) for exp_ctrl_matrix cases. + +Runs full CFD to FIXED_STEPS, injects particles only in the last STREAK_WINDOW steps, +renders streakline at the final step (not path history). +""" + +from __future__ import annotations + +import argparse +import json +import sys +from pathlib import Path + +import numpy as np + +_REPO = Path(__file__).resolve().parents[1] +sys.path.insert(0, str(_REPO / "src")) +sys.path.insert(0, str(_REPO / "tests")) + +import run_exp_ctrl_matrix_vorticity as vort +from CelerisLab import Simulation +from CelerisLab.common.streakline import ( + FlowFrame, + IntegratorConfig, + ReleaseConfig, + advance_particles_between_frames, + build_release_points, + cylinders_from_triangle_layout, + render_streakline_density, +) + +DIAMETER_CELLS = vort.DIAMETER_CELLS +DEFAULT_OUT = _REPO / "tests" / "output" / "exp_ctrl_matrix_streak_ny300" +STREAK_WINDOW_STEPS = 20_000 +STREAK_SAMPLE_EVERY = 50 +STREAK_AGE_DECAY = 100_000.0 +STREAK_BLUR_SIGMA = 0.8 + + +def build_release_points_for_triangle(layout: dict) -> np.ndarray: + release_x = min(layout["x_apex"], layout["x_rear"]) - 4.0 * DIAMETER_CELLS + y_low_edge = layout["y_lower"] - layout["radius_lb"] + y_high_edge = layout["y_upper"] + layout["radius_lb"] + ys = np.linspace(y_low_edge, y_high_edge, 4, dtype=np.float64) + return np.column_stack([np.full(4, release_x, dtype=np.float64), ys]) + + +def _apply_body_actions(sim: Simulation, a1: float, a2: float, a3: float, u_lb: float) -> None: + w1 = vort._action_to_omega_lb(a1, u_lb) + w2 = vort._action_to_omega_lb(a2, u_lb) + w3 = vort._action_to_omega_lb(a3, u_lb) + if vort.SWAP_ACTION23_BODIES: + vort._set_body_omegas(sim, w1, w3, w2) + else: + vort._set_body_omegas(sim, w1, w2, w3) + + +def run_streak_case( + case_id: str, + slug: str, + features: dict, + *, + out_dir: Path, + total_steps: int, + streak_window: int, + sample_every: int, + report_every: int, +) -> dict: + streak_start = max(0, int(total_steps) - int(streak_window)) + compat = vort._ensure_compat_config(vort.CONFIG_PATH) + sim = Simulation(compat) + layout = vort._add_triangle_cylinders(sim) + sim.initialize() + u_lb = float(sim.lbm_cfg.velocity) + dt_phys = (vort.CYLINDER_DIAMETER_M / DIAMETER_CELLS) * (u_lb / vort.INLET_U_PHYS_M_S) + cylinders = cylinders_from_triangle_layout(layout) + + release_cfg = ReleaseConfig( + mode="strip", + line_count=1, + line_span=0.0, + downstream_count=5, + downstream_spacing=1.0, + inject_per_seed=1, + ) + integrator_cfg = IntegratorConfig(alpha_t=0.25, alpha_x=0.40, max_particle_age=None) + base_release = build_release_points_for_triangle(layout) + release_points = build_release_points(base_release, release_cfg) + + particles = np.empty((0, 2), dtype=np.float64) + ages = np.empty((0,), dtype=np.float64) + prev_frame: FlowFrame | None = None + + print( + f"--- {case_id} {slug} steps={total_steps} streak_window={streak_window} " + f"(inject from step {streak_start}) ---" + ) + + for step in range(total_steps): + t_phys = step * dt_phys + a1, a2, a3 = vort._actions_at_time(t_phys, features) + _apply_body_actions(sim, a1, a2, a3, u_lb) + sim.run(1) + + if step >= streak_start and (step + 1) % sample_every == 0: + macro = sim.get_macroscopic() + curr_frame = FlowFrame( + step=int(step + 1), + ux=np.asarray(macro["ux"], dtype=np.float64), + uy=np.asarray(macro["uy"], dtype=np.float64), + ) + new_particles = np.repeat(release_points, release_cfg.inject_per_seed, axis=0) + if particles.size: + particles = np.vstack([particles, new_particles]) + ages = np.concatenate([ages, np.zeros(new_particles.shape[0], dtype=np.float64)]) + else: + particles = new_particles.copy() + ages = np.zeros(new_particles.shape[0], dtype=np.float64) + + if prev_frame is not None and particles.size: + particles, ages, _, _ = advance_particles_between_frames( + particles, + ages, + prev_frame, + curr_frame, + nx=int(sim.lbm_cfg.nx), + ny=int(sim.lbm_cfg.ny), + cfg=integrator_cfg, + cylinders=cylinders, + ) + prev_frame = curr_frame + + if report_every > 0 and (step + 1) % report_every == 0: + print( + f" step {step+1}/{total_steps} a=({a1:+.5f},{a2:+.5f},{a3:+.5f}) " + f"particles={particles.shape[0]}" + ) + + if particles.size == 0: + raise RuntimeError(f"{case_id}: no particles in streak window; lower sample_every.") + + png = out_dir / f"streakline_{case_id}_{slug}.png" + render_info = render_streakline_density( + particles, + ages, + nx=int(sim.lbm_cfg.nx), + ny=int(sim.lbm_cfg.ny), + out_path=str(png), + cylinders=cylinders, + age_decay_steps=STREAK_AGE_DECAY, + blur_sigma=STREAK_BLUR_SIGMA, + minimal_axes=True, + background_color=(1.0, 1.0, 1.0), + streak_color=(1.0, 0.0, 0.0), + show_release_points=False, + ) + sim.close() + + summary = { + "case_id": case_id, + "slug": slug, + "total_steps": int(total_steps), + "streak_window_steps": int(streak_window), + "streak_start_step": int(streak_start), + "sample_every": int(sample_every), + "particle_count_final": int(particles.shape[0]), + "release_points_dense": int(release_points.shape[0]), + "streak_png": str(png), + "swap_action23_bodies": bool(vort.SWAP_ACTION23_BODIES), + "render": render_info, + } + with (out_dir / f"summary_{case_id}_{slug}.json").open("w", encoding="utf-8") as f: + json.dump(summary, f, indent=2) + print(f" saved {png} particles={particles.shape[0]}") + return summary + + +def main() -> int: + ap = argparse.ArgumentParser(description="exp_ctrl_matrix streakline batch") + ap.add_argument("--out-dir", type=str, default=str(DEFAULT_OUT)) + ap.add_argument("--steps", type=int, default=vort.FIXED_STEPS) + ap.add_argument("--streak-window", type=int, default=STREAK_WINDOW_STEPS) + ap.add_argument("--sample-every", type=int, default=STREAK_SAMPLE_EVERY) + ap.add_argument("--report-every", type=int, default=20000) + ap.add_argument("--cases", type=str, default="") + args = ap.parse_args() + + out_dir = Path(args.out_dir) + out_dir.mkdir(parents=True, exist_ok=True) + selected = {c.strip() for c in args.cases.split(",") if c.strip()} if args.cases else None + + with vort.CONFIG_PATH.open("r", encoding="utf-8") as f: + grid = json.load(f)["grid"] + print( + f"Output: {out_dir} | grid={grid['nx']}x{grid['ny']} | steps={args.steps} | " + f"streak last {args.streak_window} steps, sample_every={args.sample_every}" + ) + + summaries = [] + for case_id, slug, features in vort.CONTROL_CASES: + if selected and case_id not in selected: + continue + summaries.append( + run_streak_case( + case_id, + slug, + features, + out_dir=out_dir, + total_steps=int(args.steps), + streak_window=int(args.streak_window), + sample_every=int(args.sample_every), + report_every=int(args.report_every), + ) + ) + + manifest = { + "grid": grid, + "steps": int(args.steps), + "streak_window_steps": int(args.streak_window), + "sample_every": int(args.sample_every), + "swap_action23_bodies": bool(vort.SWAP_ACTION23_BODIES), + "cases": summaries, + } + manifest_path = out_dir / "manifest.json" + with manifest_path.open("w", encoding="utf-8") as f: + json.dump(manifest, f, indent=2) + print(f"Manifest: {manifest_path}") + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/tests/run_exp_ctrl_matrix_vorticity.py b/tests/run_exp_ctrl_matrix_vorticity.py new file mode 100644 index 0000000..84869fb --- /dev/null +++ b/tests/run_exp_ctrl_matrix_vorticity.py @@ -0,0 +1,421 @@ +# CelerisLab/tests/run_exp_ctrl_matrix_vorticity.py +"""Batch vorticity images for three-cylinder control matrix (exp_ctrl_matrix.md). + +Runs each control law to steady-like state, saves final vorticity PNG only (no streaklines). +""" + +from __future__ import annotations + +import argparse +import json +import math +import os +import sys +import tempfile +from pathlib import Path +from typing import Any, Dict, List, Tuple + +import numpy as np +import pycuda.driver as cuda + +_REPO = Path(__file__).resolve().parents[1] +sys.path.insert(0, str(_REPO / "src")) + +from CelerisLab import Simulation +from CelerisLab.common.streakline import ( + compute_vorticity, + cylinders_from_triangle_layout, + render_vorticity_field, +) + +INLET_U_PHYS_M_S = 0.009028 +CYLINDER_DIAMETER_M = 0.010 +CENTER_SPACING_M = 0.015 +DIAMETER_CELLS = 20.0 +RAMP_TIME_S = 5.0 +INITIAL_ACTIONS_M_S = (0.0, 0.0, 0.0) +OMEGA_SIGN_FROM_ACTION = -1.0 +VORT_VMIN = -0.003 +VORT_VMAX = 0.003 + +CONFIG_PATH = _REPO / "src/CelerisLab/configs/config_lbm_three_cylinder_triangle.json" +DEFAULT_OUT = _REPO / "tests" / "output" / "exp_ctrl_matrix_vort_ny300" +FIXED_STEPS = 100000 # keep constant while grid changes +# Body order: 0=apex, 1=rear-lower(y_lower), 2=rear-upper(y_upper); swap action2/action3 targets. +SWAP_ACTION23_BODIES = True + +# From tests/exp_ctrl_matrix.md (SIGNAL_FEATURES0 .. 6) +CONTROL_CASES: List[Tuple[str, str, Dict[str, Any]]] = [ + ( + "C0", + "no_ctrl", + { + "action1": {"mean": 0.0, "components": [(0.1354, 0.0, 1.600)]}, + "action2": {"mean": 0.0, "components": [(0.1354, 0.0, 2.099)]}, + "action3": {"mean": 0.0, "components": [(0.1354, 0.0, 1.639)]}, + }, + ), + ( + "C1", + "stealth", + { + "action1": {"mean": 0.0, "components": [(0.1354, 0.0, 1.600)]}, + "action2": {"mean": -0.01806, "components": [(0.1354, 0.0, 2.099)]}, + "action3": {"mean": 0.01806, "components": [(0.1354, 0.0, 1.639)]}, + }, + ), + ( + "C2", + "deceit", + { + "action1": {"mean": 0.0, "components": [(0.1354, 0.0026, 1.600)]}, + "action2": { + "mean": -0.008730, + "components": [(0.1354, 0.0045, 2.099), (0.2708, 0.0010, 0.612)], + }, + "action3": { + "mean": 0.008730, + "components": [(0.1354, 0.0045, 1.639), (0.2708, 0.0010, -2.962)], + }, + }, + ), + ( + "C3", + "deceit_multi", + { + "action1": { + "mean": 0.0, + "components": [(0.1354, 0.0029, -2.619), (0.2708, 0.0008, 2.856)], + }, + "action2": { + "mean": -0.0140, + "components": [ + (0.1354, 0.0050, -0.933), + (0.2708, 0.0010, 0.801), + (0.1806, 0.0003, 1.854), + ], + }, + "action3": { + "mean": 0.014, + "components": [ + (0.1354, 0.0050, -1.398), + (0.2708, 0.0010, 2.208), + (0.1806, 0.0003, 1.810), + ], + }, + }, + ), + ( + "C4", + "deceit_f1p5", + { + "action1": {"mean": 0.0, "components": [(0.2031, 0.0026, 1.600)]}, + "action2": { + "mean": -0.008730, + "components": [(0.2031, 0.0045, 2.099), (0.4062, 0.0010, 0.612)], + }, + "action3": { + "mean": 0.008730, + "components": [(0.2031, 0.0045, 1.639), (0.4062, 0.0010, -2.962)], + }, + }, + ), + ( + "C5", + "deceit_multi_f1p5", + { + "action1": { + "mean": 0.0, + "components": [(0.2031, 0.0029, -2.619), (0.4062, 0.0008, 2.856)], + }, + "action2": { + "mean": -0.0140, + "components": [ + (0.2031, 0.0050, -0.933), + (0.4062, 0.0010, 0.801), + (0.2709, 0.0003, 1.854), + ], + }, + "action3": { + "mean": 0.014, + "components": [ + (0.2031, 0.0050, -1.398), + (0.4062, 0.0010, 2.208), + (0.2709, 0.0003, 1.810), + ], + }, + }, + ), + ( + "C6", + "deceit_f2", + { + "action1": { + "mean": 0.0, + "components": [(0.2708, 0.0044, -2.619), (0.8124, 0.0012, 2.856)], + }, + "action2": { + "mean": -0.014, + "components": [ + (0.2708, 0.0075, -0.933), + (0.8124, 0.0015, 0.801), + (0.5418, 0.0005, 1.854), + ], + }, + "action3": { + "mean": 0.014, + "components": [ + (0.2708, 0.0075, -1.398), + (0.8124, 0.0015, 2.208), + (0.5418, 0.0005, 1.810), + ], + }, + }, + ), +] + + +def _ensure_compat_config(config_path: Path, preferred_scheme: str = "regularized") -> str: + with config_path.open("r", encoding="utf-8") as f: + cfg = json.load(f) + method = cfg.setdefault("method", {}) + inlet = method.setdefault("inlet", {}) + outlet = method.setdefault("outlet", {}) + changed = False + if "scheme" not in inlet: + inlet["scheme"] = preferred_scheme + changed = True + if "regularized_neq_damp" not in inlet: + inlet["regularized_neq_damp"] = 0.5 + changed = True + if "blend_alpha" not in outlet: + outlet["blend_alpha"] = 0.7 + changed = True + if "backflow_clamp" not in outlet: + outlet["backflow_clamp"] = True + changed = True + if not changed: + return str(config_path) + tmp = tempfile.NamedTemporaryFile( + mode="w", suffix="_compat_lbm.json", delete=False, encoding="utf-8" + ) + with tmp: + json.dump(cfg, tmp, indent=4) + return tmp.name + + +def _triangle_layout(cfg) -> dict: + dx_phys = CYLINDER_DIAMETER_M / DIAMETER_CELLS + spacing_lb = CENTER_SPACING_M / dx_phys + radius_lb = DIAMETER_CELLS / 2.0 + y_center = 0.5 * (cfg.ny - 1) + x_cluster_center = cfg.nx / 3.0 + x_apex = x_cluster_center - (math.sqrt(3.0) / 3.0) * spacing_lb + x_rear = x_apex + (math.sqrt(3.0) / 2.0) * spacing_lb + return { + "x_apex": x_apex, + "x_rear": x_rear, + "y_center": y_center, + "y_upper": y_center + 0.5 * spacing_lb, + "y_lower": y_center - 0.5 * spacing_lb, + "radius_lb": radius_lb, + } + + +def _add_triangle_cylinders(sim: Simulation) -> dict: + layout = _triangle_layout(sim.lbm_cfg) + sim.add_cylinder(center=(layout["x_apex"], layout["y_center"]), radius=layout["radius_lb"]) + sim.add_cylinder(center=(layout["x_rear"], layout["y_lower"]), radius=layout["radius_lb"]) + sim.add_cylinder(center=(layout["x_rear"], layout["y_upper"]), radius=layout["radius_lb"]) + return layout + + +def _generate_signal(t_phys: float, feature: dict) -> float: + value = float(feature["mean"]) + for freq_hz, amp, phase in feature["components"]: + value += amp * math.cos(2.0 * math.pi * freq_hz * t_phys + phase) + return value + + +def _ramp_factor(elapsed_s: float) -> float: + if elapsed_s <= 0.0: + return 0.0 + if elapsed_s >= RAMP_TIME_S: + return 1.0 + x = elapsed_s / RAMP_TIME_S + return 0.5 * (1.0 - math.cos(math.pi * x)) + + +def _actions_at_time(t_phys: float, features: dict) -> Tuple[float, float, float]: + s1 = _generate_signal(t_phys, features["action1"]) + s2 = _generate_signal(t_phys, features["action2"]) + s3 = _generate_signal(t_phys, features["action3"]) + r = _ramp_factor(t_phys) + a1 = INITIAL_ACTIONS_M_S[0] * (1.0 - r) + s1 * r + a2 = INITIAL_ACTIONS_M_S[1] * (1.0 - r) + s2 * r + a3 = INITIAL_ACTIONS_M_S[2] * (1.0 - r) + s3 * r + return a1, a2, a3 + + +def _action_to_omega_lb(action_m_s: float, u_lb: float) -> float: + u_surf_lb = action_m_s * (u_lb / INLET_U_PHYS_M_S) + r_lb = DIAMETER_CELLS / 2.0 + return OMEGA_SIGN_FROM_ACTION * (u_surf_lb / r_lb) + + +def _set_body_omegas(sim: Simulation, omega0: float, omega1: float, omega2: float) -> None: + bodies = sim.bodies + dim = sim.lbm_cfg.dim + slot = 3 * dim + bodies.action.fill(0.0) + bodies.action[(0 * slot) + slot - 1] = np.float32(omega0) + bodies.action[(1 * slot) + slot - 1] = np.float32(omega1) + bodies.action[(2 * slot) + slot - 1] = np.float32(omega2) + cuda.memcpy_htod(bodies.action_gpu, bodies.action) + + +def _default_steps(nx: int, u_lb: float, step_multiplier: float) -> int: + base = int(round(2.0 * float(nx) / (3.0 * float(u_lb)))) + return int(round(base * float(step_multiplier))) + + +def run_case( + case_id: str, + slug: str, + features: dict, + *, + out_dir: Path, + steps: int, + report_every: int, +) -> dict: + compat = _ensure_compat_config(CONFIG_PATH) + sim = Simulation(compat) + layout = _add_triangle_cylinders(sim) + sim.initialize() + u_lb = float(sim.lbm_cfg.velocity) + dx_phys = CYLINDER_DIAMETER_M / DIAMETER_CELLS + dt_phys = dx_phys * (u_lb / INLET_U_PHYS_M_S) + cylinders = cylinders_from_triangle_layout(layout) + + print(f"--- {case_id} {slug} steps={steps} u_lb={u_lb} dt_phys={dt_phys} ---") + for i in range(steps): + t_phys = i * dt_phys + a1, a2, a3 = _actions_at_time(t_phys, features) + w1 = _action_to_omega_lb(a1, u_lb) + w2 = _action_to_omega_lb(a2, u_lb) + w3 = _action_to_omega_lb(a3, u_lb) + if SWAP_ACTION23_BODIES: + _set_body_omegas(sim, w1, w3, w2) + else: + _set_body_omegas(sim, w1, w2, w3) + sim.run(1) + if report_every > 0 and (i + 1) % report_every == 0: + print(f" step {i+1}/{steps} a=({a1:+.5f},{a2:+.5f},{a3:+.5f})") + + macro = sim.get_macroscopic() + vort = compute_vorticity(macro["ux"], macro["uy"]) + png = out_dir / f"vorticity_{case_id}_{slug}.png" + ckpt = out_dir / f"state_{case_id}_{slug}.h5" + sim.save_checkpoint(str(ckpt)) + + render_info = render_vorticity_field( + vort, + nx=int(sim.lbm_cfg.nx), + ny=int(sim.lbm_cfg.ny), + out_path=str(png), + cylinders=cylinders, + vmin=VORT_VMIN, + vmax=VORT_VMAX, + minimal_axes=True, + ) + sim.close() + + summary = { + "case_id": case_id, + "slug": slug, + "steps": int(steps), + "u_lb": u_lb, + "dt_phys": dt_phys, + "vort_png": str(png), + "checkpoint": str(ckpt), + "vort_range_data": [float(vort.min()), float(vort.max())], + "vort_plot_range": [VORT_VMIN, VORT_VMAX], + "swap_action23_bodies": bool(SWAP_ACTION23_BODIES), + "render": render_info, + } + with (out_dir / f"summary_{case_id}_{slug}.json").open("w", encoding="utf-8") as f: + json.dump(summary, f, indent=2) + print(f" saved {png}") + return summary + + +def main() -> int: + ap = argparse.ArgumentParser(description="exp_ctrl_matrix vorticity batch") + ap.add_argument("--out-dir", type=str, default=str(DEFAULT_OUT)) + ap.add_argument( + "--step-multiplier", + type=float, + default=2.0, + help="Steps = multiplier * round(2*nx/(3*u_lb)); use 2.0 after halving nx/ny.", + ) + ap.add_argument( + "--steps", + type=int, + default=FIXED_STEPS, + help=f"Total LBM steps (default {FIXED_STEPS}, held fixed across grid tweaks).", + ) + ap.add_argument("--report-every", type=int, default=20000) + ap.add_argument("--cases", type=str, default="", help="Comma list e.g. C0,C1 or empty=all.") + args = ap.parse_args() + + out_dir = Path(args.out_dir) + out_dir.mkdir(parents=True, exist_ok=True) + + selected = {c.strip() for c in args.cases.split(",") if c.strip()} if args.cases else None + summaries = [] + + with CONFIG_PATH.open("r", encoding="utf-8") as f: + grid_cfg = json.load(f)["grid"] + nx = int(grid_cfg["nx"]) + ny = int(grid_cfg["ny"]) + u_lb = 0.04 + base_steps = int(round(2.0 * nx / (3.0 * u_lb))) + steps = int(args.steps) if int(args.steps) > 0 else _default_steps(nx, u_lb, args.step_multiplier) + print( + f"Output: {out_dir} | grid={nx}x{ny} | base_steps={base_steps} " + f"x{args.step_multiplier} -> {steps} | vort [{VORT_VMIN}, {VORT_VMAX}]" + ) + + for case_id, slug, features in CONTROL_CASES: + if selected and case_id not in selected: + continue + summaries.append( + run_case( + case_id, + slug, + features, + out_dir=out_dir, + steps=steps, + report_every=int(args.report_every), + ) + ) + + manifest = { + "grid": {"nx": nx, "ny": ny}, + "base_steps": base_steps, + "step_multiplier": float(args.step_multiplier), + "steps": steps, + "vort_vmin": VORT_VMIN, + "vort_vmax": VORT_VMAX, + "swap_action23_bodies": bool(SWAP_ACTION23_BODIES), + "cases": summaries, + } + manifest_path = out_dir / "manifest.json" + with manifest_path.open("w", encoding="utf-8") as f: + json.dump(manifest, f, indent=2) + print(f"Manifest: {manifest_path}") + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/tests/run_inlet_channel_diagnostic.py b/tests/run_inlet_channel_diagnostic.py deleted file mode 100644 index 0f88a3f..0000000 --- a/tests/run_inlet_channel_diagnostic.py +++ /dev/null @@ -1,574 +0,0 @@ -# CelerisLab/tests/run_inlet_channel_diagnostic.py -"""Empty-channel inlet diagnostic: field snapshots and line profiles. - -Runs no-cylinder channel flows to isolate inlet / wall / outlet effects before -adding a body. See user matrix in module docstring sections A–C. - -Usage:: - - conda run -n pycuda_3_10 python tests/run_inlet_channel_diagnostic.py --part all - conda run -n pycuda_3_10 python tests/run_inlet_channel_diagnostic.py --part a - conda run -n pycuda_3_10 python tests/run_inlet_channel_diagnostic.py --part b --nx 401 --ny 201 - -Output (default ``tests/output/inlet_channel_diag/``):: - - A_baseline/{SRT,MRT}/snapshots/step_XXXXXX.{npz,png} - B_matrix/caseNN_.../snapshots/... - B_matrix/caseNN_.../lines/ux_lines_stepXXXXXX.png - summary.csv -""" - -from __future__ import annotations - -import argparse -import csv -import json -import os -import sys -import tempfile -from dataclasses import dataclass -from typing import Any, Dict, Iterable, List, Optional, Sequence, Tuple - -import numpy as np - -_REPO = os.path.abspath(os.path.join(os.path.dirname(__file__), "..")) -_DEFAULT_LBM = os.path.join(_REPO, "src", "CelerisLab", "configs", "config_lbm.json") - -# Default snapshot steps for parts A and B. -DEFAULT_SNAPSHOT_STEPS: Tuple[int, ...] = (100, 500, 1000, 1500, 2000) - - -@dataclass(frozen=True) -class CaseSpec: - """One empty-channel configuration.""" - - case_id: str - label: str - inlet_scheme: str - y_wall_bc: str - outlet_mode: str - collision: str = "MRT" - - -def _load_json(path: str) -> dict: - with open(path, "r", encoding="utf-8") as f: - return json.load(f) - - -def _write_json(path: str, payload: dict) -> None: - os.makedirs(os.path.dirname(path) or ".", exist_ok=True) - with open(path, "w", encoding="utf-8") as f: - json.dump(payload, f, indent=2) - - -def vorticity_z(ux: np.ndarray, uy: np.ndarray) -> np.ndarray: - """ωz = ∂uy/∂x − ∂ux/∂y on (ny, nx) arrays.""" - ux = np.asarray(ux, dtype=np.float64) - uy = np.asarray(uy, dtype=np.float64) - return np.gradient(uy, axis=1) - np.gradient(ux, axis=0) - - -def _line_y_indices(ny: int) -> List[Tuple[int, str]]: - return [ - (1, "y1"), - (ny // 2, f"y{ny // 2}"), - (ny - 2, f"y{ny - 2}"), - ] - - -def _build_cfg( - base: dict, - *, - nx: int, - ny: int, - collision: str, - inlet_scheme: str, - inlet_profile: str, - y_wall_bc: str, - outlet_mode: str, - velocity: float, - viscosity: float, -) -> dict: - cfg = json.loads(json.dumps(base)) - cfg["grid"]["nx"] = int(nx) - cfg["grid"]["ny"] = int(ny) - cfg["grid"]["nz"] = 1 - cfg["physics"]["velocity"] = float(velocity) - cfg["physics"]["viscosity"] = float(viscosity) - cfg["physics"]["rho"] = 1.0 - cfg["method"]["collision"] = str(collision).upper() - cfg["method"]["streaming"] = "double_buffer" - cfg["method"]["store_precision"] = "FP32" - cfg["method"]["les"]["enabled"] = False - cfg["method"]["inlet"]["profile"] = str(inlet_profile) - cfg["method"]["inlet"]["scheme"] = str(inlet_scheme) - cfg["method"]["y_wall_bc"] = str(y_wall_bc) - cfg["method"]["outlet"]["mode"] = str(outlet_mode) - return cfg - - -def _field_stats(rho: np.ndarray, ux: np.ndarray, vort: np.ndarray) -> Dict[str, float]: - def _f(a: np.ndarray) -> float: - b = a[np.isfinite(a)] - return float("nan") if b.size == 0 else float(np.max(np.abs(b))) - - return { - "rho_min": float(np.nanmin(rho)) if np.isfinite(rho).any() else float("nan"), - "rho_max": float(np.nanmax(rho)) if np.isfinite(rho).any() else float("nan"), - "ux_max": _f(ux), - "vort_max": _f(vort), - "finite": bool(np.isfinite(rho).all() and np.isfinite(ux).all()), - } - - -def _save_snapshot_npz( - path: str, - *, - step: int, - rho: np.ndarray, - ux: np.ndarray, - uy: np.ndarray, - vort: np.ndarray, - meta: dict, -) -> None: - os.makedirs(os.path.dirname(path), exist_ok=True) - np.savez_compressed( - path, - rho=np.asarray(rho, dtype=np.float32), - ux=np.asarray(ux, dtype=np.float32), - uy=np.asarray(uy, dtype=np.float32), - vort=np.asarray(vort, dtype=np.float32), - step=np.int32(step), - meta_json=np.asarray(json.dumps(meta)), - ) - - -def _save_field_pngs( - out_dir: str, - prefix: str, - *, - rho: np.ndarray, - ux: np.ndarray, - vort: np.ndarray, - title: str, -) -> List[str]: - try: - import matplotlib - - matplotlib.use("Agg") - import matplotlib.pyplot as plt - except ImportError: - return [] - - os.makedirs(out_dir, exist_ok=True) - ny, nx = rho.shape - extent = (0, nx - 1, 0, ny - 1) - paths: List[str] = [] - - def _one(arr: np.ndarray, name: str, cmap: str, sym: bool) -> None: - a = np.asarray(arr, dtype=np.float64) - fin = a[np.isfinite(a)] - if fin.size == 0: - vmin, vmax = -1.0, 1.0 - elif sym: - v = float(np.percentile(np.abs(fin), 99.5)) or 1.0 - vmin, vmax = -v, v - else: - vmin = float(np.percentile(fin, 0.5)) - vmax = float(np.percentile(fin, 99.5)) - if vmax <= vmin: - vmax = vmin + 1.0 - fig, ax = plt.subplots(figsize=(min(16.0, max(8.0, nx / 80.0)), min(8.0, max(3.0, ny / 50.0)))) - im = ax.imshow(a, origin="lower", aspect="auto", cmap=cmap, vmin=vmin, vmax=vmax, extent=extent) - ax.set_xlabel("x") - ax.set_ylabel("y") - ax.set_title(f"{title} — {name}") - fig.colorbar(im, ax=ax, fraction=0.046, pad=0.04) - fig.tight_layout() - p = os.path.join(out_dir, f"{prefix}_{name}.png") - fig.savefig(p, dpi=140, bbox_inches="tight") - plt.close(fig) - paths.append(p) - - _one(rho, "rho", "viridis", sym=False) - _one(ux, "ux", "RdBu_r", sym=True) - _one(vort, "vort", "RdBu_r", sym=True) - return paths - - -def _save_line_plots( - path: str, - *, - rho: np.ndarray, - ux: np.ndarray, - step: int, - case_label: str, - y_rows: Sequence[Tuple[int, str]], -) -> None: - try: - import matplotlib - - matplotlib.use("Agg") - import matplotlib.pyplot as plt - except ImportError: - return - - ny, nx = rho.shape - x = np.arange(nx, dtype=np.float64) - - fig, axes = plt.subplots(2, 1, figsize=(min(14.0, max(8.0, nx / 60.0)), 7.0), sharex=True) - for y_idx, y_lab in y_rows: - y_idx = int(np.clip(y_idx, 0, ny - 1)) - axes[0].plot(x, ux[y_idx, :], label=y_lab, linewidth=1.2) - axes[1].plot(x, rho[y_idx, :], label=y_lab, linewidth=1.2) - - axes[0].set_ylabel("u_x") - axes[0].legend(loc="best", fontsize=8) - axes[0].grid(True, alpha=0.3) - axes[1].set_ylabel("rho") - axes[1].set_xlabel("x (lattice)") - axes[1].legend(loc="best", fontsize=8) - axes[1].grid(True, alpha=0.3) - fig.suptitle(f"{case_label} — line profiles at step {step}") - fig.tight_layout() - os.makedirs(os.path.dirname(path) or ".", exist_ok=True) - fig.savefig(path, dpi=150, bbox_inches="tight") - plt.close(fig) - - -def _run_channel( - case: CaseSpec, - *, - base_cfg: dict, - nx: int, - ny: int, - velocity: float, - viscosity: float, - out_root: str, - snapshot_steps: Sequence[int], - max_step: int, - save_png: bool, - save_lines: bool, - stop_on_nan: bool, -) -> List[Dict[str, Any]]: - """Run one case; write snapshots and optional line plots. Return summary rows.""" - sys.path.insert(0, os.path.join(_REPO, "src")) - from CelerisLab import Simulation # noqa: WPS433 - - cfg = _build_cfg( - base_cfg, - nx=nx, - ny=ny, - collision=case.collision, - inlet_scheme=case.inlet_scheme, - inlet_profile="uniform", - y_wall_bc=case.y_wall_bc, - outlet_mode=case.outlet_mode, - velocity=velocity, - viscosity=viscosity, - ) - bdoc = {"objects": []} - - run_dir = os.path.join(out_root, case.case_id) - snap_dir = os.path.join(run_dir, "snapshots") - line_dir = os.path.join(run_dir, "lines") - os.makedirs(snap_dir, exist_ok=True) - if save_lines: - os.makedirs(line_dir, exist_ok=True) - - tmpd = tempfile.mkdtemp(prefix="inlet_diag_") - lbm_tmp = os.path.join(tmpd, "config_lbm.json") - body_tmp = os.path.join(tmpd, "config_body.json") - _write_json(lbm_tmp, cfg) - _write_json(body_tmp, bdoc) - - meta_base = { - "case_id": case.case_id, - "label": case.label, - "nx": nx, - "ny": ny, - "inlet_scheme": case.inlet_scheme, - "inlet_profile": "uniform", - "y_wall_bc": case.y_wall_bc, - "outlet_mode": case.outlet_mode, - "collision": case.collision, - "velocity": velocity, - "viscosity": viscosity, - } - _write_json(os.path.join(run_dir, "case_meta.json"), meta_base) - - sim = Simulation(lbm_config_path=lbm_tmp, body_config_path=body_tmp) - sim.initialize() - - y_rows = _line_y_indices(ny) - want_steps = sorted({int(s) for s in snapshot_steps if 0 < int(s) <= max_step}) - next_snap = 0 - rows: List[Dict[str, Any]] = [] - - for step in range(1, max_step + 1): - sim.step(1) - if next_snap < len(want_steps) and step == want_steps[next_snap]: - macro = sim.get_macroscopic() - rho = np.asarray(macro["rho"], dtype=np.float64) - ux = np.asarray(macro["ux"], dtype=np.float64) - uy = np.asarray(macro["uy"], dtype=np.float64) - vort = vorticity_z(ux, uy) - stats = _field_stats(rho, ux, vort) - stem = f"step_{step:06d}" - meta = {**meta_base, "step": step, **stats} - npz_path = os.path.join(snap_dir, f"{stem}.npz") - _save_snapshot_npz( - npz_path, - step=step, - rho=rho, - ux=ux, - uy=uy, - vort=vort, - meta=meta, - ) - if save_png: - _save_field_pngs( - snap_dir, - stem, - rho=rho, - ux=ux, - vort=vort, - title=f"{case.label} step {step}", - ) - if save_lines: - line_png = os.path.join(line_dir, f"lines_{stem}.png") - _save_line_plots( - line_png, - rho=rho, - ux=ux, - step=step, - case_label=case.label, - y_rows=y_rows, - ) - # Also save raw 1D data for replotting. - line_npz = os.path.join(line_dir, f"lines_{stem}.npz") - payload = {"x": np.arange(nx, dtype=np.float32)} - for y_idx, y_lab in y_rows: - payload[f"ux_{y_lab}"] = ux[y_idx, :].astype(np.float32) - payload[f"rho_{y_lab}"] = rho[y_idx, :].astype(np.float32) - payload["step"] = np.int32(step) - np.savez_compressed(line_npz, **payload) - - rows.append( - { - "case_id": case.case_id, - "label": case.label, - "collision": case.collision, - "inlet_scheme": case.inlet_scheme, - "y_wall_bc": case.y_wall_bc, - "outlet_mode": case.outlet_mode, - "step": step, - **stats, - "npz": npz_path, - } - ) - if stop_on_nan and not stats["finite"]: - sim.close() - rows[-1]["stopped_early"] = True - return rows - next_snap += 1 - - sim.close() - return rows - - -def _part_a_cases() -> List[CaseSpec]: - # Kan99b-style baseline: zou_he + free_slip + neq_extrap; SRT and MRT. - base = CaseSpec( - case_id="", - label="", - inlet_scheme="zou_he_local", - y_wall_bc="free_slip", - outlet_mode="neq_extrap", - ) - out: List[CaseSpec] = [] - for coll in ("SRT", "MRT"): - cid = f"A_{coll.lower()}_zouhe_fs_neq" - out.append( - CaseSpec( - case_id=cid, - label=f"A baseline {coll} zou_he / free_slip / neq_extrap", - inlet_scheme=base.inlet_scheme, - y_wall_bc=base.y_wall_bc, - outlet_mode=base.outlet_mode, - collision=coll, - ) - ) - return out - - -def _part_b_cases() -> List[CaseSpec]: - return [ - CaseSpec( - case_id="B_case01_zouhe_fs_neq", - label="1 zou_he / free_slip / neq_extrap", - inlet_scheme="zou_he_local", - y_wall_bc="free_slip", - outlet_mode="neq_extrap", - collision="MRT", - ), - CaseSpec( - case_id="B_case02_zouhe_bb_neq", - label="2 zou_he / bounce_back / neq_extrap", - inlet_scheme="zou_he_local", - y_wall_bc="bounce_back", - outlet_mode="neq_extrap", - collision="MRT", - ), - CaseSpec( - case_id="B_case03_zouhe_fs_zgrad", - label="3 zou_he / free_slip / zero_gradient", - inlet_scheme="zou_he_local", - y_wall_bc="free_slip", - outlet_mode="zero_gradient", - collision="MRT", - ), - CaseSpec( - case_id="B_case04_stab_fs_neq", - label="4 channel_stabilized / free_slip / neq_extrap", - inlet_scheme="channel_stabilized", - y_wall_bc="free_slip", - outlet_mode="neq_extrap", - collision="MRT", - ), - ] - - -def _write_summary_csv(path: str, rows: Sequence[Dict[str, Any]]) -> None: - if not rows: - return - keys = [ - "case_id", - "label", - "collision", - "inlet_scheme", - "y_wall_bc", - "outlet_mode", - "step", - "finite", - "rho_min", - "rho_max", - "ux_max", - "vort_max", - "stopped_early", - ] - os.makedirs(os.path.dirname(path) or ".", exist_ok=True) - with open(path, "w", encoding="utf-8", newline="") as f: - w = csv.DictWriter(f, fieldnames=keys, extrasaction="ignore") - w.writeheader() - for r in rows: - w.writerow(r) - - -def main() -> int: - ap = argparse.ArgumentParser(description="Empty-channel inlet diagnostic (fields + line profiles)") - ap.add_argument( - "--part", - choices=("a", "b", "all"), - default="all", - help="A=baseline SRT/MRT; B=4-case matrix; all=both", - ) - ap.add_argument("--nx", type=int, default=401, help="Channel length (lattice)") - ap.add_argument("--ny", type=int, default=201, help="Channel height (lattice)") - ap.add_argument("--velocity", type=float, default=0.03, help="Uniform inlet U0") - ap.add_argument("--viscosity", type=float, default=0.009, help="Kinematic viscosity") - ap.add_argument( - "--out-dir", - type=str, - default=os.path.join(_REPO, "tests", "output", "inlet_channel_diag"), - ) - ap.add_argument( - "--steps", - type=str, - default="", - help="Comma-separated snapshot steps (default: 100,500,1000,1500,2000)", - ) - ap.add_argument("--no-png", action="store_true", help="Skip rho/ux/vort PNG maps") - ap.add_argument("--no-lines", action="store_true", help="Skip ux/rho line-profile plots") - ap.add_argument( - "--continue-on-nan", - action="store_true", - help="Keep stepping after non-finite fields (default: stop case early)", - ) - args = ap.parse_args() - - if not os.path.isfile(_DEFAULT_LBM): - print(f"Missing config: {_DEFAULT_LBM}", file=sys.stderr) - return 2 - - if args.steps.strip(): - snap_steps = tuple(int(s.strip()) for s in args.steps.split(",") if s.strip()) - else: - snap_steps = DEFAULT_SNAPSHOT_STEPS - max_step = max(snap_steps) - - base_cfg = _load_json(_DEFAULT_LBM) - out_dir = os.path.abspath(args.out_dir) - os.makedirs(out_dir, exist_ok=True) - - cases: List[CaseSpec] = [] - if args.part in ("a", "all"): - cases.extend(_part_a_cases()) - if args.part in ("b", "all"): - cases.extend(_part_b_cases()) - - save_png = not args.no_png - # Part A: field maps only; Part B: fields + line plots. - all_rows: List[Dict[str, Any]] = [] - - for case in cases: - part = "A_baseline" if case.case_id.startswith("A_") else "B_matrix" - root = os.path.join(out_dir, part) - save_lines = not args.no_lines and part == "B_matrix" - print(f"--- {case.case_id}: {case.label} ({case.collision}) ---", flush=True) - rows = _run_channel( - case, - base_cfg=base_cfg, - nx=args.nx, - ny=args.ny, - velocity=args.velocity, - viscosity=args.viscosity, - out_root=root, - snapshot_steps=snap_steps, - max_step=max_step, - save_png=save_png, - save_lines=save_lines, - stop_on_nan=not args.continue_on_nan, - ) - all_rows.extend(rows) - for r in rows: - fin = "OK" if r.get("finite") else "NONFINITE" - print( - f" step {r['step']:5d} {fin} rho=[{r['rho_min']:.4f},{r['rho_max']:.4f}] " - f"ux_max={r['ux_max']:.4f} vort_max={r['vort_max']:.4f}", - flush=True, - ) - if rows and rows[-1].get("stopped_early"): - print(" (stopped early: non-finite fields)", flush=True) - - summary_path = os.path.join(out_dir, "summary.csv") - _write_summary_csv(summary_path, all_rows) - manifest = { - "snapshot_steps": list(snap_steps), - "nx": args.nx, - "ny": args.ny, - "velocity": args.velocity, - "viscosity": args.viscosity, - "line_y_indices": [{"y": y, "label": lab} for y, lab in _line_y_indices(args.ny)], - "cases": [c.case_id for c in cases], - } - _write_json(os.path.join(out_dir, "manifest.json"), manifest) - - print(f"Wrote: {summary_path}", flush=True) - print(f"Wrote: {os.path.join(out_dir, 'manifest.json')}", flush=True) - print(f"Output root: {out_dir}", flush=True) - return 0 - - -if __name__ == "__main__": - raise SystemExit(main()) diff --git a/tests/run_inlet_ghost_timing_experiment.py b/tests/run_inlet_ghost_timing_experiment.py deleted file mode 100644 index d8ecc0e..0000000 --- a/tests/run_inlet_ghost_timing_experiment.py +++ /dev/null @@ -1,460 +0,0 @@ -# CelerisLab/tests/run_inlet_ghost_timing_experiment.py -"""Minimal ghost-inlet timing experiments (post field-diagnostic). - -Experiment 1 — DDF time series at inlet center vs first interior column: - (x=0, y=NY/2) and (x=1, y=NY/2), steps 1..N. - Populations f1,f2,f5,f6,f7,f8 plus rho, ux (macro and sum f). - -Experiment 2 — Same channel with ``inlet.collide=false`` vs ``true``: - When collide is on, inlet ghost nodes undergo collision after Zou-He BC. - Compare rho_max / ux_max vs step to test ghost-source timing hypothesis. - -Usage:: - - conda run -n pycuda_3_10 python tests/run_inlet_ghost_timing_experiment.py - conda run -n pycuda_3_10 python tests/run_inlet_ghost_timing_experiment.py --exp 1 --steps 50 - conda run -n pycuda_3_10 python tests/run_inlet_ghost_timing_experiment.py --exp 2 --steps 500 -""" - -from __future__ import annotations - -import argparse -import csv -import json -import os -import sys -import tempfile -from typing import Any, Dict, List, Optional, Sequence, Tuple - -import numpy as np - -_REPO = os.path.abspath(os.path.join(os.path.dirname(__file__), "..")) -_DEFAULT_LBM = os.path.join(_REPO, "src", "CelerisLab", "configs", "config_lbm.json") - -# D2Q9 indices logged (see zou_he_velocity.cuh). -POP_IDX: Tuple[int, ...] = (1, 2, 5, 6, 7, 8) -POP_NAMES: Tuple[str, ...] = tuple(f"f{i}" for i in POP_IDX) - -# cx, cy for macroscopic ux, uy from local f (matches descriptors.cuh D2Q9). -_CX = np.array([0, 1, -1, 0, 0, 1, -1, 1, -1], dtype=np.float64) -_CY = np.array([0, 0, 0, 1, -1, 1, -1, -1, 1], dtype=np.float64) - - -def _load_json(path: str) -> dict: - with open(path, "r", encoding="utf-8") as f: - return json.load(f) - - -def _write_json(path: str, payload: dict) -> None: - os.makedirs(os.path.dirname(path) or ".", exist_ok=True) - with open(path, "w", encoding="utf-8") as f: - json.dump(payload, f, indent=2) - - -def _build_cfg( - base: dict, - *, - nx: int, - ny: int, - collision: str, - inlet_collide: bool, - velocity: float, - viscosity: float, -) -> dict: - cfg = json.loads(json.dumps(base)) - cfg["grid"]["nx"] = int(nx) - cfg["grid"]["ny"] = int(ny) - cfg["grid"]["nz"] = 1 - cfg["physics"]["velocity"] = float(velocity) - cfg["physics"]["viscosity"] = float(viscosity) - cfg["physics"]["rho"] = 1.0 - cfg["method"]["collision"] = str(collision).upper() - cfg["method"]["streaming"] = "double_buffer" - cfg["method"]["store_precision"] = "FP32" - cfg["method"]["les"]["enabled"] = False - cfg["method"]["inlet"]["profile"] = "uniform" - cfg["method"]["inlet"]["scheme"] = "zou_he_local" - cfg["method"]["inlet"]["collide"] = bool(inlet_collide) - cfg["method"]["y_wall_bc"] = "free_slip" - cfg["method"]["outlet"]["mode"] = "neq_extrap" - return cfg - - -def _macro_from_f(f: np.ndarray) -> Tuple[float, float, float]: - f = np.asarray(f, dtype=np.float64) - rho = float(np.sum(f)) - if abs(rho) < 1e-14: - return rho, 0.0, 0.0 - ux = float(np.dot(f, _CX) / rho) - uy = float(np.dot(f, _CY) / rho) - return rho, ux, uy - - -def _sample_node(ddf_qnyx: np.ndarray, x: int, y: int) -> Dict[str, float]: - f = ddf_qnyx[:, y, x].astype(np.float64) - rho_m, ux_m, uy_m = _macro_from_f(f) - out: Dict[str, float] = { - "rho_sum": rho_m, - "ux_macro": ux_m, - "uy_macro": uy_m, - } - for i, name in zip(POP_IDX, POP_NAMES): - out[name] = float(f[i]) - return out - - -def _run_steps( - cfg: dict, - *, - steps: int, - y_mid: int, - probe_x: Sequence[int] = (0, 1), -) -> Tuple[List[Dict[str, Any]], Dict[str, Any]]: - sys.path.insert(0, os.path.join(_REPO, "src")) - from CelerisLab import Simulation # noqa: WPS433 - - bdoc = {"objects": []} - tmpd = tempfile.mkdtemp(prefix="ghost_timing_") - lbm_tmp = os.path.join(tmpd, "config_lbm.json") - body_tmp = os.path.join(tmpd, "config_body.json") - with open(lbm_tmp, "w", encoding="utf-8") as f: - json.dump(cfg, f) - with open(body_tmp, "w", encoding="utf-8") as f: - json.dump(bdoc, f) - - sim = Simulation(lbm_config_path=lbm_tmp, body_config_path=body_tmp) - sim.initialize() - nx, ny = sim.lbm_cfg.nx, sim.lbm_cfg.ny - y_mid = int(np.clip(y_mid, 1, ny - 2)) - - rows: List[Dict[str, Any]] = [] - for step in range(1, int(steps) + 1): - sim.step(1) - sim.field.download_ddf() - farr = sim.field.ddf.reshape(sim.lbm_cfg.nq, ny, nx) - - rec: Dict[str, Any] = {"step": step} - for x in probe_x: - tag = "inlet" if x == 0 else "interior" - s = _sample_node(farr, int(x), y_mid) - for k, v in s.items(): - rec[f"{tag}_{k}"] = v - - # Pull semantics at interior: f[2] is read from stored f[2] at x=0 (same link index). - rec["cross_f2_match"] = abs(rec["interior_f2"] - rec["inlet_f2"]) < 1e-5 - rec["cross_f1_inlet_to_int_pull"] = float( - farr[1, y_mid, 1] - ) # after step, what x=1 holds in f1 - rec["delta_inlet_f1"] = ( - float("nan") if step == 1 else rec["inlet_f1"] - rows[-1]["inlet_f1"] - ) - rec["delta_inlet_ux"] = ( - float("nan") - if step == 1 - else rec["inlet_ux_macro"] - rows[-1]["inlet_ux_macro"] - ) - - macro = sim.get_macroscopic() - rho_f = np.asarray(macro["rho"], dtype=np.float64) - ux_f = np.asarray(macro["ux"], dtype=np.float64) - rec["domain_rho_max"] = float(np.nanmax(rho_f)) - rec["domain_rho_min"] = float(np.nanmin(rho_f)) - rec["domain_ux_max"] = float(np.nanmax(np.abs(ux_f))) - rec["finite"] = bool(np.isfinite(rho_f).all() and np.isfinite(ux_f).all()) - - rows.append(rec) - - meta = { - "nx": nx, - "ny": ny, - "y_mid": y_mid, - "probe_x": list(probe_x), - "inlet_collide": bool(cfg["method"]["inlet"].get("collide", False)), - "collision": cfg["method"]["collision"], - "inlet_scheme": cfg["method"]["inlet"]["scheme"], - "y_wall_bc": cfg["method"]["y_wall_bc"], - "outlet_mode": cfg["method"]["outlet"]["mode"], - "velocity": cfg["physics"]["velocity"], - "viscosity": cfg["physics"]["viscosity"], - } - sim.close() - return rows, meta - - -def _write_csv(path: str, rows: Sequence[Dict[str, Any]]) -> None: - if not rows: - return - keys: List[str] = [] - for r in rows: - for k in r: - if k not in keys: - keys.append(k) - os.makedirs(os.path.dirname(path) or ".", exist_ok=True) - with open(path, "w", encoding="utf-8", newline="") as f: - w = csv.DictWriter(f, fieldnames=keys) - w.writeheader() - w.writerows(rows) - - -def _plot_exp1(out_dir: str, rows: Sequence[Dict[str, Any]], y_mid: int) -> List[str]: - try: - import matplotlib - - matplotlib.use("Agg") - import matplotlib.pyplot as plt - except ImportError: - return [] - - steps = [int(r["step"]) for r in rows] - paths: List[str] = [] - - def _ts(key: str, label: str, ax, **kw): - ax.plot(steps, [r[key] for r in rows], label=label, **kw) - - # Populations - fig, axes = plt.subplots(2, 1, figsize=(11, 7), sharex=True) - for name in POP_NAMES: - _ts(f"inlet_{name}", f"inlet {name}", axes[0], linewidth=1.0) - axes[0].set_ylabel("f at x=0") - axes[0].legend(ncol=3, fontsize=7, loc="best") - axes[0].grid(True, alpha=0.3) - for name in POP_NAMES: - _ts(f"interior_{name}", f"int {name}", axes[1], linewidth=1.0) - axes[1].set_ylabel("f at x=1") - axes[1].set_xlabel("step") - axes[1].legend(ncol=3, fontsize=7, loc="best") - axes[1].grid(True, alpha=0.3) - fig.suptitle(f"Exp1 populations (y={y_mid})") - fig.tight_layout() - p1 = os.path.join(out_dir, "exp1_populations.png") - fig.savefig(p1, dpi=150, bbox_inches="tight") - plt.close(fig) - paths.append(p1) - - # rho / ux + step-to-step deltas - fig, axes = plt.subplots(3, 1, figsize=(11, 8), sharex=True) - _ts("inlet_ux_macro", "inlet ux", axes[0]) - _ts("interior_ux_macro", "interior ux", axes[0]) - axes[0].axhline(0.03, color="k", ls="--", lw=0.8, label="U0") - axes[0].set_ylabel("u_x") - axes[0].legend(fontsize=8) - axes[0].grid(True, alpha=0.3) - _ts("inlet_rho_sum", "inlet rho", axes[1]) - _ts("interior_rho_sum", "interior rho", axes[1]) - axes[1].set_ylabel("rho") - axes[1].grid(True, alpha=0.3) - _ts("delta_inlet_f1", "|Δf1| inlet", axes[2]) - axes[2].set_ylabel("Δf1") - axes[2].set_xlabel("step") - axes[2].grid(True, alpha=0.3) - fig.suptitle(f"Exp1 macro / inlet f1 increment (y={y_mid})") - fig.tight_layout() - p2 = os.path.join(out_dir, "exp1_macro_delta.png") - fig.savefig(p2, dpi=150, bbox_inches="tight") - plt.close(fig) - paths.append(p2) - - return paths - - -def _plot_exp2(out_dir: str, rows_a: Sequence[Dict[str, Any]], rows_b: Sequence[Dict[str, Any]]) -> List[str]: - try: - import matplotlib - - matplotlib.use("Agg") - import matplotlib.pyplot as plt - except ImportError: - return [] - - fig, axes = plt.subplots(2, 1, figsize=(10, 6), sharex=True) - for rows, lab, c in ( - (rows_a, "ghost (no collide)", "C0"), - (rows_b, "inlet collide", "C1"), - ): - steps = [int(r["step"]) for r in rows] - axes[0].plot(steps, [r["domain_rho_max"] for r in rows], label=lab, color=c) - axes[1].plot(steps, [r["domain_ux_max"] for r in rows], label=lab, color=c) - axes[0].set_ylabel("rho_max") - axes[0].legend() - axes[0].grid(True, alpha=0.3) - axes[1].set_ylabel("|ux|_max") - axes[1].set_xlabel("step") - axes[1].legend() - axes[1].grid(True, alpha=0.3) - fig.suptitle("Exp2: ghost vs inlet collide") - fig.tight_layout() - p = os.path.join(out_dir, "exp2_stability_compare.png") - fig.savefig(p, dpi=150, bbox_inches="tight") - plt.close(fig) - return [p] - - -def _oscillation_summary(rows: Sequence[Dict[str, Any]], *, last_n: int = 20) -> Dict[str, float]: - """High-frequency proxy: std of inlet f1 and ux over the last *last_n* steps.""" - if len(rows) < 2: - return {} - tail = rows[-min(last_n, len(rows)) :] - f1 = np.array([r["inlet_f1"] for r in tail], dtype=np.float64) - ux = np.array([r["inlet_ux_macro"] for r in tail], dtype=np.float64) - d1 = np.array([r["delta_inlet_f1"] for r in tail if np.isfinite(r["delta_inlet_f1"])], dtype=np.float64) - return { - "std_inlet_f1_last": float(np.std(f1)), - "std_inlet_ux_last": float(np.std(ux)), - "mean_abs_delta_f1_last": float(np.mean(np.abs(d1))) if d1.size else float("nan"), - } - - -def run_exp1( - base: dict, - *, - out_dir: str, - nx: int, - ny: int, - steps: int, - collision: str, - velocity: float, - viscosity: float, -) -> None: - y_mid = ny // 2 - cfg = _build_cfg( - base, - nx=nx, - ny=ny, - collision=collision, - inlet_collide=False, - velocity=velocity, - viscosity=viscosity, - ) - print(f"Exp1: zou_he ghost inlet, y_mid={y_mid}, steps={steps}", flush=True) - rows, meta = _run_steps(cfg, steps=steps, y_mid=y_mid) - meta["oscillation"] = _oscillation_summary(rows) - - exp_dir = os.path.join(out_dir, "exp1_ddf_timeseries") - os.makedirs(exp_dir, exist_ok=True) - _write_csv(os.path.join(exp_dir, "timeseries.csv"), rows) - _write_json(os.path.join(exp_dir, "meta.json"), meta) - plots = _plot_exp1(exp_dir, rows, y_mid) - for p in plots: - print(f" plot: {p}", flush=True) - - # Console summary for quick read - print(" last 5 steps (inlet center):", flush=True) - for r in rows[-5:]: - print( - f" step {r['step']:3d} f1={r['inlet_f1']:.6f} ux={r['inlet_ux_macro']:.6f} " - f"Δf1={r['delta_inlet_f1']:.2e} rho_max={r['domain_rho_max']:.4f} finite={r['finite']}", - flush=True, - ) - print(f" oscillation: {meta['oscillation']}", flush=True) - print(f"Wrote: {exp_dir}/timeseries.csv", flush=True) - - -def run_exp2( - base: dict, - *, - out_dir: str, - nx: int, - ny: int, - steps: int, - collision: str, - velocity: float, - viscosity: float, -) -> None: - y_mid = ny // 2 - exp_dir = os.path.join(out_dir, "exp2_inlet_collide") - os.makedirs(exp_dir, exist_ok=True) - - summaries: Dict[str, Any] = {} - all_rows: Dict[str, List[Dict[str, Any]]] = {} - - for collide, tag in ((False, "ghost_no_collide"), (True, "ghost_with_collide")): - cfg = _build_cfg( - base, - nx=nx, - ny=ny, - collision=collision, - inlet_collide=collide, - velocity=velocity, - viscosity=viscosity, - ) - print(f"Exp2 [{tag}]: inlet.collide={collide}, steps={steps}", flush=True) - rows, meta = _run_steps(cfg, steps=steps, y_mid=y_mid) - all_rows[tag] = rows - _write_csv(os.path.join(exp_dir, f"{tag}.csv"), rows) - - last_finite = next( - (int(r["step"]) for r in rows if not r.get("finite", True)), - None, - ) - summaries[tag] = { - **meta, - "first_nonfinite_step": last_finite, - "final_rho_max": rows[-1]["domain_rho_max"] if rows else None, - "final_finite": rows[-1].get("finite") if rows else None, - "oscillation": _oscillation_summary(rows), - } - print( - f" final rho_max={summaries[tag]['final_rho_max']:.4f} " - f"finite={summaries[tag]['final_finite']} " - f"first_nonfinite={summaries[tag]['first_nonfinite_step']}", - flush=True, - ) - - _write_json(os.path.join(exp_dir, "summary.json"), summaries) - plots = _plot_exp2(exp_dir, all_rows["ghost_no_collide"], all_rows["ghost_with_collide"]) - for p in plots: - print(f" plot: {p}", flush=True) - print(f"Wrote: {exp_dir}/summary.json", flush=True) - - -def main() -> int: - ap = argparse.ArgumentParser(description="Ghost inlet timing experiments") - ap.add_argument("--exp", choices=("1", "2", "all"), default="all") - ap.add_argument("--steps", type=int, default=50, help="Steps for exp1 (default 50)") - ap.add_argument("--steps2", type=int, default=500, help="Steps for exp2 (default 500)") - ap.add_argument("--nx", type=int, default=401) - ap.add_argument("--ny", type=int, default=201) - ap.add_argument("--collision", default="MRT", choices=("SRT", "TRT", "MRT")) - ap.add_argument("--velocity", type=float, default=0.03) - ap.add_argument("--viscosity", type=float, default=0.009) - ap.add_argument( - "--out-dir", - default=os.path.join(_REPO, "tests", "output", "inlet_ghost_timing"), - ) - args = ap.parse_args() - - if not os.path.isfile(_DEFAULT_LBM): - print(f"Missing {_DEFAULT_LBM}", file=sys.stderr) - return 2 - - base = _load_json(_DEFAULT_LBM) - out_dir = os.path.abspath(args.out_dir) - os.makedirs(out_dir, exist_ok=True) - - if args.exp in ("1", "all"): - run_exp1( - base, - out_dir=out_dir, - nx=args.nx, - ny=args.ny, - steps=args.steps, - collision=args.collision, - velocity=args.velocity, - viscosity=args.viscosity, - ) - if args.exp in ("2", "all"): - run_exp2( - base, - out_dir=out_dir, - nx=args.nx, - ny=args.ny, - steps=args.steps2, - collision=args.collision, - velocity=args.velocity, - viscosity=args.viscosity, - ) - return 0 - - -if __name__ == "__main__": - raise SystemExit(main()) diff --git a/tests/run_inlet_scenario_fields.py b/tests/run_inlet_scenario_fields.py deleted file mode 100644 index 4a092bc..0000000 --- a/tests/run_inlet_scenario_fields.py +++ /dev/null @@ -1,587 +0,0 @@ -# CelerisLab/tests/run_inlet_scenario_fields.py -"""Three-scenario inlet field export: final or last-stable step at 5000 LBM steps. - -Scenarios: - - empty_channel: zou_he_local × {SRT,MRT} × {free_slip,bounce_back} - - empty_channel_inlet_matrix: all inlet schemes × {SRT,MRT}, bounce_back only - - kan99b: zou_he_local × {SRT,MRT} × {free_slip,bounce_back}, Re=100, domain M - - sah04_case9: channel_stabilized × {SRT,MRT}, high-blockage case 9 geometry - -Outputs per run (under ``--out-dir``): - fields/final_{rho,ux,vort}.png, fields/final.npz - lines/lines_ux_rho.png, lines/lines.npz - run_meta.json - -Usage:: - - conda run -n pycuda_3_10 python tests/run_inlet_scenario_fields.py - conda run -n pycuda_3_10 python tests/run_inlet_scenario_fields.py --scenario empty_channel - conda run -n pycuda_3_10 python tests/run_inlet_scenario_fields.py --scenario empty_channel_inlet_matrix -""" - -from __future__ import annotations - -import argparse -import csv -import json -import os -import sys -import tempfile -from dataclasses import dataclass, replace -from typing import Any, Dict, List, Optional, Sequence, Tuple - -import numpy as np - -_REPO = os.path.abspath(os.path.join(os.path.dirname(__file__), "..")) -_DEFAULT_LBM = os.path.join(_REPO, "src", "CelerisLab", "configs", "config_lbm.json") - -# Kan99b lattice contract -_KAN_U_INF = 0.03 -_KAN_D = 30.0 -_KAN_R = 15.0 -_KAN_RE = 100.0 -_KAN_ALPHA = 1.0 - -# Sah04 case 9 (high tier) -_SAH_D = 30 -_SAH_NX = 80 * _SAH_D + 2 -_SAH_NY = 35 -_SAH_CX = 40.0 * _SAH_D + 0.5 -_SAH_CY = 17.0 -_SAH_RE = 200.0 -_SAH_U_MAX = 0.1 - - -@dataclass(frozen=True) -class RunSpec: - """One simulation run specification.""" - - scenario: str - run_id: str - label: str - nx: int - ny: int - collision: str - inlet_scheme: str - inlet_profile: str - y_wall_bc: str - outlet_mode: str - velocity: float - viscosity: float - steps: int - has_cylinder: bool - cylinder_center: Tuple[float, float] = (0.0, 0.0) - cylinder_radius: float = 0.0 - cylinder_omega: float = 0.0 - - -def _load_json(path: str) -> dict: - with open(path, "r", encoding="utf-8") as f: - return json.load(f) - - -def _write_json(path: str, payload: dict) -> None: - os.makedirs(os.path.dirname(path) or ".", exist_ok=True) - with open(path, "w", encoding="utf-8") as f: - json.dump(payload, f, indent=2) - - -def vorticity_z(ux: np.ndarray, uy: np.ndarray) -> np.ndarray: - ux = np.asarray(ux, dtype=np.float64) - uy = np.asarray(uy, dtype=np.float64) - return np.gradient(uy, axis=1) - np.gradient(ux, axis=0) - - -def _line_y_indices(ny: int) -> List[Tuple[int, str]]: - return [(1, "y1"), (ny // 2, f"y{ny // 2}"), (ny - 2, f"y{ny - 2}")] - - -_INLET_SCHEMES = ( - "zou_he_local", - "channel_stabilized", - "equilibrium", - "regularized", -) - - -def _empty_channel_inlet_matrix_specs() -> List[RunSpec]: - """All inlet schemes on empty channel, bounce_back, SRT/MRT (5000-step field export).""" - specs: List[RunSpec] = [] - for coll in ("SRT", "MRT"): - for scheme in _INLET_SCHEMES: - run_id = f"{coll.lower()}_{scheme}" - specs.append( - RunSpec( - scenario="empty_channel_inlet_matrix", - run_id=run_id, - label=f"empty {scheme} {coll} bounce_back", - nx=401, - ny=201, - collision=coll, - inlet_scheme=scheme, - inlet_profile="uniform", - y_wall_bc="bounce_back", - outlet_mode="neq_extrap", - velocity=0.03, - viscosity=0.009, - steps=5000, - has_cylinder=False, - ) - ) - return specs - - -def _all_specs() -> List[RunSpec]: - specs: List[RunSpec] = [] - for coll in ("SRT", "MRT"): - for wall in ("free_slip", "bounce_back"): - wid = f"{coll.lower()}_{wall}" - specs.append( - RunSpec( - scenario="empty_channel", - run_id=wid, - label=f"empty zou_he {coll} {wall}", - nx=401, - ny=201, - collision=coll, - inlet_scheme="zou_he_local", - inlet_profile="uniform", - y_wall_bc=wall, - outlet_mode="neq_extrap", - velocity=0.03, - viscosity=0.009, - steps=5000, - has_cylinder=False, - ) - ) - dom_m = (1351, 601, (450.0, 300.0)) - nu_k = _KAN_U_INF * _KAN_D / _KAN_RE - omega = 2.0 * _KAN_ALPHA * _KAN_U_INF / _KAN_D - for coll in ("SRT", "MRT"): - for wall in ("free_slip", "bounce_back"): - wid = f"{coll.lower()}_{wall}" - specs.append( - RunSpec( - scenario="kan99b", - run_id=wid, - label=f"kan99b zou_he {coll} {wall}", - nx=dom_m[0], - ny=dom_m[1], - collision=coll, - inlet_scheme="zou_he_local", - inlet_profile="uniform", - y_wall_bc=wall, - outlet_mode="neq_extrap", - velocity=_KAN_U_INF, - viscosity=nu_k, - steps=5000, - has_cylinder=True, - cylinder_center=dom_m[2], - cylinder_radius=_KAN_R, - cylinder_omega=omega, - ) - ) - u0_mean = _SAH_U_MAX / 1.5 - nu_s = _SAH_U_MAX * _SAH_D / _SAH_RE - for coll in ("SRT", "MRT"): - wid = f"{coll.lower()}_channel_stab" - specs.append( - RunSpec( - scenario="sah04_case9", - run_id=wid, - label=f"sah04 case9 channel_stab {coll}", - nx=_SAH_NX, - ny=_SAH_NY, - collision=coll, - inlet_scheme="channel_stabilized", - inlet_profile="parabolic", - y_wall_bc="bounce_back", - outlet_mode="neq_extrap", - velocity=u0_mean, - viscosity=nu_s, - steps=5000, - has_cylinder=True, - cylinder_center=(_SAH_CX, _SAH_CY), - cylinder_radius=0.5 * _SAH_D, - cylinder_omega=0.0, - ) - ) - return specs - - -def _build_cfg(base: dict, spec: RunSpec) -> dict: - cfg = json.loads(json.dumps(base)) - cfg["grid"]["nx"] = spec.nx - cfg["grid"]["ny"] = spec.ny - cfg["grid"]["nz"] = 1 - cfg["physics"]["velocity"] = float(spec.velocity) - cfg["physics"]["viscosity"] = float(spec.viscosity) - cfg["physics"]["rho"] = 1.0 - cfg["method"]["collision"] = spec.collision.upper() - cfg["method"]["streaming"] = "double_buffer" - cfg["method"]["store_precision"] = "FP32" - cfg["method"]["les"]["enabled"] = False - cfg["method"]["inlet"]["profile"] = spec.inlet_profile - cfg["method"]["inlet"]["scheme"] = spec.inlet_scheme - cfg["method"]["y_wall_bc"] = spec.y_wall_bc - cfg["method"]["outlet"]["mode"] = spec.outlet_mode - return cfg - - -def _body_doc(spec: RunSpec) -> dict: - if not spec.has_cylinder: - return {"objects": []} - return { - "objects": [ - { - "type": "cylinder", - "center": [float(spec.cylinder_center[0]), float(spec.cylinder_center[1])], - "radius": float(spec.cylinder_radius), - "omega": float(spec.cylinder_omega), - } - ] - } - - -def _save_field_pngs( - out_dir: str, - prefix: str, - *, - rho: np.ndarray, - ux: np.ndarray, - vort: np.ndarray, - title: str, -) -> List[str]: - try: - import matplotlib - - matplotlib.use("Agg") - import matplotlib.pyplot as plt - except ImportError: - return [] - - os.makedirs(out_dir, exist_ok=True) - ny, nx = rho.shape - extent = (0, nx - 1, 0, ny - 1) - paths: List[str] = [] - - def _one(arr: np.ndarray, name: str, cmap: str, sym: bool) -> None: - a = np.asarray(arr, dtype=np.float64) - fin = a[np.isfinite(a)] - if fin.size == 0: - vmin, vmax = -1.0, 1.0 - elif sym: - v = float(np.percentile(np.abs(fin), 99.5)) or 1.0 - vmin, vmax = -v, v - else: - vmin = float(np.percentile(fin, 0.5)) - vmax = float(np.percentile(fin, 99.5)) - if vmax <= vmin: - vmax = vmin + 1.0 - fw = min(18.0, max(8.0, nx / 70.0)) - fh = min(10.0, max(3.0, ny / 45.0)) - fig, ax = plt.subplots(figsize=(fw, fh)) - im = ax.imshow(a, origin="lower", aspect="auto", cmap=cmap, vmin=vmin, vmax=vmax, extent=extent) - ax.set_xlabel("x") - ax.set_ylabel("y") - ax.set_title(f"{title} — {name}") - fig.colorbar(im, ax=ax, fraction=0.046, pad=0.04) - fig.tight_layout() - p = os.path.join(out_dir, f"{prefix}_{name}.png") - fig.savefig(p, dpi=150, bbox_inches="tight") - plt.close(fig) - paths.append(p) - - _one(rho, "rho", "viridis", sym=False) - _one(ux, "ux", "RdBu_r", sym=True) - _one(vort, "vort", "RdBu_r", sym=True) - return paths - - -def _save_line_plots( - path: str, - *, - rho: np.ndarray, - ux: np.ndarray, - step: int, - label: str, - y_rows: Sequence[Tuple[int, str]], -) -> None: - try: - import matplotlib - - matplotlib.use("Agg") - import matplotlib.pyplot as plt - except ImportError: - return - - ny, nx = rho.shape - x = np.arange(nx, dtype=np.float64) - fig, axes = plt.subplots(2, 1, figsize=(min(14.0, max(8.0, nx / 55.0)), 7.0), sharex=True) - for y_idx, y_lab in y_rows: - yi = int(np.clip(y_idx, 0, ny - 1)) - axes[0].plot(x, ux[yi, :], label=y_lab, linewidth=1.0) - axes[1].plot(x, rho[yi, :], label=y_lab, linewidth=1.0) - axes[0].set_ylabel("u_x") - axes[0].legend(loc="best", fontsize=8) - axes[0].grid(True, alpha=0.3) - axes[1].set_ylabel("rho") - axes[1].set_xlabel("x (lattice)") - axes[1].legend(loc="best", fontsize=8) - axes[1].grid(True, alpha=0.3) - fig.suptitle(f"{label} — ux/rho lines at step {step}") - fig.tight_layout() - os.makedirs(os.path.dirname(path) or ".", exist_ok=True) - fig.savefig(path, dpi=150, bbox_inches="tight") - plt.close(fig) - - -def _snapshot_from_sim(sim) -> Tuple[np.ndarray, np.ndarray, np.ndarray, np.ndarray]: - macro = sim.get_macroscopic() - rho = np.asarray(macro["rho"], dtype=np.float64) - ux = np.asarray(macro["ux"], dtype=np.float64) - uy = np.asarray(macro["uy"], dtype=np.float64) - vort = vorticity_z(ux, uy) - return rho, ux, uy, vort - - -def _is_stable_fields( - rho: np.ndarray, - ux: np.ndarray, - *, - rho_lo: float = 0.85, - rho_hi: float = 1.25, - ux_cap: float = 0.15, -) -> bool: - """Finite fields within a physically plausible band (reject pre-blow-up states).""" - if not (np.isfinite(rho).all() and np.isfinite(ux).all()): - return False - r0 = float(np.min(rho)) - r1 = float(np.max(rho)) - umax = float(np.max(np.abs(ux))) - return (rho_lo <= r0) and (r1 <= rho_hi) and (umax <= ux_cap) - - -def run_one(spec: RunSpec, base_cfg: dict, out_root: str) -> Dict[str, Any]: - sys.path.insert(0, os.path.join(_REPO, "src")) - import pycuda.driver as cuda - from CelerisLab import Simulation # noqa: WPS433 - - run_dir = os.path.join(out_root, spec.scenario, spec.run_id) - field_dir = os.path.join(run_dir, "fields") - line_dir = os.path.join(run_dir, "lines") - os.makedirs(field_dir, exist_ok=True) - os.makedirs(line_dir, exist_ok=True) - - cfg = _build_cfg(base_cfg, spec) - tmpd = tempfile.mkdtemp(prefix="inlet_scenario_") - lbm_tmp = os.path.join(tmpd, "config_lbm.json") - body_tmp = os.path.join(tmpd, "config_body.json") - _write_json(lbm_tmp, cfg) - _write_json(body_tmp, _body_doc(spec)) - - sim = Simulation(lbm_config_path=lbm_tmp, body_config_path=body_tmp) - if spec.has_cylinder and spec.cylinder_omega != 0.0: - sim.bodies.get(0).state.omega = np.float32(spec.cylinder_omega) - sim.initialize() - - stream = cuda.Stream() - y_rows = _line_y_indices(spec.ny) - - last_good: Optional[Dict[str, Any]] = None - first_bad_step: Optional[int] = None - force_bad_step: Optional[int] = None - - print(f" [{spec.scenario}/{spec.run_id}] {spec.label} steps={spec.steps}", flush=True) - - for step in range(1, spec.steps + 1): - if spec.has_cylinder: - sim.bodies.zero_force_segment_async(stream) - sim.stepper.step( - 1, - action_gpu=sim.bodies.action_gpu, - obs_gpu=sim.bodies.obs_gpu, - stream=stream, - ) - if step % 100 == 0 or step == spec.steps: - stream.synchronize() - sim.bodies.download_obs_full_async(stream) - stream.synchronize() - fvec = sim.bodies.read_force(0) - if not (np.isfinite(fvec[0]) and np.isfinite(fvec[1])): - if force_bad_step is None: - force_bad_step = step - else: - sim.step(1) - - rho, ux, uy, vort = _snapshot_from_sim(sim) - if _is_stable_fields(rho, ux): - last_good = { - "step": step, - "rho": rho.copy(), - "ux": ux.copy(), - "uy": uy.copy(), - "vort": vort.copy(), - } - elif first_bad_step is None: - first_bad_step = step - - sim.close() - - if last_good is None: - raise RuntimeError(f"No finite snapshot for {spec.run_id}") - - out_step = int(last_good["step"]) - rho = last_good["rho"] - ux = last_good["ux"] - uy = last_good["uy"] - vort = last_good["vort"] - requested_final = spec.steps - used_last_stable = out_step < requested_final - - meta = { - "scenario": spec.scenario, - "run_id": spec.run_id, - "label": spec.label, - "nx": spec.nx, - "ny": spec.ny, - "collision": spec.collision, - "inlet_scheme": spec.inlet_scheme, - "inlet_profile": spec.inlet_profile, - "y_wall_bc": spec.y_wall_bc, - "outlet_mode": spec.outlet_mode, - "velocity": spec.velocity, - "viscosity": spec.viscosity, - "requested_steps": requested_final, - "output_step": out_step, - "used_last_stable": used_last_stable, - "first_nonfinite_step": first_bad_step, - "first_force_nonfinite_step": force_bad_step, - "rho_min": float(np.min(rho)), - "rho_max": float(np.max(rho)), - "ux_max": float(np.max(np.abs(ux))), - "vort_max": float(np.max(np.abs(vort[np.isfinite(vort)]))) if np.isfinite(vort).any() else float("nan"), - } - _write_json(os.path.join(run_dir, "run_meta.json"), meta) - - stem = f"step_{out_step:06d}" - np.savez_compressed( - os.path.join(field_dir, "final.npz"), - rho=rho.astype(np.float32), - ux=ux.astype(np.float32), - uy=uy.astype(np.float32), - vort=vort.astype(np.float32), - step=np.int32(out_step), - ) - - title = f"{spec.label} (step {out_step}" + (", last stable" if used_last_stable else ", final") + ")" - pngs = _save_field_pngs(field_dir, "final", rho=rho, ux=ux, vort=vort, title=title) - _save_line_plots( - os.path.join(line_dir, "lines_ux_rho.png"), - rho=rho, - ux=ux, - step=out_step, - label=spec.label, - y_rows=y_rows, - ) - line_payload: Dict[str, Any] = {"x": np.arange(spec.nx, dtype=np.float32), "step": np.int32(out_step)} - for y_idx, y_lab in y_rows: - yi = int(np.clip(y_idx, 0, spec.ny - 1)) - line_payload[f"ux_{y_lab}"] = ux[yi, :].astype(np.float32) - line_payload[f"rho_{y_lab}"] = rho[yi, :].astype(np.float32) - np.savez_compressed(os.path.join(line_dir, "lines.npz"), **line_payload) - - status = "last_stable" if used_last_stable else "final" - print( - f" -> {status} step {out_step} rho=[{meta['rho_min']:.4f},{meta['rho_max']:.4f}] " - f"ux_max={meta['ux_max']:.4f} force_bad={force_bad_step}", - flush=True, - ) - return {**meta, "field_pngs": pngs, "run_dir": run_dir} - - -def main() -> int: - ap = argparse.ArgumentParser(description="Three-scenario inlet field export (5000 steps)") - ap.add_argument( - "--scenario", - choices=( - "empty_channel", - "empty_channel_inlet_matrix", - "kan99b", - "sah04_case9", - "all", - ), - default="all", - ) - ap.add_argument( - "--collision", - default="", - help="Optional filter: SRT or MRT only", - ) - ap.add_argument("--steps", type=int, default=5000) - ap.add_argument( - "--out-dir", - default=os.path.join(_REPO, "tests", "output", "inlet_scenario_fields"), - ) - args = ap.parse_args() - - if not os.path.isfile(_DEFAULT_LBM): - print(f"Missing {_DEFAULT_LBM}", file=sys.stderr) - return 2 - - base = _load_json(_DEFAULT_LBM) - if args.scenario == "empty_channel_inlet_matrix": - specs = _empty_channel_inlet_matrix_specs() - elif args.scenario == "all": - specs = _all_specs() + _empty_channel_inlet_matrix_specs() - else: - specs = _all_specs() - if args.scenario != "all": - specs = [s for s in specs if s.scenario == args.scenario] - if args.collision.strip(): - coll = args.collision.strip().upper() - specs = [s for s in specs if s.collision.upper() == coll] - specs = [replace(s, steps=int(args.steps)) for s in specs] - - out_dir = os.path.abspath(args.out_dir) - os.makedirs(out_dir, exist_ok=True) - - rows: List[Dict[str, Any]] = [] - for spec in specs: - try: - row = run_one(spec, base, out_dir) - rows.append(row) - except Exception as e: # noqa: BLE001 - print(f" FAILED {spec.scenario}/{spec.run_id}: {e}", flush=True) - rows.append( - { - "scenario": spec.scenario, - "run_id": spec.run_id, - "label": spec.label, - "error": str(e), - } - ) - - summary_path = os.path.join(out_dir, "summary.csv") - if rows: - keys: List[str] = [] - for r in rows: - for k in r: - if k not in keys and k != "field_pngs": - keys.append(k) - with open(summary_path, "w", encoding="utf-8", newline="") as f: - w = csv.DictWriter(f, fieldnames=keys, extrasaction="ignore") - w.writeheader() - w.writerows(rows) - - _write_json( - os.path.join(out_dir, "manifest.json"), - {"steps": args.steps, "scenario_filter": args.scenario, "runs": [s.run_id for s in specs]}, - ) - print(f"Wrote: {summary_path}", flush=True) - print(f"Output: {out_dir}", flush=True) - return 0 - - -if __name__ == "__main__": - raise SystemExit(main()) diff --git a/tests/run_kan99b_rotating_cylinder.py b/tests/run_kan99b_rotating_cylinder.py index b93e936..4132515 100644 --- a/tests/run_kan99b_rotating_cylinder.py +++ b/tests/run_kan99b_rotating_cylinder.py @@ -1,30 +1,11 @@ # CelerisLab/tests/run_kan99b_rotating_cylinder.py -"""Kan99b rotating-cylinder validation driver. +"""Kan99b MRT-only rotating-cylinder validation runner. -This script executes the rotating-cylinder campaign in -``tests/Rotating_cylinder_validation_plan.md`` against Kan99b anchors. +This script follows ``tests/Kan99b_validation.md`` for the current round: -Core lattice mapping (fixed by campaign contract): - - D = 30, R = 15 - - U_inf = 0.03 - - nu = U_inf * D / Re = 0.9 / Re - - omega_body = 2 * alpha * U_inf / D = 0.002 * alpha - - inlet.profile = uniform - - y_wall_bc = free_slip - - outlet.mode = neq_extrap - - streaming = double_buffer - -Phases: - - A: domain independence at Re=100, alpha=1.0 (MRT, domains S/M/L) - - B: anchor collision sweep at Re=100, alpha=1.0 (SRT/TRT/MRT) - - C: Re=100 alpha scan - - D: Re=60 and Re=160 threshold scan - -Usage examples:: - conda run -n pycuda_3_10 python tests/run_kan99b_rotating_cylinder.py --phase a - conda run -n pycuda_3_10 python tests/run_kan99b_rotating_cylinder.py --phase b --domain M - conda run -n pycuda_3_10 python tests/run_kan99b_rotating_cylinder.py --phase c --minimal - conda run -n pycuda_3_10 python tests/run_kan99b_rotating_cylinder.py --phase all --minimal +- Primary matrix: K1-K5 with collision fixed to MRT. +- Primary inlet: regularized (uniform profile). +- Extra control: K2 with ``zou_he_local`` inlet for sensitivity only. """ from __future__ import annotations @@ -36,7 +17,7 @@ import os import sys import tempfile from dataclasses import dataclass -from typing import Any, Dict, Iterable, List, Optional, Sequence, Tuple +from typing import Any, Dict, List, Optional, Sequence, Tuple import numpy as np import pycuda.driver as cuda @@ -48,7 +29,7 @@ U_INF = 0.03 D_LATTICE = 30.0 R_LATTICE = 15.0 -# Kan99b Table I anchor (Re=100, alpha=1.0). + KAN99B_ANCHOR = { "St": 0.1655, "mean_cl": -2.4881, @@ -57,7 +38,6 @@ KAN99B_ANCHOR = { "amp_cd": 0.0993, } -# Preferred agreement bands from the validation plan (fractional errors). ANCHOR_BANDS = { "St": 0.03, "mean_cl": 0.04, @@ -66,20 +46,9 @@ ANCHOR_BANDS = { "amp_cd": 0.10, } -# Domain sensitivity thresholds vs domain L (fractional errors). -DOMAIN_THRESH = { - "St": 0.01, - "mean_cl": 0.02, - "mean_cd": 0.02, - "amp_cl": 0.03, - "amp_cd": 0.03, -} - @dataclass(frozen=True) class DomainSpec: - """Rectangular domain defined in lattice units.""" - key: str nx: int ny: int @@ -87,18 +56,45 @@ class DomainSpec: @dataclass(frozen=True) -class RunSpec: - """One executable run specification.""" - - phase: str - collision: str - domain: str +class KanCase: + case_id: str re: float alpha: float steps: int burn: int +@dataclass(frozen=True) +class RunSpec: + case_id: str + variant: str + domain: str + re: float + alpha: float + inlet_scheme: str + steps: int + burn: int + + +CASES: Tuple[KanCase, ...] = ( + KanCase("K1", 100.0, 0.5, 200_000, 80_000), + KanCase("K2", 100.0, 1.0, 200_000, 80_000), + KanCase("K3", 60.0, 1.6, 240_000, 120_000), + KanCase("K4", 100.0, 2.0, 240_000, 120_000), + KanCase("K5", 160.0, 2.0, 240_000, 120_000), +) + +CASE_K0 = KanCase("K0", 100.0, 0.0, 180_000, 72_000) + + +def _domain_specs() -> Dict[str, DomainSpec]: + return { + "S": DomainSpec("S", 1081, 481, (360.0, 240.0)), + "M": DomainSpec("M", 1351, 601, (450.0, 300.0)), + "L": DomainSpec("L", 1801, 721, (600.0, 360.0)), + } + + def _load_json(path: str) -> dict: with open(path, "r", encoding="utf-8") as f: return json.load(f) @@ -109,14 +105,6 @@ def _write_json(path: str, payload: dict) -> None: json.dump(payload, f, indent=2) -def _domain_specs() -> Dict[str, DomainSpec]: - return { - "S": DomainSpec("S", 1081, 481, (360.0, 240.0)), - "M": DomainSpec("M", 1351, 601, (450.0, 300.0)), - "L": DomainSpec("L", 1801, 721, (600.0, 360.0)), - } - - def _nu_from_re(re: float) -> float: return U_INF * D_LATTICE / float(re) @@ -127,10 +115,20 @@ def _omega_body(alpha: float) -> float: def _run_id(spec: RunSpec) -> str: a = f"{spec.alpha:.3f}".replace(".", "p") - return f"phase{spec.phase}_dom{spec.domain}_re{int(spec.re)}_a{a}_{spec.collision.lower()}" + return ( + f"{spec.case_id.lower()}_{spec.variant}_dom{spec.domain}_re{int(spec.re)}_a{a}_" + f"{spec.inlet_scheme.lower()}_mrt" + ) -def _build_cfg(base_cfg: dict, *, nx: int, ny: int, collision: str, re: float) -> dict: +def _build_cfg( + base_cfg: dict, + *, + nx: int, + ny: int, + re: float, + inlet_scheme: str, +) -> dict: cfg = json.loads(json.dumps(base_cfg)) cfg["grid"]["nx"] = int(nx) cfg["grid"]["ny"] = int(ny) @@ -138,18 +136,19 @@ def _build_cfg(base_cfg: dict, *, nx: int, ny: int, collision: str, re: float) - cfg["physics"]["velocity"] = float(U_INF) cfg["physics"]["viscosity"] = float(_nu_from_re(re)) cfg["physics"]["rho"] = 1.0 - cfg["method"]["collision"] = str(collision).upper() + cfg["method"]["collision"] = "MRT" cfg["method"]["streaming"] = "double_buffer" cfg["method"]["store_precision"] = "FP32" cfg["method"]["ddf_shifting"] = False cfg["method"]["les"]["enabled"] = False cfg["method"]["inlet"]["profile"] = "uniform" + cfg["method"]["inlet"]["scheme"] = str(inlet_scheme) cfg["method"]["outlet"]["mode"] = "neq_extrap" cfg["method"]["y_wall_bc"] = "free_slip" return cfg -def _body_doc(center: Tuple[float, float], *, alpha: float) -> dict: +def _body_doc(center: Tuple[float, float], alpha: float) -> dict: return { "objects": [ { @@ -163,13 +162,12 @@ def _body_doc(center: Tuple[float, float], *, alpha: float) -> dict: def _rfft_spectrum(x: np.ndarray, sample_dt: float) -> Tuple[np.ndarray, np.ndarray]: - v = np.asarray(x, dtype=np.float64) - if v.size < 64: + arr = np.asarray(x, dtype=np.float64) + if arr.size < 64: return np.zeros(0, dtype=np.float64), np.zeros(0, dtype=np.float64) - v = v - np.mean(v) - win = np.hanning(v.size) - spec = np.abs(np.fft.rfft(v * win)) ** 2 - freqs = np.fft.rfftfreq(v.size, d=float(sample_dt)) + arr = arr - np.mean(arr) + spec = np.abs(np.fft.rfft(arr * np.hanning(arr.size))) ** 2 + freqs = np.fft.rfftfreq(arr.size, d=float(sample_dt)) return freqs.astype(np.float64), spec.astype(np.float64) @@ -183,39 +181,34 @@ def _peak_freq_parabolic(freqs: np.ndarray, spec: np.ndarray, idx: int) -> float den = y0 - 2.0 * y1 + y2 if abs(den) < 1e-20: return float(freqs[i]) - delta = 0.5 * (y0 - y2) / den - delta = float(np.clip(delta, -1.0, 1.0)) - df = float(freqs[i + 1] - freqs[i]) - return float(freqs[i]) + delta * df + delta = float(np.clip(0.5 * (y0 - y2) / den, -1.0, 1.0)) + return float(freqs[i]) + delta * float(freqs[i + 1] - freqs[i]) def _st_from_lift(lift: np.ndarray, sample_dt: float) -> float: freqs, spec = _rfft_spectrum(lift, sample_dt=sample_dt) if freqs.size <= 1: return float("nan") - # Ignore DC bin. idx = int(np.argmax(spec[1:])) + 1 f_peak = _peak_freq_parabolic(freqs, spec, idx) return float(f_peak * D_LATTICE / U_INF) def _cycle_half_p2p(y: np.ndarray) -> float: - """Mean half peak-to-peak amplitude over cycles of demeaned signal.""" - s = np.asarray(y, dtype=np.float64) - if s.size < 8: + arr = np.asarray(y, dtype=np.float64) + if arr.size < 8: return float("nan") - d = s - np.mean(s) - crossing = np.where((d[:-1] <= 0.0) & (d[1:] > 0.0))[0] + centered = arr - np.mean(arr) + crossing = np.where((centered[:-1] <= 0.0) & (centered[1:] > 0.0))[0] if crossing.size >= 2: amps: List[float] = [] for i in range(crossing.size - 1): - seg = s[crossing[i] + 1 : crossing[i + 1] + 1] - if seg.size < 3: - continue - amps.append(0.5 * (float(np.max(seg)) - float(np.min(seg)))) + seg = arr[crossing[i] + 1 : crossing[i + 1] + 1] + if seg.size >= 3: + amps.append(0.5 * (float(np.max(seg)) - float(np.min(seg)))) if amps: return float(np.mean(amps)) - return 0.5 * (float(np.max(s)) - float(np.min(s))) + return 0.5 * (float(np.max(arr)) - float(np.min(arr))) def _vorticity_z(ux: np.ndarray, uy: np.ndarray) -> np.ndarray: @@ -232,7 +225,6 @@ def _save_vorticity_png(path: str, ux: np.ndarray, uy: np.ndarray, title: str) - import matplotlib.pyplot as plt except ImportError: return - omega = _vorticity_z(ux, uy) abs_o = np.abs(omega[np.isfinite(omega)]) vmax = float(np.percentile(abs_o, 99.5)) if abs_o.size else 1.0 @@ -268,19 +260,24 @@ def _run_one( field_every: int, save_vorticity: bool, ) -> Dict[str, Any]: - cfg = _build_cfg(base_cfg, nx=domain.nx, ny=domain.ny, collision=spec.collision, re=spec.re) - bdoc = _body_doc(domain.center, alpha=spec.alpha) + cfg = _build_cfg( + base_cfg, + nx=domain.nx, + ny=domain.ny, + re=spec.re, + inlet_scheme=spec.inlet_scheme, + ) + body = _body_doc(domain.center, alpha=spec.alpha) - tmpd = tempfile.mkdtemp(prefix="celeris_kan99b_") + tmpd = tempfile.mkdtemp(prefix="celeris_kan99b_mrt_") lbm_tmp = os.path.join(tmpd, "config_lbm.json") body_tmp = os.path.join(tmpd, "config_body.json") _write_json(lbm_tmp, cfg) - _write_json(body_tmp, bdoc) + _write_json(body_tmp, body) from CelerisLab import Simulation # noqa: WPS433 sim = Simulation(lbm_config_path=lbm_tmp, body_config_path=body_tmp) - # Contract: body omega is host-side runtime state from alpha conversion. if sim.bodies.count < 1: sim.close() raise RuntimeError("Expected one cylinder in body config.") @@ -294,7 +291,7 @@ def _run_one( sim.close() raise ValueError("burn + steps must be >= 1") - steps: List[int] = [] + step_hist: List[int] = [] fx_hist: List[float] = [] fy_hist: List[float] = [] field_snapshots: List[str] = [] @@ -315,26 +312,26 @@ def _run_one( stream.synchronize() sim.bodies.download_obs_full_async(stream) stream.synchronize() - fvec = sim.bodies.read_force(0) - fx = float(fvec[0]) - fy = float(fvec[1]) - steps.append(step) - fx_hist.append(fx) - fy_hist.append(fy) + force = sim.bodies.read_force(0) + fx = float(force[0]) + fy = float(force[1]) if not np.isfinite(fx) or not np.isfinite(fy): sim.close() raise RuntimeError(f"NaN/Inf force at step {step}") + step_hist.append(step) + fx_hist.append(fx) + fy_hist.append(fy) if field_every > 0 and (step % int(field_every) == 0 or step == total): stream.synchronize() macro = sim.get_macroscopic() - save_p = os.path.join(snap_dir, f"macro_step{step:08d}.npz") + snap_path = os.path.join(snap_dir, f"macro_step{step:08d}.npz") np.savez_compressed( - save_p, + snap_path, rho=np.asarray(macro["rho"], dtype=np.float32), ux=np.asarray(macro["ux"], dtype=np.float32), uy=np.asarray(macro["uy"], dtype=np.float32), ) - field_snapshots.append(save_p) + field_snapshots.append(snap_path) stream.synchronize() macro_last = sim.get_macroscopic() @@ -343,15 +340,15 @@ def _run_one( rho_last = np.asarray(macro_last["rho"], dtype=np.float64).reshape(domain.ny, domain.nx) sim.close() - step_arr = np.asarray(steps, dtype=np.int64) + step_arr = np.asarray(step_hist, dtype=np.int64) fx_arr = np.asarray(fx_hist, dtype=np.float64) fy_arr = np.asarray(fy_hist, dtype=np.float64) burn_mask = step_arr >= int(spec.burn) if not np.any(burn_mask): burn_mask = np.ones_like(step_arr, dtype=bool) - cl = 2.0 * fy_arr / (1.0 * (U_INF ** 2) * D_LATTICE) - cd = 2.0 * fx_arr / (1.0 * (U_INF ** 2) * D_LATTICE) + cl = 2.0 * fy_arr / (U_INF**2 * D_LATTICE) + cd = 2.0 * fx_arr / (U_INF**2 * D_LATTICE) cl_tail = cl[burn_mask] cd_tail = cd[burn_mask] st = _st_from_lift(cl_tail, sample_dt=float(rec)) @@ -375,249 +372,126 @@ def _run_one( ux_last, uy_last, title=( - f"Kan99b {spec.phase.upper()} {spec.collision} dom={spec.domain} " - f"Re={spec.re:.0f} alpha={spec.alpha:.3f}" + f"Kan99b {spec.case_id} {spec.variant} MRT dom={spec.domain} " + f"Re={spec.re:.0f} alpha={spec.alpha:.3f} inlet={spec.inlet_scheme}" ), ) return { "run_id": run_id, - "phase": spec.phase, - "collision": spec.collision, + "case_id": spec.case_id, + "variant": spec.variant, + "collision": "MRT", + "inlet_scheme": spec.inlet_scheme, + "inlet_profile": "uniform", "domain": spec.domain, "re": float(spec.re), "alpha": float(spec.alpha), "omega_body": float(_omega_body(spec.alpha)), "nu": float(_nu_from_re(spec.re)), "steps": int(spec.steps), - "burn": int(spec.burn), + "burn_in": int(spec.burn), "total_steps": int(total), "record_every": int(rec), "n_samples": int(step_arr.size), - "mean_cd": float(np.mean(cd_tail)), - "mean_cl": float(np.mean(cl_tail)), - "amp_cd": float(amp_cd), - "amp_cl": float(amp_cl), + "St": float(st), "st": float(st), + "mean_cl": float(np.mean(cl_tail)), + "mean_cd": float(np.mean(cd_tail)), + "amp_cl": float(amp_cl), + "amp_cd": float(amp_cd), "rho_min_final": float(np.min(rho_last)), "rho_max_final": float(np.max(rho_last)), + "grid": {"nx": int(domain.nx), "ny": int(domain.ny), "diameter": int(D_LATTICE)}, + "beta_real": None, + "Re_real": None, + "re_real": None, "force_csv": csv_path, "field_snapshots": field_snapshots, } -def _alpha_list_from_str(text: str) -> List[float]: - vals: List[float] = [] - for t in text.split(","): - t = t.strip() - if t: - vals.append(float(t)) - return vals - - -def _phase_runs( - phase: str, - *, - minimal: bool, - domain_key: str, - collisions: Sequence[str], - alpha_override: Optional[List[float]], - steps: int, - burn: int, -) -> List[RunSpec]: - runs: List[RunSpec] = [] - - def add_many( - p: str, - ds: Iterable[str], - cs: Iterable[str], - res: Iterable[float], - alphas: Iterable[float], - *, - phase_steps: Optional[int] = None, - phase_burn: Optional[int] = None, - ) -> None: - for d in ds: - for c in cs: - for re in res: - for a in alphas: - runs.append( - RunSpec( - phase=p, - collision=str(c).upper(), - domain=d, - re=float(re), - alpha=float(a), - steps=int(phase_steps if phase_steps is not None else steps), - burn=int(phase_burn if phase_burn is not None else burn), - ) - ) - - # Plan-driven defaults. - alpha_c = [0.0, 0.5, 1.0, 1.5, 1.7, 1.8, 1.9, 2.0] - alpha_c_min = [0.0, 1.0, 1.5, 1.8, 2.0] - alpha_d_60 = [0.0, 0.5, 1.0, 1.2, 1.4, 1.6] - alpha_d_160 = [0.0, 0.5, 1.0, 1.5, 1.8, 1.9, 2.0] - alpha_d_min = {60.0: [1.4], 160.0: [1.9]} - anchor_steps = 200_000 - anchor_burn = 80_000 - near_steps = 240_000 - near_burn = 120_000 - periodic_steps = 160_000 - periodic_burn = 64_000 - - if phase in ("a", "all"): - add_many("a", ["S", "M", "L"], ["MRT"], [100.0], [1.0], phase_steps=anchor_steps, phase_burn=anchor_burn) - if phase in ("anchor", "b", "all"): - add_many("b", [domain_key], collisions, [100.0], [1.0], phase_steps=anchor_steps, phase_burn=anchor_burn) - if phase in ("c", "all"): - alphas = alpha_override if alpha_override is not None else (alpha_c_min if minimal else alpha_c) - # Near-critical values need longer windows. - for a in alphas: - ps = near_steps if abs(a - 1.8) < 0.11 else periodic_steps - pb = near_burn if abs(a - 1.8) < 0.11 else periodic_burn - add_many("c", [domain_key], collisions, [100.0], [a], phase_steps=ps, phase_burn=pb) - if phase in ("d", "all"): - if minimal: - for re, alphas in alpha_d_min.items(): - add_many("d", [domain_key], collisions, [re], alphas, phase_steps=near_steps, phase_burn=near_burn) - else: - add_many("d", [domain_key], collisions, [60.0], alpha_d_60, phase_steps=periodic_steps, phase_burn=periodic_burn) - add_many("d", [domain_key], collisions, [160.0], alpha_d_160, phase_steps=periodic_steps, phase_burn=periodic_burn) - - # CLI override for quick tests. - if steps > 0: - for i in range(len(runs)): - runs[i] = RunSpec( - phase=runs[i].phase, - collision=runs[i].collision, - domain=runs[i].domain, - re=runs[i].re, - alpha=runs[i].alpha, - steps=steps, - burn=burn, - ) - return runs - - -def _rel_err(meas: float, ref: float) -> Optional[float]: - if not np.isfinite(meas) or ref == 0: +def _rel_err(measured: float, ref: float) -> Optional[float]: + if not np.isfinite(measured) or ref == 0.0: return None - return abs(float(meas) - float(ref)) / abs(float(ref)) + return abs(float(measured) - float(ref)) / abs(float(ref)) -def _phase_a_gate(rows: List[Dict[str, Any]]) -> Dict[str, Any]: - dom = {r["domain"]: r for r in rows} - out: Dict[str, Any] = {"phase": "a", "pass": False} - if not all(k in dom for k in ("S", "M", "L")): - out["error"] = "Phase A needs S, M, L rows." - return out - l = dom["L"] - compare: Dict[str, Any] = {} - for k in ("S", "M"): - r = dom[k] - compare[k] = { - "St": _rel_err(r["st"], l["st"]), - "mean_cl": _rel_err(r["mean_cl"], l["mean_cl"]), - "mean_cd": _rel_err(r["mean_cd"], l["mean_cd"]), - "amp_cl": _rel_err(r["amp_cl"], l["amp_cl"]), - "amp_cd": _rel_err(r["amp_cd"], l["amp_cd"]), - } - choose = "L" - m_ok = all( - (compare["M"][metric] is not None and compare["M"][metric] <= DOMAIN_THRESH[metric]) - for metric in DOMAIN_THRESH - ) - if m_ok: - choose = "M" - else: - s_ok = all( - (compare["S"][metric] is not None and compare["S"][metric] <= DOMAIN_THRESH[metric]) - for metric in DOMAIN_THRESH - ) - if s_ok: - choose = "S" - out["compare_vs_L"] = compare - out["recommended_domain"] = choose - out["pass"] = True - return out - - -def _phase_b_anchor_eval(rows: List[Dict[str, Any]]) -> Dict[str, Any]: - by_coll = {r["collision"]: r for r in rows} - out: Dict[str, Any] = {"phase": "b", "rows": {}} - for coll in ("SRT", "TRT", "MRT"): - row = by_coll.get(coll) - if row is None: +def _k2_anchor_gate(rows: Sequence[Dict[str, Any]]) -> List[Dict[str, Any]]: + """Evaluate K2 rows against Kan99b anchor tolerances.""" + out: List[Dict[str, Any]] = [] + for row in rows: + if row.get("case_id") != "K2" or "error" in row: continue - metrics = { - "St": _rel_err(row["st"], KAN99B_ANCHOR["St"]), + rel = { + "St": _rel_err(row["St"], KAN99B_ANCHOR["St"]), "mean_cl": _rel_err(row["mean_cl"], KAN99B_ANCHOR["mean_cl"]), "mean_cd": _rel_err(row["mean_cd"], KAN99B_ANCHOR["mean_cd"]), "amp_cl": _rel_err(row["amp_cl"], KAN99B_ANCHOR["amp_cl"]), "amp_cd": _rel_err(row["amp_cd"], KAN99B_ANCHOR["amp_cd"]), } - out["rows"][coll] = { - "rel_err": metrics, - "pass_bands": { - m: (metrics[m] is not None and metrics[m] <= ANCHOR_BANDS[m]) for m in ANCHOR_BANDS - }, + pass_bands = { + key: (rel[key] is not None and rel[key] <= ANCHOR_BANDS[key]) for key in ANCHOR_BANDS } + out.append( + { + "run_id": row["run_id"], + "variant": row["variant"], + "inlet_scheme": row["inlet_scheme"], + "rel_err": rel, + "pass_bands": pass_bands, + "pass_all": bool(all(pass_bands.values())), + } + ) return out -def _save_summary_plots(rows: List[Dict[str, Any]], out_dir: str) -> None: - try: - import matplotlib - - matplotlib.use("Agg") - import matplotlib.pyplot as plt - except ImportError: - return - - summary_dir = os.path.join(out_dir, "summary_plots") - os.makedirs(summary_dir, exist_ok=True) - - def plot_metric(metric: str, ylabel: str, filename: str) -> None: - fig, ax = plt.subplots(figsize=(8, 5)) - for coll in ("SRT", "TRT", "MRT"): - coll_rows = [r for r in rows if r["collision"] == coll] - if not coll_rows: - continue - # Use Re=100 sweep first if present, else all points sorted by alpha. - target = [r for r in coll_rows if abs(r["re"] - 100.0) < 1e-9] - data = target if target else coll_rows - data = sorted(data, key=lambda r: (r["re"], r["alpha"])) - ax.plot( - [r["alpha"] for r in data], - [r[metric] for r in data], - marker="o", - linewidth=1.4, - label=coll, +def _build_runs( + cases: Sequence[KanCase], + *, + domain: str, + include_k2_control: bool, + steps_override: int, + burn_override: int, +) -> List[RunSpec]: + runs: List[RunSpec] = [] + for case in cases: + steps = int(steps_override) if steps_override > 0 else int(case.steps) + burn = int(burn_override) if burn_override > 0 else int(case.burn) + runs.append( + RunSpec( + case_id=case.case_id, + variant="baseline", + domain=domain, + re=case.re, + alpha=case.alpha, + inlet_scheme="regularized", + steps=steps, + burn=burn, ) - ax.set_xlabel("alpha") - ax.set_ylabel(ylabel) - ax.set_title(f"{ylabel} vs alpha") - ax.grid(True, alpha=0.3) - ax.legend(loc="best") - fig.tight_layout() - fig.savefig(os.path.join(summary_dir, filename), dpi=150, bbox_inches="tight") - plt.close(fig) - - plot_metric("mean_cl", "mean C_L", "mean_cl_vs_alpha.png") - plot_metric("mean_cd", "mean C_D", "mean_cd_vs_alpha.png") - plot_metric("amp_cl", "C'_L (half peak-to-peak)", "amp_cl_vs_alpha.png") - plot_metric("st", "St", "st_vs_alpha.png") + ) + if include_k2_control and case.case_id == "K2": + runs.append( + RunSpec( + case_id=case.case_id, + variant="k2_inlet_control", + domain=domain, + re=case.re, + alpha=case.alpha, + inlet_scheme="zou_he_local", + steps=steps, + burn=burn, + ) + ) + return runs def main() -> int: - ap = argparse.ArgumentParser(description="Kan99b rotating-cylinder validation driver") - ap.add_argument("--phase", default="all", choices=("anchor", "a", "b", "c", "d", "all")) - ap.add_argument("--minimal", action="store_true", help="Run reduced minimum set from the plan.") - ap.add_argument("--domain", default="M", choices=("S", "M", "L"), help="Default domain for phases B/C/D.") - ap.add_argument("--collision", default="all", choices=("SRT", "TRT", "MRT", "all")) - ap.add_argument("--alpha", type=float, default=None, help="Single alpha override (for c/d phases).") - ap.add_argument("--alpha-list", type=str, default="", help="Comma-separated alpha list override.") + ap = argparse.ArgumentParser(description="Kan99b MRT-only primary matrix runner") + ap.add_argument("--case", default="all", help='Case id K1-K5/K0 or "all"') + ap.add_argument("--include-k0", action="store_true", help="Include optional K0 baseline.") + ap.add_argument("--no-k2-control", action="store_true", help="Disable K2 zou_he_local control run.") + ap.add_argument("--domain", default="M", choices=("S", "M", "L")) ap.add_argument("--steps", type=int, default=0, help="Override run steps for all selected runs.") ap.add_argument("--burn", type=int, default=0, help="Override burn steps for all selected runs.") ap.add_argument("--record-every", type=int, default=100) @@ -632,112 +506,112 @@ def main() -> int: print(f"Missing base config: {_DEFAULT_LBM}", file=sys.stderr) return 2 base_cfg = _load_json(_DEFAULT_LBM) + domains = _domain_specs() - out_dir = os.path.abspath(args.out_dir) - os.makedirs(out_dir, exist_ok=True) + sel = str(args.case).upper() + allowed = {case.case_id for case in CASES} | {"K0", "ALL"} + if sel not in allowed: + print("--case must be K0,K1,K2,K3,K4,K5,all", file=sys.stderr) + return 2 - collisions = ["SRT", "TRT", "MRT"] if args.collision == "all" else [str(args.collision).upper()] - alpha_override: Optional[List[float]] = None - if args.alpha is not None: - alpha_override = [float(args.alpha)] - elif args.alpha_list.strip(): - alpha_override = _alpha_list_from_str(args.alpha_list) - - if args.smoke: - o_steps = 2000 - o_burn = 800 + selected: List[KanCase] = [] + if sel == "ALL": + selected.extend(CASES) + if args.include_k0: + selected.insert(0, CASE_K0) + elif sel == "K0": + selected.append(CASE_K0) else: - o_steps = max(0, int(args.steps)) - o_burn = max(0, int(args.burn)) + selected.extend(case for case in CASES if case.case_id == sel) - runs = _phase_runs( - args.phase, - minimal=bool(args.minimal), - domain_key=args.domain, - collisions=collisions, - alpha_override=alpha_override, - steps=o_steps, - burn=o_burn, - ) - if not runs: + if not selected: print("No runs selected.", file=sys.stderr) return 2 - domains = _domain_specs() + steps_override = 2000 if args.smoke else max(0, int(args.steps)) + burn_override = 800 if args.smoke else max(0, int(args.burn)) + runs = _build_runs( + selected, + domain=args.domain, + include_k2_control=not bool(args.no_k2_control), + steps_override=steps_override, + burn_override=burn_override, + ) + + out_dir = os.path.abspath(args.out_dir) + os.makedirs(out_dir, exist_ok=True) rows: List[Dict[str, Any]] = [] - - contract = { - "U_inf": U_INF, - "D_lattice": D_LATTICE, - "R_lattice": R_LATTICE, - "nu_formula": "nu = U_inf * D / Re = 0.9 / Re", - "omega_formula": "omega_body = 2 * alpha * U_inf / D = 0.002 * alpha", - "method_contract": { - "inlet_profile": "uniform", - "y_wall_bc": "free_slip", - "outlet_mode": "neq_extrap", - "streaming": "double_buffer", - "store_precision": "FP32", - "les_enabled": False, - }, - } - for spec in runs: - dspec = domains[spec.domain] print( - f"--- {spec.phase.upper()} {spec.collision} dom={spec.domain} Re={spec.re:.0f} " - f"alpha={spec.alpha:.3f} burn={spec.burn} steps={spec.steps} ---", + f"--- {spec.case_id} {spec.variant} MRT dom={spec.domain} Re={spec.re:.0f} " + f"alpha={spec.alpha:.3f} inlet={spec.inlet_scheme} burn={spec.burn} steps={spec.steps} ---", flush=True, ) try: row = _run_one( spec, - domain=dspec, + domain=domains[spec.domain], base_cfg=base_cfg, out_dir=out_dir, record_every=max(1, int(args.record_every)), field_every=max(0, int(args.field_every)), save_vorticity=bool(args.save_vorticity), ) - except Exception as e: # noqa: BLE001 + if spec.case_id == "K2": + rel = _rel_err(row["St"], KAN99B_ANCHOR["St"]) + row["St_error_pct"] = 100.0 * rel if rel is not None else None + else: + row["St_error_pct"] = None + rows.append(row) + print( + f" St={row['St']:.5f} mean_CL={row['mean_cl']:.4f} mean_CD={row['mean_cd']:.4f} " + f"C'L={row['amp_cl']:.4f} C'D={row['amp_cd']:.4f}", + flush=True, + ) + except Exception as exc: # noqa: BLE001 rows.append( { "run_id": _run_id(spec), - "phase": spec.phase, - "collision": spec.collision, + "case_id": spec.case_id, + "variant": spec.variant, + "collision": "MRT", + "inlet_scheme": spec.inlet_scheme, + "inlet_profile": "uniform", "domain": spec.domain, "re": float(spec.re), "alpha": float(spec.alpha), - "error": str(e), + "steps": int(spec.steps), + "burn_in": int(spec.burn), + "error": str(exc), } ) - print(f"FAILED: {e}", flush=True) - continue - rows.append(row) - print( - " " - f"St={row['st']:.5f} mean_CL={row['mean_cl']:.4f} mean_CD={row['mean_cd']:.4f} " - f"C'L={row['amp_cl']:.4f} C'D={row['amp_cd']:.4f}", - flush=True, - ) + print(f"FAILED: {exc}", flush=True) + + k2_gate = _k2_anchor_gate(rows) + print("\n=== Kan99b K2 gate summary ===", flush=True) + print(json.dumps({"k2_runs": k2_gate}, indent=2), flush=True) - # Summary table outputs summary_csv = os.path.join(out_dir, "summary_runs.csv") csv_keys = [ "run_id", - "phase", + "case_id", + "variant", "collision", + "inlet_scheme", + "inlet_profile", "domain", "re", "alpha", "omega_body", "nu", - "burn", + "burn_in", "steps", "total_steps", "record_every", "n_samples", + "St", "st", + "St_error_pct", "mean_cl", "mean_cd", "amp_cl", @@ -748,30 +622,31 @@ def main() -> int: "error", ] with open(summary_csv, "w", newline="", encoding="utf-8") as f: - w = csv.DictWriter(f, fieldnames=csv_keys) - w.writeheader() - for r in rows: - w.writerow({k: r.get(k, "") for k in csv_keys}) - - phase_reports: Dict[str, Any] = {} - phase_a_rows = [r for r in rows if r.get("phase") == "a" and "error" not in r] - if phase_a_rows: - phase_reports["a"] = _phase_a_gate(phase_a_rows) - phase_b_rows = [r for r in rows if r.get("phase") == "b" and "error" not in r] - if phase_b_rows: - phase_reports["b"] = _phase_b_anchor_eval(phase_b_rows) - - _save_summary_plots([r for r in rows if "error" not in r], out_dir) + writer = csv.DictWriter(f, fieldnames=csv_keys) + writer.writeheader() + for row in rows: + writer.writerow({k: row.get(k, "") for k in csv_keys}) summary = { - "contract": contract, + "contract": { + "collision": "MRT", + "primary_inlet_scheme": "regularized", + "k2_control_inlet_scheme": "zou_he_local", + "inlet_profile": "uniform", + "y_wall_bc": "free_slip", + "outlet_mode": "neq_extrap", + "streaming": "double_buffer", + "store_precision": "FP32", + "les_enabled": False, + }, "requested": { - "phase": args.phase, - "minimal": bool(args.minimal), + "case": args.case, + "include_k0": bool(args.include_k0), + "include_k2_control": not bool(args.no_k2_control), "domain": args.domain, - "collision": args.collision, - "steps_override": int(o_steps), - "burn_override": int(o_burn), + "smoke": bool(args.smoke), + "steps_override": int(steps_override), + "burn_override": int(burn_override), "record_every": int(args.record_every), "field_every": int(args.field_every), "save_vorticity": bool(args.save_vorticity), @@ -781,7 +656,7 @@ def main() -> int: "completed_runs": sum(1 for r in rows if "error" not in r), "failed_runs": sum(1 for r in rows if "error" in r), }, - "phase_reports": phase_reports, + "k2_gate": k2_gate, "rows": rows, } json_out = ( @@ -789,11 +664,13 @@ def main() -> int: if args.json_out.strip() else os.path.join(out_dir, "summary_runs.json") ) + json_out_dir = os.path.dirname(json_out) + if json_out_dir: + os.makedirs(json_out_dir, exist_ok=True) _write_json(json_out, summary) print(f"Wrote: {summary_csv}", flush=True) print(f"Wrote: {json_out}", flush=True) - print(f"Wrote: {os.path.join(out_dir, 'summary_plots')}", flush=True) return 0 diff --git a/tests/run_kan99b_streakline.py b/tests/run_kan99b_streakline.py new file mode 100644 index 0000000..8f90911 --- /dev/null +++ b/tests/run_kan99b_streakline.py @@ -0,0 +1,309 @@ +# CelerisLab/tests/run_kan99b_streakline.py +"""Kan99b streakline demo using CelerisLab common streakline engine. + +Modes: + - online: run CFD and compute streakline in memory (no velocity dump). + - offline: load velocity snapshots and reconstruct streakline. +""" + +from __future__ import annotations + +import argparse +import json +import os +import tempfile +from dataclasses import dataclass +from typing import List, Tuple + +import numpy as np + +from CelerisLab import Simulation +from CelerisLab.common.streakline import ( + FlowFrame, + IntegratorConfig, + ReleaseConfig, + build_release_points, + estimate_sampling_plan, + render_streakline_density, + run_streakline_offline, + run_streakline_online, +) + +_REPO = os.path.abspath(os.path.join(os.path.dirname(__file__), "..")) +_DEFAULT_LBM = os.path.join(_REPO, "src", "CelerisLab", "configs", "config_lbm.json") + +U_INF = 0.03 +D_LATTICE = 30.0 +R_LATTICE = 15.0 +KAN99B_ST_REF = 0.1655 + + +@dataclass(frozen=True) +class DomainSpec: + key: str + nx: int + ny: int + center: Tuple[float, float] + + +def _domain_specs() -> dict: + return { + "S": DomainSpec("S", 1081, 481, (360.0, 240.0)), + "M": DomainSpec("M", 1351, 601, (450.0, 300.0)), + "L": DomainSpec("L", 1801, 721, (600.0, 360.0)), + } + + +def _load_json(path: str) -> dict: + with open(path, "r", encoding="utf-8") as f: + return json.load(f) + + +def _write_json(path: str, payload: dict) -> None: + with open(path, "w", encoding="utf-8") as f: + json.dump(payload, f, indent=2) + + +def _nu_from_re(reynolds: float) -> float: + return U_INF * D_LATTICE / float(reynolds) + + +def _omega_body(alpha: float) -> float: + return 2.0 * float(alpha) * U_INF / D_LATTICE + + +def _build_cfg(base_cfg: dict, *, nx: int, ny: int, re: float, inlet_scheme: str) -> dict: + cfg = json.loads(json.dumps(base_cfg)) + cfg["grid"]["nx"] = int(nx) + cfg["grid"]["ny"] = int(ny) + cfg["grid"]["nz"] = 1 + cfg["physics"]["velocity"] = float(U_INF) + cfg["physics"]["viscosity"] = float(_nu_from_re(re)) + cfg["physics"]["rho"] = 1.0 + cfg["method"]["collision"] = "MRT" + cfg["method"]["streaming"] = "double_buffer" + cfg["method"]["store_precision"] = "FP32" + cfg["method"]["ddf_shifting"] = False + cfg["method"]["les"]["enabled"] = False + cfg["method"]["inlet"]["profile"] = "uniform" + cfg["method"]["inlet"]["scheme"] = str(inlet_scheme) + cfg["method"]["outlet"]["mode"] = "neq_extrap" + cfg["method"]["y_wall_bc"] = "free_slip" + return cfg + + +def _build_simulation(*, domain: DomainSpec, re: float, alpha: float, inlet_scheme: str) -> Simulation: + base_cfg = _load_json(_DEFAULT_LBM) + cfg = _build_cfg(base_cfg, nx=domain.nx, ny=domain.ny, re=re, inlet_scheme=inlet_scheme) + body_doc = { + "objects": [ + { + "type": "cylinder", + "center": [float(domain.center[0]), float(domain.center[1])], + "radius": float(R_LATTICE), + "omega": float(_omega_body(alpha)), + } + ] + } + tmpd = tempfile.mkdtemp(prefix="celeris_streakline_online_") + lbm_tmp = os.path.join(tmpd, "config_lbm.json") + body_tmp = os.path.join(tmpd, "config_body.json") + _write_json(lbm_tmp, cfg) + _write_json(body_tmp, body_doc) + sim = Simulation(lbm_config_path=lbm_tmp, body_config_path=body_tmp) + sim.bodies.get(0).state.omega = np.float32(_omega_body(alpha)) + sim.initialize() + return sim + + +def _default_base_release_points(center: Tuple[float, float]) -> np.ndarray: + x_rel = float(center[0] - 6.0 * D_LATTICE) + y0 = float(center[1]) + return np.array( + [ + [x_rel, y0 - 18.0], + [x_rel, y0 - 6.0], + [x_rel, y0 + 6.0], + [x_rel, y0 + 18.0], + ], + dtype=np.float64, + ) + + +def _load_offline_frames(snapshot_dir: str) -> List[FlowFrame]: + files = sorted( + [ + os.path.join(snapshot_dir, name) + for name in os.listdir(snapshot_dir) + if name.endswith(".npz") and name.startswith("vel_step") + ] + ) + frames: List[FlowFrame] = [] + for path in files: + data = np.load(path) + step = int(np.asarray(data["step"]).reshape(-1)[0]) + frames.append( + FlowFrame( + step=step, + ux=np.asarray(data["ux"], dtype=np.float64), + uy=np.asarray(data["uy"], dtype=np.float64), + ) + ) + return frames + + +def main() -> int: + ap = argparse.ArgumentParser(description="Kan99b streakline demo (online/offline)") + ap.add_argument("--mode", default="online", choices=("online", "offline")) + ap.add_argument("--domain", default="M", choices=("S", "M", "L")) + ap.add_argument("--re", type=float, default=100.0) + ap.add_argument("--alpha", type=float, default=1.0) + ap.add_argument("--inlet-scheme", default="regularized", choices=("regularized", "zou_he_local")) + ap.add_argument("--start-step", type=int, default=60_000) + ap.add_argument("--sample-every", type=int, default=0, help="0 uses recommended value.") + ap.add_argument("--n-snapshots", type=int, default=0, help="0 uses recommended value.") + ap.add_argument("--snapshot-dir", type=str, default="", help="Required in offline mode.") + ap.add_argument("--release-mode", default="strip", choices=("point", "line", "strip")) + ap.add_argument("--line-span", type=float, default=0.0) + ap.add_argument("--line-count", type=int, default=1) + ap.add_argument( + "--downstream-count", + type=int, + default=5, + help="Particles per seed along +x (denser streak).", + ) + ap.add_argument( + "--downstream-spacing", + type=float, + default=1.0, + help="Lattice spacing between x-staggered release points.", + ) + ap.add_argument("--inject-per-seed", type=int, default=2) + ap.add_argument("--alpha-t", type=float, default=0.2) + ap.add_argument("--alpha-x", type=float, default=0.4) + ap.add_argument("--diffusion-coeff", type=float, default=0.0) + ap.add_argument( + "--out-dir", + type=str, + default=os.path.join(_REPO, "tests", "output", "streakline", "kan99b_k2_online"), + ) + args = ap.parse_args() + + domain = _domain_specs()[args.domain] + out_dir = os.path.abspath(args.out_dir) + os.makedirs(out_dir, exist_ok=True) + + plan = estimate_sampling_plan(st_ref=KAN99B_ST_REF, diameter=D_LATTICE, u_ref=U_INF) + sample_every = int(args.sample_every) if int(args.sample_every) > 0 else int(plan["save_every_recommended"]) + n_snapshots = int(args.n_snapshots) if int(args.n_snapshots) > 0 else int(plan["snapshot_count_recommended"]) + + release_cfg = ReleaseConfig( + mode=args.release_mode, + line_span=float(args.line_span), + line_count=max(1, int(args.line_count)), + downstream_count=max(1, int(args.downstream_count)), + downstream_spacing=float(args.downstream_spacing), + inject_per_seed=max(1, int(args.inject_per_seed)), + ) + integrator_cfg = IntegratorConfig( + alpha_t=float(args.alpha_t), + alpha_x=float(args.alpha_x), + diffusion_coeff=float(args.diffusion_coeff), + ) + base_release_points = _default_base_release_points(domain.center) + dense_release_points = build_release_points(base_release_points, release_cfg) + + if args.mode == "online": + sim = _build_simulation( + domain=domain, + re=float(args.re), + alpha=float(args.alpha), + inlet_scheme=args.inlet_scheme, + ) + try: + particles, ages, diag = run_streakline_online( + sim, + start_step=int(args.start_step), + sample_every=int(sample_every), + n_samples=int(n_snapshots), + release_points=base_release_points, + release_cfg=release_cfg, + integrator_cfg=integrator_cfg, + solid_center=domain.center, + solid_radius=R_LATTICE, + ) + finally: + sim.close() + frame_count = int(n_snapshots) + frame_source = "in-memory online sampling" + else: + if not args.snapshot_dir: + raise ValueError("--snapshot-dir is required in offline mode.") + frames = _load_offline_frames(os.path.abspath(args.snapshot_dir)) + if len(frames) < 2: + raise RuntimeError("Offline mode needs at least two velocity snapshots.") + particles, ages, diag = run_streakline_offline( + frames, + nx=domain.nx, + ny=domain.ny, + release_points=base_release_points, + release_cfg=release_cfg, + integrator_cfg=integrator_cfg, + solid_center=domain.center, + solid_radius=R_LATTICE, + ) + frame_count = len(frames) + frame_source = os.path.abspath(args.snapshot_dir) + + render_info = render_streakline_density( + particles, + ages, + nx=domain.nx, + ny=domain.ny, + out_path=os.path.join(out_dir, "streakline.png"), + release_points=dense_release_points, + solid_center=domain.center, + solid_radius=R_LATTICE, + age_decay_steps=integrator_cfg.age_decay_steps, + blur_sigma=1.2, + title=f"Kan99b streakline ({args.mode}, {args.release_mode})", + ) + + meta = { + "case": { + "domain": args.domain, + "re": float(args.re), + "alpha": float(args.alpha), + "inlet_scheme": args.inlet_scheme, + "collision": "MRT", + }, + "mode": args.mode, + "sampling_estimate": plan, + "sampling_used": { + "start_step": int(args.start_step), + "sample_every": int(sample_every), + "n_snapshots": int(n_snapshots), + "frame_source": frame_source, + "frames_used": int(frame_count), + }, + "release": { + "config": vars(args), + "base_points": base_release_points.tolist(), + "dense_point_count": int(dense_release_points.shape[0]), + "dense_points_preview": dense_release_points[: min(12, dense_release_points.shape[0])].tolist(), + }, + "diagnostics": diag, + "render": render_info, + } + _write_json(os.path.join(out_dir, "streakline_meta.json"), meta) + + print(f"Recommended sample_every: {plan['save_every_recommended']} steps") + print(f"Recommended snapshots: {plan['snapshot_count_recommended']}") + print(f"Mode: {args.mode} | frames used: {frame_count}") + print(f"Dense release points: {dense_release_points.shape[0]}") + print(f"Output image: {render_info['image_path']}") + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/tests/run_perf_baseline.py b/tests/run_perf_baseline.py new file mode 100644 index 0000000..9e51371 --- /dev/null +++ b/tests/run_perf_baseline.py @@ -0,0 +1,269 @@ +"""Performance baseline for minimal host-interference LBM stepping. + +This script builds a temporary config, runs warmup + measured batches, and +reports MLUPS under a FluidX3D-like benchmark mindset: + +- keep the main loop on GPU (`stepper.step`) +- avoid host downloads by default +- selectively enable host-touch paths to quantify overhead +- sweep `inlet.scheme` to check stability-sensitive combinations + +Usage:: + python tests/run_perf_baseline.py + python tests/run_perf_baseline.py --lattice-model D2Q9 --nx 384 --ny 192 --steps 30000 + python tests/run_perf_baseline.py --macro-every 500 --ddf-every 1000 + python tests/run_perf_baseline.py --with-cylinder --obs-every 50 +""" + +from __future__ import annotations + +import argparse +import json +import os +import tempfile +import time +from typing import Any, Dict, List + +import pycuda.driver as cuda + +_REPO = os.path.abspath(os.path.join(os.path.dirname(__file__), "..")) +_DEFAULT_LBM = os.path.join(_REPO, "src", "CelerisLab", "configs", "config_lbm.json") + + +def _load_json(path: str) -> dict: + with open(path, "r", encoding="utf-8") as f: + return json.load(f) + + +def _write_json(path: str, payload: dict) -> None: + with open(path, "w", encoding="utf-8") as f: + json.dump(payload, f, indent=2) + + +def _build_lbm_cfg(base: dict, args: argparse.Namespace) -> dict: + cfg = json.loads(json.dumps(base)) + cfg["grid"]["lattice_model"] = args.lattice_model + cfg["grid"]["nx"] = int(args.nx) + cfg["grid"]["ny"] = int(args.ny) + cfg["grid"]["nz"] = int(args.nz) + + cfg["physics"]["viscosity"] = float(args.viscosity) + cfg["physics"]["velocity"] = float(args.velocity) + cfg["physics"]["rho"] = float(args.rho) + + cfg["method"]["collision"] = str(args.collision).upper() + cfg["method"]["streaming"] = str(args.streaming) + cfg["method"]["store_precision"] = str(args.store_precision).upper() + cfg["method"]["ddf_shifting"] = bool(args.ddf_shifting) + cfg["method"]["les"]["enabled"] = bool(args.enable_les) + cfg["method"]["outlet"]["mode"] = str(args.outlet_mode) + cfg["method"]["inlet"]["profile"] = str(args.inlet_profile) + # Expose inlet scheme as a benchmark axis; useful when stability depends + # on collision/inlet coupling. + cfg["method"]["inlet"]["scheme"] = str(args.inlet_scheme) + cfg["method"]["y_wall_bc"] = str(args.y_wall_bc) + + cfg["cuda"]["threads_per_block"] = int(args.threads_per_block) + cfg["cuda"]["compute_capability"] = str(args.compute_capability) + return cfg + + +def _build_body_cfg(args: argparse.Namespace) -> dict: + if not args.with_cylinder: + return {"objects": []} + cx = 0.5 * float(args.nx) + cy = 0.5 * float(args.ny) + radius = max(2.0, min(float(args.nx), float(args.ny)) * 0.08) + return { + "objects": [ + { + "type": "cylinder", + "center": [cx, cy], + "radius": radius, + } + ] + } + + +def _maybe_probe_macroscopic(sim: Any, step: int, every: int, repeat: int) -> None: + if every > 0 and step % every == 0: + for _ in range(max(1, int(repeat))): + _ = sim.get_macroscopic() + + +def _maybe_probe_ddf(sim: Any, step: int, every: int, repeat: int) -> None: + if every > 0 and step % every == 0: + for _ in range(max(1, int(repeat))): + _ = sim.get_ddf() + + +def _maybe_checkpoint(sim: Any, step: int, every: int, out_dir: str) -> None: + if every > 0 and step % every == 0: + path = os.path.join(out_dir, f"checkpoint_step_{step:09d}.h5") + sim.save_checkpoint(path) + + +def _maybe_probe_obs(sim: Any, stream: cuda.Stream, step: int, every: int) -> None: + if every <= 0 or step % every != 0: + return + if sim.bodies.count <= 0: + return + sim.bodies.download_obs_full_async(stream) + stream.synchronize() + _ = sim.bodies.read_force(0) + + +def run(args: argparse.Namespace) -> Dict[str, Any]: + if not os.path.isfile(_DEFAULT_LBM): + raise FileNotFoundError(f"Base config missing: {_DEFAULT_LBM}") + + base = _load_json(_DEFAULT_LBM) + lbm_cfg = _build_lbm_cfg(base, args) + body_cfg = _build_body_cfg(args) + + tmpd = tempfile.mkdtemp(prefix="celeris_perf_baseline_") + lbm_path = os.path.join(tmpd, "config_lbm.json") + body_path = os.path.join(tmpd, "config_body.json") + ckpt_dir = os.path.join(tmpd, "checkpoints") + os.makedirs(ckpt_dir, exist_ok=True) + _write_json(lbm_path, lbm_cfg) + _write_json(body_path, body_cfg) + + from CelerisLab import Simulation # noqa: WPS433 + + sim = Simulation(lbm_config_path=lbm_path, body_config_path=body_path, device_id=args.device_id) + sim.initialize() + + stream = cuda.Stream() + total_cells = int(args.nx) * int(args.ny) * int(args.nz) + + # Warmup before measurement window. + warmup_done = 0 + while warmup_done < int(args.warmup_steps): + chunk = min(int(args.batch_steps), int(args.warmup_steps) - warmup_done) + sim.stepper.step(chunk, action_gpu=sim.bodies.action_gpu, obs_gpu=sim.bodies.obs_gpu, stream=stream) + warmup_done += chunk + stream.synchronize() + + measured_batch_s: List[float] = [] + measured_steps = int(args.steps) + done = 0 + t0 = time.perf_counter() + while done < measured_steps: + chunk = min(int(args.batch_steps), measured_steps - done) + step_start = time.perf_counter() + sim.stepper.step(chunk, action_gpu=sim.bodies.action_gpu, obs_gpu=sim.bodies.obs_gpu, stream=stream) + stream.synchronize() + step_end = time.perf_counter() + measured_batch_s.append(step_end - step_start) + + done += chunk + global_step = sim.stepper.step_count + _maybe_probe_macroscopic(sim, global_step, int(args.macro_every), int(args.macro_repeat)) + _maybe_probe_ddf(sim, global_step, int(args.ddf_every), int(args.ddf_repeat)) + _maybe_checkpoint(sim, global_step, int(args.checkpoint_every), ckpt_dir) + _maybe_probe_obs(sim, stream, global_step, int(args.obs_every)) + + stream.synchronize() + elapsed_s = time.perf_counter() - t0 + + # Optional final sanity readback (outside core timing path by default). + if args.final_macro_snapshot: + _ = sim.get_macroscopic() + + sim.close() + + mlups = (total_cells * measured_steps) / max(elapsed_s, 1e-12) / 1.0e6 + batch_ms = [1000.0 * x for x in measured_batch_s] + batch_ms_sorted = sorted(batch_ms) + p50 = batch_ms_sorted[len(batch_ms_sorted) // 2] if batch_ms_sorted else 0.0 + p90 = batch_ms_sorted[min(len(batch_ms_sorted) - 1, int(0.9 * (len(batch_ms_sorted) - 1)))] if batch_ms_sorted else 0.0 + + return { + "benchmark": "celerislab_stepper_baseline", + "device_id": int(args.device_id), + "lattice_model": args.lattice_model, + "grid": {"nx": int(args.nx), "ny": int(args.ny), "nz": int(args.nz)}, + "collision": str(args.collision).upper(), + "inlet_scheme": str(args.inlet_scheme), + "streaming": str(args.streaming), + "store_precision": str(args.store_precision).upper(), + "steps": measured_steps, + "warmup_steps": int(args.warmup_steps), + "batch_steps": int(args.batch_steps), + "mlups": float(mlups), + "elapsed_s": float(elapsed_s), + "batch_ms_p50": float(p50), + "batch_ms_p90": float(p90), + "overhead_switches": { + "macro_every": int(args.macro_every), + "macro_repeat": int(args.macro_repeat), + "ddf_every": int(args.ddf_every), + "ddf_repeat": int(args.ddf_repeat), + "checkpoint_every": int(args.checkpoint_every), + "obs_every": int(args.obs_every), + "with_cylinder": bool(args.with_cylinder), + "final_macro_snapshot": bool(args.final_macro_snapshot), + }, + } + + +def parse_args() -> argparse.Namespace: + ap = argparse.ArgumentParser(description="CelerisLab pure-step performance baseline") + ap.add_argument("--device-id", type=int, default=0) + ap.add_argument("--lattice-model", choices=("D2Q9", "D3Q19"), default="D3Q19") + ap.add_argument("--nx", type=int, default=256) + ap.add_argument("--ny", type=int, default=256) + ap.add_argument("--nz", type=int, default=256) + ap.add_argument("--steps", type=int, default=3000) + ap.add_argument("--warmup-steps", type=int, default=400) + ap.add_argument("--batch-steps", type=int, default=100) + + ap.add_argument("--collision", choices=("SRT", "TRT", "MRT"), default="SRT") + ap.add_argument("--streaming", choices=("double_buffer", "esopull"), default="double_buffer") + ap.add_argument("--store-precision", choices=("FP32", "FP16S"), default="FP32") + ap.add_argument("--ddf-shifting", action="store_true") + ap.add_argument("--enable-les", action="store_true") + ap.add_argument("--outlet-mode", choices=("neq_extrap", "zero_gradient", "blended"), default="neq_extrap") + ap.add_argument("--inlet-profile", choices=("uniform", "parabolic"), default="uniform") + ap.add_argument( + "--inlet-scheme", + choices=("zou_he_local", "channel_stabilized", "equilibrium", "regularized"), + default="zou_he_local", + ) + ap.add_argument("--y-wall-bc", choices=("bounce_back", "free_slip"), default="bounce_back") + + ap.add_argument("--viscosity", type=float, default=0.0035) + ap.add_argument("--velocity", type=float, default=0.03) + ap.add_argument("--rho", type=float, default=1.0) + ap.add_argument("--threads-per-block", type=int, default=256) + ap.add_argument("--compute-capability", type=str, default="auto") + + # Overhead attribution toggles. + ap.add_argument("--macro-every", type=int, default=0, help="Call get_macroscopic() every N steps (0=off)") + ap.add_argument("--macro-repeat", type=int, default=1, help="Repeat get_macroscopic() calls per probe step") + ap.add_argument("--ddf-every", type=int, default=0, help="Call get_ddf() every N steps (0=off)") + ap.add_argument("--ddf-repeat", type=int, default=1, help="Repeat get_ddf() calls per probe step") + ap.add_argument("--checkpoint-every", type=int, default=0, help="Save checkpoint every N steps (0=off)") + ap.add_argument("--obs-every", type=int, default=0, help="Download object obs every N steps (0=off)") + ap.add_argument("--with-cylinder", action="store_true", help="Inject one cylinder object (needed for obs probes)") + ap.add_argument("--final-macro-snapshot", action="store_true") + + ap.add_argument("--json-out", type=str, default="", help="Optional path to save metrics JSON") + return ap.parse_args() + + +def main() -> int: + args = parse_args() + result = run(args) + print(json.dumps(result, indent=2)) + if args.json_out.strip(): + out = os.path.abspath(args.json_out) + os.makedirs(os.path.dirname(out), exist_ok=True) + _write_json(out, result) + print(f"Wrote: {out}") + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/tests/run_sah04_case9_flow_diagnostics.py b/tests/run_sah04_case9_flow_diagnostics.py deleted file mode 100644 index 76e64b2..0000000 --- a/tests/run_sah04_case9_flow_diagnostics.py +++ /dev/null @@ -1,238 +0,0 @@ -# tests/run_sah04_case9_flow_diagnostics.py -"""Sah04 matrix case 9: velocity profiles at several x and full-field vorticity. - -Loads geometry and setup from ``tests/tests/run_sah04_st_matrix.py``, runs one -simulation to the last LBM step, then writes PNG + JSON under -``tests/output/sah04_case9_flow_diag/`` (default). - -Usage:: - conda run -n pycuda_3_10 python tests/run_sah04_case9_flow_diagnostics.py - conda run -n pycuda_3_10 python tests/run_sah04_case9_flow_diagnostics.py --steps 60000 --burn 20000 - -Design:: - Intended for inlet / channel diagnostics after boundary fixes; not part of - the St matrix gate. -""" - -from __future__ import annotations - -import argparse -import importlib.util -import json -import os -import shutil -import sys -import tempfile -from typing import Any, Dict, List, Sequence - -import numpy as np -import pycuda.driver as cuda - -_REPO = os.path.abspath(os.path.join(os.path.dirname(__file__), "..")) -_mtx_path = os.path.join(_REPO, "tests", "tests", "run_sah04_st_matrix.py") -_spec = importlib.util.spec_from_file_location("sah04_mtx", _mtx_path) -if _spec is None or _spec.loader is None: - raise RuntimeError(f"Cannot load {_mtx_path}") -sah = importlib.util.module_from_spec(_spec) -sys.modules["sah04_mtx"] = sah -_spec.loader.exec_module(sah) - - -def _inlet_parabolic_ref_u(y: np.ndarray, ny: int, u0_mean: float) -> np.ndarray: - """Match ``inlet_target_u`` in ``inlet_outlet.cuh`` (parabolic, mean ``u0_mean``).""" - h = max(float(ny - 2), 1.0) - yc = np.clip(y.astype(np.float64), 1.0, float(ny - 2)) - eta = (yc - 0.5) / h - shape = np.maximum(0.0, 4.0 * eta * (1.0 - eta)) - return u0_mean * 1.5 * shape - - -def _default_x_indices(nx: int) -> List[int]: - raw = [1, 2, 3, 5, 10, 20, 40, 80, 160, 320, 640, 960, 1200, 1600, 2000, 2200] - out: List[int] = [] - for x in raw: - if 1 <= x < nx - 1: - out.append(int(x)) - mid = max(1, (nx - 2) // 2) - if mid not in out: - out.append(mid) - out = sorted(set(out)) - return out - - -def _plot_profiles( - out_png: str, - *, - ux: np.ndarray, - uy: np.ndarray, - x_indices: Sequence[int], - ny: int, - u0_mean: float, - title: str, -) -> None: - import matplotlib - - matplotlib.use("Agg") - import matplotlib.pyplot as plt - - y = np.arange(ny, dtype=np.float64) - u_ref = _inlet_parabolic_ref_u(y, ny, u0_mean) - - fig, axes = plt.subplots(1, 2, figsize=(12, 6), sharey=True) - for xi in x_indices: - axes[0].plot(ux[:, xi], y, label=f"x={xi}", linewidth=1.2) - axes[1].plot(uy[:, xi], y, label=f"x={xi}", linewidth=1.2) - axes[0].plot(u_ref, y, "k--", linewidth=1.5, label="inlet parabolic ref") - axes[0].set_xlabel("u_x") - axes[0].set_ylabel("y (lattice)") - axes[0].set_title("u_x (y) at selected x") - axes[0].legend(loc="best", fontsize=7) - axes[0].grid(True, alpha=0.3) - axes[1].set_xlabel("u_y") - axes[1].set_title("u_y (y) at selected x") - axes[1].legend(loc="best", fontsize=7) - axes[1].grid(True, alpha=0.3) - fig.suptitle(title) - fig.tight_layout() - os.makedirs(os.path.dirname(os.path.abspath(out_png)) or ".", exist_ok=True) - fig.savefig(out_png, dpi=150, bbox_inches="tight") - plt.close(fig) - - -def main() -> int: - ap = argparse.ArgumentParser(description="Sah04 case 9 flow: profiles + vorticity") - ap.add_argument("--collision", default="MRT", help="SRT, TRT, or MRT") - ap.add_argument("--steps", type=int, default=60_000) - ap.add_argument("--burn", type=int, default=20_000) - ap.add_argument("--record-every", type=int, default=5) - ap.add_argument("--outlet", default="neq_extrap") - ap.add_argument( - "--out-dir", - default=os.path.join(_REPO, "tests", "output", "sah04_case9_flow_diag"), - help="Output directory for PNG + JSON", - ) - ap.add_argument( - "--x-list", - type=str, - default="", - help='Comma-separated x indices (default: built-in list); e.g. "1,2,5,40,200"', - ) - args = ap.parse_args() - - mc = next(c for c in sah.MATRIX if c.case_id == 9) - tier = sah._TIERS[mc.tier] - ny = int(tier["ny"]) - nx = int(sah._NX) - center = (sah._CX, float(tier["center_y"])) - u_max = 0.1 - u0_mean = u_max / 1.5 - nu = u_max * float(sah._D) / float(mc.re) - - if args.x_list.strip(): - x_indices = [int(s.strip()) for s in args.x_list.split(",") if s.strip()] - x_indices = [x for x in x_indices if 1 <= x < nx - 1] - else: - x_indices = _default_x_indices(nx) - - lbm_path = sah._DEFAULT_LBM - cfg = sah._load_json(lbm_path) - cfg["grid"]["nx"] = nx - cfg["grid"]["ny"] = ny - cfg["grid"]["nz"] = 1 - cfg["physics"]["viscosity"] = float(nu) - cfg["physics"]["velocity"] = float(u0_mean) - cfg["physics"]["rho"] = 1.0 - cfg["method"]["collision"] = str(args.collision).upper() - cfg["method"]["streaming"] = "double_buffer" - cfg["method"]["les"]["enabled"] = False - cfg["method"]["outlet"]["mode"] = args.outlet - - body_doc = { - "objects": [ - { - "type": "cylinder", - "center": list(center), - "radius": float(sah._R_CYL), - } - ] - } - - tmpd = tempfile.mkdtemp(prefix="celeris_sah09_diag_") - lbm_tmp = os.path.join(tmpd, "config_lbm.json") - body_tmp = os.path.join(tmpd, "config_body.json") - sah._write_json(lbm_tmp, cfg) - sah._write_json(body_tmp, body_doc) - - os.makedirs(args.out_dir, exist_ok=True) - png_profiles = os.path.join(args.out_dir, "case9_profiles_ux_uy.png") - png_vort = os.path.join(args.out_dir, "case9_omega_z_full.png") - json_path = os.path.join(args.out_dir, "case9_profiles.json") - - try: - from CelerisLab import Simulation # noqa: WPS433 - - sim = Simulation(lbm_config_path=lbm_tmp, body_config_path=body_tmp) - sim.initialize() - stream = cuda.Stream() - rec_every = max(1, int(args.record_every)) - steps = int(args.steps) - - for step in range(1, steps + 1): - sim.bodies.zero_force_segment_async(stream) - sim.stepper.step( - 1, - action_gpu=sim.bodies.action_gpu, - obs_gpu=sim.bodies.obs_gpu, - stream=stream, - ) - - stream.synchronize() - macro = sim.get_macroscopic() - ux = np.asarray(macro["ux"], dtype=np.float64) - uy = np.asarray(macro["uy"], dtype=np.float64) - sim.close() - - y = np.arange(ny, dtype=np.float64) - u_ref = _inlet_parabolic_ref_u(y, ny, u0_mean) - - title = ( - f"Sah04 case 9 {args.collision} Re={mc.re} ny={ny} " - f"steps={steps} (last step)" - ) - _plot_profiles(png_profiles, ux=ux, uy=uy, x_indices=x_indices, ny=ny, u0_mean=u0_mean, title=title) - sah.save_final_vorticity_png(png_vort, macro["ux"], macro["uy"], title=title + " omega_z") - - profiles: Dict[str, Any] = { - "case_id": 9, - "collision": str(args.collision).upper(), - "Re": mc.re, - "nx": nx, - "ny": ny, - "steps": steps, - "burn_requested": int(args.burn), - "u_max": u_max, - "u0_mean_parabolic": u0_mean, - "x_indices": list(x_indices), - "y_lattice": y.tolist(), - "inlet_parabolic_ref_ux": u_ref.tolist(), - "profiles": {}, - } - for xi in x_indices: - profiles["profiles"][str(xi)] = { - "ux": ux[:, xi].tolist(), - "uy": uy[:, xi].tolist(), - } - with open(json_path, "w", encoding="utf-8") as f: - json.dump(profiles, f, indent=2) - - print("Wrote:", png_profiles, flush=True) - print("Wrote:", png_vort, flush=True) - print("Wrote:", json_path, flush=True) - finally: - shutil.rmtree(tmpd, ignore_errors=True) - - return 0 - - -if __name__ == "__main__": - raise SystemExit(main()) diff --git a/tests/run_sah04_case9_grid_blockage_compare.py b/tests/run_sah04_case9_grid_blockage_compare.py deleted file mode 100644 index 43d9b46..0000000 --- a/tests/run_sah04_case9_grid_blockage_compare.py +++ /dev/null @@ -1,452 +0,0 @@ -# tests/run_sah04_case9_grid_blockage_compare.py -"""Sah04 matrix case 9: compare inlet-near flow vs grid refinement vs blockage. - -Runs three D2Q9 setups anchored to case 9 (high tier, Re=200, ``u_max``=0.1): - -1. **baseline** — matrix geometry: ``D=30``, ``nx=80*D+2``, ``ny=35``, - cylinder ``(40*D+0.5, 17)``, ``r=D/2``. -2. **grid_2x** — double lattice resolution with the same **blockage** ``D/H`` - and the same ``Lx/D=80`` convention: ``D=60``, ``nx=80*D+2``, ``ny=68``, - ``center_y`` doubled with the channel, ``r=D/2``, ``Re`` still based on ``D``. -3. **radius_half** — same channel height as baseline but **cylinder diameter - halved** in lattice units: ``D=15``, ``nx=80*D+2``, ``ny=35``, same - ``center_y``, ``r=D/2``, ``Re`` based on the smaller ``D`` (lower blockage). - -Outputs under ``tests/output/sah04_case9_compare/`` by default: - -- ``compare_meta.json`` — per-variant grid, ``nu``, estimated ``beta=D/H`` (no - full arrays; see ``compare_fields.npz``). -- ``compare_fields.npz`` — full ``ux`` / ``uy`` per variant. -- ``ux_vs_y_inlet.png`` — ``u_x`` vs normalized wall-normal ``eta`` at several - ``x`` indices (all variants overlaid per panel). -- ``omega_z_.png`` — final-step vorticity ``omega_z = d u_y/dx - d u_x/dy`` - for each variant. - -Forward schedule: ``burn`` warm-up steps (may be ``0``), then ``steps`` more -steps; the reported field is the state **after** ``burn + steps`` LBM steps. - -Usage:: - conda run -n pycuda_3_10 python tests/run_sah04_case9_grid_blockage_compare.py --smoke - conda run -n pycuda_3_10 python tests/run_sah04_case9_grid_blockage_compare.py \\ - --steps 20000 --burn 0 --collision MRT - -Design:: - Isolates whether an inlet-channel artifact scales with **mesh** (grid_2x) - or with **blockage** (radius_half) while keeping the Sah04-style confined - channel layout. Requires **matplotlib** for PNG output. -""" - -from __future__ import annotations - -import argparse -import json -import os -import sys -import tempfile -from dataclasses import dataclass -from typing import Any, Dict, List, Tuple - -import numpy as np -import pycuda.driver as cuda - -_REPO = os.path.abspath(os.path.join(os.path.dirname(__file__), "..")) -_DEFAULT_LBM = os.path.join( - _REPO, "src", "CelerisLab", "configs", "config_lbm.json", -) - - -def _load_json(path: str) -> dict: - with open(path, "r", encoding="utf-8") as f: - return json.load(f) - - -def _write_json(path: str, d: dict) -> None: - with open(path, "w", encoding="utf-8") as f: - json.dump(d, f, indent=2) - - -@dataclass(frozen=True) -class Variant: - """One lattice-resolved Sah04-like case (case 9 Re / u_max convention).""" - - key: str - nx: int - ny: int - center: Tuple[float, float] - d_lattice: float - r_cyl: float - re: float - note: str - - -def _variants() -> Tuple[Variant, Variant, Variant]: - d0 = 30 - ny0 = 35 - cy0 = 17.0 - v0 = Variant( - key="baseline", - nx=80 * d0 + 2, - ny=ny0, - center=(40.0 * d0 + 0.5, cy0), - d_lattice=float(d0), - r_cyl=0.5 * d0, - re=200.0, - note="Matrix case 9 (high tier): D=30, ny=35.", - ) - d2 = 60 - ny2 = 2 * ny0 - 2 - v1 = Variant( - key="grid_2x", - nx=80 * d2 + 2, - ny=ny2, - center=(40.0 * d2 + 0.5, 2.0 * cy0), - d_lattice=float(d2), - r_cyl=0.5 * d2, - re=200.0, - note="Double D and ny-2; Lx/D=80 unchanged; blockage D/H ~ baseline.", - ) - dh = 15 - v2 = Variant( - key="radius_half", - nx=80 * dh + 2, - ny=ny0, - center=(40.0 * dh + 0.5, cy0), - d_lattice=float(dh), - r_cyl=0.5 * dh, - re=200.0, - note="Half cylinder diameter in lattice units; same ny as baseline.", - ) - return v0, v1, v2 - - -def _vorticity_z_from_velocity(ux: np.ndarray, uy: np.ndarray) -> np.ndarray: - """``omega_z = d u_y/dx - d u_x/dy`` on a 2D ``(ny, nx)`` slice (unit lattice spacing).""" - ux = np.asarray(ux, dtype=np.float64) - uy = np.asarray(uy, dtype=np.float64) - duy_dx = np.gradient(uy, axis=1) - dux_dy = np.gradient(ux, axis=0) - return duy_dx - dux_dy - - -def _save_vorticity_png( - path: str, - ux: np.ndarray, - uy: np.ndarray, - *, - title: str, -) -> None: - import matplotlib - - matplotlib.use("Agg") - import matplotlib.pyplot as plt - - omega = _vorticity_z_from_velocity(ux, uy) - abs_o = np.abs(omega[np.isfinite(omega)]) - if abs_o.size: - vmax = float(np.percentile(abs_o, 99.5)) - if vmax <= 0.0: - vmax = float(np.max(abs_o)) or 1.0 - else: - vmax = 1.0 - - ny, nx = omega.shape - fw = min(18.0, max(8.0, nx / 100.0)) - fh = min(12.0, max(3.0, ny / 40.0)) - fig, ax = plt.subplots(figsize=(fw, fh)) - im = ax.imshow( - omega, - origin="lower", - aspect="equal", - cmap="RdBu_r", - vmin=-vmax, - vmax=vmax, - extent=(0, nx - 1, 0, ny - 1), - ) - ax.set_xlabel("x (lattice)") - ax.set_ylabel("y (lattice)") - ax.set_title(title) - fig.colorbar(im, ax=ax, fraction=0.046, pad=0.04, label="omega_z") - fig.tight_layout() - os.makedirs(os.path.dirname(os.path.abspath(path)) or ".", exist_ok=True) - fig.savefig(path, dpi=150, bbox_inches="tight") - plt.close(fig) - - -def _run_variant( - v: Variant, - *, - collision: str, - outlet: str, - u_max: float, - burn: int, - steps: int, -) -> Dict[str, Any]: - u0_mean = u_max / 1.5 - nu = u_max * float(v.d_lattice) / float(v.re) - - if not os.path.isfile(_DEFAULT_LBM): - raise FileNotFoundError(_DEFAULT_LBM) - - cfg = _load_json(_DEFAULT_LBM) - cfg["grid"]["nx"] = int(v.nx) - cfg["grid"]["ny"] = int(v.ny) - cfg["grid"]["nz"] = 1 - cfg["physics"]["viscosity"] = float(nu) - cfg["physics"]["velocity"] = float(u0_mean) - cfg["physics"]["rho"] = 1.0 - cfg["method"]["collision"] = collision - cfg["method"]["streaming"] = "double_buffer" - cfg["method"]["les"]["enabled"] = False - cfg["method"]["outlet"]["mode"] = outlet - - body_doc = { - "objects": [ - { - "type": "cylinder", - "center": list(v.center), - "radius": float(v.r_cyl), - } - ] - } - - tmpd = tempfile.mkdtemp(prefix="celeris_sah09cmp_") - lbm_tmp = os.path.join(tmpd, "config_lbm.json") - body_tmp = os.path.join(tmpd, "config_body.json") - _write_json(lbm_tmp, cfg) - _write_json(body_tmp, body_doc) - - from CelerisLab import Simulation # noqa: WPS433 - - sim = Simulation(lbm_config_path=lbm_tmp, body_config_path=body_tmp) - sim.initialize() - stream = cuda.Stream() - - total = int(burn) + int(steps) - if total < 1: - sim.close() - raise ValueError("burn + steps must be >= 1") - - for _step in range(1, total + 1): - sim.bodies.zero_force_segment_async(stream) - sim.stepper.step( - 1, - action_gpu=sim.bodies.action_gpu, - obs_gpu=sim.bodies.obs_gpu, - stream=stream, - ) - - stream.synchronize() - macro = sim.get_macroscopic() - ux = np.asarray(macro["ux"], dtype=np.float64).reshape(v.ny, v.nx) - uy = np.asarray(macro["uy"], dtype=np.float64).reshape(v.ny, v.nx) - rho = np.asarray(macro["rho"], dtype=np.float64).reshape(v.ny, v.nx) - sim.close() - - h_fluid = max(int(v.ny) - 2, 1) - beta_est = float(v.d_lattice) / float(h_fluid) - - return { - "key": v.key, - "nx": int(v.nx), - "ny": int(v.ny), - "center": [float(v.center[0]), float(v.center[1])], - "d_lattice": float(v.d_lattice), - "r_cyl": float(v.r_cyl), - "re": float(v.re), - "nu": float(nu), - "u_max": float(u_max), - "u0_mean": float(u0_mean), - "beta_est": beta_est, - "note": v.note, - "burn": int(burn), - "steps": int(steps), - "total_lbm_steps": int(total), - "ux_shape": list(ux.shape), - "rho_min": float(np.min(rho)), - "rho_max": float(np.max(rho)), - "ux": ux.tolist(), - "uy": uy.tolist(), - } - - -def _eta_coords(ny: int) -> np.ndarray: - """Wall-normal fractional coordinate in (0,1) for fluid rows y=1..ny-2.""" - y = np.arange(ny, dtype=np.float64) - h = max(float(ny - 2), 1.0) - out = np.zeros_like(y) - for yi in range(1, max(ny - 1, 1)): - out[yi] = (float(yi) - 0.5) / h - return out - - -def _plot_ux_vs_y_inlet( - path: str, - results: List[Dict[str, Any]], - x_indices: List[int], -) -> None: - import matplotlib - - matplotlib.use("Agg") - import matplotlib.pyplot as plt - - n_x = len(x_indices) - fig, axes = plt.subplots(1, n_x, figsize=(4.2 * n_x, 5.0), sharey=True) - if n_x == 1: - axes = [axes] - colors = {"baseline": "C0", "grid_2x": "C1", "radius_half": "C2"} - - for ax, xi in zip(axes, x_indices): - for r in results: - nx = int(r["nx"]) - if xi < 1 or xi >= nx - 1: - continue - ux = np.asarray(r["ux"], dtype=np.float64).reshape(r["ny"], r["nx"]) - eta = _eta_coords(int(r["ny"])) - y = np.arange(int(r["ny"])) - ax.plot(ux[:, xi], eta, label=r["key"], color=colors.get(r["key"], "k")) - ax.set_title(f"x = {xi}") - ax.set_xlabel("u_x") - ax.grid(True, alpha=0.3) - axes[0].set_ylabel("eta = (y-0.5)/H (fluid band)") - fig.suptitle("u_x vs wall-normal coordinate near inlet (several x)") - axes[0].set_ylim(0.0, 1.0) - axes[0].legend(loc="best", fontsize=8) - fig.tight_layout() - os.makedirs(os.path.dirname(os.path.abspath(path)) or ".", exist_ok=True) - fig.savefig(path, dpi=150, bbox_inches="tight") - plt.close(fig) - - -def main() -> int: - ap = argparse.ArgumentParser( - description="Case 9 baseline vs finer grid vs half-radius: inlet-near u_x", - ) - ap.add_argument("--collision", default="MRT", help="SRT, TRT, or MRT") - ap.add_argument("--outlet", default="neq_extrap") - ap.add_argument("--u-max", type=float, default=0.1, dest="u_max") - ap.add_argument( - "--burn", - type=int, - default=0, - help="Warm-up LBM steps before the --steps production segment (0 is valid).", - ) - ap.add_argument( - "--steps", - type=int, - default=20_000, - help="LBM steps after burn-in; final field is after burn+steps.", - ) - ap.add_argument( - "--smoke", - action="store_true", - help="Short run: burn=0, steps=2500 (overrides --burn and --steps)", - ) - ap.add_argument( - "--out-dir", - default=os.path.join(_REPO, "tests", "output", "sah04_case9_compare"), - ) - ap.add_argument( - "--inlet-x", - type=str, - default="1,2,3,5,8", - help="Comma-separated x indices for u_x(y) panels (must exist for all nx)", - ) - args = ap.parse_args() - - if args.smoke: - burn = 0 - steps = 2500 - else: - burn = max(0, int(args.burn)) - steps = max(1, int(args.steps)) - - out_dir = os.path.abspath(args.out_dir) - os.makedirs(out_dir, exist_ok=True) - - v0, v1, v2 = _variants() - variants = (v0, v1, v2) - - x_list = [int(s.strip()) for s in args.inlet_x.split(",") if s.strip()] - min_nx = min(v.nx for v in variants) - x_list = [x for x in x_list if 1 <= x < min_nx - 1] - if not x_list: - print("No valid --inlet-x indices common to all variants.", file=sys.stderr) - return 2 - - results: List[Dict[str, Any]] = [] - for v in variants: - print(f"--- {v.key}: nx={v.nx} ny={v.ny} D={v.d_lattice} r={v.r_cyl} ---", flush=True) - row = _run_variant( - v, - collision=str(args.collision).upper(), - outlet=str(args.outlet), - u_max=float(args.u_max), - burn=burn, - steps=steps, - ) - results.append(row) - - meta_path = os.path.join(out_dir, "compare_meta.json") - slim = [] - for r in results: - slim.append({k: v for k, v in r.items() if k not in ("ux", "uy")}) - _write_json( - meta_path, - { - "variants_summary": slim, - "inlet_x_used": x_list, - "burn": burn, - "steps": steps, - "total_lbm_steps": burn + steps, - "collision": str(args.collision).upper(), - }, - ) - - try: - _plot_ux_vs_y_inlet( - os.path.join(out_dir, "ux_vs_y_inlet.png"), - results, - x_list, - ) - for r in results: - key = str(r["key"]) - ux = np.asarray(r["ux"], dtype=np.float64).reshape(r["ny"], r["nx"]) - uy = np.asarray(r["uy"], dtype=np.float64).reshape(r["ny"], r["nx"]) - _save_vorticity_png( - os.path.join(out_dir, f"omega_z_{key}.png"), - ux, - uy, - title=f"omega_z final ({key}) nx={r['nx']} ny={r['ny']} " - f"D={r['d_lattice']} burn={r['burn']} steps={r['steps']}", - ) - except ImportError: - print( - "matplotlib not installed; skipped PNG. Install matplotlib for figures.", - file=sys.stderr, - ) - - full_npz = os.path.join(out_dir, "compare_fields.npz") - np.savez_compressed( - full_npz, - keys=np.array([r["key"] for r in results]), - **{f"ux_{r['key']}": np.asarray(r["ux"], dtype=np.float64) for r in results}, - **{f"uy_{r['key']}": np.asarray(r["uy"], dtype=np.float64) for r in results}, - **{ - f"omega_z_{r['key']}": _vorticity_z_from_velocity( - np.asarray(r["ux"], dtype=np.float64).reshape(r["ny"], r["nx"]), - np.asarray(r["uy"], dtype=np.float64).reshape(r["ny"], r["nx"]), - ) - for r in results - }, - ) - print(f"Wrote: {meta_path}", flush=True) - print(f"Wrote: {full_npz}", flush=True) - print(f"Wrote: {os.path.join(out_dir, 'ux_vs_y_inlet.png')}", flush=True) - for r in results: - oz_path = os.path.join(out_dir, f"omega_z_{r['key']}.png") - print(f"Wrote: {oz_path}", flush=True) - return 0 - - -if __name__ == "__main__": - raise SystemExit(main()) diff --git a/tests/run_sah04_st_matrix.py b/tests/run_sah04_st_matrix.py index 83675b9..bffd1b4 100644 --- a/tests/run_sah04_st_matrix.py +++ b/tests/run_sah04_st_matrix.py @@ -1,36 +1,18 @@ # CelerisLab/tests/run_sah04_st_matrix.py -"""Sah04 St validation matrix from tests/Sah04_St_validation_matrix.md. +"""Sah04 MRT-only Strouhal validation on S1-S4 anchors. -Runs the nine paper-anchored (Re, beta tier) cases with fixed D=30 channel -geometry, optional SRT/TRT/MRT sweep, and evaluates St against targets. +This runner implements the current validation contract in ``tests/Sah04_validation.md``: -Each case reports **two** Strouhal estimates from the same lift window: - -- **raw**: dominant rFFT peak in the paper-frequency band (no target prior). -- **guided**: same band with a Gaussian weight toward ``f0`` from the paper - ``St_target`` (reduces harmonic confusion in narrow channels). - -The matrix **5% / 10% hard-case rules** apply to **guided** ``St`` by default; -``St_raw`` is for diagnosing whether the guide helps or hides a spectrum issue. +- Cases: S1-S4 only (hard periodic anchors). +- Collision: MRT only. +- Inlet: parabolic + channel_stabilized. +- Walls: no-slip channel (from base config + confined geometry). +- Grid policy: S3/S4 can use configurable refined diameter for diagnostics. Usage:: - conda run -n pycuda_3_10 python tests/run_sah04_st_matrix.py --collision MRT - conda run -n pycuda_3_10 python tests/run_sah04_st_matrix.py --collision all --json-out sah04_matrix.json - conda run -n pycuda_3_10 python tests/run_sah04_st_matrix.py --smoke --case 3 --collision SRT - -Long diagnostic (lift + spectrum + **final-step vorticity** under ``tests/output/``):: - - conda run -n pycuda_3_10 python tests/run_sah04_st_matrix.py --collision MRT --case all \\ - --steps 200000 --burn 80000 \\ - --dump-npz-dir tests/output/sah04_long/npz \\ - --final-vorticity-dir tests/output/sah04_long/vorticity \\ - --json-out tests/output/sah04_long/matrix_mrt.json - -Requires **matplotlib** for ``--final-vorticity-dir`` (not a core package dependency). - -Design:: - Hard cases use a 5% relative St gate on **guided** St; no hard case worse - than 10%. Soft cases (2, 8) only check ordering vs neighbors in printed summary. + conda run -n pycuda_3_10 python tests/run_sah04_st_matrix.py + conda run -n pycuda_3_10 python tests/run_sah04_st_matrix.py --case S3 --smoke + conda run -n pycuda_3_10 python tests/run_sah04_st_matrix.py --gate-pct 10 --json-out tests/output/sah04_mrt/summary.json """ from __future__ import annotations @@ -49,42 +31,42 @@ import pycuda.driver as cuda _PKG_ROOT = os.path.abspath(os.path.join(os.path.dirname(__file__), "..")) _DEFAULT_LBM = os.path.join(_PKG_ROOT, "src", "CelerisLab", "configs", "config_lbm.json") -# D=30 fixed; Lx_fluid = 80D per Sah04 confined setup -_D = 30 -_NX = 80 * _D + 2 -_CX = 40.0 * _D + 0.5 -_R_CYL = 0.5 * _D - -# Blockage tiers: fluid height H = ny - 2; cylinder center (1200.5, center_y) -_TIERS: Dict[str, Dict[str, float]] = { - "low": {"ny": 62.0, "center_y": 30.5, "beta_nom": 0.5}, - "mid": {"ny": 40.0, "center_y": 19.5, "beta_nom": 0.8}, - "high": {"ny": 35.0, "center_y": 17.0, "beta_nom": 0.9}, -} +_BASE_D = 30.0 @dataclass(frozen=True) -class MatrixCase: - case_id: int - tier: str - re: float +class Sah04Case: + """One hard benchmark case from Sah04_validation.md.""" + + case_id: str + beta_nominal: float + re_nominal: float target_st: float - hard: bool + h_fluid: int steps: int burn: int -# Table from Sah04_St_validation_matrix.md -MATRIX: Tuple[MatrixCase, ...] = ( - MatrixCase(1, "low", 124.09, 0.3393, True, 80_000, 30_000), - MatrixCase(2, "low", 160.0, 0.3450, False, 60_000, 20_000), - MatrixCase(3, "low", 200.0, 0.3513, True, 60_000, 20_000), - MatrixCase(4, "mid", 110.24, 0.5363, True, 80_000, 30_000), - MatrixCase(5, "mid", 160.0, 0.5537, True, 60_000, 20_000), - MatrixCase(6, "mid", 200.0, 0.5510, True, 60_000, 20_000), - MatrixCase(7, "high", 162.82, 0.5202, True, 80_000, 30_000), - MatrixCase(8, "high", 180.0, 0.5254, False, 60_000, 20_000), - MatrixCase(9, "high", 200.0, 0.5314, True, 60_000, 20_000), +@dataclass(frozen=True) +class CaseGeometry: + """Resolved lattice geometry for one case.""" + + diameter: float + h_fluid: int + nx: int + ny: int + center_x: float + center_y: float + radius: float + beta_real: float + wall_gap_cells: float + + +CASES: Tuple[Sah04Case, ...] = ( + Sah04Case("S1", 0.3, 100.0, 0.2115, 100, 120_000, 45_000), + Sah04Case("S2", 0.5, 200.0, 0.3513, 60, 120_000, 45_000), + Sah04Case("S3", 0.8, 160.0, 0.5537, 38, 220_000, 99_000), + Sah04Case("S4", 0.9, 200.0, 0.5314, 33, 220_000, 99_000), ) @@ -93,54 +75,99 @@ def _load_json(path: str) -> dict: return json.load(f) -def _write_json(path: str, d: dict) -> None: +def _write_json(path: str, payload: dict) -> None: with open(path, "w", encoding="utf-8") as f: - json.dump(d, f, indent=2) + json.dump(payload, f, indent=2) + + +def rfft_power_spectrum(samples: np.ndarray, *, sample_dt: float) -> Tuple[np.ndarray, np.ndarray]: + """Mean-subtracted Hanning-windowed signal to rFFT power spectrum.""" + x = np.asarray(samples, dtype=np.float64) + x = x - np.mean(x) + n = x.size + if n < 64: + return np.zeros(0, dtype=np.float64), np.zeros(0, dtype=np.float64) + win = np.hanning(n) + spec = np.abs(np.fft.rfft(x * win)) ** 2 + freqs = np.fft.rfftfreq(n, d=float(sample_dt)) + return freqs.astype(np.float64), spec.astype(np.float64) + + +def _parabolic_peak_freq(freqs: np.ndarray, spec: np.ndarray, idx: int) -> float: + """Sub-bin frequency estimate with local log-parabolic interpolation.""" + i = int(np.clip(idx, 0, spec.size - 1)) + if i <= 0 or i + 1 >= spec.size: + return float(freqs[i]) + y0 = np.log(spec[i - 1] + 1e-30) + y1 = np.log(spec[i] + 1e-30) + y2 = np.log(spec[i + 1] + 1e-30) + den = y0 - 2.0 * y1 + y2 + if abs(den) < 1e-20: + return float(freqs[i]) + delta = float(np.clip(0.5 * (y0 - y2) / den, -1.0, 1.0)) + return float(freqs[i]) + delta * float(freqs[i + 1] - freqs[i]) + + +def _strouhal_from_lift( + lift: np.ndarray, + *, + diameter: float, + u_max: float, + sample_dt: float, + f_hz_min: float, + f_hz_max: float, +) -> Tuple[float, float]: + """Return guided Strouhal and guided dominant frequency.""" + freqs, spec = rfft_power_spectrum(lift, sample_dt=sample_dt) + if freqs.size == 0: + return float("nan"), float("nan") + band = (freqs >= float(f_hz_min)) & (freqs <= float(f_hz_max)) + if not np.any(band): + return float("nan"), float("nan") + f0 = 0.5 * (float(f_hz_min) + float(f_hz_max)) + sigma = max(1e-12, 0.18 * f0) + weight = np.exp(-((freqs - f0) / sigma) ** 2) + idx = int(np.argmax(spec * band.astype(np.float64) * weight)) + f_peak = _parabolic_peak_freq(freqs, spec, idx) + return float(f_peak * diameter / u_max), float(f_peak) + + +def shedding_freq_band_hz( + target_st: float, + u_max: float, + diameter: float, + *, + half_width: float = 0.42, +) -> Tuple[float, float]: + """Frequency band around target shedding frequency for robust FFT pick.""" + f0 = float(target_st) * float(u_max) / float(diameter) + return max(1e-8, f0 * (1.0 - half_width)), f0 * (1.0 + half_width) def vorticity_z_from_velocity(ux: np.ndarray, uy: np.ndarray) -> np.ndarray: - """Z-component vorticity ``ωz = ∂uy/∂x − ∂ux/∂y`` on a 2D ``(ny, nx)`` slice. - - Lattice spacing is taken as 1 in ``np.gradient`` (LBM cell units). - """ + """Return z-vorticity for 2D velocity fields.""" ux = np.asarray(ux, dtype=np.float64) uy = np.asarray(uy, dtype=np.float64) - duy_dx = np.gradient(uy, axis=1) - dux_dy = np.gradient(ux, axis=0) - return duy_dx - dux_dy + return np.gradient(uy, axis=1) - np.gradient(ux, axis=0) -def save_final_vorticity_png( - path: str, - ux: np.ndarray, - uy: np.ndarray, - *, - title: str, -) -> None: - """Write ``ωz`` heatmap from last-step ``ux``/``uy`` to ``path`` (PNG).""" +def save_final_vorticity_png(path: str, ux: np.ndarray, uy: np.ndarray, *, title: str) -> None: + """Save final-step vorticity image; requires matplotlib.""" try: import matplotlib matplotlib.use("Agg") import matplotlib.pyplot as plt - except ImportError as e: - raise RuntimeError( - "save_final_vorticity_png requires matplotlib (e.g. pip install matplotlib)." - ) from e + except ImportError as exc: + raise RuntimeError("save_final_vorticity_png requires matplotlib.") from exc omega = vorticity_z_from_velocity(ux, uy) abs_o = np.abs(omega[np.isfinite(omega)]) - if abs_o.size: - vmax = float(np.percentile(abs_o, 99.5)) - if vmax <= 0.0: - vmax = float(np.max(abs_o)) or 1.0 - else: + vmax = float(np.percentile(abs_o, 99.5)) if abs_o.size else 1.0 + if vmax <= 0.0: vmax = 1.0 - ny, nx = omega.shape - fw = min(18.0, max(8.0, nx / 100.0)) - fh = min(12.0, max(3.0, ny / 40.0)) - fig, ax = plt.subplots(figsize=(fw, fh)) + fig, ax = plt.subplots(figsize=(min(18.0, max(8.0, nx / 100.0)), min(12.0, max(3.0, ny / 40.0)))) im = ax.imshow( omega, origin="lower", @@ -159,119 +186,71 @@ def save_final_vorticity_png( plt.close(fig) -def rfft_power_spectrum( - lift: np.ndarray, +def _build_case_geometry( + case: Sah04Case, *, - sample_dt: float, -) -> Tuple[np.ndarray, np.ndarray]: - """Mean-subtracted Hanning-windowed lift → ``(freqs_hz, power)`` for positive rFFT bins.""" - x = np.asarray(lift, dtype=np.float64) - x = x - np.mean(x) - n = x.size - if n < 64: - return np.zeros(0, dtype=np.float64), np.zeros(0, dtype=np.float64) - win = np.hanning(n) - xw = x * win - spec = np.abs(np.fft.rfft(xw)) ** 2 - freqs = np.fft.rfftfreq(n, d=float(sample_dt)) - return freqs.astype(np.float64), spec.astype(np.float64) + refine_high_beta: bool, + high_beta_diameter: float, + diameter_override: Optional[float], + h_fluid_override: Optional[int], +) -> CaseGeometry: + """Map case to geometry, with optional high-beta refinement.""" + diameter = _BASE_D + h_fluid = int(case.h_fluid) + if diameter_override is not None or h_fluid_override is not None: + if diameter_override is None or h_fluid_override is None: + raise ValueError("Both --diameter-override and --h-fluid-override must be set together.") + diameter = float(diameter_override) + h_fluid = int(h_fluid_override) + elif refine_high_beta and case.case_id in ("S3", "S4"): + # Diagnostic default keeps high-blockage runs around D~80 for faster sweeps. + diameter = float(high_beta_diameter) + h_fluid = int(round(float(diameter) / float(case.beta_nominal))) - -def _parabolic_peak_freq(freqs: np.ndarray, spec: np.ndarray, idx: int) -> float: - """Refine discrete FFT peak index ``idx`` with log-power parabolic fit.""" - idx = int(np.clip(idx, 0, spec.size - 1)) - if idx <= 0 or idx + 1 >= spec.size: - return float(freqs[idx]) - i0, i1, i2 = idx - 1, idx, idx + 1 - y0, y1, y2 = ( - np.log(spec[i0] + 1e-30), - np.log(spec[i1] + 1e-30), - np.log(spec[i2] + 1e-30), - ) - denom = y0 - 2.0 * y1 + y2 - if abs(denom) < 1e-20: - return float(freqs[i1]) - delta = 0.5 * (y0 - y2) / denom - delta = float(np.clip(delta, -1.0, 1.0)) - df = float(freqs[i2] - freqs[i1]) - return float(freqs[i1]) + delta * df - - -def dual_strouhal_from_lift( - lift: np.ndarray, - *, - diameter: float, - u_max: float, - sample_dt: float, - f_hz_min: float, - f_hz_max: float, -) -> Dict[str, float]: - """Return raw and guided ``St`` and underlying ``f_peak`` (cycles per LBM step).""" - nan = {"St_raw": float("nan"), "f_raw": float("nan"), "St_guided": float("nan"), "f_guided": float("nan")} - freqs, spec = rfft_power_spectrum(lift, sample_dt=sample_dt) - if freqs.size == 0: - return nan - mask = (freqs >= float(f_hz_min)) & (freqs <= float(f_hz_max)) - if not np.any(mask): - return nan - m = mask.astype(float) - idx_raw = int(np.argmax(spec * m)) - f0 = 0.5 * (float(f_hz_min) + float(f_hz_max)) - sigma = max(1e-12, 0.18 * f0) - weight = np.exp(-((freqs - f0) / sigma) ** 2) - idx_g = int(np.argmax(spec * m * weight)) - f_raw = _parabolic_peak_freq(freqs, spec, idx_raw) - f_g = _parabolic_peak_freq(freqs, spec, idx_g) - return { - "St_raw": float(f_raw * diameter / u_max), - "f_raw": float(f_raw), - "St_guided": float(f_g * diameter / u_max), - "f_guided": float(f_g), - } - - -def dominant_strouhal_from_lift( - lift: np.ndarray, - *, - diameter: float, - u_max: float, - sample_dt: float = 1.0, - f_hz_min: float, - f_hz_max: float, -) -> Tuple[float, float]: - """Backward-compatible: returns guided ``(St, f_peak)``.""" - d = dual_strouhal_from_lift( - lift, + nx = int(80.0 * diameter + 2.0) + ny = int(h_fluid + 2) + center_x = 40.0 * diameter + 0.5 + center_y = 0.5 * float(h_fluid) + 0.5 + radius = 0.5 * diameter + beta_real = float(diameter / float(h_fluid)) + wall_gap_cells = 0.5 * float(h_fluid - diameter) + return CaseGeometry( diameter=diameter, - u_max=u_max, - sample_dt=sample_dt, - f_hz_min=f_hz_min, - f_hz_max=f_hz_max, + h_fluid=h_fluid, + nx=nx, + ny=ny, + center_x=center_x, + center_y=center_y, + radius=radius, + beta_real=beta_real, + wall_gap_cells=wall_gap_cells, ) - return d["St_guided"], d["f_guided"] -def shedding_freq_band_hz( - target_st: float, u_max: float, d_lattice: float, *, half_width: float = 0.42 -) -> Tuple[float, float]: - """Cycles per LBM step around ``f0 = St*Umax/D`` so sparse rFFT bins still get coverage.""" - f0 = float(target_st) * float(u_max) / float(d_lattice) - lo = max(1e-8, f0 * (1.0 - half_width)) - hi = f0 * (1.0 + half_width) - return lo, hi +def _relative_error(measured: float, target: float) -> Optional[float]: + if not np.isfinite(measured) or target <= 0.0: + return None + return abs(float(measured) - float(target)) / float(target) + + +def _realized_umax(ux: np.ndarray, *, probe_x: int) -> float: + """Estimate developed centerline maximum from one downstream vertical profile.""" + # Skip top/bottom walls and sample at a downstream x station. + prof = np.asarray(ux[1:-1, int(probe_x)], dtype=np.float64) + if prof.size == 0: + return float("nan") + return float(np.max(prof)) def run_one_simulation( + case: Sah04Case, + geometry: CaseGeometry, *, collision: str, - outlet: str, - nx: int, - ny: int, - center: Tuple[float, float], - re: float, - d_lattice: float, - r_cyl: float, - u_max: float, + outlet_mode: str, + inlet_profile: str, + inlet_scheme: str, + u_max_nominal: float, steps: int, burn: int, record_every: int, @@ -279,39 +258,37 @@ def run_one_simulation( f_hz_max: float, dump_npz_path: Optional[str] = None, final_vorticity_png_path: Optional[str] = None, - flow_figure_title: str = "Sah04 vorticity (final LBM step)", + crash_dump_dir: Optional[str] = None, ) -> Dict[str, Any]: - """Build configs, run steps, return St, rough Cd, curved stats.""" - u0_mean = u_max / 1.5 - nu = u_max * d_lattice / re + """Build config, run simulation, and return measured metrics.""" + nu = float(u_max_nominal * geometry.diameter / case.re_nominal) + u0_mean = float(u_max_nominal / 1.5) - lbm_path = _DEFAULT_LBM - if not os.path.isfile(lbm_path): - raise FileNotFoundError(lbm_path) - - cfg = _load_json(lbm_path) - cfg["grid"]["nx"] = nx - cfg["grid"]["ny"] = ny + cfg = _load_json(_DEFAULT_LBM) + cfg["grid"]["nx"] = int(geometry.nx) + cfg["grid"]["ny"] = int(geometry.ny) cfg["grid"]["nz"] = 1 cfg["physics"]["viscosity"] = float(nu) cfg["physics"]["velocity"] = float(u0_mean) cfg["physics"]["rho"] = 1.0 - cfg["method"]["collision"] = collision + cfg["method"]["collision"] = str(collision).upper() cfg["method"]["streaming"] = "double_buffer" cfg["method"]["les"]["enabled"] = False - cfg["method"]["outlet"]["mode"] = outlet + cfg["method"]["inlet"]["profile"] = str(inlet_profile) + cfg["method"]["inlet"]["scheme"] = str(inlet_scheme) + cfg["method"]["outlet"]["mode"] = str(outlet_mode) body_doc = { "objects": [ { "type": "cylinder", - "center": list(center), - "radius": float(r_cyl), + "center": [float(geometry.center_x), float(geometry.center_y)], + "radius": float(geometry.radius), } ] } - tmpd = tempfile.mkdtemp(prefix="celeris_sah04_mtx_") + tmpd = tempfile.mkdtemp(prefix="celeris_sah04_mrt_") lbm_tmp = os.path.join(tmpd, "config_lbm.json") body_tmp = os.path.join(tmpd, "config_body.json") _write_json(lbm_tmp, cfg) @@ -323,18 +300,14 @@ def run_one_simulation( sim.initialize() stream = cuda.Stream() + rec_every = max(1, int(record_every)) lift_hist: List[float] = [] fx_hist: List[float] = [] step_hist: List[int] = [] - rec_every = max(1, int(record_every)) - rho_every = max(2000, rec_every) - rho_snap_step: List[int] = [] - rho_snap_min: List[float] = [] - rho_snap_max: List[float] = [] n_curved = int(sim.field.n_curved) - fb = int(sim.bodies.fallback_link_count()) - lq = int(sim.bodies.low_q_link_count()) + fallback_links = int(sim.bodies.fallback_link_count()) + low_q_links = int(sim.bodies.low_q_link_count()) for step in range(1, int(steps) + 1): sim.bodies.zero_force_segment_async(stream) @@ -349,340 +322,336 @@ def run_one_simulation( sim.bodies.download_obs_full_async(stream) stream.synchronize() fvec = sim.bodies.read_force(0) - lift_hist.append(float(fvec[1])) - fx_hist.append(float(fvec[0])) - step_hist.append(int(step)) - if not np.isfinite(lift_hist[-1]): + lift = float(fvec[1]) + drag = float(fvec[0]) + if not np.isfinite(lift) or not np.isfinite(drag): + crash_npz_path: Optional[str] = None + crash_png_path: Optional[str] = None + if crash_dump_dir: + os.makedirs(crash_dump_dir, exist_ok=True) + stream.synchronize() + macro_bad = sim.get_macroscopic() + ux_bad = np.asarray(macro_bad["ux"], dtype=np.float64).reshape(geometry.ny, geometry.nx) + uy_bad = np.asarray(macro_bad["uy"], dtype=np.float64).reshape(geometry.ny, geometry.nx) + rho_bad = np.asarray(macro_bad["rho"], dtype=np.float64).reshape(geometry.ny, geometry.nx) + prefix = f"{case.case_id.lower()}_{str(collision).lower()}_crash_step{step}" + crash_npz_path = os.path.join(crash_dump_dir, f"{prefix}.npz") + np.savez_compressed( + crash_npz_path, + rho=rho_bad.astype(np.float32), + ux=ux_bad.astype(np.float32), + uy=uy_bad.astype(np.float32), + step=np.array([int(step)], dtype=np.int64), + case_id=np.array([case.case_id]), + collision=np.array([str(collision)]), + inlet_scheme=np.array([str(inlet_scheme)]), + outlet_mode=np.array([str(outlet_mode)]), + ) + crash_png_path = os.path.join(crash_dump_dir, f"{prefix}.png") + try: + save_final_vorticity_png( + crash_png_path, + ux_bad, + uy_bad, + title=( + f"Sah04 {case.case_id} {collision} crash@{step} " + f"D={int(geometry.diameter)} H={geometry.h_fluid}" + ), + ) + except Exception: # noqa: BLE001 + crash_png_path = None sim.close() - raise RuntimeError(f"NaN/Inf lift at step {step}") - if step % rho_every == 0 or step == int(steps): - stream.synchronize() - macro = sim.get_macroscopic() - rho_snap_step.append(step) - rho_snap_min.append(float(np.min(macro["rho"]))) - rho_snap_max.append(float(np.max(macro["rho"]))) - - if final_vorticity_png_path: - stream.synchronize() - macro_last = sim.get_macroscopic() - _vdir = os.path.dirname(os.path.abspath(final_vorticity_png_path)) - if _vdir: - os.makedirs(_vdir, exist_ok=True) - save_final_vorticity_png( - final_vorticity_png_path, - macro_last["ux"], - macro_last["uy"], - title=flow_figure_title, - ) + msg = f"NaN/Inf force at step {step}" + if crash_npz_path: + msg += f"; crash_npz={crash_npz_path}" + if crash_png_path: + msg += f"; crash_png={crash_png_path}" + raise RuntimeError(msg) + lift_hist.append(lift) + fx_hist.append(drag) + step_hist.append(step) + stream.synchronize() + macro_last = sim.get_macroscopic() + ux_last = np.asarray(macro_last["ux"], dtype=np.float64).reshape(geometry.ny, geometry.nx) + uy_last = np.asarray(macro_last["uy"], dtype=np.float64).reshape(geometry.ny, geometry.nx) + rho_last = np.asarray(macro_last["rho"], dtype=np.float64).reshape(geometry.ny, geometry.nx) sim.close() - lift_arr = np.array(lift_hist, dtype=np.float64) - fx_arr = np.array(fx_hist, dtype=np.float64) - step_arr = np.array(step_hist, dtype=np.int64) - burn_samp = min(int(burn) // rec_every, max(0, lift_arr.size - 16)) - tail = lift_arr[burn_samp:] - dual = dual_strouhal_from_lift( - tail, - diameter=d_lattice, - u_max=u_max, + if final_vorticity_png_path: + out_dir = os.path.dirname(os.path.abspath(final_vorticity_png_path)) + if out_dir: + os.makedirs(out_dir, exist_ok=True) + save_final_vorticity_png( + final_vorticity_png_path, + ux_last, + uy_last, + title=f"Sah04 {case.case_id} MRT Re={case.re_nominal:.0f} beta_nom={case.beta_nominal:.1f}", + ) + + lift_arr = np.asarray(lift_hist, dtype=np.float64) + fx_arr = np.asarray(fx_hist, dtype=np.float64) + step_arr = np.asarray(step_hist, dtype=np.int64) + burn_idx = min(int(burn) // rec_every, max(0, lift_arr.size - 16)) + lift_tail = lift_arr[burn_idx:] + + st, f_peak = _strouhal_from_lift( + lift_tail, + diameter=float(geometry.diameter), + u_max=float(u_max_nominal), sample_dt=float(rec_every), - f_hz_min=f_hz_min, - f_hz_max=f_hz_max, + f_hz_min=float(f_hz_min), + f_hz_max=float(f_hz_max), ) - st_guided = dual["St_guided"] - f_guided = dual["f_guided"] - mean_cd = float(np.mean(fx_arr[burn_samp:])) * 2.0 / (u_max ** 2 * d_lattice) if fx_arr.size else float("nan") - if rho_snap_min: - i0 = next((i for i, s in enumerate(rho_snap_step) if s >= burn), 0) - rho_rng = (min(rho_snap_min[i0:]), max(rho_snap_max[i0:])) - else: - rho_rng = (float("nan"), float("nan")) + mean_cd = ( + float(np.mean(fx_arr[burn_idx:]) * 2.0 / (u_max_nominal**2 * geometry.diameter)) + if fx_arr.size + else float("nan") + ) + + beta_real = float(geometry.beta_real) + probe_x = min(geometry.nx - 2, max(2, geometry.nx - 10)) + u_max_real = _realized_umax(ux_last, probe_x=probe_x) + re_real = float(u_max_real * geometry.diameter / nu) if np.isfinite(u_max_real) and nu > 0.0 else float("nan") if dump_npz_path: - freqs_pb, spec_pb = rfft_power_spectrum(tail, sample_dt=float(rec_every)) - f0_hz = 0.5 * (float(f_hz_min) + float(f_hz_max)) - sigma = max(1e-12, 0.18 * f0_hz) - w_guided = ( - np.exp(-((freqs_pb - f0_hz) / sigma) ** 2) if freqs_pb.size else np.zeros(0) - ) - band = ( - ((freqs_pb >= float(f_hz_min)) & (freqs_pb <= float(f_hz_max))).astype(np.float64) - if freqs_pb.size - else np.zeros(0) - ) - _dir = os.path.dirname(os.path.abspath(dump_npz_path)) - if _dir: - os.makedirs(_dir, exist_ok=True) + freqs, power = rfft_power_spectrum(lift_tail, sample_dt=float(rec_every)) + out_dir = os.path.dirname(os.path.abspath(dump_npz_path)) + if out_dir: + os.makedirs(out_dir, exist_ok=True) np.savez_compressed( dump_npz_path, lift_samples=lift_arr, - fx_samples=fx_arr, + drag_samples=fx_arr, sample_lbm_step=step_arr, - burn_index_samples=int(burn_samp), + burn_index_samples=int(burn_idx), record_every_lbm_steps=int(rec_every), - freqs_hz_post_burn=freqs_pb, - power_post_burn=spec_pb, - band_mask=band, - guided_gaussian_weight=w_guided, - f_hz_min=np.array([float(f_hz_min)], dtype=np.float64), - f_hz_max=np.array([float(f_hz_max)], dtype=np.float64), - f0_hz_band_mid=np.array([f0_hz], dtype=np.float64), - St_raw=np.array([float(dual["St_raw"])], dtype=np.float64), - St_guided=np.array([float(dual["St_guided"])], dtype=np.float64), - u_max=np.array([float(u_max)], dtype=np.float64), - diameter_lattice=np.array([float(d_lattice)], dtype=np.float64), + freqs_hz_post_burn=freqs, + power_post_burn=power, + rho_final=rho_last.astype(np.float32), + ux_final=ux_last.astype(np.float32), + uy_final=uy_last.astype(np.float32), + st=np.array([st], dtype=np.float64), + f_peak=np.array([f_peak], dtype=np.float64), + re_real=np.array([re_real], dtype=np.float64), + beta_real=np.array([beta_real], dtype=np.float64), ) return { - "St": float(st_guided), - "St_guided": float(st_guided), - "St_raw": float(dual["St_raw"]), - "f_peak_per_step": float(f_guided), - "f_peak_raw_per_step": float(dual["f_raw"]), - "f_peak_guided_per_step": float(f_guided), + "collision": str(collision).upper(), + "inlet_profile": inlet_profile, + "inlet_scheme": inlet_scheme, + "St": float(st), + "f_peak_per_step": float(f_peak), "mean_Cd": float(mean_cd), + "Re_real": float(re_real), + "U_max_real": float(u_max_real), + "beta_real": float(beta_real), "n_curved": n_curved, - "fallback_links": fb, - "low_q_links": lq, - "rho_min_post_burn": rho_rng[0], - "rho_max_post_burn": rho_rng[1], + "fallback_links": fallback_links, + "low_q_links": low_q_links, + "rho_min_final": float(np.min(rho_last)), + "rho_max_final": float(np.max(rho_last)), "n_lift_samples": int(lift_arr.size), } -def relative_st_error(st_meas: float, st_target: float) -> float: - if not np.isfinite(st_meas) or st_target <= 0: - return float("inf") - return abs(st_meas - st_target) / st_target - - -def evaluate_hard_cases(rows: Sequence[Dict[str, Any]], collision: str) -> Dict[str, Any]: - hard = [ - r - for r in rows - if r.get("hard") - and r.get("collision") == collision - and "St" in r - and np.isfinite(r["St"]) - ] - errs = [relative_st_error(r["St"], r["target_st"]) for r in hard] - errs_raw = [ - relative_st_error(r["St_raw"], r["target_st"]) - for r in hard - if "St_raw" in r and np.isfinite(r["St_raw"]) - ] - finite = [e for e in errs if np.isfinite(e)] - within5 = sum(1 for e in finite if e <= 0.05) - worse10 = sum(1 for e in finite if e > 0.10) - fr = [e for e in errs_raw if np.isfinite(e)] +def evaluate_rows(rows: Sequence[Dict[str, Any]], *, gate_pct: float) -> Dict[str, Any]: + """Aggregate pass/fail summary for S1-S4 hard anchors.""" + valid_rows = [r for r in rows if "error" not in r] + st_errs = [r.get("St_error_pct") for r in valid_rows if r.get("St_error_pct") is not None] + pass_count = sum(1 for v in st_errs if float(v) <= float(gate_pct)) return { - "collision": collision, - "hard_count": len(hard), - "hard_within_5pct": int(within5), - "hard_worse_than_10pct": int(worse10), - "pass_primary_rule": bool(within5 >= 5 and worse10 == 0), - "hard_median_abs_rel_err_raw": float(np.median(fr)) if fr else None, + "gate_pct": float(gate_pct), + "cases_total": len(rows), + "cases_completed": len(valid_rows), + "cases_failed": sum(1 for r in rows if "error" in r), + "cases_within_gate": int(pass_count), + "pass_gate_all_completed": bool(len(valid_rows) > 0 and pass_count == len(valid_rows)), } def main() -> int: - ap = argparse.ArgumentParser(description="Sah04 St validation matrix (9 cases × collisions)") - ap.add_argument("--collision", default="MRT", help="SRT, TRT, MRT, or all") + ap = argparse.ArgumentParser(description="Sah04 MRT-only S1-S4 validation runner") + ap.add_argument("--case", default="all", help='S1-S4 or "all"') + ap.add_argument("--collision", default="MRT", choices=("SRT", "TRT", "MRT")) ap.add_argument("--outlet", default="neq_extrap", choices=("neq_extrap", "zero_gradient", "blended")) + ap.add_argument("--inlet-profile", default="parabolic", choices=("parabolic",)) + ap.add_argument( + "--inlet-scheme", + default="channel_stabilized", + choices=("channel_stabilized", "regularized", "zou_he_local", "equilibrium"), + ) ap.add_argument("--record-every", type=int, default=5) - ap.add_argument("--case", default="all", help='Case id 1-9 or "all"') - ap.add_argument("--smoke", action="store_true", help="Short steps/burn for wiring checks") - ap.add_argument("--json-out", type=str, default=None, help="Write full result rows to JSON") + ap.add_argument("--smoke", action="store_true", help="Short run for wiring checks.") + ap.add_argument("--steps", type=int, default=None, help="Override case steps (ignored with --smoke).") + ap.add_argument("--burn", type=int, default=None, help="Override case burn (ignored with --smoke).") + ap.add_argument("--gate-pct", type=float, default=5.0, help="Pass gate for St relative error percent.") + ap.add_argument("--json-out", type=str, default=None, help="Write summary JSON.") + ap.add_argument("--dump-npz-dir", type=str, default=None, help="Optional directory for case NPZ dumps.") + ap.add_argument("--final-vorticity-dir", type=str, default=None, help="Optional directory for final vorticity PNG.") ap.add_argument( - "--steps", - type=int, - default=None, - help="Override matrix LBM steps for each case (ignored with --smoke)", - ) - ap.add_argument( - "--burn", - type=int, - default=None, - help="Override matrix burn in LBM steps (ignored with --smoke)", - ) - ap.add_argument( - "--dump-npz-dir", + "--crash-dump-dir", type=str, default=None, - help="Directory: write case{id}_{COLL}.npz (+ .meta.json) with lift, fx, sample steps, post-burn spectrum", + help="Optional directory to dump full flowfield NPZ/PNG immediately before crash.", ) ap.add_argument( - "--final-vorticity-dir", - type=str, - default=None, - help="Directory: write case{id}_{COLL}_laststep.png (omega_z from final macroscopic slice; needs matplotlib)", + "--no-refine-high-beta", + action="store_true", + help="Disable default refined geometry for S3/S4 (debug only).", ) + ap.add_argument( + "--high-beta-diameter", + type=float, + default=80.0, + help="Refined diameter for S3/S4 when high-beta refinement is enabled.", + ) + ap.add_argument("--diameter-override", type=float, default=None, help="Override cylinder diameter for selected case.") + ap.add_argument("--h-fluid-override", type=int, default=None, help="Override fluid height H for selected case.") args = ap.parse_args() - u_max = 0.1 - collisions: List[str] - if str(args.collision).lower() == "all": - collisions = ["SRT", "TRT", "MRT"] - else: - collisions = [str(args.collision).upper()] - if collisions[0] not in ("SRT", "TRT", "MRT"): - print("--collision must be SRT, TRT, MRT, or all", file=sys.stderr) - return 2 + if not os.path.isfile(_DEFAULT_LBM): + print(f"Missing base config: {_DEFAULT_LBM}", file=sys.stderr) + return 2 - case_filter: Optional[int] = None - if str(args.case).lower() != "all": - case_filter = int(args.case) - if case_filter < 1 or case_filter > 9: - print("--case must be 1-9 or all", file=sys.stderr) - return 2 - - cases_to_run = [c for c in MATRIX if case_filter is None or c.case_id == case_filter] + selected_case = str(args.case).upper() + if selected_case != "ALL" and selected_case not in {c.case_id for c in CASES}: + print("--case must be one of S1,S2,S3,S4,all", file=sys.stderr) + return 2 + cases_to_run = [c for c in CASES if selected_case == "ALL" or c.case_id == selected_case] if args.dump_npz_dir: os.makedirs(args.dump_npz_dir, exist_ok=True) if args.final_vorticity_dir: os.makedirs(args.final_vorticity_dir, exist_ok=True) rows: List[Dict[str, Any]] = [] - for coll in collisions: - for mc in cases_to_run: - tier = _TIERS[mc.tier] - ny = int(tier["ny"]) - center = (_CX, float(tier["center_y"])) - if args.smoke: - steps = 6000 - burn = 1500 - else: - steps = int(args.steps) if args.steps is not None else mc.steps - burn = int(args.burn) if args.burn is not None else mc.burn - flo, fhi = shedding_freq_band_hz(mc.target_st, u_max, float(_D)) + for case in cases_to_run: + geometry = _build_case_geometry( + case, + refine_high_beta=not bool(args.no_refine_high_beta), + high_beta_diameter=float(args.high_beta_diameter), + diameter_override=args.diameter_override, + h_fluid_override=args.h_fluid_override, + ) + steps = 5000 if args.smoke else (int(args.steps) if args.steps is not None else case.steps) + burn = 1500 if args.smoke else (int(args.burn) if args.burn is not None else case.burn) + f_lo, f_hi = shedding_freq_band_hz(case.target_st, 0.1, geometry.diameter) - dump_path: Optional[str] = None - if args.dump_npz_dir: - dump_path = os.path.join(args.dump_npz_dir, f"case{mc.case_id}_{coll}.npz") + npz_path = os.path.join(args.dump_npz_dir, f"{case.case_id.lower()}_mrt.npz") if args.dump_npz_dir else None + vort_path = ( + os.path.join(args.final_vorticity_dir, f"{case.case_id.lower()}_mrt_laststep.png") + if args.final_vorticity_dir + else None + ) - vort_path: Optional[str] = None - vort_title = "" - if args.final_vorticity_dir: - vort_path = os.path.join( - args.final_vorticity_dir, - f"case{mc.case_id}_{coll}_laststep.png", - ) - vort_title = ( - f"Sah04 case {mc.case_id} tier={mc.tier} {coll} Re={mc.re} " - f"nx={_NX} ny={ny} last LBM step={steps} (omega_z)" - ) - - print( - f"--- case {mc.case_id} tier={mc.tier} beta~{tier['beta_nom']} " - f"Re={mc.re} target_St={mc.target_st} {coll} steps={steps} burn={burn} ---", - flush=True, + print( + f"--- {case.case_id} {args.collision} beta_nom={case.beta_nominal:.1f} Re_nom={case.re_nominal:.0f} " + f"D={int(geometry.diameter)} H={geometry.h_fluid} gap~{geometry.wall_gap_cells:.2f} " + f"steps={steps} burn={burn} inlet={args.inlet_scheme}/{args.inlet_profile} ---", + flush=True, + ) + try: + out = run_one_simulation( + case, + geometry, + collision=args.collision, + outlet_mode=args.outlet, + inlet_profile=args.inlet_profile, + inlet_scheme=args.inlet_scheme, + u_max_nominal=0.1, + steps=steps, + burn=burn, + record_every=int(args.record_every), + f_hz_min=f_lo, + f_hz_max=f_hi, + dump_npz_path=npz_path, + final_vorticity_png_path=vort_path, + crash_dump_dir=args.crash_dump_dir, ) - try: - out = run_one_simulation( - collision=coll, - outlet=args.outlet, - nx=_NX, - ny=ny, - center=center, - re=float(mc.re), - d_lattice=float(_D), - r_cyl=_R_CYL, - u_max=u_max, - steps=steps, - burn=burn, - record_every=int(args.record_every), - f_hz_min=flo, - f_hz_max=fhi, - dump_npz_path=dump_path, - final_vorticity_png_path=vort_path, - flow_figure_title=vort_title or "Sah04 vorticity (final LBM step)", - ) - except Exception as e: - print(f"FAILED: {e}", file=sys.stderr) - rows.append( - { - "case_id": mc.case_id, - "tier": mc.tier, - "collision": coll, - "Re": mc.re, - "target_st": mc.target_st, - "hard": mc.hard, - "error": str(e), - } - ) - continue - - rel_err = relative_st_error(out["St"], mc.target_st) - rel_err_raw = relative_st_error(out["St_raw"], mc.target_st) - row = { - "case_id": mc.case_id, - "tier": mc.tier, - "beta_nominal": tier["beta_nom"], - "collision": coll, - "Re": mc.re, - "target_st": mc.target_st, - "hard": mc.hard, - "St": out["St"], - "St_raw": out["St_raw"], - "St_guided": out["St_guided"], - "rel_err_st": float(rel_err) if np.isfinite(rel_err) else None, - "rel_err_st_raw": float(rel_err_raw) if np.isfinite(rel_err_raw) else None, - "within_5pct": bool(np.isfinite(rel_err) and rel_err <= 0.05), - "worse_10pct": bool(np.isfinite(rel_err) and rel_err > 0.10), - "mean_Cd": out["mean_Cd"], - "n_curved": out["n_curved"], - "fallback_links": out["fallback_links"], - "low_q_links": out["low_q_links"], - "steps": steps, - "burn": burn, - } - rows.append(row) - if dump_path: - meta_path = os.path.splitext(dump_path)[0] + ".meta.json" - _write_json( - meta_path, - { - "case_id": mc.case_id, - "tier": mc.tier, - "collision": coll, - "Re": mc.re, - "target_st": mc.target_st, - "hard": mc.hard, - "steps": steps, - "burn": burn, - "record_every": int(args.record_every), - "f_hz_min": flo, - "f_hz_max": fhi, - "npz_basename": os.path.basename(dump_path), - "St_raw": out["St_raw"], - "St_guided": out["St_guided"], - "mean_Cd": out["mean_Cd"], - }, - ) - flag = "OK" if row.get("within_5pct") else ("SOFT" if not mc.hard else "CHECK") - print( - f" St_raw={out['St_raw']:.5f} St_guided={out['St_guided']:.5f} " - f"rel(guided)={100.0 * rel_err:.2f}% rel(raw)={100.0 * rel_err_raw:.2f}% " - f"Cd~{out['mean_Cd']:.4f} [{flag}]", - flush=True, + except Exception as exc: # noqa: BLE001 + rows.append( + { + "case_id": case.case_id, + "collision": str(args.collision).upper(), + "inlet_scheme": args.inlet_scheme, + "inlet_profile": args.inlet_profile, + "outlet": args.outlet, + "grid": {"nx": geometry.nx, "ny": geometry.ny, "diameter": int(geometry.diameter), "h_fluid": geometry.h_fluid}, + "error": str(exc), + } ) + print(f"FAILED: {exc}", flush=True) + continue - if not args.smoke and case_filter is None and str(args.case).lower() == "all": - print("\n=== Hard-case summary per collision (5% gate; need 5/7 & none >10%) ===") - for coll in collisions: - ev = evaluate_hard_cases(rows, coll) - print(json.dumps(ev, indent=2)) + rel_err = _relative_error(out["St"], case.target_st) + st_err_pct = (100.0 * rel_err) if rel_err is not None else None + row = { + "case_id": case.case_id, + "collision": out["collision"], + "inlet_scheme": out["inlet_scheme"], + "inlet_profile": out["inlet_profile"], + "grid": {"nx": geometry.nx, "ny": geometry.ny, "diameter": int(geometry.diameter), "h_fluid": geometry.h_fluid}, + "steps": int(steps), + "burn_in": int(burn), + "Re_nominal": float(case.re_nominal), + "Re_real": out["Re_real"], + "beta_nominal": float(case.beta_nominal), + "beta_real": out["beta_real"], + "wall_gap_cells": geometry.wall_gap_cells, + "target_St": float(case.target_st), + "St": out["St"], + "St_error_pct": float(st_err_pct) if st_err_pct is not None and np.isfinite(st_err_pct) else None, + "gate_pct": float(args.gate_pct), + "gate_pass": bool(st_err_pct is not None and st_err_pct <= float(args.gate_pct)), + "mean_Cd": out["mean_Cd"], + "U_max_real": out["U_max_real"], + "rho_min_final": out["rho_min_final"], + "rho_max_final": out["rho_max_final"], + "n_curved": out["n_curved"], + "fallback_links": out["fallback_links"], + "low_q_links": out["low_q_links"], + "n_lift_samples": out["n_lift_samples"], + } + rows.append(row) + st_err_txt = f"{row['St_error_pct']:.2f}%" if row["St_error_pct"] is not None else "n/a" + re_real_txt = f"{row['Re_real']:.2f}" if np.isfinite(row["Re_real"]) else "nan" + print( + f" St={row['St']:.5f} target={row['target_St']:.5f} err={st_err_txt} " + f"Re_real={re_real_txt} beta_real={row['beta_real']:.4f} " + f"[{'PASS' if row['gate_pass'] else 'CHECK'}]", + flush=True, + ) + + evaluation = evaluate_rows(rows, gate_pct=float(args.gate_pct)) + print("\n=== Sah04 MRT S1-S4 summary ===", flush=True) + print(json.dumps(evaluation, indent=2), flush=True) if args.json_out: - ev_all = { - coll: evaluate_hard_cases(rows, coll) - for coll in {r.get("collision") for r in rows if r.get("collision")} - if coll - } - _write_json(args.json_out, {"rows": rows, "evaluation_by_collision": ev_all}) - + json_out_path = os.path.abspath(args.json_out) + json_out_dir = os.path.dirname(json_out_path) + if json_out_dir: + os.makedirs(json_out_dir, exist_ok=True) + _write_json( + json_out_path, + { + "requested": { + "case": args.case, + "outlet": args.outlet, + "inlet_profile": args.inlet_profile, + "inlet_scheme": args.inlet_scheme, + "record_every": int(args.record_every), + "smoke": bool(args.smoke), + "steps_override": args.steps, + "burn_override": args.burn, + "gate_pct": float(args.gate_pct), + }, + "rows": rows, + "evaluation": evaluation, + }, + ) + print(f"Wrote: {json_out_path}", flush=True) return 0 diff --git a/tests/streakline_notes.md b/tests/streakline_notes.md new file mode 100644 index 0000000..b7df1d2 --- /dev/null +++ b/tests/streakline_notes.md @@ -0,0 +1,493 @@ +## Streakline postprocessing design + +这份说明面向一个最小可实现的离线后处理程序。输入是按时间保存的二维速度场序列,输出是接近实验染色图的 streakline 图像。核心路线是连续释粒,因为 streakline 正是“固定位置持续释放染料后,在某个时刻所有已释放粒子的位置集合” [Lan96]。对非定常尾迹,这比单帧 streamline 更贴近实验图像 [Lan96]。 + +搜索和全文阅读给出的信息已经足够支撑第一版实现。最直接的方案是按保存时刻连续注入粒子,用时空插值后的速度场推进粒子,再将粒子云渲染为图像 [Lan96, Ken96]。如果纯粒子结果过于尖锐,再在推进后加入一个很小的随机扩散项,作为对真实染色液扩散的近似。更完整的被动标量法当然更物理,但实现成本更高,不适合作为第一版 [Kim04]。 + +## Core recommendation + +| 方法 | 与实验染色图的对应 | 实现复杂度 | 适合作为第一版 | +|---|---|---:|---| +| 单帧 streamline | 弱 | 很低 | 不推荐 | +| 连续释粒 streakline | 强 | 低 | 最推荐 | +| streakline 加少量扩散 | 很强 | 低到中 | 推荐作为第二步 | +| 被动标量对流扩散 | 最强 | 中到高 | 暂不作为第一版 | + +最小可实现路线如下: + +- 从 CelerisLab 导出一串二维速度场快照 `u_x(x,y,t_k), u_y(x,y,t_k)` +- 选定一个固定释放点或一小段释放线 +- 在每个保存时刻都注入新粒子 +- 在两个相邻快照之间,用时间插值和空间插值计算粒子速度 +- 用二阶或四阶时间积分推进粒子 +- 删去出域粒子与进入固体的粒子 +- 将当前时刻存活粒子按位置和年龄渲染成图像 + +这个程序逻辑直接对应 [Lan96] 的 streakline 算法,插值与推进细节由 [Ken96] 和 [Dar96] 支撑。 + +## Data contract + +默认输入是规则 Cartesian 网格上的二维时间序列速度场。对当前项目,最自然的输入约定是: + +| 名称 | 含义 | +|---|---| +| `t_k` | 第 `k` 个保存时刻 | +| `ux[k, j, i]` | 时刻 `t_k` 的 x 方向速度 | +| `uy[k, j, i]` | 时刻 `t_k` 的 y 方向速度 | +| `mask[j, i]` | 可选,固体与流体标记 | +| `x_i, y_j` | 网格坐标 | + +第一版最好满足下面三条: + +- 时间快照间隔固定或至少已知 +- 网格坐标固定不动 +- 固体几何位置已知,至少能判断粒子是否进入圆柱内部 + +如果输出来自 CelerisLab,本质上只需要把每个保存时刻的 `ux, uy` 和对应时间写出来即可。算法本身是通用的,并不依赖 LBM 本身。 + +## Mathematical model for the base streakline + +### Particle motion + +粒子位置满足拉格朗日运动方程 [Lan96]: + +\[ +\frac{d \boldsymbol{x}}{dt} = \boldsymbol{u}(\boldsymbol{x}, t) +\] + +其中 + +\[ +\boldsymbol{x} = (x, y), \qquad \boldsymbol{u} = (u_x, u_y) +\] + +积分形式为 [Lan96]: + +\[ +\boldsymbol{x}(t + \Delta t) = \boldsymbol{x}(t) + \int_t^{t+\Delta t} \boldsymbol{u}(\boldsymbol{x}(\tau), \tau) \, d\tau +\] + +streakline 在观察时刻 \(t_n\) 的定义是:从固定释放位置 \(\boldsymbol{x}_s\) 在过去各时刻持续注入的粒子,在 \(t_n\) 时刻的全部位置集合 [Lan96]。 + +### Time interpolation + +速度场只在离散时刻存储,所以必须做时间插值。对位于 \(t_k \le t \le t_{k+1}\) 的任意子步,最简单的做法是线性时间插值 [Ken96]: + +\[ +\delta = \frac{t - t_k}{t_{k+1} - t_k} +\] + +\[ +\boldsymbol{u}(\boldsymbol{x}, t) = (1 - \delta) \, \boldsymbol{u}_k(\boldsymbol{x}) + \delta \, \boldsymbol{u}_{k+1}(\boldsymbol{x}) +\] + +这里 \(\boldsymbol{u}_k(\boldsymbol{x})\) 和 \(\boldsymbol{u}_{k+1}(\boldsymbol{x})\) 仍需通过空间插值得到。 + +### Spatial interpolation + +在规则网格上,第一版直接用双线性插值即可。虽然 [Ken96] 讨论的是非结构网格上的点定位与线性插值,但其一般原则完全适用于规则网格: + +- 先定位粒子所在单元 +- 再用单元顶点速度插值得到粒子点速度 [Ken96] + +若粒子位于单元局部坐标 \((\xi, \eta) \in [0,1]^2\),四个角点速度为 \(\boldsymbol{u}_{00}, \boldsymbol{u}_{10}, \boldsymbol{u}_{01}, \boldsymbol{u}_{11}\),则 + +\[ +\boldsymbol{u}(\xi, \eta) = (1-\xi)(1-\eta) \boldsymbol{u}_{00} ++ \xi (1-\eta) \boldsymbol{u}_{10} ++ (1-\xi)\eta \boldsymbol{u}_{01} ++ \xi\eta \boldsymbol{u}_{11} +\] + +## Time integration choice + +[Lan96] 和 [Ken96] 都直接使用四阶 Runge Kutta。对你的第一版程序,推荐两个选项: + +| 积分器 | 优点 | 缺点 | 建议 | +|---|---|---|---| +| RK2 | 简洁,容易调试 | 精度一般 | 最小原型可用 | +| RK4 | 文献最一致,精度更稳 | 每步插值次数更多 | 默认推荐 | + +RK4 的更新式为 [Lan96, Ken96]: + +\[ +\boldsymbol{x}_{n+1} = \boldsymbol{x}_n + \frac{1}{6}(\boldsymbol{a} + 2\boldsymbol{b} + 2\boldsymbol{c} + \boldsymbol{d}) +\] + +其中 + +\[ +\boldsymbol{a} = \Delta t \, \boldsymbol{u}(\boldsymbol{x}_n, t_n) +\] + +\[ +\boldsymbol{b} = \Delta t \, \boldsymbol{u}(\boldsymbol{x}_n + \tfrac{1}{2}\boldsymbol{a}, t_n + \tfrac{1}{2}\Delta t) +\] + +\[ +\boldsymbol{c} = \Delta t \, \boldsymbol{u}(\boldsymbol{x}_n + \tfrac{1}{2}\boldsymbol{b}, t_n + \tfrac{1}{2}\Delta t) +\] + +\[ +\boldsymbol{d} = \Delta t \, \boldsymbol{u}(\boldsymbol{x}_n + \boldsymbol{c}, t_n + \Delta t) +\] + +[Ken96] 特别强调,RK4 的每一个子步都要重新做点定位与时空插值。这在规则网格上不难实现,只是意味着一次完整步进需要四次速度查询 [Ken96]。 + +## Practical timestep guidance + +第一版不必上自适应步长,但步长不能随意设大。搜索里最重要的精度提醒来自 [Dar96]: + +- 非定常粒子积分的误差常常由时间离散控制,而不是概念本身 +- 合理步长必须不大于流动主要非定常时间尺度的同量级 [Dar96] +- 若时间步过大,粒子轨迹会在拓扑上都出错,而不仅是位置略偏 [Dar96] + +对当前项目,最简单的工程准则是: + +\[ +\Delta t_{trace} \le \min \bigl(\alpha_t \, \Delta t_{save},\; \alpha_x \, \frac{\Delta x}{\max |u|} \bigr) +\] + +其中 \(\Delta t_{save}\) 是两个保存快照之间的时间间隔,\(\Delta x\) 是网格尺度。第一版可取: + +- \(\alpha_t = 0.1 \sim 0.25\) +- \(\alpha_x = 0.25 \sim 0.5\) + +这不是文献中的严格上界,而是结合 [Dar96] 的结论给出的实现准则:粒子推进子步既要显著小于保存间隔,也不要一子步跨过太多网格。 + +[Ken96] 还给了一个很实用的自适应思路:根据相邻速度方向夹角调节步长。如果速度方向变化太快就减半,变化很小就加倍 [Ken96]。这很适合后续增强版,但不属于第一版必需项。 + +## Base streakline algorithm + +### Release strategy + +最符合染色实验的是连续释粒 [Lan96]。第一版可用两种释放方式: + +| 方式 | 适用场景 | 建议 | +|---|---|---| +| 单点释放 | 针头式染料入口 | 最简单 | +| 短线段释放 | 更像细缝或细带注入 | 更稳健 | + +如果实验是在圆柱上游某一点持续释放染色液,第一版就直接使用单点释放。 + +### Particle state + +每个粒子只需保存: + +| 字段 | 含义 | +|---|---| +| `x, y` | 当前坐标 | +| `t_birth` | 释放时刻 | +| `age` | 当前年龄 | +| `alive` | 是否仍在域内 | + +第一版不需要保存整条历史轨迹。因为你要的是当前时刻的 streakline 图,而不是每个粒子的 pathline 曲线。 + +**常见误区(务必区分)** + +| 概念 | 画什么 | 是否等于水洞染色迹线 | +|---|---|---| +| **Streakline(迹线)** | 固定源点**持续释粒**,在观察时刻 \(t_n\) 画出**所有仍存活粒子的当前位置**(可带年龄衰减权重) | 是 | +| **Pathline(迹线/轨道)** | 单个粒子从释放到当前的**整条历史轨迹** | 否 | +| **错误实现** | 每隔一段时间放一个粒子,再把**所有粒子走过的路径段**全部叠加到图上 | 否(这是 pathline 叠加,不是 streakline) | + +水洞实验:针头在固定点连续注 dye → 某一时刻拍照 → 看到的是“此刻染料粒子在流场里的分布”,一条下游色带由**不同释放时刻、当前仍在本流场中的粒子**共同构成,而不是把每个粒子从出生到现在的轨迹都画出来。 + +### Main loop pseudocode + +```text +given velocity snapshots U[k] = {ux[k], uy[k]} at times t[k] +given seeding point or seeding segment S +initialize empty particle list P + +for k = 0 to N-2: + + inject new particles at source S at time t[k] + + set t_local = t[k] + while t_local < t[k+1]: + dt = min(dt_trace, t[k+1] - t_local) + + for each particle p in P with p.alive: + v = interpolate_velocity(p.position, t_local) + advance p by one integration step using RK2 or RK4 + update p.age + if p leaves domain: + p.alive = false + if p enters solid body: + p.alive = false + + t_local = t_local + dt + + optionally remove very old particles + optionally render current particle cloud +``` + +这个结构与 [Lan96] 的离散 streakline 算法一致,只是把“从 `t_k` 到 `t_{k+1}` 的一次推进”细化成多个更小的粒子子步,以满足 [Dar96] 对精度的要求。 + +### Velocity query pseudocode + +```text +function interpolate_velocity(position x, time t): + find k such that t[k] <= t <= t[k+1] + compute delta = (t - t[k]) / (t[k+1] - t[k]) + + u_k = bilinear_interpolation(U[k], x) + u_k1 = bilinear_interpolation(U[k+1], x) + + return (1 - delta) * u_k + delta * u_k1 +``` + +这是第一版最核心的数值部件。只要这部分实现正确,streakline 程序主体就很直接。 + +## Small diffusion particle model + +当纯 streakline 太细、太锐利、不像染色液图像时,可以给每个粒子加一个很小的随机扩散项。这不是严格的被动标量求解,但能以很低代价增加染色带宽度。 + +### Guiding idea + +[Kim04] 表明,染料图像更接近一个被动标量浓度场,其基本控制方程是对流扩散方程: + +\[ +\frac{\partial c}{\partial t} + \boldsymbol{u} \cdot \nabla c = D \nabla^2 c +\] + +其中 \(c\) 是染料浓度,\(D\) 是扩散系数。对第一版粒子法,一个常见近似是将每个粒子的位置更新写成“对流加随机扩散”: + +\[ +\boldsymbol{x}_{n+1} = \boldsymbol{x}_n + \Delta \boldsymbol{x}_{adv} + \Delta \boldsymbol{x}_{diff} +\] + +其中 \(\Delta \boldsymbol{x}_{adv}\) 由 RK2 或 RK4 给出,扩散项取二维各向同性随机增量: + +\[ +\Delta \boldsymbol{x}_{diff} = \sqrt{2 D \Delta t} +\begin{bmatrix} +\eta_x \\ +\eta_y +\end{bmatrix} +\] + +这里 \(\eta_x, \eta_y \sim \mathcal{N}(0,1)\)。 + +这个形式本身是对扩散过程的标准随机游走近似。搜索结果提醒,随机游走模型如果处理不当会产生假漂移与错误浓度偏置 [Mac92]。因此第一版的使用原则应当很克制: + +- 只用于加入少量模糊和厚度 +- 不把粒子密度当成严格浓度 +- 不在强近壁统计上过度解读结果 + +### Diffusive particle update pseudocode + +```text +for each particle p in P with p.alive: + x_adv = RK4_step(p.position, t_local, dt) + + sigma = sqrt(2 * D * dt) + dx_rand = sigma * normal(0, 1) + dy_rand = sigma * normal(0, 1) + + x_new = x_adv + [dx_rand, dy_rand] + + if x_new leaves domain: + p.alive = false + else if x_new enters solid: + p.alive = false + else: + p.position = x_new + p.age += dt +``` + +### Choosing the diffusion level + +第一版不必试图从真实染料物性严格标定 \(D\)。更实用的做法是把 \(D\) 当作视觉匹配参数,并保持它足够小,使图像结构仍主要由对流控制。 + +[Kim04] 用 Schmidt 数控制扩散强弱。若已有参考速度 \(U\) 和长度尺度 \(L\),则 + +\[ +Sc = \frac{\nu}{D} +\] + +也可写成 + +\[ +D = \frac{\nu}{Sc} +\] + +对第一版,可以用下面的思路选扩散强度: + +| 目标效果 | 建议 | +|---|---| +| 只想让线条略微变厚 | 取较大 `Sc`,即很小的 `D` | +| 想模拟明显洗开和模糊 | 取较小 `Sc`,即较大的 `D` | +| 不确定 | 先从几乎看不出的弱扩散开始 | + +因为当前目标是“少量扩散”,所以推荐先把扩散当成弱修饰,而不是主导机制。 + +## Rendering logic + +最终图像不需要把每个粒子的完整轨迹都画出来。更像实验染色图的做法是把当前存活粒子投影到图像网格上,生成粒子密度图或带年龄权重的强度图。 + +### Minimal rendering choices + +| 方法 | 图像风格 | 实现难度 | +|---|---|---:| +| 直接散点 | 最简陋 | 很低 | +| 网格计数直方图 | 像浓度图 | 低 | +| 高斯核累积 | 更平滑 | 低到中 | + +第一版推荐: + +- 把每个粒子投到像素网格 +- 对像素做计数或加权累积 +- 最后做一次轻微 Gaussian blur + +这与先做完整被动标量相比便宜很多,但视觉上已经会很接近实验染色图。 + +### Optional particle weighting + +可以给粒子一个简单权重: + +\[ +I = \sum_p w_p K(\boldsymbol{x} - \boldsymbol{x}_p) +\] + +其中 \(K\) 是像素核或 Gaussian 核。第一版里,权重 \(w_p\) 可直接取 1,也可对年龄做衰减,例如: + +\[ +w_p = \exp\left(- \frac{\mathrm{age}_p}{\tau_f} \right) +\] + +这样旧粒子会逐渐淡出,图像不会无限堆积。 + +## Boundary handling + +这部分先按最小原则处理即可。 + +| 情况 | 第一版处理 | +|---|---| +| 粒子出计算域 | 直接删除 | +| 粒子进入圆柱内部 | 直接删除 | +| 粒子贴近边界滑动 | 暂不专门处理 | +| 扩散后跨入固体 | 直接删除 | + +这样做的优点是简单稳妥。后续若发现近壁 streakline 误差明显,再考虑反射、投影或更物理的壁面处理。 + +## Minimal implementation plan + +### Version 1 + +目标是尽快得到可信的 streakline 图: + +- 读取 `ux, uy, t` +- 单点连续释粒 +- 双线性空间插值 +- 线性时间插值 +- RK4 粒子推进 +- 出域删除与入固体删除 +- 粒子计数成图 + +### Version 2 + +在不改变主体架构的前提下增强视觉效果: + +- 增加短线段释放 +- 增加年龄衰减 +- 增加弱随机扩散 +- 用 Gaussian 核代替简单计数 + +### Version 3 + +若后续发现实验图像明显受扩散与混合主导,再转向更重的模型: + +- 被动标量对流扩散 +- 用 \(Sc\) 或 \(D\) 做参数标定 [Kim04] + +## Design decisions that are already justified by the literature + +下面这些设计已经有足够文献支撑,可以直接采用: + +- 用 streakline 而不是 streamline 对应持续染色实验 [Lan96] +- 用连续释放粒子离线重构 streakline [Lan96] +- 用时空插值从离散速度快照查询粒子速度 [Ken96] +- 用 RK4 作为默认推进器 [Lan96, Ken96] +- 把步长取得明显小于保存时间间隔,并避免一次跨过太多网格 [Dar96] +- 若需要更像染料图,可以加入弱扩散,或后续上升到被动标量 [Kim04] + +## Recommended default choices + +| 项目 | 默认选择 | +|---|---| +| 释放方式 | 单点连续释粒 | +| 空间插值 | 双线性 | +| 时间插值 | 线性 | +| 积分器 | RK4 | +| 粒子子步 | 保存步长的 0.1 到 0.25 | +| 固体处理 | 入固体即删除 | +| 输出图像 | 粒子密度图加轻微模糊 | +| 扩散 | 默认关闭,作为第二步 | + +## Bottom line + +对当前项目,最简洁且足够准确的程序逻辑不是去解新的染料场,而是先做一个离线连续释粒 streakline 后处理器。它只依赖时间序列速度场,程序结构清楚,数值风险也集中在可控的几个环节:时空插值、积分步长和边界删除 [Lan96, Dar96, Ken96]。在此基础上,再增加一个弱随机扩散项,就能以很小代价把图像从“几何上正确的粒子线”推进到“更像实验染色照片”的粒子云 [Kim04]。 + +## CelerisLab implementation mapping + +Current implementation lives in: + +- library: `src/CelerisLab/common/streakline.py` +- Kan99b demo CLI: `tests/run_kan99b_streakline.py` +- experiment notebook: `tests/experiment.ipynb` (CLEAN 5/6) + +Available runtime modes: + +- `online`: sample `Simulation.get_macroscopic()` directly in memory (no velocity snapshot files). +- `offline`: replay from an existing snapshot directory and run the same streakline integrator. + +Dense release support: + +- base upstream points are expanded by `release_mode` (`point|line|strip`). +- `strip` mode densifies both cross-stream (`line_count`, `line_span`) and downstream (`downstream_count`, `downstream_spacing`) directions. + +### Performance notes (why it can feel slow) + +Streakline post-processing is **CPU-side** in the current stack: + +1. **GPU→host velocity copies** dominate for large grids (`nx=6000, ny=1200`). Each `get_macroscopic()` pulls full `ux/uy` arrays. +2. Particle RK4 + bilinear interpolation runs in NumPy on CPU (vectorized over particles, not GPU). +3. Rendering accumulates polylines into an image (optionally multi-threaded via `ThreadPoolExecutor`). + +Practical tuning: + +| knob | effect | +|---|---| +| `sample_every` | larger → fewer host copies, faster, coarser streaks | +| `max_particle_age` | `None` → no hard cutoff; trails survive until boundary/solid | +| `num_threads` | `0` = auto; sets OpenBLAS/OMP threads for blur/host ops | +| `blur_sigma` | `0` disables Gaussian blur pass | + +For `experiment.ipynb` triangle case, use **`render_streakline_density(..., minimal_axes=True)`** on the **final alive particle cloud** (`positions`, `ages`). Do **not** accumulate and draw full path history (`render_snapshot_trails` is pathline-style debug only). + +### Clean render style (experiment) + +`render_snapshot_trails` defaults: + +- white background +- red streaks, brighter toward the downstream end (`fade_along_trail=True`) +- black filled cylinders +- no axes, labels, colorbar, or release-point markers + +Example commands: + +```bash +# Online in-memory streakline (no snapshot write) +conda run -n pycuda_3_10 python tests/run_kan99b_streakline.py \ + --mode online --domain M --re 100 --alpha 1.0 \ + --sample-every 300 --n-snapshots 20 \ + --release-mode strip --line-count 7 --downstream-count 6 + +# Offline replay from existing velocity snapshots +conda run -n pycuda_3_10 python tests/run_kan99b_streakline.py \ + --mode offline \ + --snapshot-dir tests/output/final_validation_round/streakline_kan99b_k2/velocity_snapshots \ + --release-mode strip --line-count 7 --downstream-count 6 +``` \ No newline at end of file diff --git a/tests/streakline_triangle_clean.png b/tests/streakline_triangle_clean.png new file mode 100644 index 0000000000000000000000000000000000000000..544453e042e5a7fe5d0de8d5838df01a319a31ba GIT binary patch literal 323486 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 literal 0 HcmV?d00001 diff --git a/tests/vorticity_triangle_clean.png b/tests/vorticity_triangle_clean.png new file mode 100644 index 0000000000000000000000000000000000000000..5c999f99d41960c577194298e3f8525b6a9f562b GIT binary patch literal 137686 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%0D47<$7Bs*6xFoc)yjO1@k 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 P<3C2Hk7XTkxb^=4(^Is- literal 0 HcmV?d00001