Skip to content

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).