orthonym.assembly.memo#

Note

Internal API. Names and behaviour may change between releases.

Scoped-per-call memoization infrastructure (M1, Levers C1 + E).

A byte-identity-preserving cache whose lifetime is exactly ONE top-level naming call. The engine re-names the same substituent fragments many times inside one molecule – the dispatch cascade, the _retry_cascade_on_gate_rejection re-entries and the recursive Tier-4 cascade all re-derive the same fragment prefixes – so a per-call cache removes that redundant work without changing any emitted name. See internal notes Part 2.

Design invariants (each is load-bearing for the 0-wrong / byte-identity contract):

  • Scope is per top-level call. A contextvars.ContextVar holds the cache dict; it is created at the outermost naming entry and torn down on exit, so cross-molecule staleness, tier drift and env-flag changes are structurally impossible and memory stays bounded.

  • Fail-open, never fail-wrong. With no active scope (or ORTHONYM_MEMO=off) every call recomputes. A cache MISS can never corrupt output; only a false HIT could, and that is exactly what a COMPLETE key and verify mode prevent.

  • ``verify`` mode is the continuous completeness check. It always recomputes and raises:class:MemoMismatch the instant a stored value disagrees with a fresh one for the same key – turning an incomplete key into a loud failure rather than a silent wrong name.

Dependency-light on purpose (contextvars + os and the provenance context variables).

orthonym.assembly.memo.verify_mismatch_count()#

Number of verify-mode same-key-different-result events since the last reset.

orthonym.assembly.memo.reset_verify_mismatches()#

Clear the verify-mode mismatch log (call before a validation corpus run).

orthonym.assembly.memo.pin_promotion_var = <ContextVar name='orthonym_pin_promotion' default=False>#

True while the default (PIN) tier re-names a molecule it could not name, with the best-effort ring-substituent producers admitted under the PIN-vocabulary guard (rules.pin_vocabulary.promote_at_pin_tier; Orthonym.name). Values computed in that re-run can differ from the first run’s for the same key, so every cache below keys them apart: neither run is ever served the other’s value, and the first run stays byte-identical to the engine without the re-run.

orthonym.assembly.memo.prefixes_apart_var = <ContextVar name='orthonym_prefixes_apart' default=False>#

True while an isotope-labelled molecule’s skeleton is named with its identical N- and C-prefixes cited apart (composition_primitives. identical_prefixes_cited_apart). Its values differ from the grouped run’s for the same key, so the caches below key them apart, as for the promotion re-run.

orthonym.assembly.memo.book_forms_var = <ContextVar name='orthonym_book_forms' default=True>#

True (the default) while the writers spell the Blue Book’s forms (assembly.book_prefixes.book_forms_enabled); book_prefixes.mechanical_forms sets it False for a run that wants the spellings before N5. Some of those runs share one memo scope with the book spellings (the terminal-fragment writer’s mechanical pre-pass, the assembly tier’s von Baeyer gate), and their values differ for the same key, so the caches below key them apart, as for the promotion re-run.

Type:

Roadmap N5

orthonym.assembly.memo.kept_forms_var = <ContextVar name='orthonym_kept_forms' default=frozenset()>#

The kinds of book spelling kept inside book_prefixes.mechanical_forms(keep_forms=...): every writer gives its mechanical spelling except those of the kept kinds (‘ring’: the single-ring names of rules.monocycle_forms). A middle rung of the retry that follows a book-spelled name no round trip confirmed: one failing spelling must not take the kept kinds with it.

orthonym.assembly.memo.dropped_forms_var = <ContextVar name='orthonym_dropped_forms' default=frozenset()>#

every writer gives its book spelling except those of the dropped kinds (‘chain’: the N/O group prefixes and ‘cyano’). The rung that follows a book-spelled name no round trip confirmed when the failing spelling is one kind: the other book spellings stay.

Type:

The kinds of book spelling switched OFF inside book_prefixes.book_forms_minus(kinds)

orthonym.assembly.memo.suffix_nitrogen_hydron_var = <ContextVar name='orthonym_suffix_nitrogen_hydron' default=False>#

True while a neutral amine is named for the cation its caller makes of it by a hydron on the nitrogen (locant_omission.suffix_nitrogen_gains_hydron): the locant of a group on a one-carbon parent is decided as if the nitrogen held that hydrogen, so a value computed in that scope differs from the plain one for the same key and the caches below key them apart, as for the promotion re-run.

exception orthonym.assembly.memo.MemoMismatch(namespace, key, stored, fresh)#

Bases: Exception

Raised in verify mode when a cached result disagrees with a fresh recompute for the same key – i.e. the cache key is INCOMPLETE and a hit would have shipped a wrong name. Carries the offending namespace/key and both values so the missing key component can be identified.

orthonym.assembly.memo.push_scope()#

Open a memo scope for this (and nested) calls, unless one is already open.

Returns a ContextVar token when THIS call created the scope (the caller owns teardown and must pass the token to:func:pop_scope), or None when a scope already existed (a nested re-entry – it shares the outer cache and must NOT reset it).

A scope opened inside:func:seeded_scopes starts with the entries it was given (see there), and one opened inside:func:recording_scopes is handed to the recorder.

orthonym.assembly.memo.pop_scope(token)#

Tear down the scope created by the matching:func:push_scope. A None token (nested re-entry) is a no-op, so only the outermost frame tears down.

orthonym.assembly.memo.fresh_scope()#

An EMPTY copy of the open scope for a computation whose ambient declarations differ from the enclosing one’s (a scope’s caches are keyed on the assumption that they stay what they were when it opened: locant_omission.isotopic_naming_scope). Returns a token for:func:pop_sandbox; with no scope open nothing changes (no caching, as before).

orthonym.assembly.memo.recording_scopes()#

Collect every top-level memo scope (see:func:push_scope) opened in the body, in the order opened, in the list it yields. The scopes stay readable after their naming has ended.

orthonym.assembly.memo.shareable_entries(scopes, into=None)#

The entries of scopes that another naming of the molecule may start with (see _SHAREABLE_NAMESPACES), added to into: the stored values, and for each the side record the nested-call memo replays on a hit (side_put).

orthonym.assembly.memo.seeded_scopes(entries)#

Every top-level memo scope opened in the body starts with a copy of entries (shareable_entries()).

orthonym.assembly.memo.push_sandbox()#

Open a throwaway COPY of the current scope for a speculative computation.

Reads still hit what the enclosing scope already holds; writes land in the copy and are discarded by:func:pop_sandbox, so the enclosing naming can never be served a value the speculation computed in its own context. With no scope open, nothing changes (no caching, as before). Always returns a token for:func:pop_sandbox.

orthonym.assembly.memo.pop_sandbox(token)#

Discard the sandbox opened by the matching:func:push_sandbox.

orthonym.assembly.memo.side_get(namespace, key)#

Return the scope-bound side value for (namespace, key), or None.

orthonym.assembly.memo.side_put(namespace, key, value)#

Store a scope-bound side value for (namespace, key) (no verify compare). A no-op with no active scope (fail-open: no scope -> no hit -> no replay).

orthonym.assembly.memo.process_cache_stats()#

{"size", "maxsize", "hits", "misses"} for the process-wide pure cache.

orthonym.assembly.memo.clear_process_cache()#

Drop every process-wide entry (tests; a worker that changes engine flags).

orthonym.assembly.memo.pure_cache_or_compute(namespace, key, compute_fn)#

Memoise a value that is a pure function of key for the LIFE OF THE PROCESS.

Same contract as:func:cache_or_compute except that the entry outlives the naming scope. Bounded LRU (ORTHONYM_PROCESS_CACHE entries, default 20,000; 0 disables). ORTHONYM_MEMO=off/verify bypass it exactly as they bypass the scope cache. The caller guarantees the key determines the value.

orthonym.assembly.memo.cache_or_compute(namespace, key, compute_fn)#

Return the memoized value for (namespace, key), computing it via compute_fn on a miss.

  • ORTHONYM_MEMO=off OR no active scope -> always compute_fn (fail-open; never caches).

  • on -> return the cached value if present, else compute + store + return.

  • verify -> ALWAYS recompute; if a value is already stored for the key and differs, raise:class:MemoMismatch; then store + return the fresh value.