Zulip Chat Archive

Stream: mathlib4

Topic: tactic for partial orders


Vasilii Nesterov (Feb 12 2025 at 09:34):

Would it be useful to have a tactic in Mathlib that can solve goals in partial order theory? It is easy to implement, and I'd like to create one to practice metaprogramming.

Kim Morrison (Feb 12 2025 at 10:15):

What goals would this tactic solve?

Vasilii Nesterov (Feb 12 2025 at 10:30):

Those that can be closed by applying reflexivity, transitivity, or antisymmetry to local hypotheses and the goal. It would also use the linearity of the order when applicable.

In both cases (partial order and linear order), the quantifier-free theory is decidable in linear time.

Kevin Buzzard (Feb 12 2025 at 12:37):

Is it this ?

Vasilii Nesterov (Feb 12 2025 at 13:13):

Hmm, I'm not sure if an aesop-based approach is the best here because it searches for the proof using tree search by guessing proof steps, whereas in this case, it can be done without guessing.

By the way, it's easy to add support for lattices in the tactic I have in mind.

Bhavik Mehta (Feb 12 2025 at 16:19):

Heather and I have discussed this a bit - I agree this would be nice to have. For the tactic you have in mind, would it be able to prove that a <= b <= c <= d <= e and b != d implies a != e in a partial order?

Peter Nelson (Feb 13 2025 at 01:06):

It would be nice to have something work for lattices, as well, even if it is just as simple as adding all the trivial inequalities that come from le_sup_iff, sup_le_iff, le_sup_left, le_sup_right and similar for inf.

Bhavik Mehta (Feb 13 2025 at 01:10):

(The thread that Kevin links to does this, but I think Vasily's idea would perform better than the aesop approach. It might work to just preprocess like nlinarith does to add all those inequalities, then running the partial/linear order version)

Vasilii Nesterov (Feb 13 2025 at 09:06):

Bhavik Mehta said:

Heather and I have discussed this a bit - I agree this would be nice to have. For the tactic you have in mind, would it be able to prove that a <= b <= c <= d <= e and b != d implies a != e in a partial order?

Thank you for this example! Everything turned out to be more complicated than I initially thought. Anyway, it seems that the problem can be solved in quadratic time (not linear as I promised).

Let me describe my algorithm for partial orders.

  1. First, we negate the goal, so our new goal is to prove False.
  2. We collect all facts in the form of x = y, x ≤ y, x < y, x ≠ y, ¬(x ≤ y), and ¬(x < y) from the context. Here, we split conjunctions. Splitting disjunctions would result in exponential blow-up, and I guess we can't do anything smart about it here. However, there should be an option to enable this splitting if needed.
  3. We replace each fact of the form x < y with two equivalent facts: x ≤ y and x ≠ y.
  4. We replace each fact of the form ¬(x ≤ y) with two equivalent facts: ¬(x < y) and x ≠ y.
  5. We define the -graph as a directed graph whose vertices are variables and whose edges correspond to facts of the form x ≤ y. Similarly, we define (undirected) =-graph.
    We then find all connected components in =-graph. Variables within the same component are be provably equal. Thus, we can "compress" them into a single vertex. During this process, we check that no fact of the form x ≠ y leads to a contradiction — otherwise, we have found a contradiction, and the goal is accomplished. After this step, we may assume that the remaining facts are in one of three forms: x ≤ y, x ≠ y, and ¬(x < y).

  6. We identify all (directed) cycles in the -graph. All variables within such a cycle must be equal, so we perform compression as before. After this step, we can assume that the -graph is acyclic.

  7. For each fact of the form ¬(x < y), we check whether y is reachable from x in the -graph. If it is, then x ≤ y, which implies x = y, allowing us to compress the graph further.

At each step, we replace our set of facts with an equisatisfiable one. If no contradiction is found by the end, then there exists a model satisfying the final set of facts: one can use the set of variables with x R y holds iff y is reachable from x in the -graph. In this case, it is impossible to derive a contradiction from the theory of partial orders.

Vasilii Nesterov (Feb 13 2025 at 09:21):

In your example, after negation, we would have a ≤ b ≤ c ≤ d ≤ e, b ≠ d, and a = e. After step 5, a and e would be compressed into a single variable, say s. Then, in step 6, we would detect the cycle s ≤ b ≤ c ≤ d ≤ s, and compressing it would lead to a contradiction with b ≠ d.

Bhavik Mehta (Feb 13 2025 at 09:48):

Nice, I can see this working! Indeed the reason I shared that example is that we'd also come up with the "do search in the directed graph on " approach, but I didn't get time to think about how to make this example work (ie I just had a preorder tactic), I'm glad to hear it helped you figure out your algorithm!

Vasilii Nesterov (Feb 14 2025 at 15:26):

Here is the PR: #21877.

Vasilii Nesterov (Feb 14 2025 at 15:30):

While coding I simplified the algorithm and also added a Preorder version.

Jireh Loreaux (Feb 14 2025 at 16:13):

feature request:

order [h1, h2, h3]

Allow the user to supply inequalities.

Bhavik Mehta (Feb 14 2025 at 22:48):

Second feature request (which can and maybe should be a secondary PR to make this one easier to review): a preprocessing step which runs the lemmas that Peter mentions first, and then does order. I don't think this will be complete for lattices, but it could be convenient nonetheless (similar to how linarith is complete for linear arithmetic and nlinarith is not complete for nonlinear arithmetic, but it's still sometimes pretty useful!)

Vasilii Nesterov (Feb 14 2025 at 22:50):

Yes, I plan to do this in the next PR.

Vasilii Nesterov (Feb 17 2025 at 12:24):

I extended the tactic to Lattice (#21966), and it seems to be complete for them as well. I wonder if it would be useful to make it complete for DistribLattice. In any case, it seems to be an interesting and nontrivial task (maybe I should use the M3-N5 theorem somehow).

Btw, the cases of HeytingAlgebra and BooleanAlgebra can be handled by reducing them to intuitionistic or classical logic and then calling itauto or tauto :upside_down: .

Yaël Dillies (Feb 17 2025 at 12:26):

Vasily Nesterov said:

Btw, the cases of HeytingAlgebra and BooleanAlgebra can be handled by reducing them to intuitionistic or classical logic and then calling itauto or tauto :upside_down: .

This was one of the motivations for getting the theory of Heyting algebras off the ground: to get to write a cool tactic :grinning_face_with_smiling_eyes:

Yaël Dillies (May 03 2025 at 13:05):

Vasilii Nesterov said:

I extended the tactic to Lattice (#21966), and it seems to be complete for them as well.

I just discovered a simple example showing that it isn't complete for LinearOrder at least:

import Mathlib

example {α : Type*} [LinearOrder α] {a b c : α} (hab : a < b)
    (habc : min a b  c) (hcba : min c b  a) : a = c := by
  order -- No contradiction found
  exact le_antisymm (by simpa [hab.le] using habc) (by simpa [hab.not_le] using hcba)

The issue seems to be that order doesn't know about docs#inf_le_iff

Yaël Dillies (May 03 2025 at 13:06):

How I came across this example: This is showing that v(a)=v(a+b)v(a) = v(a + b) if v(a)<v(b)v(a) < v(b) where vv is a valuation

Edward van de Meent (May 03 2025 at 13:18):

this doesn't mean that it's not complete for Lattice, right?

Edward van de Meent (May 03 2025 at 13:18):

(just checking my understanding)

Jovan Gerbscheid (May 03 2025 at 13:57):

Should the plan be to make the tactic complete for LinearOrder, by doing some form of case splitting, like in omega? Or just to let the tactic use the lemma min a b < b → a < b whenever it already knows min a b < b?

Vasilii Nesterov (May 03 2025 at 14:04):

Yes, this tactic is complete for both LinearOrder without lattice operations and for Lattice without assuming linearity.

It seems the problem for linear orders with lattice operations is NP-hard. We can model 3SAT problem like this: add a hypothesis f < t, for each variable x require x₀ ⊓ x₁ ≤ f and x₀ ⊔ x₁ ≥ t and then for a clause x ∨ ¬ y ∨ z require x₁ ⊔ y₀ ⊔ z₁ ≥ t.

Vasilii Nesterov (May 03 2025 at 14:08):

I'd like to solve the case of linear orders by translating the problem from an arbitrary type to Nat, and then running omega. This would save us from having to reimplement all the heuristics needed to make the procedure efficient. I wonder how hard it is to use such an approach.

Jovan Gerbscheid (May 03 2025 at 14:11):

That is a good idea. Although it's probably worth waiting until grind is ready, and then using it instead of omega.

Yaël Dillies (May 03 2025 at 14:23):

Vasilii Nesterov said:

solve the case of linear orders by translating the problem from an arbitrary type to Nat, and then running omega

That's a very interesting idea. How would it work, mathematically? It feels like you're claiming something model-theoretic about linear orders and . Something like is a "generic" model of the quantifier-free theory of linear orders? If so, is your idea to turn a proof of this fact into an effective algorithm?

Jovan Gerbscheid (May 03 2025 at 14:40):

One way it could be done (I'm not sure if there is any other way) is to use the same ideas as in to_additive (or to_dual). We could make a tag to_linear, which turns all relevant statements about or into the equivalent statements about an arbitrary linear order α. Then using the to_linear machinery, we can transform the obtained proof about or into a proof about the given linear order.

Or we can hope that grind will work directly on a LinearOrder.

Yaël Dillies (May 03 2025 at 14:42):

Okay, but for example m + 1 ≤ n ↔ m < n has no analogue in an arbitrary linear order, so not all(/very few) proofs generated by omega/grind will translate to a general linear order

Yaël Dillies (May 03 2025 at 14:43):

You need some kind of encoding of the variables in your problem and their min/max as natural numbers

Edward van de Meent (May 03 2025 at 14:47):

Yaël Dillies said:

Vasilii Nesterov said:

solve the case of linear orders by translating the problem from an arbitrary type to Nat, and then running omega

That's a very interesting idea. How would it work, mathematically? It feels like you're claiming something model-theoretic about linear orders and . Something like is a "generic" model of the quantifier-free theory of linear orders? If so, is your idea to turn a proof of this fact into an effective algorithm?

i think the idea is that any first-order statement in the theory of linear orders is true iff there are no counterexamples, which are finite, so must have an equivalent in the natural numbers. As a result, it suffices to check that the natural numbers don't contain a counterexample?

Edward van de Meent (May 03 2025 at 14:50):

although you might need the integers instead

Edward van de Meent (May 03 2025 at 14:51):

because of statements like (∀ a, ∃ b, b < a) → False

Aaron Liu (May 03 2025 at 14:51):

I thought we were working quantifier-free?

Edward van de Meent (May 03 2025 at 14:52):

ah right, that's fair

Edward van de Meent (May 03 2025 at 14:52):

model theory was some time ago

Edward van de Meent (May 03 2025 at 14:53):

in that case indeed Nat is sufficient

Yaël Dillies (May 03 2025 at 14:59):

Edward van de Meent said:

i think the idea is that any first-order statement in the theory of linear orders is true iff there are no counterexamples, which are finite, so must have an equivalent in the natural numbers. As a result, it suffices to check that the natural numbers don't contain a counterexample?

That is indeed the idea, but I am not sure how to prove "effectively".

Jovan Gerbscheid (May 03 2025 at 15:08):

Does Mathlib have some sort of model theory? I think it should be possible to

  • define the language of lattices
  • define the evaluation of statements in this language inside a given order
  • define what a model is
  • prove that a model in an arbitrary LinearOrder gives a model in Nat.

Then we use grind/omega to prove that there is no model in Nat, so we conclude there is no model in the given LinearOrder.

Aaron Liu (May 03 2025 at 15:09):

We have file#ModelTheory/Order

Yaël Dillies (May 03 2025 at 15:11):

The model-theoretic language is useful, but I doubt we actually need to prove the model-theoretic statement to implement the tactic. The model theoretic statement is more like a metatheorem that guides tactic-writing.

Jovan Gerbscheid (May 03 2025 at 15:33):

I'm not sure how the tactic would prove this without using the model theoretic theorem.

Vasilii Nesterov (May 03 2025 at 15:44):

Yes, by translation from an arbitrary type to , I mean interpreting all order operations (<, , , ) in terms of natural numbers. Any finite linearly ordered set can be embedded into the natural numbers in a way that preserves the order, so this is mathematically correct: if some linearly ordered set satisfies a set of conditions S, then its "ℕ-version" will also satisfy S. Conversely, if S can be satisfied in , then it is impossible to derive a contradiction from S using only order axioms.

On the implementation side, I had the following idea. We need a lemma stating that for any "valuation" in our type α, we can map α to in a way that preserves the order on the image of this valuation, like so:

import Mathlib

theorem exists_translation {α : Type*} [LinearOrder α] (val :   α) :  f :   ,
    ( i j, val i  val j  f i  f j) 
    ( i j, val i < val j  f i < f j)
    -- maybe more conditions?
    := by sorry

Then we take some val such that val 0, val 1, ... is equal to variables in the context. And using such an f, we can rewrite all hypotheses in the context and run omega.

Aaron Liu (May 03 2025 at 15:52):

You should have val : Fin n → α since this version is not provable.

Vasilii Nesterov (May 03 2025 at 15:53):

Yaël Dillies said:

Okay, but for example m + 1 ≤ n ↔ m < n has no analogue in an arbitrary linear order, so not all(/very few) proofs generated by omega/grind will translate to a general linear order

This is true, but this involves arithmetic. For theories with arithmetic Nat is not a "universal" (I don't remember the right model-theoretic term) model, but it's sufficient for the order theory. Maybe we should use something else and preserve some arithmetic as well.

Vasilii Nesterov (May 03 2025 at 15:56):

Aaron Liu said:

You should have val : Fin n → α since this version is not provable.

Yes, indeed. val 0 > val 1 > val 2 > ... is a counterexample. Maybe we can just use intead of here? Fin n is enough though

Edit: no, doesn't work, val 0 > val 1 > val 2 > ... > 0 is a counterexample :)

Yaël Dillies (May 03 2025 at 15:58):

Jovan Gerbscheid said:

I'm not sure how the tactic would prove this without using the model theoretic theorem.

My hope is that the model-theoretic theorem has an effective proof, and that this proof is the algorithm the tactic should follow.

Jovan Gerbscheid (May 03 2025 at 16:00):

I think the proof of the model-theoretic theorem uses case-splits. So if you were to replay this proof in the tactic, you would be doing too much work (and probably not need to call grind/omega anymore)

Aaron Liu (May 03 2025 at 16:02):

Yaël Dillies said:

Jovan Gerbscheid said:

I'm not sure how the tactic would prove this without using the model theoretic theorem.

My hope is that the model-theoretic theorem has an effective proof, and that this proof is the algorithm the tactic should follow.

I think all you get is a reduction from finite subsets of α to , not sure how you would extract an algorithm from that.

Jovan Gerbscheid (May 03 2025 at 16:03):

The exists_translation theorem (with val : Fin n → α) captures the essence of the model theoretic statement. The question is whether we want/need to use the model theory to prove it.

Aaron Liu (May 03 2025 at 16:04):

Why would we need model theory? We can just induct on n.

Jovan Gerbscheid (May 03 2025 at 16:12):

At some point I wrote a way to construct efficient Exprs of type ℕ → α (or Fin n → α), using a binary tree. This can be used to construct the val:

Code

Aaron Liu (May 03 2025 at 17:43):

I've got a working proof of exists_translation, with some nice properties.

Here it is

Vasilii Nesterov (May 03 2025 at 17:49):

Very nice! Would you like to write a tactic that uses this? I'd like to do this too, but I'm not that fast :)

Aaron Liu (May 03 2025 at 17:51):

What's the spec of this tactic? Gather up all the linear order hypotheses and convert them into ?

Cameron Zwarich (May 03 2025 at 17:51):

Edward van de Meent said:

i think the idea is that any first-order statement in the theory of linear orders is true iff there are no counterexamples, which are finite, so must have an equivalent in the natural numbers. As a result, it suffices to check that the natural numbers don't contain a counterexample?

[probably my fault for not realizing this discussion was in the pure QF context]
A counterexample doesn't necessarily have a finite model. For example, the sentence "there is a least element and a greatest element" has no counterexamples in finite linear orders, but the rationals are a counterexample.

It will probably be much simpler to make a decision procedure for dense linear orders (whose theory has quantifier elimination, with a fairly elementary proof) or the quantifier-free theory of linear orders. The decidability of the theory of all linear orders is usually established by more sophisticated techniques. In practice, I suspect that any decision procedure would need to generate explicit proof terms (possibly using small pockets of proof-by-reflection to compress the proofs) rather than using Mathlib's development of model theory to prove a decision procedure correct outright, because even if the proof effort succeeded, the resulting procedure would run too slowly in Lean's kernel to be useful.

Aaron Liu (May 03 2025 at 17:52):

I think we were focusing on the quantifier-free case

Vasilii Nesterov (May 03 2025 at 17:52):

Aaron Liu said:

What's the spec of this tactic? Gather up all the linear order hypotheses and convert them into ?

Yes, I meant this

Vasilii Nesterov (May 03 2025 at 17:55):

Cameron Zwarich said:

Edward van de Meent said:

i think the idea is that any first-order statement in the theory of linear orders is true iff there are no counterexamples, which are finite, so must have an equivalent in the natural numbers. As a result, it suffices to check that the natural numbers don't contain a counterexample?

A counterexample doesn't necessarily have a finite model. For example, the sentence "there is a least element and a greatest element" has no counterexamples in finite linear orders, but the rationals are a counterexample.

It will probably be much simpler to make a decision procedure for dense linear orders (whose theory has quantifier elimination, with a fairly elementary proof) or the quantifier-free theory of linear orders. The decidability of the theory of all linear orders is usually established by more sophisticated techniques. In practice, I suspect that any decision procedure would need to generate explicit proof terms (possibly using small pockets of proof-by-reflection to compress the proofs) rather than using Mathlib's development of model theory to prove a decision procedure correct outright, because even if the proof effort succeeded, the resulting procedure would run too slowly in Lean's kernel to be useful.

I’ve thought about dense linear orders as well. The problem is that quantifier elimination doesn’t seem very useful in practice and also leads to exponential blow-up.

Vasilii Nesterov (May 03 2025 at 17:59):

Aaron Liu said:

What's the spec of this tactic? Gather up all the linear order hypotheses and convert them into ?

Perhaps it should use order internals to collect variables and facts about them from the context.

Aaron Liu (May 03 2025 at 18:09):

Vasilii Nesterov said:

Aaron Liu said:

What's the spec of this tactic? Gather up all the linear order hypotheses and convert them into ?

Yes, I meant this

what should I call it

Vasilii Nesterov (May 03 2025 at 18:24):

In fact, I think it should be a part of the order tactic. I suggest starting from #21966. You can write a function

def translateToNat (type : Expr) (idxToAtom : Std.HashMap  Expr) (facts : Array AtomicFact) :
    MetaM <| Std.HashMap  Expr × Array AtomicFact :=
  sorry

where type is the original type, idxToAtom maps indexes to variables and facts contains facts about these variables extracted from the context. The function returns new (idxToAtom, facts) about Nats. I believe it's enough to write a lemma for each type of AtomicFact and then just rewrite each fact using the corresponding lemma.

Vasilii Nesterov (May 03 2025 at 18:34):

I'm not sure what we should do with facts of the form x = ⊤. In the order, I replace such facts with new ones of the form y ≤ x for all atoms y. We can apply the same preprocessing here, so you can assume that there are no facts of this type in translateToNat.

Aaron Liu (May 03 2025 at 18:56):

Are all the variables of the same type?

Vasilii Nesterov (May 03 2025 at 18:57):

Yes

Aaron Liu (May 03 2025 at 19:00):

Do I just push to branch#vasnesterov/order_tactic_lattices

Aaron Liu (May 03 2025 at 19:00):

or a new branch

Vasilii Nesterov (May 03 2025 at 19:01):

New, please

Vasilii Nesterov (May 03 2025 at 19:01):

I hope this one will be reviewed and merged soon (it's quite old already)

Cameron Zwarich (May 03 2025 at 19:01):

Vasilii Nesterov said:

I’ve thought about dense linear orders as well. The problem is that quantifier elimination doesn’t seem very useful in practice and also leads to exponential blow-up.

While in general the DEXPTIME nature of most QE procedures seems limiting, I have a suspicion (which might be a misconception on my part) that they could be made practical for Mathlib-sized problems. Maybe I should do some experiments and see how efficient I can make it. If it’s going to work out anywhere, it will be DLO.

Aaron Liu (May 03 2025 at 19:26):

Vasilii Nesterov said:

In fact, I think it should be a part of the order tactic. I suggest starting from #21966. You can write a function

def translateToNat (type : Expr) (idxToAtom : Std.HashMap  Expr) (facts : Array AtomicFact) :
    Std.HashMap  Expr × Array AtomicFact :=
  sorry

where type is the original type, idxToAtom maps indexes to variables and facts contains facts about these variables extracted from the context. The function returns new (idxToAtom, facts) about Nats. I believe it's enough to write a lemma for each type of AtomicFact and then just rewrite each fact using the corresponding lemma.

What do I do with facts of type .isSup and .isInf?

Vasilii Nesterov (May 03 2025 at 19:28):

I guess, rewrite with something like

theorem translation_inf {α : Type*} [LinearOrder α] {n : } (val : Fin n  α) (i j k : Fin n) :
    translation val i = translation val j  translation val k  val i = val j  val k := by sorry

Aaron Liu (May 03 2025 at 19:29):

But they don't carry around a proof

Aaron Liu (May 03 2025 at 19:30):

I could modify them to carry around a proof, but then I don't know what would break, and it might take a few hours to debug.

Vasilii Nesterov (May 03 2025 at 19:32):

Yes, for isSup and isInf the proof is always rfl

Aaron Liu (May 03 2025 at 19:32):

But for the translation the proof isn't rfl anymore so it gets complicated

Aaron Liu (May 03 2025 at 19:33):

or, I could try to special-case them but then I would need to do something different on the < and

Aaron Liu (May 03 2025 at 19:34):

I could also just double all the sup and inf and add a lot of .eq

Vasilii Nesterov (May 03 2025 at 21:40):

Yes, I think isSup i j k which states xk := xi ⊔ xj should become aux := ni ⊔ nj plus a new fact nk = aux

Aaron Liu (May 03 2025 at 23:53):

Vasilii Nesterov said:

Yes, I think isSup i j k which states xk := xi ⊔ xj should become aux := ni ⊔ nj plus a new fact nk = aux

How do I generate a new index for aux?

Vasilii Nesterov (May 04 2025 at 08:08):

At first you can map existing variables, like idxToAtomNew[i] = translation val i (q($translation $val $i) using Qq). Then when you face a fact .isSup i j k you add a new atom idxToAtomNew[newIdx] = translation val i ⊔ translation val j (where newIdx = idxToAtomNew.size) and two facts: .isSup i j newIdx and .eq k newIdx. For the latter you need to prove exactly that translation val k = translation val i ⊔ translation val j, and here you use your lemma. Does this work?

Aaron Liu (May 06 2025 at 02:06):

I have this function translateToNat, now what do I do with it?

Aaron Liu (May 06 2025 at 02:11):

I'm also not entirely sure that it works correctly


Last updated: Dec 20 2025 at 21:32 UTC