Product tensor basics — compute the geometric product via tensor contraction¶
Keywords: tensor · product tensor · rotor · einsum · E3
This example shows how to
- build a full-algebra product tensor for GP in E3,
- contract it with two random multivectors via
numpy.einsum, - compare the result against the standard
A * Boperand.
Run¶
Source¶
Code¶
#!/usr/bin/env python3
# SPDX-License-Identifier: Apache-2.0
# Copyright 2021 Christian Perwass
r"""Product tensor basics — compute the geometric product *via* tensor contraction.
This example shows how to
1. build a full-algebra product tensor for GP in E3,
2. contract it with two random multivectors via :func:`numpy.einsum`,
3. compare the result against the standard ``A * B`` operand.
Run
----
.. code-block:: bash
uv run python py/examples/ga/tensor/rotor_01.py
Keywords: tensor, product tensor, rotor, einsum, E3
"""
import numpy as np
import pytanga as pt
from typing import Any
from pytanga.algebra import EInv, to_rotor
from pytanga.geometry import RndMV
from pytanga.tensor.convert import from_tensor, to_tensor
from pytanga.tensor.ops import contract
from pytanga.tensor.product import product_tensor
def _blade_count(mask: Any) -> int:
"""Number of blades on a tensor axis (counting axes carry no blade mask)."""
return 0 if mask is None else len(mask)
def main() -> None:
# --- 1. create algebra -----------------------------------------------------
alg = pt.Algebra(3, 0, "float64")
print(f"Algebra dimension: {alg.algebra_dim} blades")
# --- 2. generate two random multivectors with random masks -----------------
rng = np.random.default_rng(2025)
# Rotor mask in G3
rot_mask = pt.BladeMask(alg, "1 + e12 + e13 + e23")
# Point mask in G3
point_mask = pt.BladeMask(alg, "e1 + e2 + e3")
# The product tensor for R * P, where R is a rotor and P is a point.
G_RP = product_tensor(rot_mask, point_mask, product=pt.EProduct.GP)
# The tensor shape is (8, 4, 4) because the rotor has 4 blades and the point has 4 blades.
print(
f" shape = {G_RP.shape} "
f"(|c_mask|={_blade_count(G_RP.masks[0])}, |a_mask|={_blade_count(G_RP.masks[1])}, "
f"|b_mask|={_blade_count(G_RP.masks[2])})"
)
# We now create the product tensor for R * P * ~R.
# The mask of the left element is the result mask of the previous product tensor,
# and the mask of the right element is the rotor mask.
# We also need to set the right involution to be the reverse, because we want to compute R * P * ~R.
# The result mask is the point mask, because we want to compute R * P * ~R,
# which is a point.
g_rp_mask = G_RP.masks[0]
assert g_rp_mask is not None # the rotor axis carries a blade mask
G_rpR = product_tensor(
g_rp_mask,
rot_mask,
c_mask=point_mask,
product=pt.EProduct.GP,
b_inv=EInv.REV,
)
print(
f" shape = {G_rpR.shape} "
f"(|c_mask|={_blade_count(G_rpR.masks[0])}, |a_mask|={_blade_count(G_rpR.masks[1])}, "
f"|b_mask|={_blade_count(G_rpR.masks[2])})"
)
# Let's now contract the two product tensors with one another.
# Inidices i and l are the indices for the rotor and j is the index for the point.
G_RPR = contract("kij,mkl->mijl", G_RP, G_rpR)
print(
f" shape = {G_RPR.shape} "
f"(|c_mask|={_blade_count(G_RPR.masks[0])}, |a_mask|={_blade_count(G_RPR.masks[1])}, "
f"|b_mask|={_blade_count(G_RPR.masks[2])}, |d_mask|={_blade_count(G_RPR.masks[3])})"
)
# Create a random rotor and a random point.
angle = rng.uniform(0, 2 * np.pi)
rotor = to_rotor(angle, bivec=alg("e1+e2+e3").complement())
print(f"\n Angle: {angle:.4f} rad")
print(f"\n Rotor: {rotor!s}")
point = RndMV(point_mask, [(-1.0, 1.0)] * len(point_mask))(rng)
rot_t = to_tensor(rotor, mask=rot_mask)
point_t = to_tensor(point, mask=point_mask)
# If we contract G_RPR with just the rotor, we obtain a rotation matrix for points.
G_rot = contract("mijl,i,l->mj", G_RPR, rot_t, rot_t)
print(
f" shape = {G_rot.shape} "
f"(|c_mask|={_blade_count(G_rot.masks[0])}, |a_mask|={_blade_count(G_rot.masks[1])}, "
)
# Now we can contract the rotation matrix with the point to obtain the rotated point.
rotated_point_t = contract("mj,j->m", G_rot, point_t)
rotated_point = from_tensor(rotated_point_t)
print(f"\n Original point: {point!s}")
print(f" Rotated point: {rotated_point!s}")
if __name__ == "__main__":
main()