@@ -14,7 +14,10 @@ namespace {
1414// Robust Bellman backup at one state: max over actions of the omax optimum.
1515// sense = Min for pessimistic (adversarial nature), Max for optimistic.
1616// A state with no actions has value 0 (cannot make progress to a target).
17- double backup (const StateActions& actions, const std::vector<double >& V, omax::Sense sense) {
17+ // controllerMax: the controller MAXIMIZES over actions (reachability) or MINIMIZES
18+ // (used for safety = 1 - min-reach-to-avoid). A state with no actions has value 0.
19+ double backup (const StateActions& actions, const std::vector<double >& V, omax::Sense sense,
20+ bool controllerMax = true ) {
1821 double best = 0.0 ;
1922 bool any = false ;
2023 std::vector<double > lo, hi, vv;
@@ -23,9 +26,10 @@ double backup(const StateActions& actions, const std::vector<double>& V, omax::S
2326 lo.reserve (act.size ()); hi.reserve (act.size ()); vv.reserve (act.size ());
2427 for (const Interval& iv : act) { lo.push_back (iv.lo ); hi.push_back (iv.hi ); vv.push_back (V[iv.to ]); }
2528 const double val = omax::optimize (lo, hi, vv, sense).value ;
26- if (!any || val > best) { best = val; any = true ; }
29+ if (!any) { best = val; any = true ; }
30+ else best = controllerMax ? std::max (best, val) : std::min (best, val);
2731 }
28- return best;
32+ return any ? best : 0.0 ;
2933}
3034
3135struct Collapsed {
@@ -142,7 +146,7 @@ IntervalResult solveReach(const IMDPModel& m, const std::set<int>& targets,
142146// needed, so it converges on nature-confinable ECs (ISSUE-0003). If the guess is
143147// not yet inductive, refine L (smaller delta) and retry.
144148IntervalResult solveOVI (const IMDPModel& m, const std::set<int >& targets,
145- double eps, omax::Sense sense) {
149+ double eps, omax::Sense sense, bool controllerMax = true ) {
146150 const int n = (int )m.size ();
147151 std::vector<double > L (n, 0.0 ), tmp (n), U (n), FU (n);
148152 for (int t : targets) L[t] = 1.0 ;
@@ -153,7 +157,7 @@ IntervalResult solveOVI(const IMDPModel& m, const std::set<int>& targets,
153157 for (int it = 0 ; it < MAXINNER ; ++it) { // refine L from below
154158 ++iters;
155159 for (int s = 0 ; s < n; ++s)
156- tmp[s] = targets.count (s) ? 1.0 : backup (m[s], L, sense);
160+ tmp[s] = targets.count (s) ? 1.0 : backup (m[s], L, sense, controllerMax );
157161 double ch = 0.0 ;
158162 for (int s = 0 ; s < n; ++s) ch = std::max (ch, std::fabs (tmp[s] - L[s]));
159163 L.swap (tmp);
@@ -163,7 +167,7 @@ IntervalResult solveOVI(const IMDPModel& m, const std::set<int>& targets,
163167 U[s] = targets.count (s) ? 1.0 : std::min (1.0 , L[s] + eps);
164168 bool inductive = true ; // verify F(U) <= U
165169 for (int s = 0 ; s < n; ++s) {
166- FU [s] = targets.count (s) ? 1.0 : backup (m[s], U, sense);
170+ FU [s] = targets.count (s) ? 1.0 : backup (m[s], U, sense, controllerMax );
167171 if (FU [s] > U[s] + 1e-12 ) inductive = false ;
168172 }
169173 if (inductive) return IntervalResult{L, U, iters}; // L <= V* <= U, gap <= eps
@@ -173,6 +177,26 @@ IntervalResult solveOVI(const IMDPModel& m, const std::set<int>& targets,
173177 return IntervalResult{L, U, iters}; // best-effort fallback
174178}
175179
180+ // Min-reach: controller MINIMIZES reach to `targets`. Used for safety.
181+ IntervalResult minReach (const IMDPModel& m, const std::set<int >& targets,
182+ double eps, omax::Sense sense) {
183+ return solveOVI (m, targets, eps, sense, /* controllerMax=*/ false );
184+ }
185+
186+ // Safety = 1 - min-reach-to-avoid (controller maximizes staying out of `avoid`).
187+ IntervalResult safetyFromMinReach (const IMDPModel& m, const std::set<int >& avoid,
188+ double eps, omax::Sense sense) {
189+ IntervalResult mr = minReach (m, avoid, eps, sense);
190+ IntervalResult r;
191+ r.iterations = mr.iterations ;
192+ r.lower .resize (mr.lower .size ()); r.upper .resize (mr.upper .size ());
193+ for (size_t s = 0 ; s < mr.lower .size (); ++s) {
194+ r.lower [s] = 1.0 - mr.upper [s]; // safety lower = 1 - max possible reach
195+ r.upper [s] = 1.0 - mr.lower [s];
196+ }
197+ return r;
198+ }
199+
176200IntervalResult dispatch (const IMDPModel& m, const std::set<int >& targets,
177201 double eps, omax::Sense sense, Method method) {
178202 return (method == Method::MECCollapse) ? solveReach (m, targets, eps, sense)
@@ -194,5 +218,14 @@ IntervalResult maxReachOptimistic(const IMDPModel& m, const std::set<int>& targe
194218 return dispatch (m, targets, eps, omax::Sense::Max, method);
195219}
196220
221+ // Robust safety: max over controller of P(never reach `avoid`); pessimistic =
222+ // nature adversarial (maximizes reach to avoid, omax Sense::Max).
223+ IntervalResult maxSafetyPessimistic (const IMDPModel& m, const std::set<int >& avoid, double eps) {
224+ return safetyFromMinReach (m, avoid, eps, omax::Sense::Max);
225+ }
226+ IntervalResult maxSafetyOptimistic (const IMDPModel& m, const std::set<int >& avoid, double eps) {
227+ return safetyFromMinReach (m, avoid, eps, omax::Sense::Min);
228+ }
229+
197230} // namespace solve
198231} // namespace impact
0 commit comments