34
34
import numpy as np
35
35
import scipy .linalg as LA
36
36
37
- from .hessian import Hessian , to_upper_triangular_vector
37
+ from .hessian import to_upper_triangular_vector
38
38
from .trust_region import trsbox_geometry
39
39
from .util import sumsq , model_value
40
40
@@ -74,7 +74,7 @@ def __init__(self, npt, x0, f0, xl, xu, f0_nsamples, n=None, abs_tol=-1e20, prec
74
74
# Model information
75
75
self .model_const = 0.0 # constant term for model m(s) = c + J*s
76
76
self .model_grad = np .zeros ((n ,)) # Jacobian term for model m(s) = c + J*s
77
- self .model_hess = Hessian ( n )
77
+ self .model_hess = np . zeros (( n , n ) )
78
78
79
79
# Saved point (in absolute coordinates) - always check this value before quitting solver
80
80
self .xsave = None
@@ -199,7 +199,7 @@ def shift_base(self, xbase_shift):
199
199
self .factorisation_current = False
200
200
201
201
# Update model (always centred on xbase)
202
- Hx = self .model_hess .vec_mul (xbase_shift )
202
+ Hx = self .model_hess .dot (xbase_shift )
203
203
self .model_const += np .dot (self .model_grad + 0.5 * Hx , xbase_shift )
204
204
self .model_grad += Hx
205
205
return
@@ -209,7 +209,7 @@ def save_point(self, x, f, nsamples, x_in_abs_coords=True):
209
209
self .xsave = x .copy () if x_in_abs_coords else self .as_absolute_coordinates (x )
210
210
self .fsave = f
211
211
self .gradsave = self .model_grad .copy ()
212
- self .hesssave = self .model_hess .as_full (). copy ()
212
+ self .hesssave = self .model_hess .copy ()
213
213
self .nsamples_save = nsamples
214
214
return True
215
215
else :
@@ -219,7 +219,7 @@ def get_final_results(self):
219
219
# Return x and fval for optimal point (either from xsave+fsave or kopt)
220
220
if self .fsave is None or self .fopt () <= self .fsave : # optimal has changed since xsave+fsave were last set
221
221
g , hess = self .build_full_model () # model based at xopt
222
- return self .xopt (abs_coordinates = True ).copy (), self .fopt (), g , hess . as_full () , self .nsamples [self .kopt ]
222
+ return self .xopt (abs_coordinates = True ).copy (), self .fopt (), g , hess , self .nsamples [self .kopt ]
223
223
else :
224
224
return self .xsave , self .fsave , self .gradsave , self .hesssave , self .nsamples_save
225
225
@@ -231,7 +231,7 @@ def model_value(self, d, d_based_at_xopt=True, with_const_term=False):
231
231
# Model is always centred around xbase
232
232
const = self .model_const if with_const_term else 0.0
233
233
d_to_use = d + self .xopt () if d_based_at_xopt else d
234
- Hd = self .model_hess .vec_mul (d_to_use )
234
+ Hd = self .model_hess .dot (d_to_use )
235
235
return const + np .dot (self .model_grad + 0.5 * Hd , d_to_use )
236
236
237
237
def interpolation_matrix (self ):
@@ -281,7 +281,7 @@ def interpolate_model(self, verbose=False, min_chg_hess=True, get_norm_model_chg
281
281
# It's good to see which bits are needed for this specifically (here & 1 line below)
282
282
for t in range (self .npt ()- 1 ):
283
283
dx = self .xpt (fval_row_idx [t ]) - self .xopt ()
284
- rhs [t ] = rhs [t ] - 0.5 * np .dot (dx , self .model_hess .vec_mul (dx )) # include old Hessian
284
+ rhs [t ] = rhs [t ] - 0.5 * np .dot (dx , self .model_hess .dot (dx )) # include old Hessian
285
285
286
286
try :
287
287
coeffs = self .solve_system (rhs )
@@ -296,7 +296,7 @@ def interpolate_model(self, verbose=False, min_chg_hess=True, get_norm_model_chg
296
296
# Old gradient and Hessian (save so can compute changes later)
297
297
if verbose or get_norm_model_chg :
298
298
old_model_grad = self .model_grad .copy ()
299
- old_model_hess = self .model_hess .as_full ()
299
+ old_model_hess = self .model_hess .copy ()
300
300
else :
301
301
old_model_grad = None
302
302
old_model_hess = None
@@ -305,24 +305,21 @@ def interpolate_model(self, verbose=False, min_chg_hess=True, get_norm_model_chg
305
305
self .model_const = self .fopt () # true in all cases
306
306
if self .npt () == self .n () + 1 :
307
307
self .model_grad = coeffs .copy ()
308
- self .model_hess = Hessian ( self .n ()) # zeros
308
+ self .model_hess = np . zeros (( self .n (), self . n ()))
309
309
elif self .npt () == (self .n () + 1 ) * (self .n () + 2 ) // 2 :
310
310
self .model_grad = coeffs [:self .n ()]
311
- self .model_hess = Hessian (self .n (), coeffs [self .n ():]) # rest of coeffs are upper triangular part of Hess
311
+ self .model_hess = build_symmetric_matrix_from_vector (self .n (), coeffs [self .n ():]) # rest of coeffs are upper triangular part of Hess
312
312
else :
313
313
self .model_grad = coeffs [self .npt ()- 1 :] # last n values
314
- if min_chg_hess :
315
- hess_full = self .model_hess .as_full ()
316
- else :
317
- hess_full = np .zeros ((self .n (), self .n ()))
314
+ if not min_chg_hess :
315
+ self .model_hess = np .zeros ((self .n (), self .n ()))
318
316
for i in range (self .npt ()- 1 ):
319
317
dx = self .xpt (fval_row_idx [i ]) - self .xopt ()
320
- hess_full += coeffs [i ] * np .outer (dx , dx )
321
- self .model_hess = Hessian (self .n (), hess_full )
318
+ self .model_hess += coeffs [i ] * np .outer (dx , dx )
322
319
323
320
# Base model at xbase, not xopt (note negative signs)
324
321
xopt = self .xopt ()
325
- Hx = self .model_hess .vec_mul (xopt )
322
+ Hx = self .model_hess .dot (xopt )
326
323
self .model_const += np .dot (- self .model_grad + 0.5 * Hx , xopt )
327
324
self .model_grad += - Hx
328
325
@@ -331,7 +328,7 @@ def interpolate_model(self, verbose=False, min_chg_hess=True, get_norm_model_chg
331
328
norm_chg_hess = 0.0
332
329
if verbose or get_norm_model_chg :
333
330
norm_chg_grad = LA .norm (self .model_grad - old_model_grad )
334
- norm_chg_hess = LA .norm (self .model_hess . as_full () - old_model_hess , ord = 'fro' )
331
+ norm_chg_hess = LA .norm (self .model_hess - old_model_hess , ord = 'fro' )
335
332
if verbose :
336
333
for k in range (self .npt ()):
337
334
f_pred = self .model_value (self .xpt (k ), d_based_at_xopt = False , with_const_term = True )
@@ -342,7 +339,7 @@ def interpolate_model(self, verbose=False, min_chg_hess=True, get_norm_model_chg
342
339
343
340
def build_full_model (self ):
344
341
# Make model centred around xopt
345
- g = self .model_grad + self .model_hess .vec_mul (self .xopt ())
342
+ g = self .model_grad + self .model_hess .dot (self .xopt ())
346
343
return g , self .model_hess
347
344
348
345
def lagrange_polynomial (self , k , factorise_first = True ):
@@ -382,21 +379,20 @@ def lagrange_polynomial(self, k, factorise_first=True):
382
379
c = 1.0 if k == self .kopt else 0.0 # true in all cases
383
380
if self .npt () == self .n () + 1 :
384
381
g = coeffs .copy ()
385
- hess = Hessian ( self .n ()) # zeros
382
+ H = np . zeros (( self .n (), self . n ()))
386
383
elif self .npt () == (self .n () + 1 ) * (self .n () + 2 ) // 2 :
387
384
g = coeffs [:self .n ()]
388
- hess = Hessian (self .n (), coeffs [self .n ():]) # rest of coeffs are upper triangular part of Hess
385
+ H = build_symmetric_matrix_from_vector (self .n (), coeffs [self .n ():]) # rest of coeffs are upper triangular part of Hess
389
386
else :
390
387
g = coeffs [self .npt () - 1 :] # last n values
391
388
fval_row_idx = np .delete (np .arange (self .npt ()), self .kopt ) # indices of all rows except kopt
392
- hess_full = np .zeros ((self .n (), self .n ()))
389
+ H = np .zeros ((self .n (), self .n ()))
393
390
for i in range (self .npt () - 1 ):
394
391
dx = self .xpt (fval_row_idx [i ]) - self .xopt ()
395
- hess_full += coeffs [i ] * np .outer (dx , dx )
396
- hess = Hessian (self .n (), hess_full )
392
+ H += coeffs [i ] * np .outer (dx , dx )
397
393
398
394
# (c, g, hess) currently based around xopt
399
- return c , g , hess
395
+ return c , g , H
400
396
401
397
def poisedness_constant (self , delta , xbase = None , xbase_in_abs_coords = True ):
402
398
# Calculate the poisedness constant of the current interpolation set in B(xbase, delta)
@@ -407,14 +403,14 @@ def poisedness_constant(self, delta, xbase=None, xbase_in_abs_coords=True):
407
403
elif xbase_in_abs_coords :
408
404
xbase = xbase - self .xbase # shift to correct position
409
405
for k in range (self .npt ()):
410
- c , g , hess = self .lagrange_polynomial (k , factorise_first = True ) # based at self.xopt()
406
+ c , g , H = self .lagrange_polynomial (k , factorise_first = True ) # based at self.xopt()
411
407
# Switch base of poly from xopt to xbase, as required by trsbox_geometry
412
408
base_chg = self .xopt () - xbase
413
- Hx = hess . vec_mul (base_chg )
409
+ Hx = H . dot (base_chg )
414
410
c += np .dot (- g + 0.5 * Hx , base_chg )
415
411
g += - Hx
416
- xmax = trsbox_geometry (xbase , c , g , hess , self .sl , self .su , delta )
417
- lmax = abs (c + model_value (g , hess , xmax - xbase )) # evaluate Lagrange poly
412
+ xmax = trsbox_geometry (xbase , c , g , H , self .sl , self .su , delta )
413
+ lmax = abs (c + model_value (g , H , xmax - xbase )) # evaluate Lagrange poly
418
414
if overall_max is None or lmax > overall_max :
419
415
overall_max = lmax
420
416
return overall_max
@@ -456,3 +452,14 @@ def build_interpolation_matrix(Y, approx_delta=1.0):
456
452
right_scaling [p :] = approx_delta
457
453
return A , left_scaling , right_scaling
458
454
455
+
456
+ def build_symmetric_matrix_from_vector (n , entries ):
457
+ assert entries .shape == (n * (n + 1 )// 2 ,), "Entries vector has wrong size, got %g, expect %g (for n=%g)" % (len (entries ), n * (n + 1 )// 2 , n )
458
+ A = np .zeros ((n , n ))
459
+ ih = - 1
460
+ for j in range (n ): # j = 0, ..., n-1
461
+ for i in range (j + 1 ): # i = 0, ..., j
462
+ ih += 1
463
+ A [i , j ] = entries [ih ]
464
+ A [j , i ] = entries [ih ]
465
+ return A
0 commit comments