qten.abstracts
Module reference for qten.abstracts.
abstracts
Shared abstract protocols and dispatch helpers used across QTen.
This module defines small mixins that describe common behavior without tying it to a specific tensor, symbolic, or geometry implementation. The abstractions are intended for implementers of QTen objects: they define the public contracts for operator multimethods, immutable update workflows, dual/base relationships, functional dispatch, span membership, ray representatives, and explicit type conversion.
Repository usage
Concrete modules such as qten.geometries,
qten.symbolics, and qten.linalg inherit
from these protocols to share public behavior while documenting their
domain-specific semantics on the concrete classes.
Design convention
These base classes document generic mechanics only. Concrete classes should document the mathematical meaning of each operation, valid operand types, returned object types, and any domain-specific validation errors.
UpdatableType
module-attribute
UpdatableType = TypeVar('UpdatableType', bound='Updatable')
BaseType
module-attribute
BaseType = TypeVar('BaseType')
Operable
dataclass
Operable()
Bases: ABC
Mixin defining the symbolic operator protocol used across QTen.
Operable provides the shared multimethod-based
arithmetic, comparison, and logical operator surface used by symbolic
objects throughout the codebase. Concrete subclasses opt into this protocol
by registering implementations for the relevant dunder methods on the class
or on cooperating types.
Supported operator families
Operable defines dispatch points for:
- containment via
in, - arithmetic operators such as
+,-, unary-,*,@,/,//, and**, - equality and ordered comparisons,
- logical
&and|, - reflected arithmetic for
+,-,*, and/.
Implementer workflow
Each operator method is a multimethod.multimethod
dispatch point. Concrete modules register implementations on those dispatch
points instead of overriding every dunder method on each subclass. For
example, an implementing module can define addition for two concrete types
with:
@Operable.__add__.register
def _(a: MyType, b: MyType) -> MyType:
...
After registration, normal Python syntax such as a + b calls
Operable.__add__, and multimethod selects the registered implementation
from the runtime argument types. The registration should describe the
concrete operand types and return a value that preserves the concrete type's
invariants.
Reflected operators
Reflected helpers such as __radd__ call back into the corresponding
Operable multimethod with the operands reversed. This lets one
registration support both direct and reflected syntax when the registered
type signature matches the reversed operands.
Design notes
- The base implementations intentionally raise
NotImplementedErrorso unsupported combinations fail loudly. - Reflected helpers such as
__radd__delegate back into the same multimethod registry, keeping dispatch symmetric. - Public API docs for concrete operator behavior should generally live on the concrete type, not on this abstract mixin.
Updatable
Bases: ABC, Generic[UpdatableType]
Protocol for immutable-style objects that can produce updated copies.
Subclasses implement _updated() to construct the new object, while
update()
enforces common safety rules around object identity and dataclass fields
with init=False.
Implementer workflow
- Implement
_updated(**kwargs)with the same semantic keyword names users should pass toupdate(). - Return a new object instead of mutating and returning
self. - For dataclasses, do not manually copy
init=Falsecache fields in_updated(). The publicupdate()wrapper copies those fields when the returned object has the same runtime type.
Use cases
Updatable is useful for immutable value objects that want a single public
update entry point while preserving invariants enforced by their
constructor or custom update implementation.
update
update(**kwargs) -> UpdatableType
Return an updated instance of this object.
This method delegates the construction of the updated object to
_updated(), then enforces common safety and dataclass consistency
rules:
_updated()must return a new object, notself.- If both
selfand the returned object are dataclasses of the same runtime type, fields withinit=Falseare copied fromselfto the returned object.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
**kwargs
|
Any
|
Keyword arguments forwarded to |
{}
|
Returns:
| Type | Description |
|---|---|
UpdatableType
|
A new instance representing the updated state. |
Raises:
| Type | Description |
|---|---|
RuntimeError
|
If |
Source code in src/qten/abstracts.py
236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 | |
HasDual
Bases: ABC
Protocol for objects with a domain-specific dual representation.
The meaning of "dual" is defined by the concrete domain. Geometry classes
use it for direct/reciprocal lattice pairs, while symbolic or linear
algebra objects may use it for paired spaces or representations. The
property should return the corresponding object without mutating self.
dual
abstractmethod
property
dual
Return the dual object associated with this instance.
Returns:
| Type | Description |
|---|---|
Any
|
Dual representation of this object. Concrete subclasses should return a value of the domain-specific dual type. |
HasBase
Bases: Generic[BaseType], ABC
An object that is expressed in a specific base (basis/coordinate system).
"Base" here means the same thing you would mean for a vector: a basis (or basis-like structure) that defines how the object's representation is written. Examples include a vector's basis, a lattice/affine space's basis, a function expanded in a basis of functions, or an operator expressed in a particular coordinate frame.
The key idea is that the mathematical object is the same, but its
representation depends on the base. Implementations should therefore
provide rebase(...) to return a new equivalent object expressed in a new
base, without mutating the original.
Implementer workflow
base()should return the current representation context.rebase(new_base)should change only the representation, not the underlying mathematical object.- Incompatible target bases should fail with a clear error, usually
ValueError.
Examples:
An offset vector can be rebased from one affine space to another by changing its coordinate column while preserving the same Cartesian vector.
base
abstractmethod
base() -> BaseType
Return the base (basis/coordinate system) this object is currently expressed in.
This should be a lightweight, stable descriptor of the representation context
(e.g., a basis matrix, lattice, coordinate frame, or function basis). The
returned base is used by rebase(...) to construct an equivalent object in a
new base, so implementations should not mutate internal state and should
prefer returning an immutable or effectively immutable object.
Returns:
| Type | Description |
|---|---|
BaseType
|
Base object that defines this instance's current representation. |
Source code in src/qten/abstracts.py
338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 | |
rebase
abstractmethod
rebase(new_base: BaseType) -> HasBase[BaseType]
Return an equivalent object expressed in new_base.
Implementations must preserve the underlying mathematical object while
changing only its representation. This method should be pure: do not
mutate self or new_base. Prefer returning a new instance, even if
the base is unchanged; if you choose to return self for identical
bases, document that behavior and ensure immutability.
new_base is expected to be compatible with the object. If it is not,
raise a clear error, typically ValueError. Do not silently coerce
incompatible bases.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
new_base
|
BaseType
|
Target base for the returned representation. |
required |
Returns:
| Type | Description |
|---|---|
HasBase[BaseType]
|
Equivalent object expressed in |
Raises:
| Type | Description |
|---|---|
ValueError
|
If |
Source code in src/qten/abstracts.py
356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 | |
AbstractKet
Bases: Generic[_InnerProductType], ABC
The base class for all ket-like objects that the inner product is defined via <bra|ket> syntax.
The _InnerProductType type parameter describes the value returned by the
inner product between this ket and another ket-like object.
Implementations decide whether the returned value is a scalar, symbolic expression, tensor, or another domain-specific object. The abstract interface only records that two values of the same ket type can be paired.
ket
abstractmethod
ket(another: Self) -> _InnerProductType
Return the inner product mapping between this ket and another ket.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
another
|
Self
|
Other ket-like object to pair with this ket. |
required |
Returns:
| Type | Description |
|---|---|
_InnerProductType
|
Inner-product value or mapping defined by the concrete ket type. |
Source code in src/qten/abstracts.py
404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 | |
Functional
Bases: ABC
Registry-dispatched callable object for operations on runtime values.
A Functional instance acts like a callable operator. Implementations are
registered by decorating functions with
register(obj_type). When the
functional is called, dispatch searches both:
- the runtime type of the input object, and
- the runtime type of the functional object itself.
This makes it possible to define operation families where subclasses inherit registrations from base functionals while still supporting specific overrides.
Registration workflow
- Define a concrete
Functionalsubclass. - Register one or more implementations with
register(obj_type). - Call the functional instance with an object, or call
invoke(obj)explicitly. - The most specific registered implementation is invoked.
A typical registration looks like:
class MyTransform(Functional):
...
@MyTransform.register(MyObject)
def _(transform: MyTransform, obj: MyObject) -> MyObject:
...
After registration, transform(obj) and transform.invoke(obj) both call
the registered function. The first argument passed to the function is the
functional instance itself, and the second argument is the runtime object.
Dispatch behavior
Dispatch is based on (type(obj), type(self)). If an exact registration is
absent, the resolver walks the object MRO and the functional MRO until it
finds an inherited match. This allows a registration on a base functional
class to act as a fallback for subclasses.
Caching
Resolved runtime pairs are cached in _resolved_methods. Whenever a new
registration is added, stale cache entries affected by that object type are
invalidated automatically.
register
classmethod
register(obj_type: type)
Register a function defining the action of the Functional on a specific object type.
This method returns a decorator. The decorated function should accept
the functional instance as its first argument and an object of
obj_type as its second argument. Any keyword arguments passed to
invoke() are forwarded to the
decorated function.
Dispatch is resolved at call time via MRO, so only the exact
(obj_type, cls) key is stored here. Resolution later searches both:
- the MRO of the runtime object type,
- the MRO of the runtime functional type.
This means registrations on a functional superclass are inherited by subclass functionals unless a more specific registration overrides them.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
obj_type
|
type
|
The type of object the function applies to. |
required |
Returns:
| Type | Description |
|---|---|
Callable
|
A decorator that registers the function for the specified object type. |
Examples:
@MyFunctional.register(MyObject)
def _(functional: MyFunctional, obj: MyObject) -> MyObject:
...
Source code in src/qten/abstracts.py
488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 | |
get_applicable_types
staticmethod
get_applicable_types() -> tuple[type, ...]
Get all object types that can be applied by this Functional.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
cls
|
Type[Functional]
|
Functional class whose direct registrations should be inspected. |
required |
Returns:
| Type | Description |
|---|---|
Tuple[Type, ...]
|
A tuple of all registered object types that this |
Source code in src/qten/abstracts.py
570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 | |
allows
allows(obj: Any) -> bool
Check if this Functional can be applied on the given object.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
obj
|
Any
|
The object to check for applicability. |
required |
Returns:
| Type | Description |
|---|---|
bool
|
True if this |
Notes
Applicability is checked using the same inherited dispatch rules as
invoke(): both the object's MRO
and the functional-class MRO are searched.
Source code in src/qten/abstracts.py
591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 | |
invoke
invoke(obj: Any, **kwargs) -> Any
Apply this functional to obj using registered multimethod dispatch.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
obj
|
Any
|
Runtime object to dispatch on. |
required |
**kwargs
|
Any
|
Additional keyword arguments forwarded to the resolved implementation. |
{}
|
Returns:
| Type | Description |
|---|---|
Any
|
Result produced by the resolved registered method. |
Raises:
| Type | Description |
|---|---|
NotImplementedError
|
If no registration exists for the runtime pair
|
Source code in src/qten/abstracts.py
613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 | |
__call__
__call__(obj: Any, **kwargs) -> Any
Apply this functional to obj.
This is a thin wrapper around invoke().
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
obj
|
Any
|
Runtime object to dispatch on. |
required |
**kwargs
|
Any
|
Additional keyword arguments forwarded to the resolved implementation. |
{}
|
Returns:
| Type | Description |
|---|---|
Any
|
Result produced by the resolved registered method. |
Raises:
| Type | Description |
|---|---|
NotImplementedError
|
If no registration exists for the runtime pair after MRO fallback. |
See Also
invoke(obj, **kwargs)
Full dispatch method used by this call wrapper.
Source code in src/qten/abstracts.py
648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 | |
Span
dataclass
Span()
Bases: Operable, ABC, Generic[_ElementType]
Protocol for objects representing the span of a finite element set.
The specific meaning of "span" depends on the context. For example, in a vector space, the span of a set of vectors is the set of all linear combinations of those vectors. In a topological space, the span of a set of points might be the smallest closed set containing those points.
Spans participate in Operable membership using Python's in protocol:
x in span dispatches to span.__contains__(x).
Containment behavior
Spanqueries, compared byelements().Convertiblequeries, converted totype(self)before comparison.
The _ElementType type variable represents the type of elements that define the span.
Registration mechanism
Span does not define a custom registry. It participates in the
Operable multimethod registry by registering
containment implementations on Operable.__contains__. That means normal
Python syntax such as subspan in span is routed through the same
multimethod dispatch used by other operator protocols.
Implementer workflow
Concrete spans must return a stable tuple from
elements(). The default containment logic
assumes that these elements are hashable and can be compared for equality.
elements
abstractmethod
elements() -> tuple[_ElementType, ...]
Return the elements contained in this span.
Returns:
| Type | Description |
|---|---|
Tuple[_ElementType, ...]
|
Immutable tuple of elements represented by this span. |
Source code in src/qten/abstracts.py
717 718 719 720 721 722 723 724 725 726 727 | |
HasRays
Bases: ABC
Protocol for objects that can choose a canonical ray representative.
In projective settings, multiple values can differ by an overall scalar
while representing the same ray. Concrete implementations define the
canonicalization convention used by rays().
Implementer workflow
The returned representative should be deterministic and should preserve the object's mathematical ray. Implementations commonly normalize signs, phases, or scalar coefficients.
rays
abstractmethod
rays() -> Self
Return a canonical representative of this object's ray.
Returns:
| Type | Description |
|---|---|
Self
|
Canonical representative chosen by the concrete implementation. |
Source code in src/qten/abstracts.py
758 759 760 761 762 763 764 765 766 767 768 | |
Convertible
Bases: ABC
Mixin for objects that support explicit type-to-type conversion.
Convertible provides a small global conversion registry. A source class
registers conversion functions with
add_conversion(TargetType),
and instances call convert(TargetType)
to obtain the requested representation.
The registry key is (source_type, destination_type). Conversion lookup
first checks the concrete source type exactly, then walks source supertypes
in MRO order. Resolved parent conversions are cached for the concrete source
type, so repeated conversions avoid the MRO scan.
Use cases
This protocol is useful when two QTen objects represent the same mathematical data in different public forms, but the conversion should be explicit rather than automatic.
Notes
Conversion functions should not mutate the source object. They should
return a new object or an immutable view appropriate for the target type.
If no conversion function is found, conversion fails with
NotImplementedError.
Registration mechanism
add_conversion(T) returns a decorator for registering one directed
conversion from the class receiving the decorator to T. The decorated
function should accept one source instance and return one destination
instance.
For example, @Source.add_conversion(Target) stores the decorated function
under the exact key (Source, Target). Later, source.convert(Target)
first checks (type(source), Target). If that exact key is absent, lookup
walks the source type's MRO and tries (SourceParent, Target),
(SourceGrandparent, Target), and so on.
The destination type is not relaxed during lookup. A conversion registered
for (Source, BaseTarget) is not used by source.convert(DerivedTarget),
and a conversion registered for (Source, DerivedTarget) is not used by
source.convert(BaseTarget). Register each destination type explicitly when
those conversions should be available.
add_conversion
classmethod
add_conversion(
T: type[B],
) -> Callable[[Callable[[A], B]], Callable[[A], B]]
Register a conversion from cls to T.
The decorated function is stored under (cls, T). When an instance of
cls later calls convert(T),
that function is used to produce the converted object.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
T
|
Type[B]
|
Destination type produced by the registered conversion function. |
required |
Returns:
| Type | Description |
|---|---|
Callable[[Callable[[A], B]], Callable[[A], B]]
|
Decorator that stores the conversion function and returns it unchanged. |
Examples:
@MyType.add_conversion(TargetType)
def to_target(x: MyType) -> TargetType:
...
Source code in src/qten/abstracts.py
824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 | |
convert
convert(T: type[B]) -> B
Convert this instance to the requested target type.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
T
|
Type[B]
|
Destination type to convert into. |
required |
Returns:
| Type | Description |
|---|---|
B
|
Converted object produced by the registered conversion function. |
Raises:
| Type | Description |
|---|---|
NotImplementedError
|
If no conversion function has been registered for
|
Source code in src/qten/abstracts.py
861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 | |