use crate::clifford::{bits, CliffordAlgebra, Metric, Multivector};
use crate::games::partizan::Game;
use crate::linalg::integer::reduce_integer_vector;
use crate::scalar::Integer;
use std::collections::{BTreeMap, BTreeSet};
use super::relations::{
eval_relation, relation_search_certificate, GameRelation, RelationSearchCertificate,
};
pub(super) const DEFAULT_RELATION_BOUND: i128 = 3;
const MAX_AUTO_RELATION_CANDIDATES: usize = 100;
#[derive(Clone)]
pub struct GameExterior {
alg: CliffordAlgebra<Integer>,
gens: Vec<Game>,
relations: Vec<GameRelation>,
relation_search_complete: bool,
relation_certificate: RelationSearchCertificate,
}
impl GameExterior {
pub fn new(gens: Vec<Game>) -> GameExterior {
GameExterior::with_relation_search(gens, DEFAULT_RELATION_BOUND)
}
pub fn free(gens: Vec<Game>) -> GameExterior {
GameExterior::with_relations(gens, vec![])
}
pub fn with_relation_search(gens: Vec<Game>, bound: i128) -> GameExterior {
let (relations, complete, candidate_count) = discover_relations(&gens, bound);
let rel_certificate =
relation_search_certificate(&gens, bound, complete, candidate_count, &relations, true);
let mut ext = GameExterior::with_relations(gens, relations);
ext.relation_search_complete = complete;
ext.relation_certificate = rel_certificate;
ext
}
pub fn with_relations(gens: Vec<Game>, relations: Vec<GameRelation>) -> GameExterior {
let n = gens.len();
for rel in &relations {
assert_eq!(
rel.coeffs.len(),
n,
"game relation length must match generator count"
);
assert!(
eval_relation(&gens, &rel.coeffs).eq(&Game::zero()),
"declared game relation does not evaluate to zero"
);
}
let rel_certificate = relation_search_certificate(&gens, 0, true, None, &relations, false);
GameExterior {
alg: CliffordAlgebra::new(n, Metric::grassmann(n)),
gens,
relation_certificate: rel_certificate,
relations,
relation_search_complete: true,
}
}
pub fn algebra(&self) -> &CliffordAlgebra<Integer> {
&self.alg
}
pub fn relations(&self) -> &[GameRelation] {
&self.relations
}
pub fn relation_search_complete(&self) -> bool {
self.relation_search_complete
}
pub fn relation_search_certificate(&self) -> &RelationSearchCertificate {
&self.relation_certificate
}
pub fn generator(&self, i: usize) -> Multivector<Integer> {
self.reduce(&self.alg.e(i))
}
pub fn game(&self, i: usize) -> &Game {
&self.gens[i]
}
pub fn value_of_grade1(&self, mv: &Multivector<Integer>) -> Game {
let mut acc = Game::zero();
let mv = self.reduce(mv);
for (&blade, coeff) in &mv.terms {
assert_eq!(
blade.count_ones(),
1,
"value_of_grade1 expects a grade-1 element"
);
let i = blade.trailing_zeros() as usize;
acc = acc.add(&self.gens[i].times_int(coeff.0));
}
acc
}
pub fn add(&self, a: &Multivector<Integer>, b: &Multivector<Integer>) -> Multivector<Integer> {
self.reduce(&self.alg.add(a, b))
}
pub fn scalar_mul(&self, s: i128, a: &Multivector<Integer>) -> Multivector<Integer> {
self.reduce(&self.alg.scalar_mul(&Integer(s), a))
}
pub fn wedge(
&self,
a: &Multivector<Integer>,
b: &Multivector<Integer>,
) -> Multivector<Integer> {
self.reduce(&self.alg.wedge(a, b))
}
pub fn is_zero(&self, mv: &Multivector<Integer>) -> bool {
self.reduce(mv).is_zero()
}
pub fn reduce(&self, mv: &Multivector<Integer>) -> Multivector<Integer> {
if self.relations.is_empty() || mv.is_zero() {
return mv.clone();
}
let mut out = self.alg.zero();
let mut by_grade: BTreeMap<usize, BTreeMap<u128, i128>> = BTreeMap::new();
for (&blade, coeff) in &mv.terms {
by_grade
.entry(blade.count_ones() as usize)
.or_default()
.insert(blade, coeff.0);
}
for (grade, terms) in by_grade {
let reduced = self.reduce_grade(grade, &terms);
for (blade, coeff) in reduced {
if coeff != 0 {
out.terms.insert(blade, Integer(coeff));
}
}
}
out
}
fn reduce_grade(&self, grade: usize, terms: &BTreeMap<u128, i128>) -> BTreeMap<u128, i128> {
if grade == 0 {
return terms.clone();
}
let basis = grade_masks(self.gens.len(), grade);
if basis.is_empty() {
return BTreeMap::new();
}
let index: BTreeMap<u128, usize> = basis.iter().enumerate().map(|(i, &m)| (m, i)).collect();
let mut v = vec![0i128; basis.len()];
for (&blade, &coeff) in terms {
if let Some(&i) = index.get(&blade) {
v[i] += coeff;
}
}
let rows = self.relation_rows_for_grade(grade, &basis, &index);
reduce_integer_vector(&mut v, rows);
basis.into_iter().zip(v).filter(|&(_, c)| c != 0).collect()
}
fn relation_rows_for_grade(
&self,
grade: usize,
basis: &[u128],
index: &BTreeMap<u128, usize>,
) -> Vec<Vec<i128>> {
let mut rows = Vec::new();
let lower_basis = grade_masks(self.gens.len(), grade - 1);
for rel in &self.relations {
let rel_mv = relation_multivector(rel);
for mask in &lower_basis {
let blade = self.alg.blade(&bits(*mask));
let wedged = self.alg.wedge(&rel_mv, &blade);
let mut row = vec![0i128; basis.len()];
for (&b, coeff) in &wedged.terms {
if let Some(&i) = index.get(&b) {
row[i] += coeff.0;
}
}
if row.iter().any(|&x| x != 0) {
rows.push(row);
}
}
}
rows
}
}
pub(super) fn relation_multivector(rel: &GameRelation) -> Multivector<Integer> {
let mut terms = BTreeMap::new();
for (i, &coeff) in rel.coeffs.iter().enumerate() {
if coeff != 0 {
terms.insert(1u128 << i, Integer(coeff));
}
}
Multivector { terms }
}
fn canonical_relation(mut coeffs: Vec<i128>) -> Option<Vec<i128>> {
let first = coeffs.iter().position(|&c| c != 0)?;
if coeffs[first] < 0 {
for c in &mut coeffs {
*c = -*c;
}
}
Some(coeffs)
}
pub(super) fn discover_relations(
gens: &[Game],
bound: i128,
) -> (Vec<GameRelation>, bool, Option<usize>) {
if gens.is_empty() || bound <= 0 {
return (Vec::new(), true, Some(0));
}
let n = gens.len();
let mut seen = BTreeSet::new();
let mut out = Vec::new();
for i in 0..n {
for c in 1..=bound {
let mut coeffs = vec![0i128; n];
coeffs[i] = c;
if push_relation_if_independent(gens, coeffs, &mut seen, &mut out) {
break;
}
}
}
let Some(count) = bounded_relation_candidate_count(n, bound) else {
return (out, false, None);
};
if count > MAX_AUTO_RELATION_CANDIDATES {
return (out, false, Some(count));
}
let mut candidates = Vec::new();
enumerate_bounded_relations(n, bound, &mut |coeffs| {
if let Some(key) = canonical_relation(coeffs) {
candidates.push(key);
}
});
candidates.sort_by_key(|v| (v.iter().map(|c| c.abs()).sum::<i128>(), v.clone()));
for coeffs in candidates {
push_relation_if_independent(gens, coeffs, &mut seen, &mut out);
}
(out, true, Some(count))
}
fn bounded_relation_candidate_count(n: usize, bound: i128) -> Option<usize> {
let width = usize::try_from(bound.checked_mul(2)?.checked_add(1)?).ok()?;
let mut count = 1usize;
for _ in 0..n {
count = count.checked_mul(width)?;
}
count.checked_sub(1)
}
fn enumerate_bounded_relations(n: usize, bound: i128, f: &mut impl FnMut(Vec<i128>)) {
fn rec(i: usize, n: usize, bound: i128, coeffs: &mut [i128], f: &mut impl FnMut(Vec<i128>)) {
if i == n {
if coeffs.iter().any(|&c| c != 0) {
f(coeffs.to_vec());
}
return;
}
for c in -bound..=bound {
coeffs[i] = c;
rec(i + 1, n, bound, coeffs, f);
}
}
let mut coeffs = vec![0i128; n];
rec(0, n, bound, &mut coeffs, f);
}
fn push_relation_if_independent(
gens: &[Game],
coeffs: Vec<i128>,
seen: &mut BTreeSet<Vec<i128>>,
out: &mut Vec<GameRelation>,
) -> bool {
let Some(key) = canonical_relation(coeffs) else {
return false;
};
if !seen.insert(key.clone()) {
return false;
}
if !eval_relation(gens, &key).eq(&Game::zero()) {
return false;
}
let mut reduced = key.clone();
let rows: Vec<Vec<i128>> = out.iter().map(|r| r.coeffs.clone()).collect();
reduce_integer_vector(&mut reduced, rows);
if reduced.iter().all(|&c| c == 0) {
return false;
}
out.push(GameRelation::new(key));
true
}
pub(super) fn grade_masks(n: usize, grade: usize) -> Vec<u128> {
if grade > n {
return Vec::new();
}
fn rec(n: usize, grade: usize, start: usize, mask: u128, out: &mut Vec<u128>) {
if grade == 0 {
out.push(mask);
return;
}
for i in start..=n - grade {
rec(n, grade - 1, i + 1, mask | (1u128 << i), out);
}
}
let mut out = Vec::new();
rec(n, grade, 0, 0, &mut out);
out
}