Source code for symforce.ops.interfaces.storage

# ----------------------------------------------------------------------------
# SymForce - Copyright 2022, Skydio, Inc.
# This source code is under the Apache 2.0 license found in the LICENSE file.
# ----------------------------------------------------------------------------

import symforce.internal.symbolic as sf
from symforce import ops
from symforce import typing as T


[docs]class Storage: """ Interface for objects that implement the storage concept. Because this class is registered using :class:`symforce.ops.impl.class_storage_ops.ClassStorageOps` (see bottom of this file), any object that inherits from ``Storage`` and that implements the functions defined in this class can be used with the StorageOps concept. E.g. calling:: ops.StorageOps.storage_dim(my_obj) will return the same result as ``my_obj.storage_dim()`` if ``my_obj`` inherits from this class. """ # Type that represents this or any subclasses StorageT = T.TypeVar("StorageT", bound="Storage")
[docs] @classmethod def storage_dim(cls) -> int: """ Dimension of underlying storage """ raise NotImplementedError()
[docs] def __repr__(self: StorageT) -> str: """ String representation of this type. """ raise NotImplementedError()
[docs] def to_storage(self: StorageT) -> T.List[T.Scalar]: """ Flat list representation of the underlying storage, length of :meth:`storage_dim`. This is used purely for plumbing, it is NOT like a tangent space. """ raise NotImplementedError()
[docs] @classmethod def from_storage(cls: T.Type[StorageT], elements: T.Sequence[T.Scalar]) -> StorageT: """ Construct from a flat list representation. Opposite of :meth:`to_storage`. """ raise NotImplementedError()
[docs] def __eq__(self: StorageT, other: T.Any) -> bool: """ Returns exact equality between self and other. """ if not isinstance(self, other.__class__): return False self_list, other_list = self.to_storage(), other.to_storage() if not len(self_list) == len(other_list): return False if not all(s == o for s, o in zip(self_list, other_list)): return False return True
[docs] def subs(self: StorageT, *args: T.Any, **kwargs: T.Any) -> StorageT: """ Substitute given values of each scalar element into a new instance. """ return ops.StorageOps.subs(self, *args, **kwargs)
# TODO(hayk): Way to get sf.simplify to work on these types directly?
[docs] def simplify(self: StorageT) -> StorageT: """ Simplify each scalar element into a new instance. """ return self.from_storage(sf.simplify(sf.sympy.Matrix(self.to_storage())))
[docs] @classmethod def symbolic(cls: T.Type[StorageT], name: str, **kwargs: T.Any) -> StorageT: """ Construct a symbolic element with the given name prefix. Kwargs are forwarded to :class:`sf.Symbol <symforce.symbolic.Symbol>` (for example, sympy assumptions). """ return cls.from_storage( [sf.Symbol(f"{name}_{i}", **kwargs) for i in range(cls.storage_dim())] )
[docs] def evalf(self: StorageT) -> StorageT: """ Numerical evaluation. """ return self.from_storage([ops.StorageOps.evalf(e) for e in self.to_storage()])
[docs] def __hash__(self) -> int: """ Hash this object in immutable form, by combining all their scalar hashes. NOTE(hayk, nathan): This is somewhat dangerous because we don't always guarantee that Storage objects are immutable (e.g. sf.Matrix). If you add this object as a key to a dict, modify it, and access the dict, it will show up as another key because it breaks the abstraction that an object will maintain the same hash over its lifetime. """ return tuple(self.to_storage()).__hash__()
from ..impl.class_storage_ops import ClassStorageOps ops.StorageOps.register(Storage, ClassStorageOps)