Closed nunoplopes closed 4 years ago
@NikolajBjorner Christoph found out that just changing the seed makes the first query flip from sat (smt.random_seed=2) to unsat (smt.random_seed=1). Also, using "(check-sat-using (then fpa2bv ufbv))" always gives unsat regardless of seed. Could it be a bug in the smt_context?
bv2fpa_converter inserts an interpretation for fp.to_sbv. Under that interpretation, the negation of the formula under the quantifier becomes unsat in the current model. Then it determines that the quantified formula is satisfiable. @wintersteiger: consider line 467 in bv2fpa_converter.cpp for the purpose of model-based quantifier instantiation. Is it sound to fix an arbitrary interpretation for fp.to_sbv?
No, it probably isn't, thanks for the pointer!
@wintersteiger: it would be good to address this. I see that fpa2bv_converter inserts an uninterpreted function and this gets passed to the bv2fpa_converter.
The "to_ieee" function seems to be internal, not part of the smtlib2 formalism on http://smtlib.cs.uiowa.edu/theories-FloatingPoint.shtml, or at least I could not spot it there.
The to_sbv function is said to be under-specified among certain arguments in the notes
"fp.to_* functions are unspecified for NaN and infinity input values.
In addition, fp.to_ubv and fp.to_sbv are unspecified for finite number inputs
that are out of range (which includes all negative numbers for fp.to_ubv).
Thus, it may be required to do something along option 2. The implementation uses dynamically generated uninterpreted functions, so it isn't necessary to enrich the signature with uninterpreted versions of these functions, but for model building to make sense we should map fp.to_sbv to an adequate ite, it seems. Having two copes for OP_FPA_TO_FP, OP_FPA_TO_FP_UNSIGNED, OP_FPA_TO_UBV, OP_FPA_TO_SBV, OP_FPA_TO_REAL,
introduces 5 new operators with identical rewriting and bit-blasting behavior as their original counter-parts. What are the cases where this kind of underspecification applies and, the most useful question, could you make this surgery as you know how to navigate the semantics?
ref ce06cd0d7a09fd95c7631332d13c825cfce4b88a
alas, a simpler solution seems available: pass in the expression built around the uninterpreted function to the model converter, use this for defining the original function and also include the uninterpreted helper function.
FWIW, my understanding of "to_ieee" is that it returns the internal BV representation as-is. This is so that we can have the round-trip of a float->BV->float to be a no-op. None of the other functions allows this. So this "to_ieee" should be the easiest to fix hopefully. (btw the rewriter doesn't seem to fold these round-trips though)
Even the simpler fix requires some extensive work. The function to_fp functions assume that its arguments are of a special form, something like (fp.tosbv (rm xx) (fp sgn exp sig)). The functions rm and fp seem not to have inverses (yet). If they had inverse functions, then we could access the values of xx, sgn, exp and sig when passing arbitrary two arguments, including variables. I have outlined a way to create a generic unfolding for the fp.to.. functions, but it cannot be completed without a way of dealing with the nested arguments. I presume fp function is a bijection so we could define projection functions. Perhaps using partial inverses for fp.bv2rm is fine as long as the model construction guarantees that the arguments to fp.to_sbv are in the right form. @wintersteiger, could you take a look at either take over or comment?
Yes, I'll fix this, but I have zero time at the moment, so this will take a while.
to_ieee
is a Z3-only function with no guarantees whatsoever. to_sbv
and many others are unspecified for some inputs, so the "else" case in the models should not be added in the context of MBQI (is model completion on during that step?). The fpa2bv conversion assumes that all arguments of a function are already translated to bit-vectors, usually in the form of three bit-vectors and often a rounding mode too.
The initial idea for quantified formulas was to just translate all (quantified) floats to (quantified) bit-vectors and then run the smt
tactic, but later I added a proper FP theory which does the conversion when things become relevant and I never properly tested that in the presence of quantifiers.
This query is UNSAT with E-matching (correct) and SAT with MBQI (wrong):
(declare-fun undef_2 () (_ FloatingPoint 8 24))
(declare-fun blks_val () (Array (_ BitVec 80) (_ BitVec 85)))
(assert (forall ((undef_1 (_ FloatingPoint 8 24)))
(let ((a!1 (forall ((|#idx0| (_ BitVec 80)))
(= ((_ extract 84 84) (select blks_val |#idx0|)) #b0)))
(a!2 (fp.to_ieee_bv (fp.neg ((_ to_fp 8 24) (fp.to_ieee_bv undef_1)))))
(a!3 (fp.to_ieee_bv (fp.neg ((_ to_fp 8 24) (fp.to_ieee_bv undef_2))))))
(let ((a!4 (not (= ((_ to_fp 8 24) a!3) ((_ to_fp 8 24) a!2)))))
(and a!1 a!4)))))
(check-sat)
Output:
$ z3 bug.smt2
unsat
$ z3 smt.ematching=false bug.smt2
sat
@NikolajBjorner: fp
is not a bijection (multiple representations of NaN) and neither is bv2rm (unspecified cases). What's the purpose of inverses? The reverse translation from bit-vectors back to floats is in bv2fp_converter
, which is used in the fpa2bv_model_converter
and I don't think we need more than that, do we?
Aside: I don't seem to be able to disable MBQI, e.g. with
(set-option :smt.ematching false)
(set-option :smt.mbqi true)
it still runs it instead of returning unknown.
also need to disable smt.auto_config to disable mbqi.
What's the purpose of inverses?
Only an indirect purpose: we can define functions f(x,y) when the arguments are variables, but not f(rm(z), fp(u,v,w)). The auxiliary fresh functions that are introduced work when the arguments have this special form. If rm and fp are not surjective, then it can be ambiguous what is z, u, v, w if f can only be defined in terms of 'x' and 'y'. This is the impasse I reached when attempting to register a function definition of the form:
f(x, y) := ite(c1,..., ite(c2, ..., ite(c3, f_fresh(x, y))))
The idea would be to synthesize the model for f(x,y) using this definition.
The current conversion code is written such that every ground instance of f(rm(t1), fp(t2, t3, t4)) is translated to an extended definition. The function f_fresh is cached so that it is recycled across different t1, t2, t3, t4 arguments. Model construction for f would somehow need to reflect the equation for f, as far as I understand. Of course, I could very well be off somewhere in the reading of the intent.
Fix is now merged!
@NikolajBjorner: Your evaluate
, get_macro
and my evaluate_partial_theory_func
in model_evaluator
seem to do similar things, perhaps those could be simplified?
See this:
Prints:
The first check gives
sat
, while the second givesunsat
. The value assertedfor%fb
is its model, which shouldn't change the satisfiability of the formula.