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