use crate::diff::diff;
use crate::kernel::{ExprData, ExprId, ExprPool};
use crate::simplify::engine::simplify;
use std::fmt;
mod coords;
#[cfg(test)]
mod tests;
pub use coords::Coordinates;
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum VectorError {
Differentiation(String),
RepeatedCoordinate {
coordinate: String,
},
NotACoordinateSymbol {
got: String,
},
NonOrthogonal {
pair: (usize, usize),
inner_product: String,
},
DegenerateScaleFactor {
index: usize,
scale_factor: String,
proven_zero: bool,
},
}
impl fmt::Display for VectorError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
VectorError::Differentiation(msg) => {
write!(f, "could not differentiate a field component: {msg}")
}
VectorError::RepeatedCoordinate { coordinate } => write!(
f,
"coordinate `{coordinate}` appears twice; the three coordinates must be distinct symbols"
),
VectorError::NotACoordinateSymbol { got } => write!(
f,
"`{got}` is not a symbol and cannot be used as a coordinate"
),
VectorError::NonOrthogonal {
pair,
inner_product,
} => write!(
f,
"the embedding is not orthogonal: tangent vectors {} and {} have inner product `{inner_product}`, which was not shown to vanish",
pair.0, pair.1
),
VectorError::DegenerateScaleFactor {
index,
scale_factor,
proven_zero,
} => {
if *proven_zero {
write!(
f,
"scale factor h{} = `{scale_factor}` is identically zero; the chart is degenerate",
index + 1
)
} else {
write!(
f,
"scale factor h{} = `{scale_factor}` could not be shown to be non-zero, and every operator divides by it",
index + 1
)
}
}
}
}
}
impl std::error::Error for VectorError {}
impl crate::errors::AlkahestError for VectorError {
fn code(&self) -> &'static str {
match self {
VectorError::Differentiation(_) => "E-VEC-001",
VectorError::RepeatedCoordinate { .. } => "E-VEC-002",
VectorError::NotACoordinateSymbol { .. } => "E-VEC-003",
VectorError::NonOrthogonal { .. } => "E-VEC-004",
VectorError::DegenerateScaleFactor { .. } => "E-VEC-005",
}
}
fn remediation(&self) -> Option<&'static str> {
match self {
VectorError::Differentiation(_) => Some(
"the field contains a function with no differentiation rule; register it in PrimitiveRegistry or rewrite the component",
),
VectorError::RepeatedCoordinate { .. } => Some(
"pass three distinct coordinate symbols, e.g. Coordinates::cylindrical(rho, phi, z)",
),
VectorError::NotACoordinateSymbol { .. } => {
Some("build each coordinate with `pool.symbol(..)` before constructing Coordinates")
}
VectorError::NonOrthogonal { .. } => Some(
"these formulas hold only for orthogonal charts; supply an orthogonal embedding, or work in Cartesian coordinates and transform the result by hand",
),
VectorError::DegenerateScaleFactor { .. } => Some(
"restrict the chart to a region where the scale factor is non-zero, or re-parametrise; a chart that collapses a direction has no unique orthonormal frame there",
),
}
}
}
pub fn dot(a: &[ExprId; 3], b: &[ExprId; 3], pool: &ExprPool) -> ExprId {
let terms: Vec<ExprId> = (0..3).map(|i| pool.mul(vec![a[i], b[i]])).collect();
simplify(pool.add(terms), pool).value
}
pub fn cross(a: &[ExprId; 3], b: &[ExprId; 3], pool: &ExprPool) -> [ExprId; 3] {
let neg = pool.integer(-1_i32);
let mut out = [a[0]; 3];
for (i, slot) in out.iter_mut().enumerate() {
let (j, k) = ((i + 1) % 3, (i + 2) % 3);
let plus = pool.mul(vec![a[j], b[k]]);
let minus = pool.mul(vec![neg, a[k], b[j]]);
*slot = simplify(pool.add(vec![plus, minus]), pool).value;
}
out
}
pub fn norm_squared(a: &[ExprId; 3], pool: &ExprPool) -> ExprId {
dot(a, a, pool)
}
pub fn norm(a: &[ExprId; 3], pool: &ExprPool) -> ExprId {
let sq = norm_squared(a, pool);
simplify(pool.func("sqrt", vec![sq]), pool).value
}
pub fn scale(s: ExprId, a: &[ExprId; 3], pool: &ExprPool) -> [ExprId; 3] {
let mut out = [a[0]; 3];
for i in 0..3 {
out[i] = simplify(pool.mul(vec![s, a[i]]), pool).value;
}
out
}
pub fn add(a: &[ExprId; 3], b: &[ExprId; 3], pool: &ExprPool) -> [ExprId; 3] {
let mut out = [a[0]; 3];
for i in 0..3 {
out[i] = simplify(pool.add(vec![a[i], b[i]]), pool).value;
}
out
}
pub fn sub(a: &[ExprId; 3], b: &[ExprId; 3], pool: &ExprPool) -> [ExprId; 3] {
let neg = pool.integer(-1_i32);
let mut out = [a[0]; 3];
for i in 0..3 {
let nb = pool.mul(vec![neg, b[i]]);
out[i] = simplify(pool.add(vec![a[i], nb]), pool).value;
}
out
}
fn d(expr: ExprId, var: ExprId, pool: &ExprPool) -> Result<ExprId, VectorError> {
diff(expr, var, pool)
.map(|r| r.value)
.map_err(|e| VectorError::Differentiation(e.to_string()))
}
fn is_one(id: ExprId, pool: &ExprPool) -> bool {
pool.with(id, |data| matches!(data, ExprData::Integer(n) if n.0 == 1))
}
fn divide(num: ExprId, den: ExprId, pool: &ExprPool) -> ExprId {
if is_one(den, pool) {
return num;
}
let inv = pool.pow(den, pool.integer(-1_i32));
pool.mul(vec![inv, num])
}
fn times(a: ExprId, b: ExprId, pool: &ExprPool) -> ExprId {
if is_one(a, pool) {
return b;
}
if is_one(b, pool) {
return a;
}
pool.mul(vec![a, b])
}
fn weight(num: ExprId, den: ExprId, pool: &ExprPool) -> ExprId {
if is_one(den, pool) {
return num;
}
simplify(divide(num, den, pool), pool).value
}
pub fn gradient(
f: ExprId,
coords: &Coordinates,
pool: &ExprPool,
) -> Result<[ExprId; 3], VectorError> {
let vars = coords.vars();
let h = coords.scale_factors();
let mut out = [f; 3];
for i in 0..3 {
let df = d(f, vars[i], pool)?;
out[i] = simplify(divide(df, h[i], pool), pool).value;
}
Ok(out)
}
pub fn divergence(
field: &[ExprId; 3],
coords: &Coordinates,
pool: &ExprPool,
) -> Result<ExprId, VectorError> {
let vars = coords.vars();
let h = coords.scale_factors();
let big_h = coords.jacobian_factor(pool);
let mut terms = Vec::with_capacity(3);
for i in 0..3 {
let weighted = times(weight(big_h, h[i], pool), field[i], pool);
terms.push(d(weighted, vars[i], pool)?);
}
let total = pool.add(terms);
Ok(simplify(divide(total, big_h, pool), pool).value)
}
pub fn curl(
field: &[ExprId; 3],
coords: &Coordinates,
pool: &ExprPool,
) -> Result<[ExprId; 3], VectorError> {
let vars = coords.vars();
let h = coords.scale_factors();
let neg = pool.integer(-1_i32);
let mut out = [field[0]; 3];
for (i, slot) in out.iter_mut().enumerate() {
let (j, k) = ((i + 1) % 3, (i + 2) % 3);
let hk_fk = times(h[k], field[k], pool);
let hj_fj = times(h[j], field[j], pool);
let a = d(hk_fk, vars[j], pool)?;
let b = d(hj_fj, vars[k], pool)?;
let bracket = pool.add(vec![a, pool.mul(vec![neg, b])]);
let den = simplify(times(h[j], h[k], pool), pool).value;
*slot = simplify(divide(bracket, den, pool), pool).value;
}
Ok(out)
}
pub fn laplacian(f: ExprId, coords: &Coordinates, pool: &ExprPool) -> Result<ExprId, VectorError> {
let vars = coords.vars();
let h = coords.scale_factors();
let big_h = coords.jacobian_factor(pool);
let mut terms = Vec::with_capacity(3);
for i in 0..3 {
let df = d(f, vars[i], pool)?;
let hi_sq = times(h[i], h[i], pool);
let weighted = times(weight(big_h, hi_sq, pool), df, pool);
terms.push(d(weighted, vars[i], pool)?);
}
let total = pool.add(terms);
Ok(simplify(divide(total, big_h, pool), pool).value)
}
pub fn vector_laplacian(
field: &[ExprId; 3],
coords: &Coordinates,
pool: &ExprPool,
) -> Result<[ExprId; 3], VectorError> {
if coords.is_cartesian(pool) {
let mut out = [field[0]; 3];
for i in 0..3 {
out[i] = laplacian(field[i], coords, pool)?;
}
return Ok(out);
}
let div = divergence(field, coords, pool)?;
let grad_div = gradient(div, coords, pool)?;
let cc = curl(&curl(field, coords, pool)?, coords, pool)?;
Ok(sub(&grad_div, &cc, pool))
}