use std::borrow::Cow;
use std::sync::Arc;
use nalgebra::DVector;
use crate::context::compute::ComputeContext;
use crate::context::error::OxiflowError;
use crate::context::value::ContextValue;
use crate::context::variable::ContextVariable;
use crate::mesh::Mesh;
use crate::model::traits::{PhysicalModel, RequiresContext};
use crate::operators::FluxDivergenceOperator;
fn negate_context_value(value: ContextValue) -> Result<ContextValue, OxiflowError> {
use ContextValue::*;
match value {
Scalar(x) => Ok(Scalar(-x)),
Vector(x) => Ok(Vector(-x)),
Matrix(x) => Ok(Matrix(-x)),
ScalarField(x) => Ok(ScalarField(-x)),
VectorField(x) => Ok(VectorField(-x)),
other => Err(OxiflowError::TypeMismatch {
expected: "negatable ContextValue (Scalar/Vector/Matrix/ScalarField/VectorField)",
actual: other.variant_name(),
}),
}
}
#[allow(dead_code)]
struct FluxDivergenceCalculator<Op: FluxDivergenceOperator> {
scheme: Arc<Op>,
mesh: Arc<Op::MeshType>,
instance_id: Cow<'static, str>,
}
pub struct DiscretizedModel<Op: FluxDivergenceOperator> {
scheme: Arc<Op>,
mesh: Arc<Op::MeshType>,
#[allow(dead_code)]
flux_calculator: FluxDivergenceCalculator<Op>,
name: String,
}
impl<Op: FluxDivergenceOperator> DiscretizedModel<Op> {
pub fn new(scheme: Arc<Op>, mesh: Arc<Op::MeshType>, name: impl Into<String>) -> Self {
let name = name.into();
let flux_calculator = FluxDivergenceCalculator {
scheme: Arc::clone(&scheme),
mesh: Arc::clone(&mesh),
instance_id: Cow::Owned(name.clone()),
};
Self {
scheme,
mesh,
flux_calculator,
name,
}
}
}
impl<Op: FluxDivergenceOperator> RequiresContext for DiscretizedModel<Op> {
fn required_variables(&self) -> Vec<ContextVariable> {
self.scheme.required_variables()
}
fn optional_variables(&self) -> Vec<ContextVariable> {
self.scheme.optional_variables()
}
fn depends_on(&self) -> Vec<ContextVariable> {
self.scheme.depends_on()
}
fn priority(&self) -> u32 {
self.scheme.priority()
}
}
impl<Op: FluxDivergenceOperator> PhysicalModel for DiscretizedModel<Op> {
fn compute_physics(
&self,
state: &ContextValue,
ctx: &ComputeContext,
) -> Result<ContextValue, OxiflowError> {
negate_context_value(self.scheme.apply(state, &self.mesh, ctx)?)
}
fn initial_state(&self, mesh: &dyn Mesh) -> ContextValue {
ContextValue::ScalarField(DVector::from_element(mesh.n_dof(), 0.0))
}
fn name(&self) -> &str {
&self.name
}
fn description(&self) -> Option<&str> {
Some("discretized model — wraps a FluxDivergenceOperator as -∇·F(u, ∇u) (DD-038)")
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::mesh::structured::UniformGrid1D;
struct ConstantFlux(f64);
impl RequiresContext for ConstantFlux {
fn required_variables(&self) -> Vec<ContextVariable> {
vec![ContextVariable::Time]
}
}
impl FluxDivergenceOperator for ConstantFlux {
type MeshType = UniformGrid1D;
fn apply(
&self,
field: &ContextValue,
_mesh: &Self::MeshType,
_ctx: &ComputeContext,
) -> Result<ContextValue, OxiflowError> {
let n = field.as_scalar_field()?.len();
Ok(ContextValue::ScalarField(DVector::from_element(n, self.0)))
}
}
fn fixture(value: f64) -> DiscretizedModel<ConstantFlux> {
let mesh = Arc::new(UniformGrid1D::new(4, 0.0, 1.0).unwrap());
DiscretizedModel::new(Arc::new(ConstantFlux(value)), mesh, "constant_flux_test")
}
#[test]
fn compute_physics_negates_scheme_output() {
let model = fixture(3.0);
let state = ContextValue::ScalarField(DVector::from_element(4, 0.0));
let ctx = ComputeContext::new(0.0, 0.01);
let result = model.compute_physics(&state, &ctx).unwrap();
assert!(result
.as_scalar_field()
.unwrap()
.iter()
.all(|&v| (v + 3.0).abs() < 1e-12));
}
#[test]
fn required_variables_delegates_to_scheme() {
let model = fixture(1.0);
assert_eq!(model.required_variables(), vec![ContextVariable::Time]);
}
#[test]
fn initial_state_is_zero_sized_from_mesh() {
let model = fixture(1.0);
let mesh = UniformGrid1D::new(6, 0.0, 1.0).unwrap();
let state = model.initial_state(&mesh);
let field = state.as_scalar_field().unwrap();
assert_eq!(field.len(), 6);
assert!(field.iter().all(|&v| v == 0.0));
}
#[test]
fn name_returns_identifier() {
let model = fixture(1.0);
assert_eq!(model.name(), "constant_flux_test");
}
#[test]
fn negate_context_value_rejects_boolean() {
let err = negate_context_value(ContextValue::Boolean(true)).unwrap_err();
assert!(matches!(err, OxiflowError::TypeMismatch { .. }));
}
}