use alloc::format;
use core::fmt::{Debug, Display};
use deep_causality_algebra::RealField;
use deep_causality_num::FromPrimitive;
use deep_causality_par::MaybeParallel;
use deep_causality_tensor::CausalTensor;
use deep_causality_topology::{HodgeDecomposition, LatticeComplex, Manifold};
use crate::error::physics_error::PhysicsError;
use crate::quantities::fluid_dynamics::velocity_one_form::VelocityOneForm;
#[derive(Debug, Clone, PartialEq)]
pub struct SolenoidalField<R: RealField> {
field: CausalTensor<R>,
}
impl<R> SolenoidalField<R>
where
R: RealField + MaybeParallel + FromPrimitive + Default + PartialEq + Debug + Display,
{
pub fn from_leray_projection<const D: usize>(
velocity: &VelocityOneForm<R>,
manifold: &Manifold<LatticeComplex<D, R>, R>,
) -> Result<(Self, CausalTensor<R>), PhysicsError> {
Self::from_leray_projection_opts(
velocity,
manifold,
&deep_causality_topology::HodgeDecomposeOptions::default(),
)
}
pub fn from_leray_projection_opts<const D: usize>(
velocity: &VelocityOneForm<R>,
manifold: &Manifold<LatticeComplex<D, R>, R>,
opts: &deep_causality_topology::HodgeDecomposeOptions<R>,
) -> Result<(Self, CausalTensor<R>), PhysicsError> {
let projection = manifold
.leray_project_opts(velocity.as_tensor(), opts)
.map_err(|e| PhysicsError::TopologyError(format!("Leray projection failed: {e}")))?;
let (projected, potential) = projection.into_parts();
Ok((Self { field: projected }, potential))
}
#[allow(clippy::too_many_arguments)]
pub fn from_open_leray_projection_weighted_opts<const D: usize>(
velocity: &VelocityOneForm<R>,
manifold: &Manifold<LatticeComplex<D, R>, R>,
zeroed: &[usize],
prescribed: &[usize],
reference: &[usize],
rows: &[deep_causality_topology::CutFaceConstraint<R>],
opts: &deep_causality_topology::HodgeDecomposeOptions<R>,
x0: Option<&[R]>,
) -> Result<(Self, CausalTensor<R>), PhysicsError> {
let projection = manifold
.leray_project_open_weighted_opts(
velocity.as_tensor(),
zeroed,
prescribed,
reference,
rows,
opts,
x0,
)
.map_err(|e| {
PhysicsError::TopologyError(format!("open weighted Leray projection failed: {e}"))
})?;
let (projected, potential) = projection.into_parts();
Ok((Self { field: projected }, potential))
}
pub fn from_hodge_projection(
decomposition: &HodgeDecomposition<R>,
) -> Result<Self, PhysicsError> {
if decomposition.grade() != 1 {
return Err(PhysicsError::DimensionMismatch(format!(
"SolenoidalField requires a grade-1 decomposition, got grade {}",
decomposition.grade()
)));
}
let co_exact = decomposition.co_exact().as_slice();
let harmonic = decomposition.harmonic().as_slice();
if co_exact.len() != harmonic.len() {
return Err(PhysicsError::DimensionMismatch(format!(
"SolenoidalField: component length mismatch ({} vs {})",
co_exact.len(),
harmonic.len()
)));
}
let data: alloc::vec::Vec<R> = co_exact
.iter()
.zip(harmonic.iter())
.map(|(b, h)| *b + *h)
.collect();
if let Some(idx) = data.iter().position(|v| !v.is_finite()) {
return Err(PhysicsError::NumericalInstability(format!(
"SolenoidalField: non-finite coefficient at index {idx}"
)));
}
let len = data.len();
let field =
CausalTensor::new(data, alloc::vec![len]).expect("1-D tensor allocation cannot fail");
Ok(Self { field })
}
pub fn constrain_edges(self, edges: &[usize]) -> Self {
if edges.is_empty() {
return self;
}
let mut data = self.field.into_vec();
for &e in edges {
data[e] = R::zero();
}
let len = data.len();
Self {
field: CausalTensor::new(data, alloc::vec![len])
.expect("1-D tensor allocation cannot fail"),
}
}
pub fn with_lift(self, lift: &[(usize, R)]) -> Self {
if lift.is_empty() {
return self;
}
let mut data = self.field.into_vec();
for &(e, value) in lift {
data[e] = value;
}
let len = data.len();
Self {
field: CausalTensor::new(data, alloc::vec![len])
.expect("1-D tensor allocation cannot fail"),
}
}
pub fn as_one_form(&self) -> &CausalTensor<R> {
&self.field
}
pub fn len(&self) -> usize {
self.field.len()
}
pub fn is_empty(&self) -> bool {
self.field.len() == 0
}
}