Skip to content

Commit 0099674

Browse files
committed
fp-stability: ascii-only — convert em-dash/arrow/math glyphs in comments + output
Replaced non-ASCII in the toolchain (em-dash/en-dash -> '-', '->' for arrows, '>=' for >=, '+/-', '~' for approx, 'inf'/'Linf' for the infinity glyph, '...' for ellipsis) across fp_stability*.py, verrou.sh, and the fp-stability command help. Display/comment text only; no logic change. The viz command's pre-existing glyphs are left untouched (not part of this PR).
1 parent c58d44f commit 0099674

6 files changed

Lines changed: 46 additions & 46 deletions

File tree

toolchain/bootstrap/verrou.sh

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
#!/bin/bash
22
#
33
# Opt-in installer for Verrou (the Valgrind FP-perturbation tool used by
4-
# `./mfc.sh fp-stability`). Verrou is NOT a Python/pip package it is a fork of
4+
# `./mfc.sh fp-stability`). Verrou is NOT a Python/pip package - it is a fork of
55
# Valgrind. By default this downloads a prebuilt, hash-verified artifact (seconds);
66
# if none is available for this tag/arch it falls back to a source build (~20 min).
77
# fp-stability auto-runs this on first use when Verrou is absent (printing what it
@@ -19,7 +19,7 @@ set -euo pipefail
1919
VALGRIND_VERSION="3.26.0"
2020
VERROU_COMMIT="a58d434"
2121
# Prebuilt artifacts (built once per arch) live in a small companion repo. The tag
22-
# pins to the (valgrind, verrou) pair above bump all three together.
22+
# pins to the (valgrind, verrou) pair above - bump all three together.
2323
VERROU_DIST_REPO="${VERROU_DIST_REPO:-sbryngelson/verrou-dist}"
2424
VERROU_DIST_TAG="${VERROU_DIST_TAG:-v1}"
2525
PREFIX="${VERROU_HOME:-$HOME/.local/verrou}"
@@ -46,7 +46,7 @@ case "$(uname -m)" in
4646
aarch64|arm64)
4747
arch_tag="aarch64"
4848
echo "WARNING: $(uname -m) detected. Valgrind builds here, but Verrou's FP backends are" >&2
49-
echo " best-validated on x86_64 treat results as experimental on this arch." >&2
49+
echo " best-validated on x86_64 - treat results as experimental on this arch." >&2
5050
;;
5151
*)
5252
echo "WARNING: unrecognised arch $(uname -m); the build may fail. Proceeding anyway." >&2
@@ -73,39 +73,39 @@ try_prebuilt() {
7373
if command -v curl >/dev/null 2>&1; then curl -fsSL -o "$2" "$1"; else wget -q -O "$2" "$1"; fi
7474
}
7575
if ! _fetch "$base" "$dl/$asset" || ! _fetch "$base.sha256" "$dl/$asset.sha256"; then
76-
echo "==> No prebuilt for this tag/arch building from source instead."
76+
echo "==> No prebuilt for this tag/arch - building from source instead."
7777
rm -rf "$dl"; return 1
7878
fi
7979
if ! ( cd "$dl" && sha256sum -c "$asset.sha256" >/dev/null 2>&1 ); then
80-
echo "WARNING: prebuilt checksum mismatch building from source instead." >&2
80+
echo "WARNING: prebuilt checksum mismatch - building from source instead." >&2
8181
rm -rf "$dl"; return 1
8282
fi
8383

8484
# Extract + verify in a staging dir, then swap into $PREFIX atomically. set -e
8585
# is suppressed inside a function used as an `if` condition, so check each step
86-
# explicitly otherwise a failed extract would fall through and the source
86+
# explicitly - otherwise a failed extract would fall through and the source
8787
# build would install on top of a half-written tree (or a stale one on --force).
8888
local stage="$dl/stage"
8989
mkdir -p "$stage"
9090
if tar --zstd --help >/dev/null 2>&1; then
91-
tar -C "$stage" --zstd -xf "$dl/$asset" || { echo "WARNING: prebuilt extract failed building from source instead." >&2; rm -rf "$dl"; return 1; }
91+
tar -C "$stage" --zstd -xf "$dl/$asset" || { echo "WARNING: prebuilt extract failed - building from source instead." >&2; rm -rf "$dl"; return 1; }
9292
else
93-
zstd -dc "$dl/$asset" | tar -C "$stage" -xf - || { echo "WARNING: prebuilt extract failed building from source instead." >&2; rm -rf "$dl"; return 1; }
93+
zstd -dc "$dl/$asset" | tar -C "$stage" -xf - || { echo "WARNING: prebuilt extract failed - building from source instead." >&2; rm -rf "$dl"; return 1; }
9494
fi
9595

9696
# Valgrind bakes its build prefix into the binary; the artifact's env.sh sets
9797
# VALGRIND_LIB relative to the tree so the relocated install works. Verify the
9898
# staged tree runs before committing it.
9999
if ! ( . "${stage}/env.sh" && "${stage}/bin/valgrind" --tool=verrou --version >/dev/null 2>&1 ); then
100-
echo "WARNING: prebuilt did not run building from source instead." >&2
100+
echo "WARNING: prebuilt did not run - building from source instead." >&2
101101
rm -rf "$dl"; return 1
102102
fi
103103

104104
# Commit only now: replace any existing $PREFIX atomically.
105105
mkdir -p "$(dirname "$PREFIX")"
106106
rm -rf "$PREFIX"
107107
if ! mv "$stage" "$PREFIX"; then
108-
echo "WARNING: could not install prebuilt to ${PREFIX} building from source instead." >&2
108+
echo "WARNING: could not install prebuilt to ${PREFIX} - building from source instead." >&2
109109
rm -rf "$dl"; return 1
110110
fi
111111
rm -rf "$dl"

toolchain/mfc/cli/commands.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -899,25 +899,25 @@
899899
help="Run floating-point stability tests using Verrou.",
900900
description=(
901901
"Runs Verrou random-rounding stability analysis on a built-in suite of small "
902-
"1-D cases, or given a case .py (positional INPUT) on your own case. Each "
902+
"1-D cases, or - given a case .py (positional INPUT) - on your own case. Each "
903903
"case is run N times under Verrou's random IEEE-754 rounding and compared "
904904
"against a nearest-rounding reference. PASS/FAIL is scale-free: a case must "
905905
"retain at least ~24 significant bits (single precision) under random rounding "
906906
"(no per-case thresholds).\n\n"
907907
"With a case .py, that case is run as a SINGLE serial CPU process under Verrou "
908-
"(~30x slower, and run many times), so it must be a small, short proxy large "
908+
"(~30x slower, and run many times), so it must be a small, short proxy - large "
909909
"grids or long runs are rejected with guidance; serial .dat I/O is forced. "
910910
"Example: ./mfc.sh fp-stability my_case.py\n\n"
911911
"Uses a Verrou-enabled Valgrind at $VERROU_HOME/bin/valgrind (defaults to "
912912
"$HOME/.local/verrou); if absent it is installed automatically (a pinned, "
913-
"hash-verified prebuilt is downloaded, with a source build as fallback) "
913+
"hash-verified prebuilt is downloaded, with a source build as fallback) - "
914914
"aborts if that install fails. The simulation and pre_process binaries must "
915915
"be serial (no-MPI, no-GPU) debug builds.\n\n"
916916
"Analysis passes (skip with --no-* flags):\n"
917917
" float proxy One run with --rounding-mode=float (single-precision sensitivity)\n"
918918
" vprec sweep Runs at mantissa bits [52, 23, 16, 10] (precision floor curve)\n"
919919
" cancellation --check-cancellation origins, ranked by significant digits lost\n"
920-
" float-max --check-max-float detection of doublefloat overflow sites\n"
920+
" float-max --check-max-float detection of double->float overflow sites\n"
921921
),
922922
include_common=["mfc_config", "verbose", "debug_log"],
923923
positionals=[

toolchain/mfc/fp_stability.py

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
(scale-free: -log2(max_dev/scale) vs one global floor, no per-case threshold).
99
1010
B. Float proxy (--no-float-proxy to skip)
11-
One run with --rounding-mode=float deterministic proxy for
11+
One run with --rounding-mode=float - deterministic proxy for
1212
single-precision sensitivity without recompiling.
1313
1414
C. VPREC precision sweep (--no-vprec to skip)
@@ -24,7 +24,7 @@
2424
2525
E. Float-max overflow detection (--no-float-max to skip)
2626
One run with --check-max-float=yes; reports locations where a
27-
doublefloat conversion would overflow to ±Inf.
27+
double->float conversion would overflow to +/-Inf.
2828
2929
Logs are saved to fp-stability-logs/ and uploaded as CI artifacts.
3030
On GitHub Actions: a step summary table and ::warning:: file annotations
@@ -334,7 +334,7 @@ def _merge(*dicts):
334334
"name": "low_mach",
335335
"description": "1-D water shock with low_Mach=1 HLLC correction active",
336336
"compare": ["cons.1.00.000050.dat", "prim.3.00.000050.dat"],
337-
"ill_cond": "low_Mach correction: velocity perturbation ~u/c cancels severely at M0",
337+
"ill_cond": "low_Mach correction: velocity perturbation ~u/c cancels severely at M~0",
338338
"pre": _merge(
339339
_BASE_PRE,
340340
_WATER_EOS,
@@ -475,10 +475,10 @@ def _run_case(
475475
result["cancellation_macro"] = {(path, line): macro for (path, line) in locs if (macro := _macro_context(path, line))}
476476
if locs:
477477
worst = max(bits.values()) if bits else 0
478-
cons.print(f" cancellation: {len(locs)} site(s), worst loses {worst / math.log2(10):.0f} of ~16 digits")
478+
cons.print(f" cancellation: {len(locs)} site(s), worst loses >= {worst / math.log2(10):.0f} of ~16 digits")
479479
n_macro = len(result["cancellation_macro"])
480480
if n_macro:
481-
cons.print(f" [dim]{n_macro} inside fypp expansions line maps to multiple instances[/dim]")
481+
cons.print(f" [dim]{n_macro} inside fypp expansions - line maps to multiple instances[/dim]")
482482
else:
483483
cons.print(" cancellation: none detected")
484484
except Exception as exc:
@@ -518,7 +518,7 @@ def _load_user_case(input_path: str) -> dict:
518518
"""Build a single fp-stability case from a user case .py.
519519
520520
The case is run as ONE serial CPU process under Verrou (so it must be small
521-
and short a coarsened proxy of a production run, not the real thing); a grid
521+
and short - a coarsened proxy of a production run, not the real thing); a grid
522522
too large to be feasible errors. The output files to compare are auto-detected
523523
from the reference run, so 'compare' is left empty here.
524524
"""
@@ -533,14 +533,14 @@ def _load_user_case(input_path: str) -> dict:
533533
t_stop = int(params.get("t_step_stop", 0) or 0)
534534
work = cells * max(t_stop, 1)
535535
if cells > FP_CASE_MAX_CELLS:
536-
raise MFCException(f"case has {cells:,} cells too large for Verrou (~30x slowdown, run many times). " f"Use a coarsened proxy (<= {FP_CASE_MAX_CELLS:,} cells).")
536+
raise MFCException(f"case has {cells:,} cells - too large for Verrou (~30x slowdown, run many times). " f"Use a coarsened proxy (<= {FP_CASE_MAX_CELLS:,} cells).")
537537
if work > FP_CASE_MAX_WORK:
538538
raise MFCException(
539-
f"case is ~{work:,} cell-steps ({cells:,} cells x {t_stop} time steps) too slow under "
539+
f"case is ~{work:,} cell-steps ({cells:,} cells x {t_stop} time steps) - too slow under "
540540
f"Verrou (~30x, run many times). Reduce m/n/p or t_step_stop (target <= {FP_CASE_MAX_WORK:,} cell-steps)."
541541
)
542542
stem = os.path.splitext(os.path.basename(input_path))[0]
543-
if stem == "case": # examples/<name>/case.py the dir name is more telling
543+
if stem == "case": # examples/<name>/case.py - the dir name is more telling
544544
stem = os.path.basename(os.path.dirname(os.path.abspath(input_path))) or stem
545545
return {
546546
"name": stem,
@@ -554,10 +554,10 @@ def _load_user_case(input_path: str) -> dict:
554554

555555
def _install_verrou() -> str:
556556
"""Verrou is absent: install it via the bootstrap (downloads a pinned, hash-verified
557-
prebuilt; source build as fallback) and return the valgrind path. Aborts on failure
557+
prebuilt; source build as fallback) and return the valgrind path. Aborts on failure -
558558
fp-stability cannot run without Verrou, so this is a hard error, not a skip."""
559559
script = os.path.join(MFC_ROOT_DIR, "toolchain", "bootstrap", "verrou.sh")
560-
cons.print("[bold]Verrou not found installing it (downloads a prebuilt artifact, ~seconds; source build as fallback)...[/bold]")
560+
cons.print("[bold]Verrou not found - installing it (downloads a prebuilt artifact, ~seconds; source build as fallback)...[/bold]")
561561
if subprocess.run(["bash", script], check=False).returncode != 0:
562562
raise MFCException("Verrou install failed (see output above). Fix the issue and re-run, install manually with `bash toolchain/bootstrap/verrou.sh`, or pass --verrou-binary PATH.")
563563
verrou_bin = _find_verrou()

toolchain/mfc/fp_stability_metrics.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -155,7 +155,7 @@ def _max_abs_np(ref_dir: str, compare_files: list) -> float:
155155

156156

157157
def _parse_cancel_gen(gen_path: str) -> list:
158-
"""Parse cc-gen-file TSV (file\\tline\\tsymbol) sorted unique [(fname, line)] for MFC sources."""
158+
"""Parse cc-gen-file TSV (file\\tline\\tsymbol) -> sorted unique [(fname, line)] for MFC sources."""
159159
if not os.path.isfile(gen_path):
160160
return []
161161
locs = []
@@ -215,7 +215,7 @@ def _parse_vg_error_locs(log_path: str, error_keyword: str) -> list:
215215
# Verrou exposes no per-site bit-count, but --cc-threshold-double is a severity
216216
# filter: a site is reported only if it lost >= the threshold bits. Sweeping these
217217
# levels and taking the highest each site survives gives a per-site "bits lost"
218-
# severity (a lower bound no false positives). 48 is near the full 53-bit
218+
# severity (a lower bound - no false positives). 48 is near the full 53-bit
219219
# double mantissa (the top of the sweep), not the mantissa width itself.
220220
CANCEL_BIT_LEVELS = [10, 20, 30, 40, 48]
221221

toolchain/mfc/fp_stability_report.py

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -36,10 +36,10 @@ def _emit_github_annotations(results: list):
3636
note = "catastrophic cancellation site"
3737
bits = site_bits.get((fname, lineno))
3838
if bits:
39-
note += f" loses {bits / math.log2(10):.0f} of ~16 digits"
39+
note += f" - loses >= {bits / math.log2(10):.0f} of ~16 digits"
4040
macro = macro_sites.get((fname, lineno))
4141
if macro:
42-
note += f" inside a {macro}-expanded line, may represent multiple instances"
42+
note += f" - inside a {macro}-expanded line, may represent multiple instances"
4343
print(f"::notice {loc},title={title}::{note}", flush=True)
4444
n_cc = len(r.get("cancellation_locs", []))
4545
if n_cc > 3:
@@ -51,7 +51,7 @@ def _more_md(total: int, shown: int, noun: str) -> str:
5151
or '' when nothing was truncated."""
5252
if total <= shown:
5353
return ""
54-
return f"- and {total - shown} more {noun}; see `fp-stability-logs/`"
54+
return f"- ...and {total - shown} more {noun}; see `fp-stability-logs/`"
5555

5656

5757
def _emit_github_summary(results: list, n_samples: int):
@@ -70,26 +70,26 @@ def _emit_github_summary(results: list, n_samples: int):
7070

7171
md = []
7272
md.append("## FP Stability Results\n")
73-
md.append(f"**{n_pass} passed, {n_fail} failed** {n_samples} random-rounding samples per case\n")
73+
md.append(f"**{n_pass} passed, {n_fail} failed** - {n_samples} random-rounding samples per case\n")
7474
md.append(
7575
f"> **Coverage:** {len(results)} one-dimensional case(s) "
7676
f"({', '.join(r['name'] for r in results)}). A pass means stable in the code paths these "
77-
"cases exercise not a guarantee for multi-D, viscous, MHD, IGR, or bubble-dynamics paths "
77+
"cases exercise - not a guarantee for multi-D, viscous, MHD, IGR, or bubble-dynamics paths "
7878
"they do not reach.\n"
7979
)
8080

81-
# Main results table pass/fail is scale-free: bits retained vs a single floor
81+
# Main results table - pass/fail is scale-free: bits retained vs a single floor
8282
md.append(f"_Pass = at least **{MIN_SIG_BITS} significant bits** retained under random rounding (scale-free; no per-case threshold)._\n")
8383
md.append("| Case | Status | bits retained | max\\_dev | Float proxy |")
8484
md.append("|------|:------:|:------:|--------:|--------:|")
8585
for r in results:
8686
status = "PASS" if r["passed"] else "FAIL"
87-
bits = f"{r['sig_bits']:.1f}" if r.get("sig_bits") is not None else ""
88-
fp = f"{r['float_proxy']:.2e}" if r["float_proxy"] is not None else ""
87+
bits = f"{r['sig_bits']:.1f}" if r.get("sig_bits") is not None else "-"
88+
fp = f"{r['float_proxy']:.2e}" if r["float_proxy"] is not None else "-"
8989
md.append(f"| `{r['name']}` | {status} | {bits} / {MIN_SIG_BITS} | {r['max_dev']:.2e} | {fp} |")
9090
md.append("")
9191

92-
# Cancellation ORIGINS where ill-conditioning actually arises, led with the
92+
# Cancellation ORIGINS - where ill-conditioning actually arises, led with the
9393
# most severe (most bits lost).
9494
cases_with_cancel = [r for r in results if r.get("cancellation_locs")]
9595
if cases_with_cancel:
@@ -98,7 +98,7 @@ def _emit_github_summary(results: list, n_samples: int):
9898
"> Subtraction of nearly-equal values loses leading significant digits. A double carries "
9999
"~**16 significant digits** (53 bits); each entry shows how many that subtraction throws away "
100100
"(worst case, a lower bound). Losing ~8 digits halves your accuracy; losing ~13+ leaves only "
101-
"single-precision trust. Site *count* is not severity one site losing many digits outweighs "
101+
"single-precision trust. Site *count* is not severity - one site losing many digits outweighs "
102102
"many mild ones.\n"
103103
)
104104
for r in cases_with_cancel:
@@ -108,17 +108,17 @@ def _emit_github_summary(results: list, n_samples: int):
108108
ordered = sorted(sites, key=lambda e: (-e["bits"], e["where"]))
109109
if ordered:
110110
w = ordered[0]
111-
md.append(f"**`{r['name']}`** {len(ordered)} site(s); worst loses {w['bits'] / math.log2(10):.0f} of ~16 digits\n")
111+
md.append(f"**`{r['name']}`** - {len(ordered)} site(s); worst loses >= {w['bits'] / math.log2(10):.0f} of ~16 digits\n")
112112
for e in ordered[:15]:
113113
lost = e["bits"] / math.log2(10)
114-
ambiguous = f" _{e['macro']}-expanded, may represent multiple instances_" if e["macro"] else ""
115-
md.append(f"- ** {lost:.0f} digits lost** (~{_digits_left(e['bits']):.0f} of 16 left) `{e['where']}`{ambiguous}")
114+
ambiguous = f" - _{e['macro']}-expanded, may represent multiple instances_" if e["macro"] else ""
115+
md.append(f"- **>= {lost:.0f} digits lost** (~{_digits_left(e['bits']):.0f} of 16 left) - `{e['where']}`{ambiguous}")
116116
footer = _more_md(len(ordered), 15, "site(s)")
117117
if footer:
118118
md.append(footer)
119119
md.append("")
120120

121-
# VPREC sweep one column per mantissa-bit level showing the L∞ deviation at
121+
# VPREC sweep - one column per mantissa-bit level showing the Linf deviation at
122122
# that reduced precision ("crash" = run diverged/failed; dash = not measured).
123123
if any(r["vprec"] for r in results):
124124
_labels = {52: "52b", 23: "23b", 16: "16b", 10: "10b"}
@@ -133,7 +133,7 @@ def _emit_github_summary(results: list, n_samples: int):
133133
for b in VPREC_MANTISSA_BITS:
134134
d = vmap.get(b)
135135
if d is None:
136-
cols.append("")
136+
cols.append("-")
137137
elif d == float("inf"):
138138
cols.append("crash")
139139
else:
@@ -146,7 +146,7 @@ def _emit_github_summary(results: list, n_samples: int):
146146
if cases_with_fmax:
147147
md.append("### Float32 overflow sites (check\\_max\\_float)\n")
148148
for r in cases_with_fmax:
149-
md.append(f"**`{r['name']}`** {len(r['float_max_locs'])} site(s)\n")
149+
md.append(f"**`{r['name']}`** - {len(r['float_max_locs'])} site(s)\n")
150150
for fname, lineno in r["float_max_locs"][:10]:
151151
md.append(f"- `{fname}:{lineno}`")
152152
footer = _more_md(len(r["float_max_locs"]), 10, "site(s)")

0 commit comments

Comments
 (0)