Rotation library Examples

The simcoon Rotation class inherits from scipy.spatial.transform.Rotation, so all scipy factory methods and operations are available directly. Plain scipy Rotation objects can be upgraded with from_scipy() or passed directly to C++ methods that accept rotations.

import numpy as np
from scipy.spatial.transform import Rotation as ScipyRotation
import simcoon as sim

Rotation API examples

These examples demonstrate the main rotation functions available in simcoon.

v = np.array([1.0, 0.0, 0.0])
m = np.eye(3)
angle = np.pi / 4  # 45 degrees
axis = 2  # y-axis (1=x, 2=y, 3=z)
active = True
  1. Rotate a vector using the Rotation class

This example shows how to build a rotation from an angle and axis and rotate a vector using simcoon.Rotation.apply().

rot = sim.Rotation.from_axis_angle(angle, axis)
Rmat = rot.as_matrix()
v_rot1 = rot.apply(v)
print("Rotated vector:", v_rot1)
print("Rotation matrix:\n", Rmat)
Rotated vector: [ 0.70710678  0.         -0.70710678]
Rotation matrix:
 [[ 0.70710678  0.          0.70710678]
 [ 0.          1.          0.        ]
 [-0.70710678  0.          0.70710678]]
  1. Rotate a vector using Euler angles (scipy syntax)

Uppercase letters indicate intrinsic rotations (rotating frame). Lowercase letters indicate extrinsic rotations (fixed frame).

rot2 = sim.Rotation.from_euler("ZXZ", [np.pi / 6, np.pi / 4, np.pi / 3])
v_rot2 = rot2.apply(v)
print("Rotated vector:", v_rot2)
Rotated vector: [0.12682648 0.78033009 0.61237244]
  1. Rotate a matrix (3x3 tensor)

This example shows how to rotate a 3x3 tensor using simcoon.Rotation.apply_tensor().

m_rot1 = rot.apply_tensor(m)
print("Rotated matrix:\n", m_rot1)
Rotated matrix:
 [[1. 0. 0.]
 [0. 1. 0.]
 [0. 0. 1.]]
  1. Rotate a matrix using a rotation from Euler angles

rot_euler = sim.Rotation.from_euler("ZXZ", [np.pi / 6, np.pi / 4, np.pi / 3])
m_rot2 = rot_euler.apply_tensor(m)
print("Rotated matrix:\n", m_rot2)
Rotated matrix:
 [[ 1.00000000e+00  0.00000000e+00 -5.55111512e-17]
 [ 0.00000000e+00  1.00000000e+00 -5.55111512e-17]
 [-5.55111512e-17 -5.55111512e-17  1.00000000e+00]]
  1. Build a rotation matrix from Euler angles

psi, theta, phi = np.pi / 6, np.pi / 4, np.pi / 3
r_euler = sim.Rotation.from_euler("ZXZ", [psi, theta, phi])
R_euler = r_euler.as_matrix()
print("Rotation matrix from Euler angles (ZXZ):\n", R_euler)
Rotation matrix from Euler angles (ZXZ):
 [[ 0.12682648 -0.9267767   0.35355339]
 [ 0.78033009 -0.12682648 -0.61237244]
 [ 0.61237244  0.35355339  0.70710678]]

Rotation of mechanical quantities

These examples demonstrate how to rotate mechanical quantities such as stress, strain, and stiffness matrices.

  1. Rotate a stress vector

This example uses simcoon.Rotation.apply_stress().

stress = np.array([1, 2, 3, 4, 5, 6], dtype=float)
rot_stress1 = rot.apply_stress(stress, active)
print("Rotated stress:", rot_stress1)
Rotated stress: [ 7.          2.         -3.          7.07106781  1.          1.41421356]
  1. Rotate a strain vector

This example uses simcoon.Rotation.apply_strain().

strain = np.array([0.1, 0.2, 0.3, 0.4, 0.5, 0.6], dtype=float)
rot_strain1 = rot.apply_strain(strain, active)
print("Rotated strain:", rot_strain1)
Rotated strain: [ 0.45        0.2        -0.05        0.70710678  0.2         0.14142136]
  1. Rotate a stiffness matrix (L)

This example uses simcoon.Rotation.apply_stiffness().

L6 = np.eye(6)
rotL1 = rot.apply_stiffness(L6, active)
print("Rotated L:\n", rotL1)
Rotated L:
 [[ 1.50000000e+00  0.00000000e+00 -5.00000000e-01  0.00000000e+00
  -1.11022302e-16  0.00000000e+00]
 [ 0.00000000e+00  1.00000000e+00  0.00000000e+00  0.00000000e+00
   0.00000000e+00  0.00000000e+00]
 [-5.00000000e-01  0.00000000e+00  1.50000000e+00  0.00000000e+00
   1.11022302e-16  0.00000000e+00]
 [ 0.00000000e+00  0.00000000e+00  0.00000000e+00  1.00000000e+00
   0.00000000e+00  0.00000000e+00]
 [-1.11022302e-16  0.00000000e+00  1.11022302e-16  0.00000000e+00
   5.00000000e-01  0.00000000e+00]
 [ 0.00000000e+00  0.00000000e+00  0.00000000e+00  0.00000000e+00
   0.00000000e+00  1.00000000e+00]]
  1. Rotate a compliance matrix (M)

This example uses simcoon.Rotation.apply_compliance().

M6 = np.eye(6)
rotM1 = rot.apply_compliance(M6, active)
print("Rotated M:\n", rotM1)
Rotated M:
 [[ 7.50000000e-01  0.00000000e+00  2.50000000e-01  0.00000000e+00
   1.11022302e-16  0.00000000e+00]
 [ 0.00000000e+00  1.00000000e+00  0.00000000e+00  0.00000000e+00
   0.00000000e+00  0.00000000e+00]
 [ 2.50000000e-01  0.00000000e+00  7.50000000e-01  0.00000000e+00
  -1.11022302e-16  0.00000000e+00]
 [ 0.00000000e+00  0.00000000e+00  0.00000000e+00  1.00000000e+00
   0.00000000e+00  0.00000000e+00]
 [ 1.11022302e-16  0.00000000e+00 -1.11022302e-16  0.00000000e+00
   2.00000000e+00  0.00000000e+00]
 [ 0.00000000e+00  0.00000000e+00  0.00000000e+00  0.00000000e+00
   0.00000000e+00  1.00000000e+00]]
  1. Rotate a strain concentration tensor (A)

This example uses simcoon.Rotation.apply_strain_concentration().

A6 = np.eye(6)
rotA1 = rot.apply_strain_concentration(A6, active)
print("Rotated A:\n", rotA1)
Rotated A:
 [[1. 0. 0. 0. 0. 0.]
 [0. 1. 0. 0. 0. 0.]
 [0. 0. 1. 0. 0. 0.]
 [0. 0. 0. 1. 0. 0.]
 [0. 0. 0. 0. 1. 0.]
 [0. 0. 0. 0. 0. 1.]]
  1. Rotate a stress concentration tensor (B)

This example uses simcoon.Rotation.apply_stress_concentration().

B6 = np.eye(6)
rotB1 = rot.apply_stress_concentration(B6, active)
print("Rotated B:\n", rotB1)
Rotated B:
 [[1. 0. 0. 0. 0. 0.]
 [0. 1. 0. 0. 0. 0.]
 [0. 0. 1. 0. 0. 0.]
 [0. 0. 0. 1. 0. 0.]
 [0. 0. 0. 0. 1. 0.]
 [0. 0. 0. 0. 0. 1.]]
  1. Rotate a cubic stiffness tensor

Provide the elastic stiffness tensor for a cubic material and rotate it.

E = 70000.0
nu = 0.3
G = 23000.0
L = sim.L_cubic([E, nu, G], "EnuG")
print(np.array_str(L, precision=2, suppress_small=True))

d = sim.check_symetries(L, 1.0e-2)
print(d["umat_type"])
print(d["props"])

x = sim.L_cubic_props(L)
print(x)

alpha = np.pi / 4.0
rot_z = sim.Rotation.from_axis_angle(alpha, 3)
L_rotate = rot_z.apply_stiffness(L)
print(np.array_str(L_rotate, suppress_small=True))
[[94230.77 40384.62 40384.62     0.       0.       0.  ]
 [40384.62 94230.77 40384.62     0.       0.       0.  ]
 [40384.62 40384.62 94230.77     0.       0.       0.  ]
 [    0.       0.       0.   23000.       0.       0.  ]
 [    0.       0.       0.       0.   23000.       0.  ]
 [    0.       0.       0.       0.       0.   23000.  ]]
ELCUB
[[7.0e+04]
 [3.0e-01]
 [2.3e+04]]
[[7.0e+04]
 [3.0e-01]
 [2.3e+04]]
[[90307.69230769 44307.69230769 40384.61538462    -0.
      0.             0.        ]
 [44307.69230769 90307.69230769 40384.61538462     0.
      0.             0.        ]
 [40384.61538462 40384.61538462 94230.76923077     0.
      0.             0.        ]
 [    0.            -0.             0.         26923.07692308
      0.             0.        ]
 [    0.             0.             0.             0.
  23000.             0.        ]
 [    0.             0.             0.             0.
      0.         23000.        ]]

Scipy interoperability

simcoon.Rotation inherits from scipy.spatial.transform.Rotation, so every scipy feature is available. Plain scipy Rotation objects can be upgraded to simcoon.Rotation via from_scipy() to unlock the mechanics methods.

  1. simcoon.Rotation IS a scipy Rotation

Type checks and scipy methods work directly.

r = sim.Rotation.from_axis_angle(np.pi / 4, 3)
print("isinstance(r, ScipyRotation):", isinstance(r, ScipyRotation))
print("Quaternion:", r.as_quat())
print("Rotation vector:", r.as_rotvec())
print("Euler ZXZ:", r.as_euler("ZXZ", degrees=True))
isinstance(r, ScipyRotation): True
Quaternion: [0.         0.         0.38268343 0.92387953]
Rotation vector: [0.         0.         0.78539816]
/home/runner/work/simcoon/simcoon/examples/continuum_mechanics/rotation.py:188: UserWarning: Gimbal lock detected. Setting third angle to zero since it is not possible to uniquely determine all angles.
  print("Euler ZXZ:", r.as_euler("ZXZ", degrees=True))
Euler ZXZ: [45.  0.  0.]
  1. Upgrade a plain scipy Rotation to simcoon

Use from_scipy() to add mechanics capabilities to an existing scipy rotation (e.g. one returned by a third-party library).

scipy_rot = ScipyRotation.from_euler("z", 45, degrees=True)
print("Type before:", type(scipy_rot))

smc_rot = sim.Rotation.from_scipy(scipy_rot)
print("Type after:", type(smc_rot))

# The quaternion is preserved exactly
print("Quaternions match:", np.allclose(scipy_rot.as_quat(), smc_rot.as_quat()))

# Now mechanics methods are available
L_iso = sim.L_iso([70000, 0.3], "Enu")
L_rotated = smc_rot.apply_stiffness(L_iso)
print("Rotated stiffness L[0,0]:", L_rotated[0, 0])
Type before: <class 'scipy.spatial.transform._rotation.Rotation'>
Type after: <class 'simcoon.rotation.Rotation'>
Quaternions match: True
Rotated stiffness L[0,0]: 94230.76923076922
  1. Verify scipy and simcoon produce the same rotation

A simcoon rotation and a scipy rotation built from the same parameters give identical stiffness results.

angle_test = np.pi / 6
r_sim = sim.Rotation.from_axis_angle(angle_test, 3)
r_scipy = sim.Rotation.from_scipy(ScipyRotation.from_rotvec([0, 0, angle_test]))

L_sim = r_sim.apply_stiffness(L_iso)
L_scipy = r_scipy.apply_stiffness(L_iso)
print("Stiffness matrices match:", np.allclose(L_sim, L_scipy))
Stiffness matrices match: True
  1. Compose simcoon and scipy rotations

Because simcoon.Rotation inherits __mul__ from scipy, composition with either type works directly.

r1 = sim.Rotation.from_axis_angle(np.pi / 4, 3)  # simcoon
r2 = ScipyRotation.from_euler("x", 30, degrees=True)  # plain scipy

r_composed = r1 * r2  # returns simcoon.Rotation
print("Composed type:", type(r_composed))
print("Composed is simcoon Rotation:", isinstance(r_composed, sim.Rotation))
Composed type: <class 'simcoon.rotation.Rotation'>
Composed is simcoon Rotation: True
  1. Batch rotations and mean (scipy features)

Generate random orientations and compute their mean — all scipy batch operations work out of the box.

rots = sim.Rotation.random(100)
r_mean = rots.mean()
print("Mean rotation type:", type(r_mean))
print("Mean rotation angle:", np.degrees(r_mean.magnitude()), "degrees")
Mean rotation type: <class 'scipy.spatial.transform._rotation.Rotation'>
Mean rotation angle: 36.569099517764435 degrees
  1. Slerp interpolation

Use scipy’s Slerp class for multi-keyframe interpolation, or the convenience slerp_to() method for simple two-rotation interpolation.

from scipy.spatial.transform import Slerp

r_start = sim.Rotation.identity()
r_end = sim.Rotation.from_axis_angle(np.pi / 2, 3)

# Via scipy Slerp class
key_rots = sim.Rotation.concatenate([r_start, r_end])
slerp = Slerp([0, 1], key_rots)
for t in [0.0, 0.25, 0.5, 0.75, 1.0]:
    r_t = slerp(t)
    print(f"  t={t:.2f}: angle = {np.degrees(r_t.magnitude()):.1f} deg")

# Via convenience method
r_mid = r_start.slerp_to(r_end, 0.5)
print("Midpoint angle:", np.degrees(r_mid.magnitude()), "degrees")
  t=0.00: angle = 0.0 deg
  t=0.25: angle = 22.5 deg
  t=0.50: angle = 45.0 deg
  t=0.75: angle = 67.5 deg
  t=1.00: angle = 90.0 deg
Midpoint angle: 45.0 degrees

Total running time of the script: (0 minutes 0.015 seconds)

Gallery generated by Sphinx-Gallery