LCOV - code coverage report
Current view: top level - buildbot/coverage/build/src/theory/ff - parse.cpp (source / functions) Hit Total Coverage
Test: coverage.info Lines: 146 155 94.2 %
Date: 2026-02-16 11:40:47 Functions: 17 18 94.4 %
Branches: 101 127 79.5 %

           Branch data     Line data    Source code
       1                 :            : /******************************************************************************
       2                 :            :  * Top contributors (to current version):
       3                 :            :  *   Alex Ozdemir, Daniel Larraz
       4                 :            :  *
       5                 :            :  * This file is part of the cvc5 project.
       6                 :            :  *
       7                 :            :  * Copyright (c) 2009-2025 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                 :            :  * ****************************************************************************
      12                 :            :  *
      13                 :            :  * parsing structured field terms
      14                 :            :  *
      15                 :            :  * NB: many functions in this file return an empty std::optional if parsing
      16                 :            :  * fails.
      17                 :            :  */
      18                 :            : 
      19                 :            : #include "theory/ff/parse.h"
      20                 :            : 
      21                 :            : // external includes
      22                 :            : 
      23                 :            : // std includes
      24                 :            : #include <numeric>
      25                 :            : #include <optional>
      26                 :            : #include <unordered_map>
      27                 :            : #include <unordered_set>
      28                 :            : 
      29                 :            : // internal includes
      30                 :            : #include "theory/ff/util.h"
      31                 :            : #include "theory/theory.h"
      32                 :            : 
      33                 :            : namespace cvc5::internal {
      34                 :            : namespace theory {
      35                 :            : namespace ff {
      36                 :            : namespace parse {
      37                 :            : 
      38                 :            : // fwd declarations for Spectrum
      39                 :            : namespace {
      40                 :            : 
      41                 :            : /**
      42                 :            :  * Characterization of a univariate, degree <= 2 polynomial.
      43                 :            :  * The members uniquely determine such a polynomial, up to its leading
      44                 :            :  * coefficient.
      45                 :            :  *
      46                 :            :  * This is a helper class for parsing terms that constrain a variable to be: 0,
      47                 :            :  * 1, or both.
      48                 :            :  */
      49                 :            : struct Spectrum
      50                 :            : {
      51                 :            :   /** the variable; ignore if degree is 0 */
      52                 :            :   Node var;
      53                 :            :   /** the degree in {0, 1, 2} */
      54                 :            :   uint8_t degree;
      55                 :            :   /** value at 0 */
      56                 :            :   FiniteFieldValue val0;
      57                 :            :   /** value at 1 */
      58                 :            :   FiniteFieldValue val1;
      59                 :            : };
      60                 :            : 
      61                 :            : using SpectrumOpt = std::optional<Spectrum>;
      62                 :            : 
      63                 :            : /**
      64                 :            :  * Attempt to compute a Spectrum for the polynomial defined by t.
      65                 :            :  *
      66                 :            :  * @param t a field term
      67                 :            :  * @param depth how deep to search in term before giving up; reducing the depth
      68                 :            :  *              reduces the runtime of this function, but increases the number
      69                 :            :  *              of terms for which this function returns an empty optional.
      70                 :            :  *              depth 5 is enough to parse all the patterns I've seen for
      71                 :            :  *              bit constraints and similar.
      72                 :            :  * @return none if t is too deep or mulitvariate or has degreee > 2; otherwise,
      73                 :            :  * a Spectrum.
      74                 :            :  */
      75                 :            : SpectrumOpt spectrum(const Node& t, uint8_t depth = 5);
      76                 :            : 
      77                 :            : }  // namespace
      78                 :            : 
      79                 :          0 : bool zeroConstraint(const Node& fact)
      80                 :            : {
      81                 :          0 :   SpectrumOpt r = spectrum(fact);
      82                 :          0 :   return r.has_value() && r->degree == 1 && r->val0.getValue() == 0;
      83                 :            : }
      84                 :            : 
      85                 :         12 : bool oneConstraint(const Node& fact)
      86                 :            : {
      87                 :         12 :   SpectrumOpt r = spectrum(fact);
      88                 :         24 :   return r.has_value() && r->degree == 1 && r->val1.getValue() == 0;
      89                 :            : }
      90                 :            : 
      91                 :      11610 : std::optional<Node> bitConstraint(const Node& fact)
      92                 :            : {
      93                 :      23220 :   SpectrumOpt r = spectrum(fact);
      94 [ +  + ][ +  + ]:      16196 :   if (r.has_value() && r->degree == 2 && r->val0.getValue() == 0
         [ +  + ][ -  - ]
      95 [ +  + ][ +  + ]:      16196 :       && r->val1.getValue() == 0)
         [ +  + ][ +  + ]
                 [ -  - ]
      96                 :            :   {
      97                 :        705 :     return {r->var};
      98                 :            :   }
      99                 :      10905 :   return {};
     100                 :            : }
     101                 :            : 
     102                 :            : namespace {
     103                 :            : 
     104                 :            : /**
     105                 :            :  * Given spectra for f and g, compute an (optional) spectrum for f @ g, where @
     106                 :            :  * is point-wise operation
     107                 :            :  *
     108                 :            :  * @param a spectrum for f
     109                 :            :  * @param b spectrum for g
     110                 :            :  * @param dOp binary operator on uint8_t for computing the degree of f @ g
     111                 :            :  * @param fOp the binary point-wise operator @
     112                 :            :  *
     113                 :            :  * */
     114                 :            : template <typename DegreeOp, typename FieldOp>
     115                 :      13485 : SpectrumOpt spectrumOp(SpectrumOpt&& a,
     116                 :            :                        SpectrumOpt&& b,
     117                 :            :                        DegreeOp dOp,
     118                 :            :                        FieldOp fOp)
     119                 :            : {
     120 [ +  + ][ +  + ]:      13485 :   if (!(a.has_value() && b.has_value()))
                 [ +  + ]
     121                 :            :   {
     122                 :            :     // failed child
     123                 :       3502 :     return {};
     124                 :            :   }
     125 [ +  + ][ +  + ]:       9983 :   if (a->degree && b->degree && a->var != b->var)
         [ +  + ][ +  + ]
     126                 :            :   {
     127                 :            :     // multivariate
     128                 :       1855 :     return {};
     129                 :            :   }
     130                 :       8128 :   uint8_t degree = dOp(a->degree, b->degree);
     131         [ +  + ]:       8128 :   if (degree > 2)
     132                 :            :   {
     133                 :            :     // high-degree
     134                 :         13 :     return {};
     135                 :            :   }
     136                 :      16230 :   FiniteFieldValue val0 = fOp(a->val0, b->val0);
     137                 :       8115 :   FiniteFieldValue val1 = fOp(a->val1, b->val1);
     138         [ +  + ]:       8115 :   Node&& var = a->degree ? std::move(a->var) : std::move(b->var);
     139                 :       8115 :   return {{var, degree, val0, val1}};
     140                 :            : };
     141                 :            : 
     142                 :      36322 : SpectrumOpt spectrum(const Node& t, uint8_t depth)
     143                 :            : {
     144         [ +  + ]:      36322 :   if (t.getKind() == Kind::NOT)
     145                 :            :   {
     146                 :       5563 :     return {};
     147                 :            :   }
     148                 :      30759 :   Assert(t.getType().isFiniteField() || t.getKind() == Kind::EQUAL) << t;
     149         [ +  + ]:      30759 :   if (isFfLeaf(t))
     150                 :            :   {
     151         [ +  + ]:      19083 :     if (t.isConst())
     152                 :            :     {
     153                 :            :       // this is a constant
     154                 :       7453 :       FiniteFieldValue v = t.getConst<FiniteFieldValue>();
     155                 :       7453 :       return {{Node::null(), 0, v, v}};
     156                 :            :     }
     157                 :            : 
     158                 :            :     // this is an (ff) variable
     159                 :      11630 :     const Integer modulus = t.getType().getFfSize();
     160                 :      11630 :     return {{t, 1, {1, modulus}, {0, modulus}}};
     161                 :            :   }
     162 [ +  + ][ +  + ]:      11676 :   switch (t.getKind())
                    [ - ]
     163                 :            :   {
     164                 :       2392 :     case Kind::FINITE_FIELD_ADD:
     165                 :            :     {
     166                 :       4784 :       SpectrumOpt acc = spectrum(t[0], depth - 1);
     167         [ +  + ]:       6312 :       for (size_t i = 1, n = t.getNumChildren(); i < n; ++i)
     168                 :            :       {
     169                 :       7840 :         acc = spectrumOp(
     170                 :       3920 :             std::move(acc),
     171                 :       7840 :             spectrum(t[i], depth - 1),
     172                 :       1225 :             [](const uint8_t& x, const uint8_t& y) { return std::max(x, y); },
     173                 :       2450 :             [](const FiniteFieldValue& x, const FiniteFieldValue& y) {
     174                 :       2450 :               return x + y;
     175                 :       3920 :             });
     176                 :            :       }
     177                 :       2392 :       return acc;
     178                 :            :     }
     179                 :       6059 :     case Kind::EQUAL:
     180                 :            :     {
     181                 :            :       return spectrumOp(
     182                 :      12118 :           spectrum(t[0], depth - 1),
     183                 :      12118 :           spectrum(t[1], depth - 1),
     184                 :       4586 :           [](const uint8_t& x, const uint8_t& y) { return std::max(x, y); },
     185                 :       9172 :           [](const FiniteFieldValue& x, const FiniteFieldValue& y) {
     186                 :       9172 :             return x - y;
     187                 :       6059 :           });
     188                 :            :     }
     189                 :       2764 :     case Kind::FINITE_FIELD_MULT:
     190                 :            :     {
     191                 :       5528 :       SpectrumOpt acc = spectrum(t[0], depth - 1);
     192         [ +  + ]:       6270 :       for (size_t i = 1, n = t.getNumChildren(); i < n; ++i)
     193                 :            :       {
     194                 :       7012 :         acc = spectrumOp(
     195                 :       3506 :             std::move(acc),
     196                 :       7012 :             spectrum(t[i], depth - 1),
     197                 :       2317 :             [](const uint8_t& x, const uint8_t& y) { return x + y; },
     198                 :       4608 :             [](const FiniteFieldValue& x, const FiniteFieldValue& y) {
     199                 :       4608 :               return x * y;
     200                 :       3506 :             });
     201                 :            :       }
     202                 :       2764 :       return acc;
     203                 :            :     }
     204                 :        461 :     case Kind::FINITE_FIELD_BITSUM:
     205                 :            :     {
     206                 :            :       // give up
     207                 :        461 :       return {};
     208                 :            :     }
     209                 :          0 :     default: Unreachable() << t.getKind();
     210                 :            :   }
     211                 :            :   return {};
     212                 :            : }
     213                 :            : 
     214                 :            : }  // namespace
     215                 :            : 
     216                 :        443 : std::optional<std::pair<Node, FiniteFieldValue>> linearMonomial(const Node& t)
     217                 :            : {
     218                 :        886 :   TypeNode ty = t.getType();
     219         [ -  + ]:        443 :   if (!ty.isFiniteField())
     220                 :            :   {
     221                 :          0 :     return {};
     222                 :            :   }
     223                 :        443 :   const Integer& p = ty.getFfSize();
     224                 :            : 
     225                 :            :   // X
     226         [ +  + ]:        443 :   if (t.isVar())
     227                 :            :   {
     228                 :        100 :     return {{t, FiniteFieldValue(1, p)}};
     229                 :            :   }
     230                 :            : 
     231                 :            :   // (ff.neg X)
     232 [ +  + ][ +  - ]:        343 :   if (t.getKind() == Kind::FINITE_FIELD_NEG && t[0].isVar())
         [ +  + ][ +  + ]
                 [ -  - ]
     233                 :            :   {
     234                 :          2 :     return {{t[0], FiniteFieldValue(-1, p)}};
     235                 :            :   }
     236                 :            : 
     237                 :            :   // (ff.mul ? ?)
     238 [ +  + ][ +  + ]:        341 :   if (t.getKind() == Kind::FINITE_FIELD_MULT && t.getNumChildren() == 2)
                 [ +  + ]
     239                 :            :   {
     240                 :            :     // (ff.mul k X)
     241                 :        143 :     if (t[0].isConst() && t[1].isVar())
     242                 :            :     {
     243                 :         77 :       return {{t[1], t[0].getConst<FiniteFieldValue>()}};
     244                 :            :     }
     245                 :            : 
     246                 :            :     // (ff.mul X k)
     247                 :         66 :     if (t[1].isConst() && t[0].isVar())
     248                 :            :     {
     249                 :          1 :       return {{t[0], t[1].getConst<FiniteFieldValue>()}};
     250                 :            :     }
     251                 :            :   }
     252                 :            : 
     253                 :        263 :   return {};
     254                 :            : }
     255                 :            : 
     256                 :            : std::optional<std::pair<std::vector<std::pair<Node, FiniteFieldValue>>,
     257                 :            :                         std::vector<Node>>>
     258                 :        151 : extractLinearMonomials(const Node& t)
     259                 :            : {
     260                 :        302 :   TypeNode ty = t.getType();
     261         [ -  + ]:        151 :   if (!ty.isFiniteField())
     262                 :            :   {
     263                 :          0 :     return {};
     264                 :            :   }
     265         [ -  + ]:        151 :   if (t.getKind() != Kind::FINITE_FIELD_ADD)
     266                 :            :   {
     267                 :          0 :     return {};
     268                 :            :   }
     269                 :        302 :   std::vector<std::pair<Node, FiniteFieldValue>> monomials{};
     270                 :        151 :   std::vector<Node> otherSummands{};
     271                 :            : 
     272         [ +  + ]:        589 :   for (const Node& summand : t)
     273                 :            :   {
     274                 :        876 :     auto monomial = linearMonomial(summand);
     275         [ +  + ]:        438 :     if (monomial.has_value())
     276                 :            :     {
     277                 :        176 :       monomials.push_back(std::move(monomial.value()));
     278                 :            :     }
     279                 :            :     else
     280                 :            :     {
     281                 :        262 :       otherSummands.push_back(summand);
     282                 :            :     }
     283                 :            :   }
     284                 :            : 
     285                 :        151 :   return {{std::move(monomials), std::move(otherSummands)}};
     286                 :            : }
     287                 :            : 
     288                 :            : std::optional<
     289                 :            :     std::pair<std::vector<std::pair<FiniteFieldValue, std::vector<Node>>>,
     290                 :            :               std::vector<Node>>>
     291                 :        148 : bitSums(const Node& t, std::unordered_set<Node> bits)
     292                 :            : {
     293                 :            :   // 1. get linear monomials
     294                 :        296 :   auto res = extractLinearMonomials(t);
     295         [ -  + ]:        148 :   if (!res.has_value())
     296                 :            :   {
     297                 :          0 :     return {};
     298                 :            :   }
     299                 :        148 :   auto& [monomials, rest] = res.value();
     300         [ +  + ]:        148 :   if (monomials.empty())
     301                 :            :   {
     302                 :         79 :     return {};
     303                 :            :   }
     304                 :            : 
     305         [ +  - ]:         69 :   Trace("ff::parse::debug") << "bitSums start " << t << std::endl;
     306                 :            : 
     307                 :            :   // we'll need to build some monomials
     308                 :         69 :   auto nm = t.getNodeManager();
     309                 :        258 :   auto mkMonomial = [&nm](TNode n, const FiniteFieldValue& coeff) {
     310                 :        129 :     return nm->mkNode(Kind::FINITE_FIELD_MULT, nm->mkConst(coeff), n);
     311                 :         69 :   };
     312                 :            : 
     313                 :            :   // 2. choose a subset of monomials w/ unique coefficients
     314                 :            :   // (preferring monomials with vars in bits)
     315                 :            : 
     316                 :            :   // that subset, as a (coeff -> var) map
     317                 :            :   // later, we'll iterate over monomials by coeff; this map will give the var
     318                 :            :   std::unordered_map<FiniteFieldValue, Node, FiniteFieldValueHashFunction>
     319                 :        138 :       bitMonomials{};
     320                 :            :   // that subset, as a priority_queue over (-abs(signed_int(coeff)), coeff)
     321                 :            :   // later, we'll iterate over monomials in the coeff order given here
     322                 :        138 :   std::priority_queue<std::pair<Integer, FiniteFieldValue>> q{};
     323                 :            : 
     324         [ +  + ]:        242 :   for (const auto& [var, coeff] : monomials)
     325                 :            :   {
     326                 :            :     // iterate over monomials: (var, coeff) pairs
     327         [ +  - ]:        346 :     Trace("ff::parse::debug")
     328                 :        173 :         << "bitMonomial " << coeff << " " << var << std::endl;
     329                 :            : 
     330                 :        173 :     auto it = bitMonomials.find(coeff);
     331         [ +  + ]:        173 :     if (it == bitMonomials.end())
     332                 :            :     {
     333                 :            :       // new coefficient
     334         [ +  - ]:        103 :       Trace("ff::parse::debug") << " fresh bit" << var << std::endl;
     335                 :        103 :       bitMonomials.insert(it, {coeff, var});
     336                 :        103 :       q.emplace(-coeff.toSignedInteger().abs(), coeff);
     337                 :            :     }
     338 [ +  + ][ +  + ]:         70 :     else if (bits.count(var) && !bits.count(it->second))
                 [ +  + ]
     339                 :            :     {
     340                 :            :       // old coefficient, bit var, & the old var is not a bit; evict it.
     341         [ +  - ]:          6 :       Trace("ff::parse::debug")
     342                 :          3 :           << " " << var << " evicts " << it->second << std::endl;
     343                 :          3 :       rest.push_back(mkMonomial(it->second, coeff));
     344                 :          3 :       it->second = var;
     345                 :            :     }
     346                 :            :     else
     347                 :            :     {
     348                 :            :       // skip
     349         [ +  - ]:        134 :       Trace("ff::parse::debug")
     350                 :         67 :           << " skipped " << coeff << " " << var << std::endl;
     351         [ +  - ]:        134 :       Trace("ff::parse::debug")
     352                 :         67 :           << "  isBit: " << std::boolalpha << !!bits.count(var) << std::endl;
     353                 :         67 :       rest.push_back(mkMonomial(var, coeff));
     354                 :            :     }
     355                 :            :   }
     356                 :            : 
     357                 :            :   // 3. extact bitsums
     358                 :            : 
     359                 :        138 :   std::vector<std::pair<FiniteFieldValue, std::vector<Node>>> bitSums{};
     360                 :        138 :   FiniteFieldValue two(2, t.getType().getFfSize());
     361                 :            :   // choose a starting constant k; we'll search for a run: k*x, 2k*y, 4k*z, ...
     362                 :            :   // we choose k from the priority queue (i.e., k with least abs(k) goes first)
     363         [ +  + ]:        172 :   while (!q.empty())
     364                 :            :   {
     365                 :        206 :     FiniteFieldValue k = q.top().second;
     366                 :        103 :     q.pop();
     367                 :            : 
     368                 :            :     // a list of variables x, y, z, ... in the discovered run
     369                 :        206 :     std::vector<Node> bsBits{};
     370                 :            : 
     371                 :            :     // search for a run: k*x, 2k*y, 4k*z, ...
     372                 :        206 :     FiniteFieldValue coeff = k;
     373         [ +  + ]:        206 :     while (bitMonomials.count(coeff))
     374                 :            :     {
     375                 :        206 :       auto var = bitMonomials.at(coeff);
     376                 :        103 :       bsBits.push_back(var);
     377                 :        103 :       bitMonomials.erase(coeff);
     378                 :        103 :       coeff *= two;
     379                 :            :     }
     380                 :            : 
     381         [ +  + ]:        103 :     if (bsBits.size() > 1)
     382                 :            :     {
     383                 :            :       // save as a bitsum if the length is greater than 1
     384                 :         16 :       bitSums.emplace_back(k, std::move(bsBits));
     385                 :            :     }
     386         [ +  + ]:         87 :     else if (bsBits.size() == 1)
     387                 :            :     {
     388                 :            :       // if one bit, add its summand to rest
     389                 :         59 :       rest.push_back(mkMonomial(bsBits[0], coeff / two));
     390                 :            :     }
     391                 :            :   }
     392                 :            : 
     393 [ -  + ][ -  + ]:         69 :   Assert(bitMonomials.empty());
                 [ -  - ]
     394 [ -  + ][ -  + ]:         69 :   Assert(q.empty());
                 [ -  - ]
     395                 :            : 
     396                 :         69 :   return {{std::move(bitSums), std::move(rest)}};
     397                 :            : }
     398                 :            : 
     399                 :     459379 : std::optional<Node> disjunctiveBitConstraint(const Node& t)
     400                 :            : {
     401         [ +  + ]:     146999 :   if (t.getKind() == Kind::OR && t.getNumChildren() == 2
     402                 :     528192 :       && t[0].getKind() == Kind::EQUAL && t[1].getKind() == Kind::EQUAL
     403                 :     606378 :       && t[0][1].getType().isFiniteField() && t[1][0].getType().isFiniteField())
     404                 :            :   {
     405                 :            :     using theory::ff::parse::oneConstraint;
     406                 :            :     using theory::ff::parse::zeroConstraint;
     407                 :          6 :     if ((oneConstraint(t[0]) && zeroConstraint(t[1]))
     408                 :          6 :         || (oneConstraint(t[1]) && zeroConstraint(t[0])))
     409                 :            :     {
     410                 :          0 :       return {theory::ff::parse::spectrum(t[0])->var};
     411                 :            :     }
     412                 :            :   }
     413                 :     459379 :   return {};
     414                 :            : }
     415                 :            : 
     416                 :            : }  // namespace parse
     417                 :            : }  // namespace ff
     418                 :            : }  // namespace theory
     419                 :            : }  // namespace cvc5::internal

Generated by: LCOV version 1.14