1+ import numpy as np
2+ import pytest
3+
4+ from spotpython .surrogate .kriging import Kriging
5+
6+
7+ def _make_xy (k = 2 ):
8+ X = np .array ([[0.0 ] * k , [0.5 ] * k , [1.0 ] * k ], dtype = float )
9+ y = np .array ([0.1 , 0.2 , 0.3 ], dtype = float )
10+ return X , y
11+
12+
13+ def test_fit_regression_default_bounds_and_state_updated (monkeypatch ):
14+ X , y = _make_xy (k = 2 )
15+ model = Kriging (method = "regression" )
16+
17+ seen = {"bounds" : None , "x_likelihood" : None }
18+
19+ def fake_max_likelihood (bounds ):
20+ seen ["bounds" ] = bounds
21+ # k=2 thetas + 1 lambda
22+ return np .array ([0.1 , - 0.2 , - 6.0 ], dtype = float ), - 999.0
23+
24+ def fake_likelihood (x ):
25+ seen ["x_likelihood" ] = x
26+ n = X .shape [0 ]
27+ return 3.14 , np .eye (n ), np .eye (n )
28+
29+ monkeypatch .setattr (model , "max_likelihood" , fake_max_likelihood , raising = True )
30+ monkeypatch .setattr (model , "likelihood" , fake_likelihood , raising = True )
31+
32+ ret = model .fit (X , y )
33+
34+ assert ret is model
35+
36+ expected_bounds = [(model .min_theta , model .max_theta )] * X .shape [1 ] + [
37+ (model .min_Lambda , model .max_Lambda )
38+ ]
39+ assert seen ["bounds" ] == expected_bounds
40+
41+ assert np .allclose (model .theta , np .array ([0.1 , - 0.2 ]))
42+ assert np .allclose (model .Lambda , np .array ([- 6.0 ]))
43+ assert np .allclose (model .logtheta_lambda_ , np .array ([0.1 , - 0.2 , - 6.0 ]))
44+ assert model .negLnLike == pytest .approx (3.14 )
45+ assert np .allclose (model .Psi_ , np .eye (X .shape [0 ]))
46+ assert np .allclose (model .U_ , np .eye (X .shape [0 ]))
47+ assert np .allclose (seen ["x_likelihood" ], np .array ([0.1 , - 0.2 , - 6.0 ]))
48+
49+
50+ def test_fit_interpolation_bounds_no_lambda_and_lambda_none (monkeypatch ):
51+ X , y = _make_xy (k = 2 )
52+ model = Kriging (method = "interpolation" )
53+
54+ seen = {"bounds" : None , "x_likelihood" : None }
55+
56+ def fake_max_likelihood (bounds ):
57+ seen ["bounds" ] = bounds
58+ # Only k=2 thetas
59+ return np .array ([0.25 , - 0.75 ], dtype = float ), - 1.0
60+
61+ def fake_likelihood (x ):
62+ seen ["x_likelihood" ] = x
63+ n = X .shape [0 ]
64+ return 1.23 , np .eye (n ), np .eye (n )
65+
66+ monkeypatch .setattr (model , "max_likelihood" , fake_max_likelihood , raising = True )
67+ monkeypatch .setattr (model , "likelihood" , fake_likelihood , raising = True )
68+
69+ model .fit (X , y )
70+
71+ expected_bounds = [(model .min_theta , model .max_theta )] * X .shape [1 ]
72+ assert seen ["bounds" ] == expected_bounds
73+
74+ assert np .allclose (model .theta , np .array ([0.25 , - 0.75 ]))
75+ assert model .Lambda is None
76+ assert np .allclose (model .logtheta_lambda_ , np .array ([0.25 , - 0.75 ]))
77+ assert model .negLnLike == pytest .approx (1.23 )
78+ assert np .allclose (model .Psi_ , np .eye (X .shape [0 ]))
79+ assert np .allclose (model .U_ , np .eye (X .shape [0 ]))
80+ assert np .allclose (seen ["x_likelihood" ], np .array ([0.25 , - 0.75 ]))
81+
82+
83+ def test_fit_reinterpolation_bounds_and_state_updated (monkeypatch ):
84+ X , y = _make_xy (k = 3 )
85+ model = Kriging (method = "reinterpolation" )
86+
87+ seen = {"bounds" : None }
88+
89+ def fake_max_likelihood (bounds ):
90+ seen ["bounds" ] = bounds
91+ # k=3 thetas + 1 lambda
92+ return np .array ([0.0 , 0.1 , 0.2 , - 3.0 ], dtype = float ), - 5.0
93+
94+ def fake_likelihood (x ):
95+ n = X .shape [0 ]
96+ return 0.77 , np .eye (n ), np .eye (n )
97+
98+ monkeypatch .setattr (model , "max_likelihood" , fake_max_likelihood , raising = True )
99+ monkeypatch .setattr (model , "likelihood" , fake_likelihood , raising = True )
100+
101+ model .fit (X , y )
102+
103+ expected_bounds = [(model .min_theta , model .max_theta )] * X .shape [1 ] + [
104+ (model .min_Lambda , model .max_Lambda )
105+ ]
106+ assert seen ["bounds" ] == expected_bounds
107+
108+ assert np .allclose (model .theta , np .array ([0.0 , 0.1 , 0.2 ]))
109+ assert np .allclose (model .Lambda , np .array ([- 3.0 ]))
110+ assert np .allclose (model .logtheta_lambda_ , np .array ([0.0 , 0.1 , 0.2 , - 3.0 ]))
111+ assert model .negLnLike == pytest .approx (0.77 )
112+ assert np .allclose (model .Psi_ , np .eye (X .shape [0 ]))
113+ assert np .allclose (model .U_ , np .eye (X .shape [0 ]))
114+
115+
116+ def test_fit_isotropic_uses_k_theta_bounds_but_sets_n_theta_1 (monkeypatch ):
117+ X , y = _make_xy (k = 3 )
118+ model = Kriging (method = "regression" , isotropic = True )
119+
120+ seen = {"bounds" : None }
121+
122+ def fake_max_likelihood (bounds ):
123+ seen ["bounds" ] = bounds
124+ # despite isotropic, code builds k thetas + 1 lambda bounds
125+ return np .array ([0.5 , 0.4 , 0.3 , - 4.0 ], dtype = float ), - 2.0
126+
127+ def fake_likelihood (x ):
128+ n = X .shape [0 ]
129+ return 2.22 , np .eye (n ), np .eye (n )
130+
131+ monkeypatch .setattr (model , "max_likelihood" , fake_max_likelihood , raising = True )
132+ monkeypatch .setattr (model , "likelihood" , fake_likelihood , raising = True )
133+
134+ model .fit (X , y )
135+
136+ expected_bounds = [(model .min_theta , model .max_theta )] * X .shape [1 ] + [
137+ (model .min_Lambda , model .max_Lambda )
138+ ]
139+ assert seen ["bounds" ] == expected_bounds
140+ assert model .n_theta == 1
141+ # Only first theta retained in model.theta for isotropic
142+ assert np .allclose (model .theta , np .array ([0.5 ]))
143+ # Lambda is taken from the first param after n_theta
144+ assert np .allclose (model .Lambda , np .array ([0.4 ]))
145+ # logtheta_lambda_ still holds all optimized parameters
146+ assert np .allclose (model .logtheta_lambda_ , np .array ([0.5 , 0.4 , 0.3 , - 4.0 ]))
147+
148+
149+ def test_fit_respects_explicit_bounds_override (monkeypatch ):
150+ X , y = _make_xy (k = 2 )
151+ model = Kriging (method = "regression" )
152+
153+ seen = {"bounds" : None , "x_likelihood" : None }
154+
155+ def fake_max_likelihood (bounds ):
156+ seen ["bounds" ] = bounds
157+ # Must match provided bounds length (2 theta + 1 lambda)
158+ return np .array ([1.0 , - 1.0 , - 2.0 ], dtype = float ), - 7.0
159+
160+ def fake_likelihood (x ):
161+ seen ["x_likelihood" ] = x
162+ n = X .shape [0 ]
163+ return 4.56 , np .eye (n ), np .eye (n )
164+
165+ monkeypatch .setattr (model , "max_likelihood" , fake_max_likelihood , raising = True )
166+ monkeypatch .setattr (model , "likelihood" , fake_likelihood , raising = True )
167+
168+ custom_bounds = [(- 1.0 , 1.0 ), (- 2.0 , 2.0 ), (- 8.0 , - 1.0 )]
169+ model .fit (X , y , bounds = custom_bounds )
170+
171+ assert seen ["bounds" ] == custom_bounds
172+ assert np .allclose (model .theta , np .array ([1.0 , - 1.0 ]))
173+ assert np .allclose (model .Lambda , np .array ([- 2.0 ]))
174+ assert model .negLnLike == pytest .approx (4.56 )
175+ assert np .allclose (seen ["x_likelihood" ], np .array ([1.0 , - 1.0 , - 2.0 ]))
176+
177+
178+ def test_fit_regression_with_optim_p_adds_p_bounds_and_sets_p (monkeypatch ):
179+ X , y = _make_xy (k = 2 )
180+ model = Kriging (method = "regression" , optim_p = True , n_p = 2 , min_p = 1.1 , max_p = 1.9 )
181+
182+ seen = {"bounds" : None }
183+
184+ def fake_max_likelihood (bounds ):
185+ seen ["bounds" ] = bounds
186+ # k=2 thetas + 1 lambda + n_p=2 p-values
187+ return np .array ([0.0 , 0.2 , - 5.0 , 1.3 , 1.7 ], dtype = float ), - 10.0
188+
189+ def fake_likelihood (x ):
190+ n = X .shape [0 ]
191+ return 0.5 , np .eye (n ), np .eye (n )
192+
193+ monkeypatch .setattr (model , "max_likelihood" , fake_max_likelihood , raising = True )
194+ monkeypatch .setattr (model , "likelihood" , fake_likelihood , raising = True )
195+
196+ model .fit (X , y )
197+
198+ expected_bounds = (
199+ [(model .min_theta , model .max_theta )] * X .shape [1 ]
200+ + [(model .min_Lambda , model .max_Lambda )]
201+ + [(model .min_p , model .max_p )] * model .n_p
202+ )
203+ assert seen ["bounds" ] == expected_bounds
204+ assert np .allclose (model .theta , np .array ([0.0 , 0.2 ]))
205+ assert np .allclose (model .Lambda , np .array ([- 5.0 ]))
206+ assert np .allclose (model .p_val , np .array ([1.3 , 1.7 ]))
207+
208+
209+ def test_fit_interpolation_with_optim_p_adds_p_bounds_and_sets_p (monkeypatch ):
210+ X , y = _make_xy (k = 2 )
211+ model = Kriging (method = "interpolation" , optim_p = True , n_p = 2 , min_p = 1.2 , max_p = 1.8 )
212+
213+ seen = {"bounds" : None }
214+
215+ def fake_max_likelihood (bounds ):
216+ seen ["bounds" ] = bounds
217+ # k=2 thetas + n_p=2 p-values
218+ return np .array ([0.3 , - 0.4 , 1.25 , 1.55 ], dtype = float ), - 3.0
219+
220+ def fake_likelihood (x ):
221+ n = X .shape [0 ]
222+ return 1.0 , np .eye (n ), np .eye (n )
223+
224+ monkeypatch .setattr (model , "max_likelihood" , fake_max_likelihood , raising = True )
225+ monkeypatch .setattr (model , "likelihood" , fake_likelihood , raising = True )
226+
227+ model .fit (X , y )
228+
229+ expected_bounds = (
230+ [(model .min_theta , model .max_theta )] * X .shape [1 ]
231+ + [(model .min_p , model .max_p )] * model .n_p
232+ )
233+ assert seen ["bounds" ] == expected_bounds
234+ assert model .Lambda is None
235+ assert np .allclose (model .theta , np .array ([0.3 , - 0.4 ]))
236+ assert np .allclose (model .p_val , np .array ([1.25 , 1.55 ]))
0 commit comments