Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .github/workflows/python-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -586,12 +586,16 @@ jobs:
# Re-uses the default libtotapi.so artifact built earlier in
# this job (the "Build default libtotapi.so" step landed in
# PR #207) for test_non_mono_so_raises.
# test_set_after_run.py (#253) is listed here as well so its
# MONO_LIB_PATH-gated fp / ti classes run on the mono image;
# its TestTotSetAfterRun class also runs in the main pytest step.
env:
MONO_LIB_PATH: ${{ github.workspace }}/tot/libtotapi_mono.so
TOTLIB_PATH: ${{ github.workspace }}/tot/libtotapi.so
PYTHONPATH: python
run: |
python -m pytest python/totlib/tests/test_mono_routing.py \
python/totlib/tests/test_set_after_run.py \
--forked --timeout=120 --timeout-method=signal -v

- name: Mono pipeline coupling (Phase 2c PR-B)
Expand Down
15 changes: 15 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,21 @@ The format is loosely based on [Keep a Changelog](https://keepachangelog.com/).

### Changed

- **tr / fp / ti libraries, `Tot` and `tot_mcp`: a parameter set after a
run re-preps the module, whoever set it (#253).** `tr_set_param` /
`fp_set_param` / `ti_set_param` used to clear the module's private
"prepared" flag themselves; tot's `tr:` / `fp:` / `ti:` dispatcher wrote
to the registries directly and left the flag standing, so after a run a
`Tot.set_param("tr:NSMAX", 3)` continued the old state with `tr_prep`
skipped and the #189 pre-run species check never fired. The registries
now count every successful set and `tr_run` / `fp_run` / `ti_run`
re-prep when the count moved since their last prep. Behaviour change for
`Tot` and `tot_mcp` only: a set after a run now restarts the module
(`NT` and `T` back to 0), which is what `trlib` / `fplib` / `tilib` and
`TotPipeline` have always done; a set the registry refused changes
nothing. `eq` has no prepared flag on `develop`; the same counter is the
natural precondition for `eq_save` (#256).

- **FP library: `fp_run` refuses `NRMAX` above `FP_MAX_NRMAX` (100).** Such a
run was made, and `fp_get_state` then returned rc=3: its results could be
read only from the log on unit 6 or from `fp_regress.dat`
Expand Down
28 changes: 18 additions & 10 deletions fp/fp_api.f90
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ MODULE fp_api
NRMAX, NSAMAX, NPMAX, NTHMAX, NTG2, TIMEFP, &
NTMAX, &
RNT, RWT, RTT, RJT, RPCT, RPWT
USE fp_param_registry, ONLY: fp_param_set, fp_param_set_str
USE fp_param_registry, ONLY: fp_param_set, fp_param_set_str, fp_param_generation
USE plinit, ONLY: pl_init
USE equnit, ONLY: eq_init
USE obinit, ONLY: ob_init
Expand All @@ -62,6 +62,9 @@ MODULE fp_api
! Lifecycle flags (module-scope state, single instance only at L-3).
LOGICAL, SAVE :: g_initialized = .FALSE.
LOGICAL, SAVE :: g_prepared = .FALSE.
! Registry mutation counter seen by the last successful prep (#253).
! -1 = never prepared. See fp_param_generation in the registry.
INTEGER, SAVE :: g_prep_generation = -1

CONTAINS

Expand Down Expand Up @@ -135,10 +138,10 @@ FUNCTION fp_api_set_param(name, value) RESULT(ierr) BIND(C, NAME="fp_set_param")
IF (fp_param_set(TRIM(fname), REAL(value, KIND=rkind)) /= 0) THEN
ierr = FP_ERR_INVALID
ELSE
! A user who changes a profile-shape-altering variable needs to
! re-run fp_prep; invalidate g_prepared so the next fp_run picks
! up the new parameters.
g_prepared = .FALSE.
! A successful set advances the registry's mutation counter;
! fp_run compares it with g_prep_generation and re-runs fp_prep
! (#253: the same holds for sets that reach the registry without
! passing through here, such as tot's "fp:" dispatcher).
ierr = FP_OK
END IF
END FUNCTION fp_api_set_param
Expand Down Expand Up @@ -179,7 +182,8 @@ FUNCTION fp_api_set_param_str(name, value) RESULT(ierr) BIND(C, NAME="fp_set_par
IF (fp_param_set_str(TRIM(fname), TRIM(fvalue)) /= 0) THEN
ierr = FP_ERR_INVALID
ELSE
g_prepared = .FALSE.
! The registry's mutation counter makes the next fp_run re-prep
! (see fp_api_set_param).
ierr = FP_OK
END IF
END FUNCTION fp_api_set_param_str
Expand Down Expand Up @@ -219,15 +223,18 @@ FUNCTION fp_api_run(ntmax_in) RESULT(ierr) BIND(C, NAME="fp_run")
! fp_prep allocates the FPCOMM arrays, sets up the velocity / radial
! mesh, and initializes the distribution function. It must run before
! the first fp_loop call and any time the caller updated a profile-
! shape-altering namelist variable (fp_api_set_param clears
! g_prepared).
IF (.NOT. g_prepared) THEN
! shape-altering namelist variable: the registry counts every
! successful set (fp_param_generation), from fp_api_set_param or
! from tot's "fp:" dispatcher alike (#253), and a changed count
! means re-prep.
IF (.NOT. g_prepared .OR. g_prep_generation /= fp_param_generation()) THEN
CALL fp_prep(prep_ierr)
IF (prep_ierr /= 0) THEN
ierr = FP_ERR_CALC_FAILED
RETURN
END IF
g_prepared = .TRUE.
g_prep_generation = fp_param_generation()
END IF

! Override NTMAX for this call so fp_loop steps exactly ntmax_in
Expand Down Expand Up @@ -301,7 +308,8 @@ FUNCTION fp_api_get_state(state) RESULT(ierr) BIND(C, NAME="fp_get_state")
! The arrays keep the extents of the last fp_prep (fp_allocate_ntg2
! gives all six the same shape (NRMAX,NSAMAX,NTG2M)), while NRMAX /
! NSAMAX are the current namelist values. fp_set_param on a count
! clears g_prepared without reallocating, so until the next fp_run
! advances the registry counter without reallocating, so until the
! next fp_run
! the two can disagree (#245). Copy MIN(current count, allocated
! extent) along each axis and report the copied extents, so the
! [nsamax][nrmax] slice the caller reads is exactly the data that
Expand Down
40 changes: 37 additions & 3 deletions fp/fp_param_registry.f90
Original file line number Diff line number Diff line change
Expand Up @@ -38,13 +38,47 @@ MODULE fp_param_registry
IMPLICIT NONE
PRIVATE
PUBLIC :: fp_param_set
PUBLIC :: fp_param_generation
PUBLIC :: fp_param_set_str
PUBLIC :: fp_param_check
PUBLIC :: parse_array_subscript_pub ! exported for unit-test only

INTEGER, SAVE :: g_generation = 0

CONTAINS

!-------------------------------------------------------------------
! Mutation counter (#253).
!
! Every successful set through this registry, whoever calls it
! (fp_api_set_param, tot's "fp:" dispatcher, a future caller),
! advances g_generation. fp_api compares the value it saw when it
! last ran fp_prep with the current one and re-preps on a change,
! so the "prepared" state cannot go stale behind the C ABI's back.
! A set that fails leaves the counter alone: nothing changed.
!-------------------------------------------------------------------
FUNCTION fp_param_generation() RESULT(gen)
INTEGER :: gen
gen = g_generation
END FUNCTION fp_param_generation

FUNCTION fp_param_set(name, value) RESULT(ierr)
CHARACTER(LEN=*), INTENT(IN) :: name
REAL(rkind), INTENT(IN) :: value
INTEGER :: ierr
ierr = fp_param_set_impl(name, value)
IF (ierr == 0) g_generation = g_generation + 1
END FUNCTION fp_param_set

FUNCTION fp_param_set_str(name, value) RESULT(ierr)
CHARACTER(LEN=*), INTENT(IN) :: name
CHARACTER(LEN=*), INTENT(IN) :: value
INTEGER :: ierr
ierr = fp_param_set_str_impl(name, value)
IF (ierr == 0) g_generation = g_generation + 1
END FUNCTION fp_param_set_str

FUNCTION fp_param_set_impl(name, value) RESULT(ierr)
CHARACTER(LEN=*), INTENT(IN) :: name
REAL(rkind), INTENT(IN) :: value
INTEGER :: ierr
Expand Down Expand Up @@ -156,7 +190,7 @@ FUNCTION fp_param_set(name, value) RESULT(ierr)
CASE DEFAULT
ierr = 1 ! unknown name
END SELECT
END FUNCTION fp_param_set
END FUNCTION fp_param_set_impl

!-------------------------------------------------------------------
! fp_param_check : the counts a run cannot be made with, checked
Expand Down Expand Up @@ -251,7 +285,7 @@ END FUNCTION fp_param_check
! KNAMEQ='eqdata' (from pl_init) is missing in cwd, so eq_load
! fails silently and downstream BESEKNX trips with NCALC=-2.
!-------------------------------------------------------------------
FUNCTION fp_param_set_str(name, value) RESULT(ierr)
FUNCTION fp_param_set_str_impl(name, value) RESULT(ierr)
CHARACTER(LEN=*), INTENT(IN) :: name
CHARACTER(LEN=*), INTENT(IN) :: value
INTEGER :: ierr
Expand All @@ -262,7 +296,7 @@ FUNCTION fp_param_set_str(name, value) RESULT(ierr)
CASE DEFAULT
ierr = 1 ! unknown string-valued name
END SELECT
END FUNCTION fp_param_set_str
END FUNCTION fp_param_set_str_impl

!-------------------------------------------------------------------
! Split "NAME" or "NAME[N]" into (base, idx).
Expand Down
4 changes: 2 additions & 2 deletions python/fplib/tests/test_state_extents.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@

The FPCOMM profile arrays (``RNT``, ``RWT``, ... in ``fp/fpcomm.f90``) keep
the extents of the last ``fp_prep``; ``NRMAX`` / ``NSAMAX`` are the current
namelist values. ``fp_set_param`` on either count clears ``g_prepared``
without reallocating, so until the next ``fp_run`` the two disagree, and
namelist values. ``fp_set_param`` on either count marks the module for
re-prep without reallocating, so until the next ``fp_run`` the two disagree, and
``fp_get_state`` used to copy ``nsa = 1..NSAMAX``, ``nr = 1..NRMAX`` out of
arrays that are smaller than that (a read past the allocation: an abort
under ``-fcheck=all``, garbage in the padding otherwise).
Expand Down
21 changes: 21 additions & 0 deletions python/totlib/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,27 @@ Set one numeric parameter. `name` MUST be `"<ns>:<bare>"`. Validation
runs Python-side first, so missing/unknown prefixes raise
`TotlibInvalidParamError` before any FFI call.

A set after a run restarts the module it addresses on that module's
next run: `tr:` / `fp:` / `ti:` parameters reach the module's registry,
which counts every successful set, and `tr_run` / `fp_run` / `ti_run`
re-run their `*_prep` (initial profiles, metric, BPSD state; `NT` and `T`
back to 0) when the count moved since their last prep. `Tot.run` drives
`tr_run` only (L-6), so for `tr:` that is the next `Tot.run`; for `fp:` /
`ti:` it is the next `fp_run` / `ti_run`, reached through `Fplib` /
`Tilib` on the mono image or through the C ABI. This is the same
semantics as `Trlib.set_param` / `TotPipeline.set_param` (#253). A set the
registry refuses (unknown name, bad index) changes nothing and the next
run continues. There is no "live" update of a running module through
`set_param`; a coupling input that should not restart the module is a
registry-level classification still to be designed.

The count is per registry. On the mono image several modules read the
same `plcomm` variables (`ti:RR` and `eq:RR` are one `RR`), and a set
through one module's registry does not move another module's count:
after `Tot.set_param("ti:RR", ...)`, `fp_run` does not re-prep. That is
the pre-existing shared-storage property of the mono image, not something
`set_param` can see; set each module's own parameters.

### `Tot.set_param_str(name, value) -> None`

Set one string parameter. At L-3, only the `tr:` and `eq:` namespaces
Expand Down
Loading
Loading