Skip to content

Commit 6541d49

Browse files
levineuwirthclaude
andcommitted
R-10: equiv_pos1 / equiv_pos2 (3-literal Boolean tautologies)
Mirror of Lean #50. The two positive-polarity halves of propositional-equivalence reasoning, deferred from the R-7 boolean cluster as nested case-splits. No premises: - equiv_pos1: (cl (not (= a b)) a (not b)) ~(a=b) \/ a \/ ~b - equiv_pos2: (cl (not (= a b)) (not a) b) ~(a=b) \/ ~a \/ b Built by nested [classic] case analysis (outer on a=b, inner on b/a) reusing the R-7/R-8 eq_mp/eq_mpr transports and the or_ind/or_introl/or_intror combinators. A direct port of Lean's nested Or.elim structure to Coq's or_ind. Footprint {classic} -- the eq transports go through axiom-free eq_ind, so no propext is pulled (unlike equiv_simplify, these prove disjunctions not equalities). Both clauses are sanity-checked for matching a/b arguments. Tests + allowlist for both. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent 6083b54 commit 6541d49

4 files changed

Lines changed: 149 additions & 4 deletions

File tree

rocq-bridge/src/alethe_walker.ml

Lines changed: 107 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1174,6 +1174,107 @@ let elab_equiv_simplify (ctx : walker_ctx) (s : Alethe.step)
11741174
raise (Walker_error
11751175
"'equiv_simplify' expects clause (cl (= lhs rhs))")
11761176

1177+
(* =========================================================
1178+
[equiv_pos1] / [equiv_pos2] (R-10): 3-literal Boolean
1179+
tautologies, no premises. The two positive-polarity halves of
1180+
propositional-equivalence reasoning cvc5 emits alongside the
1181+
R-7 boolean cluster (deferred from R-7 as they are nested
1182+
case-splits). Built by nested [classic] case analysis with the
1183+
R-7/R-8 [eq_mp]/[eq_mpr] transports; footprint [{classic}]
1184+
(the transports go through axiom-free [eq_ind]). Mirror of
1185+
Lean #50's [elabEquivPos1]/[elabEquivPos2].
1186+
========================================================= *)
1187+
1188+
(** [equiv_pos1]: clause [(cl (not (= a b)) a (not b))] ≡
1189+
[~(a=b) \/ a \/ ~b]. Nested [classic]: case [a=b]; if not, left
1190+
disjunct. If [a=b], case [b]; if [b], transport to [a] via
1191+
[eq_mpr] for the middle disjunct; if [~b], the right disjunct. *)
1192+
let elab_equiv_pos1 (ctx : walker_ctx) (s : Alethe.step)
1193+
: EConstr.t * Alethe.Sexp.t list =
1194+
match s.clause with
1195+
| [ List [ Atom "not"; List [ Atom "="; a; b ] ]; a';
1196+
List [ Atom "not"; b' ] ] ->
1197+
if a <> a' || b <> b' then
1198+
raise (Walker_error "'equiv_pos1' argument mismatch in clause");
1199+
let a_e = sexp_to_constr ctx a in
1200+
let b_e = sexp_to_constr ctx b in
1201+
let eq_ab = sexp_to_constr ctx (List [ Atom "="; a; b ]) in
1202+
let not_eq = mk_not eq_ab in
1203+
let not_b = mk_not b_e in
1204+
let inner_ty = mk_or a_e not_b in
1205+
let result_ty = mk_or not_eq inner_ty in
1206+
let pos_outer =
1207+
abstract_lam ctx.sigma_ref "eqH" eq_ab (fun eq_h ->
1208+
let pos_inner =
1209+
abstract_lam ctx.sigma_ref "hb" b_e (fun hb ->
1210+
let a_proof = eq_mpr ctx eq_h a_e b_e hb in
1211+
mk_or_intror not_eq inner_ty
1212+
(mk_or_introl a_e not_b a_proof))
1213+
in
1214+
let neg_inner =
1215+
abstract_lam ctx.sigma_ref "hnb" not_b (fun hnb ->
1216+
mk_or_intror not_eq inner_ty
1217+
(mk_or_intror a_e not_b hnb))
1218+
in
1219+
mk_or_ind b_e not_b result_ty pos_inner neg_inner
1220+
(mk_classic b_e))
1221+
in
1222+
let neg_outer =
1223+
abstract_lam ctx.sigma_ref "hne" not_eq (fun hne ->
1224+
mk_or_introl not_eq inner_ty hne)
1225+
in
1226+
(mk_or_ind eq_ab not_eq result_ty pos_outer neg_outer
1227+
(mk_classic eq_ab),
1228+
s.clause)
1229+
| _ ->
1230+
raise (Walker_error
1231+
"'equiv_pos1' expects clause (cl (not (= a b)) a (not b))")
1232+
1233+
(** [equiv_pos2]: clause [(cl (not (= a b)) (not a) b)] ≡
1234+
[~(a=b) \/ ~a \/ b]. Mirror of [equiv_pos1]: if [a=b] and [a]
1235+
holds, transport to [b] via [eq_mp] for the right disjunct; if
1236+
[~a], the middle disjunct. *)
1237+
let elab_equiv_pos2 (ctx : walker_ctx) (s : Alethe.step)
1238+
: EConstr.t * Alethe.Sexp.t list =
1239+
match s.clause with
1240+
| [ List [ Atom "not"; List [ Atom "="; a; b ] ];
1241+
List [ Atom "not"; a' ]; b' ] ->
1242+
if a <> a' || b <> b' then
1243+
raise (Walker_error "'equiv_pos2' argument mismatch in clause");
1244+
let a_e = sexp_to_constr ctx a in
1245+
let b_e = sexp_to_constr ctx b in
1246+
let eq_ab = sexp_to_constr ctx (List [ Atom "="; a; b ]) in
1247+
let not_eq = mk_not eq_ab in
1248+
let not_a = mk_not a_e in
1249+
let inner_ty = mk_or not_a b_e in
1250+
let result_ty = mk_or not_eq inner_ty in
1251+
let pos_outer =
1252+
abstract_lam ctx.sigma_ref "eqH" eq_ab (fun eq_h ->
1253+
let pos_inner =
1254+
abstract_lam ctx.sigma_ref "ha" a_e (fun ha ->
1255+
let b_proof = eq_mp ctx eq_h a_e b_e ha in
1256+
mk_or_intror not_eq inner_ty
1257+
(mk_or_intror not_a b_e b_proof))
1258+
in
1259+
let neg_inner =
1260+
abstract_lam ctx.sigma_ref "hna" not_a (fun hna ->
1261+
mk_or_intror not_eq inner_ty
1262+
(mk_or_introl not_a b_e hna))
1263+
in
1264+
mk_or_ind a_e not_a result_ty pos_inner neg_inner
1265+
(mk_classic a_e))
1266+
in
1267+
let neg_outer =
1268+
abstract_lam ctx.sigma_ref "hne" not_eq (fun hne ->
1269+
mk_or_introl not_eq inner_ty hne)
1270+
in
1271+
(mk_or_ind eq_ab not_eq result_ty pos_outer neg_outer
1272+
(mk_classic eq_ab),
1273+
s.clause)
1274+
| _ ->
1275+
raise (Walker_error
1276+
"'equiv_pos2' expects clause (cl (not (= a b)) (not a) b)")
1277+
11771278
let elab_false_step (_ctx : walker_ctx) (s : Alethe.step)
11781279
: EConstr.t * Alethe.Sexp.t list =
11791280
match s.clause with
@@ -1262,15 +1363,18 @@ let elab_step (env : Environ.env) (sigma_ref : Evd.evar_map ref)
12621363
| "and_neg" -> elab_and_neg ctx s
12631364
(* Propositional-equality tautology simplification (R-8). *)
12641365
| "equiv_simplify" -> elab_equiv_simplify ctx s
1366+
(* 3-literal equivalence tautologies (R-10). *)
1367+
| "equiv_pos1" -> elab_equiv_pos1 ctx s
1368+
| "equiv_pos2" -> elab_equiv_pos2 ctx s
12651369
| other ->
12661370
raise (Walker_error
12671371
(Printf.sprintf
1268-
"rule '%s' not yet supported (R-8 scope: \
1372+
"rule '%s' not yet supported (R-10 scope: \
12691373
assume / or / resolution (n-ary) / false / \
12701374
la_generic / la_mult_neg / refl / symm / trans / \
12711375
cong / hole / rare_rewrite / implies / equiv1 / \
1272-
equiv2 / not_and / and_neg / equiv_simplify; \
1273-
subsequent PRs add equiv_pos)"
1376+
equiv2 / not_and / and_neg / equiv_simplify / \
1377+
equiv_pos1 / equiv_pos2)"
12741378
other))
12751379
in
12761380
store_step st s.id proof clause

rocq-bridge/src/alethe_walker.mli

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,9 @@
3131
by [classic] case-analysis — footprint [{classic}]), and
3232
[equiv_simplify] (R-8, propositional-equality tautologies via
3333
[propositional_extensionality]; the double-negation pattern
34-
also pulls [classic]).
34+
also pulls [classic]), and the 3-literal equivalence
35+
tautologies [equiv_pos1]/[equiv_pos2] (R-10, nested [classic]
36+
case-splits — footprint [{classic}]).
3537
* [alethe_walker_test] tactic accepting a string-literal
3638
trace, parallel to Lean's [aletheWalkerTest].
3739

rocq-bridge/theories/Test.v

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1205,3 +1205,38 @@ Qed.
12051205

12061206
Print alethe_walker_bycontra_axiom_free.
12071207
Print Assumptions alethe_walker_bycontra_axiom_free.
1208+
1209+
(** Alethe walker — R-10 equiv_pos1 / equiv_pos2 (3-literal
1210+
Boolean tautologies).
1211+
1212+
Mirror of lean-bridge/Test/Tactic.lean's equiv_pos tests
1213+
(Lean #50). The two positive-polarity halves of propositional-
1214+
equivalence reasoning, deferred from the R-7 boolean cluster as
1215+
they are nested case-splits. No premises; built by nested
1216+
[classic] case analysis with the eq_mp/eq_mpr transports.
1217+
Footprint [{classic}] — the transports go through axiom-free
1218+
[eq_ind], so no propext is pulled. *)
1219+
1220+
(* equiv_pos1: ~(a=b) \/ a \/ ~b. *)
1221+
Theorem alethe_walker_equiv_pos1_axiom_free :
1222+
forall (a b : Prop), ~ (a = b) \/ a \/ ~ b.
1223+
Proof.
1224+
intros a b.
1225+
alethe_walker_test "(
1226+
(step t0 (cl (not (= a b)) a (not b)) :rule equiv_pos1) )".
1227+
Qed.
1228+
1229+
Print alethe_walker_equiv_pos1_axiom_free.
1230+
Print Assumptions alethe_walker_equiv_pos1_axiom_free.
1231+
1232+
(* equiv_pos2: ~(a=b) \/ ~a \/ b. *)
1233+
Theorem alethe_walker_equiv_pos2_axiom_free :
1234+
forall (a b : Prop), ~ (a = b) \/ ~ a \/ b.
1235+
Proof.
1236+
intros a b.
1237+
alethe_walker_test "(
1238+
(step t0 (cl (not (= a b)) (not a) b) :rule equiv_pos2) )".
1239+
Qed.
1240+
1241+
Print alethe_walker_equiv_pos2_axiom_free.
1242+
Print Assumptions alethe_walker_equiv_pos2_axiom_free.

tools/axiom_allowlist.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -531,6 +531,10 @@
531531
"alethe_walker_equiv_simplify_or_idem_axiom_free":
532532
["propositional_extensionality"],
533533
"alethe_walker_bycontra_axiom_free":
534+
["classic"],
535+
"alethe_walker_equiv_pos1_axiom_free":
536+
["classic"],
537+
"alethe_walker_equiv_pos2_axiom_free":
534538
["classic"]
535539
}
536540
}

0 commit comments

Comments
 (0)