@@ -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