@@ -113,11 +113,18 @@ def test_classify_using_hessian(self, simulator, dataset):
113113 "Z" ,
114114 ):
115115 assert k in info
116- assert "hessian_fd" in info and info ["hessian_fd" ].shape == (2 , 2 )
117- assert "eigenvalues" in info and info ["eigenvalues" ].shape == (2 ,)
118- assert "eigenvectors" in info and info ["eigenvectors" ].shape == (2 , 2 )
116+ assert info ["hessian_fd" ].shape == (2 , 2 )
117+ assert info ["eigenvalues" ].shape == (2 ,)
118+ assert info ["eigenvectors" ].shape == (2 , 2 )
119119 assert info ["x" ].shape == (2 ,)
120- assert "Z" in info and info ["Z" ].ndim == 2
120+ assert info ["dx" ].shape == (2 ,)
121+ assert len (info ["names" ]) == 2
122+ assert isinstance (info ["best_cost" ], float )
123+ assert isinstance (info ["span0" ], tuple )
124+ assert len (info ["span0" ]) == 2
125+ assert isinstance (info ["span1" ], tuple )
126+ assert len (info ["span1" ]) == 2
127+ assert info ["Z" ].ndim == 2
121128 assert info ["Z" ].shape [0 ] == info ["Z" ].shape [1 ] # grid is square
122129
123130 # Hessian may be NaN on some model combinations
@@ -256,80 +263,3 @@ def test_insensitive_classify_using_hessian(self, model, parameter_values):
256263 "Classification cannot proceed due to infinite cost value(s)."
257264 " The result is near the upper bound of R0_a [Ohm]."
258265 )
259-
260- def test_quadratic_hessian_full_coverage (self ):
261- """
262- Construct a tiny deterministic quadratic problem so classify_using_hessian
263- executes the full finite-difference Hessian calculation, eigen decomposition,
264- the grid evaluation loop (no exceptions), and the final packing/return.
265- """
266-
267- # Minimal test parameters object used by classify_using_hessian
268- class TestParameters :
269- def __init__ (self ):
270- self .names = ["p0" , "p1" ]
271-
272- def get_bounds (self ):
273- return {"lower" : np .array ([- 10.0 , - 10.0 ]), "upper" : np .array ([10.0 , 10.0 ])}
274-
275- class TestProblem :
276- def __init__ (self , A , b , c = 0.0 ):
277- self .parameters = TestParameters ()
278- self .A = np .asarray (A , dtype = float )
279- self .b = np .asarray (b , dtype = float )
280- self .c = float (c )
281-
282- def evaluate (self , x ):
283- x = np .asarray (x , dtype = float )
284- val = 0.5 * float (x .T @ self .A @ x ) + float (self .b .dot (x )) + self .c
285- return type ("R" , (), {"values" : val })
286-
287- class TestOptim :
288- def __init__ (self , problem ):
289- self .problem = problem
290-
291- class TestResult :
292- pass
293-
294- A = np .array ([[4.0 , 0.5 ], [0.5 , 2.0 ]])
295- b = np .array ([0.1 , - 0.2 ])
296- problem = TestProblem (A , b )
297-
298- x = np .array ([1.23 , - 0.45 ], dtype = float )
299- best_cost = problem .evaluate (x ).values
300-
301- optim = TestOptim (problem = problem )
302-
303- result = TestResult ()
304- result .x = x
305- result .best_cost = best_cost
306- result .optim = optim
307- result .minimising = True
308-
309- dx = np .array ([1e-3 , 1e-3 ], dtype = float )
310-
311- message , info = pybop .classify_using_hessian (result , dx = dx , cost_tolerance = 1e-8 )
312-
313- assert isinstance (info , dict )
314- H = info ["hessian_fd" ]
315- assert H .shape == (2 , 2 )
316- assert np .isfinite (H ).all ()
317- assert np .allclose (H , H .T , atol = 1e-8 )
318-
319- evals = info ["eigenvalues" ]
320- evecs = info ["eigenvectors" ]
321- assert evals .shape == (2 ,)
322- assert evecs .shape == (2 , 2 )
323- assert np .isfinite (evals ).all ()
324- assert np .isfinite (evecs ).all ()
325-
326- assert "minimum" in message .lower ()
327-
328- assert "param0" in info and "param1" in info and "Z" in info
329- param0 = info ["param0" ]
330- param1 = info ["param1" ]
331- Z = info ["Z" ]
332- assert Z .shape == (41 , 41 )
333- assert np .isfinite (Z ).all ()
334- assert param0 .min () < x [0 ] < param0 .max ()
335- assert param1 .min () < x [1 ] < param1 .max ()
0 commit comments