LCOV - code coverage report
Current view: top level - buildbot/coverage/build/src/theory/quantifiers - oracle_engine.cpp (source / functions) Hit Total Coverage
Test: coverage.info Lines: 141 170 82.9 %
Date: 2026-04-18 10:29:03 Functions: 12 14 85.7 %
Branches: 73 146 50.0 %

           Branch data     Line data    Source code
       1                 :            : /******************************************************************************
       2                 :            :  * This file is part of the cvc5 project.
       3                 :            :  *
       4                 :            :  * Copyright (c) 2009-2026 by the authors listed in the file AUTHORS
       5                 :            :  * in the top-level source directory and their institutional affiliations.
       6                 :            :  * All rights reserved.  See the file COPYING in the top-level source
       7                 :            :  * directory for licensing information.
       8                 :            :  * ****************************************************************************
       9                 :            :  *
      10                 :            :  * Oracle engine
      11                 :            :  */
      12                 :            : 
      13                 :            : #include "theory/quantifiers/oracle_engine.h"
      14                 :            : 
      15                 :            : #include "expr/attribute.h"
      16                 :            : #include "expr/skolem_manager.h"
      17                 :            : #include "options/quantifiers_options.h"
      18                 :            : #include "theory/decision_manager.h"
      19                 :            : #include "theory/quantifiers/first_order_model.h"
      20                 :            : #include "theory/quantifiers/quantifiers_attributes.h"
      21                 :            : #include "theory/quantifiers/quantifiers_inference_manager.h"
      22                 :            : #include "theory/quantifiers/quantifiers_registry.h"
      23                 :            : #include "theory/quantifiers/term_registry.h"
      24                 :            : #include "theory/quantifiers/term_tuple_enumerator.h"
      25                 :            : #include "theory/trust_substitutions.h"
      26                 :            : 
      27                 :            : using namespace cvc5::internal::kind;
      28                 :            : using namespace cvc5::context;
      29                 :            : 
      30                 :            : namespace cvc5::internal {
      31                 :            : namespace theory {
      32                 :            : namespace quantifiers {
      33                 :            : 
      34                 :            : /** Attribute true for input variables */
      35                 :            : struct OracleInputVarAttributeId
      36                 :            : {
      37                 :            : };
      38                 :            : typedef expr::Attribute<OracleInputVarAttributeId, bool>
      39                 :            :     OracleInputVarAttribute;
      40                 :            : /** Attribute true for output variables */
      41                 :            : struct OracleOutputVarAttributeId
      42                 :            : {
      43                 :            : };
      44                 :            : typedef expr::Attribute<OracleOutputVarAttributeId, bool>
      45                 :            :     OracleOutputVarAttribute;
      46                 :            : 
      47                 :        535 : OracleEngine::OracleEngine(Env& env,
      48                 :            :                            QuantifiersState& qs,
      49                 :            :                            QuantifiersInferenceManager& qim,
      50                 :            :                            QuantifiersRegistry& qr,
      51                 :        535 :                            TermRegistry& tr)
      52                 :            :     : QuantifiersModule(env, qs, qim, qr, tr),
      53                 :        535 :       d_oracleFuns(userContext()),
      54                 :        535 :       d_ochecker(env.getOracleChecker()),
      55                 :        535 :       d_consistencyCheckPassed(false),
      56                 :       1070 :       d_dstrat(env, "OracleArgValue", qs.getValuation())
      57                 :            : {
      58 [ -  + ][ -  + ]:        535 :   Assert(d_ochecker != nullptr);
                 [ -  - ]
      59                 :        535 : }
      60                 :            : 
      61                 :        326 : void OracleEngine::presolve()
      62                 :            : {
      63                 :            :   // Ensure all oracle functions in top-level substitutions occur in
      64                 :            :   // lemmas. Otherwise the oracles will not be invoked for those values
      65                 :            :   // and the model will be inaccurate.
      66                 :            :   std::unordered_map<Node, Node> subs =
      67                 :        326 :       d_env.getTopLevelSubstitutions().get().getSubstitutions();
      68                 :        326 :   std::unordered_set<Node> visited;
      69                 :        326 :   std::vector<TNode> visit;
      70         [ -  + ]:        326 :   for (const std::pair<const Node, Node>& s : subs)
      71                 :            :   {
      72                 :          0 :     visit.push_back(s.second);
      73                 :            :   }
      74                 :        326 :   TNode cur;
      75         [ -  + ]:        326 :   while (!visit.empty())
      76                 :            :   {
      77                 :          0 :     cur = visit.back();
      78                 :          0 :     visit.pop_back();
      79         [ -  - ]:          0 :     if (visited.find(cur) == visited.end())
      80                 :            :     {
      81                 :          0 :       visited.insert(cur);
      82         [ -  - ]:          0 :       if (OracleCaller::isOracleFunctionApp(cur))
      83                 :            :       {
      84                 :          0 :         Node k = SkolemManager::mkPurifySkolem(cur);
      85                 :          0 :         Node eq = k.eqNode(cur);
      86                 :          0 :         d_qim.lemma(eq, InferenceId::QUANTIFIERS_ORACLE_PURIFY_SUBS);
      87                 :          0 :       }
      88         [ -  - ]:          0 :       if (cur.getNumChildren() > 0)
      89                 :            :       {
      90                 :          0 :         visit.insert(visit.end(), cur.begin(), cur.end());
      91                 :            :       }
      92                 :            :     }
      93                 :            :   }
      94                 :            :   // register the decision strategy which will insist that arguments are
      95                 :            :   // decided to be equal to values.
      96                 :        326 :   d_qim.getDecisionManager()->registerStrategy(
      97                 :            :       DecisionManager::STRAT_ORACLE_ARG_VALUE,
      98                 :            :       &d_dstrat,
      99                 :            :       DecisionManager::STRAT_SCOPE_LOCAL_SOLVE);
     100                 :        326 : }
     101                 :            : 
     102                 :       2690 : bool OracleEngine::needsCheck(Theory::Effort e)
     103                 :            : {
     104                 :       2690 :   return e == Theory::Effort::EFFORT_LAST_CALL;
     105                 :            : }
     106                 :            : 
     107                 :            : // the model is built at this effort level
     108                 :       1182 : OracleEngine::QEffort OracleEngine::needsModel(CVC5_UNUSED Theory::Effort e)
     109                 :            : {
     110                 :       1182 :   return QEFFORT_MODEL;
     111                 :            : }
     112                 :            : 
     113                 :       2364 : void OracleEngine::reset_round(CVC5_UNUSED Theory::Effort e)
     114                 :            : {
     115                 :       2364 :   d_consistencyCheckPassed = false;
     116                 :       2364 : }
     117                 :            : 
     118                 :        326 : void OracleEngine::registerQuantifier(CVC5_UNUSED Node q) {}
     119                 :            : 
     120                 :       3546 : void OracleEngine::check(CVC5_UNUSED Theory::Effort e, QEffort quant_e)
     121                 :            : {
     122         [ +  + ]:       3546 :   if (quant_e != QEFFORT_MODEL)
     123                 :            :   {
     124                 :       2364 :     return;
     125                 :            :   }
     126                 :            : 
     127                 :       1182 :   FirstOrderModel* fm = d_treg.getModel();
     128                 :       1182 :   TermDb* termDatabase = d_treg.getTermDatabase();
     129                 :       1182 :   NodeManager* nm = nodeManager();
     130                 :       1182 :   unsigned nquant = fm->getNumAssertedQuantifiers();
     131                 :       1182 :   std::vector<Node> currInterfaces;
     132         [ +  + ]:       2364 :   for (unsigned i = 0; i < nquant; i++)
     133                 :            :   {
     134                 :       1182 :     Node q = fm->getAssertedQuantifier(i);
     135         [ -  + ]:       1182 :     if (d_qreg.getOwner(q) != this)
     136                 :            :     {
     137                 :          0 :       continue;
     138                 :            :     }
     139                 :       1182 :     currInterfaces.push_back(q);
     140         [ +  - ]:       1182 :   }
     141 [ -  + ][ -  - ]:       1182 :   if (d_oracleFuns.empty() && currInterfaces.empty())
                 [ -  + ]
     142                 :            :   {
     143                 :          0 :     return;
     144                 :            :   }
     145                 :       1182 :   beginCallDebug();
     146                 :            :   // Note that we currently ignore oracle interface quantified formulas, and
     147                 :            :   // look directly at the oracle functions. Note that:
     148                 :            :   // (1) The lemmas with InferenceId QUANTIFIERS_ORACLE_INTERFACE are not
     149                 :            :   // guarded by a quantified formula. This means that we are assuming that all
     150                 :            :   // oracle interface quantified formulas are top-level assertions. This is
     151                 :            :   // correct because we do not expose a way of embedding oracle interfaces into
     152                 :            :   // formulas at the user level.
     153                 :            :   // (2) We assume that oracle functions have associated oracle interface
     154                 :            :   // quantified formulas that are in currInterfaces.
     155                 :            :   // (3) We currently ignore oracle interface quantified formulas that are
     156                 :            :   // not associated with oracle functions.
     157                 :            :   //
     158                 :            :   // The current design choices above are due to the fact that our support is
     159                 :            :   // limited to "definitional SMTO" (see Polgreen et al 2022). In particular,
     160                 :            :   // we only support oracles that define I/O equalities for oracle functions
     161                 :            :   // only. The net effect of this class hence is to check the consistency of
     162                 :            :   // oracle functions, and allow "sat" or otherwise add a lemma with id
     163                 :            :   // QUANTIFIERS_ORACLE_INTERFACE.
     164                 :       1182 :   std::vector<Node> learnedLemmas;
     165                 :       1182 :   bool allFappsConsistent = true;
     166                 :            :   // iterate over oracle functions
     167         [ +  + ]:       2362 :   for (const Node& f : d_oracleFuns)
     168                 :            :   {
     169                 :       1182 :     TNodeTrie* tat = termDatabase->getTermArgTrie(f);
     170         [ -  + ]:       1182 :     if (!tat)
     171                 :            :     {
     172                 :          0 :       continue;
     173                 :            :     }
     174                 :       2364 :     std::vector<Node> apps = tat->getLeaves(f.getType().getArgTypes().size());
     175         [ +  - ]:       2364 :     Trace("oracle-calls") << "Oracle fun " << f << " with " << apps.size()
     176                 :       1182 :                           << " applications." << std::endl;
     177         [ +  + ]:       2362 :     for (const auto& fapp : apps)
     178                 :            :     {
     179                 :       1182 :       std::vector<Node> arguments;
     180                 :       1182 :       arguments.push_back(f);
     181                 :            :       // evaluate arguments
     182         [ +  + ]:       2578 :       for (const auto& arg : fapp)
     183                 :            :       {
     184                 :       1396 :         arguments.push_back(fm->getValue(arg));
     185                 :       1396 :       }
     186                 :            :       // call oracle
     187                 :       1182 :       Node fappWithValues = nm->mkNode(Kind::APPLY_UF, arguments);
     188                 :       1182 :       Node predictedResponse = fm->getValue(fapp);
     189                 :            :       Node result =
     190                 :       2366 :           d_ochecker->checkConsistent(fappWithValues, predictedResponse);
     191         [ +  + ]:       1180 :       if (!result.isNull())
     192                 :            :       {
     193                 :            :         // Note that we add (=> (= args values) (= (f args) result))
     194                 :            :         // instead of (= (f values) result) here. The latter may be more
     195                 :            :         // compact, but we require introducing literals for (= args values)
     196                 :            :         // so that they can be preferred by the decision strategy.
     197                 :        966 :         std::vector<Node> disj;
     198                 :       1932 :         Node conc = nm->mkNode(Kind::EQUAL, fapp, result);
     199                 :        966 :         disj.push_back(conc);
     200         [ +  + ]:       2039 :         for (size_t i = 0, nchild = fapp.getNumChildren(); i < nchild; i++)
     201                 :            :         {
     202                 :       1073 :           Node eqa = fapp[i].eqNode(arguments[i + 1]);
     203                 :       1073 :           eqa = rewrite(eqa);
     204                 :            :           // Insist that the decision strategy tries to make (= args values)
     205                 :            :           // true first. This is to ensure that the value of the oracle can be
     206                 :            :           // used.
     207                 :       1073 :           d_dstrat.addLiteral(eqa);
     208                 :       1073 :           disj.push_back(eqa.notNode());
     209                 :       1073 :         }
     210                 :        966 :         Node lem = nm->mkOr(disj);
     211                 :        966 :         learnedLemmas.push_back(lem);
     212                 :        966 :         allFappsConsistent = false;
     213                 :        966 :       }
     214                 :       1186 :     }
     215                 :       1182 :   }
     216                 :            :   // if all were consistent, we can terminate
     217         [ +  + ]:       1180 :   if (allFappsConsistent)
     218                 :            :   {
     219         [ +  - ]:        428 :     Trace("oracle-engine-state")
     220                 :        214 :         << "All responses consistent, no lemmas added" << std::endl;
     221                 :        214 :     d_consistencyCheckPassed = true;
     222                 :            :   }
     223                 :            :   else
     224                 :            :   {
     225         [ +  + ]:       1932 :     for (const Node& l : learnedLemmas)
     226                 :            :     {
     227         [ +  - ]:        966 :       Trace("oracle-engine-state") << "adding lemma " << l << std::endl;
     228                 :        966 :       d_qim.lemma(l, InferenceId::QUANTIFIERS_ORACLE_INTERFACE);
     229                 :            :     }
     230                 :            :   }
     231                 :            :   // general SMTO: call constraint generators and assumption generators here
     232                 :            : 
     233                 :       1180 :   endCallDebug();
     234         [ +  - ]:       1184 : }
     235                 :            : 
     236                 :        214 : bool OracleEngine::checkCompleteFor(Node q)
     237                 :            : {
     238         [ -  + ]:        214 :   if (d_qreg.getOwner(q) != this)
     239                 :            :   {
     240                 :          0 :     return false;
     241                 :            :   }
     242                 :            :   // Only true if oracle consistency check was successful. Notice that
     243                 :            :   // we can say true for *all* oracle interface quantified formulas in the
     244                 :            :   // case that the consistency check passed. In particular, the invocation
     245                 :            :   // of oracle interfaces does not need to be complete.
     246                 :        214 :   return d_consistencyCheckPassed;
     247                 :            : }
     248                 :            : 
     249                 :        326 : void OracleEngine::checkOwnership(Node q)
     250                 :            : {
     251                 :            :   // take ownership of quantified formulas that are oracle interfaces
     252                 :        326 :   QuantAttributes& qa = d_qreg.getQuantAttributes();
     253         [ -  + ]:        326 :   if (!qa.isOracleInterface(q))
     254                 :            :   {
     255                 :          0 :     return;
     256                 :            :   }
     257                 :        326 :   d_qreg.setOwner(q, this);
     258                 :            :   // We expect oracle interfaces to be limited to definitional SMTO currently.
     259         [ +  - ]:        326 :   if (Configuration::isAssertionBuild())
     260                 :            :   {
     261                 :        326 :     std::vector<Node> inputs, outputs;
     262                 :        326 :     Node assume, constraint, oracle;
     263         [ -  + ]:        326 :     if (!getOracleInterface(q, inputs, outputs, assume, constraint, oracle))
     264                 :            :     {
     265                 :          0 :       DebugUnhandled() << "Not an oracle interface " << q;
     266                 :            :     }
     267                 :            :     else
     268                 :            :     {
     269                 :        326 :       Assert(outputs.size() == 1) << "Unhandled oracle constraint " << q;
     270                 :        326 :       Assert(constraint.isConst() && constraint.getConst<bool>())
     271                 :          0 :           << "Unhandled oracle constraint " << q;
     272                 :            :     }
     273                 :        326 :     CVC5_UNUSED bool isOracleFun = false;
     274         [ +  - ]:        326 :     if (assume.getKind() == Kind::EQUAL)
     275                 :            :     {
     276         [ +  + ]:        978 :       for (size_t i = 0; i < 2; i++)
     277                 :            :       {
     278 [ +  + ][ -  - ]:        652 :         if (OracleCaller::isOracleFunctionApp(assume[i])
     279 [ +  + ][ +  - ]:        652 :             && assume[1 - i] == outputs[0])
         [ +  + ][ +  - ]
                 [ -  - ]
     280                 :            :         {
     281                 :        326 :           isOracleFun = true;
     282                 :            :         }
     283                 :            :       }
     284                 :            :     }
     285                 :        326 :     Assert(isOracleFun)
     286                 :          0 :         << "Non-definitional oracle interface quantified formula " << q;
     287                 :        326 :   }
     288                 :            : }
     289                 :            : 
     290                 :          0 : std::string OracleEngine::identify() const
     291                 :            : {
     292                 :          0 :   return std::string("OracleEngine");
     293                 :            : }
     294                 :            : 
     295                 :        535 : void OracleEngine::declareOracleFun(Node f) { d_oracleFuns.push_back(f); }
     296                 :            : 
     297                 :          0 : std::vector<Node> OracleEngine::getOracleFuns() const
     298                 :            : {
     299                 :          0 :   std::vector<Node> ofuns;
     300         [ -  - ]:          0 :   for (const Node& f : d_oracleFuns)
     301                 :            :   {
     302                 :          0 :     ofuns.push_back(f);
     303                 :            :   }
     304                 :          0 :   return ofuns;
     305                 :          0 : }
     306                 :            : 
     307                 :        535 : Node OracleEngine::mkOracleInterface(const std::vector<Node>& inputs,
     308                 :            :                                      const std::vector<Node>& outputs,
     309                 :            :                                      Node assume,
     310                 :            :                                      Node constraint,
     311                 :            :                                      Node oracleNode)
     312                 :            : {
     313 [ -  + ][ -  + ]:        535 :   Assert(!assume.isNull());
                 [ -  - ]
     314 [ -  + ][ -  + ]:        535 :   Assert(!constraint.isNull());
                 [ -  - ]
     315 [ -  + ][ -  + ]:        535 :   Assert(oracleNode.getKind() == Kind::ORACLE);
                 [ -  - ]
     316                 :        535 :   NodeManager* nm = oracleNode.getNodeManager();
     317                 :            :   Node ipl = nm->mkNode(Kind::INST_PATTERN_LIST,
     318                 :       1070 :                         nm->mkNode(Kind::INST_ATTRIBUTE, oracleNode));
     319                 :        535 :   std::vector<Node> vars;
     320                 :            :   OracleInputVarAttribute oiva;
     321         [ +  + ]:       1280 :   for (Node v : inputs)
     322                 :            :   {
     323                 :        745 :     v.setAttribute(oiva, true);
     324                 :        745 :     vars.push_back(v);
     325                 :        745 :   }
     326                 :            :   OracleOutputVarAttribute oova;
     327         [ +  + ]:       1070 :   for (Node v : outputs)
     328                 :            :   {
     329                 :        535 :     v.setAttribute(oova, true);
     330                 :        535 :     vars.push_back(v);
     331                 :        535 :   }
     332                 :        535 :   Node bvl = nm->mkNode(Kind::BOUND_VAR_LIST, vars);
     333                 :       1070 :   Node body = nm->mkNode(Kind::ORACLE_FORMULA_GEN, assume, constraint);
     334                 :       1070 :   return nm->mkNode(Kind::FORALL, bvl, body, ipl);
     335                 :        535 : }
     336                 :            : 
     337                 :        326 : bool OracleEngine::getOracleInterface(Node q,
     338                 :            :                                       std::vector<Node>& inputs,
     339                 :            :                                       std::vector<Node>& outputs,
     340                 :            :                                       Node& assume,
     341                 :            :                                       Node& constraint,
     342                 :            :                                       Node& oracleNode) const
     343                 :            : {
     344                 :        326 :   QuantAttributes& qa = d_qreg.getQuantAttributes();
     345         [ +  - ]:        326 :   if (qa.isOracleInterface(q))
     346                 :            :   {
     347                 :            :     // fill in data
     348                 :            :     OracleInputVarAttribute oiva;
     349         [ +  + ]:       1085 :     for (const Node& v : q[0])
     350                 :            :     {
     351         [ +  + ]:        759 :       if (v.getAttribute(oiva))
     352                 :            :       {
     353                 :        433 :         inputs.push_back(v);
     354                 :            :       }
     355                 :            :       else
     356                 :            :       {
     357 [ -  + ][ -  + ]:        326 :         Assert(v.getAttribute(OracleOutputVarAttribute()));
                 [ -  - ]
     358                 :        326 :         outputs.push_back(v);
     359                 :            :       }
     360                 :       1085 :     }
     361 [ -  + ][ -  + ]:        326 :     Assert(q[1].getKind() == Kind::ORACLE_FORMULA_GEN);
                 [ -  - ]
     362                 :        326 :     assume = q[1][0];
     363                 :        326 :     constraint = q[1][1];
     364 [ -  + ][ -  + ]:        326 :     Assert(q.getNumChildren() == 3);
                 [ -  - ]
     365 [ -  + ][ -  + ]:        326 :     Assert(q[2].getNumChildren() == 1);
                 [ -  - ]
     366 [ -  + ][ -  + ]:        326 :     Assert(q[2][0].getNumChildren() == 1);
                 [ -  - ]
     367 [ -  + ][ -  + ]:        326 :     Assert(q[2][0][0].getKind() == Kind::ORACLE);
                 [ -  - ]
     368                 :        326 :     oracleNode = q[2][0][0];
     369                 :        326 :     return true;
     370                 :            :   }
     371                 :          0 :   return false;
     372                 :            : }
     373                 :            : 
     374                 :            : }  // namespace quantifiers
     375                 :            : }  // namespace theory
     376                 :            : }  // namespace cvc5::internal

Generated by: LCOV version 1.14