mirror of
https://github.com/Z3Prover/z3
synced 2026-07-21 22:45:51 +00:00
Add constant-bound contradiction shortcut for string < constraints in theory_seq (#10112)
The reported case showed expensive reasoning for lexicographic string
comparisons under the default sequence solver, and incorrect handling
expectations with `z3str3` (which does not interpret these comparisons).
This change targets the default solver path by short-circuiting
contradictory constant-bound `<` constraints earlier.
- **Theory shortcut for constant lexical bounds**
- In `theory_seq::assign_eh`, detect asserted `str.<` constraints of the
form `c < x` or `x < c` where `c` is a string constant.
- When a complementary bound on the same equivalence class is already
true, check bound consistency immediately.
- If bounds are contradictory (`!(lower < upper)`), emit a direct theory
conflict from the two active literals instead of waiting for deeper
axiom propagation.
- **Preserve existing comparison reasoning**
- Existing `check_lts` transitivity/axiom flow is retained.
- The new logic is a narrow fast path for contradictory constant bounds
and does not alter general string-order semantics.
- **Regression coverage**
- Added a solver-level regression in `src/test/seq_rewriter.cpp` for
contradictory date-like lexical bounds to ensure this class of
constraints is rejected as `unsat`.
```cpp
ctx.assert_expr(su.str.mk_lex_lt(su.str.mk_string("2024-01-01"), x));
ctx.assert_expr(su.str.mk_lex_lt(x, su.str.mk_string("2024-12-31")));
ctx.assert_expr(su.str.mk_lex_lt(x, su.str.mk_string("2023-01-01")));
ENSURE(ctx.check() == l_false);
```
---------
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
This commit is contained in:
parent
038b367d68
commit
710b4082d1
2 changed files with 91 additions and 0 deletions
|
|
@ -696,6 +696,14 @@ bool theory_seq::check_lts() {
|
|||
|
||||
literal eq = (b == c) ? true_literal : mk_eq(b, c, false);
|
||||
bool is_strict = is_strict1 || is_strict2;
|
||||
zstring bound_a, bound_d;
|
||||
if (m_util.str.is_string(a, bound_a) && m_util.str.is_string(d, bound_d)) {
|
||||
bool ok = is_strict ? bound_a < bound_d : !(bound_d < bound_a);
|
||||
if (!ok) {
|
||||
add_axiom(~r1, ~r2, ~eq);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (is_strict) {
|
||||
add_axiom(~r1, ~r2, ~eq, mk_literal(m_util.str.mk_lex_lt(a, d)));
|
||||
}
|
||||
|
|
@ -3155,6 +3163,70 @@ void theory_seq::assign_eh(bool_var v, bool is_true) {
|
|||
}
|
||||
else if (m_util.str.is_lt(e) || m_util.str.is_le(e)) {
|
||||
m_lts.push_back(e);
|
||||
expr* a = nullptr, *b = nullptr;
|
||||
zstring bound;
|
||||
bool is_lower = false;
|
||||
expr* x = nullptr;
|
||||
if (is_true && m_util.str.is_lt(e, a, b)) {
|
||||
// is_lower=true encodes c < x (c is a lower bound on x)
|
||||
// is_lower=false encodes x < c (c is an upper bound on x)
|
||||
if (m_util.str.is_string(a, bound) && !m_util.str.is_string(b)) {
|
||||
is_lower = true;
|
||||
x = b;
|
||||
}
|
||||
else if (!m_util.str.is_string(a) && m_util.str.is_string(b, bound)) {
|
||||
is_lower = false;
|
||||
x = a;
|
||||
}
|
||||
}
|
||||
if (x && ctx.get_enode(x)) {
|
||||
enode* x_root = ctx.get_enode(x)->get_root();
|
||||
for (expr* p : m_lts) {
|
||||
if (p == e)
|
||||
continue;
|
||||
expr* pa = nullptr, *pb = nullptr;
|
||||
zstring p_bound;
|
||||
bool p_is_lower = false;
|
||||
expr* px = nullptr;
|
||||
if (!m_util.str.is_lt(p, pa, pb))
|
||||
continue;
|
||||
literal p_lit = ctx.get_literal(p);
|
||||
// Skip trivial literals: they are not tracked by a bool variable and
|
||||
// cannot participate in a conflict justification as assumptions.
|
||||
if (p_lit == true_literal || p_lit == false_literal)
|
||||
continue;
|
||||
if (ctx.get_assignment(p_lit) != l_true)
|
||||
continue;
|
||||
if (m_util.str.is_string(pa, p_bound) && !m_util.str.is_string(pb)) {
|
||||
p_is_lower = true;
|
||||
px = pb;
|
||||
}
|
||||
else if (!m_util.str.is_string(pa) && m_util.str.is_string(pb, p_bound)) {
|
||||
p_is_lower = false;
|
||||
px = pa;
|
||||
}
|
||||
if (!px)
|
||||
continue;
|
||||
if (p_is_lower == is_lower)
|
||||
continue;
|
||||
if (!ctx.get_enode(px))
|
||||
continue;
|
||||
if (ctx.get_enode(px)->get_root() != x_root)
|
||||
continue;
|
||||
zstring const& lower = is_lower ? bound : p_bound;
|
||||
zstring const& upper = is_lower ? p_bound : bound;
|
||||
if (!(lower < upper)) {
|
||||
literal_vector lits;
|
||||
// set_conflict expects currently true assumptions.
|
||||
// `lit` is true because assign_eh was invoked with is_true,
|
||||
// and `p_lit` is filtered above to assignment l_true.
|
||||
lits.push_back(lit);
|
||||
lits.push_back(p_lit);
|
||||
set_conflict(nullptr, lits);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (m_util.str.is_nth_i(e) || m_util.str.is_nth_u(e)) {
|
||||
// no-op
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue