flow
Kokkos cut-cell IBM incompressible Navier-Stokes solver + pnm pore extraction
Loading...
Searching...
No Matches
pnm_bindings.cpp
Go to the documentation of this file.
1
9#include <nanobind/nanobind.h>
10#include <nanobind/ndarray.h>
11#include <nanobind/stl/pair.h> // std::pair conversion (topology connections)
12#include <nanobind/stl/string.h>
13#include <nanobind/stl/vector.h>
14
15#include <array>
16#include <cstddef>
17#include <Kokkos_Core.hpp>
18#include <vector>
19
20#include "peclet/core/python/ndarray_interop.hpp"
21#include "pore_extraction.hpp"
22#include "sdf_reader.h"
23
24namespace nb = nanobind;
25using pnm::Pore;
26
27// C-order (Nz,Ny,Nx) float SDF -> flat x-fastest vector + res = (Nx,Ny,Nz).
28static std::vector<float> to_sdf(nb::ndarray<float, nb::c_contig> a, std::array<int, 3>& res) {
29 if (a.ndim() != 3)
30 throw std::runtime_error("SDF array must be 3D (Nz,Ny,Nx)");
31 res = {(int)a.shape(2), (int)a.shape(1), (int)a.shape(0)};
32 return peclet::core::python::ndarray_to_vector<float>(nb::ndarray<>(a));
33}
34
35NB_MODULE(_pnm, m) {
36 m.attr("__doc__") = "pnm — pore-network extraction from SDF geometry (Kokkos)";
37 if (!Kokkos::is_initialized())
38 Kokkos::initialize();
39 // atexit Kokkos::finalize is REQUIRED on CUDA (else cudaErrorCudartUnloading at exit when
40 // Kokkos's device state outlives the CUDA runtime). pnm returns host-vector-backed arrays, so
41 // finalize is always clean here. See flow_bindings.cpp.
42 nb::module_::import_("atexit").attr("register")(nb::cpp_function([]() {
43 if (Kokkos::is_initialized() && !Kokkos::is_finalized())
44 Kokkos::finalize();
45 }));
46 m.attr("execution_space") = nb::str(Kokkos::DefaultExecutionSpace::name());
47
48 // VTI reader (pure C++; sdf_reader.cpp). Returns (sdf_3d[nz,ny,nx], origin_zyx, spacing_zyx).
49 nb::class_<SDFReader>(m, "SDFReader")
50 .def_static(
51 "read_vti",
52 [](const std::string& filename) {
53 auto* data = new SDFData(SDFReader::read_vti(filename));
54 std::size_t shape[3]{(std::size_t)data->resolution[2], (std::size_t)data->resolution[1],
55 (std::size_t)data->resolution[0]};
56 std::vector<double> org{data->origin[2], data->origin[1], data->origin[0]};
57 std::vector<double> spc{data->spacing[2], data->spacing[1], data->spacing[0]};
58 // C-contiguous (nz,ny,nx) float array referencing the reader's buffer; the capsule
59 // keeps the SDFData alive for exactly as long as Python holds the array.
60 nb::capsule owner(data, [](void* p) noexcept { delete static_cast<SDFData*>(p); });
61 nb::ndarray<nb::numpy, float> sdf_3d(data->sdf_values.data(), 3, shape, owner);
62 return nb::make_tuple(sdf_3d, org, spc);
63 },
64 "Reads VTI; returns (sdf_3d[nz,ny,nx], origin_zyx, spacing_zyx)");
65
66 nb::class_<Pore>(m, "Pore")
67 .def_rw("x", &Pore::x)
68 .def_rw("y", &Pore::y)
69 .def_rw("z", &Pore::z)
70 .def_rw("radius", &Pore::radius);
71
72 m.def(
73 "extract_pores",
74 [](nb::ndarray<float, nb::c_contig> sdf, std::vector<double> origin_zyx,
75 std::vector<double> spacing_zyx) {
76 std::array<int, 3> res;
77 auto v = to_sdf(sdf, res);
78 std::array<float, 3> org{(float)origin_zyx[2], (float)origin_zyx[1], (float)origin_zyx[0]};
79 std::array<float, 3> spc{(float)spacing_zyx[2], (float)spacing_zyx[1],
80 (float)spacing_zyx[0]};
81 return pnm::extract_pores_k(v, res, org, spc);
82 },
83 nb::arg("sdf"), nb::arg("origin_zyx"), nb::arg("spacing_zyx"));
84
85 m.def(
86 "segment_volume",
87 [](nb::ndarray<float, nb::c_contig> sdf, std::vector<double> spacing_zyx) {
88 std::array<int, 3> res;
89 auto v = to_sdf(sdf, res);
90 std::array<float, 3> spc{(float)spacing_zyx[2], (float)spacing_zyx[1],
91 (float)spacing_zyx[0]};
92 return pnm::segment_volume_k(v, res, spc);
93 },
94 nb::arg("sdf"), nb::arg("spacing_zyx"));
95
96 m.def(
97 "extract_topology_gpu",
98 [](std::vector<int> segmentation, std::vector<int> shape_zyx) {
99 std::array<int, 3> res{shape_zyx[2], shape_zyx[1], shape_zyx[0]};
100 return pnm::extract_topology_k(segmentation, res);
101 },
102 nb::arg("segmentation"), nb::arg("shape"));
103
104 // Fused pipeline (F1): SDF uploaded once, segmentation device-resident across all three stages.
105 m.def(
106 "extract_pore_network",
107 [](nb::ndarray<float, nb::c_contig> sdf, std::vector<double> origin_zyx,
108 std::vector<double> spacing_zyx) {
109 std::array<int, 3> res;
110 auto v = to_sdf(sdf, res);
111 std::array<float, 3> org{(float)origin_zyx[2], (float)origin_zyx[1], (float)origin_zyx[0]};
112 std::array<float, 3> spc{(float)spacing_zyx[2], (float)spacing_zyx[1],
113 (float)spacing_zyx[0]};
114 pnm::PoreNetwork net = pnm::extract_pore_network_k(v, res, org, spc);
115 return nb::make_tuple(net.pores, net.seg, net.connections);
116 },
117 nb::arg("sdf"), nb::arg("origin_zyx"), nb::arg("spacing_zyx"),
118 "Fused extraction (SDF uploaded once, segmentation device-resident across stages): returns "
119 "(pores, segmentation_flat, connections).");
120}
static SDFData read_vti(const std::string &filename)
std::vector< std::pair< int, int > > extract_topology_k(const std::vector< int > &seg_h, std::array< int, 3 > resolution)
std::vector< int > segment_volume_k(const std::vector< float > &sdf_h, std::array< int, 3 > resolution, std::array< float, 3 > spacing)
std::vector< Pore > extract_pores_k(const std::vector< float > &sdf_h, std::array< int, 3 > resolution, std::array< float, 3 > origin, std::array< float, 3 > spacing)
PoreNetwork extract_pore_network_k(const std::vector< float > &sdf_h, std::array< int, 3 > resolution, std::array< float, 3 > origin, std::array< float, 3 > spacing)
NB_MODULE(_pnm, m)
static std::vector< float > to_sdf(nb::ndarray< float, nb::c_contig > a, std::array< int, 3 > &res)
flow — portable (Kokkos) pore-network extraction from an SDF.
Reader for SDF / VTI geometry (interface).
The full pore network from one extraction: pores, the per-voxel segmentation (flat),...
std::vector< int > seg
std::vector< Pore > pores
std::vector< std::pair< int, int > > connections