npstat is hosted by Hepforge, IPPP Durham
NPStat  5.10.0
LOrPE1DVariableDegreeCVPicker.hh
Go to the documentation of this file.
1 #ifndef NPSTAT_LORPE1DVARIABLEDEGREECVPICKER_HH_
2 #define NPSTAT_LORPE1DVARIABLEDEGREECVPICKER_HH_
3 
4 /*!
5 // \file LOrPE1DVariableDegreeCVPicker.hh
6 //
7 // \brief Simultaneous optimization of LOrPE bandwidth and degree
8 // choice by cross-validation
9 //
10 // Author: I. Volobouev
11 //
12 // August 2022
13 */
14 
16 
18 
19 namespace npstat {
20  class LOrPE1DVariableDegreeCVPicker;
21 
22  namespace Private {
23  template<class Lorpe>
24  class LOrPE1DVariableDegreeCVPickerHelper : public Functor1<double,double>
25  {
26  public:
27  typedef std::map<double,LOrPE1DCVResult> StatusMap;
28 
30  Lorpe& lorpe, const LOrPE1DVariableDegreeCVPicker& picker)
31  : lorpe_(lorpe),
32  picker_(picker)
33  {}
34 
35  inline virtual ~LOrPE1DVariableDegreeCVPickerHelper() {}
36 
37  virtual double operator()(const double& degree) const;
38 
39  // This method returns an invalid result if the degree key is not memoized
40  inline LOrPE1DCVResult getMemoized(const double degree) const
41  {
42  const auto it = statusMap_.find(degree);
43  if (it == statusMap_.end())
44  return LOrPE1DCVResult();
45  else
46  return it->second;
47  }
48 
49  private:
50  Lorpe& lorpe_;
51  const LOrPE1DVariableDegreeCVPicker& picker_;
52  mutable StatusMap statusMap_;
53  };
54  }
55 
57  {
58  public:
59  /**
60  // LOrPE1DVariableDegreeCVPicker constructor arguments are:
61  //
62  // i_maxDeg -- Maximum filter degree to consider
63  // in the optimization procedure.
64  //
65  // minBwFactors -- Minimum bandwidth factors to
66  // consider for each filter degree.
67  // The number of points in the degree
68  // grid will be set to the size
69  // of this vector. For example,
70  // if i_maxDeg = 2 and the size
71  // of minBwFactors is 5, the following
72  // five degrees will be considered:
73  // 0.0, 0.5, 1.0, 1.5, 2.0. The
74  // first element of "minBwFactors"
75  // will be used for degree 0, the
76  // second for degree 0.5, etc. The
77  // size of "minBwFactors" vector
78  // must be at least 2.
79  //
80  // maxBwFactors -- Maximum bandwidth factors to
81  // consider for each degree. The size
82  // of this vector should be equal to
83  // the size of "minBwFactors".
84  //
85  // startingBwFactors -- Bandwidth factors to use to start
86  // searches (for each filter degree).
87  // The size of this vector should be
88  // equal to the size of "minBwFactors".
89  //
90  // nBwFactors -- The size of the grid of bandwidth
91  // factors. For each degree, this grid
92  // will be generated by
93  // "EquidistantInLogSpace" class with
94  // the minimum and maximum taken from
95  // the corresponding elements of
96  // "minBwFactors" and "maxBwFactors"
97  // vectors. This parameter must be
98  // at least 2.
99  //
100  // initialStepSizeInFactorCells -- Initial step size in the units of
101  // bandwidth cell width, for the
102  // optimization over bandwidth factors.
103  //
104  // initialDeg -- Initial guess for the best filter
105  // degree.
106  //
107  // initialStepSizeInDegreeCells -- Initial step size in the units of
108  // filter degree cell width, for the
109  // optimization over filter degrees.
110  // The width of the filter degree cell
111  // is just
112  // i_maxDeg/(minBwFactors.size() - 1).
113  */
114  inline LOrPE1DVariableDegreeCVPicker(const double i_maxDeg,
115  const std::vector<double>& minBwFactors,
116  const std::vector<double>& maxBwFactors,
117  const std::vector<double>& startingBwFactors,
118  const unsigned nBwFactors,
119  const unsigned initialStepSizeInFactorCells,
120  const double initialDeg,
121  const unsigned initialStepSizeInDegreeCells)
122  : degAxis_(UniformAxis(minBwFactors.size(), 0.0, i_maxDeg)),
123  minBwFactorForEachDeg_(minBwFactors),
124  maxBwFactorForEachDeg_(maxBwFactors),
125  startingBwFactorForEachDeg_(startingBwFactors),
126  nBwFactors_(nBwFactors),
127  initialStepSizeInFactorsGridCells_(initialStepSizeInFactorCells),
128  initialStepSizeInDegreeGridCells_(initialStepSizeInDegreeCells)
129  {
130  const unsigned nDegsInTheGrid = minBwFactorForEachDeg_.size();
131 
132  assert(i_maxDeg > 0.0);
133  assert(nDegsInTheGrid > 1U);
134  assert(maxBwFactorForEachDeg_.size() == nDegsInTheGrid);
135  assert(startingBwFactorForEachDeg_.size() == nDegsInTheGrid);
136 
137  for (unsigned i=0; i<nDegsInTheGrid; ++i)
138  {
139  assert(minBwFactorForEachDeg_[i] > 0.0);
140  assert(maxBwFactorForEachDeg_[i] > minBwFactorForEachDeg_[i]);
141  assert(startingBwFactorForEachDeg_[i] > 0.0);
142  }
143  assert(nBwFactors_ > 1U);
144 
145  if (initialDeg <= 0.0)
146  initialDegCell_ = 0U;
147  else if (initialDeg >= i_maxDeg)
148  initialDegCell_ = nDegsInTheGrid - 1U;
149  else
150  {
151  const std::pair<unsigned,double>& cell = degAxis_.getInterval(initialDeg);
152  initialDegCell_ = cell.first;
153  if (cell.second < 0.5)
154  ++initialDegCell_;
155  }
156 
157  if (!initialStepSizeInDegreeGridCells_)
158  initialStepSizeInDegreeGridCells_ = 1U;
159  if (!initialStepSizeInFactorsGridCells_)
160  initialStepSizeInFactorsGridCells_ = 1U;
161  }
162 
163  template<class Lorpe>
164  inline LOrPE1DCVResult crossValidate(Lorpe& lorpe) const
165  {
167  typedef LOrPE1DCVResult Result;
168 
169  const unsigned nscan = degAxis_.nCoords();
170  const Helper helper(lorpe, *this);
171  unsigned imin = UINT_MAX;
172  double fMinusOne, fmin, fPlusOne;
173 
174  const MinSearchStatus1D status = goldenSectionSearchOnAGrid(
175  helper, degAxis_, initialDegCell_, initialStepSizeInDegreeGridCells_,
176  &imin, &fMinusOne, &fmin, &fPlusOne);
177 
178  if (status == MIN_SEARCH_FAILED)
179  {
180  std::vector<double> buffer(2*nscan);
181  double* degrees = &buffer[0];
182  double* cvValues = degrees + nscan;
183 
184  for (unsigned i=0; i<nscan; ++i)
185  {
186  const double deg = degAxis_.coordinate(i);
187  degrees[i] = deg;
188  const LOrPE1DCVResult& searched = helper.getMemoized(deg);
189  if (searched.isValid())
190  cvValues[i] = searched.cvFunction();
191  else
192  cvValues[i] = -helper(deg);
193  }
194 
195  const ScanExtremum1D& scanMax = findScanMaximum1D(degrees, nscan,
196  cvValues, nscan);
197  LOrPE1DCVResult searched = helper.getMemoized(scanMax.location());
198  if (scanMax.isOnTheBoundary())
199  assert(searched.isValid());
200  else if (searched.isValid())
201  searched.setDegreeBoundaryFlag(false);
202  else
203  // Run actuall cross-validation at the guessed degree
204  searched = cvArbitraryDegree(lorpe, scanMax.location());
205  return searched;
206  }
207  else if (status == MIN_SEARCH_OK)
208  {
209  assert(imin);
210  assert(imin + 1U < nscan);
211 
212  const double degGuess = degAxis_.coordinate(imin);
213  Result resGuess = helper.getMemoized(degGuess);
214  assert(resGuess.isValid());
215  resGuess.setDegreeBoundaryFlag(false);
216 
217  // Parabolic approximation in the degree variable
218  double bestDegree, extremumValue;
219  const bool isMinimum = parabolicExtremum(
220  degAxis_.coordinate(imin - 1U), fMinusOne,
221  degGuess, fmin,
222  degAxis_.coordinate(imin + 1U), fPlusOne,
223  &bestDegree, &extremumValue);
224  assert(isMinimum);
225 
226  // Run actuall cross-validation at the guessed degree
227  const Result bestGuess = cvArbitraryDegree(lorpe, bestDegree);
228  return bestGuess.cvFunction() > resGuess.cvFunction() ?
229  bestGuess : resGuess;
230  }
231  else
232  {
233  // Optimum is on the boundary of the degree grid
234  assert(imin < nscan);
235  const double deg = degAxis_.coordinate(imin);
236  const LOrPE1DCVResult& searched = helper.getMemoized(deg);
237  assert(searched.isValid());
238  return searched;
239  }
240  }
241 
242  private:
243  template<class Lorpe>
244  friend class Private::LOrPE1DVariableDegreeCVPickerHelper;
245 
246  template<class Lorpe>
247  inline LOrPE1DCVResult cvArbitraryDegree(Lorpe& lorpe, const double bestDegree) const
248  {
249  const unsigned nscan = degAxis_.nCoords();
250 
251  double minBwFactor, maxBwFactor, firstBwFactorToTry;
252  const unsigned ibest = degAxis_.getInterval(bestDegree).first;
253  const unsigned inext = ibest + 1U;
254  if (inext < nscan)
255  {
256  const double xbest = degAxis_.coordinate(ibest);
257  const double xnext = degAxis_.coordinate(inext);
258 
259  // Perform logarithmic interpolation of the min/max/firstToTry
260  // bandwidth factors to the given degree which no longer
261  // has to be on the degree grid
262  const LinearMapper1d m0(xbest, std::log(minBwFactorForEachDeg_[ibest]),
263  xnext, std::log(minBwFactorForEachDeg_[inext]));
264  minBwFactor = std::exp(m0(bestDegree));
265 
266  const LinearMapper1d m1(xbest, std::log(maxBwFactorForEachDeg_[ibest]),
267  xnext, std::log(maxBwFactorForEachDeg_[inext]));
268  maxBwFactor = std::exp(m1(bestDegree));
269 
270  const LinearMapper1d m2(xbest, std::log(startingBwFactorForEachDeg_[ibest]),
271  xnext, std::log(startingBwFactorForEachDeg_[inext]));
272  firstBwFactorToTry = std::exp(m2(bestDegree));
273  }
274  else
275  {
276  minBwFactor = minBwFactorForEachDeg_[ibest];
277  maxBwFactor = maxBwFactorForEachDeg_[ibest];
278  firstBwFactorToTry = startingBwFactorForEachDeg_[ibest];
279  }
280 
281  const LOrPE1DFixedDegreeCVPicker r2(bestDegree,
282  minBwFactor, maxBwFactor, nBwFactors_,
283  firstBwFactorToTry, initialStepSizeInFactorsGridCells_);
284  LOrPE1DCVResult cv = r2.crossValidate(lorpe);
285  assert(cv.isValid());
286  cv.setDegreeBoundaryFlag(false);
287  return cv;
288  }
289 
290  DualAxis degAxis_;
291  std::vector<double> minBwFactorForEachDeg_;
292  std::vector<double> maxBwFactorForEachDeg_;
293  std::vector<double> startingBwFactorForEachDeg_;
294  unsigned nBwFactors_;
295  unsigned initialStepSizeInFactorsGridCells_;
296  unsigned initialDegCell_;
297  unsigned initialStepSizeInDegreeGridCells_;
298  };
299 
300  namespace Private {
301  template<class Lorpe>
302  inline double
303  LOrPE1DVariableDegreeCVPickerHelper<Lorpe>::operator()(const double& degree) const
304  {
305  // Check that the degree argument is actually on
306  // the degree grid. Figure out the bandwidth range
307  // and the first bandwidth to try for this degree.
308  const double eps = 1.e-5;
309  const std::pair<unsigned,double>& cell = picker_.degAxis_.getInterval(degree);
310  assert(cell.second < eps || cell.second > 1.0 - eps);
311  unsigned ideg = cell.first;
312  if (cell.second < eps)
313  ++ideg;
314  assert(ideg < picker_.degAxis_.nCoords());
315 
316  const double minBwFactor = picker_.minBwFactorForEachDeg_[ideg];
317  const double maxBwFactor = picker_.maxBwFactorForEachDeg_[ideg];
318  const double firstBwFactorToTry = picker_.startingBwFactorForEachDeg_[ideg];
319 
320  // Now, find the optimal bandwidth for this degree
321  const LOrPE1DFixedDegreeCVPicker r(
322  degree, minBwFactor, maxBwFactor, picker_.nBwFactors_,
323  firstBwFactorToTry, picker_.initialStepSizeInFactorsGridCells_);
324  const LOrPE1DCVResult& status = r.crossValidate(lorpe_);
325  assert(status.isValid());
326 
327  // Remember the optimal result for this degree
328  const auto insertResult = statusMap_.insert(std::make_pair(degree,status));
329  assert(insertResult.second);
330  return -status.cvFunction();
331  }
332  }
333 }
334 
335 #endif // NPSTAT_LORPE1DVARIABLEDEGREECVPICKER_HH_
Optimization of LOrPE bandwidth choice by cross-validation with a hybrid algorithm combining golden s...
Linear transformation functor.
Definition: LOrPE1DCVResult.hh:19
Definition: LOrPE1DVariableDegreeCVPicker.hh:57
LOrPE1DVariableDegreeCVPicker(const double i_maxDeg, const std::vector< double > &minBwFactors, const std::vector< double > &maxBwFactors, const std::vector< double > &startingBwFactors, const unsigned nBwFactors, const unsigned initialStepSizeInFactorCells, const double initialDeg, const unsigned initialStepSizeInDegreeCells)
Definition: LOrPE1DVariableDegreeCVPicker.hh:114
Definition: LOrPE1DVariableDegreeCVPicker.hh:25
Definition: UniformAxis.hh:28
Definition: AbsArrayProjector.hh:14
MinSearchStatus1D goldenSectionSearchOnAGrid(const Functor1< double, double > &f, const DualAxis &axis, unsigned i0, unsigned initialStep, unsigned *imin, double *fMinusOne, double *fmin, double *fPlusOne)
LinearMapper1dTmpl< double > LinearMapper1d
Definition: LinearMapper1d.hh:75
bool parabolicExtremum(double x0, double y0, double x1, double y1, double x2, double y2, double *extremumCoordinate, double *extremumValue)
Definition: SimpleFunctors.hh:58