NumPy integration¶
pybFoam’s Field<T> bindings make every field behave like a NumPy
array. np.asarray(field) constructs a view that shares memory
with OpenFOAM’s internal buffer — no copy, no conversion. This page
describes the mechanism and the contract.
How it’s done¶
NumPy’s np.asarray(x) calls x.__array__() if the object has
one. Every Field<T> binding implements __array__ as a small
lambda that hands NumPy a description of the underlying OpenFOAM
storage:
.def("__array__", [](Field<Type>& self, ...) {
size_t shape[2] = { self.size(), pTraits<Type>::nComponents };
return nb::ndarray<nb::numpy, scalar>(self.data(), ndim, shape);
}, nb::rv_policy::reference_internal)
The ndarray is built directly over self.data() — only a
(pointer, dtype, shape, strides) description crosses the language
boundary. Shape comes from pTraits<Type>::nComponents: 1 for
scalar, 3 for vector, 9 for tensor, 6 for symmTensor.
The reference_internal return policy keeps the field alive for as
long as the NumPy view exists.
The reverse direction — constructing a field from a NumPy array —
exists too (scalarField(np_array)). That one copies the data
into OpenFOAM-owned storage so the field can outlive the NumPy
buffer.
Using it¶
A scalar field gives you a 1-D array; a multi-component field gives
you a 2-D (N, k) array:
Field type |
|
dtype |
|---|---|---|
|
|
|
|
|
same as scalar |
|
|
same as scalar |
|
|
same as scalar |
The view is writable. Mutating it from either side is visible on the other:
from pybFoam import scalarField
import numpy as np
a = scalarField([0.0] * 10)
b = scalarField([1.0] * 10)
np_a = np.asarray(a)
a += b + 5.0 # mutate via the C++ binding
assert np_a[0] == 6.0 # visible through the NumPy view
np_a[0:2] = 42.0 # mutate via NumPy
assert a[0] == 42.0 # visible through the C++ binding
The same contract holds for the volScalarField.internalField() and
boundary-patch buffers, and for the geometry accessors of sampling
objects (plane.Cf(), plane.points(), line.distance()).