use crate::context::compute::ComputeContext;
use crate::context::error::OxiflowError;
use crate::context::state::MultiDomainState;
use crate::model::traits::RequiresContext;
use crate::solver::scenario::DomainId;
#[non_exhaustive]
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Interface {
source: DomainId,
target: DomainId,
label: Option<String>,
}
impl Interface {
pub fn new(source: DomainId, target: DomainId) -> Self {
Self {
source,
target,
label: None,
}
}
pub fn with_label(mut self, label: impl Into<String>) -> Self {
self.label = Some(label.into());
self
}
pub fn source(&self) -> &DomainId {
&self.source
}
pub fn target(&self) -> &DomainId {
&self.target
}
pub fn label(&self) -> Option<&str> {
self.label.as_deref()
}
}
impl std::fmt::Display for Interface {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match &self.label {
Some(l) => write!(f, "Interface({} → {} [{}])", self.source, self.target, l),
None => write!(f, "Interface({} → {})", self.source, self.target),
}
}
}
pub trait CouplingOperator: RequiresContext + Send + Sync {
fn apply(
&self,
states: &MultiDomainState,
ctx: &ComputeContext,
interface: &Interface,
) -> Result<MultiDomainState, OxiflowError>;
fn interface(&self) -> &Interface;
}
#[cfg(test)]
mod tests {
use super::*;
fn lahar() -> DomainId {
DomainId::new("lahar")
}
fn lake() -> DomainId {
DomainId::new("lake")
}
#[test]
fn interface_new() {
let iface = Interface::new(lahar(), lake());
assert_eq!(iface.source(), &lahar());
assert_eq!(iface.target(), &lake());
assert!(iface.label().is_none());
}
#[test]
fn interface_with_label() {
let iface = Interface::new(lahar(), lake()).with_label("lahar-lake");
assert_eq!(iface.label(), Some("lahar-lake"));
}
#[test]
fn interface_display_no_label() {
let iface = Interface::new(lahar(), lake());
assert_eq!(format!("{}", iface), "Interface(lahar → lake)");
}
#[test]
fn interface_display_with_label() {
let iface = Interface::new(lahar(), lake()).with_label("lahar-lake");
assert_eq!(format!("{}", iface), "Interface(lahar → lake [lahar-lake])");
}
#[test]
fn interface_clone_equals_original() {
let iface = Interface::new(lahar(), lake()).with_label("test");
assert_eq!(iface.clone(), iface);
}
#[test]
fn coupling_operator_is_object_safe() {
fn assert_object_safe<T: CouplingOperator + ?Sized>() {}
assert_object_safe::<dyn CouplingOperator>();
}
use crate::context::quantity::PhysicalQuantity;
use crate::context::value::ContextValue;
use crate::context::variable::ContextVariable;
use nalgebra::DVector;
struct PassthroughCoupling {
interface: Interface,
}
impl RequiresContext for PassthroughCoupling {
fn required_variables(&self) -> Vec<ContextVariable> {
vec![]
}
}
impl CouplingOperator for PassthroughCoupling {
fn apply(
&self,
states: &MultiDomainState,
_ctx: &ComputeContext,
_interface: &Interface,
) -> Result<MultiDomainState, OxiflowError> {
Ok(states.clone())
}
fn interface(&self) -> &Interface {
&self.interface
}
}
#[test]
fn passthrough_coupling_returns_unchanged_state() {
let iface = Interface::new(lahar(), lake());
let op = PassthroughCoupling {
interface: iface.clone(),
};
let mut states = MultiDomainState::new();
states.set(
lahar(),
PhysicalQuantity::concentration(),
ContextValue::ScalarField(DVector::from_vec(vec![1.0, 2.0])),
);
let ctx = ComputeContext::new(0.0, 0.1);
let result = op.apply(&states, &ctx, &iface).unwrap();
assert!(result
.get(&lahar(), &PhysicalQuantity::concentration())
.is_some());
}
#[test]
fn coupling_operator_interface_accessor() {
let iface = Interface::new(lahar(), lake());
let op = PassthroughCoupling {
interface: iface.clone(),
};
assert_eq!(op.interface(), &iface);
}
}