lazylookup.hh
1 #ifndef _lazylookup_hh
2 #define _lazylookup_hh
3 
4 /* lazylookup.hh
5  *
6  * Some lazy lookup helpers (maps, vectors, arrays, linear interpolating grid)
7  * meant for caching expensive functions "as you go."
8  *
9  *
10  * Cosmin Deaconu
11  * <cozzyd@kicp.uchicago.edu>
12  *
13  **/
14 
15 #include <map>
16 #include <vector>
17 #include <array>
18 #include <bitset>
19 #include <stdio.h>
20 #include <functional>
21 
22 
23 /* You can use the robin hood unordered_map instead of the STL one
24  * by including robin hood before this header */
25 #ifdef ROBIN_HOOD_H_INCLUDED
26 #define lazylookup_umap robin_hood::unordered_map
27 #else
28 #include <unordered_map>
29 #define lazylookup_umap std::unordered_map
30 #endif
31 
32 
33 namespace lazylookup
34 {
35 
36 
47  template<class M, typename K, typename V>
48  class generic_map
49  {
50  private:
51  V dummyVal;
52  M m;
53  std::function<V(K)> Fn;
54  std::size_t nfilled;
55 
56  public:
57  generic_map(std::function<V(K)> fn, V dummy = V())
58  : dummyVal(dummy), Fn(fn), nfilled(0) { ; }
59 
60  V at(K key)
61  {
62  auto res = m.insert(std::pair<K,V>(key,dummyVal));
63  if (res.second)
64  {
65  //now actually evaluate!
66  res.first->second = Fn(key);
67  nfilled++;
68  }
69  return res.first->second;
70  }
71  V operator[](K key) { return at(key); }
72 
73  std::size_t get_n_filled() const { return nfilled; }
74  };
75 
76 
80  template <typename K, typename V>
82 
84  template <typename K, typename V>
85  using map = generic_map<std::map<K,V>,K,V>;
86 
87 
88 
100  template<typename T>
101  class vec
102  {
103  private:
104  std::vector<T> v;
105  std::vector<bool> filled;
106  std::size_t nfilled;
107  std::size_t max_size;
108  std::function<T(std::size_t)> Fn;
109  public:
110  vec(std::function<T(std::size_t)> fn) : nfilled(0), max_size(0), Fn(fn) {; }
111  vec(std::function<T(std::size_t)> fn, std::size_t N, std::size_t grow_limit = 0) :
112  v(N),filled(N), nfilled(0), max_size(grow_limit), Fn(fn) { ; }
113 
114  void resize(std::size_t N)
115  {
116  v.resize(N);
117  filled.resize(N);
118  }
119 
120  // this won't truncate if we're already past max size.
121  void set_grow_limit(std::size_t grow_limit) { max_size = grow_limit; }
122  T at(std::size_t i)
123  {
124 
125  bool must_resize = i >=v.size();
126  if (must_resize)
127  {
128  //too big. we can't do it.
129  if (max_size && i+1 > max_size)
130  {
131  return Fn(i);
132  }
133 
134  v.resize(i+1);
135  filled.resize(i+1);
136  }
137 
138  if (must_resize || !filled[i])
139  {
140  filled[i] = true;
141  nfilled++;
142  v[i] = Fn(i);
143  }
144 
145  return v[i];
146  }
147 
148  T operator[](std::size_t i) { return at(i); }
149 
150  std::size_t get_n_filled() { return nfilled; }
151  };
152 
156  template<std::size_t N, typename T>
157  class arr
158  {
159  private:
160  std::array<T,N> a;
161  std::bitset<N> filled;
162  std::size_t nfilled;
163  std::function<T(std::size_t)> Fn;
164  public:
165 
166  arr(std::function<T(std::size_t)> fn)
167  : nfilled(0), Fn(fn) {;}
168 
169  T at(std::size_t i)
170  {
171  // if out of bounds, just evaluate function.
172  if (i >= N)
173  return Fn(i);
174 
175  if (!filled[i])
176  {
177  filled[i] = true;
178  a[i] = Fn(i);
179  nfilled++;
180  }
181  return a[i];
182  }
183 
184  T operator[](std::size_t i) { return at(i); }
185  std::size_t get_n_filled() { return nfilled; }
186  };
187 
188 
189  //forward declaration for grid interpolator storage types
190  namespace grid_interpolator_storage
191  {
192  class dense;
193  class sparse;
194  }
195 
209  template <std::size_t Ndims, typename Storage =grid_interpolator_storage::dense>
211  {
212  private:
213  std::array<std::size_t,Ndims > Ns;
214  std::array<double,Ndims > mins;
215  std::array<double,Ndims > maxes;
216 
217  std::array<double,Ndims > deltas;
218  std::array<std::size_t,Ndims> prods; //products of dimensions (used in calculating glboal bin number);
219 
220  std::size_t Nbins;
221  Storage storage;
222  double eps;
223  std::function<double(const std::array<double, Ndims>&)> Fn;
224 
225 
226  std::size_t global_bin(const std::array<std::size_t, Ndims> & bins)
227  {
228  std::size_t bin = 0;
229  for (std::size_t i = 0; i < Ndims; i++)
230  {
231  bin += prods[i] * bins[i];
232  }
233  return bin;
234  }
235  double bin_val(const std::array<std::size_t, Ndims> & bins)
236  {
237  size_t gbin = global_bin(bins);
238  if (!storage.have(gbin))
239  {
240  std::array<double, Ndims> X;
241  for (std::size_t i = 0; i < Ndims; i++)
242  {
243  X[i] = mins[i] + bins[i]*deltas[i];
244  }
245  storage.fill(gbin, Fn(X));
246  }
247  return storage.val(gbin);
248  }
249 
250  //TODO see about removing some of these branches if possible
251  bool get_bins(const std::array<double, Ndims> & vals,
252  std::array<std::size_t,Ndims> & low_bins,
253  std::array<std::size_t,Ndims> & high_bins,
254  std::array<double,Ndims> & fracs)
255  {
256  for (std::size_t i = 0; i < Ndims; i++)
257  {
258  if (vals[i] > maxes[i]) return false;
259  if (vals[i] < mins[i]) return false;
260 
261  double fbin = (vals[i] - mins[i]) / deltas[i];
262  int ibin = fbin;
263  low_bins[i] = ibin;
264  high_bins[i] = ibin+1;
265  fracs[i] = fbin-ibin;
266 
267  if (fracs[i] >= 0 && fracs[i] < eps)
268  {
269  fracs[i] = 0;
270  high_bins[i] = ibin;
271  }
272  else if (fracs[i] < 0 && fracs[i] > -eps)
273  {
274  low_bins[i]=high_bins[i];
275  fracs[i] = 0;
276  }
277  }
278 
279  return true;
280  }
281 
282 
283 
284  public:
292  grid_interpolator( std::function<double(const std::array<double,Ndims>&)> f,
293  const std::array<std::size_t, Ndims> & dim_Ns,
294  const std::array<double, Ndims> & dim_mins,
295  const std::array<double, Ndims> & dim_maxes,
296  double bin_eps = 1e-4)
297  : Ns(dim_Ns), mins(dim_mins), maxes(dim_maxes) , Nbins(1), eps(bin_eps), Fn(f)
298  {
299 
300  for (std::size_t i = 0; i < Ndims; i++)
301  {
302  prods[i] = Nbins;
303  Nbins *= (Ns[i]+1);
304  deltas[i] = (maxes[i]-mins[i])/(Ns[i]);
305  }
306 
307  storage.make_room(Nbins);
308  }
309 
310  double val( const std::array<double, Ndims> & X )
311  {
312  //figure out what bin we are in
313  std::array<std::size_t, Ndims > low_bins;
314  std::array<std::size_t, Ndims > high_bins;
315  std::array<double,Ndims> fracs;
316 
317  //out of bounds, just evaluate the function
318  if (!get_bins(X, low_bins, high_bins, fracs))
319  {
320  return Fn(X);
321  }
322 
323 
324  //specialization for 1D
325  if (Ndims == 1)
326  {
327 
328  return !fracs[0] ? bin_val(low_bins) :
329  fracs[0] ==1 ? bin_val(high_bins) :
330  fracs[0] * bin_val(high_bins) + (1-fracs[0]) * bin_val(low_bins);
331  }
332 
333 
334  //specialization for 2D
335  if (Ndims ==2)
336  {
337  double sum = 0;
338  double f00 = (1-fracs[0]) * (1-fracs[1]);
339  double f11 = fracs[0] * fracs[1];
340  double f01 = (1-fracs[0]) * fracs[1];
341  double f10 = (1-fracs[1]) * fracs[0];
342 
343  if (f00) sum+= f00 * bin_val(low_bins);
344  if (f11) sum+= f11 * bin_val(high_bins);
345  if (f10) sum += f10 * bin_val({high_bins[0], low_bins[1]});
346  if (f01) sum += f01 * bin_val({low_bins[0], high_bins[1]});
347 
348  return sum;
349  }
350 
351  double sum = 0;
352  std::array<std::size_t,Ndims> bins;
353 
354  //loop over all vertices.
355  //Each vertex is either lower (0) or upper (1);
356  //There is probably some template magic to express this at compile time
357  // TODO: specialize for low numbers of dimensions
358  for (std::size_t V = 0; V < (1 << Ndims); V++)
359  {
360  double prod = 1;
361  for (std::size_t i = 0; i < Ndims; i++)
362  {
363  bool high = V & (1 << i);
364  prod *= high ? fracs[i]: 1-fracs[i];
365  if (!prod) break; //this coefficient will be zero. no need to calculate.
366  bins[i] = high ? high_bins[i] : low_bins[i];
367  }
368  if (!prod) continue;
369 
370  sum += prod*bin_val(bins);
371  }
372 
373  return sum;
374  }
375 
376  double operator[](std::array<double,Ndims> where ) { return val(where); }
377 
378  };
379 
380  namespace grid_interpolator_storage
381  {
382  //These are only meant to be used by the grid_interpolator!!!!
383  class dense
384  {
385  template <std::size_t Ndims,typename S>
386  friend class lazylookup::grid_interpolator;
387 
388  private:
389  void make_room(std::size_t N)
390  {
391  vals.resize(N);
392  filled.resize(N);
393  }
394 
395 
396  bool have(std::size_t bin)
397  {
398  return filled[bin];
399  }
400 
401  void fill(std::size_t bin, double val)
402  {
403  vals[bin] = val;
404  filled[bin] = true;
405  }
406 
407  double val(std::size_t bin)
408  {
409  return vals[bin];
410  }
411 
412 
413  private:
414  std::vector<double> vals;
415  std::vector<bool> filled;
416  };
417 
418 
419  class sparse
420  {
421 
422  template <std::size_t Ndims,typename S>
423  friend class lazylookup::grid_interpolator;
424  private:
425 
426  void make_room(std::size_t N)
427  {
428  (void) N;
429  }
430 
431  bool have(std::size_t bin)
432  {
433  //try to insert a dummy value, so we have the cached version of this afterwards
434  cached_insert = m.insert(std::pair<std::size_t,double> (bin,0));
435  return !cached_insert.second;
436  }
437 
438  //We always check if we have it first, so we can use our cached insert return value
439  double val(std::size_t bin)
440  {
441  (void) bin;
442  return cached_insert.first->second;
443  }
444 
445  void fill(std::size_t bin, double val)
446  {
447  (void) bin;
448  cached_insert.first->second = val;
449  }
450 
451  private:
452  lazylookup_umap<std::size_t, double> m;
453  std::pair<lazylookup_umap<std::size_t,double>::iterator,bool> cached_insert;
454  };
455 
456  }
457 
458 
459 }
460 #endif
grid_interpolator(std::function< double(const std::array< double, Ndims > &)> f, const std::array< std::size_t, Ndims > &dim_Ns, const std::array< double, Ndims > &dim_mins, const std::array< double, Ndims > &dim_maxes, double bin_eps=1e-4)
Definition: lazylookup.hh:292