@@ -492,6 +492,157 @@ def test_isinf(self):
492492# =============================================================================
493493
494494
495+ class TestTriangularExtraction :
496+ """Test upper triangle extraction."""
497+
498+ def test_triu_k0 (self ):
499+ mat = [[1 , 2 , 3 ], [4 , 5 , 6 ], [7 , 8 , 9 ]]
500+ np_result = np_backend .triu (np_backend .array (mat ), k = 0 )
501+ pure_result = pure_backend .triu (pure_backend .array (mat ), k = 0 )
502+ assert to_list (np_result .flatten ()) == to_list (pure_result .flatten ())
503+ assert to_list (pure_result .flatten ()) == [1 , 2 , 3 , 0 , 5 , 6 , 0 , 0 , 9 ]
504+
505+ def test_triu_k1 (self ):
506+ mat = [[1 , 2 , 3 ], [4 , 5 , 6 ], [7 , 8 , 9 ]]
507+ np_result = np_backend .triu (np_backend .array (mat ), k = 1 )
508+ pure_result = pure_backend .triu (pure_backend .array (mat ), k = 1 )
509+ assert to_list (np_result .flatten ()) == to_list (pure_result .flatten ())
510+ assert to_list (pure_result .flatten ()) == [0 , 2 , 3 , 0 , 0 , 6 , 0 , 0 , 0 ]
511+
512+ def test_triu_k_neg1 (self ):
513+ mat = [[1 , 2 , 3 ], [4 , 5 , 6 ], [7 , 8 , 9 ]]
514+ np_result = np_backend .triu (np_backend .array (mat ), k = - 1 )
515+ pure_result = pure_backend .triu (pure_backend .array (mat ), k = - 1 )
516+ assert to_list (np_result .flatten ()) == to_list (pure_result .flatten ())
517+ assert to_list (pure_result .flatten ()) == [1 , 2 , 3 , 4 , 5 , 6 , 0 , 8 , 9 ]
518+
519+
520+ class TestInvert :
521+ """Test boolean inversion."""
522+
523+ def test_invert_list (self ):
524+ bools = [True , False , True , False , True ]
525+ pure_result = pure_backend .invert (pure_backend .array (bools ))
526+ assert to_list (pure_result ) == [False , True , False , True , False ]
527+
528+ def test_invert_gfoarray (self ):
529+ arr = pure_backend .array ([True , False , False , True ])
530+ result = pure_backend .invert (arr )
531+ assert to_list (result ) == [False , True , True , False ]
532+
533+
534+ class TestDefaultRng :
535+ """Test Generator factory via default_rng."""
536+
537+ def test_generator_has_methods (self ):
538+ rng = pure_backend .random .default_rng (42 )
539+ assert callable (getattr (rng , "random" , None ))
540+ assert callable (getattr (rng , "standard_normal" , None ))
541+ assert callable (getattr (rng , "uniform" , None ))
542+ assert callable (getattr (rng , "integers" , None ))
543+
544+ def test_seeded_determinism (self ):
545+ rng1 = pure_backend .random .default_rng (123 )
546+ vals1 = to_list (rng1 .random (size = 5 ))
547+ rng2 = pure_backend .random .default_rng (123 )
548+ vals2 = to_list (rng2 .random (size = 5 ))
549+ assert vals1 == vals2
550+
551+ def test_array_output_shape (self ):
552+ rng = pure_backend .random .default_rng (0 )
553+ result = rng .uniform (0.0 , 1.0 , size = 10 )
554+ assert len (to_list (result )) == 10
555+
556+ def test_integers_range (self ):
557+ rng = pure_backend .random .default_rng (7 )
558+ result = rng .integers (0 , 10 , size = 50 )
559+ values = to_list (result )
560+ assert all (0 <= v < 10 for v in values )
561+
562+
563+ class TestLinAlgError :
564+ """Test LinAlgError exception type."""
565+
566+ def test_is_exception_class (self ):
567+ assert issubclass (pure_backend .linalg .LinAlgError , Exception )
568+ assert issubclass (np_backend .linalg .LinAlgError , Exception )
569+
570+ def test_can_be_raised_and_caught (self ):
571+ with pytest .raises (pure_backend .linalg .LinAlgError ):
572+ raise pure_backend .linalg .LinAlgError ("singular matrix" )
573+
574+ def test_can_be_caught_as_exception (self ):
575+ with pytest .raises (Exception , match = "test" ):
576+ raise np_backend .linalg .LinAlgError ("test" )
577+
578+
579+ class TestLinAlgEigh :
580+ """Test eigendecomposition."""
581+
582+ def test_numpy_backend_returns_eigenvalues_and_vectors (self ):
583+ mat = np_backend .array ([[2.0 , 1.0 ], [1.0 , 3.0 ]])
584+ eigenvalues , eigenvectors = np_backend .linalg .eigh (mat )
585+ assert len (to_list (eigenvalues )) == 2
586+ assert eigenvectors .shape == (2 , 2 )
587+
588+ def test_pure_backend_raises (self ):
589+ mat = pure_backend .array ([[2.0 , 1.0 ], [1.0 , 3.0 ]])
590+ with pytest .raises (NotImplementedError ):
591+ pure_backend .linalg .eigh (mat )
592+
593+
594+ class TestNdarrayType :
595+ """Test ndarray type export."""
596+
597+ def test_numpy_isinstance (self ):
598+ import gradient_free_optimizers ._array_backend as arr_backend
599+
600+ arr = arr_backend .array ([1 , 2 , 3 ])
601+ assert isinstance (arr , arr_backend .ndarray )
602+
603+ def test_pure_isinstance (self ):
604+ arr = pure_backend .array ([1 , 2 , 3 ])
605+ from gradient_free_optimizers ._array_backend ._pure import GFOArray
606+
607+ assert isinstance (arr , GFOArray )
608+
609+
610+ class TestBooleanSetitem :
611+ """Test boolean indexing with __setitem__."""
612+
613+ def test_setitem_scalar (self ):
614+ arr = pure_backend .array ([1.0 , 2.0 , 3.0 , 4.0 ])
615+ mask = [True , False , True , False ]
616+ arr [mask ] = 0.0
617+ assert to_list (arr ) == [0.0 , 2.0 , 0.0 , 4.0 ]
618+
619+ def test_setitem_array_values (self ):
620+ arr = pure_backend .array ([1.0 , 2.0 , 3.0 , 4.0 ])
621+ mask = [False , True , False , True ]
622+ arr [mask ] = pure_backend .array ([20.0 , 40.0 ])
623+ assert to_list (arr ) == [1.0 , 20.0 , 3.0 , 40.0 ]
624+
625+
626+ class TestReshapeNegativeOne :
627+ """Test reshape with -1 dimension inference."""
628+
629+ def test_reshape_neg1_first (self ):
630+ arr = pure_backend .array ([1 , 2 , 3 , 4 , 5 , 6 ])
631+ result = arr .reshape ((- 1 , 2 ))
632+ assert result .shape == (3 , 2 )
633+
634+ def test_reshape_neg1_second (self ):
635+ arr = pure_backend .array ([1 , 2 , 3 , 4 , 5 , 6 ])
636+ result = arr .reshape ((2 , - 1 ))
637+ assert result .shape == (2 , 3 )
638+
639+ def test_reshape_neg1_matches_numpy (self ):
640+ values = list (range (12 ))
641+ np_result = np_backend .array (values ).reshape ((- 1 , 3 ))
642+ pure_result = pure_backend .array (values ).reshape ((- 1 , 3 ))
643+ assert np_result .shape == pure_result .shape == (4 , 3 )
644+
645+
495646class TestBackendSelection :
496647 """Test that backend selection works correctly."""
497648
0 commit comments