docc 0.8.3 (0d44999b)
C++ API Reference
Loading...
Searching...
No Matches
extreme_values.h
Go to the documentation of this file.
1
29
30#pragma once
31
32#include "sdfg/options.h"
36
37#include <memory>
38#include <string>
39#include <unordered_map>
40
41namespace sdfg {
42namespace symbolic {
43
45
46// Default proof-search work budget (node visits per top-level query). Some
47// queries over deeply-nested tile nests fan out into many fruitless sub-proofs;
48// the cap bails them early with a conservative "unknown".
49inline constexpr int64_t DEFAULT_BOUND_BUDGET = 50000;
50
51// Budget for the bound queries of delinearization (`AssumptionsBounds`). A
52// failed proof only keeps an access 1-D, and failing proofs exhaust the budget on every candidate split.
53inline constexpr int64_t DELINEARIZATION_PROOF_BUDGET = 5000;
54
55// Discoverable, threadable override for the budget above; shared by any analysis
56// or pass that constructs a BoundAnalysis.
57inline constexpr OptionKey<int64_t> BOUND_BUDGET{"symbolic.bound_budget"};
58
65struct Interval {
68
70 bool failed() const {
71 return lower.is_null() && upper.is_null();
72 }
73
75 bool has_lower() const {
76 return !lower.is_null();
77 }
78
80 bool has_upper() const {
81 return !upper.is_null();
82 }
83
85 static Interval exact(const Expression& expr) {
86 return {expr, expr};
87 }
88
90 static Interval failure() {
91 return {SymEngine::null, SymEngine::null};
92 }
93};
94
111public:
113 const SymbolSet& parameters,
114 const Assumptions& assumptions,
115 bool use_tight_assumptions,
116 int64_t budget = DEFAULT_BOUND_BUDGET,
117 bool project_constraints = true
118 );
119
122
125
128
129private:
130 static constexpr size_t MAX_DEPTH = 100;
131 static constexpr size_t MAX_GENERATORS = 16;
132
133 const SymbolSet& parameters_;
134 const Assumptions& assumptions_;
135 bool use_tight_;
136 int64_t budget_;
137 // When false, skip coupled-constraint projection: cheaper, sound, but looser bounds.
138 bool project_constraints_;
139
140 // Cycle detection: symbols currently being bounded
141 SymbolSet visiting_;
142
143 // Monotonic counter incremented every time the cycle guard in
144 // `visit_symbol` fires (a symbol's resolution was abandoned because
145 // we were already bounding it). Used by `visit()` to detect that a
146 // subtree's result was influenced by the current `visiting_` state —
147 // such context-tainted results must not be cached, because the same
148 // expression evaluated outside the cycle could yield a tighter bound
149 // (e.g. via coupled-constraint projection). Compared at entry vs.
150 // exit of `visit_uncached`; if it grew, the result is tainted.
151 size_t cycle_hits_ = 0;
152
153 // Like `cycle_hits_`, for the `MAX_DEPTH` cutoff (results computed under it are depth-dependent).
154 size_t limit_hits_ = 0;
155
156 // Memoization cache for visit() results, keyed by canonical SymEngine
157 // hash+eq on the input expression. Only results computed without hitting
158 // the cycle guard, the depth limit, or the work budget are cached (including
159 // failures and one-sided intervals): those depend solely on the expression
160 // and this instance's fixed inputs. Tainted results are recomputed.
161 struct BasicHash {
162 size_t operator()(const Expression& e) const noexcept {
163 return e->hash();
164 }
165 };
166 struct BasicEq {
167 bool operator()(const Expression& a, const Expression& b) const noexcept {
168 return a.get() == b.get() || SymEngine::eq(*a, *b);
169 }
170 };
171 std::unordered_map<Expression, Interval, BasicHash, BasicEq> cache_;
172
173 Interval visit(const Expression& expr, size_t depth);
174 Interval visit_uncached(const Expression& expr, size_t depth);
175
176 Interval visit_symbol(const SymEngine::RCP<const SymEngine::Symbol>& sym, size_t depth);
177 Interval visit_function(const SymEngine::RCP<const SymEngine::FunctionSymbol>& func, size_t depth);
178 Interval visit_pow(const SymEngine::RCP<const SymEngine::Pow>& pow_expr, size_t depth);
179 Interval visit_mul(const SymEngine::RCP<const SymEngine::Mul>& mul_expr, size_t depth);
180 Interval visit_add(const SymEngine::RCP<const SymEngine::Add>& add_expr, size_t depth);
181 Interval visit_max(const Expression& expr, size_t depth);
182 Interval visit_min(const Expression& expr, size_t depth);
183
184 // Add sub-strategies
185 Interval visit_add_affine(const AffineCoeffs& coeffs, const SymbolVec& gens, size_t depth);
186 Interval visit_add_polynomial(const Polynomial& poly, const SymbolVec& gens, size_t depth);
187 Interval visit_add_argwise(const SymEngine::vec_basic& args, size_t depth);
188
189 // Tightens an affine-sum interval by projecting coupled constraints onto
190 // the SUM as a single linear functional. Per-symbol projection (already
191 // done in `visit_symbol`) loses coupling: bounding `x + y` with the
192 // constraint `x + y <= K` registered on both symbols would sum the
193 // per-symbol upper bounds `x <= K - y` and `y <= K - x`, each resolved
194 // independently down to `K`, yielding `2*K` instead of `K`.
195 //
196 // This helper looks for a non-negative integer linear combination of
197 // registered constraints whose generator coefficients match those of the
198 // input sum exactly. When such a combination exists, every term in the
199 // sum cancels against the constraint combination, leaving a parameter-
200 // only residue that is the tightening bound. Greedy per-generator search
201 // is sufficient for the common patterns (single halo-guard constraint,
202 // block-diagonal multi-axis stencil guards).
203 //
204 // Returns a (possibly partially-null) `Interval` to be intersected with
205 // the result of the regular per-term bounding flow.
206 Interval visit_add_coupled_constraints(const AffineCoeffs& coeffs, const SymbolVec& gens, size_t depth);
207};
208
209// ---- Backward-compatible free functions ----
210
219Expression minimum(const Expression expr, const SymbolSet& parameters, const Assumptions& assumptions, bool tight);
220
229Expression maximum(const Expression expr, const SymbolSet& parameters, const Assumptions& assumptions, bool tight);
230
231// ---- Sound inequality proofs ----
232//
233// All `is_*` predicates return TRUE only when the inequality is provable from
234// the supplied assumptions; FALSE means "unknown OR disproven" — callers must
235// not interpret a false result as the negation of the predicate.
236//
237// Proof strategy (in order, all sound):
238// 1. Direct: `symbolic::is_true(Op(a, b))` for trivially decidable cases.
239// 2. Interval: compute `lower(a-b)` / `upper(a-b)` via `BoundAnalysis` and
240// compare against zero.
241// 3. Min/Max descent: when the residue contains a `Min`/`Max` subexpression
242// in a monotone-nondecreasing position (e.g. additive top-level), recurse
243// on each replacement that yields a sound bound on the residue.
244//
245// Affine expressions over symbols with assumption-derived bounds are the
246// expected input shape (subscript indices, stride differences). The descent
247// is sound for these; for arbitrary expressions the interval step is the
248// guaranteed-sound floor.
249
251bool is_nonneg(const Expression& expr, const SymbolSet& parameters, const Assumptions& assumptions, bool tight = false);
252
254bool is_positive(const Expression& expr, const SymbolSet& parameters, const Assumptions& assumptions, bool tight = false);
255
257bool is_nonpos(const Expression& expr, const SymbolSet& parameters, const Assumptions& assumptions, bool tight = false);
258
260bool is_negative(const Expression& expr, const SymbolSet& parameters, const Assumptions& assumptions, bool tight = false);
261
263bool is_ge(
264 const Expression& a,
265 const Expression& b,
266 const SymbolSet& parameters,
267 const Assumptions& assumptions,
268 bool tight = false
269);
270
272bool is_gt(
273 const Expression& a,
274 const Expression& b,
275 const SymbolSet& parameters,
276 const Assumptions& assumptions,
277 bool tight = false
278);
279
281bool is_le(
282 const Expression& a,
283 const Expression& b,
284 const SymbolSet& parameters,
285 const Assumptions& assumptions,
286 bool tight = false
287);
288
290bool is_lt(
291 const Expression& a,
292 const Expression& b,
293 const SymbolSet& parameters,
294 const Assumptions& assumptions,
295 bool tight = false
296);
297
299bool is_eq(
300 const Expression& a,
301 const Expression& b,
302 const SymbolSet& parameters,
303 const Assumptions& assumptions,
304 bool tight = false
305);
306
307// ---- BoundAnalysis-based overloads (cache-amortized) ----
308//
309// These overloads route all internal interval queries through the supplied
310// `BoundAnalysis`, so the cache amortizes across many predicate calls under
311// the same `(parameters, assumptions, tight)` triple. They omit the empty-
312// parameter fallback that the legacy overloads perform when the caller's
313// parameter set is non-empty; this fallback is a no-op for callers that
314// already use empty parameters (e.g. `delinearize`) and matters only when
315// proving inequalities about parameters themselves.
316
318bool is_nonneg(const Expression& expr, BoundAnalysis& ba);
319
321bool is_positive(const Expression& expr, BoundAnalysis& ba);
322
324bool is_nonpos(const Expression& expr, BoundAnalysis& ba);
325
327bool is_negative(const Expression& expr, BoundAnalysis& ba);
328
330bool is_ge(const Expression& a, const Expression& b, BoundAnalysis& ba);
331
333bool is_gt(const Expression& a, const Expression& b, BoundAnalysis& ba);
334
336bool is_le(const Expression& a, const Expression& b, BoundAnalysis& ba);
337
339bool is_lt(const Expression& a, const Expression& b, BoundAnalysis& ba);
340
342bool is_eq(const Expression& a, const Expression& b, BoundAnalysis& ba);
343
360public:
362 : assums_(assums), loose_(empty_params(), assums, /*tight=*/false, DELINEARIZATION_PROOF_BUDGET),
363 tight_(empty_params(), assums, /*tight=*/true, DELINEARIZATION_PROOF_BUDGET) {
364 }
365
366 const Assumptions& assums() const {
367 return assums_;
368 }
370 return loose_;
371 }
373 return tight_;
374 }
375
377 bool is_nonneg(const Expression& expr, const SymbolSet& parameters);
379 bool is_positive(const Expression& expr, const SymbolSet& parameters);
380
382 std::unordered_map<
384 std::shared_ptr<const DelinearizeResult>,
385 SymEngine::RCPBasicHash,
386 SymEngine::RCPBasicKeyEq>&
388 return delinearize_memo_;
389 }
390
391private:
392 static const SymbolSet& empty_params() {
393 static const SymbolSet kEmpty;
394 return kEmpty;
395 }
396
397 const Assumptions& assums_;
398 BoundAnalysis loose_;
399 BoundAnalysis tight_;
400
401 // Keyed by (strict, parameter names), then expression.
402 std::unordered_map<
403 std::string,
404 std::unordered_map<Expression, bool, SymEngine::RCPBasicHash, SymEngine::RCPBasicKeyEq>>
405 proof_memo_;
406
407 std::unordered_map<
409 std::shared_ptr<const DelinearizeResult>,
410 SymEngine::RCPBasicHash,
411 SymEngine::RCPBasicKeyEq>
412 delinearize_memo_;
413};
414
415} // namespace symbolic
416} // namespace sdfg
Symbol assumptions for reasoning about bounds, constness, and evolution.
bool is_nonneg(const Expression &expr, const SymbolSet &parameters)
Memoized loose-mode is_nonneg(expr, parameters, assums(), false).
BoundAnalysis & tight()
Definition extreme_values.h:372
bool is_positive(const Expression &expr, const SymbolSet &parameters)
Memoized loose-mode is_positive(expr, parameters, assums(), false).
AssumptionsBounds(const Assumptions &assums)
Definition extreme_values.h:361
std::unordered_map< Expression, std::shared_ptr< const DelinearizeResult >, SymEngine::RCPBasicHash, SymEngine::RCPBasicKeyEq > & delinearize_memo()
Memo for delinearize(expr, *this), which is pure in (expr, assums()).
Definition extreme_values.h:387
const Assumptions & assums() const
Definition extreme_values.h:366
BoundAnalysis & loose()
Definition extreme_values.h:369
Map from symbols to their assumptions.
Definition assumptions.h:313
Computes bounds of symbolic expressions using assumptions.
Definition extreme_values.h:110
Expression lower_bound(const Expression &expr)
Convenience: compute only the lower bound.
BoundAnalysis(const SymbolSet &parameters, const Assumptions &assumptions, bool use_tight_assumptions, int64_t budget=DEFAULT_BOUND_BUDGET, bool project_constraints=true)
Interval bound(const Expression &expr)
Compute both lower and upper bounds of an expression.
Expression upper_bound(const Expression &expr)
Convenience: compute only the upper bound.
SymEngine::RCP< const SymEngine::Basic > Expression
Reference-counted pointer to a symbolic expression (SymEngine Basic).
Definition symbolic.h:83
std::set< Symbol, SymEngine::RCPBasicKeyLess > SymbolSet
Ordered set of symbols.
Definition symbolic.h:95
std::vector< Symbol > SymbolVec
Vector of symbols.
Definition symbolic.h:92
Definition assumptions.h:55
Expression maximum(const Expression expr, const SymbolSet &parameters, const Assumptions &assumptions, bool tight)
Compute the maximum of an expression.
constexpr OptionKey< int64_t > BOUND_BUDGET
Definition extreme_values.h:57
bool is_ge(const Expression &a, const Expression &b, const SymbolSet &parameters, const Assumptions &assumptions, bool tight=false)
Prove a >= b.
bool is_gt(const Expression &a, const Expression &b, const SymbolSet &parameters, const Assumptions &assumptions, bool tight=false)
Prove a > b.
bool is_nonneg(const Expression &expr, const SymbolSet &parameters, const Assumptions &assumptions, bool tight=false)
Prove expr >= 0.
std::unordered_map< Symbol, Expression, SymEngine::RCPBasicHash, SymEngine::RCPBasicKeyEq > AffineCoeffs
Map from symbols to their coefficients in an affine expression.
Definition polynomials.h:48
constexpr int64_t DEFAULT_BOUND_BUDGET
Definition extreme_values.h:49
bool is_eq(const Expression &a, const Expression &b, const SymbolSet &parameters, const Assumptions &assumptions, bool tight=false)
Prove a == b (both a >= b and a <= b).
bool is_lt(const Expression &a, const Expression &b, const SymbolSet &parameters, const Assumptions &assumptions, bool tight=false)
Prove a < b.
bool is_le(const Expression &a, const Expression &b, const SymbolSet &parameters, const Assumptions &assumptions, bool tight=false)
Prove a <= b.
constexpr int64_t DELINEARIZATION_PROOF_BUDGET
Definition extreme_values.h:53
Expression minimum(const Expression expr, const SymbolSet &parameters, const Assumptions &assumptions, bool tight)
Compute the minimum of an expression.
SymEngine::RCP< const SymEngine::MExprPoly > Polynomial
Type representing a SymEngine polynomial expression.
Definition polynomials.h:45
bool is_nonpos(const Expression &expr, const SymbolSet &parameters, const Assumptions &assumptions, bool tight=false)
Prove expr <= 0.
bool is_positive(const Expression &expr, const SymbolSet &parameters, const Assumptions &assumptions, bool tight=false)
Prove expr > 0.
bool is_negative(const Expression &expr, const SymbolSet &parameters, const Assumptions &assumptions, bool tight=false)
Prove expr < 0.
Definition analysis.h:7
Polynomial representation and manipulation.
Definition options.h:40
Definition delinearization.h:12
Result of bounding an expression: an interval [lower, upper].
Definition extreme_values.h:65
bool failed() const
Returns true if both bounds failed.
Definition extreme_values.h:70
static Interval exact(const Expression &expr)
Creates an exact interval [expr, expr] for a constant or parameter.
Definition extreme_values.h:85
bool has_lower() const
Returns true if the lower bound was computed.
Definition extreme_values.h:75
Expression upper
Definition extreme_values.h:67
bool has_upper() const
Returns true if the upper bound was computed.
Definition extreme_values.h:80
Expression lower
Definition extreme_values.h:66
static Interval failure()
Creates a failure result.
Definition extreme_values.h:90
Symbolic system for integer-based symbol analysis.