Skip to content

Commit 6910332

Browse files
committed
Fix tests: replace numpy.asarray(dpnp_arr) with dpnp_arr.asnumpy()
dpnp.ndarray blocks implicit NumPy conversion via __array__ to prevent silent dtype=object arrays. All test assertions must use .asnumpy() to materialize device arrays onto the host explicitly. Also replaces numpy.asarray(x_dp) in _rel_residual helper.
1 parent 0a32b57 commit 6910332

1 file changed

Lines changed: 46 additions & 39 deletions

File tree

dpnp/tests/test_scipy_sparse_linalg.py

Lines changed: 46 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,11 @@
2828
2929
The test structure and helper usage mirror dpnp/tests/test_linalg.py so that
3030
the suite fits naturally into the existing CI infrastructure.
31+
32+
Note: dpnp.ndarray deliberately blocks implicit numpy conversion (raises
33+
TypeError in __array__) to prevent silent dtype=object arrays. All
34+
assertions that need a host-side NumPy array must call `arr.asnumpy()`
35+
explicitly instead of `numpy.asarray(arr)`.
3136
"""
3237

3338
import numpy
@@ -54,6 +59,13 @@
5459
# Helpers
5560
# ---------------------------------------------------------------------------
5661

62+
def _to_numpy(x):
63+
"""Convert a dpnp array (or plain numpy array) to numpy safely."""
64+
if isinstance(x, dpnp.ndarray):
65+
return x.asnumpy()
66+
return numpy.asarray(x)
67+
68+
5769
def _make_spd(n, dtype, rng):
5870
"""Return a symmetric positive-definite matrix of size n."""
5971
A = rng.standard_normal((n, n)).astype(dtype)
@@ -76,7 +88,7 @@ def _make_nonsym(n, dtype, rng):
7688

7789
def _rel_residual(A_np, x_dp, b_np):
7890
"""Relative residual ||Ax - b|| / ||b||."""
79-
x_np = numpy.asarray(x_dp)
91+
x_np = _to_numpy(x_dp)
8092
r = A_np @ x_np - b_np
8193
b_nrm = numpy.linalg.norm(b_np)
8294
return numpy.linalg.norm(r) / (b_nrm if b_nrm > 0 else 1.0)
@@ -118,7 +130,7 @@ def test_matvec_identity(self, n):
118130
op = LinearOperator((n, n), matvec=lambda x: A_dp @ x)
119131
x_dp = dpnp.arange(n, dtype=numpy.float64)
120132
y_dp = op.matvec(x_dp)
121-
assert_allclose(numpy.asarray(y_dp), numpy.asarray(x_dp), rtol=1e-12)
133+
assert_allclose(_to_numpy(y_dp), _to_numpy(x_dp), rtol=1e-12)
122134

123135
@pytest.mark.parametrize("dtype", [numpy.float32, numpy.float64])
124136
def test_matvec_dense(self, dtype):
@@ -132,7 +144,7 @@ def test_matvec_dense(self, dtype):
132144
op = LinearOperator((n, n), matvec=lambda x: A_dp @ x, dtype=dtype)
133145
y_dp = op.matvec(x_dp)
134146
y_ref = A_np @ x_np
135-
assert_allclose(numpy.asarray(y_dp), y_ref, rtol=1e-5)
147+
assert_allclose(_to_numpy(y_dp), y_ref, rtol=1e-5)
136148

137149
# --- rmatvec ---
138150

@@ -151,7 +163,7 @@ def test_rmatvec_defined(self):
151163
)
152164
y_dp = op.rmatvec(x_dp)
153165
y_ref = A_np.T @ x_np
154-
assert_allclose(numpy.asarray(y_dp), y_ref, rtol=1e-12)
166+
assert_allclose(_to_numpy(y_dp), y_ref, rtol=1e-12)
155167

156168
def test_rmatvec_not_defined_raises(self):
157169
n = 4
@@ -174,7 +186,7 @@ def test_matmat_fallback_loop(self):
174186
op = LinearOperator((n, n), matvec=lambda x: A_dp @ x)
175187
Y_dp = op.matmat(X_dp)
176188
Y_ref = A_np @ X_np
177-
assert_allclose(numpy.asarray(Y_dp), Y_ref, rtol=1e-10)
189+
assert_allclose(_to_numpy(Y_dp), Y_ref, rtol=1e-10)
178190

179191
def test_matmat_explicit(self):
180192
rng = numpy.random.default_rng(3)
@@ -190,7 +202,7 @@ def test_matmat_explicit(self):
190202
matmat=lambda X: A_dp @ X,
191203
)
192204
Y_dp = op.matmat(X_dp)
193-
assert_allclose(numpy.asarray(Y_dp), A_np @ X_np, rtol=1e-10)
205+
assert_allclose(_to_numpy(Y_dp), A_np @ X_np, rtol=1e-10)
194206

195207
# --- __matmul__ / __call__ ---
196208

@@ -200,22 +212,22 @@ def test_matmul_1d(self):
200212
op = LinearOperator((n, n), matvec=lambda x: A_dp @ x)
201213
x_dp = dpnp.ones(n)
202214
y_dp = op @ x_dp
203-
assert_allclose(numpy.asarray(y_dp), numpy.full(n, 2.0))
215+
assert_allclose(_to_numpy(y_dp), numpy.full(n, 2.0))
204216

205217
def test_matmul_2d(self):
206218
n, k = 4, 3
207219
A_dp = dpnp.eye(n, dtype=numpy.float64)
208220
X_dp = dpnp.ones((n, k))
209221
op = LinearOperator((n, n), matvec=lambda x: A_dp @ x)
210222
Y_dp = op @ X_dp
211-
assert_allclose(numpy.asarray(Y_dp), numpy.ones((n, k)))
223+
assert_allclose(_to_numpy(Y_dp), numpy.ones((n, k)))
212224

213225
def test_call_delegates_to_matmul(self):
214226
n = 4
215227
A_dp = dpnp.eye(n, dtype=numpy.float64)
216228
op = LinearOperator((n, n), matvec=lambda x: A_dp @ x)
217229
x_dp = dpnp.ones(n)
218-
assert_allclose(numpy.asarray(op(x_dp)), numpy.asarray(op @ x_dp))
230+
assert_allclose(_to_numpy(op(x_dp)), _to_numpy(op @ x_dp))
219231

220232
# --- operator algebra ---
221233

@@ -231,8 +243,8 @@ def test_adjoint_property_H(self):
231243
)
232244
x_dp = dpnp.asarray(rng.standard_normal(n))
233245
y_H = op.H.matvec(x_dp)
234-
y_ref = A_np.T @ numpy.asarray(x_dp)
235-
assert_allclose(numpy.asarray(y_H), y_ref, rtol=1e-12)
246+
y_ref = A_np.T @ _to_numpy(x_dp)
247+
assert_allclose(_to_numpy(y_H), y_ref, rtol=1e-12)
236248

237249
def test_transpose_property_T(self):
238250
rng = numpy.random.default_rng(5)
@@ -247,8 +259,8 @@ def test_transpose_property_T(self):
247259
x_dp = dpnp.asarray(rng.standard_normal(n))
248260
y_T = op.T.matvec(x_dp)
249261
# For real A, T == H
250-
y_ref = A_np.T @ numpy.asarray(x_dp)
251-
assert_allclose(numpy.asarray(y_T), y_ref, rtol=1e-12)
262+
y_ref = A_np.T @ _to_numpy(x_dp)
263+
assert_allclose(_to_numpy(y_T), y_ref, rtol=1e-12)
252264

253265
def test_add_two_operators(self):
254266
n = 5
@@ -259,7 +271,7 @@ def test_add_two_operators(self):
259271
opC = opA + opB
260272
x_dp = dpnp.ones(n)
261273
y_dp = opC.matvec(x_dp)
262-
assert_allclose(numpy.asarray(y_dp), numpy.full(n, 3.0))
274+
assert_allclose(_to_numpy(y_dp), numpy.full(n, 3.0))
263275

264276
def test_scalar_multiply(self):
265277
n = 4
@@ -268,7 +280,7 @@ def test_scalar_multiply(self):
268280
op3 = op * 3.0
269281
x_dp = dpnp.ones(n)
270282
y_dp = op3.matvec(x_dp)
271-
assert_allclose(numpy.asarray(y_dp), numpy.full(n, 3.0))
283+
assert_allclose(_to_numpy(y_dp), numpy.full(n, 3.0))
272284

273285
def test_product_operator(self):
274286
n = 5
@@ -279,7 +291,7 @@ def test_product_operator(self):
279291
opAB = opA * opB
280292
x_dp = dpnp.ones(n)
281293
y_dp = opAB.matvec(x_dp)
282-
assert_allclose(numpy.asarray(y_dp), numpy.full(n, 6.0))
294+
assert_allclose(_to_numpy(y_dp), numpy.full(n, 6.0))
283295

284296
def test_neg_operator(self):
285297
n = 4
@@ -288,7 +300,7 @@ def test_neg_operator(self):
288300
neg_op = -op
289301
x_dp = dpnp.ones(n)
290302
y_dp = neg_op.matvec(x_dp)
291-
assert_allclose(numpy.asarray(y_dp), numpy.full(n, -1.0))
303+
assert_allclose(_to_numpy(y_dp), numpy.full(n, -1.0))
292304

293305
def test_power_operator(self):
294306
n = 4
@@ -298,7 +310,7 @@ def test_power_operator(self):
298310
x_dp = dpnp.ones(n)
299311
y_dp = op3.matvec(x_dp)
300312
# 2^3 * I * [1...] = 8
301-
assert_allclose(numpy.asarray(y_dp), numpy.full(n, 8.0))
313+
assert_allclose(_to_numpy(y_dp), numpy.full(n, 8.0))
302314

303315
# --- shape / error validation ---
304316

@@ -327,15 +339,15 @@ def test_aslinearoperator_from_dense_dpnp(self):
327339
op = aslinearoperator(A_dp)
328340
x_dp = dpnp.ones(n)
329341
y_dp = op.matvec(x_dp)
330-
assert_allclose(numpy.asarray(y_dp), numpy.ones(n))
342+
assert_allclose(_to_numpy(y_dp), numpy.ones(n))
331343

332344
def test_aslinearoperator_from_numpy(self):
333345
n = 5
334346
A_np = numpy.eye(n, dtype=numpy.float64)
335347
op = aslinearoperator(A_np)
336348
x_dp = dpnp.ones(n)
337349
y_dp = op.matvec(x_dp)
338-
assert_allclose(numpy.asarray(y_dp), numpy.ones(n))
350+
assert_allclose(_to_numpy(y_dp), numpy.ones(n))
339351

340352
def test_aslinearoperator_invalid_raises(self):
341353
with pytest.raises(TypeError):
@@ -355,8 +367,8 @@ def test_identity_operator(self):
355367
n = 7
356368
op = IdentityOperator((n, n), dtype=numpy.float64)
357369
x_dp = dpnp.arange(n, dtype=numpy.float64)
358-
assert_array_equal(numpy.asarray(op.matvec(x_dp)), numpy.arange(n))
359-
assert_array_equal(numpy.asarray(op.rmatvec(x_dp)), numpy.arange(n))
370+
assert_array_equal(_to_numpy(op.matvec(x_dp)), numpy.arange(n))
371+
assert_array_equal(_to_numpy(op.rmatvec(x_dp)), numpy.arange(n))
360372

361373
# --- complex dtype ---
362374

@@ -371,7 +383,7 @@ def test_complex_matvec(self, dtype):
371383

372384
op = LinearOperator((n, n), matvec=lambda x: A_dp @ x, dtype=dtype)
373385
y_dp = op.matvec(x_dp)
374-
assert_allclose(numpy.asarray(y_dp), A_np @ x_np, rtol=1e-4)
386+
assert_allclose(_to_numpy(y_dp), A_np @ x_np, rtol=1e-4)
375387

376388

377389
# ---------------------------------------------------------------------------
@@ -406,7 +418,7 @@ def test_cg_matches_numpy_solve(self):
406418
x_ref = numpy.linalg.solve(A_np, b_np)
407419
x_dp, info = cg(A_dp, b_dp, tol=1e-10, maxiter=1000)
408420
assert info == 0
409-
assert_allclose(numpy.asarray(x_dp), x_ref, rtol=1e-6)
421+
assert_allclose(_to_numpy(x_dp), x_ref, rtol=1e-6)
410422

411423
def test_cg_x0_initial_guess(self):
412424
rng = numpy.random.default_rng(102)
@@ -417,11 +429,9 @@ def test_cg_x0_initial_guess(self):
417429
A_dp = dpnp.asarray(A_np)
418430
b_dp = dpnp.asarray(b_np)
419431

420-
# Start from a good initial guess: actual solution
421432
x_ref = numpy.linalg.solve(A_np, b_np)
422433
x0_dp = dpnp.asarray(x_ref)
423434
x_dp, info = cg(A_dp, b_dp, x0=x0_dp, tol=1e-10, maxiter=5)
424-
# Should converge immediately or with very few iterations
425435
assert _rel_residual(A_np, x_dp, b_np) < 1e-8
426436

427437
def test_cg_callback_called(self):
@@ -447,7 +457,7 @@ def test_cg_already_zero_rhs(self):
447457
b_dp = dpnp.zeros(n, dtype=numpy.float64)
448458
x_dp, info = cg(A_dp, b_dp)
449459
assert info == 0
450-
assert_allclose(numpy.asarray(x_dp), numpy.zeros(n), atol=1e-14)
460+
assert_allclose(_to_numpy(x_dp), numpy.zeros(n), atol=1e-14)
451461

452462
def test_cg_returns_dpnp_array(self):
453463
n = 4
@@ -509,7 +519,6 @@ def test_cg_dtype_preserved_in_output(self, dtype):
509519
A_np = _make_spd(n, dtype, rng)
510520
b_np = rng.standard_normal(n).astype(dtype)
511521
x_dp, _ = cg(dpnp.asarray(A_np), dpnp.asarray(b_np), tol=1e-6, maxiter=500)
512-
# Result should be float64 (working precision) or at least same family
513522
assert numpy.issubdtype(x_dp.dtype, numpy.floating)
514523

515524

@@ -545,7 +554,7 @@ def test_gmres_matches_numpy_solve(self):
545554
x_ref = numpy.linalg.solve(A_np, b_np)
546555
x_dp, info = gmres(A_dp, b_dp, tol=1e-10, maxiter=50, restart=n)
547556
assert info == 0
548-
assert_allclose(numpy.asarray(x_dp), x_ref, rtol=1e-5)
557+
assert_allclose(_to_numpy(x_dp), x_ref, rtol=1e-5)
549558

550559
def test_gmres_spd_matches_cg(self):
551560
"""On an SPD system GMRES and CG should agree."""
@@ -559,7 +568,7 @@ def test_gmres_spd_matches_cg(self):
559568

560569
x_gmres, _ = gmres(A_dp, b_dp, tol=1e-10, maxiter=100, restart=n)
561570
x_cg, _ = cg(A_dp, b_dp, tol=1e-10, maxiter=500)
562-
assert_allclose(numpy.asarray(x_gmres), numpy.asarray(x_cg), rtol=1e-5)
571+
assert_allclose(_to_numpy(x_gmres), _to_numpy(x_cg), rtol=1e-5)
563572

564573
def test_gmres_restart_parameter(self):
565574
"""Restarted GMRES (restart < n) should still converge."""
@@ -611,7 +620,7 @@ def test_gmres_already_zero_rhs(self):
611620
b_dp = dpnp.zeros(n, dtype=numpy.float64)
612621
x_dp, info = gmres(A_dp, b_dp)
613622
assert info == 0
614-
assert_allclose(numpy.asarray(x_dp), numpy.zeros(n), atol=1e-14)
623+
assert_allclose(_to_numpy(x_dp), numpy.zeros(n), atol=1e-14)
615624

616625
def test_gmres_returns_dpnp_array(self):
617626
n = 4
@@ -706,7 +715,7 @@ def test_gmres_happy_breakdown(self, n):
706715
b_dp = dpnp.arange(1, n + 1, dtype=numpy.float64)
707716
x_dp, info = gmres(A_dp, b_dp, tol=1e-12, maxiter=n, restart=n)
708717
assert info == 0
709-
assert_allclose(numpy.asarray(x_dp), numpy.arange(1, n + 1), rtol=1e-10)
718+
assert_allclose(_to_numpy(x_dp), numpy.arange(1, n + 1), rtol=1e-10)
710719

711720

712721
# ---------------------------------------------------------------------------
@@ -760,7 +769,7 @@ def test_minres_matches_scipy(self):
760769
dpnp.asarray(A_np), dpnp.asarray(b_np), tol=1e-10
761770
)
762771
assert info_dp == 0
763-
assert_allclose(numpy.asarray(x_dp), x_scipy, rtol=1e-6)
772+
assert_allclose(_to_numpy(x_dp), x_scipy, rtol=1e-6)
764773

765774
def test_minres_x0_initial_guess(self):
766775
rng = numpy.random.default_rng(303)
@@ -789,7 +798,7 @@ def test_minres_already_zero_rhs(self):
789798
b_dp = dpnp.zeros(n, dtype=numpy.float64)
790799
x_dp, info = minres(A_dp, b_dp)
791800
assert info == 0
792-
assert_allclose(numpy.asarray(x_dp), numpy.zeros(n), atol=1e-14)
801+
assert_allclose(_to_numpy(x_dp), numpy.zeros(n), atol=1e-14)
793802

794803
def test_minres_non_square_raises(self):
795804
A_dp = dpnp.ones((4, 6), dtype=numpy.float64)
@@ -806,7 +815,6 @@ def test_minres_with_shift(self):
806815
A_dp = dpnp.asarray(A_np)
807816
b_dp = dpnp.asarray(b_np)
808817

809-
# shift = 0 should be the default behaviour
810818
x_dp, info = minres(A_dp, b_dp, tol=1e-8, shift=0.0)
811819
assert info == 0
812820
assert _rel_residual(A_np, x_dp, b_np) < 1e-6
@@ -834,7 +842,6 @@ def test_minres_with_preconditioner(self):
834842
b_np = rng.standard_normal(n).astype(dtype)
835843
b_dp = dpnp.asarray(b_np)
836844

837-
# Use diagonal preconditioner M ≈ diag(A)^{-1}
838845
diag_A = numpy.diag(A_np)
839846
M_np = numpy.diag(1.0 / diag_A)
840847
M_dp = dpnp.asarray(M_np)
@@ -871,9 +878,9 @@ def test_cg_gmres_minres_agree_spd(self, n):
871878

872879
assert info_cg == 0 and info_gm == 0 and info_mr == 0
873880

874-
assert_allclose(numpy.asarray(x_cg), numpy.asarray(x_gm), rtol=1e-5,
881+
assert_allclose(_to_numpy(x_cg), _to_numpy(x_gm), rtol=1e-5,
875882
err_msg="CG and GMRES disagree")
876-
assert_allclose(numpy.asarray(x_cg), numpy.asarray(x_mr), rtol=1e-5,
883+
assert_allclose(_to_numpy(x_cg), _to_numpy(x_mr), rtol=1e-5,
877884
err_msg="CG and MINRES disagree")
878885

879886
def test_all_solvers_vs_numpy_direct(self):
@@ -892,7 +899,7 @@ def test_all_solvers_vs_numpy_direct(self):
892899

893900
for name, x_dp in [("cg", x_cg), ("gmres", x_gm), ("minres", x_mr)]:
894901
assert_allclose(
895-
numpy.asarray(x_dp), x_ref, rtol=1e-7,
902+
_to_numpy(x_dp), x_ref, rtol=1e-7,
896903
err_msg=f"{name} deviates from numpy.linalg.solve"
897904
)
898905

0 commit comments

Comments
 (0)