use crate::{LorentzianMetric, PhysicsError};
use core::fmt::Debug;
use core::iter::Sum;
use deep_causality_num::{FromPrimitive, RealField};
use deep_causality_sparse::CsrMatrix;
use deep_causality_tensor::{CausalTensor, EinSumOp, Tensor};
use deep_causality_topology::SimplicialManifold;
pub fn relativistic_current_kernel<R, M>(
em_manifold: &SimplicialManifold<R, R>,
spacetime_metric: &M,
) -> Result<CausalTensor<R>, PhysicsError>
where
R: RealField + FromPrimitive + Default + PartialEq + Debug,
M: LorentzianMetric,
{
let complex = em_manifold.complex();
let skeletons = complex.skeletons();
if skeletons.len() < 3 {
return Err(PhysicsError::DimensionMismatch(
"Requires at least 2-simplices for EM 2-form".into(),
));
}
if spacetime_metric.dimension() < 4 {
return Err(PhysicsError::DimensionMismatch(format!(
"Spacetime needs 4D, got {}D",
spacetime_metric.dimension()
)));
}
let hodge_ops = complex
.hodge_star_operators()
.map_err(|e| PhysicsError::CalculationError(format!("Hodge ⋆ unavailable: {}", e)))?;
let coboundary_ops = complex.coboundary_operators();
if hodge_ops.len() < 4 {
return Err(PhysicsError::CalculationError(format!(
"Missing Hodge star operators: need 4, have {}",
hodge_ops.len()
)));
}
if coboundary_ops.len() < 3 {
return Err(PhysicsError::CalculationError(format!(
"Missing coboundary operators: need 3, have {}",
coboundary_ops.len()
)));
}
let n0 = skeletons[0].simplices().len();
let n1 = skeletons[1].simplices().len();
let n2 = skeletons[2].simplices().len();
let data = em_manifold.data().as_slice();
if data.len() < n0 + n1 + n2 {
return Err(PhysicsError::CalculationError(
"Manifold data too short for 2-form extraction".into(),
));
}
let f_2form: Vec<R> = data[n0 + n1..n0 + n1 + n2].to_vec();
let star_f = apply_csr_real(&hodge_ops[2], &f_2form);
let d_star_f = apply_csr_i8(&coboundary_ops[2], &star_f);
let j_data = apply_csr_real(&hodge_ops[3], &d_star_f);
let len = j_data.len();
CausalTensor::new(j_data, vec![len]).map_err(PhysicsError::from)
}
#[allow(clippy::needless_range_loop)]
fn apply_csr_real<R>(matrix: &CsrMatrix<R>, vec: &[R]) -> Vec<R>
where
R: RealField,
{
let n_rows = matrix.shape().0;
let mut result = vec![R::zero(); n_rows];
for row in 0..n_rows {
let row_start = matrix.row_indices()[row];
let row_end = matrix.row_indices()[row + 1];
for idx in row_start..row_end {
let col = matrix.col_indices()[idx];
let val = matrix.values()[idx];
if col < vec.len() {
result[row] += val * vec[col];
}
}
}
result
}
#[allow(clippy::needless_range_loop)]
fn apply_csr_i8<R>(matrix: &CsrMatrix<i8>, vec: &[R]) -> Vec<R>
where
R: RealField + FromPrimitive,
{
let n_rows = matrix.shape().0;
let mut result = vec![R::zero(); n_rows];
for row in 0..n_rows {
let row_start = matrix.row_indices()[row];
let row_end = matrix.row_indices()[row + 1];
for idx in row_start..row_end {
let col = matrix.col_indices()[idx];
let val_i8 = matrix.values()[idx];
let val = R::from_f64(val_i8 as f64).expect("R::from_f64(i8) failed");
if col < vec.len() {
result[row] += val * vec[col];
}
}
}
result
}
pub fn energy_momentum_tensor_em_kernel<R>(
em_tensor: &CausalTensor<R>,
metric: &CausalTensor<R>,
) -> Result<CausalTensor<R>, PhysicsError>
where
R: RealField + FromPrimitive + Sum + Default + PartialOrd + Debug,
{
if em_tensor.num_dim() != 2 || metric.num_dim() != 2 {
return Err(PhysicsError::DimensionMismatch(
"Tensors must be Rank 2".into(),
));
}
let gf = metric.matmul(em_tensor)?;
let f_lower = gf.matmul(metric)?;
let f2_op =
EinSumOp::<R>::contraction(em_tensor.clone(), f_lower.clone(), vec![0, 1], vec![0, 1]);
let f2_tensor = CausalTensor::ein_sum(&f2_op)?;
let f2_val = if f2_tensor.shape().is_empty()
|| (f2_tensor.shape().len() == 1 && f2_tensor.shape()[0] == 1)
{
f2_tensor.data()[0]
} else {
return Err(PhysicsError::CalculationError(
"Scalar contraction failed".into(),
));
};
let f_mixed = em_tensor.matmul(metric)?;
let f_mixed_t_op = EinSumOp::<R>::transpose(f_mixed.clone(), vec![1, 0]);
let f_mixed_t = CausalTensor::ein_sum(&f_mixed_t_op)?;
let term1 = em_tensor.matmul(&f_mixed_t)?;
let metric_inv = metric.inverse()?;
let quarter = R::from_f64(0.25)
.ok_or_else(|| PhysicsError::NumericalInstability("R::from_f64(0.25) failed".into()))?;
let term2 = metric_inv * (quarter * f2_val);
let stress_energy = term1 - term2;
Ok(stress_energy)
}