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: 169 174 97.1 %
Date: 2026-04-21 10:32:34 Functions: 18 18 100.0 %
Branches: 118 165 71.5 %

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

Generated by: LCOV version 1.14