Skip to content

Make the assumptions system extensible by downstream libraries - #2371

Open
jessegrabowski wants to merge 8 commits into
pymc-devs:mainfrom
jessegrabowski:assumption-extension
Open

Make the assumptions system extensible by downstream libraries#2371
jessegrabowski wants to merge 8 commits into
pymc-devs:mainfrom
jessegrabowski:assumption-extension

Conversation

@jessegrabowski

Copy link
Copy Markdown
Member

Downstream libraries can define their own assumption keys now, and both the built-in and downstream ones can be set and queried off the key object.

Declaring and checking assumptions

from pytensor.assumptions import SYMMETRIC, assume

x = assume(x, symmetric=True)   # unchanged
x = SYMMETRIC.assume(x)         # same thing, off the key
SYMMETRIC.holds(x)              # True

holds builds a throwaway FunctionGraph per call; pass one in (SYMMETRIC.holds(x, fgraph)) when asking about several variables of the same graph.

Adding your own assumption

Constructing an AssumptionKey registers it, which is all it takes for assume(), debugprint, and the drain rewrite to pick it up. register_matrix_property_rules(key) hands it the built-in trailing-two-axes propagation; a key with its own algebra registers rules the way the built-in properties do.

# before
from pytensor.tensor.rewriting.assumptions import _KEY_BY_NAME

TIME_VARYING = AssumptionKey("time_varying", "tv")
register_assumption(TIME_VARYING, SpecifyAssumptions)(specify_assumption_rule)
_KEY_BY_NAME[TIME_VARYING.name] = TIME_VARYING  # else the drain rewrite KeyErrors

def declare_time_varying(x):
    return SpecifyAssumptions({TIME_VARYING.name: FactState.TRUE})(x)

def is_time_varying(x):
    fgraph = FunctionGraph(outputs=[x], clone=False)
    return check_assumption(fgraph, x, TIME_VARYING)

# after
TIME_VARYING = AssumptionKey("time_varying", "tv")

x = TIME_VARYING.assume(x)      # or assume(x, time_varying=True)
TIME_VARYING.holds(x)

SpecifyAssumptions takes keys, not names

SpecifyAssumptions stores (AssumptionKey, FactState) pairs rather than (str, FactState), so SpecifyAssumptions({"diagonal": ...}) now raises with a pointer to assume(). Holding a key implies it is registered, which makes an unregistered assumption unrepresentable instead of something the drain rewrite has to check for at compile time. AssumptionKey.__reduce__ keeps that true across pickling, where the default dataclass path would skip __init__ and restore a key with no rules installed.

unique_indices no longer inherits the matrix rules

The trailing-two-axes rules were registered for every key rather than just the matrix ones. An Alloc broadcast therefore claimed unique_indices still held on an array where every entry repeats, and that fact exists to license lifting an index earlier.

Rules declared with register_universal_assumption install onto keys created after the declaration, so a key defined by a downstream library is no longer silently skipped by the modules that previously looped over a frozen ALL_KEYS tuple at import time.
assume() keeps its named arguments for the built-in properties so they stay documented and typo-checked, and accepts registered extension keys through **assumptions.
Holding a key implies it is registered, so a graph can no longer carry a declaration the system cannot resolve and the drain rewrite needs no lookup. AssumptionKey.__reduce__ keeps that true across pickling, where the default dataclass path would skip __init__ and restore a key with no rules installed.
The trailing-two-axes rules were registered for every key, so Alloc broadcasting an index array claimed the result still had distinct entries while duplicating every one of them -- a fact that exists to license lifting an index earlier.
A key wanting all but one of the rules registers its own with prepend=True, since rules are tried in registration order until one answers.
@jessegrabowski jessegrabowski added bug Something isn't working enhancement New feature or request assumptions labels Aug 23, 2026
@review-notebook-app

Copy link
Copy Markdown

Check out this pull request on  ReviewNB

See visual diffs & provide feedback on Jupyter Notebooks.


Powered by ReviewNB

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

assumptions bug Something isn't working enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant