Skip to content

Commit d9ae9e7

Browse files
committed
also fixed 0-dim issue and more tight tests
1 parent b2d3f26 commit d9ae9e7

2 files changed

Lines changed: 105 additions & 5 deletions

File tree

src/csrc/quadblas_interface.cpp

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,14 @@ int
2525
qblas_dot(size_t n, Sleef_quad *x, size_t incx,
2626
Sleef_quad *y, size_t incy, Sleef_quad *result)
2727
{
28-
if (!x || !y || !result || n == 0) {
28+
if (!result) {
29+
return -1;
30+
}
31+
if (n == 0) {
32+
*result = Sleef_cast_from_doubleq1(0.0);
33+
return 0;
34+
}
35+
if (!x || !y) {
2936
return -1;
3037
}
3138
*result = cblas_qdot((int)n, x, (int)incx, y, (int)incy);
@@ -38,7 +45,10 @@ qblas_gemv(char layout, char trans, size_t m, size_t n,
3845
Sleef_quad *x, size_t incx,
3946
Sleef_quad *beta, Sleef_quad *y, size_t incy)
4047
{
41-
if (!alpha || !A || !x || !beta || !y || m == 0 || n == 0) {
48+
if (m == 0 || n == 0) {
49+
return 0;
50+
}
51+
if (!alpha || !A || !x || !beta || !y) {
4252
return -1;
4353
}
4454
cblas_qgemv(to_layout(layout), to_trans(trans),
@@ -56,7 +66,10 @@ qblas_gemm(char layout, char transa, char transb,
5666
Sleef_quad *B, size_t ldb,
5767
Sleef_quad *beta, Sleef_quad *C, size_t ldc)
5868
{
59-
if (!alpha || !A || !B || !beta || !C || m == 0 || n == 0 || k == 0) {
69+
if (m == 0 || n == 0 || k == 0) {
70+
return 0;
71+
}
72+
if (!alpha || !A || !B || !beta || !C) {
6073
return -1;
6174
}
6275
cblas_qgemm(to_layout(layout), to_trans(transa), to_trans(transb),

tests/test_dot.py

Lines changed: 89 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -417,13 +417,100 @@ def test_repeated_column_matrix(self):
417417
"""Test matrices with repeated columns"""
418418
A = create_quad_array([1, 0, 0, 0, 1, 0, 0, 0, 1], shape=(3, 3)) # Identity
419419
B = create_quad_array([2, 2, 2, 3, 3, 3, 4, 4, 4], shape=(3, 3)) # Each column repeated
420-
421420
result = np.matmul(A, B)
422-
423421
# Result should be same as B (identity multiplication)
424422
assert_quad_array_equal(result, B)
425423

426424

425+
class TestEmptyDimensions:
426+
"""Test operations with empty dimensions (zero-length)"""
427+
dtype = QuadPrecDType(backend='sleef')
428+
429+
@pytest.mark.parametrize("A_shape,B_shape", [
430+
((0,), (0,)), # 1-D dot, n=0 → scalar 0
431+
((0, 3), (3, 4)), # GEMM, m=0 → (0, 4)
432+
((2, 0), (0, 3)), # GEMM, k=0 → (2, 3) zeros
433+
((2, 3), (3, 0)), # GEMM, p=0 → (2, 0)
434+
((3, 0), (0,)), # GEMV, n=0 → (3,) zeros
435+
((0, 3), (3,)), # GEMV, m=0 → (0,)
436+
], ids=lambda s: "x".join(str(d) for d in s))
437+
def test_empty_matmul_matches_float64(self, A_shape, B_shape):
438+
A_q = np.zeros(A_shape, dtype=self.dtype)
439+
B_q = np.zeros(B_shape, dtype=self.dtype)
440+
A_f = np.zeros(A_shape, dtype=np.float64)
441+
B_f = np.zeros(B_shape, dtype=np.float64)
442+
443+
R_q = np.matmul(A_q, B_q)
444+
R_f = np.matmul(A_f, B_f)
445+
446+
# 1-D dot returns a 0-d scalar QuadPrecision (no .shape attribute).
447+
q_shape = () if isinstance(R_q, QuadPrecision) else R_q.shape
448+
assert q_shape == R_f.shape, f"shape {q_shape} != float64 {R_f.shape}"
449+
assert R_q.dtype == self.dtype
450+
451+
R_q_as_f = (np.float64(R_q) if isinstance(R_q, QuadPrecision)
452+
else np.asarray(R_q).astype(np.float64))
453+
assert np.array_equal(R_q_as_f, R_f), f"values {R_q_as_f!r} != {R_f!r}"
454+
455+
456+
class TestOutKeyword:
457+
"""np.matmul with a pre-allocated `out=` array. The result must be
458+
written into `out` and `out` must be returned (numpy ufunc semantics)."""
459+
460+
dtype = QuadPrecDType(backend='sleef')
461+
462+
@pytest.mark.parametrize("A_shape,B_shape,out_shape", [
463+
((2, 3), (3, 4), (2, 4)), # GEMM
464+
((4, 5), (5,), (4,)), # GEMV
465+
((1, 3), (3, 1), (1, 1)), # 1x1 GEMM degenerate
466+
], ids=lambda s: "x".join(str(d) for d in s) if s else "scalar")
467+
def test_out_writes_in_place(self, A_shape, B_shape, out_shape):
468+
rng = np.random.default_rng(seed=7)
469+
A_f = rng.standard_normal(A_shape)
470+
B_f = rng.standard_normal(B_shape)
471+
A_q = _qnd(A_f)
472+
B_q = _qnd(B_f)
473+
474+
# Pre-fill `out` with a sentinel; matmul must overwrite every cell.
475+
out = np.full(out_shape, 999.0, dtype=self.dtype)
476+
result = np.matmul(A_q, B_q, out=out)
477+
478+
assert result is out, "np.matmul should return the `out` array"
479+
assert out.dtype == self.dtype
480+
481+
ref = np.matmul(A_f, B_f)
482+
out_as_f = np.asarray(out).astype(np.float64)
483+
# Sentinel must be gone everywhere — catches partial writes.
484+
assert not np.any(out_as_f == 999.0), "sentinel cells were not written"
485+
assert out_as_f.shape == ref.shape
486+
np.testing.assert_allclose(out_as_f, ref, rtol=1e-14, atol=1e-14)
487+
488+
def test_out_wrong_shape_raises(self):
489+
"""Mismatched `out` shape must raise (numpy enforces this, not us,
490+
but the resolver shouldn't crash trying)."""
491+
A = _qnd(np.ones((2, 3)))
492+
B = _qnd(np.ones((3, 4)))
493+
bad_out = np.zeros((2, 5), dtype=self.dtype) # wrong cols
494+
with pytest.raises(ValueError):
495+
np.matmul(A, B, out=bad_out)
496+
497+
498+
class TestMixedBackend:
499+
"""matmul with backend-mismatched or non-SLEEF operands must fail cleanly
500+
rather than crash. The exception class is loose because M2 may change
501+
the longdouble path; the contract here is just 'raises, no segfault'."""
502+
503+
@pytest.mark.parametrize("backend_a,backend_b", [
504+
("sleef", "longdouble"),
505+
("longdouble", "sleef"),
506+
])
507+
def test_mixed_backend_raises(self, backend_a, backend_b):
508+
A = np.zeros((2, 3), dtype=QuadPrecDType(backend=backend_a))
509+
B = np.zeros((3, 2), dtype=QuadPrecDType(backend=backend_b))
510+
with pytest.raises((TypeError, NotImplementedError)):
511+
np.matmul(A, B)
512+
513+
427514
# ================================================================================
428515
# NUMERICAL STABILITY AND PRECISION TESTS
429516
# ================================================================================

0 commit comments

Comments
 (0)