Autoware.Auto
more_thuente_line_search.hpp
Go to the documentation of this file.
1 // Copyright 2020 the Autoware Foundation
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 //    http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14 //
15 // Co-developed by Tier IV, Inc. and Apex.AI, Inc.
16 
17 
18 // This file contains modified code from the following open source projects
19 // published under the licenses listed below:
20 //
21 // Software License Agreement (BSD License)
22 //
23 // Point Cloud Library (PCL) - www.pointclouds.org
24 // Copyright (c) 2010-2011, Willow Garage, Inc.
25 // Copyright (c) 2012-, Open Perception, Inc.
26 //
27 // All rights reserved.
28 //
29 // Redistribution and use in source and binary forms, with or without
30 // modification, are permitted provided that the following conditions
31 // are met:
32 //
33 // * Redistributions of source code must retain the above copyright
34 // notice, this list of conditions and the following disclaimer.
35 // * Redistributions in binary form must reproduce the above
36 // copyright notice, this list of conditions and the following
37 // disclaimer in the documentation and/or other materials provided
38 // with the distribution.
39 // * Neither the name of the copyright holder(s) nor the names of its
40 // contributors may be used to endorse or promote products derived
41 // from this software without specific prior written permission.
42 //
43 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
44 // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
45 // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
46 // FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
47 // COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
48 // INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
49 // BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
50 // LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
51 // CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
52 // LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
53 // ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
54 // POSSIBILITY OF SUCH DAMAGE.
55 
56 #ifndef OPTIMIZATION__LINE_SEARCH__MORE_THUENTE_LINE_SEARCH_HPP_
57 #define OPTIMIZATION__LINE_SEARCH__MORE_THUENTE_LINE_SEARCH_HPP_
58 
60 #include <optimization/utils.hpp>
62 
63 #include <limits>
64 #include <algorithm>
65 #include <utility>
66 
67 namespace autoware
68 {
69 namespace common
70 {
71 namespace comp = helper_functions::comparisons;
72 namespace optimization
73 {
74 
75 namespace detail
76 {
78 constexpr common::types::float32_t kDelta = 0.66F;
79 } // namespace detail
80 
94 class OPTIMIZATION_PUBLIC MoreThuenteLineSearch : public LineSearch<MoreThuenteLineSearch>
95 {
96 public:
101  {
102  kMinimization,
103  kMaximization
104  };
105 
119  const StepT max_step,
120  const StepT min_step,
121  const OptimizationDirection optimization_direction = OptimizationDirection::kMinimization,
122  const StepT mu = 1.e-4F,
123  const StepT eta = 0.1F, // Default value suggested in Section 5 of the paper.
124  const std::int32_t max_iterations = 10)
125  : LineSearch{max_step},
126  m_step_min{min_step},
127  m_optimization_direction{optimization_direction},
128  m_mu{mu},
129  m_eta{eta},
130  m_max_iterations{max_iterations}
131  {
132  if (min_step < 0.0F) {throw std::domain_error("Min step cannot be negative.");}
133  if (max_step < min_step) {throw std::domain_error("Max step cannot be smaller than min step.");}
134  if (mu < 0.0F || mu > 1.0F) {throw std::domain_error("mu must be in (0, 1).");}
135  if (eta < 0.0F || eta > 1.0F) {throw std::domain_error("eta must be in (0, 1).");}
136  if (max_iterations < 1) {throw std::domain_error("Less than 1 iteration is not allowed.");}
137  m_compute_mode.set_score().set_jacobian();
138  }
139 
157  template<typename DomainValueT, typename OptimizationProblemT>
158  DomainValueT compute_next_step_(
159  const DomainValueT & x0,
160  const DomainValueT & initial_step,
161  OptimizationProblemT & optimization_problem);
162 
163 private:
165  struct Interval
166  {
167  StepT a_l;
168  StepT a_u;
169  };
170 
174  template<typename OptimizationProblemT>
175  class ObjectiveFunction;
176 
181  template<typename ObjectiveFunctionT>
182  class AuxiliaryFunction;
183 
184  // Find the next step as described in section 4 of the paper.
185  template<typename FunctionValueT>
186  StepT find_next_step_length(
187  const FunctionValueT & f_t, const FunctionValueT & f_l, const FunctionValueT & f_u);
188 
189  // Find the next [a_l, a_u] interval as described in the "Updating Algorithm" with function psi
190  // and "Modifier Updating Algorithm" with function phi.
191  template<typename FunctionValueT>
192  Interval update_interval(
193  const FunctionValueT & f_t, const FunctionValueT & f_l, const FunctionValueT & f_u);
194 
195  StepT m_step_min{};
196  OptimizationDirection m_optimization_direction;
197  ComputeMode m_compute_mode{};
198  StepT m_mu{};
199  StepT m_eta{};
200  std::int32_t m_max_iterations{};
201 };
202 
203 template<typename DomainValueT, typename OptimizationProblemT>
205  const DomainValueT & x0, const DomainValueT & initial_step,
206  OptimizationProblemT & optimization_problem)
207 {
208  auto a_t = std::min(static_cast<StepT>(initial_step.norm()), get_step_max());
209  if (a_t < m_step_min) {
210  // We don't want to perform the line search as the initial step is out of allowed bounds. We
211  // assume that the optimizer knows what it is doing and return the initial_step unmodified.
212  return initial_step;
213  }
214  // Function phi as defined in eq. 1.3
215  using FunctionPhi = ObjectiveFunction<OptimizationProblemT>;
216  // Function phi as defined right before eq. 2.1
217  using FunctionPsi = AuxiliaryFunction<FunctionPhi>;
218  FunctionPhi phi{x0, initial_step, optimization_problem, m_optimization_direction};
219  FunctionPsi psi{phi, m_mu};
220 
221 
222  Interval interval{m_step_min, get_step_max()};
223  const auto phi_0 = phi(0.0F);
224  auto phi_t = phi(a_t);
225  auto psi_t = psi(a_t);
226  auto f_l = psi(interval.a_l);
227  auto f_u = psi(interval.a_u);
228 
229  using ValueT = typename OptimizationProblemT::Value;
230 
231  bool use_auxiliary_function = true;
232  // Follows the "Search Algorithm" as presented in the paper.
233  for (auto step_iterations = 0; step_iterations < m_max_iterations; ++step_iterations) {
234  constexpr decltype(psi_t.value) ZERO{};
235  if ((psi_t.value <= ZERO) &&
236  (std::abs(phi_t.derivative) <= static_cast<ValueT>(m_eta) * std::abs(phi_0.derivative)))
237  {
238  // We reached the termination condition as the step satisfies the strong Wolfe conditions (the
239  // ones in the if condition). This means we have converged and are ready to return the found
240  // step.
241  break;
242  }
243 
244  // Pick next step size by interpolating either phi or psi depending on which update algorithm is
245  // currently being used.
246  if (use_auxiliary_function) {
247  a_t = find_next_step_length(psi_t, f_l, f_u);
248  } else {
249  a_t = find_next_step_length(phi_t, f_l, f_u);
250  }
251  if (a_t < m_step_min || std::isnan(a_t)) {
252  // This can happen if we are closer than the minimum step to the optimum. We don't want to do
253  // anything in this case.
254  a_t = 0.0F;
255  break;
256  }
257  phi_t = phi(a_t);
258  psi_t = psi(a_t);
259 
260  // Decide if we want to switch to using a "Modified Updating Algorithm" (shown after theorem 3.2
261  // in the paper) by switching from using function psi to using function phi. The decision
262  // follows the logic in the paragraph right before theorem 3.3 in the paper.
263  if (use_auxiliary_function && (psi_t.value <= 0.0 && psi_t.derivative > 0.0)) {
264  use_auxiliary_function = false;
265  // We now want to switch to using phi so compute the required values.
266  f_l = phi(interval.a_l);
267  f_u = phi(interval.a_u);
268  }
269 
270  if (use_auxiliary_function) {
271  // Update the interval that will be used to generate the next step using the
272  // "Updating Algorithm" (right after theorem 2.1 in the paper).
273  interval = update_interval(psi_t, f_l, f_u);
274  f_l = psi(interval.a_l);
275  f_u = psi(interval.a_u);
276  } else {
277  // Update the interval that will be used to generate the next step using the
278  // "Modified Updating Algorithm" (right after theorem 3.2 in the paper).
279  interval = update_interval(phi_t, f_l, f_u);
280  f_l = phi(interval.a_l);
281  f_u = phi(interval.a_u);
282  }
283  constexpr auto EPS = std::numeric_limits<StepT>::epsilon();
284  if (comp::approx_eq(interval.a_u, interval.a_l, m_step_min, EPS)) {
285  // The interval has converged to a point so we can stop here.
286  a_t = interval.a_u;
287  break;
288  }
289  }
290  return a_t * phi.get_step_direction();
291 }
292 
293 template<typename OptimizationProblemT>
294 class MoreThuenteLineSearch::ObjectiveFunction
295 {
296  using ValueT = typename OptimizationProblemT::Value;
297  using JacobianT = typename OptimizationProblemT::Jacobian;
298  using DomainValueT = typename OptimizationProblemT::DomainValue;
299 
300 public:
303  {
305  ValueT value;
306  ValueT derivative;
307  };
308 
309  ObjectiveFunction(
310  const DomainValueT & starting_state,
311  const DomainValueT & initial_step,
312  OptimizationProblemT & underlying_function,
313  const OptimizationDirection direction)
314  : m_starting_state{starting_state},
315  m_step_direction{initial_step.normalized()},
316  m_underlying_function{underlying_function}
317  {
318  m_compute_mode.set_score().set_jacobian();
319  m_underlying_function.evaluate(m_starting_state, m_compute_mode);
320  m_underlying_function.jacobian(m_starting_state, m_underlying_function_jacobian);
321  const auto derivative = m_underlying_function_jacobian.dot(m_step_direction);
322  switch (direction) {
324  if (derivative > ValueT{0.0}) {
325  m_step_direction *= -1.0;
326  }
327  break;
329  if (derivative < ValueT{0.0}) {
330  m_step_direction *= -1.0;
331  }
332  // The function phi must have a derivative < 0 following the introduction of the
333  // More-Thuente paper. In case we want to solve a maximization problem, the derivative will
334  // be positive and we need to make a dual problem from it by flipping the values of phi.
335  m_multiplier = ValueT{-1.0};
336  break;
337  }
338  }
339 
341  FunctionValue operator()(const StepT & step_size)
342  {
343  if (step_size < StepT{0.0}) {throw std::runtime_error("Step cannot be negative");}
344  const auto current_state = m_starting_state + step_size * m_step_direction;
345  m_underlying_function.evaluate(current_state, m_compute_mode);
346  m_underlying_function.jacobian(current_state, m_underlying_function_jacobian);
347  return {
348  step_size,
349  m_multiplier * m_underlying_function(current_state),
350  m_multiplier * m_underlying_function_jacobian.dot(m_step_direction)};
351  }
352 
354  const DomainValueT & get_step_direction() const noexcept {return m_step_direction;}
355 
356 private:
357  DomainValueT m_starting_state;
358  DomainValueT m_step_direction;
359  OptimizationProblemT & m_underlying_function;
360  ComputeMode m_compute_mode{};
361  JacobianT m_underlying_function_jacobian;
362  ValueT m_multiplier{1.0};
363 };
364 
365 
366 template<typename ObjectiveFunctionT>
367 class MoreThuenteLineSearch::AuxiliaryFunction
368 {
369  using FunctionValue = typename ObjectiveFunctionT::FunctionValue;
370  using ValueT = decltype(FunctionValue::value);
371 
372 public:
374  AuxiliaryFunction(ObjectiveFunctionT & objective_function, const StepT & mu)
375  : m_objective_function{objective_function},
376  m_mu{mu},
377  m_initial_objective_function_value{objective_function(0.0F)} {}
378 
380  FunctionValue operator()(const StepT & step_size)
381  {
382  const auto & objective_function_value = m_objective_function(step_size);
383  const auto value =
384  objective_function_value.value -
385  m_initial_objective_function_value.value -
386  static_cast<ValueT>(m_mu) * static_cast<ValueT>(step_size) *
387  objective_function_value.derivative;
388  const auto derivative =
389  objective_function_value.derivative - static_cast<ValueT>(m_mu) *
390  m_initial_objective_function_value.derivative;
391  return {step_size, value, derivative};
392  }
393 
394 private:
395  ObjectiveFunctionT & m_objective_function;
396  StepT m_mu{};
397  FunctionValue m_initial_objective_function_value{};
398  FunctionValue m_value{};
399 };
400 
401 template<typename FunctionValueT>
402 MoreThuenteLineSearch::StepT MoreThuenteLineSearch::find_next_step_length(
403  const FunctionValueT & f_t, const FunctionValueT & f_l, const FunctionValueT & f_u)
404 {
405  using ValueT = decltype(FunctionValueT::value);
406 
407  if (std::isnan(f_t.argument) || std::isnan(f_l.argument) || std::isnan(f_u.argument)) {
408  throw std::runtime_error("Got nan values in the step computation function.");
409  }
410  constexpr auto kValueEps = 0.00001;
411  constexpr auto kStepEps = 0.00001F;
412  // A lambda to calculate the minimizer of the cubic that interpolates f_a, f_a_derivative, f_b and
413  // f_b_derivative on [a, b]. Equation 2.4.52 [Sun, Yuan 2006]
414  const auto find_cubic_minimizer = [kStepEps](const auto & f_a, const auto & f_b) -> StepT {
415  if (comp::approx_eq(f_a.argument, f_b.argument, kStepEps, kStepEps)) {
416  return f_a.argument;
417  }
418  const auto z = static_cast<ValueT>(3.0) * (f_a.value - f_b.value) /
419  (static_cast<ValueT>(f_b.argument) - static_cast<ValueT>(f_a.argument)) + f_a.derivative +
420  f_b.derivative;
421  const auto w = std::sqrt(z * z - f_a.derivative * f_b.derivative);
422  // Equation 2.4.56 [Sun, Yuan 2006]
423  return static_cast<StepT>(
424  static_cast<ValueT>(f_b.argument) -
425  (static_cast<ValueT>(f_b.argument) - static_cast<ValueT>(f_a.argument)) *
426  (f_b.derivative + w - z) /
427  (f_b.derivative - f_a.derivative + static_cast<ValueT>(2.0) * w));
428  };
429 
430  // A lambda to calculate the minimizer of the quadratic that interpolates f_a, f_b and f'_a
431  const auto find_a_q = [kStepEps](
432  const FunctionValueT & f_a, const FunctionValueT & f_b) -> StepT {
433  if (comp::approx_eq(f_a.argument, f_b.argument, kStepEps, kStepEps)) {
434  return f_a.argument;
435  }
436  return
437  static_cast<StepT>(
438  static_cast<ValueT>(f_a.argument) + static_cast<ValueT>(0.5) *
439  (static_cast<ValueT>(f_b.argument) - static_cast<ValueT>(f_a.argument)) *
440  (static_cast<ValueT>(f_b.argument) - static_cast<ValueT>(f_a.argument)) *
441  f_a.derivative /
442  (f_a.value - f_b.value +
443  (static_cast<ValueT>(f_b.argument) - static_cast<ValueT>(f_a.argument)) *
444  f_a.derivative));
445  };
446 
447  // A lambda to calculate the minimizer of the quadratic that interpolates f'_a, and f'_b
448  const auto find_a_s = [kStepEps](
449  const FunctionValueT & f_a, const FunctionValueT & f_b) -> StepT {
450  if (comp::approx_eq(f_a.argument, f_b.argument, kStepEps, kStepEps)) {
451  return f_a.argument;
452  }
453  return static_cast<StepT>(
454  static_cast<ValueT>(f_a.argument) +
455  (static_cast<ValueT>(f_b.argument) - static_cast<ValueT>(f_a.argument)) * f_a.derivative /
456  (f_a.derivative - f_b.derivative));
457  };
458 
459  // We cover here all the cases presented in the More-Thuente paper in section 4.
460  if (f_t.value > f_l.value) { // Case 1 from section 4.
461  const auto a_c = find_cubic_minimizer(f_l, f_t);
462  const auto a_q = find_a_q(f_l, f_t);
463  if (std::fabs(a_c - f_l.argument) < std::fabs(a_q - f_l.argument)) {
464  return a_c;
465  } else {
466  return 0.5F * (a_q + a_c);
467  }
468  } else if (f_t.derivative * f_l.derivative < 0) { // Case 2 from section 4.
469  const auto a_c = find_cubic_minimizer(f_l, f_t);
470  const auto a_s = find_a_s(f_l, f_t);
471  if (std::fabs(a_c - f_t.argument) >= std::fabs(a_s - f_t.argument)) {
472  return a_c;
473  } else {
474  return a_s;
475  }
476  } else if (comp::abs_lte(std::abs(f_t.derivative), std::abs(f_l.derivative), kValueEps)) {
477  // Case 3 from section 4.
478  const auto a_c = find_cubic_minimizer(f_l, f_t);
479  const auto a_s = find_a_s(f_l, f_t);
480  if (std::fabs(a_c - f_t.argument) < std::fabs(a_s - f_t.argument)) {
481  return std::min(
482  f_t.argument + detail::kDelta * (f_u.argument - f_t.argument),
483  static_cast<StepT>(a_c));
484  } else {
485  return std::max(
486  f_t.argument + detail::kDelta * (f_u.argument - f_t.argument),
487  static_cast<StepT>(a_s));
488  }
489  } else { // Case 4 from section 4.
490  return find_cubic_minimizer(f_t, f_u);
491  }
492 }
493 
494 template<typename FunctionValueT>
495 MoreThuenteLineSearch::Interval MoreThuenteLineSearch::update_interval(
496  const FunctionValueT & f_t, const FunctionValueT & f_l, const FunctionValueT & f_u)
497 {
498  using ValueT = decltype(FunctionValueT::value);
499 
500  // Following either "Updating Algorithm" or "Modifier Updating Algorithm" depending on the
501  // provided function f (can be psi or phi).
502  if (f_t.value > f_l.value) {
503  return {f_l.argument, f_t.argument}; // case a
504  } else if (f_t.derivative * static_cast<ValueT>(f_t.argument - f_l.argument) < 0) {
505  return {f_t.argument, f_u.argument}; // case b
506  } else if (f_t.derivative * static_cast<ValueT>(f_t.argument - f_l.argument) > 0) {
507  return {f_t.argument, f_l.argument}; // case c
508  }
509  // Converged to a point.
510  return {f_t.argument, f_t.argument};
511 }
512 
513 } // namespace optimization
514 } // namespace common
515 } // namespace autoware
516 
517 
518 #endif // OPTIMIZATION__LINE_SEARCH__MORE_THUENTE_LINE_SEARCH_HPP_
utils.hpp
autoware::common::helper_functions::comparisons::approx_eq
bool approx_eq(const T &a, const T &b, const T &abs_eps, const T &rel_eps)
Check for approximate equality in absolute and relative terms.
Definition: float_comparisons.hpp:139
autoware::common::helper_functions::comparisons::abs_lte
bool abs_lte(const T &a, const T &b, const T &eps)
Check for approximate less than or equal in absolute terms.
Definition: float_comparisons.hpp:69
autoware::common::optimization::MoreThuenteLineSearch::MoreThuenteLineSearch
MoreThuenteLineSearch(const StepT max_step, const StepT min_step, const OptimizationDirection optimization_direction=OptimizationDirection::kMinimization, const StepT mu=1.e-4F, const StepT eta=0.1F, const std::int32_t max_iterations=10)
Constructs a new instance.
Definition: more_thuente_line_search.hpp:118
autoware::common::optimization::detail::kDelta
constexpr common::types::float32_t kDelta
This value is used in More-Thuente paper without explanation (in the paper: Section 4,...
Definition: more_thuente_line_search.hpp:78
autoware::common::optimization::MoreThuenteLineSearch
This class describes a More-Thuente line search as presented in the paper "Line Search Algorithms wit...
Definition: more_thuente_line_search.hpp:94
autoware::common::optimization::LineSearch
Base class (CRTP) to mange the step length during optimization.
Definition: line_search.hpp:34
autoware
This file defines the lanelet2_map_provider_node class.
Definition: quick_sort.hpp:24
float_comparisons.hpp
autoware::common::optimization::LineSearch< MoreThuenteLineSearch >::get_step_max
StepT get_step_max() const noexcept
Definition: line_search.hpp:65
line_search.hpp
autoware::common::optimization::MoreThuenteLineSearch::ObjectiveFunction::FunctionValue::argument
StepT argument
Definition: more_thuente_line_search.hpp:304
autoware::common::helper_functions::comparisons
Definition: bool_comparisons.hpp:32
autoware::common::types::float32_t
float float32_t
Definition: types.hpp:45
autoware::common::optimization::MoreThuenteLineSearch::ObjectiveFunction::FunctionValue
A utility struct that holds the argument, value and derivative of a function.
Definition: more_thuente_line_search.hpp:302
autoware::common::optimization::MoreThuenteLineSearch::ObjectiveFunction::FunctionValue::value
ValueT value
Definition: more_thuente_line_search.hpp:305
autoware::common::optimization::LineSearch< MoreThuenteLineSearch >::StepT
float_t StepT
Definition: line_search.hpp:38
autoware::common::optimization::MoreThuenteLineSearch::compute_next_step_
DomainValueT compute_next_step_(const DomainValueT &x0, const DomainValueT &initial_step, OptimizationProblemT &optimization_problem)
Calculates the next step.
Definition: more_thuente_line_search.hpp:204
autoware::common::optimization::MoreThuenteLineSearch::OptimizationDirection
OptimizationDirection
An enum that defines the direction of optimization.
Definition: more_thuente_line_search.hpp:100
autoware::common::optimization::MoreThuenteLineSearch::OptimizationDirection::kMaximization
@ kMaximization
autoware::common::optimization::MoreThuenteLineSearch::OptimizationDirection::kMinimization
@ kMinimization
autoware::common::optimization::MoreThuenteLineSearch::ObjectiveFunction::FunctionValue::derivative
ValueT derivative
Definition: more_thuente_line_search.hpp:306