Skip to content

Commit 873212a

Browse files
committed
Add solver with tests
0 parents  commit 873212a

7 files changed

Lines changed: 863 additions & 0 deletions

File tree

.gitignore

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
.lia.cache
2+
*.vo
3+
*.glob
4+
*.aux
5+
*.vok
6+
*.vos
7+
.lia.cache
8+
.depend
9+
_build
10+
_opam
11+
\#*.*\#
12+
*.v~
13+
_RocqProject

LICENSE

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
MIT License
2+
3+
Copyright 2026 EPFL SYSTEMF.
4+
5+
Permission is hereby granted, free of charge, to any person obtaining
6+
a copy of this software and associated documentation files (the
7+
“Software”), to deal in the Software without restriction, including
8+
without limitation the rights to use, copy, modify, merge, publish,
9+
distribute, sublicense, and/or sell copies of the Software, and to
10+
permit persons to whom the Software is furnished to do so, subject to
11+
the following conditions:
12+
13+
The above copyright notice and this permission notice shall be
14+
included in all copies or substantial portions of the Software.
15+
16+
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND,
17+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

Solver.v

Lines changed: 185 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,185 @@
1+
From Stdlib Require Import Classes.RelationClasses Lists.List.
2+
From Ltac2 Require Import Ltac2 FMap Constr Printf List Fresh.
3+
Import ListNotations.
4+
5+
(* A complete solver for StrictOrder relations *)
6+
(* solves by finding paths between edges of a graph induced *)
7+
(* by the relation assertions from hypotheses. *)
8+
(* Additionally proves contradictions by finding cycles. *)
9+
10+
(* Finds an item and returns its index *)
11+
Ltac2 rec findi (f : 'a -> bool) (xs : 'a list) : int option :=
12+
let rec aux f xs i :=
13+
match xs with
14+
| [] => None
15+
| x :: xs =>
16+
if f x then Some i else aux f xs (Int.add i 1)
17+
end in
18+
aux f xs 0.
19+
20+
(* turns a term into some normalized string representation *)
21+
Ltac2 rec term_to_string (t: constr): string :=
22+
let msg := fprintf "%t" (eval cbv in $t) in
23+
Message.to_string msg.
24+
25+
26+
Ltac2 rec apply_first (tacs: (unit -> constr) list): constr :=
27+
match tacs with
28+
| [] => Control.zero (Tactic_failure (Some (fprintf "No tactic succeeded")))
29+
| tac :: rest =>
30+
Control.plus tac (fun _ => apply_first rest)
31+
end.
32+
33+
(* constructs a term that is the transitive path between two elements *)
34+
(* the parameter is a list of hypothesis identifiers *)
35+
Ltac2 rec mk_trans (hyp_list: ident list): constr :=
36+
match hyp_list with
37+
| [] => Control.throw (Tactic_failure (Some (fprintf "No hypotheses to apply for transitivity")))
38+
| [h] => Control.hyp h
39+
| h :: rest =>
40+
let h_constr := Control.hyp h in
41+
let rest_constr := mk_trans rest in
42+
Control.plus
43+
(fun _ => constr:(transitivity $h_constr $rest_constr))
44+
(fun _ =>
45+
Control.throw (Tactic_failure (Some (
46+
fprintf "Failed to compose a transitive step. Does your relation implement %t?" open_constr:(Transitive)))))
47+
end.
48+
49+
(* tries to interpret `h` as `rel a b` and returns a string representation of `a` and `b` *)
50+
Ltac2 get_elements (h: constr) (rel: constr): (string * string) option :=
51+
match! h with
52+
| ?r ?a ?b =>
53+
if Constr.equal r rel then
54+
Some (term_to_string a, term_to_string b)
55+
else None
56+
| _ => None
57+
end.
58+
59+
(* tried to normalize a term into the form `rel a b` for some a and b *)
60+
Ltac2 normalize (h: constr) (rel: constr): constr option :=
61+
Control.plus
62+
(fun _ =>
63+
let a := open_constr:(_) in
64+
let b := open_constr:(_) in
65+
let rel_ab := open_constr:($rel $a $b) in
66+
let rel_ab_nf := eval cbv in $rel_ab in
67+
let h_nf := eval cbv in $h in
68+
Std.unify rel_ab_nf h_nf;
69+
Some constr:($rel $a $b))
70+
(fun _ => None).
71+
72+
(* rewrite all hypotheses and the goal using `normalize` *)
73+
Ltac2 normalize_relations (rel: constr): unit :=
74+
let goal := Control.goal () in
75+
(match normalize goal rel with
76+
| Some h =>
77+
let cl := {
78+
Std.on_hyps := Some [];
79+
Std.on_concl := Std.AllOccurrences
80+
} in
81+
Std.change None (fun _ => h) cl
82+
| None => ()
83+
end);
84+
List.iter (fun ((id, _, tp)) =>
85+
match normalize tp rel with
86+
| Some h =>
87+
let cl := {
88+
Std.on_hyps := Some [(id, Std.AllOccurrences, Std.InHyp)];
89+
Std.on_concl := Std.NoOccurrences
90+
} in
91+
Std.change None (fun _ => h) cl
92+
| None => ()
93+
end
94+
) (Control.hyps ()).
95+
96+
(* constructs the graph from relations in hypotheses *)
97+
Ltac2 build_graph (rel: constr): (string, (string * ident) list) FMap.t :=
98+
List.fold_left (fun acc ((id, _, tp)) =>
99+
match get_elements tp rel with
100+
| Some (a_str, b_str) =>
101+
match FMap.find_opt a_str acc with
102+
| Some existing =>
103+
FMap.add a_str ((b_str, id) :: existing) acc
104+
| None =>
105+
FMap.add a_str [(b_str, id)] acc
106+
end
107+
| None => acc
108+
end
109+
) (FMap.empty FSet.Tags.string_tag) (Control.hyps ()).
110+
111+
(* given a proof of a self-loop, proves anything by irreflexivity *)
112+
Ltac2 prove_contradiction self_loop: constr :=
113+
let proof_false :=
114+
Control.plus
115+
(fun _ => constr:(irreflexivity $self_loop))
116+
(fun _ =>
117+
Control.throw (Tactic_failure (Some (
118+
fprintf "Failed to derive a contradiction. Does your relation implement %t?" open_constr:(Irreflexive))))) in
119+
open_constr:(ltac2:(ltac1:(exfalso); exact $proof_false)).
120+
121+
(* in the graph `hyps_map` tries to find a path from `from` to `to` *)
122+
Ltac2 find_path hyps_map from to: constr :=
123+
let rec dfs visited goal current path :=
124+
match findi (String.equal current) visited with
125+
| Some idx =>
126+
(* we have found a cycle *)
127+
let cycle_proof := mk_trans (List.rev (List.firstn (Int.add idx 1) path)) in
128+
prove_contradiction cycle_proof
129+
| None =>
130+
let visited := current :: visited in
131+
match FMap.find_opt current hyps_map with
132+
| Some lst =>
133+
let try_path (next, hyp_id) () :=
134+
if String.equal next goal then
135+
mk_trans (List.rev (hyp_id :: path))
136+
else
137+
dfs visited goal next (hyp_id :: path) in
138+
apply_first (List.map try_path lst)
139+
| None =>
140+
Control.zero (Tactic_failure (Some (fprintf "No path found from %s to %s" current goal)))
141+
end
142+
end in
143+
dfs [] to from [].
144+
145+
Ltac2 print_full_goal () :=
146+
let hyps := Control.hyps () in
147+
List.iter (fun h =>
148+
match h with
149+
| (id, body, ty) =>
150+
(match body with
151+
| None => printf "%I : %t" id ty
152+
| Some b => printf "%I := %t : %t" id b ty
153+
end)
154+
end
155+
) hyps;
156+
printf "----------------------------------------";
157+
let g := Control.goal () in
158+
printf "|- %t" g.
159+
160+
(* tries to find a cycle in the graph that will be a contradiction *)
161+
Ltac2 find_contradiction hyps_map: constr :=
162+
let try_contra hyps_map name :=
163+
prove_contradiction (find_path hyps_map name name) in
164+
let rec try_contras hyps_map names :=
165+
match names with
166+
| name :: rest =>
167+
Control.plus (fun _=> try_contra hyps_map name) (fun _ => try_contras hyps_map rest)
168+
| [] =>
169+
Control.throw (Tactic_failure (Some (fprintf "No contradiction found")))
170+
end in
171+
try_contras hyps_map (List.map fst (FMap.bindings hyps_map)).
172+
173+
(* collect all hyps of the rel relation, put them in a map, do a dfs until we find the required one *)
174+
Ltac2 solve_strict_order rel: constr :=
175+
let hyps_map := build_graph rel in
176+
match get_elements (Control.goal ()) rel with
177+
| Some (a_str, b_str) =>
178+
Control.plus (fun _ => find_path hyps_map a_str b_str) (fun _ => find_contradiction hyps_map)
179+
| _ =>
180+
find_contradiction hyps_map
181+
end.
182+
183+
Ltac2 Notation "strict_order" rel(constr) :=
184+
normalize_relations rel;
185+
Control.refine (fun _ => solve_strict_order rel).

0 commit comments

Comments
 (0)