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