1 #ifndef MBA_MBA_HPP
2 #define MBA_MBA_HPP
3
4 /*
5 The MIT License
6
7 Copyright (c) 2015 Denis Demidov <dennis.demidov@gmail.com>
8
9 Permission is hereby granted, free of charge, to any person obtaining a copy
10 of this software and associated documentation files (the "Software"), to deal
11 in the Software without restriction, including without limitation the rights
12 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
13 copies of the Software, and to permit persons to whom the Software is
14 furnished to do so, subject to the following conditions:
15
16 The above copyright notice and this permission notice shall be included in
17 all copies or substantial portions of the Software.
18
19 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
20 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
21 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
22 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
23 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
24 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
25 THE SOFTWARE.
26 */
27
28 /**
29 * \file mba/mba.hpp
30 * \author Denis Demidov <dennis.demidov@gmail.com>
31 * \brief Multilevel B-spline interpolation.
32 */
33
34 #include <iostream>
35 #include <iomanip>
36 #include <map>
37 #include <list>
38 #include <utility>
39 #include <algorithm>
40 #include <array>
41 #include <memory>
42 #include <functional>
43 #include <cassert>
44
45 #include <boost/container/flat_map.hpp>
46 #include <boost/multi_array.hpp>
47 #include <type_traits>
48 #include <boost/io/ios_state.hpp>
49 #include <boost/numeric/ublas/matrix.hpp>
50 #include <boost/numeric/ublas/lu.hpp>
51
52 namespace mba {
53 namespace detail {
54
55 template <size_t N, size_t M>
56 struct power : std::integral_constant<size_t, N * power<N, M-1>::value> {};
57
58 template <size_t N>
59 struct power<N, 0> : std::integral_constant<size_t, 1> {};
60
61 /// N-dimensional grid iterator (nested loop with variable depth).
62 template <unsigned NDim>
63 class grid_iterator {
64 public:
65 typedef std::array<size_t, NDim> index;
66
grid_iterator(const std::array<size_t,NDim> & dims)67 explicit grid_iterator(const std::array<size_t, NDim> &dims)
68 : N(dims), idx(0)
69 {
70 std::fill(i.begin(), i.end(), 0);
71 done = (i == N);
72 }
73
grid_iterator(size_t dim)74 explicit grid_iterator(size_t dim) : idx(0) {
75 std::fill(N.begin(), N.end(), dim);
76 std::fill(i.begin(), i.end(), 0);
77 done = (0 == dim);
78 }
79
operator [](size_t d) const80 size_t operator[](size_t d) const {
81 return i[d];
82 }
83
operator *() const84 const index& operator*() const {
85 return i;
86 }
87
position() const88 size_t position() const {
89 return idx;
90 }
91
operator ++()92 grid_iterator& operator++() {
93 done = true;
94 for(size_t d = NDim; d--; ) {
95 if (++i[d] < N[d]) {
96 done = false;
97 break;
98 }
99 i[d] = 0;
100 }
101
102 ++idx;
103
104 return *this;
105 }
106
operator bool() const107 operator bool() const { return !done; }
108
109 private:
110 index N, i;
111 bool done;
112 size_t idx;
113 };
114
115 template <typename T, size_t N>
operator +(std::array<T,N> a,const std::array<T,N> & b)116 std::array<T, N> operator+(std::array<T, N> a, const std::array<T, N> &b) {
117 std::transform(a.begin(), a.end(), b.begin(), a.begin(), std::plus<T>());
118 return a;
119 }
120
121 template <typename T, size_t N>
operator -(std::array<T,N> a,T b)122 std::array<T, N> operator-(std::array<T, N> a, T b) {
123 std::transform(a.begin(), a.end(), a.begin(), std::bind2nd(std::minus<T>(), b));
124 return a;
125 }
126
127 template <typename T, size_t N>
operator *(std::array<T,N> a,T b)128 std::array<T, N> operator*(std::array<T, N> a, T b) {
129 std::transform(a.begin(), a.end(), a.begin(), std::bind2nd(std::multiplies<T>(), b));
130 return a;
131 }
132
133 // Value of k-th B-Spline basic function at t.
Bspline(size_t k,double t)134 inline double Bspline(size_t k, double t) {
135 assert(0 <= t && t < 1);
136 assert(k < 4);
137
138 switch (k) {
139 case 0:
140 return (t * (t * (-t + 3) - 3) + 1) / 6;
141 case 1:
142 return (t * t * (3 * t - 6) + 4) / 6;
143 case 2:
144 return (t * (t * (-3 * t + 3) + 3) + 1) / 6;
145 case 3:
146 return t * t * t / 6;
147 default:
148 return 0;
149 }
150 }
151
152 // Checks if p is between lo and hi
153 template <typename T, size_t N>
boxed(const std::array<T,N> & lo,const std::array<T,N> & p,const std::array<T,N> & hi)154 bool boxed(const std::array<T,N> &lo, const std::array<T,N> &p, const std::array<T,N> &hi) {
155 for(unsigned i = 0; i < N; ++i) {
156 if (p[i] < lo[i] || p[i] > hi[i]) return false;
157 }
158 return true;
159 }
160
safe_divide(double a,double b)161 inline double safe_divide(double a, double b) {
162 return b == 0.0 ? 0.0 : a / b;
163 }
164
165 template <unsigned NDim>
166 class control_lattice {
167 public:
168 typedef std::array<size_t, NDim> index;
169 typedef std::array<double, NDim> point;
170
~control_lattice()171 virtual ~control_lattice() {}
172
173 virtual double operator()(const point &p) const = 0;
174
175 virtual void report(std::ostream&) const = 0;
176
177 template <class CooIter, class ValIter>
residual(CooIter coo_begin,CooIter coo_end,ValIter val_begin) const178 double residual(CooIter coo_begin, CooIter coo_end, ValIter val_begin) const {
179 double res = 0.0;
180
181 CooIter p = coo_begin;
182 ValIter v = val_begin;
183
184 for(; p != coo_end; ++p, ++v) {
185 (*v) -= (*this)(*p);
186 res = std::max(res, std::abs(*v));
187 }
188
189 return res;
190 }
191 };
192
193 template <unsigned NDim>
194 class initial_approximation : public control_lattice<NDim> {
195 public:
196 typedef typename control_lattice<NDim>::point point;
197
initial_approximation(std::function<double (const point &)> f)198 initial_approximation(std::function<double(const point&)> f)
199 : f(f) {}
200
operator ()(const point & p) const201 double operator()(const point &p) const {
202 return f(p);
203 }
204
report(std::ostream & os) const205 void report(std::ostream &os) const {
206 os << "initial approximation";
207 }
208 private:
209 std::function<double(const point&)> f;
210 };
211
212 template <unsigned NDim>
213 class control_lattice_dense : public control_lattice<NDim> {
214 public:
215 typedef typename control_lattice<NDim>::index index;
216 typedef typename control_lattice<NDim>::point point;
217
218 template <class CooIter, class ValIter>
control_lattice_dense(const point & coo_min,const point & coo_max,index grid_size,CooIter coo_begin,CooIter coo_end,ValIter val_begin)219 control_lattice_dense(
220 const point &coo_min, const point &coo_max, index grid_size,
221 CooIter coo_begin, CooIter coo_end, ValIter val_begin
222 ) : cmin(coo_min), cmax(coo_max), grid(grid_size)
223 {
224 for(unsigned i = 0; i < NDim; ++i) {
225 hinv[i] = (grid[i] - 1) / (cmax[i] - cmin[i]);
226 cmin[i] -= 1 / hinv[i];
227 grid[i] += 2;
228 }
229
230 boost::multi_array<double, NDim> delta(grid);
231 boost::multi_array<double, NDim> omega(grid);
232
233 std::fill(delta.data(), delta.data() + delta.num_elements(), 0.0);
234 std::fill(omega.data(), omega.data() + omega.num_elements(), 0.0);
235
236 CooIter p = coo_begin;
237 ValIter v = val_begin;
238
239 for(; p != coo_end; ++p, ++v) {
240 if (!boxed(coo_min, *p, coo_max)) continue;
241
242 index i;
243 point s;
244
245 for(unsigned d = 0; d < NDim; ++d) {
246 double u = ((*p)[d] - cmin[d]) * hinv[d];
247 i[d] = floor(u) - 1;
248 s[d] = u - floor(u);
249 }
250
251 std::array< double, power<4, NDim>::value > w;
252 double sum_w2 = 0.0;
253
254 for(grid_iterator<NDim> d(4); d; ++d) {
255 double prod = 1.0;
256 for(unsigned k = 0; k < NDim; ++k) prod *= Bspline(d[k], s[k]);
257
258 w[d.position()] = prod;
259 sum_w2 += prod * prod;
260 }
261
262 for(grid_iterator<NDim> d(4); d; ++d) {
263 double w1 = w[d.position()];
264 double w2 = w1 * w1;
265 double phi = (*v) * w1 / sum_w2;
266
267 index j = i + (*d);
268
269 delta(j) += w2 * phi;
270 omega(j) += w2;
271 }
272 }
273
274 phi.resize(grid);
275
276 std::transform(
277 delta.data(), delta.data() + delta.num_elements(),
278 omega.data(), phi.data(), safe_divide
279 );
280 }
281
operator ()(const point & p) const282 double operator()(const point &p) const {
283 index i;
284 point s;
285
286 for(unsigned d = 0; d < NDim; ++d) {
287 double u = (p[d] - cmin[d]) * hinv[d];
288 i[d] = floor(u) - 1;
289 s[d] = u - floor(u);
290 }
291
292 double f = 0;
293
294 for(grid_iterator<NDim> d(4); d; ++d) {
295 double w = 1.0;
296 for(unsigned k = 0; k < NDim; ++k) w *= Bspline(d[k], s[k]);
297
298 f += w * phi(i + (*d));
299 }
300
301 return f;
302 }
303
report(std::ostream & os) const304 void report(std::ostream &os) const {
305 boost::io::ios_all_saver stream_state(os);
306
307 os << "dense [" << grid[0];
308 for(unsigned i = 1; i < NDim; ++i)
309 os << ", " << grid[i];
310 os << "] (" << phi.num_elements() * sizeof(double) << " bytes)";
311 }
312
append_refined(const control_lattice_dense & r)313 void append_refined(const control_lattice_dense &r) {
314 static const std::array<double, 5> s = {
315 0.125, 0.500, 0.750, 0.500, 0.125
316 };
317
318 for(grid_iterator<NDim> i(r.grid); i; ++i) {
319 double f = r.phi(*i);
320
321 if (f == 0.0) continue;
322
323 for(grid_iterator<NDim> d(5); d; ++d) {
324 index j;
325 bool skip = false;
326 for(unsigned k = 0; k < NDim; ++k) {
327 j[k] = 2 * i[k] + d[k] - 3;
328 if (j[k] >= grid[k]) {
329 skip = true;
330 break;
331 }
332 }
333
334 if (skip) continue;
335
336 double c = 1.0;
337 for(unsigned k = 0; k < NDim; ++k) c *= s[d[k]];
338
339 phi(j) += f * c;
340 }
341 }
342 }
343
fill_ratio() const344 double fill_ratio() const {
345 size_t total = phi.num_elements();
346 size_t nonzeros = total - std::count(phi.data(), phi.data() + total, 0.0);
347
348 return static_cast<double>(nonzeros) / total;
349 }
350
351 private:
352 point cmin, cmax, hinv;
353 index grid;
354
355 boost::multi_array<double, NDim> phi;
356
357 };
358
359 template <unsigned NDim>
360 class control_lattice_sparse : public control_lattice<NDim> {
361 public:
362 typedef typename control_lattice<NDim>::index index;
363 typedef typename control_lattice<NDim>::point point;
364
365 template <class CooIter, class ValIter>
control_lattice_sparse(const point & coo_min,const point & coo_max,index grid_size,CooIter coo_begin,CooIter coo_end,ValIter val_begin)366 control_lattice_sparse(
367 const point &coo_min, const point &coo_max, index grid_size,
368 CooIter coo_begin, CooIter coo_end, ValIter val_begin
369 ) : cmin(coo_min), cmax(coo_max), grid(grid_size)
370 {
371 for(unsigned i = 0; i < NDim; ++i) {
372 hinv[i] = (grid[i] - 1) / (cmax[i] - cmin[i]);
373 cmin[i] -= 1 / hinv[i];
374 grid[i] += 2;
375 }
376
377 std::map<index, two_doubles> dw;
378
379 CooIter p = coo_begin;
380 ValIter v = val_begin;
381
382 for(; p != coo_end; ++p, ++v) {
383 if (!boxed(coo_min, *p, coo_max)) continue;
384
385 index i;
386 point s;
387
388 for(unsigned d = 0; d < NDim; ++d) {
389 double u = ((*p)[d] - cmin[d]) * hinv[d];
390 i[d] = floor(u) - 1;
391 s[d] = u - floor(u);
392 }
393
394 std::array< double, power<4, NDim>::value > w;
395 double sum_w2 = 0.0;
396
397 for(grid_iterator<NDim> d(4); d; ++d) {
398 double prod = 1.0;
399 for(unsigned k = 0; k < NDim; ++k) prod *= Bspline(d[k], s[k]);
400
401 w[d.position()] = prod;
402 sum_w2 += prod * prod;
403 }
404
405 for(grid_iterator<NDim> d(4); d; ++d) {
406 double w1 = w[d.position()];
407 double w2 = w1 * w1;
408 double phi = (*v) * w1 / sum_w2;
409
410 two_doubles delta_omega = {w2 * phi, w2};
411
412 append(dw[i + (*d)], delta_omega);
413 }
414 }
415
416 phi.insert(//boost::container::ordered_unique_range,
417 boost::make_transform_iterator(dw.begin(), delta_over_omega),
418 boost::make_transform_iterator(dw.end(), delta_over_omega)
419 );
420 }
421
operator ()(const point & p) const422 double operator()(const point &p) const {
423 index i;
424 point s;
425
426 for(unsigned d = 0; d < NDim; ++d) {
427 double u = (p[d] - cmin[d]) * hinv[d];
428 i[d] = floor(u) - 1;
429 s[d] = u - floor(u);
430 }
431
432 double f = 0;
433
434 for(grid_iterator<NDim> d(4); d; ++d) {
435 double w = 1.0;
436 for(unsigned k = 0; k < NDim; ++k) w *= Bspline(d[k], s[k]);
437
438 f += w * get_phi(i + (*d));
439 }
440
441 return f;
442 }
443
report(std::ostream & os) const444 void report(std::ostream &os) const {
445 boost::io::ios_all_saver stream_state(os);
446
447 size_t grid_size = grid[0];
448
449 os << "sparse [" << grid[0];
450 for(unsigned i = 1; i < NDim; ++i) {
451 os << ", " << grid[i];
452 grid_size *= grid[i];
453 }
454
455 size_t bytes = phi.size() * sizeof(std::pair<index, double>);
456 size_t dense_bytes = grid_size * sizeof(double);
457
458 double compression = static_cast<double>(bytes) / dense_bytes;
459 os << "] (" << bytes << " bytes, compression: "
460 << std::fixed << std::setprecision(2) << compression << ")";
461 }
462 private:
463 point cmin, cmax, hinv;
464 index grid;
465
466 typedef boost::container::flat_map<index, double> sparse_grid;
467 sparse_grid phi;
468
469 typedef std::array<double, 2> two_doubles;
470
delta_over_omega(const std::pair<index,two_doubles> & dw)471 static std::pair<index, double> delta_over_omega(const std::pair<index, two_doubles> &dw) {
472 return std::make_pair(dw.first, safe_divide(dw.second[0], dw.second[1]));
473 }
474
append(two_doubles & a,const two_doubles & b)475 static void append(two_doubles &a, const two_doubles &b) {
476 std::transform(a.begin(), a.end(), b.begin(), a.begin(), std::plus<double>());
477 }
478
get_phi(const index & i) const479 double get_phi(const index &i) const {
480 typename sparse_grid::const_iterator c = phi.find(i);
481 return c == phi.end() ? 0.0 : c->second;
482 }
483 };
484
485 } // namespace detail
486
487 template <unsigned NDim>
488 class linear_approximation {
489 public:
490 typedef typename detail::control_lattice<NDim>::point point;
491
492 template <class CooIter, class ValIter>
linear_approximation(CooIter coo_begin,CooIter coo_end,ValIter val_begin)493 linear_approximation(CooIter coo_begin, CooIter coo_end, ValIter val_begin)
494 {
495 namespace ublas = boost::numeric::ublas;
496
497 size_t n = std::distance(coo_begin, coo_end);
498
499 if (n <= NDim) {
500 // Not enough points to get a unique plane
501 std::fill(C.begin(), C.end(), 0.0);
502 C[NDim] = std::accumulate(val_begin, val_begin + n, 0.0) / n;
503 return;
504 }
505
506 ublas::matrix<double> A(NDim+1, NDim+1); A.clear();
507 ublas::vector<double> f(NDim+1); f.clear();
508
509 CooIter p = coo_begin;
510 ValIter v = val_begin;
511
512 double sum_val = 0.0;
513
514 // Solve least-squares problem to get approximation with a plane.
515 for(; p != coo_end; ++p, ++v, ++n) {
516 std::array<double, NDim+1> x;
517 std::copy(p->begin(), p->end(), boost::begin(x));
518 x[NDim] = 1.0;
519
520 for(unsigned i = 0; i <= NDim; ++i) {
521 for(unsigned j = 0; j <= NDim; ++j) {
522 A(i,j) += x[i] * x[j];
523 }
524 f(i) += x[i] * (*v);
525 }
526
527 sum_val += (*v);
528 }
529
530 ublas::permutation_matrix<size_t> pm(NDim+1);
531 ublas::lu_factorize(A, pm);
532
533 bool singular = false;
534 for(unsigned i = 0; i <= NDim; ++i) {
535 if (A(i,i) == 0.0) {
536 singular = true;
537 break;
538 }
539 }
540
541 if (singular) {
542 std::fill(C.begin(), C.end(), 0.0);
543 C[NDim] = sum_val / n;
544 } else {
545 ublas::lu_substitute(A, pm, f);
546 for(unsigned i = 0; i <= NDim; ++i) C[i] = f(i);
547 }
548 }
549
operator ()(const point & p) const550 double operator()(const point &p) const {
551 double f = C[NDim];
552
553 for(unsigned i = 0; i < NDim; ++i)
554 f += C[i] * p[i];
555
556 return f;
557 }
558 private:
559 std::array<double, NDim+1> C;
560 };
561
562 template <unsigned NDim>
563 class MBA {
564 public:
565 typedef std::array<size_t, NDim> index;
566 typedef std::array<double, NDim> point;
567
568 template <class CooIter, class ValIter>
MBA(const point & coo_min,const point & coo_max,index grid,CooIter coo_begin,CooIter coo_end,ValIter val_begin,unsigned max_levels=8,double tol=1e-8,double min_fill=0.5,std::function<double (point)> initial=std::function<double (point)> ())569 MBA(
570 const point &coo_min, const point &coo_max, index grid,
571 CooIter coo_begin, CooIter coo_end, ValIter val_begin,
572 unsigned max_levels = 8, double tol = 1e-8, double min_fill = 0.5,
573 std::function<double(point)> initial = std::function<double(point)>()
574 )
575 {
576 init(
577 coo_min, coo_max, grid,
578 coo_begin, coo_end, val_begin,
579 max_levels, tol, min_fill, initial
580 );
581 }
582
583 template <class CooRange, class ValRange>
MBA(const point & coo_min,const point & coo_max,index grid,CooRange coo,ValRange val,unsigned max_levels=8,double tol=1e-8,double min_fill=0.5,std::function<double (point)> initial=std::function<double (point)> ())584 MBA(
585 const point &coo_min, const point &coo_max, index grid,
586 CooRange coo, ValRange val,
587 unsigned max_levels = 8, double tol = 1e-8, double min_fill = 0.5,
588 std::function<double(point)> initial = std::function<double(point)>()
589 )
590 {
591 init(
592 coo_min, coo_max, grid,
593 boost::begin(coo), boost::end(coo), boost::begin(val),
594 max_levels, tol, min_fill, initial
595 );
596 }
597
operator ()(const point & p) const598 double operator()(const point &p) const {
599 double f = 0.0;
600
601 for(const auto &psi : cl) {
602 f += (*psi)(p);
603 }
604
605 return f;
606 }
607
operator <<(std::ostream & os,const MBA & h)608 friend std::ostream& operator<<(std::ostream &os, const MBA &h) {
609 size_t level = 0;
610 for(const auto &psi : h.cl) {
611 os << "level " << ++level << ": ";
612 psi->report(os);
613 os << std::endl;
614 }
615 return os;
616 }
617
618 private:
619 typedef detail::control_lattice<NDim> lattice;
620 typedef detail::initial_approximation<NDim> initial_approximation;
621 typedef detail::control_lattice_dense<NDim> dense_lattice;
622 typedef detail::control_lattice_sparse<NDim> sparse_lattice;
623
624
625 std::list< std::shared_ptr<lattice> > cl;
626
627 template <class CooIter, class ValIter>
init(const point & cmin,const point & cmax,index grid,CooIter coo_begin,CooIter coo_end,ValIter val_begin,unsigned max_levels,double tol,double min_fill,std::function<double (point)> initial)628 void init(
629 const point &cmin, const point &cmax, index grid,
630 CooIter coo_begin, CooIter coo_end, ValIter val_begin,
631 unsigned max_levels, double tol, double min_fill,
632 std::function<double(point)> initial
633 )
634 {
635 using namespace mba::detail;
636
637 const ptrdiff_t n = std::distance(coo_begin, coo_end);
638 std::vector<double> val(val_begin, val_begin + n);
639
640 double res, eps = 0.0;
641 for(ptrdiff_t i = 0; i < n; ++i)
642 eps = std::max(eps, std::abs(val[i]));
643 eps *= tol;
644
645 if (initial) {
646 // Start with the given approximation.
647 cl.push_back(std::make_shared<initial_approximation>(initial));
648 res = cl.back()->residual(coo_begin, coo_end, val.begin());
649 if (res <= eps) return;
650 }
651
652 size_t lev = 1;
653 // Create dense head of the hierarchy.
654 {
655 auto psi = std::make_shared<dense_lattice>(
656 cmin, cmax, grid, coo_begin, coo_end, val.begin());
657
658 res = psi->residual(coo_begin, coo_end, val.begin());
659 double fill = psi->fill_ratio();
660
661 for(; (lev < max_levels) && (res > eps) && (fill > min_fill); ++lev) {
662 grid = grid * 2ul - 1ul;
663
664 auto f = std::make_shared<dense_lattice>(
665 cmin, cmax, grid, coo_begin, coo_end, val.begin());
666
667 res = f->residual(coo_begin, coo_end, val.begin());
668 fill = f->fill_ratio();
669
670 f->append_refined(*psi);
671 psi.swap(f);
672 }
673
674 cl.push_back(psi);
675 }
676
677 // Create sparse tail of the hierrchy.
678 for(; (lev < max_levels) && (res > eps); ++lev) {
679 grid = grid * 2ul - 1ul;
680
681 cl.push_back(std::make_shared<sparse_lattice>(
682 cmin, cmax, grid, coo_begin, coo_end, val.begin()));
683
684 res = cl.back()->residual(coo_begin, coo_end, val.begin());
685 }
686 }
687 };
688
689 } // namespace mba
690
691 #endif
692