Skip to content

Commit 45b9f2b

Browse files
committed
Return PM.matrix native results as NumPy arrays
1 parent 6a877bf commit 45b9f2b

3 files changed

Lines changed: 33 additions & 22 deletions

File tree

src/nns/_nnscore_bindings.cpp

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
#include <nanobind/stl/string.h>
44
#include <nanobind/stl/vector.h>
55

6+
#include <cstdint>
67
#include <cstddef>
78
#include <stdexcept>
89
#include <string>
@@ -16,6 +17,7 @@ namespace {
1617

1718
using Vector = nb::ndarray<const double, nb::ndim<1>, nb::c_contig>;
1819
using IntVector = nb::ndarray<const int, nb::ndim<1>, nb::c_contig>;
20+
using Matrix = nb::ndarray<nb::numpy, double, nb::shape<-1, -1>, nb::f_contig>;
1921

2022
std::size_t checked_size(const Vector& x, const char* name) {
2123
const std::size_t n = x.shape(0);
@@ -25,6 +27,14 @@ std::size_t checked_size(const Vector& x, const char* name) {
2527
return n;
2628
}
2729

30+
Matrix matrix_from_column_major_vector(std::vector<double>&& values, std::size_t dim) {
31+
auto* storage = new std::vector<double>(std::move(values));
32+
nb::capsule owner(storage, [](void* p) noexcept {
33+
delete static_cast<std::vector<double>*>(p);
34+
});
35+
return Matrix(storage->data(), {dim, dim}, owner, {1, static_cast<int64_t>(dim)});
36+
}
37+
2838
void check_same_size(const Vector& x, const Vector& y, const char* x_name, const char* y_name) {
2939
if (checked_size(x, x_name) != checked_size(y, y_name)) {
3040
throw std::invalid_argument(std::string(x_name) + " and " + y_name + " must have the same length.");
@@ -136,14 +146,14 @@ nb::dict pm_matrix_dict(double degree_lpm,
136146
throw std::invalid_argument("target length must equal d.");
137147
}
138148
checked_flat_matrix_size(variable, n, d, "variable");
139-
const nns::PMMatrixResult result = nns::pm_matrix(degree_lpm, degree_upm, target.data(),
140-
variable.data(), n, d, pop_adj, norm);
149+
nns::PMMatrixResult result = nns::pm_matrix(degree_lpm, degree_upm, target.data(),
150+
variable.data(), n, d, pop_adj, norm);
141151
nb::dict out;
142-
out["cupm"] = result.cupm;
143-
out["dupm"] = result.dupm;
144-
out["dlpm"] = result.dlpm;
145-
out["clpm"] = result.clpm;
146-
out["cov.matrix"] = result.cov;
152+
out["cupm"] = matrix_from_column_major_vector(std::move(result.cupm), result.dim);
153+
out["dupm"] = matrix_from_column_major_vector(std::move(result.dupm), result.dim);
154+
out["dlpm"] = matrix_from_column_major_vector(std::move(result.dlpm), result.dim);
155+
out["clpm"] = matrix_from_column_major_vector(std::move(result.clpm), result.dim);
156+
out["cov.matrix"] = matrix_from_column_major_vector(std::move(result.cov), result.dim);
147157
out["dim"] = result.dim;
148158
return out;
149159
}

src/nns/pm_matrix.py

Lines changed: 12 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -56,21 +56,11 @@ def pm_matrix(
5656
)
5757
dim = int(native_result["dim"])
5858
result: PMMatrixResult = {
59-
"cupm": np.asarray(native_result["cupm"], dtype=np.float64).reshape(
60-
(dim, dim), order="F"
61-
),
62-
"dupm": np.asarray(native_result["dupm"], dtype=np.float64).reshape(
63-
(dim, dim), order="F"
64-
),
65-
"dlpm": np.asarray(native_result["dlpm"], dtype=np.float64).reshape(
66-
(dim, dim), order="F"
67-
),
68-
"clpm": np.asarray(native_result["clpm"], dtype=np.float64).reshape(
69-
(dim, dim), order="F"
70-
),
71-
"cov.matrix": np.asarray(native_result["cov.matrix"], dtype=np.float64).reshape(
72-
(dim, dim), order="F"
73-
),
59+
"cupm": _native_matrix(native_result["cupm"], dim),
60+
"dupm": _native_matrix(native_result["dupm"], dim),
61+
"dlpm": _native_matrix(native_result["dlpm"], dim),
62+
"clpm": _native_matrix(native_result["clpm"], dim),
63+
"cov.matrix": _native_matrix(native_result["cov.matrix"], dim),
7464
}
7565
if resolved_names is not None:
7666
result["names"] = resolved_names
@@ -116,6 +106,13 @@ def pm_matrix(
116106
return result
117107

118108

109+
def _native_matrix(value: Any, dim: int) -> NDArray[np.float64]:
110+
matrix = np.asarray(value, dtype=np.float64)
111+
if matrix.shape == (dim, dim):
112+
return cast(NDArray[np.float64], matrix)
113+
return cast(NDArray[np.float64], matrix.reshape((dim, dim), order="F"))
114+
115+
119116
def _resolve_names(names: Sequence[str] | None, n_cols: int) -> list[str] | None:
120117
if names is None:
121118
return None

tests/invariants/test_native_original_src_coverage.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,10 @@ def test_direct_native_partial_moment_smoke(native: ModuleType) -> None:
8989
)
9090
assert native_pm["dim"] == 2
9191
assert set(native_pm) >= {"cupm", "dupm", "dlpm", "clpm", "cov.matrix", "dim"}
92+
for key in ("cupm", "dupm", "dlpm", "clpm", "cov.matrix"):
93+
assert isinstance(native_pm[key], np.ndarray)
94+
assert native_pm[key].shape == (2, 2)
95+
assert native_pm[key].flags.f_contiguous
9296

9397

9498
def test_direct_native_fast_lm_smoke(native: ModuleType) -> None:

0 commit comments

Comments
 (0)