1 /******************************************************************************
2 * Top contributors (to current version):
3 * Andrew Reynolds, Gereon Kremer, Haniel Barbosa
5 * This file is part of the cvc5 project.
7 * Copyright (c) 2009-2022 by the authors listed in the file AUTHORS
8 * in the top-level source directory and their institutional affiliations.
9 * All rights reserved. See the file COPYING in the top-level source
10 * directory for licensing information.
11 * ****************************************************************************
13 * The solver for SyGuS queries.
16 #include "smt/sygus_solver.h"
20 #include "base/modal_exception.h"
21 #include "expr/dtype.h"
22 #include "expr/dtype_cons.h"
23 #include "expr/skolem_manager.h"
24 #include "options/base_options.h"
25 #include "options/option_exception.h"
26 #include "options/quantifiers_options.h"
27 #include "options/smt_options.h"
28 #include "smt/preprocessor.h"
29 #include "smt/smt_solver.h"
30 #include "theory/datatypes/sygus_datatype_utils.h"
31 #include "theory/quantifiers/quantifiers_attributes.h"
32 #include "theory/quantifiers/sygus/sygus_grammar_cons.h"
33 #include "theory/quantifiers/sygus/sygus_utils.h"
34 #include "theory/quantifiers_engine.h"
35 #include "theory/rewriter.h"
36 #include "theory/smt_engine_subsolver.h"
38 using namespace cvc5::internal::theory
;
39 using namespace cvc5::internal::kind
;
41 namespace cvc5::internal
{
44 SygusSolver::SygusSolver(Env
& env
, SmtSolver
& sms
)
47 d_sygusVars(userContext()),
48 d_sygusConstraints(userContext()),
49 d_sygusAssumps(userContext()),
50 d_sygusFunSymbols(userContext()),
51 d_sygusConjectureStale(userContext(), true),
52 d_subsolverCd(userContext(), nullptr)
56 SygusSolver::~SygusSolver() {}
58 void SygusSolver::declareSygusVar(Node var
)
60 Trace("smt") << "SygusSolver::declareSygusVar: " << var
<< " "
61 << var
.getType() << "\n";
62 d_sygusVars
.push_back(var
);
63 // don't need to set that the conjecture is stale
66 void SygusSolver::declareSynthFun(Node fn
,
69 const std::vector
<Node
>& vars
)
71 Trace("smt") << "SygusSolver::declareSynthFun: " << fn
<< "\n";
72 NodeManager
* nm
= NodeManager::currentNM();
73 d_sygusFunSymbols
.push_back(fn
);
76 Node bvl
= nm
->mkNode(BOUND_VAR_LIST
, vars
);
77 // use an attribute to mark its bound variable list
78 SygusSynthFunVarListAttribute ssfvla
;
79 fn
.setAttribute(ssfvla
, bvl
);
81 // whether sygus type encodes syntax restrictions
82 if (!sygusType
.isNull() && sygusType
.isDatatype()
83 && sygusType
.getDType().isSygus())
85 Node sym
= nm
->mkBoundVar("sfproxy", sygusType
);
86 // use an attribute to mark its grammar
87 SygusSynthGrammarAttribute ssfga
;
88 fn
.setAttribute(ssfga
, sym
);
89 // we must expand definitions for sygus operators in the block
90 expandDefinitionsSygusDt(sygusType
);
93 // sygus conjecture is now stale
94 d_sygusConjectureStale
= true;
97 void SygusSolver::assertSygusConstraint(Node n
, bool isAssume
)
99 Trace("smt") << "SygusSolver::assertSygusConstrant: " << n
100 << ", isAssume=" << isAssume
<< "\n";
103 d_sygusAssumps
.push_back(n
);
107 d_sygusConstraints
.push_back(n
);
110 // sygus conjecture is now stale
111 d_sygusConjectureStale
= true;
114 void SygusSolver::assertSygusInvConstraint(Node inv
,
119 Trace("smt") << "SygusSolver::assertSygusInvConstrant: " << inv
<< " " << pre
120 << " " << trans
<< " " << post
<< "\n";
121 // build invariant constraint
123 // get variables (regular and their respective primed versions)
124 std::vector
<Node
> terms
;
125 std::vector
<Node
> vars
;
126 std::vector
<Node
> primed_vars
;
127 terms
.push_back(inv
);
128 terms
.push_back(pre
);
129 terms
.push_back(trans
);
130 terms
.push_back(post
);
131 // variables are built based on the invariant type
132 NodeManager
* nm
= NodeManager::currentNM();
133 std::vector
<TypeNode
> argTypes
= inv
.getType().getArgTypes();
134 for (const TypeNode
& tn
: argTypes
)
136 vars
.push_back(nm
->mkBoundVar(tn
));
137 d_sygusVars
.push_back(vars
.back());
138 std::stringstream ss
;
139 ss
<< vars
.back() << "'";
140 primed_vars
.push_back(nm
->mkBoundVar(ss
.str(), tn
));
141 d_sygusVars
.push_back(primed_vars
.back());
144 // make relevant terms; 0 -> Inv, 1 -> Pre, 2 -> Trans, 3 -> Post
145 for (unsigned i
= 0; i
< 4; ++i
)
148 Trace("smt-debug") << "Make inv-constraint term #" << i
<< " : " << op
149 << " with type " << op
.getType() << "...\n";
150 std::vector
<Node
> children
;
151 children
.push_back(op
);
152 // transition relation applied over both variable lists
155 children
.insert(children
.end(), vars
.begin(), vars
.end());
156 children
.insert(children
.end(), primed_vars
.begin(), primed_vars
.end());
160 children
.insert(children
.end(), vars
.begin(), vars
.end());
162 terms
[i
] = nm
->mkNode(APPLY_UF
, children
);
163 // make application of Inv on primed variables
167 children
.push_back(op
);
168 children
.insert(children
.end(), primed_vars
.begin(), primed_vars
.end());
169 terms
.push_back(nm
->mkNode(APPLY_UF
, children
));
173 std::vector
<Node
> conj
;
174 conj
.push_back(nm
->mkNode(IMPLIES
, terms
[1], terms
[0]));
175 Node term0_and_2
= nm
->mkNode(AND
, terms
[0], terms
[2]);
176 conj
.push_back(nm
->mkNode(IMPLIES
, term0_and_2
, terms
[4]));
177 conj
.push_back(nm
->mkNode(IMPLIES
, terms
[0], terms
[3]));
178 Node constraint
= nm
->mkNode(AND
, conj
);
180 d_sygusConstraints
.push_back(constraint
);
182 // sygus conjecture is now stale
183 d_sygusConjectureStale
= true;
186 SynthResult
SygusSolver::checkSynth(Assertions
& as
, bool isNext
)
188 Trace("smt") << "SygusSolver::checkSynth" << std::endl
;
189 // if applicable, check if the subsolver is the correct one
192 // if we are not using check-synth-next, we always reconstruct the solver.
193 d_sygusConjectureStale
= true;
195 if (usingSygusSubsolver() && d_subsolverCd
.get() != d_subsolver
.get())
197 // this can occur if we backtrack to a place where we were using a different
198 // subsolver than the current one. In this case, we should reconstruct
200 d_sygusConjectureStale
= true;
202 if (d_sygusConjectureStale
)
204 NodeManager
* nm
= NodeManager::currentNM();
205 // build synthesis conjecture from asserted constraints and declared
206 // variables/functions
207 Trace("smt") << "Sygus : Constructing sygus constraint...\n";
208 Node body
= nm
->mkAnd(listToVector(d_sygusConstraints
));
209 // note that if there are no constraints, then assumptions are irrelevant
210 if (!d_sygusConstraints
.empty() && !d_sygusAssumps
.empty())
212 Node bodyAssump
= nm
->mkAnd(listToVector(d_sygusAssumps
));
213 body
= nm
->mkNode(IMPLIES
, bodyAssump
, body
);
215 body
= body
.notNode();
216 Trace("smt") << "...constructed sygus constraint " << body
<< std::endl
;
217 if (!d_sygusVars
.empty())
219 Node boundVars
= nm
->mkNode(BOUND_VAR_LIST
, listToVector(d_sygusVars
));
220 body
= nm
->mkNode(EXISTS
, boundVars
, body
);
221 Trace("smt") << "...constructed exists " << body
<< std::endl
;
223 if (!d_sygusFunSymbols
.empty())
225 body
= quantifiers::SygusUtils::mkSygusConjecture(
226 listToVector(d_sygusFunSymbols
), body
);
228 Trace("smt") << "...constructed forall " << body
<< std::endl
;
230 Trace("smt") << "Check synthesis conjecture: " << body
<< std::endl
;
232 d_sygusConjectureStale
= false;
236 // if we are using a subsolver, initialize it now
237 if (usingSygusSubsolver())
239 // we generate a new solver engine to do the SyGuS query
240 initializeSygusSubsolver(d_subsolver
, as
);
242 // store the pointer (context-dependent)
243 d_subsolverCd
= d_subsolver
.get();
245 // also assert the internal SyGuS conjecture
246 d_subsolver
->assertFormula(d_conj
);
251 Assert(d_subsolver
!= nullptr);
254 if (usingSygusSubsolver())
256 r
= d_subsolver
->checkSat();
260 std::vector
<Node
> query
;
261 query
.push_back(d_conj
);
262 r
= d_smtSolver
.checkSatisfiability(as
, query
);
264 // The result returned by the above call is typically "unknown", which may
265 // or may not correspond to a state in which we solved the conjecture
266 // successfully. Instead we call getSynthSolutions below. If this returns
267 // true, then we were successful. In this case, we set the synthesis result to
270 // This behavior is done for 2 reasons:
271 // (1) if we do not negate the synthesis conjecture, the subsolver in some
272 // cases cannot answer "sat", e.g. in the presence of recursive function
273 // definitions. Instead the SyGuS language standard itself indicates that
274 // a correct solution for a conjecture is one where the synthesis conjecture
275 // is *T-valid* (in the presence of defined recursive functions). In other
276 // words, a SyGuS query asks to prove that the conjecture is valid when
277 // witnessed by the given solution.
278 // (2) we do not want the solver to explicitly answer "unsat" by giving an
279 // unsatisfiable set of formulas to the underlying PropEngine, or otherwise
280 // we will not be able to ask for further solutions. This is critical for
281 // incremental solving where multiple solutions are returned for the same
282 // set of constraints. Thus, the internal SyGuS solver will mark unknown
283 // with IncompleteId::QUANTIFIERS_SYGUS_SOLVED. Furthermore, this id may be
284 // overwritten by other means of incompleteness, so we cannot rely on this
285 // identifier being the final reason for unknown.
287 // Thus, we use getSynthSolutions as means of knowing the conjecture was
290 std::map
<Node
, Node
> sol_map
;
291 if (getSynthSolutions(sol_map
))
293 // if we have solutions, we return "solution"
294 sr
= SynthResult(SynthResult::SOLUTION
);
295 // Check that synthesis solutions satisfy the conjecture
296 if (options().smt
.checkSynthSol
)
298 checkSynthSolution(as
, sol_map
);
303 // otherwise, we return "unknown"
304 sr
= SynthResult(SynthResult::UNKNOWN
, UnknownExplanation::UNKNOWN_REASON
);
309 bool SygusSolver::getSynthSolutions(std::map
<Node
, Node
>& solMap
)
311 Trace("smt") << "SygusSolver::getSynthSolutions" << std::endl
;
312 if (usingSygusSubsolver())
314 // use the call to get the synth solutions from the subsolver
315 return d_subsolver
->getSubsolverSynthSolutions(solMap
);
317 return getSubsolverSynthSolutions(solMap
);
320 bool SygusSolver::getSubsolverSynthSolutions(std::map
<Node
, Node
>& solMap
)
322 Trace("smt") << "SygusSolver::getSubsolverSynthSolutions" << std::endl
;
323 std::map
<Node
, std::map
<Node
, Node
>> solMapn
;
324 // fail if the theory engine does not have synthesis solutions
325 QuantifiersEngine
* qe
= d_smtSolver
.getQuantifiersEngine();
326 if (qe
== nullptr || !qe
->getSynthSolutions(solMapn
))
330 for (std::pair
<const Node
, std::map
<Node
, Node
>>& cs
: solMapn
)
332 for (std::pair
<const Node
, Node
>& s
: cs
.second
)
334 solMap
[s
.first
] = s
.second
;
340 void SygusSolver::checkSynthSolution(Assertions
& as
,
341 const std::map
<Node
, Node
>& sol_map
)
345 verbose(1) << "SyGuS::checkSynthSolution: checking synthesis solution"
350 InternalError() << "SygusSolver::checkSynthSolution(): Got empty solution!";
353 Trace("check-synth-sol") << "Got solution map:\n";
354 // the set of synthesis conjectures in our assertions
355 std::unordered_set
<Node
> conjs
;
356 conjs
.insert(d_conj
);
357 // For each of the above conjectures, the functions-to-synthesis and their
358 // solutions. This is used as a substitution below.
359 std::vector
<Node
> fvars
;
360 std::vector
<Node
> fsols
;
361 for (const std::pair
<const Node
, Node
>& pair
: sol_map
)
363 Trace("check-synth-sol")
364 << " " << pair
.first
<< " --> " << pair
.second
<< "\n";
365 fvars
.push_back(pair
.first
);
366 fsols
.push_back(pair
.second
);
369 Trace("check-synth-sol") << "Starting new SMT Engine\n";
371 Trace("check-synth-sol") << "Retrieving assertions\n";
372 // Build conjecture from original assertions
373 // check all conjectures
374 for (const Node
& conj
: conjs
)
376 // Start new SMT engine to check solutions
377 std::unique_ptr
<SolverEngine
> solChecker
;
378 initializeSygusSubsolver(solChecker
, as
);
379 solChecker
->getOptions().writeSmt().checkSynthSol
= false;
380 solChecker
->getOptions().writeQuantifiers().sygusRecFun
= false;
381 Assert(conj
.getKind() == FORALL
);
382 Node conjBody
= conj
[1];
383 // we must expand definitions here, since define-fun may contain the
384 // function-to-synthesize, which needs to be substituted.
385 conjBody
= d_smtSolver
.getPreprocessor()->expandDefinitions(conjBody
);
386 // Apply solution map to conjecture body
387 conjBody
= conjBody
.substitute(
388 fvars
.begin(), fvars
.end(), fsols
.begin(), fsols
.end());
392 verbose(1) << "SyGuS::checkSynthSolution: -- body substitutes to "
393 << conjBody
<< std::endl
;
395 Trace("check-synth-sol")
396 << "Substituted body of assertion to " << conjBody
<< "\n";
397 solChecker
->assertFormula(conjBody
);
398 Result r
= solChecker
->checkSat();
401 verbose(1) << "SyGuS::checkSynthSolution: result is " << r
<< std::endl
;
403 Trace("check-synth-sol") << "Satsifiability check: " << r
<< "\n";
406 InternalError() << "SygusSolver::checkSynthSolution(): could not check "
410 else if (r
.getStatus() == Result::SAT
)
413 << "SygusSolver::checkSynthSolution(): produced solution leads to "
414 "satisfiable negated conjecture.";
419 void SygusSolver::initializeSygusSubsolver(std::unique_ptr
<SolverEngine
>& se
,
422 initializeSubsolver(se
, d_env
);
423 // carry the ordinary define-fun definitions
424 const context::CDList
<Node
>& alistDefs
= as
.getAssertionListDefinitions();
425 std::unordered_set
<Node
> processed
;
426 for (const Node
& def
: alistDefs
)
428 // only consider define-fun, represented as (= f (lambda ...)).
429 if (def
.getKind() == EQUAL
)
431 Assert(def
[0].isVar());
432 std::vector
<Node
> formals
;
434 if (def
[1].getKind() == LAMBDA
)
436 formals
.insert(formals
.end(), def
[1][0].begin(), def
[1][0].end());
439 se
->defineFunction(def
[0], formals
, dbody
);
440 processed
.insert(def
);
443 // Also assert auxiliary assertions
444 const context::CDList
<Node
>& alist
= as
.getAssertionList();
445 for (size_t i
= 0, asize
= alist
.size(); i
< asize
; ++i
)
448 // ignore definitions here
449 if (processed
.find(a
) == processed
.end())
451 se
->assertFormula(a
);
456 bool SygusSolver::usingSygusSubsolver() const
458 // use SyGuS subsolver if in incremental mode
459 return options().base
.incrementalSolving
;
462 void SygusSolver::expandDefinitionsSygusDt(TypeNode tn
) const
464 std::unordered_set
<TypeNode
> processed
;
465 std::vector
<TypeNode
> toProcess
;
466 toProcess
.push_back(tn
);
468 while (index
< toProcess
.size())
470 TypeNode tnp
= toProcess
[index
];
472 Assert(tnp
.isDatatype());
473 Assert(tnp
.getDType().isSygus());
474 const std::vector
<std::shared_ptr
<DTypeConstructor
>>& cons
=
475 tnp
.getDType().getConstructors();
476 for (const std::shared_ptr
<DTypeConstructor
>& c
: cons
)
478 Node op
= c
->getSygusOp();
479 // Only expand definitions if the operator is not constant, since
480 // calling expandDefinitions on them should be a no-op. This check
481 // ensures we don't try to expand e.g. bitvector extract operators,
482 // whose type is undefined, and thus should not be passed to
483 // expandDefinitions.
484 Node eop
= op
.isConst()
486 : d_smtSolver
.getPreprocessor()->expandDefinitions(op
);
488 datatypes::utils::setExpandedDefinitionForm(op
, eop
);
489 // also must consider the arguments
490 for (unsigned j
= 0, nargs
= c
->getNumArgs(); j
< nargs
; ++j
)
492 TypeNode tnc
= c
->getArgType(j
);
493 if (tnc
.isDatatype() && tnc
.getDType().isSygus()
494 && processed
.find(tnc
) == processed
.end())
496 toProcess
.push_back(tnc
);
497 processed
.insert(tnc
);
504 std::vector
<Node
> SygusSolver::listToVector(const NodeList
& list
)
506 std::vector
<Node
> vec
;
507 for (const Node
& n
: list
)
515 } // namespace cvc5::internal