Skip to content

Commit 0ab0f2d

Browse files
authored
Add elaboration for MACRO_STR_EQ_LEN_UNIFY_PREFIX (cvc5#11658)
This adds proof elaboration for MACRO_STR_EQ_LEN_UNIFY_PREFIX. It does not require any new rules, apart from a RARE rewrite for turning a dual implicaition into an equality.
1 parent 348845e commit 0ab0f2d

File tree

5 files changed

+248
-0
lines changed

5 files changed

+248
-0
lines changed

include/cvc5/cvc5_proof_rule.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3636,6 +3636,8 @@ enum ENUM(ProofRewriteRule)
36363636
EVALUE(BOOL_IMPL_TRUE2),
36373637
/** Auto-generated from RARE rule bool-impl-elim */
36383638
EVALUE(BOOL_IMPL_ELIM),
3639+
/** Auto-generated from RARE rule bool-dual-impl-eq */
3640+
EVALUE(BOOL_DUAL_IMPL_EQ),
36393641
/** Auto-generated from RARE rule bool-or-true */
36403642
EVALUE(BOOL_OR_TRUE),
36413643
/** Auto-generated from RARE rule bool-or-flatten */

proofs/eo/cpc/rules/Rewrites.eo

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -291,6 +291,10 @@
291291
:args (t1 s1)
292292
:conclusion (= (=> t1 s1) (or (not t1) s1))
293293
)
294+
(declare-rule bool-dual-impl-eq ((t1 Bool) (s1 Bool))
295+
:args (t1 s1)
296+
:conclusion (= (and (=> t1 s1) (=> s1 t1)) (= t1 s1))
297+
)
294298
(declare-rule bool-or-true ((xs1 Bool :list) (ys1 Bool :list))
295299
:args (xs1 ys1)
296300
:conclusion (= ($singleton_elim (or xs1 true ys1)) true)

src/rewriter/basic_rewrite_rcons.cpp

Lines changed: 229 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -224,6 +224,12 @@ void BasicRewriteRCons::ensureProofForTheoryRewrite(CDProof* cdp,
224224
handledMacro = true;
225225
}
226226
break;
227+
case ProofRewriteRule::MACRO_STR_EQ_LEN_UNIFY_PREFIX:
228+
if (ensureProofMacroStrEqLenUnifyPrefix(cdp, eq))
229+
{
230+
handledMacro = true;
231+
}
232+
break;
227233
case ProofRewriteRule::MACRO_STR_EQ_LEN_UNIFY:
228234
if (ensureProofMacroStrEqLenUnify(cdp, eq))
229235
{
@@ -926,6 +932,229 @@ bool BasicRewriteRCons::ensureProofMacroSubstrStripSymLength(CDProof* cdp,
926932
return true;
927933
}
928934

935+
936+
bool BasicRewriteRCons::ensureProofMacroStrEqLenUnifyPrefix(CDProof* cdp,
937+
const Node& eq)
938+
{
939+
Trace("brc-macro") << "Expand macro str eq len unify prefix " << eq
940+
<< std::endl;
941+
NodeManager* nm = nodeManager();
942+
theory::strings::ArithEntail ae(nullptr);
943+
theory::strings::StringsEntail sent(nullptr, ae);
944+
945+
Assert(eq[1].getKind() == Kind::AND);
946+
Node eq1p = eq[1];
947+
// get the equations equal empty
948+
// we group (and (= s t) (= t1 "") ... (= tn "")) to
949+
// (and (= s t) (and (= t1 "") ... (= tn ""))) and store in eq1p.
950+
std::vector<Node> empeqs;
951+
if (eq[1].getNumChildren() > 2)
952+
{
953+
empeqs.insert(empeqs.end(), eq[1].begin() + 1, eq[1].end());
954+
Node eq1g = nm->mkAnd(empeqs);
955+
eq1p = nm->mkNode(Kind::AND, eq[1][0], eq1g);
956+
Node eqg = eq1p.eqNode(eq[1]);
957+
cdp->addStep(eqg, ProofRule::ACI_NORM, {}, {eqg});
958+
}
959+
else
960+
{
961+
empeqs.push_back(eq[1][1]);
962+
}
963+
964+
// prove eq[0] => eq1p in cdfwd
965+
CDProof cdfwd(d_env);
966+
Node eqsrc = eq[0];
967+
Node ret = sent.inferEqsFromContains(eqsrc[0], eqsrc[1]);
968+
Trace("brc-macro") << "[1] setup forward implication" << std::endl;
969+
bool eqFlipped = false;
970+
if (ret.isNull())
971+
{
972+
Trace("brc-macro") << "...failed " << ret << ", try flip" << std::endl;
973+
eqsrc = eq[0][1].eqNode(eq[0][0]);
974+
ret = sent.inferEqsFromContains(eqsrc[0], eqsrc[1]);
975+
if (ret.isNull())
976+
{
977+
Trace("brc-macro") << "... failed to replicate " << ret << std::endl;
978+
return false;
979+
}
980+
eqFlipped = true;
981+
cdfwd.addStep(eqsrc, ProofRule::SYMM, {eq[0]}, {});
982+
}
983+
Node len1 = nm->mkNode(Kind::STRING_LENGTH, eqsrc[0]);
984+
Node len2 = nm->mkNode(Kind::STRING_LENGTH, eqsrc[1]);
985+
Node leneq = proveCong(&cdfwd, len1, {eqsrc});
986+
Node li[2];
987+
std::vector<Node> eqi;
988+
eqi.resize(2);
989+
for (size_t i = 0; i < 2; i++)
990+
{
991+
Node l = i == 0 ? len1 : len2;
992+
TConvProofGenerator tcpg(d_env, nullptr);
993+
li[i] = ae.rewriteLengthIntro(l, &tcpg);
994+
if (li[i] != l)
995+
{
996+
Node equiv = l.eqNode(li[i]);
997+
std::shared_ptr<ProofNode> pfn = tcpg.getProofFor(equiv);
998+
cdfwd.addProof(pfn);
999+
eqi[i] = pfn->getResult();
1000+
}
1001+
}
1002+
Node leneqi = li[0].eqNode(li[1]);
1003+
if (leneqi != leneq)
1004+
{
1005+
Node equiv = proveCong(&cdfwd, leneq, eqi);
1006+
cdfwd.addStep(leneqi, ProofRule::EQ_RESOLVE, {leneq, equiv}, {});
1007+
}
1008+
Trace("brc-macro") << "...length: " << li[0] << " == " << li[1] << std::endl;
1009+
// based on swapping above, we should have len1i >= len2i
1010+
Node diff = nm->mkNode(Kind::SUB, li[1], li[0]);
1011+
Node diffn = theory::arith::PolyNorm::getPolyNorm(diff);
1012+
Trace("brc-macro") << "...norm diff " << diffn << std::endl;
1013+
Node diffneqz = diffn.eqNode(nm->mkConstInt(Rational(0)));
1014+
Node equiv = leneqi.eqNode(diffneqz);
1015+
if (!ensureProofArithPolyNormRel(&cdfwd, equiv))
1016+
{
1017+
Trace("brc-macro") << "... failed poly norm rel" << std::endl;
1018+
return false;
1019+
}
1020+
cdfwd.addStep(diffneqz, ProofRule::EQ_RESOLVE, {leneqi, equiv}, {});
1021+
Trace("brc-macro") << "...have " << diffneqz << std::endl;
1022+
1023+
// get the concatenation term corresponding to the components equated to empty
1024+
std::vector<Node> concat;
1025+
std::map<Node, Node> empMap;
1026+
Assert(eq1p.getKind() == Kind::AND && eq1p.getNumChildren() == 2);
1027+
for (const Node& ee : empeqs)
1028+
{
1029+
Assert(ee.getKind() == Kind::EQUAL && ee[0].getType().isStringLike());
1030+
concat.push_back(ee[0]);
1031+
empMap[ee[0]] = ee;
1032+
}
1033+
TypeNode stype = concat[0].getType();
1034+
Node cc = theory::strings::utils::mkConcat(concat, stype);
1035+
Node lcc = nm->mkNode(Kind::STRING_LENGTH, cc);
1036+
TConvProofGenerator tcpg(d_env, nullptr);
1037+
Node lcci = ae.rewriteLengthIntro(lcc, &tcpg);
1038+
Trace("brc-macro") << "...normalized concat length " << lcci << std::endl;
1039+
Node lcceq = lcc.eqNode(lcci);
1040+
std::shared_ptr<ProofNode> pfn = tcpg.getProofFor(lcceq);
1041+
cdfwd.addProof(pfn);
1042+
1043+
// the length of the empty components should be equal to the difference
1044+
// the length of the equality before and after removing empty components
1045+
Node pnEq = lcci.eqNode(diffneqz[0]);
1046+
if (!cdfwd.addStep(pnEq, ProofRule::ARITH_POLY_NORM, {}, {pnEq}))
1047+
{
1048+
Trace("brc-macro") << "...fail poly norm " << pnEq << std::endl;
1049+
return false;
1050+
}
1051+
Node pnEq2 = lcc.eqNode(diffneqz[1]);
1052+
cdfwd.addStep(pnEq2, ProofRule::TRANS, {lcceq, pnEq, diffneqz}, {});
1053+
// now have proven (str.len (str.++ t1 ... tn)) = 0,
1054+
// need t1 = "" ^ ... ^ tn = ""
1055+
Trace("brc-macro") << "...have " << pnEq2 << std::endl;
1056+
Node eqconv = pnEq2.eqNode(eq1p[1]);
1057+
Trace("brc-macro") << "- subgoal " << eqconv << std::endl;
1058+
// subgoal, which can be filled with RARE rules
1059+
// str-len-eq-zero-concat-rec and str-len-eq-zero-base
1060+
cdfwd.addTrustedStep(eqconv, TrustId::MACRO_THEORY_REWRITE_RCONS, {}, {});
1061+
cdfwd.addStep(eq1p[1], ProofRule::EQ_RESOLVE, {pnEq2, eqconv}, {});
1062+
1063+
// proves (=> (and t="") (= (= (str.++ s t) r) (= s r)))
1064+
CDProof cdmid(d_env);
1065+
Node srcRew = eqsrc[1];
1066+
Trace("brc-macro") << "[2] source term to rewrite " << srcRew << std::endl;
1067+
Assert(srcRew.getKind() == Kind::STRING_CONCAT);
1068+
std::vector<Node> eqe;
1069+
std::map<Node, Node>::iterator it;
1070+
for (const Node& tc : srcRew)
1071+
{
1072+
it = empMap.find(tc);
1073+
if (it != empMap.end())
1074+
{
1075+
eqe.push_back(it->second);
1076+
}
1077+
else
1078+
{
1079+
eqe.push_back(Node::null());
1080+
}
1081+
}
1082+
Node cres = proveCong(&cdmid, srcRew, eqe);
1083+
if (cres.isNull())
1084+
{
1085+
Trace("brc-macro") << "...fail cong" << std::endl;
1086+
return false;
1087+
}
1088+
Trace("brc-macro") << "...cong to " << cres[1] << std::endl;
1089+
Node tgtRew = eq1p[0][eqFlipped ? 0 : 1];
1090+
Trace("brc-macro") << "...target is " << tgtRew << std::endl;
1091+
1092+
Node eqacin = cres[1].eqNode(tgtRew);
1093+
if (!cdmid.addStep(eqacin, ProofRule::ACI_NORM, {}, {eqacin}))
1094+
{
1095+
Trace("brc-macro") << "...fail aci norm" << std::endl;
1096+
return false;
1097+
}
1098+
Trace("brc-macro") << "...proved subs empty " << eqacin << std::endl;
1099+
Node teq = srcRew.eqNode(tgtRew);
1100+
cdmid.addStep(teq, ProofRule::TRANS, {cres, eqacin}, {});
1101+
1102+
Node eqqeq = eq[0].eqNode(eq1p[0]);
1103+
std::vector<Node> eqee;
1104+
for (size_t i = 0; i < 2; i++)
1105+
{
1106+
Node eee = eq[0][i].eqNode(eq1p[0][i]);
1107+
eqee.push_back(eee);
1108+
if (eee[0] == eee[1])
1109+
{
1110+
Node refl = eee[0].eqNode(eee[0]);
1111+
cdmid.addStep(refl, ProofRule::REFL, {}, {eee[0]});
1112+
}
1113+
}
1114+
std::vector<Node> cargs;
1115+
ProofRule cr = expr::getCongRule(eq[0], cargs);
1116+
cdmid.addStep(eqqeq, cr, eqee, cargs);
1117+
Node implMid = nm->mkNode(Kind::IMPLIES, eq1p[1], eqqeq);
1118+
cdmid.addStep(implMid, ProofRule::SCOPE, {eqqeq}, empeqs);
1119+
Trace("brc-macro") << "...intermediate result: " << implMid << std::endl;
1120+
std::shared_ptr<ProofNode> pfmid = cdmid.getProofFor(implMid);
1121+
Assert(implMid[1][0] == eq[0]);
1122+
Assert(implMid[1][1] == eq1p[0]);
1123+
1124+
// finish the forward proof
1125+
cdfwd.addProof(pfmid);
1126+
cdfwd.addStep(implMid[1], ProofRule::MODUS_PONENS, {eq1p[1], implMid}, {});
1127+
cdfwd.addStep(eq1p[0], ProofRule::EQ_RESOLVE, {eq[0], implMid[1]}, {});
1128+
cdfwd.addStep(eq1p, ProofRule::AND_INTRO, {eq1p[0], eq1p[1]}, {});
1129+
Node impl = nm->mkNode(Kind::IMPLIES, eq[0], eq1p);
1130+
cdfwd.addStep(impl, ProofRule::SCOPE, {eq1p}, {eq[0]});
1131+
cdp->addProof(cdfwd.getProofFor(impl));
1132+
1133+
// reverse proof is easy
1134+
CDProof cdrev(d_env);
1135+
cdrev.addProof(pfmid);
1136+
cdrev.addStep(implMid[1], ProofRule::MODUS_PONENS, {eq1p[1], implMid}, {});
1137+
Node equivs = implMid[1][1].eqNode(implMid[1][0]);
1138+
cdrev.addStep(equivs, ProofRule::SYMM, {implMid[1]}, {});
1139+
cdrev.addStep(
1140+
implMid[1][0], ProofRule::EQ_RESOLVE, {implMid[1][1], equivs}, {});
1141+
Node implrev = nm->mkNode(Kind::IMPLIES, eq1p, eq[0]);
1142+
cdrev.addStep(implrev, ProofRule::SCOPE, {eq[0]}, {eq1p[0], eq1p[1]});
1143+
cdp->addProof(cdrev.getProofFor(implrev));
1144+
1145+
// dual implication
1146+
Node eqfinal = proveDualImplication(cdp, impl, implrev);
1147+
1148+
// if we grouped the empty equations, we close with a transitive step
1149+
// which we added via ACI_NORM above
1150+
if (eq1p != eq[1])
1151+
{
1152+
cdp->addStep(eq, ProofRule::TRANS, {eqfinal, eq1p.eqNode(eq[1])}, {});
1153+
}
1154+
1155+
return true;
1156+
}
1157+
9291158
bool BasicRewriteRCons::ensureProofMacroStrEqLenUnify(CDProof* cdp,
9301159
const Node& eq)
9311160
{

src/rewriter/basic_rewrite_rcons.h

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -191,6 +191,16 @@ class BasicRewriteRCons : protected EnvObj
191191
* @return true if added a closed proof of eq to cdp.
192192
*/
193193
bool ensureProofMacroSubstrStripSymLength(CDProof* cdp, const Node& eq);
194+
/**
195+
* Elaborate a rewrite eq that was proven by
196+
* ProofRewriteRule::MACRO_STR_EQ_LEN_UNIFY_PREFIX.
197+
*
198+
* @param cdp The proof to add to.
199+
* @param eq The rewrite proven by
200+
* ProofRewriteRule::MACRO_STR_EQ_LEN_UNIFY_PREFIX.
201+
* @return true if added a closed proof of eq to cdp.
202+
*/
203+
bool ensureProofMacroStrEqLenUnifyPrefix(CDProof* cdp, const Node& eq);
194204
/**
195205
* Elaborate a rewrite eq that was proven by
196206
* ProofRewriteRule::MACRO_STR_EQ_LEN_UNIFY.

src/theory/booleans/rewrites

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,9 @@
1414
(define-rule bool-impl-true2 ((t Bool)) (=> true t) t)
1515
(define-rule bool-impl-elim ((t Bool) (s Bool)) (=> t s) (or (not t) s))
1616

17+
; used in proof elaboration
18+
(define-rule bool-dual-impl-eq ((t Bool) (s Bool)) (and (=> t s) (=> s t)) (= t s))
19+
1720
(define-rule bool-or-true ((xs Bool :list) (ys Bool :list)) (or xs true ys) true)
1821
(define-rule* bool-or-flatten ((xs Bool :list) (b Bool) (ys Bool :list) (zs Bool :list)) (or xs (or b ys) zs) (or xs b ys zs))
1922

0 commit comments

Comments
 (0)