Closure registry¶
Every source-term closure — friction, heat transfer, catalyst, filter,
reaction kind — registers itself with a small central registry keyed by
(family, name). The registry is the single source of truth that the
TOML schema Literal[...] choices, the integer kind-codes stored on the
Pipe, and the builder's dispatch all agree against.
This page documents the read side — how to enumerate which closure kinds exist and which fields each one takes from Python. The full field tables (kind → required + optional fields, with units and defaults) are hand-written in Closures — reference table; this API lets you reach the same data programmatically.
Adding a closure is a multi-file change
register_closure is one link in a chain — a new closure kind also
touches the schema Literal, the builder dispatch, the case-IO
writer, and the closures-reference table, and drift-tests fail if any
link is missing. The narrative of that chain is in
Closures — reference table; this page is
the registry surface those pieces share.
Enumerating kinds and fields¶
from aeolus1d.closures import registry
registry.families() # ('friction', 'heat', 'catalyst', ...)
registry.names_in("friction") # ('none', 'darcy_const', 'colebrook', 'minor_loss')
registry.fields_for("reaction", "twc_brinkmeier") # the TOML fields it reads
registry.kinds_in("reaction") # tuple of ClosureKind records
aeolus1d.closures.registry.families ¶
families() -> Iterable[str]
The registered family names.
Inputs
none.
Returns:
a LIVE view of the registry's keys (dict_keys), not a snapshot:
it grows as families are registered, and adding a family while
iterating it raises RuntimeError. Wrap in tuple() to keep a
stable list.
Refuses:
none.
Edges:
the view is live (test_families_is_a_live_view).
aeolus1d.closures.registry.names_in ¶
names_in(family: str) -> tuple[str, ...]
All registered names for a family, in registration order.
Schema validation tests check that the dataclass's
Literal[...] arms match this list exactly.
Inputs
family: any string.
Returns:
a tuple of names in registration order; a fresh tuple.
Refuses:
none: an unknown family is NOT an error.
Edges:
an unknown family returns (): so does a real one whose module
has not been imported yet ("reaction" is empty until
closures.reaction_demo is), so a caller must import first
(test_a_family_is_empty_until_its_module_is_imported).
order is registration order (test_names_kinds_and_fields_keep_registration_order).
aeolus1d.closures.registry.kinds_in ¶
kinds_in(family: str) -> tuple[ClosureKind, ...]
All ClosureKind records for a family. Form-factory
code that needs per-kind metadata (fields list) iterates this.
Inputs
family: any string.
Returns:
a tuple of the frozen ClosureKind records, registration order.
Refuses:
none: an unknown family is not an error.
Edges:
an unknown family returns ()
(test_names_kinds_and_fields_keep_registration_order).
aeolus1d.closures.registry.fields_for ¶
fields_for(family: str, name: str) -> tuple[str, ...]
Schema fields consumed by one specific kind.
Inputs
family: a registered family. name: a kind registered in it.
Returns:
the stored fields tuple (the record's own, immutable).
Refuses:
KeyError (bare key) for an unknown family or name.
Edges:
an unknown kind raises rather than returning ()
(test_lookups_of_an_unknown_kind_raise_key_error).
aeolus1d.closures.registry.closure_kind_fields ¶
closure_kind_fields(family: str) -> dict[str, list[str]]
Build the KIND_FIELDS dict the schema dataclass exposes
to the form factory. Replaces hand-maintained ClassVar
dicts on each schema class. schema.PorousSpec.KIND_FIELDS =
closure_kind_fields("porous") is the migration pattern.
Inputs
family: any string.
Returns:
{name: [field, ...]} in registration order. The lists are
fresh copies: editing one does not touch the registry.
Refuses:
none: an unknown family gives {}, not an error.
Edges:
the returned lists are copies
(test_names_kinds_and_fields_keep_registration_order).
an unknown family gives an empty dict, silently
(test_a_family_is_empty_until_its_module_is_imported).
Kind ↔ code mapping¶
The Pipe stores each active closure as an integer kind-code (e.g.
friction_kind, heat_kind) for the numba kernels. These resolve names
to codes and back within a family.
aeolus1d.closures.registry.code_for_name ¶
code_for_name(family: str, name: str) -> int
The integer dispatch code for (family, name). Raises
KeyError for unknown names.
Inputs
family: a family that has been registered (its module imported). name: a kind registered in it.
Returns:
the dispatch integer.
Refuses:
KeyError for an unknown family or an unknown name. The message
is the bare missing key (KeyError: 'bogus'), not a sentence, and
does not say which of the two was missing. Never a default code.
Edges:
a name that is not registered raises; there is no silent default
(test_lookups_of_an_unknown_kind_raise_key_error).
an unregistered family raises too, e.g. before its module is
imported (test_a_family_is_empty_until_its_module_is_imported).
aeolus1d.closures.registry.name_for_code ¶
name_for_code(family: str, code: int) -> str
Reverse lookup. Useful when the writer wants to round-trip a saved integer back to its TOML name.
Inputs
family: a family key. code: a dispatch integer.
Returns:
the kind name registered at code (unique per family).
Refuses:
KeyError with a message naming the code and the family, for an
unknown family or a code no kind has.
Edges:
an unknown code, and an unknown family, both raise
(test_lookups_of_an_unknown_kind_raise_key_error).
the round trip name_for_code(f, code_for_name(f, n)) == n holds
(test_a_code_maps_back_to_its_name).
The registered record¶
Each (family, name) pair is a ClosureKind carrying its integer code
and field list. New closures register one via register_closure.
aeolus1d.closures.registry.ClosureKind
dataclass
¶
One registered closure kind.
family is the closure family ("friction" / "heat" /
…). name is the schema-level token ("darcy_const").
code is the Numba dispatch integer that Pipe.<family>_kind
is set to. fields lists the schema field names this kind
consumes — drives KIND_FIELDS for the inspector form
factory's per-kind field reveal.
aeolus1d.closures.registry.register_closure ¶
register_closure(
family: str,
name: str,
code: int,
fields: tuple[str, ...] = (),
) -> ClosureKind
Register a closure kind. Idempotent for re-import (the same (family, name, code, fields) tuple registered twice is a no-op); raises on conflict (same name with different code, or vice versa) so accidental drift fails loudly.
Inputs
family: the closure family key ("friction", "heat", ...); any
string, created on first use.
name: the schema-level kind token, unique within the family.
code: the dispatch integer, unique within the family (not checked
for type or range; Pipe.<family>_kind and the @njit chains
that read it are the caller's responsibility).
fields: the schema field names the kind consumes, as a TUPLE. A
list is stored as given, makes the record unhashable, and never
equals the same fields registered as a tuple.
Returns:
the ClosureKind now stored under (family, name); on an
identical re-registration, the ORIGINAL stored object. Mutates the
process-wide registry (module state), in registration order.
Refuses:
ValueError when (family, name) is already registered with a
different code or different fields, and when another name in the
same family already has code. The message names both records.
A refused call changes nothing.
Edges:
the same four values twice -> the first record, no change
(test_an_identical_re_registration_returns_the_first_record).
same name with another code, or with other fields -> refused
(test_a_conflicting_re_registration_is_refused_and_changes_nothing).
another name on a taken code -> refused; the same code in another
family is fine (test_a_code_collision_is_per_family).
fields as a list conflicts with the same fields as a tuple
(test_fields_must_be_a_tuple_to_re_register).