pub fn vec_add(
v1: &[f64],
v2: &[f64],
) -> Result<Vec<f64>, String> {
if v1.len() != v2.len() {
return Err(format!(
"Dimension mismatch: \
v1.len() = {}, v2.len() \
= {}",
v1.len(),
v2.len()
));
}
Ok(v1.iter().zip(v2.iter()).map(|(a, b)| a + b).collect())
}
pub fn vec_sub(
v1: &[f64],
v2: &[f64],
) -> Result<Vec<f64>, String> {
if v1.len() != v2.len() {
return Err(format!(
"Dimension mismatch: \
v1.len() = {}, v2.len() \
= {}",
v1.len(),
v2.len()
));
}
Ok(v1.iter().zip(v2.iter()).map(|(a, b)| a - b).collect())
}
#[must_use]
pub fn scalar_mul(
v: &[f64],
s: f64,
) -> Vec<f64> {
v.iter().map(|&a| a * s).collect()
}
pub fn dot_product(
v1: &[f64],
v2: &[f64],
) -> Result<f64, String> {
if v1.len() != v2.len() {
return Err(format!(
"Dimension mismatch: \
v1.len() = {}, v2.len() \
= {}",
v1.len(),
v2.len()
));
}
Ok(v1.iter().zip(v2.iter()).map(|(a, b)| a * b).sum())
}
#[must_use]
pub fn norm(v: &[f64]) -> f64 {
v.iter().map(|&a| a * a).sum::<f64>().sqrt()
}
#[must_use]
pub fn l1_norm(v: &[f64]) -> f64 {
v.iter().map(|&a| a.abs()).sum()
}
#[must_use]
pub fn linf_norm(v: &[f64]) -> f64 {
v.iter()
.map(|&a| a.abs())
.fold(0.0, |max, val| if val > max { val } else { max })
}
#[must_use]
pub fn lp_norm(
v: &[f64],
p: f64,
) -> f64 {
if p <= 0.0 {
return f64::NAN;
}
if (p - 1.0).abs() < f64::EPSILON {
return l1_norm(v);
}
if p.is_infinite() {
return linf_norm(v);
}
v.iter()
.map(|&a| a.abs().powf(p))
.sum::<f64>()
.powf(1.0 / p)
}
pub fn normalize(v: &[f64]) -> Result<Vec<f64>, String> {
let n = norm(v);
if n == 0.0 {
return Err("Cannot normalize a zero \
vector."
.to_string());
}
Ok(scalar_mul(v, 1.0 / n))
}
pub fn cross_product(
v1: &[f64],
v2: &[f64],
) -> Result<Vec<f64>, String> {
if v1.len() != 3 || v2.len() != 3 {
return Err("Cross product \
is only defined \
for 3D vectors."
.to_string());
}
Ok(vec![
v1[1].mul_add(v2[2], -(v1[2] * v2[1])),
v1[2].mul_add(v2[0], -(v1[0] * v2[2])),
v1[0].mul_add(v2[1], -(v1[1] * v2[0])),
])
}
pub fn distance(
v1: &[f64],
v2: &[f64],
) -> Result<f64, String> {
let diff = vec_sub(v1, v2)?;
Ok(norm(&diff))
}
pub fn angle(
v1: &[f64],
v2: &[f64],
) -> Result<f64, String> {
let n1 = norm(v1);
let n2 = norm(v2);
if n1 == 0.0 || n2 == 0.0 {
return Ok(0.0);
}
let dot = dot_product(v1, v2)?;
let cos_theta = (dot / (n1 * n2)).clamp(-1.0, 1.0);
Ok(cos_theta.acos())
}
pub fn project(
v1: &[f64],
v2: &[f64],
) -> Result<Vec<f64>, String> {
if v1.len() != v2.len() {
return Err("Vectors must \
have the same \
dimension."
.to_string());
}
let dot = dot_product(v1, v2)?;
let dot2 = dot_product(v2, v2)?;
if dot2 == 0.0 {
return Ok(vec![0.0; v1.len()]);
}
Ok(scalar_mul(v2, dot / dot2))
}
pub fn reflect(
v: &[f64],
n: &[f64],
) -> Result<Vec<f64>, String> {
if v.len() != n.len() {
return Err("Vectors must \
have the same \
dimension."
.to_string());
}
let dot = dot_product(v, n)?;
let scaled_n = scalar_mul(n, 2.0 * dot);
vec_sub(v, &scaled_n)
}
pub fn lerp(
v1: &[f64],
v2: &[f64],
t: f64,
) -> Result<Vec<f64>, String> {
if v1.len() != v2.len() {
return Err("Vectors must \
have the same \
dimension."
.to_string());
}
Ok(v1
.iter()
.zip(v2.iter())
.map(|(&a, &b)| (1.0 - t).mul_add(a, t * b))
.collect())
}
pub fn is_orthogonal(
v1: &[f64],
v2: &[f64],
epsilon: f64,
) -> Result<bool, String> {
let dot = dot_product(v1, v2)?;
Ok(dot.abs() < epsilon)
}
pub fn is_parallel(
v1: &[f64],
v2: &[f64],
epsilon: f64,
) -> Result<bool, String> {
if v1.len() != v2.len() {
return Err("Vectors must \
have the same \
dimension."
.to_string());
}
let n1 = norm(v1);
let n2 = norm(v2);
if n1 == 0.0 || n2 == 0.0 {
return Ok(true);
}
let dot = dot_product(v1, v2)?;
let cos_theta = (dot / (n1 * n2)).abs();
Ok((1.0 - cos_theta).abs() < epsilon)
}
pub fn cosine_similarity(
v1: &[f64],
v2: &[f64],
) -> Result<f64, String> {
let n1 = norm(v1);
let n2 = norm(v2);
if n1 == 0.0 || n2 == 0.0 {
return Err("Cosine similarity is \
undefined for zero \
vectors."
.to_string());
}
let dot = dot_product(v1, v2)?;
Ok(dot / (n1 * n2))
}