use crate::{PhysicsError, StiffnessTensor, Strain, Stress, StressTensor, Temperature};
use deep_causality_num::{FromPrimitive, RealField};
use deep_causality_tensor::{CausalTensor, EinSumOp, Tensor};
pub fn hookes_law_kernel<R>(
stiffness: &StiffnessTensor<R>,
strain: &Strain<R>,
) -> Result<StressTensor<R>, PhysicsError>
where
R: RealField + Default,
{
if stiffness.inner().num_dim() != 4 || strain.inner().num_dim() != 2 {
return Err(PhysicsError::DimensionMismatch(format!(
"Hooke's Law requires Stiffness Rank 4 and Strain Rank 2. Got {} and {}",
stiffness.inner().num_dim(),
strain.inner().num_dim()
)));
}
let op = EinSumOp::<R>::contraction(
stiffness.inner().clone(),
strain.inner().clone(),
vec![2, 3], vec![0, 1], );
let res = CausalTensor::ein_sum(&op)?;
Ok(StressTensor::new(res))
}
pub fn von_mises_stress_kernel<R>(stress: &StressTensor<R>) -> Result<Stress<R>, PhysicsError>
where
R: RealField + Default + FromPrimitive,
{
let s = stress.inner();
if s.num_dim() != 2 || s.shape() != [3, 3] {
return Err(PhysicsError::DimensionMismatch(
"Von Mises requires 3x3 Stress Tensor".into(),
));
}
let trace_op = EinSumOp::<R>::trace(s.clone(), 0, 1);
let trace_tensor = CausalTensor::ein_sum(&trace_op)?;
let trace_val = if trace_tensor.shape().is_empty()
|| (trace_tensor.shape().len() == 1 && trace_tensor.shape()[0] == 1)
{
trace_tensor.data()[0]
} else {
return Err(PhysicsError::CalculationError("Trace failed".into()));
};
let three = R::from_f64(3.0)
.ok_or_else(|| PhysicsError::NumericalInstability("R::from_f64(3.0) failed".into()))?;
let sigma_m = trace_val / three;
let identity = CausalTensor::<R>::identity(&[3, 3])?;
let mean_stress_tensor = identity * sigma_m;
let s_deviatoric: CausalTensor<R> = s.clone() - mean_stress_tensor;
let j2_op = EinSumOp::<R>::contraction(
s_deviatoric.clone(),
s_deviatoric.clone(),
vec![0, 1],
vec![0, 1],
);
let j2_tensor = CausalTensor::ein_sum(&j2_op)?;
let j2_val = if j2_tensor.shape().is_empty()
|| (j2_tensor.shape().len() == 1 && j2_tensor.shape()[0] == 1)
{
j2_tensor.data()[0]
} else {
return Err(PhysicsError::CalculationError(
"J2 calculation failed".into(),
));
};
let half = R::from_f64(0.5)
.ok_or_else(|| PhysicsError::NumericalInstability("R::from_f64(0.5) failed".into()))?;
let j2 = half * j2_val;
let vm = (three * j2).sqrt();
Stress::new(vm)
}
pub fn thermal_expansion_kernel<R>(
coeff: R,
delta_temp: Temperature<R>,
) -> Result<CausalTensor<R>, PhysicsError>
where
R: deep_causality_num::RealField + Default + PartialOrd,
{
let val = coeff * delta_temp.value();
let identity = CausalTensor::<R>::identity(&[3, 3])?;
let strain = identity * val;
Ok(strain)
}