Skip to content

EigenVectors

Represents the eigenvectors of a square matrix, one per column.

Parameters

FieldTypeDescription
datanp.ndarray | list | tupleArray-like data of shape (N, N): square, non-empty, dtype in the allowlist below. Columns are the eigenvectors.
atolfloat | NoneKeyword-only. Absolute tolerance for the orthonormality check (V^T V ≈ I, or V^H V ≈ I for complex dtypes). Defaults to 100 * eps(dtype) * sqrt(N).
rtolfloatKeyword-only. Relative tolerance for the same check. Defaults to 0.0, since the reference matrix is the identity and rtol has no effect on its zero entries.

Raises

ExceptionCondition
TypeErrordata can't be converted into a uniform array (e.g. ragged nested lists)
ValueErrorThe resulting dtype is unsupported (see Supported Dtypes below), data isn't 2-D, isn't square, is empty (0x0), contains a non-finite value (NaN/Inf), or its columns aren't orthonormal within atol/rtol

Supported Dtypes

Inherited unchanged from Array:

CategoryDtypes
Booleanbool
Signed / unsigned integerint8int64, uint8uint64
Floating-pointfloat16, float32, float64
Complexcomplex64, complex128

object, structured, and datetime64 dtypes are not supported.

Attributes

AttributeTypeDescription
datanp.ndarrayThe wrapped (N, N) array; column k is the k-th eigenvector. Reading it returns a copy. Note: assigning .data = ... re-validates only the dtype (inherited from Array) — it does not re-check squareness, finiteness, or orthonormality, so it's possible to leave the instance holding non-orthonormal columns after such an assignment. Use the constructor, copy(), or coerce() when full re-validation matters.
shapetuple[int, int](N, N).
ndimintAlways 2.
dtypenp.dtypeDtype of the array as constructed — not forced to float32.
sizeintN * N.

Methods

MethodDescription
to_numpy(copy=True)Returns the array as np.ndarray. Pass copy=False to get a reference to the internal array instead — faster for large data, but mutating it mutates the EigenVectors too.
copy()Returns a new EigenVectors with an independent copy of the data, re-validated through the constructor using its default atol/rtol.
EigenVectors.coerce(value)Returns value unchanged if it's already an EigenVectors; otherwise wraps a np.ndarray/list/tuple into one via the constructor (full validation, default atol/rtol). Raises TypeError for any other input.
check_orthonormality(data, atol=None, rtol=0.0)Checks whether an arbitrary square (N, N) array's columns are orthonormal — not limited to self's own data. Raises ValueError if data isn't square 2-D or is empty.
is_orthonormal(atol=None, rtol=0.0)Checks this instance's own data for orthonormality within the given tolerance (delegates to check_orthonormality).

Operators

OperationBehavior
v1 == v2True only if other is also an EigenVectors with the same dtype, shape, and values (NaN counts as equal to NaN here). False for anything else.
len(v)N (number of rows, equal to the number of columns).
np.asarray(v)Works directly — NumPy functions accept an EigenVectors in place of an np.ndarray. Always returns a copy; use to_numpy(copy=False) for a zero-copy view.
hash(v)Not supported — an EigenVectors can't be used as a dict key or set member.

Visualization

datatypes.visualize(eigenvectors, entity_path=...) has a dedicated handler: for N == 3 it logs the columns as 3D basis arrows (rr.Arrows3D) from the world origin, colored red/green/blue and labeled e0/e1/e2; for N == 2 it logs 2D basis arrows (rr.Arrows2D) the same way, colored red/green and labeled e0/e1. For any other N it falls back to text (rr.TextLog), since there's no spatial rendering for a higher-dimensional basis. No label handler is registered for user-supplied labels — passing label to visualize() has no effect for EigenVectors; the e0/e1/e2 axis labels are generated internally and always shown.

Example

python
"""Demonstrates the Telekinesis EigenVectors datatype."""

import time

import numpy as np
from loguru import logger
import rerun as rr

from telekinesis import datatypes

def eigenvectors_example():
    """Demonstrate creation, inspection, visualization, update, tolerance relaxation, eigen-relation verification, and serialization."""

    # ======================= Create ============================================
    matrix = np.array([[2.0, 1.0], [1.0, 2.0]], dtype=np.float64)
    w, v = np.linalg.eigh(matrix)
    eigenvectors = datatypes.EigenVectors(v)

    logger.info(f"Input eigenvectors:\n{v}")
    logger.info(f"Original EigenVectors: {eigenvectors}")

    # ======================= Inspect ===========================================
    data = eigenvectors.data
    shape = eigenvectors.shape
    size = eigenvectors.size
    dtype = eigenvectors.dtype
    ndim = eigenvectors.ndim
    numpy_array = eigenvectors.to_numpy()
    eigenvectors_copy = eigenvectors.copy()

    logger.info(
        f"shape={shape}, "
        f"size={size}, "
        f"ndim={ndim}, "
        f"dtype={dtype}"
    )
    logger.info(f"Data:\n{data}")
    logger.info(f"NumPy array: {numpy_array}")
    logger.info(f"Copied EigenVectors: {eigenvectors_copy}")

    # ======================= Visualize =========================================
    rr.init("eigenvectors_example", spawn=True)
    datatypes.visualize(eigenvectors, entity_path="/EigenVectors", label="Original EigenVectors")

    # ======================= Update ============================================
    _, new_v = np.linalg.eigh(np.array([[5.0, 2.0], [2.0, 5.0]], dtype=np.float64))
    eigenvectors.data = new_v
    logger.info(f"Updated EigenVectors: {eigenvectors}")
    datatypes.visualize(
        eigenvectors, entity_path="/EigenVectors/updated", label="Updated EigenVectors"
    )

    # ======================= Tolerance =========================================
    relaxed = datatypes.EigenVectors(v + 1e-10, atol=1e-6)
    logger.info(f"Noisy input accepted with atol=1e-6: {relaxed}")

    # ======================= Verify ============================================
    for k in range(eigenvectors.shape[1]):
        v_k = data[:, k]
        lhs = matrix @ v_k
        rhs = w[k] * v_k
        logger.info(f"Eigenvector {k} satisfies A @ v == w * v: {np.allclose(lhs, rhs)}")

    # ======================= Serialize / Deserialize ===========================
    start = time.perf_counter()
    serialized = datatypes.serialize(eigenvectors)
    serialization_ms = (time.perf_counter() - start) * 1000

    start = time.perf_counter()
    deserialized = datatypes.deserialize(serialized)["param_0"]
    deserialization_ms = (time.perf_counter() - start) * 1000

    logger.info(f"Deserialized EigenVectors: {deserialized}")
    logger.info(f"Round-trip successful: {deserialized == eigenvectors}")
    logger.info(f"Serialization time: {serialization_ms:.3f} ms")
    logger.info(f"Deserialization time: {deserialization_ms:.3f} ms")


if __name__ == "__main__":
    eigenvectors_example()

See also EigenValues and Array.