Skip to content

Commit bf5adae

Browse files
0.32.2
1 parent 2e45329 commit bf5adae

2 files changed

Lines changed: 238 additions & 0 deletions

File tree

test/test_build_Psi_matrix.py

Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
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()

test/test_build_psi_vector.py

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
import numpy as np
2+
import pytest
3+
from spotpython.surrogate.kriging import Kriging
4+
5+
6+
def test_build_psi_vec_basic():
7+
"""Test basic functionality of build_psi_vec"""
8+
# Setup simple test case
9+
X = np.array([[0.0, 0.0], [1.0, 1.0]])
10+
y = np.array([0.0, 1.0])
11+
12+
model = Kriging()
13+
model.fit(X, y)
14+
15+
# Test point
16+
x_test = np.array([0.5, 0.5])
17+
psi = model.build_psi_vec(x_test)
18+
19+
# Check shape and basic properties
20+
assert psi.shape == (2,)
21+
assert np.all(psi >= 0) and np.all(psi <= 1)
22+
23+
24+
def test_build_psi_vec_isotropic():
25+
"""Test build_psi_vec with isotropic model"""
26+
X = np.array([[0.0, 0.0], [1.0, 0.0], [0.0, 1.0]])
27+
y = np.array([0.0, 1.0, 1.0])
28+
29+
model = Kriging(isotropic=True)
30+
model.fit(X, y)
31+
32+
# Points with same distance should have same correlation
33+
x_test1 = np.array([0.5, 0.0]) # Distance 0.5 from [0,0]
34+
x_test2 = np.array([0.0, 0.5]) # Distance 0.5 from [0,0]
35+
36+
psi1 = model.build_psi_vec(x_test1)
37+
psi2 = model.build_psi_vec(x_test2)
38+
39+
assert np.allclose(psi1, psi2)
40+
41+
42+
def test_build_psi_vec_with_categorical():
43+
"""Test build_psi_vec with mixed numeric and categorical variables"""
44+
X = np.array([[0.0, 0], [1.0, 0], [0.0, 1]])
45+
y = np.array([0.0, 1.0, 2.0])
46+
47+
model = Kriging(var_type=["num", "factor"])
48+
model.fit(X, y)
49+
50+
# Test points with same/different categories
51+
x_same_cat = np.array([0.5, 0]) # Same category as first point
52+
x_diff_cat = np.array([0.5, 1]) # Different category
53+
54+
psi_same = model.build_psi_vec(x_same_cat)
55+
psi_diff = model.build_psi_vec(x_diff_cat)
56+
57+
# Correlation should be higher for same category
58+
assert psi_same[0] > psi_diff[0]
59+
60+
61+
def test_build_psi_vec_extreme_distances():
62+
"""Test build_psi_vec with extreme distances"""
63+
X = np.array([[0.0, 0.0], [1.0, 1.0]])
64+
y = np.array([0.0, 1.0])
65+
66+
model = Kriging()
67+
model.fit(X, y)
68+
69+
# Test very close point
70+
x_close = np.array([0.0, 0.0]) # Same as first training point
71+
psi_close = model.build_psi_vec(x_close)
72+
assert np.isclose(psi_close[0], 1.0, atol=1e-10)
73+
74+
# Test very far point
75+
x_far = np.array([100.0, 100.0])
76+
psi_far = model.build_psi_vec(x_far)
77+
assert np.all(psi_far < 0.1) # Should have low correlation
78+
79+
80+
def test_build_psi_vec_numerical_stability():
81+
"""Test numerical stability of build_psi_vec"""
82+
X = np.array([[0.0, 0.0], [1e-10, 1e-10]])
83+
y = np.array([0.0, 1.0])
84+
85+
model = Kriging(nugget=1e-10)
86+
model.fit(X, y)
87+
88+
# Test with very small distances
89+
x_test = np.array([1e-20, 1e-20])
90+
psi = model.build_psi_vec(x_test)
91+
92+
assert not np.any(np.isnan(psi))
93+
assert not np.any(np.isinf(psi))
94+
95+
96+
def test_build_psi_vec_different_scales():
97+
"""Test build_psi_vec with variables on different scales"""
98+
X = np.array([[0.0, 0.0], [0.1, 1000.0]])
99+
y = np.array([0.0, 1.0])
100+
101+
model = Kriging()
102+
model.fit(X, y)
103+
104+
x_test = np.array([0.05, 500.0])
105+
psi = model.build_psi_vec(x_test)
106+
107+
assert not np.any(np.isnan(psi))
108+
assert np.all(psi >= 0) and np.all(psi <= 1)

0 commit comments

Comments
 (0)