use super::VectorError;
use crate::diff::diff;
use crate::kernel::{ExprData, ExprId, ExprPool};
use crate::matrix::zero_test::{zero_status, ZeroStatus};
use crate::simplify::engine::simplify;
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Coordinates {
vars: [ExprId; 3],
scale: [ExprId; 3],
label: String,
}
fn validate_vars(vars: &[ExprId; 3], pool: &ExprPool) -> Result<(), VectorError> {
for (i, &v) in vars.iter().enumerate() {
let is_symbol = pool.with(v, |d| matches!(d, ExprData::Symbol { .. }));
if !is_symbol {
return Err(VectorError::NotACoordinateSymbol {
got: pool.display(v).to_string(),
});
}
for &w in &vars[..i] {
if v == w {
return Err(VectorError::RepeatedCoordinate {
coordinate: pool.display(v).to_string(),
});
}
}
}
Ok(())
}
impl Coordinates {
pub fn cartesian(
x: ExprId,
y: ExprId,
z: ExprId,
pool: &ExprPool,
) -> Result<Self, VectorError> {
let vars = [x, y, z];
validate_vars(&vars, pool)?;
let one = pool.integer(1_i32);
Ok(Coordinates {
vars,
scale: [one; 3],
label: "cartesian".to_string(),
})
}
pub fn cylindrical(
rho: ExprId,
phi: ExprId,
z: ExprId,
pool: &ExprPool,
) -> Result<Self, VectorError> {
let vars = [rho, phi, z];
validate_vars(&vars, pool)?;
let one = pool.integer(1_i32);
Ok(Coordinates {
vars,
scale: [one, rho, one],
label: "cylindrical".to_string(),
})
}
pub fn spherical(
r: ExprId,
theta: ExprId,
phi: ExprId,
pool: &ExprPool,
) -> Result<Self, VectorError> {
let vars = [r, theta, phi];
validate_vars(&vars, pool)?;
let one = pool.integer(1_i32);
let sin_theta = pool.func("sin", vec![theta]);
let r_sin_theta = pool.mul(vec![r, sin_theta]);
Ok(Coordinates {
vars,
scale: [one, r, r_sin_theta],
label: "spherical".to_string(),
})
}
pub fn from_embedding(
vars: [ExprId; 3],
embedding: [ExprId; 3],
label: impl Into<String>,
pool: &ExprPool,
) -> Result<Self, VectorError> {
validate_vars(&vars, pool)?;
let mut tangent = [[embedding[0]; 3]; 3];
for (i, &u) in vars.iter().enumerate() {
for (k, &component) in embedding.iter().enumerate() {
let partial = diff(component, u, pool)
.map_err(|e| VectorError::Differentiation(e.to_string()))?
.value;
tangent[i][k] = simplify(partial, pool).value;
}
}
for i in 0..3 {
for j in (i + 1)..3 {
let inner = super::dot(&tangent[i], &tangent[j], pool);
let status = zero_status(pool, inner);
if status != ZeroStatus::Zero {
return Err(VectorError::NonOrthogonal {
pair: (i, j),
inner_product: pool.display(inner).to_string(),
});
}
}
}
let mut scale = [embedding[0]; 3];
for i in 0..3 {
let h_sq = super::norm_squared(&tangent[i], pool);
let status = zero_status(pool, h_sq);
if status != ZeroStatus::NonZero {
let h = simplify(pool.func("sqrt", vec![h_sq]), pool).value;
return Err(VectorError::DegenerateScaleFactor {
index: i,
scale_factor: pool.display(h).to_string(),
proven_zero: status == ZeroStatus::Zero,
});
}
scale[i] = simplify(pool.func("sqrt", vec![h_sq]), pool).value;
}
Ok(Coordinates {
vars,
scale,
label: label.into(),
})
}
pub fn vars(&self) -> [ExprId; 3] {
self.vars
}
pub fn scale_factors(&self) -> [ExprId; 3] {
self.scale
}
pub fn label(&self) -> &str {
&self.label
}
pub fn jacobian_factor(&self, pool: &ExprPool) -> ExprId {
simplify(pool.mul(self.scale.to_vec()), pool).value
}
pub fn is_cartesian(&self, pool: &ExprPool) -> bool {
self.scale
.iter()
.all(|&h| pool.with(h, |d| matches!(d, ExprData::Integer(n) if n.0 == 1)))
}
}