9595 cache. norm_gamma_fpr = matrix_operations:: norm2 ( & cache. gamma_fpr ) ;
9696 }
9797
98+ /// Score the current feasible half step and cache it if it is the best so far.
99+ pub ( crate ) fn cache_best_half_step ( & mut self , u_current : & [ f64 ] ) {
100+ self . compute_fpr ( u_current) ;
101+ self . cache . cache_best_half_step ( ) ;
102+ }
103+
98104 /// Computes a gradient step; does not compute the gradient
99105 fn gradient_step ( & mut self , u_current : & [ f64 ] ) {
100106 // take a gradient step:
@@ -123,12 +129,19 @@ where
123129 . for_each ( |( ( grad_step, u) , grad) | * grad_step = * u - gamma * * grad) ;
124130 }
125131
132+ /// Cache the squared norm of the current gradient.
133+ fn cache_gradient_norm ( & mut self ) {
134+ self . cache . gradient_u_norm_sq = matrix_operations:: norm2_squared ( & self . cache . gradient_u ) ;
135+ }
136+
126137 /// Computes a projection on `gradient_step`
127138 fn half_step ( & mut self ) {
128139 let cache = & mut self . cache ;
129140 // u_half_step ← projection(gradient_step)
130141 cache. u_half_step . copy_from_slice ( & cache. gradient_step ) ;
131142 self . problem . constraints . project ( & mut cache. u_half_step ) ;
143+ cache. gradient_step_u_half_step_diff_norm_sq =
144+ matrix_operations:: norm2_squared_diff ( & cache. gradient_step , & cache. u_half_step ) ;
132145 }
133146
134147 /// Computes an LBFGS direction; updates `cache.direction_lbfgs`
@@ -157,7 +170,7 @@ where
157170
158171 // rhs ← cost + LIP_EPS * |f| - <gradfx, gamma_fpr> + (L/2/gamma) ||gamma_fpr||^2
159172 cost_value + LIPSCHITZ_UPDATE_EPSILON * cost_value. abs ( ) - inner_prod_grad_fpr
160- + ( GAMMA_L_COEFF / ( 2.0 * gamma) ) * ( cache. norm_gamma_fpr . powi ( 2 ) )
173+ + ( GAMMA_L_COEFF / ( 2.0 * gamma) ) * cache. norm_gamma_fpr * cache . norm_gamma_fpr
161174 }
162175
163176 /// Updates the estimate of the Lipscthiz constant
@@ -166,9 +179,7 @@ where
166179
167180 // Compute the cost at the half step
168181 ( self . problem . cost ) ( & self . cache . u_half_step , & mut cost_u_half_step) ?;
169-
170- // Compute the cost at u_current (save it in `cache.cost_value`)
171- ( self . problem . cost ) ( u_current, & mut self . cache . cost_value ) ?;
182+ debug_assert ! ( matrix_operations:: is_finite( & [ self . cache. cost_value] ) ) ;
172183
173184 let mut it_lipschitz_search = 0 ;
174185
@@ -221,16 +232,12 @@ where
221232 let cache = & mut self . cache ;
222233
223234 // dist squared ← norm(gradient step - u half step)^2
224- let dist_squared =
225- matrix_operations:: norm2_squared_diff ( & cache. gradient_step , & cache. u_half_step ) ;
226-
227235 // rhs_ls ← f - (gamma/2) * norm(gradf)^2
228236 // + 0.5 * dist squared / gamma
229237 // - sigma * norm_gamma_fpr^2
230- let fbe = cache. cost_value
231- - 0.5 * cache. gamma * matrix_operations:: norm2_squared ( & cache. gradient_u )
232- + 0.5 * dist_squared / cache. gamma ;
233- let sigma_fpr_sq = cache. sigma * cache. norm_gamma_fpr . powi ( 2 ) ;
238+ let fbe = cache. cost_value - 0.5 * cache. gamma * cache. gradient_u_norm_sq
239+ + 0.5 * cache. gradient_step_u_half_step_diff_norm_sq / cache. gamma ;
240+ let sigma_fpr_sq = cache. sigma * cache. norm_gamma_fpr * cache. norm_gamma_fpr ;
234241 cache. rhs_ls = fbe - sigma_fpr_sq;
235242 }
236243
@@ -247,20 +254,14 @@ where
247254 // point `u_plus`
248255 ( self . problem . cost ) ( & self . cache . u_plus , & mut self . cache . cost_value ) ?;
249256 ( self . problem . gradf ) ( & self . cache . u_plus , & mut self . cache . gradient_u ) ?;
257+ self . cache_gradient_norm ( ) ;
250258
251259 self . gradient_step_uplus ( ) ; // gradient_step ← u_plus - gamma * gradient_u
252260 self . half_step ( ) ; // u_half_step ← project(gradient_step)
253261
254- // Compute: dist_squared ← norm(gradient_step - u_half_step)^2
255- let dist_squared = matrix_operations:: norm2_squared_diff (
256- & self . cache . gradient_step ,
257- & self . cache . u_half_step ,
258- ) ;
259-
260262 // Update the LHS of the line search condition
261- self . cache . lhs_ls = self . cache . cost_value
262- - 0.5 * gamma * matrix_operations:: norm2_squared ( & self . cache . gradient_u )
263- + 0.5 * dist_squared / self . cache . gamma ;
263+ self . cache . lhs_ls = self . cache . cost_value - 0.5 * gamma * self . cache . gradient_u_norm_sq
264+ + 0.5 * self . cache . gradient_step_u_half_step_diff_norm_sq / self . cache . gamma ;
264265
265266 Ok ( self . cache . lhs_ls > self . cache . rhs_ls )
266267 }
@@ -270,6 +271,7 @@ where
270271 u_current. copy_from_slice ( & self . cache . u_half_step ) ; // set u_current ← u_half_step
271272 ( self . problem . cost ) ( u_current, & mut self . cache . cost_value ) ?; // cost value
272273 ( self . problem . gradf ) ( u_current, & mut self . cache . gradient_u ) ?; // compute gradient
274+ self . cache_gradient_norm ( ) ;
273275 self . gradient_step ( u_current) ; // updated self.cache.gradient_step
274276 self . half_step ( ) ; // updates self.cache.u_half_step
275277
@@ -295,6 +297,13 @@ where
295297
296298 Ok ( ( ) )
297299 }
300+
301+ /// Compute the cost value at the best cached feasible half step.
302+ pub ( crate ) fn cost_value_at_best_half_step ( & mut self ) -> Result < f64 , SolverError > {
303+ let mut cost = 0.0 ;
304+ ( self . problem . cost ) ( & self . cache . best_u_half_step , & mut cost) ?;
305+ Ok ( cost)
306+ }
298307}
299308
300309/// Implementation of the `step` and `init` methods of [trait.AlgorithmEngine.html]
@@ -320,7 +329,7 @@ where
320329 self . cache . cache_previous_gradient ( ) ;
321330
322331 // compute the fixed point residual
323- self . compute_fpr ( u_current) ;
332+ self . cache_best_half_step ( u_current) ;
324333
325334 // exit if the exit conditions are satisfied (||gamma*fpr|| < eps and,
326335 // if activated, ||gamma*r + df - df_prev|| < eps_akkt)
@@ -353,6 +362,7 @@ where
353362 self . cache . reset ( ) ;
354363 ( self . problem . cost ) ( u_current, & mut self . cache . cost_value ) ?; // cost value
355364 self . estimate_loc_lip ( u_current) ?; // computes the gradient as well! (self.cache.gradient_u)
365+ self . cache_gradient_norm ( ) ;
356366 self . cache . gamma = GAMMA_L_COEFF / f64:: max ( self . cache . lipschitz_constant , MIN_L_ESTIMATE ) ;
357367 self . cache . sigma = ( 1.0 - GAMMA_L_COEFF ) / ( 4.0 * self . cache . gamma ) ;
358368 self . gradient_step ( u_current) ; // updated self.cache.gradient_step
@@ -367,12 +377,14 @@ where
367377/* --------------------------------------------------------------------------------------------- */
368378#[ cfg( test) ]
369379mod tests {
380+ use std:: cell:: Cell ;
370381
371382 use crate :: constraints;
372383 use crate :: core:: panoc:: panoc_engine:: PANOCEngine ;
373384 use crate :: core:: panoc:: * ;
374385 use crate :: core:: Problem ;
375386 use crate :: mocks;
387+ use crate :: FunctionCallResult ;
376388
377389 #[ test]
378390 fn t_compute_fpr ( ) {
@@ -537,6 +549,13 @@ mod tests {
537549 panoc_engine. cache . cost_value = 24.0 ;
538550 panoc_engine. cache . gamma = 2.34 ;
539551 panoc_engine. cache . gradient_u . copy_from_slice ( & [ 2.4 , -9.7 ] ) ;
552+ panoc_engine. cache . gradient_u_norm_sq =
553+ crate :: matrix_operations:: norm2_squared ( & panoc_engine. cache . gradient_u ) ;
554+ panoc_engine. cache . gradient_step_u_half_step_diff_norm_sq =
555+ crate :: matrix_operations:: norm2_squared_diff (
556+ & panoc_engine. cache . gradient_step ,
557+ & panoc_engine. cache . u_half_step ,
558+ ) ;
540559 panoc_engine. cache . sigma = 0.066 ;
541560 panoc_engine. cache . norm_gamma_fpr = 2.5974 ;
542561
@@ -552,4 +571,40 @@ mod tests {
552571 "rhs_ls is wrong" ,
553572 ) ;
554573 }
574+
575+ #[ test]
576+ fn t_update_lipschitz_constant_reuses_cached_cost_at_current_iterate ( ) {
577+ let n = 2 ;
578+ let mem = 5 ;
579+ let bounds = constraints:: NoConstraints :: new ( ) ;
580+ let cost_calls = Cell :: new ( 0usize ) ;
581+ let cost = |u : & [ f64 ] , c : & mut f64 | -> FunctionCallResult {
582+ cost_calls. set ( cost_calls. get ( ) + 1 ) ;
583+ * c = 0.5 * crate :: matrix_operations:: norm2_squared ( u) ;
584+ Ok ( ( ) )
585+ } ;
586+ let grad = |u : & [ f64 ] , g : & mut [ f64 ] | -> FunctionCallResult {
587+ g. copy_from_slice ( u) ;
588+ Ok ( ( ) )
589+ } ;
590+ let problem = Problem :: new ( & bounds, grad, cost) ;
591+ let mut panoc_cache = PANOCCache :: new ( n, 1e-6 , mem) ;
592+ let mut panoc_engine = PANOCEngine :: new ( problem, & mut panoc_cache) ;
593+
594+ let u_current = [ 1.0 , -2.0 ] ;
595+ panoc_engine. cache . cost_value = 2.5 ;
596+ panoc_engine. cache . u_half_step . copy_from_slice ( & [ 0.1 , -0.1 ] ) ;
597+ panoc_engine. cache . gradient_u . copy_from_slice ( & u_current) ;
598+ panoc_engine. cache . gamma = 0.5 ;
599+ panoc_engine. cache . lipschitz_constant = 1.9 ;
600+ panoc_engine. compute_fpr ( & u_current) ;
601+
602+ panoc_engine. update_lipschitz_constant ( & u_current) . unwrap ( ) ;
603+
604+ assert_eq ! (
605+ 1 ,
606+ cost_calls. get( ) ,
607+ "update_lipschitz_constant should only evaluate the half-step cost"
608+ ) ;
609+ }
555610}
0 commit comments