use crate::governance::expr::Expr;
use crate::scalar::promote::Scalar;
use crate::scalar::trig::{exact_cos_sin_pi, TrigError};
pub fn rotor_expr(unit_bivector: Expr, angle_num: i64, angle_den: i64) -> Result<Expr, TrigError> {
let (cos_half, sin_half) = exact_cos_sin_pi(angle_num, 2 * angle_den)?;
let cos_expr = Expr::Literal(Scalar::from(cos_half));
if sin_half.is_zero() {
return Ok(cos_expr);
}
let sin_expr = Expr::Literal(Scalar::from(sin_half));
let sin_bv = Expr::Mul(Box::new(sin_expr), Box::new(unit_bivector));
if cos_half_is_zero(angle_num, angle_den) {
return Ok(sin_bv);
}
Ok(Expr::Add(Box::new(cos_expr), Box::new(sin_bv)))
}
fn cos_half_is_zero(angle_num: i64, angle_den: i64) -> bool {
if angle_den == 0 {
return false;
}
let rem = angle_num.rem_euclid(2 * angle_den);
rem == angle_den
}
pub fn coordinate_rotor_expr(
gen_i: u8,
gen_j: u8,
angle_num: i64,
angle_den: i64,
) -> Result<Expr, TrigError> {
let bivector = Expr::Outer(
Box::new(Expr::Generator(gen_i)),
Box::new(Expr::Generator(gen_j)),
);
rotor_expr(bivector, angle_num, angle_den)
}
pub fn apply_rotor(rotor: Expr, target: Expr) -> Expr {
Expr::Sandwich(Box::new(rotor), Box::new(target))
}
#[cfg(test)]
mod tests {
use super::*;
use crate::algebra::mv::Mv;
use crate::algebra::ops;
use crate::algebra::signature::Signature;
use crate::governance::expr::EvalContext;
fn eval_in_vga3(expr: &Expr, params: &[Scalar]) -> Mv {
let sig = Signature::new(0, 0, 3).unwrap();
let ctx = EvalContext {
params,
sig: &sig,
derived_gens: &[],
constructions: &[],
mv_table: &[],
governances: &[],
mv_governance_indices: &[],
embeddings: &[],
morphisms: &[],
probe_mv: None,
object_mv: None,
};
expr.eval(&ctx)
}
#[test]
fn rotor_90_in_e12_plane() {
let r = coordinate_rotor_expr(0, 1, 1, 2).unwrap(); let target = Expr::Generator(0); let rotated = apply_rotor(r, target);
let result = eval_in_vga3(&rotated, &[]);
let c0 = result.coefficient(0b001); let c1 = result.coefficient(0b010); assert!(c0.is_zero(), "e₁ component should be zero, got {:?}", c0);
assert!(!c1.is_zero(), "e₂ component should be nonzero");
}
#[test]
fn rotor_180_in_e12_plane() {
let r = coordinate_rotor_expr(0, 1, 1, 1).unwrap(); let target = Expr::Generator(0);
let rotated = apply_rotor(r, target);
let result = eval_in_vga3(&rotated, &[]);
let c0 = result.coefficient(0b001);
assert_eq!(
c0,
Scalar::from(-1i64),
"180° rotation of e₁ should give -e₁"
);
}
#[test]
fn rotor_360_is_identity() {
let r = coordinate_rotor_expr(0, 1, 2, 1).unwrap(); let target = Expr::Generator(0);
let rotated = apply_rotor(r, target);
let result = eval_in_vga3(&rotated, &[]);
let c0 = result.coefficient(0b001);
assert_eq!(c0, Scalar::from(1i64), "360° rotation should be identity");
}
#[test]
fn rotor_60_preserves_norm() {
let sig = Signature::new(0, 0, 3).unwrap();
let r_expr = coordinate_rotor_expr(0, 1, 1, 3).unwrap(); let v_expr = Expr::Add(
Box::new(Expr::Mul(
Box::new(Expr::Literal(Scalar::from(3i64))),
Box::new(Expr::Generator(0)),
)),
Box::new(Expr::Mul(
Box::new(Expr::Literal(Scalar::from(4i64))),
Box::new(Expr::Generator(1)),
)),
);
let ctx = EvalContext {
params: &[],
sig: &sig,
derived_gens: &[],
constructions: &[],
mv_table: &[],
governances: &[],
mv_governance_indices: &[],
embeddings: &[],
morphisms: &[],
probe_mv: None,
object_mv: None,
};
let v = v_expr.eval(&ctx);
let rotated_expr = apply_rotor(r_expr, v_expr);
let rv = rotated_expr.eval(&ctx);
let norm_v = ops::norm_squared(&v, &sig);
let norm_rv = ops::norm_squared(&rv, &sig);
assert_eq!(norm_v, norm_rv, "rotation should preserve norm²");
}
#[test]
fn rotor_non_constructible_rejected() {
let err = coordinate_rotor_expr(0, 1, 1, 7); assert!(err.is_err());
}
}