Release Notes

0.1.0

New Features

  • Added two functions for inspecting the engines of an acceleration domain: available_engines() returns the names of the engines that can be enabled for a domain, and active_engine() returns the name of the engine it has resolved to. Both accept an AccelerationDomain or the name of one.

    from coheriq import active_engine, available_engines
    
    available_engines("mylib")  # ('reference', 'jax-accelerated', 'mpi')
    active_engine("mylib")      # None, until an engine is resolved
    

    available_engines() reports engines that have already been registered as well as those advertised through the coheriq.engines.<domain> entry point group but not yet imported, because enable_engine() accepts either. Advertised plugins are deliberately not imported in order to answer the question, so a name being listed means that enable_engine() will attempt it, not that it is guaranteed to load.

    active_engine() returns None while the domain has not resolved an engine yet, meaning a different one can still be enabled, and otherwise the name of the engine that is active. None therefore means “not decided yet”, while REFERENCE means “decided on the reference implementation”, so a caller can distinguish the window in which it can still act from the state in which the decision has already been made.

    Neither function resolves or freezes the domain’s implementation, so an engine can still be enabled afterwards. This is a deliberate contrast with calling an acceleration candidate: asking what is active never commits the domain to an answer.

  • A domain’s own implementations are now addressable as an engine named REFERENCE (the string "reference"). Every domain has this engine; it is the one in effect until another is enabled, and it is the root that every other engine for that domain inherits from.

    import coheriq
    
    # Pin the reference implementation, regardless of MYLIB_ENGINE.
    coheriq.enable_engine("mylib", coheriq.REFERENCE)
    

    Enabling it explicitly is worth doing even though it is the default, because an explicit call takes precedence over the domain’s environment variable. Declining to choose and choosing the reference implementation reach the same state, but only the latter is immune to being overridden from the environment – which matters when comparing against the reference implementation, debugging a suspected engine bug, or pinning known-good behavior in a test.

    Pinning the reference implementation is a forward transition into the same settled state that a first call would reach, so it does not weaken the one-way activation contract: it forecloses the other engines, and it cannot undo an engine that is already active.

    Because "reference" names an engine that every domain owns, it cannot be claimed by a third-party engine; AccelerationEngine rejects it. Names beginning with "reference" are refused on the same grounds, reserving the prefix against a possible future design in which a domain versions its reference implementation.

  • enable_engine(), available_engines(), active_engine(), and AccelerationEngine now accept the module a domain is named after, wherever they accepted a domain name. This is the recommended way to identify a domain.

    import coheriq
    import mylib
    
    coheriq.enable_engine(mylib, "fast-engine")
    

    A domain is registered as a side effect of importing the library that defines it, so the string form requires that the import has already happened. That precondition is invisible in the call itself, and violating it produced a puzzling report that no such domain was registered. Passing the module makes the precondition unstatable, since a module cannot be named before it has been imported.

    The string form continues to work and is equivalent once the library has been imported. It remains the only option where the module form is unavailable, such as in a single file that defines a domain and enables an engine for it.

Upgrade Notes

  • A domain’s name should now be the __name__ of the module it belongs to, so that users can pass that module in place of the name. This is the name the library is imported under, which is not always the name it is distributed under: a project published as my-lib and imported as my_lib should have a domain name of my_lib. Constructing the domain in a submodule is still fine: the name need only be an importable module.

    Earlier guidance described the name loosely as the name of the library, without tying it to an importable module. For most domains the two already coincide and nothing needs to change.

    AccelerationDomain now issues a CoheriqDomainWarning when constructed with a name that does not resolve to an importable module, because users of such a domain cannot activate an engine by passing its module. The convention is advisory: a domain whose name violates it works exactly as before, and a domain defined in a script, a notebook, or a test has no package to be named after. The check runs at construction, where the name is passed, and never imports the name it checks.

  • The example domain in example/ is now named coheriq_ragged_domain rather than ragged, to follow the convention above. Its engine entry point group is correspondingly coheriq.engines.coheriq_ragged_domain. The RAGGED_ENGINE environment variable is unchanged, since the environment prefix is independent of the domain name.

Other Notes

  • Added support for Python 3.15. No code changes were necessary, so older releases are expected to work on Python 3.15 too.

  • Coheriq warnings now have their own hierarchy, so they can be filtered as a group: CoheriqWarning derives from UserWarning, and CoheriqDomainWarning derives from it.

    import warnings
    
    import coheriq
    
    warnings.filterwarnings("ignore", category=coheriq.CoheriqWarning)