Skip to content

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 NotImplementedError so 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 to update().
  • Return a new object instead of mutating and returning self.
  • For dataclasses, do not manually copy init=False cache fields in _updated(). The public update() 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, not self.
  • If both self and the returned object are dataclasses of the same runtime type, fields with init=False are copied from self to the returned object.

Parameters:

Name Type Description Default
**kwargs Any

Keyword arguments forwarded to _updated() to define the new state.

{}

Returns:

Type Description
UpdatableType

A new instance representing the updated state.

Raises:

Type Description
RuntimeError

If _updated() returns self instead of a new instance.

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
def update(self, **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, not `self`.
    - If both `self` and the returned object are dataclasses of the same
      runtime type, fields with `init=False` are copied from `self` to the
      returned object.

    Parameters
    ----------
    **kwargs : Any
        Keyword arguments forwarded to `_updated()` to define the new state.

    Returns
    -------
    UpdatableType
        A new instance representing the updated state.

    Raises
    ------
    RuntimeError
        If `_updated()` returns `self` instead of a new instance.
    """
    out = self._updated(**kwargs)
    if out is self:
        raise RuntimeError(
            f"{type(self).__name__}._updated() must not return self; return a new object."
        )
    if type(out) is type(self) and is_dataclass(self) and is_dataclass(out):
        for f in fields(self):
            if not f.init:
                object.__setattr__(out, f.name, getattr(self, f.name))
    return out

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
@abstractmethod
def base(self) -> 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(...)`][qten.abstracts.HasBase.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
    -------
    BaseType
        Base object that defines this instance's current representation.
    """
    raise NotImplementedError()

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

Raises:

Type Description
ValueError

If new_base is incompatible with this object's representation.

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
@abstractmethod
def rebase(self, 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
    ----------
    new_base : BaseType
        Target base for the returned representation.

    Returns
    -------
    HasBase[BaseType]
        Equivalent object expressed in `new_base`.

    Raises
    ------
    ValueError
        If `new_base` is incompatible with this object's representation.
    """
    raise NotImplementedError()

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
@abstractmethod
def ket(self, another: Self) -> _InnerProductType:
    """
    Return the inner product mapping between this ket and `another` ket.

    Parameters
    ----------
    another : Self
        Other ket-like object to pair with this ket.

    Returns
    -------
    _InnerProductType
        Inner-product value or mapping defined by the concrete ket type.
    """
    raise NotImplementedError()

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 Functional subclass.
  • 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
@classmethod
def register(cls, obj_type: type):
    """
    Register a function defining the action of the [`Functional`][qten.abstracts.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()`][qten.abstracts.Functional.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
    ----------
    obj_type : type
        The type of object the function applies to.

    Returns
    -------
    Callable
        A decorator that registers the function for the specified object type.

    Examples
    --------
    ```python
    @MyFunctional.register(MyObject)
    def _(functional: MyFunctional, obj: MyObject) -> MyObject:
        ...
    ```
    """

    def decorator(func: Callable):
        cls._registered_methods[(obj_type, cls)] = func
        cls._invalidate_resolved_methods(obj_type)
        return func

    return decorator

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 Functional can handle.

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
@staticmethod
def get_applicable_types(cls) -> Tuple[Type, ...]:
    """
    Get all object types that can be applied by this [`Functional`][qten.abstracts.Functional].

    Parameters
    ----------
    cls : Type[Functional]
        Functional class whose direct registrations should be inspected.

    Returns
    -------
    Tuple[Type, ...]
        A tuple of all registered object types that this [`Functional`][qten.abstracts.Functional] can handle.
    """
    types = set()
    for obj_type, functional_type in cls._registered_methods.keys():
        if functional_type is cls:
            types.add(obj_type)
    return tuple(types)

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 Functional can be applied on the object, False otherwise.

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
def allows(self, obj: Any) -> bool:
    """
    Check if this [`Functional`][qten.abstracts.Functional] can be applied on the given object.

    Parameters
    ----------
    obj : Any
        The object to check for applicability.

    Returns
    -------
    bool
        True if this [`Functional`][qten.abstracts.Functional] can be applied on the object, False otherwise.

    Notes
    -----
    Applicability is checked using the same inherited dispatch rules as
    [`invoke()`][qten.abstracts.Functional.invoke]: both the object's MRO
    and the functional-class MRO are searched.
    """
    return self._resolve_method(type(obj), type(self)) is not None

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 (type(obj), type(self)) after MRO fallback.

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
def invoke(self, obj: Any, **kwargs) -> Any:
    """
    Apply this functional to `obj` using registered multimethod dispatch.

    Parameters
    ----------
    obj : Any
        Runtime object to dispatch on.
    **kwargs : Any
        Additional keyword arguments forwarded to the resolved
        implementation.

    Returns
    -------
    Any
        Result produced by the resolved registered method.

    Raises
    ------
    NotImplementedError
        If no registration exists for the runtime pair
        `(type(obj), type(self))` after MRO fallback.
    """
    functional_class = type(self)
    obj_class = type(obj)
    method = self._resolve_method(obj_class, functional_class)

    if method is None:
        raise NotImplementedError(
            f"No function registered for {obj_class.__name__} "
            f"with {functional_class.__name__}"
        )

    return method(self, obj, **kwargs)

__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
def __call__(self, obj: Any, **kwargs) -> Any:
    """
    Apply this functional to `obj`.

    This is a thin wrapper around [`invoke()`][qten.abstracts.Functional.invoke].

    Parameters
    ----------
    obj : Any
        Runtime object to dispatch on.
    **kwargs : Any
        Additional keyword arguments forwarded to the resolved
        implementation.

    Returns
    -------
    Any
        Result produced by the resolved registered method.

    Raises
    ------
    NotImplementedError
        If no registration exists for the runtime pair after MRO fallback.

    See Also
    --------
    [`invoke(obj, **kwargs)`][qten.abstracts.Functional.invoke]
        Full dispatch method used by this call wrapper.
    """
    return self.invoke(obj, **kwargs)

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

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
@abstractmethod
def elements(self) -> Tuple[_ElementType, ...]:
    """
    Return the elements contained in this span.

    Returns
    -------
    Tuple[_ElementType, ...]
        Immutable tuple of elements represented by this span.
    """
    pass

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
@abstractmethod
def rays(self) -> Self:
    """
    Return a canonical representative of this object's ray.

    Returns
    -------
    Self
        Canonical representative chosen by the concrete implementation.
    """
    raise NotImplementedError()

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
@classmethod
def add_conversion(
    cls: Type[A], 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)`][qten.abstracts.Convertible.convert],
    that function is used to produce the converted object.

    Parameters
    ----------
    T : Type[B]
        Destination type produced by the registered conversion function.

    Returns
    -------
    Callable[[Callable[[A], B]], Callable[[A], B]]
        Decorator that stores the conversion function and returns it
        unchanged.

    Examples
    --------
    ```python
    @MyType.add_conversion(TargetType)
    def to_target(x: MyType) -> TargetType:
        ...
    ```
    """

    def decorator(func: Callable[[A], B]) -> Callable[[A], B]:
        _type_conversion_table[(cls, T)] = cast(Callable[[Any], Any], func)
        return func

    return decorator

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 (type(self), T) or any source supertype via add_conversion().

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
@final
def convert(self, T: Type[B]) -> B:
    """
    Convert this instance to the requested target type.

    Parameters
    ----------
    T : Type[B]
        Destination type to convert into.

    Returns
    -------
    B
        Converted object produced by the registered conversion function.

    Raises
    ------
    NotImplementedError
        If no conversion function has been registered for
        `(type(self), T)` or any source supertype via
        [`add_conversion()`][qten.abstracts.Convertible.add_conversion].
    """
    source_type = type(self)
    table_get = _type_conversion_table.get

    convertor = table_get((source_type, T))
    if convertor is None:
        for super_type in source_type.__mro__[1:]:
            convertor = table_get((super_type, T))
            if convertor is not None:
                # Cache resolved parent conversion under the concrete source type.
                _type_conversion_table[(source_type, T)] = convertor
                break

    if convertor is None:
        raise NotImplementedError(
            f"No conversion from {source_type.__name__} to {T.__name__}!"
        )
    return cast(Callable[["Convertible"], B], convertor)(self)