Use logging and tqdm; fix numpy where-without-out warning #4

Merged
drawblank merged 3 commits from modernize-logging-and-progress into master 2026-08-17 12:53:20 +00:00
Contributor

Three commits: the numpy fix, the logging/tqdm work, and the version bump to 1.2.0.

Logging

Output went through bare print() — stage banners prefixed with #, a dot per completed energy, and a \r percentage counter in the Penn ELF loop. None of it could be silenced, redirected, or levelled.

Stage messages are now logger.info on per-module loggers, configured in main() with a --log-level flag (DEBUG…CRITICAL, default INFO). The checksum-failure message became logger.warning, since it reports a corrupted download rather than routine progress. The commented-out debug print in endf_reader._print_debug is now a live logger.debug, reachable via --log-level DEBUG — parsing Si takes 14 ms, so the per-record level check is irrelevant.

Progress bars

tqdm on the four long loops. Two details that matter:

  • The work is wrapped in logging_redirect_tqdm(), which routes log records through tqdm.write(). Without it, a message arriving mid-loop interleaves with the bar's \r redraws and tears the line.
  • Bars pass disable=None, so tqdm suppresses itself when stderr is not a tty. Redirected and CI runs produce clean log lines with no bar frames — which is what the compile job wants.

Confirmed in a real terminal:

22:41:31 INFO  cstool.apps.cstool: Computing elastic total cross-sections and iCDFs.
elastic iCDF: 100%|██████████████████████████| 128/128 [00:02<00:00, 45.93E/s]
22:41:34 INFO  cstool.apps.cstool: Computing inelastic-Kieft total cross-sections and iCDFs.
Ashley iCDF: 100%|███████████████████████████| 128/128 [00:01<00:00, 99.68E/s]
...
Penn ELF: 100%|████████████████████████████| 1200/1200 [01:27<00:00, 13.78w/s]
Penn iCDF: 100%|█████████████████████████████| 128/128 [00:25<00:00,  4.99E/s]

numpy warning

np.log(y, where=y>0) without out= leaves masked entries uninitialized, which numpy warns about twice per compile. The next line overwrote every masked entry with -inf, so nothing was actually reading garbage — the code just relied on that. Now out=np.full(shape, -inf) makes the intent explicit and the fill replaces the separate assignment.

Verified equivalent: old and new expressions give bit-identical arrays over 200 random inputs containing zeros and negatives.

Verification

  • Full silicon.yaml compile, exit=0, twice
  • Redirected run: 9 log lines, no dots, no percent counters, zero numpy warnings
  • TTY run on aluminium.yaml: all four bars render and complete cleanly
  • uv run basedpyright: 0 errors, 0 warnings, 0 notes

Behaviour change

The version banner is logged rather than printed, so it no longer precedes --help output and is silenced by --log-level WARNING.

Three commits: the numpy fix, the logging/tqdm work, and the version bump to 1.2.0. ## Logging Output went through bare `print()` — stage banners prefixed with `#`, a dot per completed energy, and a `\r` percentage counter in the Penn ELF loop. None of it could be silenced, redirected, or levelled. Stage messages are now `logger.info` on per-module loggers, configured in `main()` with a `--log-level` flag (DEBUG…CRITICAL, default INFO). The checksum-failure message became `logger.warning`, since it reports a corrupted download rather than routine progress. The commented-out debug print in `endf_reader._print_debug` is now a live `logger.debug`, reachable via `--log-level DEBUG` — parsing Si takes 14 ms, so the per-record level check is irrelevant. ## Progress bars tqdm on the four long loops. Two details that matter: - The work is wrapped in `logging_redirect_tqdm()`, which routes log records through `tqdm.write()`. Without it, a message arriving mid-loop interleaves with the bar's `\r` redraws and tears the line. - Bars pass `disable=None`, so tqdm suppresses itself when stderr is not a tty. Redirected and CI runs produce clean log lines with no bar frames — which is what the `compile` job wants. Confirmed in a real terminal: ``` 22:41:31 INFO cstool.apps.cstool: Computing elastic total cross-sections and iCDFs. elastic iCDF: 100%|██████████████████████████| 128/128 [00:02<00:00, 45.93E/s] 22:41:34 INFO cstool.apps.cstool: Computing inelastic-Kieft total cross-sections and iCDFs. Ashley iCDF: 100%|███████████████████████████| 128/128 [00:01<00:00, 99.68E/s] ... Penn ELF: 100%|████████████████████████████| 1200/1200 [01:27<00:00, 13.78w/s] Penn iCDF: 100%|█████████████████████████████| 128/128 [00:25<00:00, 4.99E/s] ``` ## numpy warning `np.log(y, where=y>0)` without `out=` leaves masked entries uninitialized, which numpy warns about twice per compile. The next line overwrote every masked entry with `-inf`, so nothing was actually reading garbage — the code just relied on that. Now `out=np.full(shape, -inf)` makes the intent explicit and the fill replaces the separate assignment. Verified equivalent: old and new expressions give bit-identical arrays over 200 random inputs containing zeros and negatives. ## Verification - Full `silicon.yaml` compile, `exit=0`, twice - Redirected run: 9 log lines, no dots, no percent counters, **zero numpy warnings** - TTY run on `aluminium.yaml`: all four bars render and complete cleanly - `uv run basedpyright`: 0 errors, 0 warnings, 0 notes ## Behaviour change The version banner is logged rather than printed, so it no longer precedes `--help` output and is silenced by `--log-level WARNING`.
np.log(y, where=y>0) without out= leaves the masked entries uninitialized,
which numpy warns about:

    UserWarning: 'where' used without 'out', expect uninitialized memory in
    output. If this is intentional, use out=None.

The following line overwrote every masked entry with -inf, so nothing was
actually reading garbage, but the code relied on that and the warning fired
twice per compile. Passing out=np.full(shape, -inf) makes the intent
explicit and lets the fill replace the separate assignment.

Verified equivalent: the old and new expressions produce bit-identical
arrays over 200 random inputs containing zeros and negative values.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Output went through bare print(): stage banners prefixed with '#', a dot per
completed energy, and a '\r' percentage counter in the Penn ELF loop. None of
it could be silenced, redirected, or given a level.

Stage messages are now logger.info on per-module loggers, configured in
main() with a --log-level flag (DEBUG..CRITICAL, default INFO). The four
long loops get tqdm bars: elastic iCDF, Ashley iCDF, Penn ELF and Penn iCDF.

Two details worth noting:

- The work is wrapped in logging_redirect_tqdm(), which routes log records
  through tqdm.write(). Without it a message arriving mid-loop interleaves
  with the bar's own \r redraws and tears the line.
- Bars pass disable=None, so tqdm suppresses itself when stderr is not a
  tty. A redirected or CI run therefore produces clean log lines with no
  bar frames, which is what the Forgejo compile job wants.

The checksum-failure message becomes logger.warning rather than info; it
reports a corrupted download, not routine progress. The commented-out debug
print in endf_reader._print_debug becomes a real logger.debug call, now
reachable with --log-level DEBUG; parsing Si takes 14 ms, so the
per-record level check does not matter.

Side effect: the version banner is logged rather than printed, so it no
longer precedes --help output and is silenced by --log-level WARNING.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Minor bump: adds the --log-level flag and tqdm progress bars.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
drawblank merged commit d9d564fd90 into master 2026-08-17 12:53:20 +00:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
science/cstool!4
No description provided.