1+ import numpy as np
2+ import pytest
3+ from spotpython .surrogate .kriging import Kriging
4+ from numpy .linalg import LinAlgError
5+
6+ def test_build_Psi_basic_properties ():
7+ """Test basic properties of the correlation matrix"""
8+ # Setup
9+ X = np .array ([[0.0 , 0.0 ], [1.0 , 1.0 ], [2.0 , 2.0 ]])
10+ y = np .array ([0.0 , 1.0 , 2.0 ])
11+ model = Kriging ()
12+ model .fit (X , y )
13+
14+ # Get upper triangle of Psi
15+ Psi_upper = model .build_Psi ()
16+ n = X .shape [0 ]
17+
18+ # Test shape
19+ assert Psi_upper .shape == (n , n )
20+
21+ # Test that it's upper triangular (lower part is zero)
22+ assert np .allclose (np .tril (Psi_upper , k = - 1 ), 0 )
23+
24+ # Test values are between 0 and 1
25+ assert np .all (Psi_upper >= 0 )
26+ assert np .all (Psi_upper <= 1 )
27+
28+ def test_build_Psi_symmetry ():
29+ """Test that the full correlation matrix is symmetric"""
30+ X = np .array ([[0.0 , 0.0 ], [1.0 , 0.0 ], [0.0 , 1.0 ]])
31+ y = np .array ([0.0 , 1.0 , 1.0 ])
32+ model = Kriging ()
33+ model .fit (X , y )
34+
35+ Psi_upper = model .build_Psi ()
36+ # Construct full Psi (add upper triangle to its transpose)
37+ Psi_full = Psi_upper + Psi_upper .T + np .eye (X .shape [0 ])
38+
39+ # Test symmetry
40+ assert np .allclose (Psi_full , Psi_full .T )
41+
42+ def test_build_Psi_distance_correlation ():
43+ """Test that correlation decreases with distance"""
44+ X = np .array ([[0.0 , 0.0 ], [0.1 , 0.1 ], [1.0 , 1.0 ]])
45+ y = np .array ([0.0 , 0.1 , 1.0 ])
46+ model = Kriging ()
47+ model .fit (X , y )
48+
49+ Psi_upper = model .build_Psi ()
50+
51+ # Points closer together should have higher correlation
52+ # than points further apart
53+ corr_close = Psi_upper [0 , 1 ] # correlation between [0,0] and [0.1,0.1]
54+ corr_far = Psi_upper [0 , 2 ] # correlation between [0,0] and [1.0,1.0]
55+ assert corr_close > corr_far
56+
57+ def test_build_Psi_with_categorical ():
58+ """Test Psi correlation matrix construction with mixed numeric and categorical variables"""
59+ # Create a simple dataset with clear categorical patterns
60+ X = np .array ([
61+ [0.5 , 0 ], # First point, category 0
62+ [0.6 , 0 ], # Close to first point, same category
63+ [0.5 , 1 ], # Same as first point but different category
64+ ])
65+ y = np .array ([0.0 , 0.1 , 1.0 ])
66+
67+ # Initialize Kriging with explicit categorical specification
68+ model = Kriging (
69+ var_type = ["num" , "factor" ],
70+ theta0 = [1.0 , 1.0 ], # Set initial theta values
71+ nugget = 1e-6 # Add small nugget for numerical stability
72+ )
73+ model .fit (X , y )
74+
75+ Psi_upper = model .build_Psi ()
76+
77+ # Points 0 and 1: Similar x-values, same category
78+ corr_same_cat = Psi_upper [0 , 1 ]
79+ # Points 0 and 2: Same x-value, different category
80+ corr_diff_cat = Psi_upper [0 , 2 ]
81+
82+ print (f"Correlation for same category: { corr_same_cat } " )
83+ print (f"Correlation for different category: { corr_diff_cat } " )
84+
85+ # Use np.isclose with high relative tolerance for numerical stability
86+ assert corr_same_cat > 0.1 # Should have meaningful correlation
87+ assert corr_diff_cat < corr_same_cat # Different category should have lower correlation
88+
89+ def test_build_Psi_isotropic ():
90+ """Test Psi construction with isotropic model"""
91+ X = np .array ([[0.0 , 0.0 ], [1.0 , 1.0 ], [2.0 , 2.0 ]])
92+ y = np .array ([0.0 , 1.0 , 2.0 ])
93+ model = Kriging (isotropic = True )
94+ model .fit (X , y )
95+
96+ Psi_upper = model .build_Psi ()
97+
98+ # For isotropic model, correlations should depend only on
99+ # total distance, not direction
100+ dist_01 = np .sqrt (2 ) # distance between points 0 and 1
101+ dist_12 = np .sqrt (2 ) # distance between points 1 and 2
102+ assert np .isclose (Psi_upper [0 , 1 ], Psi_upper [1 , 2 ])
103+
104+ def test_build_Psi_numerical_stability ():
105+ """Test numerical stability with less extreme values"""
106+ X = np .array ([[0.0 , 0.0 ],
107+ [1e-5 , 1e-5 ], # Changed from 1e-10
108+ [1.0 , 1.0 ]])
109+ y = np .array ([0.0 , 1e-5 , 1.0 ]) # Changed from 1e-10
110+ model = Kriging (nugget = 1e-6 ) # Add small nugget for stability
111+ model .fit (X , y )
112+
113+ Psi_upper = model .build_Psi ()
114+
115+ # Check that matrix is not ill-conditioned
116+ assert not np .any (np .isnan (Psi_upper ))
117+ assert not np .any (np .isinf (Psi_upper ))
118+ assert model .cnd_Psi < 1e12 # Relaxed condition number threshold
119+
120+ def test_build_Psi_errors ():
121+ """Test error handling"""
122+ # Use empty arrays instead of single point
123+ X = np .array ([])
124+ y = np .array ([])
125+ model = Kriging ()
126+
127+ # Should raise error for empty arrays
128+ with pytest .raises ((ValueError , LinAlgError )):
129+ model .fit (X , y )
130+ model .build_Psi ()
0 commit comments