--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/dolfinx_access/function.rs Thu Feb 26 09:32:12 2026 -0500 @@ -0,0 +1,812 @@ +/*! +Python and C++ wrapper for Dolfinx Function<f64> +*/ + +use super::ffi; +use super::ffi::Function_f64; +use alg_tools::bounds::{Bounds, GlobalAnalysis, MinMaxMapping}; +use alg_tools::euclidean::Euclidean; +use alg_tools::fe_model::base::LocalModel; +use alg_tools::instance::Instance; +use alg_tools::loc::Loc; +use alg_tools::mapping::{BasicDecomposition, DifferentiableImpl, Mapping, Space}; +use alg_tools::norms::{Dist, HasDual, Norm, Normed, L2}; +use itertools::izip; +use pyo3::ffi::c_str; +use pyo3::prelude::*; +use pyo3::types::{PyDict, PyFunction}; +use std::ffi::CStr; + +/// A helper structure of dealing with dolfinx functions. +/// `N` is the domain dimension, `O` the order, and `D` is the codomain dimension. +#[allow(non_camel_case_types)] +pub struct DolfinxPyFunction_f64<'py, const N: u32, const O: u32 = 1, const D: u32 = 1> { + #[allow(dead_code)] + /// Python object. + /// + /// This is crucial for maintaining [`cxx`] live, when we obtained the object from Python. + /// + /// If we needed to copy-on-write, or initialise without obtaining and object from Python, + /// this will become `None`. + pub u: Option<Bound<'py, PyAny>>, + /// We need to maintain a reference to the FunctionSpace Python wrapper, as it's difficult + /// to recreate if we loose `py`. Fenics is a one-way hell full of pointless wrappers upon + /// wrappers upon wrappers,very difficult to create having with just the unwrapped object. + function_space: Bound<'py, PyAny>, + /// Do we have the only reference? + /// + /// This is check on construction only, when we have the GIL. + /// If we have the only reference at that time, this will not change during the lifetime. + /// We use this to implement copy-on-write optimisations. + owned: bool, + /// C++ object pointer + cxx: *mut Function_f64, +} + +struct Helpers { + dot: Py<PyFunction>, + norm2_squared: Py<PyFunction>, + dist2_squared: Py<PyFunction>, +} + +use std::sync::OnceLock; +static HELPERS: OnceLock<Helpers> = OnceLock::new(); + +fn get_helpers(py: Python<'_>) -> PyResult<&Helpers> { + HELPERS.get_or_try_init(|| { + let extras = PyModule::import(py, "pointsource_pde.dolfinx_extras")?; + let g = |s| -> PyResult<Py<PyFunction>> { Ok(extras.getattr(s)?.cast_into()?.unbind()) }; + Ok(Helpers { + dot: g("dot")?, + norm2_squared: g("norm2_squared")?, + dist2_squared: g("dist2_squared")?, + }) + }) +} + +impl<'py, const N: u32, const O: u32, const D: u32> Drop for DolfinxPyFunction_f64<'py, N, O, D> { + fn drop(&mut self) { + // If Python is not managing our C++ object, we need to free it + if let None = self.u { + if self.cxx != std::ptr::null_mut() { + unsafe { ffi::drop_Function_f64(self.cxx) }; + } + } + } +} + +impl<'py, const N: u32, const O: u32, const D: u32> Clone for DolfinxPyFunction_f64<'py, N, O, D> { + fn clone(&self) -> Self { + DolfinxPyFunction_f64 { + u: None, // It's a different new function, so no Python instance + function_space: self.function_space.clone(), + owned: self.owned, + cxx: unsafe { ffi::clone_Function_f64(self.cxx) }, + } + } +} + +impl<'py, const N: u32, const O: u32, const D: u32> Space for DolfinxPyFunction_f64<'py, N, O, D> { + type Principal = Self; + type Decomp = BasicDecomposition; +} + +impl<'py, const N: u32, const O: u32, const D: u32> DolfinxPyFunction_f64<'py, N, O, D> { + /// Create a new instance, assuming `cxx` has been correctly extracted from `u` + /// and checked to match `N`, `O`, `D`, and mesh restrictions. + pub(crate) fn new_prechecked(u: Bound<'py, PyAny>, cxx: *mut Function_f64) -> PyResult<Self> { + let function_space = u.getattr("_V")?; + let owned = u.get_refcnt() <= 1; + // TODO: check dimensionality + Ok(DolfinxPyFunction_f64 { u: Some(u), function_space, owned, cxx }) + } + + /// Ensures that `self.u` exists. + /// Panics if there's a failure to create it if needed. + pub(crate) fn _make_py(&mut self, py: Python<'py>) { + if let None = self.u { + // We need to do a lot of work here, due to Fenics one-wayness. + + // This takes ownership of our pointer, so if we fail to create the Python + // object, we have a lot of restoring to do. + let cpp_object = unsafe { + Py::<PyAny>::from_owned_ptr( + py, + ffi::wrap_Function_f64(self.cxx) as *mut pyo3::ffi::PyObject, + ) + }; + let locals = PyDict::new(py); + match locals.set_item("cpp_object", cpp_object).and_then(|()| { + locals.set_item("V", self.function_space.as_borrowed())?; + // TODO: precompile + py.run(CREATE_FUNCTION_HACK, None, Some(&locals))?; + locals.get_item("wrapper") + }) { + Err(e) => { + // We should restore to recover + panic!("Failure to pass object to Python: {:?}", e); + } + Ok(maybe_p) => { + self.u = Some(maybe_p.unwrap()); + } + } + } + } + + /// Returns a (possibly new) Python presentation, without consuming current object. + /// + /// Please use this functionality through [`pyo3::types::IntoPyObject`]. + pub(crate) fn _into_py_ref<'a>( + &'a mut self, + py: Python<'py>, + ) -> PyResult<pyo3::Borrowed<'a, 'py, PyAny>> { + self._make_py(py); + self.owned = false; + match self.u { + Some(ref p) => Ok(p.as_borrowed()), + None => unreachable!("This cannot happen"), + } + } + + /// Returns a (possibly new) Python presentation, consuming current object. + /// + /// Please use this functionality through [`pyo3::types::IntoPyObject`]. + pub(crate) fn _into_py(mut self, py: Python<'py>) -> PyResult<pyo3::Bound<'py, PyAny>> { + self._make_py(py); + self.cxx = std::ptr::null_mut(); + self.owned = false; + match std::mem::replace(&mut self.u, None) { + Some(p) => Ok(p), + None => unreachable!("This cannot happen"), + } + } + + /// Create a new FEM function on the same function space, with unintialised data. + pub fn similar(&self) -> Self { + DolfinxPyFunction_f64 { + u: None, + function_space: self.function_space.clone(), + owned: true, + cxx: unsafe { ffi::similar_Function_f64(self.cxx) }, + } + } + + /// Create a new FEM function on the same function space, with zero data. + pub fn similar_zeros(&self) -> Self { + let new = self.similar(); + for x in unsafe { ffi::data_mut_Function_f64(new.cxx) } { + *x = 0.0; + } + new + } +} + +pub(crate) static CREATE_FUNCTION_HACK: &CStr = c_str!( + "\ +from dolfinx.fem import Function +from dolfinx.la import Vector +x = Vector(cpp_object.x) +# Fenics is very one-way. It does not provide a proper way to create the wrapper, +# directly from the unwrapped C++ +wrapper = Function.__new__(Function, V, x, \"?\", x.array.dtype) +# This all just repeats Function.__init__ without creating a new ._cpp_object. +super(Function, wrapper).__init__(V.ufl_function_space()) +wrapper._cpp_object = cpp_object; +wrapper._V = V +wrapper._x = x +wrapper.name = \"?\" +" +); + +trait ExpandCoordDolphinx { + fn to_dolphinx(self) -> [f64; 3]; + #[allow(dead_code)] + fn from_dolphinx(val: [f64; 3]) -> Self; +} + +impl ExpandCoordDolphinx for Loc<1, f64> { + #[inline] + fn to_dolphinx(self) -> [f64; 3] { + let Loc([x1]) = self; + [x1, 0.0, 0.0] + } + + #[inline] + fn from_dolphinx([x1, _, _]: [f64; 3]) -> Self { + [x1].into() + } +} + +impl ExpandCoordDolphinx for Loc<2, f64> { + #[inline] + fn to_dolphinx(self) -> [f64; 3] { + let Loc([x1, x2]) = self; + [x1, x2, 0.0] + } + + #[inline] + fn from_dolphinx([x1, x2, _]: [f64; 3]) -> Self { + [x1, x2].into() + } +} + +impl ExpandCoordDolphinx for Loc<3, f64> { + #[inline] + fn to_dolphinx(self) -> [f64; 3] { + let Loc([x1, x2, x3]) = self; + [x1, x2, x3] + } + + #[inline] + fn from_dolphinx([x1, x2, x3]: [f64; 3]) -> Self { + [x1, x2, x3].into() + } +} + +macro_rules! impl_diff { + ($n:literal ; $($o:literal)+) => { $( + impl<'py> DifferentiableImpl<Loc<$n, f64>> for DolfinxPyFunction_f64<'py, $n, $o, 1> { + type Derivative = Loc<$n, f64>; + + fn differential_impl<I: Instance<Loc<$n, f64>>>(&self, x: I) -> Self::Derivative { + let x_own = x.own(); + let f = self.cxx; + let expand_x = x_own.to_dolphinx(); + // Find cell containing `x` + let cell = unsafe { ffi::cell_Function_f64(f, &expand_x) }; + if cell < 0 { + Loc::ORIGIN + } else { + let (model, _) = unsafe { + // Find the coordinates of the corresponding triangle. We guarantee a + // triangular mesh in the constructor of `DolfinxPyFunction_f64`. + let simplex_coords = ffi::cell_coords_Function_f64_triangle(f, cell); + // Construct a new P2 model on that cell, as this is easier than dealing + // with the awful documentation of Dolphinx and C++, and probably having + // to reimplemen our P2 stuff anyway, to do any optimisation with the basix + // basis functions. + super::model_dolfinx_p2_cell(f, &simplex_coords, cell, false) + }; + model.differential(&x_own) + } + } + } + )+ }; +} + +macro_rules! impl_mapping { + ($($n:literal)+) => { $( + impl<'py, const O: u32> Mapping<Loc<$n, f64>> for DolfinxPyFunction_f64<'py, $n, O, 1> { + type Codomain = f64; + + fn apply<I: Instance<Loc<$n, f64>>>(&self, x: I) -> f64 { + let expand_x = x.own().to_dolphinx(); + let f = self.cxx; + unsafe { + // Find cell containing `x` + return ffi::eval_Function_f64(f, &expand_x); + } + } + } + //impl_diff!($n; 2 3 4 5 6 7 8); + )+ }; +} + +impl_mapping!(1 2 3); +// Extension to 1D should be easy. +//impl_diff!(1; 2 3 4 5 6 7 8); +impl_diff!(2; 2 3 4 5 6 7 8); + +impl<'py> GlobalAnalysis<f64, Bounds<f64>> for DolfinxPyFunction_f64<'py, 2, 2, 1> { + fn global_analysis(&self) -> Bounds<f64> { + let min = + unsafe { ffi::min_Function_f64_p2(self.cxx) }.map_or(f64::NEG_INFINITY, |res| res.v); + let max = unsafe { ffi::max_Function_f64_p2(self.cxx) }.map_or(f64::INFINITY, |res| res.v); + Bounds(min, max) + } +} + +/// Only second-order polynomials on a triangular mesh are implemented. +/// +/// May panic if assumptions of the underlying functionn do not hold despite +/// verification in the constructors of [`DolfinxPyFunction_f64`]. +impl<'py> MinMaxMapping<Loc<2>, f64> for DolfinxPyFunction_f64<'py, 2, 2, 1> { + fn minimise(&mut self, _tolerance: f64, _max_steps: usize) -> (Loc<2>, f64) { + let res = unsafe { ffi::min_Function_f64_p2(self.cxx) }.unwrap(); + (res.x.into(), res.v) + } + + fn maximise(&mut self, _tolerance: f64, _max_steps: usize) -> (Loc<2>, f64) { + let res = unsafe { ffi::max_Function_f64_p2(self.cxx) }.unwrap(); + (res.x.into(), res.v) + } +} + +/* +enum MaybeValid<A> { + Valid(A), + Invalid(A), +} + +use MaybeValid::*; + +impl<A> MaybeValid<A> { + #[inline] + fn uninitialised_ok(self) -> A { + match self { + Valid(a) => a, + Invalid(a) => a, + } + } +} +*/ + +impl<'py, const N: u32, const O: u32, const D: u32> DolfinxPyFunction_f64<'py, N, O, D> { + /// If owned, call `f_valid` with mutable data. + /// If not owned, create a new C++ fem.Function, uninitialised, and call `f_invalid` with its + /// mutable data, as well as the original immutable data. + #[inline] + fn with_mut_data_and_orig<F, G, Z>(&mut self, f_valid: F, f_invalid: G) -> Z + where + F: Fn(&mut [f64]) -> Z, + G: Fn(&mut [f64], &[f64]) -> Z, + { + if self.owned { + return f_valid(unsafe { ffi::data_mut_Function_f64(self.cxx) }); + } else { + // Create a completely new Function + let old_data = unsafe { ffi::data_mut_Function_f64(self.cxx) }; + let new = unsafe { ffi::similar_Function_f64(self.cxx) }; + let new_data = unsafe { ffi::data_mut_Function_f64(new) }; + let res = f_invalid(new_data, old_data); + // Invalidate Python reference; we're managing the object now + self.cxx = new; + self.u = None; + self.owned = true; + return res; + } + } + + /// If owned, call `f` with mutable data. + /// If not owned, create a new C++ fem.Function, uninitialised, and call `f` with its + /// mutable data. + #[inline] + fn with_mut_data<F, Z>(&mut self, f: F) -> Z + where + F: Fn(&mut [f64]) -> Z, + { + if self.owned { + return f(unsafe { ffi::data_mut_Function_f64(self.cxx) }); + } else { + // Create a completely new Function + let new = unsafe { ffi::similar_Function_f64(self.cxx) }; + let new_data = unsafe { ffi::data_mut_Function_f64(new) }; + let res = f(new_data); + // Invalidate Python reference; we're managing the object now + self.cxx = new; + self.u = None; + self.owned = true; + return res; + } + } + + /// Create a similar new function, and call `f` with both the mutable (unininitialised) + /// new data, and original data. + /// + /// Returns object for the new data. + #[inline] + fn with_new_data_and_orig<F>(&self, f: F) -> Self + where + F: Fn(&mut [f64], &[f64]), + { + // Create a completely new Function + let new = self.similar(); + let old_data = unsafe { ffi::data_Function_f64(self.cxx) }; + let new_data = unsafe { ffi::data_mut_Function_f64(new.cxx) }; + f(new_data, old_data); + new + } + + /// Similar to [`with_mut_data`], but check compatibility with `other` and also pass its + /// immutable data slice as the final argument. + #[inline] + fn with_mut_data_and_orig_binop<F, G, Z>( + &mut self, + other: &DolfinxPyFunction_f64<'py, N, O, D>, + f_valid: F, + f_invalid: G, + ) -> Z + where + F: Fn(&mut [f64], &[f64]) -> Z, + G: Fn(&mut [f64], &[f64], &[f64]) -> Z, + { + let data_other = unsafe { + ffi::check_compat_Function_f64(self.cxx, other.cxx).unwrap(); + ffi::data_Function_f64(other.cxx) + }; + self.with_mut_data_and_orig( + |data_self_valid| { + assert_eq!(data_self_valid.len(), data_other.len()); + f_valid(data_self_valid, data_other) + }, + |data_self_invalid, data_orig| { + assert_eq!(data_self_invalid.len(), data_other.len()); + assert_eq!(data_orig.len(), data_other.len()); + f_invalid(data_self_invalid, data_orig, data_other) + }, + ) + } + + /// Similar to [`with_new_data_and_orig`], but check compatibility with `other` and also + /// pass its immutable data slice as the final argument. + #[inline] + fn with_new_data_and_orig_binop<F>( + &self, + other: &DolfinxPyFunction_f64<'py, N, O, D>, + f: F, + ) -> Self + where + F: Fn(&mut [f64], &[f64], &[f64]), + { + let data_other = unsafe { + ffi::check_compat_Function_f64(self.cxx, other.cxx).unwrap(); + ffi::data_Function_f64(other.cxx) + }; + self.with_new_data_and_orig(|data, data_orig| { + assert_eq!(data.len(), data_other.len()); + assert_eq!(data_orig.len(), data_other.len()); + f(data, data_orig, data_other) + }) + } +} + +macro_rules! impl_unop { + ($trait:ident, $fn:ident) => { + impl<'py, const N: u32, const O: u32, const D: u32> $trait + for DolfinxPyFunction_f64<'py, N, O, D> + { + type Output = Self; + + fn $fn(mut self) -> Self { + self.with_mut_data_and_orig( + |a| a.iter_mut().for_each(|x| *x = x.$fn()), + |a, a_orig| { + izip!(a.iter_mut(), a_orig.iter()).for_each(|(x, x_orig)| { + *x = x_orig.$fn(); + }) + }, + ); + return self; + } + } + + impl<'py, 'a, const N: u32, const O: u32, const D: u32> $trait + for &'a DolfinxPyFunction_f64<'py, N, O, D> + { + type Output = DolfinxPyFunction_f64<'py, N, O, D>; + + fn $fn(self) -> Self::Output { + self.with_new_data_and_orig(|a, a_orig| { + izip!(a.iter_mut(), a_orig.iter()).for_each(|(x, x_orig)| { + *x = x_orig.$fn(); + }) + }) + } + } + }; +} + +use std::ops::Neg; + +impl_unop!(Neg, neg); + +macro_rules! impl_scalarop { + ($trait:ident, $fn:ident, $trait_assign:ident, $fn_assign:ident) => { + impl<'py, const N: u32, const O: u32, const D: u32> $trait<f64> + for DolfinxPyFunction_f64<'py, N, O, D> + { + type Output = Self; + + fn $fn(mut self, α: f64) -> Self { + self.with_mut_data_and_orig( + |a| { + a.iter_mut().for_each(|x| { + x.$fn_assign(α); + }) + }, + |a, a_orig| { + izip!(a.iter_mut(), a_orig.iter()).for_each(|(x, x_orig)| { + *x = x_orig.$fn(α); + }) + }, + ); + return self; + } + } + + impl<'py, 'a, const N: u32, const O: u32, const D: u32> $trait<f64> + for &'a DolfinxPyFunction_f64<'py, N, O, D> + { + type Output = DolfinxPyFunction_f64<'py, N, O, D>; + + fn $fn(self, α: f64) -> Self::Output { + self.with_new_data_and_orig(|a, a_orig| { + izip!(a.iter_mut(), a_orig.iter()).for_each(|(x, x_orig)| { + *x = x_orig.$fn(α); + }) + }) + } + } + + impl<'py, const N: u32, const O: u32, const D: u32> $trait_assign<f64> + for DolfinxPyFunction_f64<'py, N, O, D> + { + fn $fn_assign(&mut self, α: f64) { + self.with_mut_data_and_orig( + |a| { + a.iter_mut().for_each(|x| { + x.$fn_assign(α); + }) + }, + |a, a_orig| { + izip!(a.iter_mut(), a_orig.iter()).for_each(|(x, x_orig)| { + *x = x_orig.$fn(α); + }) + }, + ); + } + } + }; +} + +use std::ops::{Div, DivAssign, Mul, MulAssign}; + +impl_scalarop!(Mul, mul, MulAssign, mul_assign); +impl_scalarop!(Div, div, DivAssign, div_assign); + +macro_rules! impl_binop_consume { + ($trait:ident, $fn:ident, $trait_assign:ident, $fn_assign:ident) => { + impl<'py, const N: u32, const O: u32, const D: u32> $trait<Self> + for DolfinxPyFunction_f64<'py, N, O, D> + { + type Output = Self; + + #[inline] + fn $fn(self, other: Self) -> Self { + self.$fn(&other) + } + } + + impl<'py, const N: u32, const O: u32, const D: u32> $trait_assign<Self> + for DolfinxPyFunction_f64<'py, N, O, D> + { + #[inline] + fn $fn_assign(&mut self, other: Self) { + self.$fn_assign(&other) + } + } + + impl<'py, 'a, const N: u32, const O: u32, const D: u32> $trait<&'a Self> + for DolfinxPyFunction_f64<'py, N, O, D> + { + type Output = Self; + + fn $fn(mut self, other: &'a Self) -> Self { + self.with_mut_data_and_orig_binop( + other, + |data, data_other| { + izip!(data.iter_mut(), data_other.iter()).for_each(|(x, y)| { + x.$fn_assign(y); + }) + }, + |data, data_orig, data_other| { + izip!(data.iter_mut(), data_orig.iter(), data_other.iter()).for_each( + |(x, x_orig, y)| { + *x = x_orig.$fn(y); + }, + ) + }, + ); + return self; + } + } + + impl<'py, 'a, const N: u32, const O: u32, const D: u32> $trait_assign<&'a Self> + for DolfinxPyFunction_f64<'py, N, O, D> + { + fn $fn_assign(&mut self, other: &'a Self) { + self.with_mut_data_and_orig_binop( + other, + |data, data_other| { + izip!(data.iter_mut(), data_other.iter()).for_each(|(x, y)| { + x.$fn_assign(y); + }) + }, + |data, data_orig, data_other| { + izip!(data.iter_mut(), data_orig.iter(), data_other.iter()).for_each( + |(x, x_orig, y)| { + *x = x_orig.$fn(y); + }, + ) + }, + ); + } + } + }; +} + +macro_rules! impl_binop_ref { + ($trait:ident, $fn:ident) => { + impl<'py, 'b, const N: u32, const O: u32, const D: u32> $trait<Self> + for &'b DolfinxPyFunction_f64<'py, N, O, D> + { + type Output = DolfinxPyFunction_f64<'py, N, O, D>; + + fn $fn(self, other: Self) -> Self::Output { + self.$fn(&other) + } + } + + impl<'py, 'a, 'b, const N: u32, const O: u32, const D: u32> $trait<&'a Self> + for &'b DolfinxPyFunction_f64<'py, N, O, D> + { + type Output = DolfinxPyFunction_f64<'py, N, O, D>; + + fn $fn(self, other: &'a Self) -> Self::Output { + self.with_new_data_and_orig_binop(other, |data_dest, data, data_other| { + izip!(data_dest.iter_mut(), data.iter(), data_other.iter()).for_each( + |(x, y, z)| { + *x = y.$fn(z); + }, + ) + }) + } + } + }; +} + +macro_rules! impl_binop { + ($trait:ident, $fn:ident, $trait_assign:ident, $fn_assign:ident) => { + impl_binop_consume!($trait, $fn, $trait_assign, $fn_assign); + impl_binop_ref!($trait, $fn); + }; +} + +use std::ops::{Add, AddAssign, Sub, SubAssign}; + +impl_binop!(Add, add, AddAssign, add_assign); +impl_binop!(Sub, sub, SubAssign, sub_assign); + +//use alg_tools::euclidean::Euclidean; +use alg_tools::linops::{VectorSpace, AXPY}; +//use alg_tools::norms::{Dist, HasDual, Norm, Normed, L2}; +use alg_tools::self_ownable; + +self_ownable!(DolfinxPyFunction_f64<'py, N, O, D> where 'py, const N: u32, const O: u32, const D: u32); + +impl<'py, const N: u32, const O: u32, const D: u32> Dist<L2, f64> + for DolfinxPyFunction_f64<'py, N, O, D> +{ + fn dist<I: Instance<Self>>(&self, other: I, p: L2) -> f64 { + other.eval_ref(|v| (self - v).norm(p)) + } +} + +impl<'py, const N: u32, const O: u32, const D: u32> Norm<L2, f64> + for DolfinxPyFunction_f64<'py, N, O, D> +{ + fn norm(&self, _p: L2) -> f64 { + self.norm2_squared().sqrt() + } +} + +impl<'py, const N: u32, const O: u32, const D: u32> VectorSpace + for DolfinxPyFunction_f64<'py, N, O, D> +{ + type Field = f64; + type PrincipalV = Self; + + /// Return a similar zero as `self`. + fn similar_origin(&self) -> Self { + let mut new = self.similar(); + new.set_zero(); + new + } +} + +impl<'py, const N: u32, const O: u32, const D: u32> AXPY for DolfinxPyFunction_f64<'py, N, O, D> { + /// Computes `y = βy + αx`, where `y` is `Self`. + fn axpy<I: Instance<Self>>(&mut self, α: Self::Field, x: I, β: Self::Field) { + x.eval(|r| { + self.with_mut_data_and_orig_binop( + r, + |data, data_other| { + izip!(data.iter_mut(), data_other.iter()).for_each(|(y, x)| { + *y = β * (*y) + α * (*x); + }) + }, + |data, data_orig, data_other| { + izip!(data.iter_mut(), data_orig.iter(), data_other.iter()).for_each( + |(y, y_orig, x)| { + *y = β * (*y_orig) + α * (*x); + }, + ) + }, + ); + }) + } + + /// Set self to zero. + fn set_zero(&mut self) { + self.with_mut_data(|data| { + data.iter_mut().for_each(|y| *y = 0.0); + }) + } +} + +impl<'py, const N: u32, const O: u32, const D: u32> HasDual + for DolfinxPyFunction_f64<'py, N, O, D> +{ + type DualSpace = Self; + + fn dual_origin(&self) -> <Self::DualSpace as VectorSpace>::PrincipalV { + self.similar_origin() + } +} + +impl<'py, const N: u32, const O: u32, const D: u32> Normed for DolfinxPyFunction_f64<'py, N, O, D> { + type NormExp = L2; + + fn norm_exponent(&self) -> L2 { + // TODO: maybe H1 more approriate for O=2 + L2 + } +} + +impl<'py, const N: u32, const O: u32, const D: u32> Euclidean + for DolfinxPyFunction_f64<'py, N, O, D> +{ + type PrincipalE = Self; + + fn dot<I: Instance<Self>>(&self, other: I) -> f64 { + let py = self.function_space.py(); + get_helpers(py) + .unwrap() + .dot + .call1(py, (self.own(), other.own())) + .unwrap() + .extract(py) + .unwrap() + /* + let data = unsafe { ffi::data_mut_Function_f64(self.cxx) }; + other.eval_decompose(|x| { + let data_other = unsafe { ffi::data_mut_Function_f64(x.cxx) }; + izip!(data.iter(), data_other.iter()) + .map(|(y, x)| y * x) + .sum() + })*/ + } + + fn norm2_squared(&self) -> f64 { + let py = self.function_space.py(); + get_helpers(py) + .unwrap() + .norm2_squared + .call1(py, (self.own(),)) + .unwrap() + .extract(py) + .unwrap() + } + + fn dist2_squared<I: Instance<Self>>(&self, other: I) -> f64 { + //other.eval_ref(|v| self.norm2_squared() + 2.0 * self.dot(v) + v.norm2_squared_div2()) + let py = self.function_space.py(); + get_helpers(py) + .unwrap() + .dist2_squared + .call1(py, (self.own(), other.own())) + .unwrap() + .extract(py) + .unwrap() + } +}