|
| 1 | +#include "abstraction.h" |
| 2 | + |
| 3 | +#include <cmath> |
| 4 | +#include <algorithm> |
| 5 | + |
| 6 | +namespace impact { |
| 7 | +namespace abstraction { |
| 8 | + |
| 9 | +namespace { constexpr double INV_SQRT2 = 0.70710678118654752440; } |
| 10 | + |
| 11 | +double normalCdf(double z) { return 0.5 * std::erfc(-z * INV_SQRT2); } |
| 12 | + |
| 13 | +double massInInterval(double mu, double sigma, double a, double b) { |
| 14 | + return normalCdf((b - mu) / sigma) - normalCdf((a - mu) / sigma); |
| 15 | +} |
| 16 | + |
| 17 | +Bound transitionInterval1D(double muLo, double muHi, double sigma, double a, double b) { |
| 18 | + if (muHi < muLo) std::swap(muLo, muHi); |
| 19 | + const double center = 0.5 * (a + b); |
| 20 | + const double muMax = std::min(std::max(center, muLo), muHi); // closest to centre |
| 21 | + double hi = massInInterval(muMax, sigma, a, b); |
| 22 | + double lo = std::min(massInInterval(muLo, sigma, a, b), |
| 23 | + massInInterval(muHi, sigma, a, b)); // farther endpoint |
| 24 | + lo = std::max(0.0, std::min(1.0, lo)); |
| 25 | + hi = std::max(0.0, std::min(1.0, hi)); |
| 26 | + if (lo > hi) lo = hi; |
| 27 | + return {lo, hi}; |
| 28 | +} |
| 29 | + |
| 30 | +Bound transitionIntervalBox(const std::vector<double>& muLo, |
| 31 | + const std::vector<double>& muHi, |
| 32 | + const std::vector<double>& sigma, |
| 33 | + const std::vector<double>& aLo, |
| 34 | + const std::vector<double>& aHi) { |
| 35 | + double lo = 1.0, hi = 1.0; |
| 36 | + for (size_t d = 0; d < sigma.size(); ++d) { |
| 37 | + Bound b = transitionInterval1D(muLo[d], muHi[d], sigma[d], aLo[d], aHi[d]); |
| 38 | + lo *= b.lo; hi *= b.hi; |
| 39 | + } |
| 40 | + return {lo, hi}; |
| 41 | +} |
| 42 | + |
| 43 | +SparseReach buildSparseReach1D(const System1D& sys, double prune) { |
| 44 | + const int N = std::max(1, (int)std::llround((sys.xub - sys.xlb) / sys.eta)); |
| 45 | + const int TARGET = N, SINK = N + 1; |
| 46 | + const int M = std::max(0, (int)std::llround((sys.uub - sys.ulb) / sys.ueta)); // input grid points = M+1 |
| 47 | + |
| 48 | + SparseReach out; |
| 49 | + out.nCells = N; |
| 50 | + out.nnz = 0; |
| 51 | + out.model.assign(N + 2, {}); |
| 52 | + out.targets.insert(TARGET); |
| 53 | + |
| 54 | + auto cellLo = [&](int j) { return sys.xlb + j * sys.eta; }; |
| 55 | + auto isTargetCell = [&](int j) { |
| 56 | + double lo = cellLo(j), hi = lo + sys.eta; |
| 57 | + return lo >= sys.tlo - 1e-12 && hi <= sys.thi + 1e-12; |
| 58 | + }; |
| 59 | + |
| 60 | + const double W = 6.0 * sys.sigma; // truncation window (~all mass within 6 sigma) |
| 61 | + |
| 62 | + for (int i = 0; i < N; ++i) { |
| 63 | + if (isTargetCell(i)) { // absorbing target cell |
| 64 | + out.model[i].push_back({ {TARGET, 1.0, 1.0} }); |
| 65 | + out.nnz += 1; |
| 66 | + continue; |
| 67 | + } |
| 68 | + const double xl = cellLo(i), xr = xl + sys.eta; |
| 69 | + for (int k = 0; k <= M; ++k) { |
| 70 | + const double u = sys.ulb + k * sys.ueta; |
| 71 | + // affine mean range over the source cell (handle sign of a) |
| 72 | + double m1 = sys.a * xl + sys.b * u, m2 = sys.a * xr + sys.b * u; |
| 73 | + double muLo = std::min(m1, m2), muHi = std::max(m1, m2); |
| 74 | + |
| 75 | + solve::ActionDist row; |
| 76 | + // mass into the target region [tlo,thi] |
| 77 | + Bound bT = transitionInterval1D(muLo, muHi, sys.sigma, sys.tlo, sys.thi); |
| 78 | + if (bT.hi > prune) row.push_back({TARGET, bT.lo, bT.hi}); |
| 79 | + |
| 80 | + // mass into non-target grid cells within the support window |
| 81 | + int jmin = (int)std::floor((muLo - W - sys.xlb) / sys.eta); |
| 82 | + int jmax = (int)std::floor((muHi + W - sys.xlb) / sys.eta); |
| 83 | + jmin = std::max(0, jmin); jmax = std::min(N - 1, jmax); |
| 84 | + for (int j = jmin; j <= jmax; ++j) { |
| 85 | + if (isTargetCell(j)) continue; // counted in the TARGET aggregate |
| 86 | + double cl = cellLo(j), cr = cl + sys.eta; |
| 87 | + Bound b = transitionInterval1D(muLo, muHi, sys.sigma, cl, cr); |
| 88 | + if (b.hi > prune) row.push_back({j, b.lo, b.hi}); |
| 89 | + } |
| 90 | + |
| 91 | + // remaining mass (outside grid / pruned) -> SINK, with feasible bounds |
| 92 | + double sumLo = 0, sumHi = 0; |
| 93 | + for (const auto& iv : row) { sumLo += iv.lo; sumHi += iv.hi; } |
| 94 | + double sinkLo = std::max(0.0, 1.0 - sumHi); |
| 95 | + double sinkHi = std::min(1.0, 1.0 - sumLo); |
| 96 | + row.push_back({SINK, sinkLo, sinkHi}); |
| 97 | + |
| 98 | + out.nnz += (long long)row.size(); |
| 99 | + out.model[i].push_back(std::move(row)); |
| 100 | + } |
| 101 | + } |
| 102 | + out.model[TARGET].push_back({ {TARGET, 1.0, 1.0} }); // absorbing |
| 103 | + out.model[SINK].push_back({ {SINK, 1.0, 1.0} }); |
| 104 | + return out; |
| 105 | +} |
| 106 | + |
| 107 | +} // namespace abstraction |
| 108 | +} // namespace impact |
0 commit comments