Skip to content

fix(tot): cascade eq_api_init in tot_init (#209)#211

Merged
k-yoshimi merged 3 commits into
developfrom
l7b-ii-eq-init-cascade
May 20, 2026
Merged

fix(tot): cascade eq_api_init in tot_init (#209)#211
k-yoshimi merged 3 commits into
developfrom
l7b-ii-eq-init-cascade

Conversation

@k-yoshimi
Copy link
Copy Markdown
Owner

@k-yoshimi k-yoshimi commented May 20, 2026

Summary

Closes #209. tot_api_init now cascades eq_api_init (and tot_api_finalize cascades eq_api_finalize) so direct ctypes callers can drive eq_set_param / eq_run immediately after tot_init() — no extra eq_init() round trip.

Option A from the issue body. Stays within the k-yoshimi-led C-ABI boundary per feedback_fortran_refactor_needs_lead_signoff.md (no Fortran structural refactor of eq/tr/etc.).

What's in the PR (2 commits)

  • 427f5972 fix(tot): cascade eq_api_init in tot_init — the core change. tot_api.f90: USE eq_api (init/finalize hooks only), add g_eq_present flag, call eq_api_init() first in init (mirrors binary lifecycle order pl→eq→tr→…), call eq_api_finalize() last in finalize (reverse). Rollback paths on per-module init failure also tear down eq. New regression test + Layer-C smoke cleanup.
  • e6765344 fix(tot): wire eq deps into static check + harden test fork isolation — review-feedback follow-up. tot/Makefile adds ../eq/eq_state.o + ../eq/eq_api.o to TOT_L6_API_OBJ and tot_api.o's prereq list so the static make tot_api_check chain (CI's tot_api_check_all step) can compile + link cleanly. tot/tot_api.h doc updated. New test gets a pytestmark = [pytest.mark.forked] so it self-enforces fork isolation locally (CI uses --forked globally).

Why the cascade

eq_api keeps its own module-local g_initialized flag (eq/eq_api.f90:71) separate from tot_api's. Pre-this-PR, tot_api_init's call chain (tr_api_init → equnit::eq_init) brought up eq's COMMON state but did NOT flip eq_api's flag. A direct-ctypes caller running tot_init() → eq_set_param("MODELG", 3.0) hit EQ_ERR_NOT_INIT (2). The Phase 2b Layer-C smoke (test_mono_bpsd_smoke.py) had to call lib.eq_init() explicitly as a workaround. This PR removes that workaround.

Tests

  • New: python/totlib/tests/test_tot_init_eq_api_cascade.py — regression on BOTH default libtotapi.so (TOTLIB_PATH) and libtotapi_mono.so (MONO_LIB_PATH). Asserts eq_set_param returns EQ_OK (0) after tot_init() with no explicit eq_init(). Module-level pytest.mark.forked for per-test fork isolation.
  • Updated: python/totlib/tests/test_mono_bpsd_smoke.py::test_bpsd_round_trip — explicit lib.eq_init() call removed; the test now implicitly serves as a second regression guard.
  • 1e-10 equivalence (totlib/tests/test_equivalence.py) preserved.
  • Full local pytest --forked run: 155/156 pass. The 1 fail (test_close_raises_exception_group_when_multiple_modules_fail) is a pre-existing Python 3.10 ExceptionGroup compat issue — unrelated; reproduces on develop HEAD baseline.

Reviewer findings (in-house + Codex, both passes)

  • HIGH: none.
  • MED (Codex, first pass): tot/Makefile static-check chain didn't include eq objects — fixed in e6765344.
  • MED (in-house, first pass): g_eq_present is decorative until L-7 state aggregation lands — accepted as-is (matches existing g_tr_present style).
  • LOWs (header doc + pytest-forked enforcement) — fixed in e6765344.
  • Second pass: both reviewers SHIP IT.

Out of scope (carried over)

Test plan

  • CI green on python-tests.yml (totlib + eq + tot_api_check_all)
  • Bugbot completes with no remaining HIGH/MED
  • Mono Linux job green (verifies the cascade in the mono image)

🤖 Generated with Claude Code


Note

Medium Risk
Touches orchestrator lifecycle by adding eq_api_init/eq_api_finalize to tot_init/tot_finalize and adjusting rollback order, which could affect initialization/finalization behavior across modules if ordering assumptions exist.

Overview
tot_init() now cascades eq_api_init() (and tot_finalize() cascades eq_api_finalize()) so callers can use the eq C ABI (eq_set_param/eq_run) immediately after tot_init() without a separate eq_init() call.

Build/test coverage was updated to match: the tot_api_check static-link path now includes eq API/state objects in the tot/Makefile, the mono BPSD smoke test drops the explicit eq_init() workaround, and a new fork-isolated regression test asserts the cascade works for both libtotapi.so and libtotapi_mono.so.

Reviewed by Cursor Bugbot for commit bda3a75. Bugbot is set up for automated code reviews on this repo. Configure here.

k-yoshimi and others added 3 commits May 20, 2026 23:11
Before this change, tot_api_init brought up tr/ti/fp/wr but NOT eq_api.
eq_api maintains its own module-local g_initialized flag (eq/eq_api.f90:71)
separate from tot_api's. Direct ctypes callers chaining `tot_init() ->
eq_set_param(...)` would hit EQ_ERR_NOT_INIT unless they also explicitly
called eq_init() (which the Phase 2b Layer-C smoke test had to).

Option A from #209: tot_api_init now cascades eq_api_init alongside the
other per-module API inits, in binary-lifecycle order (eq first, then
tr/ti/fp/wr). tot_api_finalize mirrors this with eq_api_finalize in
reverse order. Per the existing tot_api.f90 header comment, the second
equnit_eq_init call (from tr_api_init's CALL eq_init) is idempotent —
so the cascade has no behavioral side-effect on the existing
per-module path.

The change is C-ABI extension only — no structural refactor of any
Fortran module — and stays within the k-yoshimi-led boundary per
feedback_fortran_refactor_needs_lead_signoff.md.

Tests
- python/totlib/tests/test_tot_init_eq_api_cascade.py (new): regression
  on both default libtotapi.so and libtotapi_mono.so. Asserts
  eq_set_param after tot_init returns EQ_OK without an explicit
  eq_init().
- python/totlib/tests/test_mono_bpsd_smoke.py::test_bpsd_round_trip:
  the now-redundant explicit lib.eq_init() call is removed, leaving
  the smoke test itself as a second regression guard.
- 1e-10 equivalence (totlib/tests/test_equivalence.py) preserved.

Closes #209.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…#209)

Follow-up to 427f597 addressing reviewer findings:

1. tot/Makefile: tot_api.f90 now USEs eq_api, but TOT_L6_API_OBJ
   listed only tr/ti/fp/wrx state+api objects. Add ../eq/eq_state.o
   and ../eq/eq_api.o so the static `make tot_api_check` chain (used
   by CI's tot_api_check_all step after `make -C tot libs` bootstraps
   the non-PIC tree) can compile + link cleanly. Also add explicit
   delegated build rules `../eq/eq_state.o:` and `../eq/eq_api.o:`
   mirroring the existing tr/ti/fp/wrx delegations, and add
   ../eq/libeq.a + ../eq/{eq_state.o,eq_api.o} to tot_api.o's
   prerequisite list so make's topological order matches. The PIC
   path (libtotapi.so / libtotapi_mono.so) was already correct since
   it links against ../eq/libeqapi.so. (Codex retrospective MED.)

2. tot/tot_api.h: header docstring updated to mention eq_init in the
   fan-out list. (Both reviewers LOW; consumer-facing source-of-truth.)

3. python/totlib/tests/test_tot_init_eq_api_cascade.py: add a
   module-level `pytestmark = [pytest.mark.forked]` so the file
   self-documents the per-test fork-isolation requirement and gets
   forked even on a local run that forgets the `--forked` CLI flag.
   eq_api's g_initialized is module-level (eq/eq_api.f90:71); a prior
   in-process test flipping it to TRUE could let this test pass
   spuriously without isolation. (Codex retrospective LOW.)

No behavioral change to the cascade itself.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
CI link of tests/c_abi/test_smoke fails on develop because eq_api.o
references eq_common_get_grid_counts_ / eq_common_get_scalars_ / etc.
which live in eq_api_common.f. That helper is in eq's OBJ_API but
NOT bundled into libeq.a, so the previously-added ../eq/eq_api.o
entry in TOT_L6_API_OBJ (commit e676534) needs eq_api_common.o
alongside it.

Add ../eq/eq_api_common.o to TOT_L6_API_OBJ + delegated build rule.
Verified locally with `make -C tot libs && make -C tot GFLIBS=
tot_api_check` (CI's no-graphics build mode) — exits 0.

The PIC paths (libtotapi.so / libtotapi_mono.so) are unaffected:
they link against ../eq/libeqapi.so where eq_api_common.o is already
co-linked via eq's OBJ_LIBEQAPI = OBJS_PIC + OBJ_API_PIC + stubs.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@k-yoshimi
Copy link
Copy Markdown
Owner Author

@cursor review

Copy link
Copy Markdown

@cursor cursor Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Bugbot reviewed your changes and found no new issues!

Comment @cursor review or bugbot run to trigger another review on this PR

Reviewed by Cursor Bugbot for commit bda3a75. Configure here.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant