use crate::algebra::mv::Mv;
use crate::algebra::ops;
use crate::algebra::signature::Signature;
use crate::governance::construction::Construction;
use crate::scalar::Scalar;
#[derive(Clone, Debug)]
pub enum Expr {
Param(usize),
Generator(u8),
DerivedGen(usize),
Literal(Scalar),
Add(Box<Expr>, Box<Expr>),
Mul(Box<Expr>, Box<Expr>),
Neg(Box<Expr>),
Pow(Box<Expr>, u32),
Construct(usize, Vec<Box<Expr>>),
Outer(Box<Expr>, Box<Expr>),
Inner(Box<Expr>, Box<Expr>),
Reverse(Box<Expr>),
Dual(Box<Expr>),
Sandwich(Box<Expr>, Box<Expr>),
ValueRef(usize),
GradeProject(Box<Expr>, u8),
LeftContract(Box<Expr>, Box<Expr>),
ScalarProduct(Box<Expr>, Box<Expr>),
Read(Box<Expr>, usize),
WithGov(usize, Box<Expr>),
Embed(Box<Expr>, usize),
Morph(Box<Expr>, usize),
Probe,
Object,
}
use crate::governance::composition::Embedding;
use crate::governance::governance::Governance;
use crate::governance::morphism::Morphism;
pub struct EvalContext<'a> {
pub params: &'a [Scalar],
pub sig: &'a Signature,
pub derived_gens: &'a [Mv],
pub constructions: &'a [Construction],
pub mv_table: &'a [Mv],
pub governances: &'a [Governance],
pub mv_governance_indices: &'a [usize],
pub embeddings: &'a [Embedding],
pub morphisms: &'a [Morphism],
pub probe_mv: Option<&'a Mv>,
pub object_mv: Option<&'a Mv>,
}
impl Expr {
pub fn eval(&self, ctx: &EvalContext) -> Mv {
match self {
Expr::Param(i) => {
debug_assert!(
*i < ctx.params.len(),
"Expr::Param({}) out of range (have {})",
i,
ctx.params.len()
);
Mv::scalar(ctx.params[*i].clone())
}
Expr::Generator(k) => Mv::generator(*k),
Expr::DerivedGen(i) => {
debug_assert!(
*i < ctx.derived_gens.len(),
"Expr::DerivedGen({}) out of range",
i
);
ctx.derived_gens[*i].clone()
}
Expr::Literal(s) => Mv::scalar(s.clone()),
Expr::Add(a, b) => a.eval(ctx) + b.eval(ctx),
Expr::Mul(a, b) => {
let ma = a.eval(ctx);
let mb = b.eval(ctx);
ops::geometric(&ma, &mb, ctx.sig)
}
Expr::Neg(a) => -a.eval(ctx),
Expr::Pow(base, n) => {
let b = base.eval(ctx);
let mut result = Mv::scalar(Scalar::from(1i64));
for _ in 0..*n {
result = ops::geometric(&result, &b, ctx.sig);
}
result
}
Expr::Construct(idx, args) => {
let sub_params: Vec<Scalar> = args
.iter()
.map(|a| {
let mv = a.eval(ctx);
mv.coefficient(0) })
.collect();
let sub_ctx = EvalContext {
params: &sub_params,
sig: ctx.sig,
derived_gens: ctx.derived_gens,
constructions: ctx.constructions,
mv_table: ctx.mv_table,
governances: ctx.governances,
mv_governance_indices: ctx.mv_governance_indices,
embeddings: &[],
morphisms: &[],
probe_mv: None,
object_mv: None,
};
ctx.constructions[*idx].body.eval(&sub_ctx)
}
Expr::Outer(a, b) => {
let ma = a.eval(ctx);
let mb = b.eval(ctx);
ops::outer(&ma, &mb, ctx.sig)
}
Expr::Inner(a, b) => {
let ma = a.eval(ctx);
let mb = b.eval(ctx);
ops::inner(&ma, &mb, ctx.sig)
}
Expr::Reverse(a) => ops::reverse(&a.eval(ctx)),
Expr::Dual(a) => ops::dual(&a.eval(ctx), ctx.sig),
Expr::Sandwich(a, b) => {
let ma = a.eval(ctx);
let mb = b.eval(ctx);
ops::sandwich(&ma, &mb, ctx.sig)
}
Expr::ValueRef(idx) => {
debug_assert!(
*idx < ctx.mv_table.len(),
"Expr::ValueRef({}) out of range (have {})",
idx,
ctx.mv_table.len()
);
ctx.mv_table[*idx].clone()
}
Expr::GradeProject(a, k) => ops::grade_project(&a.eval(ctx), *k),
Expr::LeftContract(a, b) => {
let ma = a.eval(ctx);
let mb = b.eval(ctx);
ops::left_contract(&ma, &mb, ctx.sig)
}
Expr::ScalarProduct(a, b) => {
let ma = a.eval(ctx);
let mb = b.eval(ctx);
Mv::scalar(ops::scalar_product_value(&ma, &mb, ctx.sig))
}
Expr::Read(inner, param_idx) => {
let mv = inner.eval(ctx);
let gov_idx = match inner.as_ref() {
Expr::ValueRef(idx) => ctx.mv_governance_indices.get(*idx).copied(),
_ => None,
};
let gi = gov_idx.unwrap_or_else(|| {
panic!(
"Expr::Read requires governance context: the inner expression must be a \
ValueRef with a corresponding governance index in mv_governance_indices"
)
});
let gov = ctx.governances.get(gi).unwrap_or_else(|| {
panic!(
"Expr::Read: governance index {} not found (have {} governances)",
gi,
ctx.governances.len()
)
});
for class_idx in 0..gov.geom_classes.len() {
if let Ok(geoit) = crate::governance::govern(&mv, gov, class_idx) {
if let Ok(val) = geoit.read(*param_idx) {
return Mv::scalar(val);
}
}
}
panic!(
"Expr::Read: could not extract parameter {} — Mv does not satisfy any \
class in governance index {}",
param_idx, gi
);
}
Expr::WithGov(gov_idx, inner) => {
let gov = ctx.governances.get(*gov_idx).unwrap_or_else(|| {
panic!(
"Expr::WithGov: governance index {} not found (have {} governances)",
gov_idx,
ctx.governances.len()
)
});
let sub_ctx = EvalContext {
params: ctx.params,
sig: &gov.sig,
derived_gens: &gov.derived_gens,
constructions: &gov.constructions,
mv_table: ctx.mv_table,
governances: ctx.governances,
mv_governance_indices: ctx.mv_governance_indices,
embeddings: ctx.embeddings,
morphisms: ctx.morphisms,
probe_mv: None,
object_mv: None,
};
inner.eval(&sub_ctx)
}
Expr::Embed(inner, emb_idx) => {
let mv = inner.eval(ctx);
let emb = ctx.embeddings.get(*emb_idx).unwrap_or_else(|| {
panic!(
"Expr::Embed: embedding index {} not found (have {} embeddings)",
emb_idx,
ctx.embeddings.len()
)
});
emb.embed_mv(&mv)
}
Expr::Morph(inner, morph_idx) => {
let mv = inner.eval(ctx);
let morph = ctx.morphisms.get(*morph_idx).unwrap_or_else(|| {
panic!(
"Expr::Morph: morphism index {} not found (have {} morphisms)",
morph_idx,
ctx.morphisms.len()
)
});
morph.apply_mv(&mv)
}
Expr::Probe => ctx.probe_mv.cloned().unwrap_or_else(|| {
panic!("Expr::Probe evaluated outside render context (no probe Mv bound)")
}),
Expr::Object => ctx.object_mv.cloned().unwrap_or_else(|| {
panic!("Expr::Object evaluated outside render context (no object Mv bound)")
}),
}
}
}
impl Expr {
pub fn param(i: usize) -> Box<Expr> {
Box::new(Expr::Param(i))
}
pub fn gen(k: u8) -> Box<Expr> {
Box::new(Expr::Generator(k))
}
pub fn dgen(i: usize) -> Box<Expr> {
Box::new(Expr::DerivedGen(i))
}
pub fn lit(n: i64) -> Box<Expr> {
Box::new(Expr::Literal(Scalar::from(n)))
}
pub fn lit_scalar(s: Scalar) -> Box<Expr> {
Box::new(Expr::Literal(s))
}
#[allow(clippy::should_implement_trait)]
pub fn add(a: Box<Expr>, b: Box<Expr>) -> Box<Expr> {
Box::new(Expr::Add(a, b))
}
#[allow(clippy::should_implement_trait)]
pub fn mul(a: Box<Expr>, b: Box<Expr>) -> Box<Expr> {
Box::new(Expr::Mul(a, b))
}
#[allow(clippy::should_implement_trait)]
pub fn neg(a: Box<Expr>) -> Box<Expr> {
Box::new(Expr::Neg(a))
}
pub fn pow(base: Box<Expr>, n: u32) -> Box<Expr> {
Box::new(Expr::Pow(base, n))
}
pub fn construct(idx: usize, args: Vec<Box<Expr>>) -> Box<Expr> {
Box::new(Expr::Construct(idx, args))
}
pub fn outer(a: Box<Expr>, b: Box<Expr>) -> Box<Expr> {
Box::new(Expr::Outer(a, b))
}
pub fn inner(a: Box<Expr>, b: Box<Expr>) -> Box<Expr> {
Box::new(Expr::Inner(a, b))
}
pub fn reverse(a: Box<Expr>) -> Box<Expr> {
Box::new(Expr::Reverse(a))
}
pub fn dual(a: Box<Expr>) -> Box<Expr> {
Box::new(Expr::Dual(a))
}
pub fn sandwich(a: Box<Expr>, b: Box<Expr>) -> Box<Expr> {
Box::new(Expr::Sandwich(a, b))
}
pub fn value_ref(idx: usize) -> Box<Expr> {
Box::new(Expr::ValueRef(idx))
}
pub fn grade_project(a: Box<Expr>, k: u8) -> Box<Expr> {
Box::new(Expr::GradeProject(a, k))
}
pub fn left_contract(a: Box<Expr>, b: Box<Expr>) -> Box<Expr> {
Box::new(Expr::LeftContract(a, b))
}
pub fn scalar_product(a: Box<Expr>, b: Box<Expr>) -> Box<Expr> {
Box::new(Expr::ScalarProduct(a, b))
}
pub fn read(inner: Box<Expr>, param_idx: usize) -> Box<Expr> {
Box::new(Expr::Read(inner, param_idx))
}
pub fn with_gov(gov_idx: usize, inner: Box<Expr>) -> Box<Expr> {
Box::new(Expr::WithGov(gov_idx, inner))
}
pub fn embed(inner: Box<Expr>, emb_idx: usize) -> Box<Expr> {
Box::new(Expr::Embed(inner, emb_idx))
}
pub fn morph(inner: Box<Expr>, morph_idx: usize) -> Box<Expr> {
Box::new(Expr::Morph(inner, morph_idx))
}
pub fn probe() -> Box<Expr> {
Box::new(Expr::Probe)
}
pub fn object() -> Box<Expr> {
Box::new(Expr::Object)
}
}
#[derive(Clone, Debug)]
pub enum EvalError {
ParamOutOfRange { index: usize, len: usize },
DerivedGenOutOfRange { index: usize, len: usize },
ValueRefOutOfRange { index: usize, len: usize },
GovernanceNotFound { index: usize, len: usize },
EmbeddingNotFound { index: usize, len: usize },
MorphismNotFound { index: usize, len: usize },
ConstructionOutOfRange { index: usize, len: usize },
ProbeNotBound,
ObjectNotBound,
CircularConstruction { index: usize, depth: usize },
}
impl std::fmt::Display for EvalError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
EvalError::ParamOutOfRange { index, len } => {
write!(f, "Param({}) out of range (have {})", index, len)
}
EvalError::DerivedGenOutOfRange { index, len } => {
write!(f, "DerivedGen({}) out of range (have {})", index, len)
}
EvalError::ValueRefOutOfRange { index, len } => {
write!(f, "ValueRef({}) out of range (have {})", index, len)
}
EvalError::GovernanceNotFound { index, len } => {
write!(f, "governance index {} not found (have {})", index, len)
}
EvalError::EmbeddingNotFound { index, len } => {
write!(f, "embedding index {} not found (have {})", index, len)
}
EvalError::MorphismNotFound { index, len } => {
write!(f, "morphism index {} not found (have {})", index, len)
}
EvalError::ConstructionOutOfRange { index, len } => {
write!(f, "Construct({}) out of range (have {})", index, len)
}
EvalError::ProbeNotBound => write!(
f,
"Expr::Probe used but no probe point bound (outside render context)"
),
EvalError::ObjectNotBound => write!(
f,
"Expr::Object used but no object bound (outside render context)"
),
EvalError::CircularConstruction { index, depth } => write!(
f,
"circular construction at index {} (depth {})",
index, depth
),
}
}
}
#[derive(Clone, Debug)]
pub struct ContextShape {
pub param_count: usize,
pub derived_gen_count: usize,
pub construction_count: usize,
pub mv_table_size: usize,
pub governance_count: usize,
pub embedding_count: usize,
pub morphism_count: usize,
pub has_probe: bool,
pub has_object: bool,
}
impl ContextShape {
pub fn for_construction(
arity: usize,
derived_gen_count: usize,
construction_count: usize,
) -> Self {
ContextShape {
param_count: arity,
derived_gen_count,
construction_count,
mv_table_size: 0,
governance_count: 0,
embedding_count: 0,
morphism_count: 0,
has_probe: false,
has_object: false,
}
}
}
impl Expr {
pub fn validate(&self, shape: &ContextShape) -> Result<(), Vec<EvalError>> {
let mut errors = Vec::new();
self.validate_inner(shape, &mut errors, &mut Vec::new(), 0);
if errors.is_empty() {
Ok(())
} else {
Err(errors)
}
}
fn validate_inner(
&self,
shape: &ContextShape,
errors: &mut Vec<EvalError>,
construct_stack: &mut Vec<usize>,
depth: usize,
) {
match self {
Expr::Param(i) => {
if *i >= shape.param_count {
errors.push(EvalError::ParamOutOfRange {
index: *i,
len: shape.param_count,
});
}
}
Expr::Generator(_) | Expr::Literal(_) => {}
Expr::DerivedGen(i) => {
if *i >= shape.derived_gen_count {
errors.push(EvalError::DerivedGenOutOfRange {
index: *i,
len: shape.derived_gen_count,
});
}
}
Expr::Add(a, b)
| Expr::Mul(a, b)
| Expr::Outer(a, b)
| Expr::Inner(a, b)
| Expr::Sandwich(a, b)
| Expr::LeftContract(a, b)
| Expr::ScalarProduct(a, b) => {
a.validate_inner(shape, errors, construct_stack, depth);
b.validate_inner(shape, errors, construct_stack, depth);
}
Expr::Neg(a) | Expr::Reverse(a) | Expr::Dual(a) => {
a.validate_inner(shape, errors, construct_stack, depth);
}
Expr::Pow(base, _) => {
base.validate_inner(shape, errors, construct_stack, depth);
}
Expr::GradeProject(a, _) => {
a.validate_inner(shape, errors, construct_stack, depth);
}
Expr::Construct(idx, args) => {
if *idx >= shape.construction_count {
errors.push(EvalError::ConstructionOutOfRange {
index: *idx,
len: shape.construction_count,
});
}
if construct_stack.contains(idx) {
errors.push(EvalError::CircularConstruction { index: *idx, depth });
} else {
construct_stack.push(*idx);
for arg in args {
arg.validate_inner(shape, errors, construct_stack, depth + 1);
}
construct_stack.pop();
}
}
Expr::ValueRef(idx) => {
if *idx >= shape.mv_table_size {
errors.push(EvalError::ValueRefOutOfRange {
index: *idx,
len: shape.mv_table_size,
});
}
}
Expr::Read(inner, _) => {
inner.validate_inner(shape, errors, construct_stack, depth);
}
Expr::WithGov(gov_idx, inner) => {
if *gov_idx >= shape.governance_count {
errors.push(EvalError::GovernanceNotFound {
index: *gov_idx,
len: shape.governance_count,
});
}
inner.validate_inner(shape, errors, construct_stack, depth);
}
Expr::Embed(inner, emb_idx) => {
if *emb_idx >= shape.embedding_count {
errors.push(EvalError::EmbeddingNotFound {
index: *emb_idx,
len: shape.embedding_count,
});
}
inner.validate_inner(shape, errors, construct_stack, depth);
}
Expr::Morph(inner, morph_idx) => {
if *morph_idx >= shape.morphism_count {
errors.push(EvalError::MorphismNotFound {
index: *morph_idx,
len: shape.morphism_count,
});
}
inner.validate_inner(shape, errors, construct_stack, depth);
}
Expr::Probe => {
if !shape.has_probe {
errors.push(EvalError::ProbeNotBound);
}
}
Expr::Object => {
if !shape.has_object {
errors.push(EvalError::ObjectNotBound);
}
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::algebra::signature::Signature;
use crate::scalar::Rat;
static VGA3_SIG: Signature = Signature { i: 0, d: 0, h: 3 };
#[test]
fn eval_param() {
let params = vec![Scalar::from(7i64)];
let ctx = EvalContext {
params: ¶ms,
sig: &VGA3_SIG,
derived_gens: &[],
constructions: &[],
mv_table: &[],
governances: &[],
mv_governance_indices: &[],
embeddings: &[],
morphisms: &[],
probe_mv: None,
object_mv: None,
};
let mv = Expr::Param(0).eval(&ctx);
assert_eq!(mv.coefficient(0), Scalar::from(7i64));
}
#[test]
fn eval_generator() {
let ctx = EvalContext {
params: &[],
sig: &VGA3_SIG,
derived_gens: &[],
constructions: &[],
mv_table: &[],
governances: &[],
mv_governance_indices: &[],
embeddings: &[],
morphisms: &[],
probe_mv: None,
object_mv: None,
};
let mv = Expr::Generator(2).eval(&ctx);
assert_eq!(mv.coefficient(0b100), Scalar::from(1i64));
assert_eq!(mv.len(), 1);
}
#[test]
fn eval_scale_param_times_gen() {
let params = vec![Scalar::from(3i64)];
let ctx = EvalContext {
params: ¶ms,
sig: &VGA3_SIG,
derived_gens: &[],
constructions: &[],
mv_table: &[],
governances: &[],
mv_governance_indices: &[],
embeddings: &[],
morphisms: &[],
probe_mv: None,
object_mv: None,
};
let expr = Expr::Mul(Expr::param(0), Expr::gen(1));
let mv = expr.eval(&ctx);
assert_eq!(mv.coefficient(0b010), Scalar::from(3i64));
assert_eq!(mv.len(), 1);
}
#[test]
fn eval_add() {
let params = vec![Scalar::from(3i64), Scalar::from(4i64)];
let ctx = EvalContext {
params: ¶ms,
sig: &VGA3_SIG,
derived_gens: &[],
constructions: &[],
mv_table: &[],
governances: &[],
mv_governance_indices: &[],
embeddings: &[],
morphisms: &[],
probe_mv: None,
object_mv: None,
};
let expr = Expr::Add(
Box::new(Expr::Mul(Expr::param(0), Expr::gen(0))),
Box::new(Expr::Mul(Expr::param(1), Expr::gen(1))),
);
let mv = expr.eval(&ctx);
assert_eq!(mv.coefficient(0b001), Scalar::from(3i64));
assert_eq!(mv.coefficient(0b010), Scalar::from(4i64));
}
#[test]
fn eval_vga_vector() {
let params = vec![Scalar::from(3i64), Scalar::from(4i64), Scalar::from(5i64)];
let ctx = EvalContext {
params: ¶ms,
sig: &VGA3_SIG,
derived_gens: &[],
constructions: &[],
mv_table: &[],
governances: &[],
mv_governance_indices: &[],
embeddings: &[],
morphisms: &[],
probe_mv: None,
object_mv: None,
};
let expr = Expr::Add(
Expr::add(
Expr::mul(Expr::param(0), Expr::gen(0)),
Expr::mul(Expr::param(1), Expr::gen(1)),
),
Expr::mul(Expr::param(2), Expr::gen(2)),
);
let mv = expr.eval(&ctx);
assert_eq!(mv.coefficient(0b001), Scalar::from(3i64));
assert_eq!(mv.coefficient(0b010), Scalar::from(4i64));
assert_eq!(mv.coefficient(0b100), Scalar::from(5i64));
assert_eq!(mv.len(), 3);
}
#[test]
fn eval_pow_squared() {
let sig = Signature::new(0, 0, 1).unwrap();
let ctx = EvalContext {
params: &[],
sig: &sig,
derived_gens: &[],
constructions: &[],
mv_table: &[],
governances: &[],
mv_governance_indices: &[],
embeddings: &[],
morphisms: &[],
probe_mv: None,
object_mv: None,
};
let expr = Expr::Pow(Expr::gen(0), 2);
let mv = expr.eval(&ctx);
assert_eq!(mv.coefficient(0), Scalar::from(1i64));
}
#[test]
fn eval_pow_negative_square() {
let sig = Signature::new(1, 0, 0).unwrap();
let ctx = EvalContext {
params: &[],
sig: &sig,
derived_gens: &[],
constructions: &[],
mv_table: &[],
governances: &[],
mv_governance_indices: &[],
embeddings: &[],
morphisms: &[],
probe_mv: None,
object_mv: None,
};
let expr = Expr::Pow(Expr::gen(0), 2);
let mv = expr.eval(&ctx);
assert_eq!(mv.coefficient(0), Scalar::from(-1i64));
}
#[test]
fn eval_derived_gen() {
let dg = Mv::from_rat_terms(&[(0b01, Rat::new(1, 2)), (0b10, Rat::new(1, 2))]);
let sig = Signature::new(0, 0, 2).unwrap();
let ctx = EvalContext {
params: &[],
sig: &sig,
derived_gens: &[dg],
constructions: &[],
mv_table: &[],
governances: &[],
mv_governance_indices: &[],
embeddings: &[],
morphisms: &[],
probe_mv: None,
object_mv: None,
};
let expr = Expr::DerivedGen(0);
let mv = expr.eval(&ctx);
assert_eq!(mv.coefficient(0b01), Scalar::Rat(Rat::new(1, 2)));
assert_eq!(mv.coefficient(0b10), Scalar::Rat(Rat::new(1, 2)));
}
#[test]
fn eval_neg() {
let params = vec![Scalar::from(5i64)];
let ctx = EvalContext {
params: ¶ms,
sig: &VGA3_SIG,
derived_gens: &[],
constructions: &[],
mv_table: &[],
governances: &[],
mv_governance_indices: &[],
embeddings: &[],
morphisms: &[],
probe_mv: None,
object_mv: None,
};
let expr = Expr::Neg(Expr::mul(Expr::param(0), Expr::gen(0)));
let mv = expr.eval(&ctx);
assert_eq!(mv.coefficient(0b001), Scalar::from(-5i64));
}
}