Compare commits

...

39 Commits

Author SHA1 Message Date
Leonardo de Moura
da40cc194f chore: fix test 2025-03-02 14:25:05 -08:00
Leonardo de Moura
cfc20cbfad fix: Rat.floor and Rat.ceil
This PR fixes bugs in `Std.Internal.Rat.floor` and `Std.Internal.Rat.ceil`.
2025-03-02 14:22:07 -08:00
Leonardo de Moura
3eb07cac44 feat: cooper_right helper theorem for cutsat (#7293)
This PR adds support theorems for the Cooper-Right conflict resolution
rule used in the cutsat procedure. During model construction, when
attempting to extend the model to a variable x, cutsat may find a
conflict that involves two inequalities (the lower and upper bounds for
x). This is a special case of Cooper-Dvd-Right when there is no
divisibility constraint.
2025-03-02 19:21:08 +00:00
Mac Malone
58034bf237 feat: lake: display newest job in monitor (#7291)
This PR changes the Lake job monitor to display the last (i.e., newest)
running/unfinished job rather than the first. This avoids the monitor
focusing too long on any one job (e.g., "Running job computation").
2025-03-02 18:38:23 +00:00
Leonardo de Moura
7ba7ea4e16 feat: helper theorems for cooper_dvd_right (#7292)
This PR adds support theorems for the **Cooper-Dvd-Right** conflict
resolution rule used in the cutsat procedure. During model construction,
when attempting to extend the model to a variable `x`, cutsat may find a
conflict that involves two inequalities (the lower and upper bounds for
`x`) and a divisibility constraint.
2025-03-02 18:09:55 +00:00
Leonardo de Moura
4877e84031 feat: cooper_left helper theorem for cutsat (#7290)
This PR adds support theorems for the **Cooper-Left** conflict
resolution rule used in the cutsat procedure. During model
construction,when attempting to extend the model to a variable `x`,
cutsat may find a conflict that involves two inequalities (the lower and
upper bounds for `x`). This is a special case of Cooper-Dvd-Left when
there is no divisibility constraint.
2025-03-02 16:34:48 +00:00
Henrik Böving
9c47f395c8 refactor: change iff lowering rule in bv_decide (#7287)
This PR uses a better lowering rule for iff in bv_decide's
preprocessing.
2025-03-02 12:20:27 +00:00
Kim Morrison
3f98b4835c chore: add Fin.mk_eq_zero simp lemma (#7286) 2025-03-02 11:11:23 +00:00
Leonardo de Moura
a86145b6bb feat: non-chronological backtracking for cutsat (#7284)
This PR implements non-choronological backtracking for the cutsat
procedure. The procedure has two main kinds of case-splits:
disequalities and Cooper resolvents. This PR focus on the first kind.
2025-03-01 23:19:11 +00:00
dependabot[bot]
c4d3a74f32 chore: CI: bump dawidd6/action-download-artifact from 8 to 9 (#7285)
Bumps
[dawidd6/action-download-artifact](https://github.com/dawidd6/action-download-artifact)
from 8 to 9.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/dawidd6/action-download-artifact/releases">dawidd6/action-download-artifact's
releases</a>.</em></p>
<blockquote>
<h2>v9</h2>
<h2>What's Changed</h2>
<ul>
<li>add merge_multiple option by <a
href="https://github.com/timostroehlein"><code>@​timostroehlein</code></a>
in <a
href="https://redirect.github.com/dawidd6/action-download-artifact/pull/327">dawidd6/action-download-artifact#327</a></li>
</ul>
<h2>New Contributors</h2>
<ul>
<li><a
href="https://github.com/timostroehlein"><code>@​timostroehlein</code></a>
made their first contribution in <a
href="https://redirect.github.com/dawidd6/action-download-artifact/pull/327">dawidd6/action-download-artifact#327</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/dawidd6/action-download-artifact/compare/v8...v9">https://github.com/dawidd6/action-download-artifact/compare/v8...v9</a></p>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="07ab29fd4a"><code>07ab29f</code></a>
add merge_multiple option (<a
href="https://redirect.github.com/dawidd6/action-download-artifact/issues/327">#327</a>)</li>
<li>See full diff in <a
href="https://github.com/dawidd6/action-download-artifact/compare/v8...v9">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=dawidd6/action-download-artifact&package-manager=github_actions&previous-version=8&new-version=9)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after
your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge
and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating
it. You can achieve the same result by closing it manually
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)


</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-03-01 20:47:28 +00:00
Leonardo de Moura
c74865fbe2 feat: helper theorems for cooper_dvd_left (#7279)
This PR adds support theorems for the **Cooper-Dvd-Left** conflict
resolution rule used in the cutsat procedure. During model construction,
when attempting to extend the model to a variable `x`, cutsat may find a
conflict that involves two inequalities (the lower and upper bounds for
`x`) and a divisibility constraint:

```lean
a * x + p ≤ 0
b * x + q ≤ 0
d ∣ c * x + s
```

We apply Cooper's quantifier elimination to produce:

```lean
OrOver (Int.lcm a (a * d / Int.gcd(a * d) c)) fun k =>
     b * p + (-a) * q + b * k ≤ 0 ∧
     a ∣ p + k ∧
     a * d ∣ c * p + (-a) * s + c * k
```

Here, `OrOver` is a "big-or" operator. This PR introduces the following
theorem, which encapsulates the above approach via reflection:

```lean
theorem cooper_dvd_left (ctx : Context) (p₁ p₂ p₃ : Poly) (d : Int) (n : Nat)
    : cooper_dvd_left_cert p₁ p₂ p₃ d n
      → p₁.denote' ctx ≤ 0
      → p₂.denote' ctx ≤ 0
      → d ∣ p₃.denote' ctx
      → OrOver n (cooper_dvd_left_split ctx p₁ p₂ p₃ d) :=
```

For each `0 <= k < n`, we generate the three implied facts using:

```lean
theorem cooper_dvd_left_split_ineq (ctx : Context) (p₁ p₂ p₃ : Poly) (d : Int) (k : Nat) (b : Int) (p' : Poly)
    : cooper_dvd_left_split ctx p₁ p₂ p₃ d k
      → cooper_dvd_left_split_ineq_cert p₁ p₂ k b p'
      → p'.denote ctx ≤ 0

theorem cooper_dvd_left_split_dvd1 (ctx : Context) (p₁ p₂ p₃ : Poly) (d : Int) (k : Nat) (a : Int) (p' : Poly)
    : cooper_dvd_left_split ctx p₁ p₂ p₃ d k
      → cooper_dvd_left_split_dvd1_cert p₁ p' a k
      → a ∣ p'.denote ctx

theorem cooper_dvd_left_split_dvd2 (ctx : Context) (p₁ p₂ p₃ : Poly) (d : Int) (k : Nat) (d' : Int) (p' : Poly)
    : cooper_dvd_left_split ctx p₁ p₂ p₃ d k
      → cooper_dvd_left_split_dvd2_cert p₁ p₃ d k d' p'
      → d' ∣ p'.denote ctx
```

Two helper `OrOver` theorems are used to process the `OrOver`:

```lean
theorem orOver_unsat {p} : ¬ OrOver 0 p

theorem orOver_resolve {n p} : OrOver (n+1) p → ¬ p n → OrOver n p
```

Where `p` is instantiated using `cooper_dvd_left_split ctx p₁ p₂ p₃ d`.
2025-03-01 02:18:12 +00:00
Leonardo de Moura
93a908469c feat: cutsat counterexamples (#7278)
This PR adds counterexamples for linear integer constraints in the
`grind` tactic. This feature is implemented in the cutsat procedure.
2025-02-28 19:05:27 +00:00
Joachim Breitner
903fe29863 chore: release_notes.py: report on all commit types (#7258)
I missed a few that we should not be shy of.
2025-02-28 17:39:18 +00:00
Henrik Böving
84da113355 feat: add all bitwuzla level 1 if rewrites to bv_decide (#7275)
This PR adds all level 1 rewrites from Bitwuzla to the preprocessor of
bv_decide.
2025-02-28 16:04:09 +00:00
Markus Himmel
75df4c0b52 fix: statement of a UIntX conversion lemma (#7273)
This PR fixes the statement of a `UIntX` conversion lemma.
2025-02-28 15:15:58 +00:00
Sebastian Ullrich
ad5a746cdd fix: realizeConst fixes (#7272)
Emerged and fixed while adding more `realizeConst` callers
2025-02-28 14:59:13 +00:00
Paul Reichert
2bd3ce5463 fix: harmonize foldr signature of the tree map with that of List (#7271)
This PR changes the order of arguments of the folding function expected
by the tree map's `foldr` and `foldrM` functions so that they are
consistent with the API of `List`.

---------

Co-authored-by: Paul Reichert <6992158+datokrat@users.noreply.github.com>
2025-02-28 14:36:56 +00:00
Henrik Böving
2b752ec245 feat: add IntX and ISize support for bv_decide (#7269)
This PR implements support for `IntX` and `ISize` in `bv_decide`.
2025-02-28 10:33:11 +00:00
Paul Reichert
909ee719aa feat: tree map lemmas for keys and toList (#7260)
This PR provides lemmas about the tree map functions `keys` and `toList`
and their interactions with other functions for which lemmas already
exist. Moreover, a bug in `foldr` (calling `foldlM` instead of `foldrM`)
is fixed.

---------

Co-authored-by: Paul Reichert <6992158+datokrat@users.noreply.github.com>
2025-02-28 10:14:13 +00:00
Markus Himmel
7dd5e957da feat: ToExpr IntX (#7268)
This PR implements `Lean.ToExpr` for finite signed integers.
2025-02-28 09:32:30 +00:00
Markus Himmel
d67e0eea47 feat: IntX theory for simprocs and bv_decide (#7259)
This PR contains theorems about `IntX` that are required for `bv_decide`
and the `IntX` simprocs.

A more comprehensive set of theorems about `IntX` will be part of future
PRs.
2025-02-28 07:04:52 +00:00
Kim Morrison
10bfeba2d9 chore: aligning Int.ediv/fdiv/tdiv theorems (#7266)
This PR begins the alignment of `Int.ediv/fdiv/tdiv` theorems.
2025-02-28 05:27:40 +00:00
Leonardo de Moura
4285f8ba05 feat: improve cutsat model search procedure (#7267)
This PR improves the cutsat search procedure. It adds support for find
an approximate rational solution, checks disequalities, and adds stubs
for all missing cases.
2025-02-28 04:26:53 +00:00
Leonardo de Moura
d8be3ef7a8 doc: cutsat procedure (#7262) 2025-02-27 21:15:34 +00:00
Paul Reichert
c924768879 fix: add @[specialize] annotations to helpers used in alter and modify of the hash map (#7245)
This PR adds missing `@[specialize]` annotations to the `alter` and
`modify` functions in `Std.Data.DHashMap.Internal.AssocList`, which are
used by the corresponding hash map functions.

Zulip thread:
https://leanprover.zulipchat.com/#narrow/channel/270676-lean4/topic/.60Std.2EHashMap.2Emodify.60.20and.20.60alter.60.20do.20not.20inline.20the.20function

Co-authored-by: Paul Reichert <6992158+datokrat@users.noreply.github.com>
2025-02-27 15:43:05 +00:00
Henrik Böving
c1e76e8976 perf: optimize LRAT trimming in bv_decide (#7257)
This PR improves performance of LRAT trimming in bv_decide.

The underlying idea is taken from LRAT trimming as implemented in
[`lrat-trim`](https://github.com/arminbiere/lrat-trim/t): As we only
filter about half to two thirds of the LRAT proof steps anyway, there is
no need to use tree or hash maps to store information about them and we
can instead use arrays indexed by the proof step directly. This does not
meaningfully increase the amount of memory required but makes the
trimming step basically disappear from profiles, e.g.
`smt/non-incremental/QF_BV/20210312-Bouvier/vlsat3_a72.smt2` [used
to](https://share.firefox.dev/41kJTle) have 8% of its time spent in
trimming [now](https://share.firefox.dev/3QAKI4w) 1.5%.
2025-02-27 13:47:21 +00:00
Paul Reichert
60a9f8e492 feat: well-formedness lemmas for raw tree map operations (#7237)
This PR provides proofs that the raw tree map operations are well-formed
and refactors the file structure of the tree map, introducing new
modules `Std.{DTreeMap,TreeMap,TreeSet}.Raw` and splittting
`AdditionalOperations` into separate files for bundled and raw types.

---------

Co-authored-by: Paul Reichert <6992158+datokrat@users.noreply.github.com>
2025-02-27 13:08:41 +00:00
Kim Morrison
604133d189 chore: cleanup of remaining Array-specific material (#7253)
This PR takes Array-specific lemmas at the end of `Array/Lemmas.lean`
(i.e. material that does not have exact correspondences with
`List/Lemmas.lean`) and moves them to more appropriate homes. More to
come.
2025-02-27 10:51:30 +00:00
Kim Morrison
d3781bb787 fix: definition of Min (Option α), and basic lemmas (#7255)
This PR fixes the definition of `Min (Option α)`. This is a breaking
change. This treats `none` as the least element,
so `min none x = min x none = none` for all `x : Option α`. Prior to
nightly-2025-02-27, we instead had `min none (some x) = min (some x)
none = some x`. Also adds basic lemmas relating `min`, `max`, `≤` and
`<` on `Option`.
2025-02-27 10:44:44 +00:00
Sebastian Ullrich
87e8da5230 chore: temporarily disable Elab.async in the server (#7254)
...pending further testing of #7241 post-release
2025-02-27 08:31:54 +00:00
Kim Morrison
727c696d9f chore: add @[simp] to List.getElem_append_left|right (#7216)
Helps with confluence.
2025-02-27 03:01:33 +00:00
Mac Malone
cf2b7f4c1b feat: lake: builtin inits, elabs, & macros for DSL (#7171)
This PR changes the Lake DSL to use builtin elaborators, macros, and
initializers.

This works out of the box for the Lake executable and is supported in
interactive contexts through the Lake plugin.
2025-02-27 02:34:14 +00:00
Leonardo de Moura
cd4383b6f3 feat: refine inequalites using disequalities in cutsat (#7252)
This PR implements inequality refinement using disequalities. It
minimizes the number of case splits cutsat will have to perform.
2025-02-27 01:33:58 +00:00
Cameron Zwarich
0d9859370a fix: make extern decls evaluate as ⊤ instead of ⊥ in LCNF.elimDeadBranches (#6928)
This PR makes extern decls evaluate as ⊤ rather than the default value
of ⊥ in the LCNF elimDeadBranches analysis.
2025-02-27 01:24:47 +00:00
Cameron Zwarich
c292ae2e0e fix: don't create reduced arity LCNF decls with no params (#7086)
This PR makes the arity reduction pass in the new code generator match
the old one when it comes to the behavior of decls with no used
parameters. This is important, because otherwise we might create a
top-level decl with no params that contains unreachable code, which
would get evaluated unconditionally during initialization. This actually
happens when initializing Init.Core built with the new code generator.
2025-02-27 01:23:34 +00:00
Kim Morrison
3113847806 chore: reenable Vector variable name linters (#7251) 2025-02-26 23:59:28 +00:00
Kim Morrison
d275455674 chore: alignment of a List/Array/Vector.reverse lemma (#7250)
Minor lemma alignment missed earlier.
2025-02-26 23:59:06 +00:00
Kim Morrison
a4d10742d3 feat: align List/Array/Vector.any/all theorems (#7249)
This PR completes alignment of theorems about
`List/Array/Vector.any/all`.
2025-02-26 23:53:53 +00:00
Leonardo de Moura
777fba495a feat: cutsat implied equalities (#7248)
This PR implements simple equality propagation in cutsat `p <= 0 -> -p
<= 0 -> p = 0`
2025-02-26 22:52:37 +00:00
130 changed files with 5289 additions and 1352 deletions

View File

@@ -34,7 +34,7 @@ jobs:
- name: Download artifact from the previous workflow.
if: ${{ steps.workflow-info.outputs.pullRequestNumber != '' }}
id: download-artifact
uses: dawidd6/action-download-artifact@v8 # https://github.com/marketplace/actions/download-workflow-artifact
uses: dawidd6/action-download-artifact@v9 # https://github.com/marketplace/actions/download-workflow-artifact
with:
run_id: ${{ github.event.workflow_run.id }}
path: artifacts

View File

@@ -65,20 +65,21 @@ def format_markdown_description(pr_number, description):
link = f"[#{pr_number}](https://github.com/leanprover/lean4/pull/{pr_number})"
return f"{link} {description}"
def commit_types():
# see doc/dev/commit_convention.md
return ['feat', 'fix', 'doc', 'style', 'refactor', 'test', 'chore', 'perf']
def count_commit_types(commits):
counts = {
'total': len(commits),
'feat': 0,
'fix': 0,
'refactor': 0,
'doc': 0,
'chore': 0
}
for commit_type in commit_types():
counts[commit_type] = 0
for _, first_line, _ in commits:
for commit_type in ['feat:', 'fix:', 'refactor:', 'doc:', 'chore:']:
if first_line.startswith(commit_type):
counts[commit_type.rstrip(':')] += 1
for commit_type in commit_types():
if first_line.startswith(f'{commit_type}:'):
counts[commit_type] += 1
break
return counts
@@ -158,8 +159,9 @@ def main():
counts = count_commit_types(commits)
print(f"For this release, {counts['total']} changes landed. "
f"In addition to the {counts['feat']} feature additions and {counts['fix']} fixes listed below "
f"there were {counts['refactor']} refactoring changes, {counts['doc']} documentation improvements "
f"and {counts['chore']} chores.\n")
f"there were {counts['refactor']} refactoring changes, {counts['doc']} documentation improvements, "
f"{counts['perf']} performance improvements, {counts['test']} improvements to the test suite "
f"and {counts['style'] + counts['chore']} other changes.\n")
section_order = sort_sections_order()
sorted_changelog = sorted(changelog.items(), key=lambda item: section_order.index(format_section_title(item[0])) if format_section_title(item[0]) in section_order else len(section_order))

View File

@@ -555,6 +555,10 @@ def unattach {α : Type _} {p : α → Prop} (xs : Array { x // p x }) : Array
(xs.push a).unattach = xs.unattach.push a.1 := by
simp only [unattach, Array.map_push]
@[simp] theorem mem_unattach {p : α Prop} {xs : Array { x // p x }} {a} :
a xs.unattach h : p a, a, h xs := by
simp only [unattach, mem_map, Subtype.exists, exists_and_right, exists_eq_right]
@[simp] theorem size_unattach {p : α Prop} {xs : Array { x // p x }} :
xs.unattach.size = xs.size := by
unfold unattach
@@ -676,6 +680,20 @@ and simplifies these to the function directly taking the value.
simp
rw [List.find?_subtype hf]
@[simp] theorem all_subtype {p : α Prop} {xs : Array { x // p x }} {f : { x // p x } Bool} {g : α Bool}
(hf : x h, f x, h = g x) (w : stop = xs.size) :
xs.all f 0 stop = xs.unattach.all g := by
subst w
rcases xs with xs
simp [hf]
@[simp] theorem any_subtype {p : α Prop} {xs : Array { x // p x }} {f : { x // p x } Bool} {g : α Bool}
(hf : x h, f x, h = g x) (w : stop = xs.size) :
xs.any f 0 stop = xs.unattach.any g := by
subst w
rcases xs with xs
simp [hf]
/-! ### Simp lemmas pushing `unattach` inwards. -/
@[simp] theorem unattach_filter {p : α Prop} {xs : Array { x // p x }}

View File

@@ -144,6 +144,8 @@ end List
namespace Array
theorem size_eq_length_toList (xs : Array α) : xs.size = xs.toList.length := rfl
@[deprecated toList_toArray (since := "2024-09-09")] abbrev data_toArray := @List.toList_toArray
@[deprecated Array.toList (since := "2024-09-10")] abbrev Array.data := @Array.toList

View File

@@ -23,6 +23,18 @@ section countP
variable (p q : α Bool)
@[simp] theorem _root_.List.countP_toArray (l : List α) : countP p l.toArray = l.countP p := by
simp [countP]
induction l with
| nil => rfl
| cons hd tl ih =>
simp only [List.foldr_cons, ih, List.countP_cons]
split <;> simp_all
@[simp] theorem countP_toList (xs : Array α) : xs.toList.countP p = countP p xs := by
cases xs
simp
@[simp] theorem countP_empty : countP p #[] = 0 := rfl
@[simp] theorem countP_push_of_pos (xs) (pa : p a) : countP p (xs.push a) = countP p xs + 1 := by
@@ -150,6 +162,13 @@ section count
variable [BEq α]
@[simp] theorem _root_.List.count_toArray (l : List α) (a : α) : count a l.toArray = l.count a := by
simp [count, List.count_eq_countP]
@[simp] theorem count_toList (xs : Array α) (a : α) : xs.toList.count a = xs.count a := by
cases xs
simp
@[simp] theorem count_empty (a : α) : count a #[] = 0 := rfl
theorem count_push (a b : α) (xs : Array α) :

View File

@@ -282,6 +282,10 @@ end erase
/-! ### eraseIdx -/
theorem eraseIdx_eq_eraseIdxIfInBounds {xs : Array α} {i : Nat} (h : i < xs.size) :
xs.eraseIdx i h = xs.eraseIdxIfInBounds i := by
simp [eraseIdxIfInBounds, h]
theorem eraseIdx_eq_take_drop_succ (xs : Array α) (i : Nat) (h) : xs.eraseIdx i = xs.take i ++ xs.drop (i + 1) := by
rcases xs with xs
simp only [List.size_toArray] at h

File diff suppressed because it is too large Load Diff

View File

@@ -6,6 +6,7 @@ Authors: Mario Carneiro, Kim Morrison
prelude
import Init.Data.Array.Lemmas
import Init.Data.Array.Attach
import Init.Data.Array.OfFn
import Init.Data.List.MapIdx
set_option linter.listVariables true -- Enforce naming conventions for `List`/`Array`/`Vector` variables.

View File

@@ -23,6 +23,9 @@ open Nat
/-! ### mapM -/
@[simp] theorem mapM_id {xs : Array α} {f : α Id β} : xs.mapM f = xs.map f := by
induction xs; simp_all
@[simp] theorem mapM_append [Monad m] [LawfulMonad m] (f : α m β) {xs ys : Array α} :
(xs ++ ys).mapM f = (return ( xs.mapM f) ++ ( ys.mapM f)) := by
rcases xs with xs

View File

@@ -16,6 +16,25 @@ set_option linter.indexVariables true -- Enforce naming conventions for index va
namespace Array
@[simp] theorem ofFn_zero (f : Fin 0 α) : ofFn f = #[] := rfl
theorem ofFn_succ (f : Fin (n+1) α) :
ofFn f = (ofFn (fun (i : Fin n) => f i.castSucc)).push (f n, by omega) := by
ext i h₁ h₂
· simp
· simp [getElem_push]
split <;> rename_i h₃
· rfl
· congr
simp at h₁ h₂
omega
@[simp] theorem _rooy_.List.toArray_ofFn (f : Fin n α) : (List.ofFn f).toArray = Array.ofFn f := by
ext <;> simp
@[simp] theorem toList_ofFn (f : Fin n α) : (Array.ofFn f).toList = List.ofFn f := by
apply List.ext_getElem <;> simp
@[simp]
theorem ofFn_eq_empty_iff {f : Fin n α} : ofFn f = #[] n = 0 := by
rw [ Array.toList_inj]

View File

@@ -13,6 +13,7 @@ import Init.Data.Nat.Div.Lemmas
import Init.Data.Nat.Mod
import Init.Data.Nat.Div.Lemmas
import Init.Data.Int.Bitwise.Lemmas
import Init.Data.Int.LemmasAux
import Init.Data.Int.Pow
set_option linter.missingDocs true
@@ -569,6 +570,11 @@ theorem toInt_ofNat {n : Nat} (x : Nat) :
have p : 0 i % (2^n : Nat) := by omega
simp [toInt_eq_toNat_bmod, Int.toNat_of_nonneg p]
theorem toInt_ofInt_eq_self {w : Nat} (hw : 0 < w) {n : Int}
(h : -2 ^ (w - 1) n) (h' : n < 2 ^ (w - 1)) : (BitVec.ofInt w n).toInt = n := by
have hw : w = (w - 1) + 1 := by omega
rw [toInt_ofInt, Int.bmod_eq_self_of_le] <;> (rw [hw]; simp [Int.natCast_pow]; omega)
@[simp] theorem ofInt_natCast (w n : Nat) :
BitVec.ofInt w (n : Int) = BitVec.ofNat w n := rfl
@@ -2693,6 +2699,9 @@ theorem toInt_neg {x : BitVec w} :
rw [ BitVec.zero_sub, toInt_sub]
simp [BitVec.toInt_ofNat]
theorem ofInt_neg {w : Nat} {n : Int} : BitVec.ofInt w (-n) = -BitVec.ofInt w n :=
eq_of_toInt_eq (by simp [toInt_neg])
@[simp] theorem toFin_neg (x : BitVec n) :
(-x).toFin = Fin.ofNat' (2^n) (2^n - x.toNat) :=
rfl
@@ -4109,9 +4118,7 @@ theorem sub_le_sub_iff_le {x y z : BitVec w} (hxz : z ≤ x) (hyz : z ≤ y) :
theorem msb_eq_toInt {x : BitVec w}:
x.msb = decide (x.toInt < 0) := by
by_cases h : x.msb <;>
· simp [h, toInt_eq_msb_cond]
omega
by_cases h : x.msb <;> simp [h, toInt_eq_msb_cond] <;> omega
theorem msb_eq_toNat {x : BitVec w}:
x.msb = decide (x.toNat 2 ^ (w - 1)) := by

View File

@@ -45,6 +45,7 @@ theorem val_ne_iff {a b : Fin n} : a.1 ≠ b.1 ↔ a ≠ b := not_congr val_inj
theorem forall_iff {p : Fin n Prop} : ( i, p i) i h, p i, h :=
fun h i hi => h i, hi, fun h i, hi => h i hi
/-- Restatement of `Fin.mk.injEq` as an `iff`. -/
protected theorem mk.inj_iff {n a b : Nat} {ha : a < n} {hb : b < n} :
(a, ha : Fin n) = b, hb a = b := Fin.ext_iff
@@ -55,6 +56,14 @@ theorem eq_mk_iff_val_eq {a : Fin n} {k : Nat} {hk : k < n} :
theorem mk_val (i : Fin n) : (i, i.isLt : Fin n) = i := Fin.eta ..
@[simp] theorem mk_eq_zero {n a : Nat} {ha : a < n} [NeZero n] :
(a, ha : Fin n) = 0 a = 0 :=
mk.inj_iff
@[simp] theorem zero_eq_mk {n a : Nat} {ha : a < n} [NeZero n] :
0 = (a, ha : Fin n) a = 0 := by
simp [eq_comm]
@[simp] theorem val_ofNat' (n : Nat) [NeZero n] (a : Nat) :
(Fin.ofNat' n a).val = a % n := rfl

View File

@@ -17,10 +17,12 @@ open Nat
This file defines the `Int` type as well as
* coercions, conversions, and compatibility with numeric literals,
* basic arithmetic operations add/sub/mul/div/mod/pow,
* basic arithmetic operations add/sub/mul/pow,
* a few `Nat`-related operations such as `negOfNat` and `subNatNat`,
* relations `<`/`≤`/`≥`/`>`, the `NonNeg` property and `min`/`max`,
* decidability of equality, relations and `NonNeg`.
Division and modulus operations are defined in `Init.Data.Int.DivMod.Basic`.
-/
/--

View File

@@ -227,33 +227,4 @@ theorem cooper_resolution_dvd_right
· exact Int.mul_neg _ _ Int.neg_le_of_neg_le lower
· exact Int.mul_neg _ _ Int.neg_mul _ _ dvd
/--
Left Cooper resolution of an upper and lower bound.
-/
theorem cooper_resolution_left
{a b p q : Int} (a_pos : 0 < a) (b_pos : 0 < b) :
( x, p a * x b * x q)
( k : Int, 0 k k < a b * k + b * p a * q a k + p) := by
have h := cooper_resolution_dvd_left
a_pos b_pos Int.zero_lt_one (c := 1) (s := 0) (p := p) (q := q)
simp only [Int.mul_one, Int.one_mul, Int.mul_zero, Int.add_zero, gcd_one, Int.ofNat_one,
Int.ediv_one, lcm_self, Int.natAbs_of_nonneg (Int.le_of_lt a_pos), Int.one_dvd, and_true,
and_self] at h
exact h
/--
Right Cooper resolution of an upper and lower bound.
-/
theorem cooper_resolution_right
{a b p q : Int} (a_pos : 0 < a) (b_pos : 0 < b) :
( x, p a * x b * x q)
( k : Int, 0 k k < b a * k + b * p a * q b k - q) := by
have h := cooper_resolution_dvd_right
a_pos b_pos Int.zero_lt_one (c := 1) (s := 0) (p := p) (q := q)
have : k : Int, (b -k + q) (b k - q) := by
intro k
rw [ Int.dvd_neg, Int.neg_add, Int.neg_neg, Int.sub_eq_add_neg]
simp only [Int.mul_one, Int.one_mul, Int.mul_zero, Int.add_zero, gcd_one, Int.ofNat_one,
Int.ediv_one, lcm_self, Int.natAbs_of_nonneg (Int.le_of_lt b_pos), Int.one_dvd, and_true,
and_self, Int.neg_eq_neg_one_mul, this] at h
exact h
end Int

View File

@@ -21,25 +21,25 @@ and satisfy `x / 0 = 0` and `x % 0 = x`.
In early versions of Lean, the typeclasses provided by `/` and `%`
were defined in terms of `tdiv` and `tmod`, and these were named simply as `div` and `mod`.
However we decided it was better to use `ediv` and `emod`,
However we decided it was better to use `ediv` and `emod` for the default typeclass instances,
as they are consistent with the conventions used in SMTLib, and Mathlib,
and often mathematical reasoning is easier with these conventions.
At that time, we did not rename `div` and `mod` to `tdiv` and `tmod` (along with all their lemma).
In September 2024, we decided to do this rename (with deprecations in place),
and later we intend to rename `ediv` and `emod` to `div` and `mod`, as nearly all users will only
ever need to use these functions and their associated lemmas.
In December 2024, we removed `tdiv` and `tmod`, but have not yet renamed `ediv` and `emod`.
In December 2024, we removed `div` and `mod`, but have not yet renamed `ediv` and `emod`.
-/
/-! ### E-rounding division
This pair satisfies `0 ≤ mod x y < natAbs y` for `y ≠ 0`.
This pair satisfies `0 ≤ emod x y < natAbs y` for `y ≠ 0`.
-/
/--
Integer division. This version of `Int.div` uses the E-rounding convention
(euclidean division), in which `Int.emod x y` satisfies `0 ≤ mod x y < natAbs y` for `y ≠ 0`
Integer division. This version of integer division uses the E-rounding convention
(euclidean division), in which `Int.emod x y` satisfies `0 ≤ emod x y < natAbs y` for `y ≠ 0`
and `Int.ediv` is the unique function satisfying `emod x y + (ediv x y) * y = x`.
This is the function powering the `/` notation on integers.
@@ -71,7 +71,7 @@ def ediv : (@& Int) → (@& Int) → Int
| -[m+1], -[n+1] => ofNat (succ (m / succ n))
/--
Integer modulus. This version of `Int.mod` uses the E-rounding convention
Integer modulus. This version of integer modulus uses the E-rounding convention
(euclidean division), in which `Int.emod x y` satisfies `0 ≤ emod x y < natAbs y` for `y ≠ 0`
and `Int.ediv` is the unique function satisfying `emod x y + (ediv x y) * y = x`.
@@ -229,7 +229,7 @@ def fdiv : Int → Int → Int
| -[m+1], -[n+1] => ofNat (succ m / succ n)
/--
Integer modulus. This version of `Int.mod` uses the F-rounding convention
Integer modulus. This version of integer modulus uses the F-rounding convention
(flooring division), in which `Int.fdiv x y` satisfies `fdiv x y = floor (x / y)`
and `Int.fmod` is the unique function satisfying `fmod x y + (fdiv x y) * y = x`.
@@ -268,11 +268,14 @@ Balanced mod (and balanced div) are a division and modulus pair such
that `b * (Int.bdiv a b) + Int.bmod a b = a` and
`-b/2 ≤ Int.bmod a b < b/2` for all `a : Int` and `b > 0`.
This is used in Omega as well as signed bitvectors.
Note that unlike `emod`, `fmod`, and `tmod`,
`bmod` takes a natural number as the second argument, rather than an integer.
This function is used in `omega` as well as signed bitvectors.
-/
/--
Balanced modulus. This version of Integer modulus uses the
Balanced modulus. This version of integer modulus uses the
balanced rounding convention, which guarantees that
`-m/2 ≤ bmod x m < m/2` for `m ≠ 0` and `bmod x m` is congruent
to `x` modulo `m`.

View File

@@ -18,7 +18,7 @@ open Nat (succ)
namespace Int
-- /-! ### dvd -/
/-! ### dvd -/
protected theorem dvd_def (a b : Int) : (a b) = Exists (fun c => b = a * c) := rfl
@@ -67,7 +67,7 @@ protected theorem dvd_neg {a b : Int} : a -b ↔ a b := by
theorem ofNat_dvd_left {n : Nat} {z : Int} : (n : Int) z n z.natAbs := by
rw [ natAbs_dvd_natAbs, natAbs_ofNat]
/-! ### *div zero -/
/-! ### ediv zero -/
@[simp] theorem zero_ediv : b : Int, 0 / b = 0
| ofNat _ => show ofNat _ = _ by simp
@@ -77,7 +77,7 @@ theorem ofNat_dvd_left {n : Nat} {z : Int} : (↑n : Int) z ↔ n z.natA
| ofNat _ => show ofNat _ = _ by simp
| -[_+1] => rfl
/-! ### mod zero -/
/-! ### emod zero -/
@[simp] theorem zero_emod (b : Int) : 0 % b = 0 := rfl
@@ -89,7 +89,6 @@ theorem ofNat_dvd_left {n : Nat} {z : Int} : (↑n : Int) z ↔ n z.natA
@[simp, norm_cast] theorem ofNat_emod (m n : Nat) : ((m % n) : Int) = m % n := rfl
/-! ### mod definitions -/
theorem emod_add_ediv : a b : Int, a % b + b * (a / b) = a
@@ -106,12 +105,17 @@ where
Int.neg_neg (_-_), Int.neg_sub, Int.sub_sub_self, Int.add_right_comm]
exact congrArg (fun x => -(ofNat x + 1)) (Nat.mod_add_div ..)
/-- Variant of `emod_add_ediv` with the multiplication written the other way around. -/
theorem emod_add_ediv' (a b : Int) : a % b + a / b * b = a := by
rw [Int.mul_comm]; exact emod_add_ediv ..
theorem ediv_add_emod (a b : Int) : b * (a / b) + a % b = a := by
rw [Int.add_comm]; exact emod_add_ediv ..
/-- Variant of `ediv_add_emod` with the multiplication written the other way around. -/
theorem ediv_add_emod' (a b : Int) : a / b * b + a % b = a := by
rw [Int.mul_comm]; exact ediv_add_emod ..
theorem emod_def (a b : Int) : a % b = a - b * (a / b) := by
rw [ Int.add_sub_cancel (a % b), emod_add_ediv]
@@ -170,7 +174,7 @@ theorem add_ediv_of_dvd_left {a b c : Int} (H : c a) : (a + b) / c = a / c +
@[simp] theorem mul_ediv_cancel_left (b : Int) (H : a 0) : (a * b) / a = b :=
Int.mul_comm .. Int.mul_ediv_cancel _ H
theorem div_nonneg_iff_of_pos {a b : Int} (h : 0 < b) : a / b 0 a 0 := by
theorem ediv_nonneg_iff_of_pos {a b : Int} (h : 0 < b) : 0 a / b 0 a := by
rw [Int.div_def]
match b, h with
| Int.ofNat (b+1), _ =>
@@ -178,6 +182,9 @@ theorem div_nonneg_iff_of_pos {a b : Int} (h : 0 < b) : a / b ≥ 0 ↔ a ≥ 0
norm_cast
simp
@[deprecated ediv_nonneg_iff_of_pos (since := "2025-02-28")]
abbrev div_nonneg_iff_of_pos := @ediv_nonneg_iff_of_pos
/-! ### emod -/
theorem emod_nonneg : (a : Int) {b : Int}, b 0 0 a % b

View File

@@ -94,6 +94,14 @@ theorem eq_one_of_mul_eq_one_left {a b : Int} (H : 0 ≤ b) (H' : a * b = 1) : b
instance decidableDvd : DecidableRel (α := Int) (· ·) := fun _ _ =>
decidable_of_decidable_of_iff (dvd_iff_emod_eq_zero ..).symm
protected theorem mul_dvd_mul_iff_left {a b c : Int} (h : a 0) : (a * b) (a * c) b c :=
by rintro d, h'; exact d, by rw [Int.mul_assoc] at h'; exact (mul_eq_mul_left_iff h).mp h',
by rintro d, rfl; exact d, by simp [Int.mul_assoc]
protected theorem mul_dvd_mul_iff_right {a b c : Int} (h : a 0) : (b * a) (c * a) b c := by
rw [Int.mul_comm b a, Int.mul_comm c a]
exact Int.mul_dvd_mul_iff_left h
/-! ### *div zero -/
@[simp] protected theorem zero_tdiv : b : Int, tdiv 0 b = 0
@@ -234,6 +242,13 @@ theorem tdiv_eq_fdiv {a b : Int} :
rw [fdiv_eq_tdiv]
omega
theorem tdiv_eq_ediv_of_dvd {a b : Int} (h : b a) : a.tdiv b = a / b := by
simp [tdiv_eq_ediv, h]
theorem fdiv_eq_ediv_of_dvd {a b : Int} (h : b a) : a.fdiv b = a / b := by
simp [fdiv_eq_ediv, h]
/-! ### mod zero -/
@[simp] theorem zero_tmod (b : Int) : tmod 0 b = 0 := by cases b <;> simp [tmod]
@@ -251,9 +266,6 @@ theorem tdiv_eq_fdiv {a b : Int} :
/-! ### mod definitions -/
theorem ediv_add_emod' (a b : Int) : a / b * b + a % b = a := by
rw [Int.mul_comm]; exact ediv_add_emod ..
theorem tmod_add_tdiv : a b : Int, tmod a b + b * (a.tdiv b) = a
| ofNat _, ofNat _ => congrArg ofNat (Nat.mod_add_div ..)
| ofNat m, -[n+1] => by
@@ -274,9 +286,11 @@ theorem tmod_add_tdiv : ∀ a b : Int, tmod a b + b * (a.tdiv b) = a
theorem tdiv_add_tmod (a b : Int) : b * a.tdiv b + tmod a b = a := by
rw [Int.add_comm]; apply tmod_add_tdiv ..
/-- Variant of `tmod_add_tdiv` with the multiplication written the other way around. -/
theorem tmod_add_tdiv' (m k : Int) : tmod m k + m.tdiv k * k = m := by
rw [Int.mul_comm]; apply tmod_add_tdiv
/-- Variant of `tdiv_add_tmod` with the multiplication written the other way around. -/
theorem tdiv_add_tmod' (m k : Int) : m.tdiv k * k + tmod m k = m := by
rw [Int.mul_comm]; apply tdiv_add_tmod
@@ -300,9 +314,17 @@ theorem fmod_add_fdiv : ∀ a b : Int, a.fmod b + b * a.fdiv b = a
show -((succ m % succ n) : Int) + -(succ n * (succ m / succ n)) = -(succ m)
rw [ Int.neg_add]; exact congrArg (-ofNat ·) <| Nat.mod_add_div ..
/-- Variant of `fmod_add_fdiv` with the multiplication written the other way around. -/
theorem fmod_add_fdiv' (a b : Int) : a.fmod b + (a.fdiv b) * b = a := by
rw [Int.mul_comm]; exact fmod_add_fdiv ..
theorem fdiv_add_fmod (a b : Int) : b * a.fdiv b + a.fmod b = a := by
rw [Int.add_comm]; exact fmod_add_fdiv ..
/-- Variant of `fdiv_add_fmod` with the multiplication written the other way around. -/
theorem fdiv_add_fmod' (a b : Int) : (a.fdiv b) * b + a.fmod b = a := by
rw [Int.mul_comm]; exact fdiv_add_fmod ..
theorem fmod_def (a b : Int) : a.fmod b = a - b * a.fdiv b := by
rw [ Int.add_sub_cancel (a.fmod b), fmod_add_fdiv]
@@ -396,6 +418,11 @@ theorem ediv_nonneg_of_nonpos_of_nonpos {a b : Int} (Ha : a ≤ 0) (Hb : b ≤ 0
rw [Int.div_def, ediv]
exact le_add_one (ediv_nonneg (ofNat_zero_le a) (Int.le_trans (ofNat_zero_le b) (le.intro 1 rfl)))
theorem ediv_pos_of_neg_of_neg {a b : Int} (ha : a < 0) (hb : b < 0) : 0 < a / b := by
rw [Int.div_def]
match a, b, ha, hb with
| .negSucc a, .negSucc b, _, _ => apply ofNat_succ_pos
theorem ediv_nonpos {a b : Int} (Ha : 0 a) (Hb : b 0) : a / b 0 :=
Int.nonpos_of_neg_nonneg <| Int.ediv_neg .. Int.ediv_nonneg Ha (Int.neg_nonneg_of_nonpos Hb)
@@ -446,6 +473,10 @@ protected theorem ediv_eq_of_eq_mul_left {a b c : Int}
(H1 : b 0) (H2 : a = c * b) : a / b = c :=
Int.ediv_eq_of_eq_mul_right H1 (by rw [Int.mul_comm, H2])
protected theorem eq_ediv_of_mul_eq_left {a b c : Int}
(H1 : b 0) (H2 : a * b = c) : a = c / b :=
(Int.ediv_eq_of_eq_mul_left H1 H2.symm).symm
/-! ### emod -/
theorem mod_def' (m n : Int) : m % n = emod m n := rfl
@@ -715,16 +746,100 @@ theorem ediv_eq_ediv_of_mul_eq_mul {a b c d : Int}
/-! ### tdiv -/
@[simp] protected theorem tdiv_one : a : Int, a.tdiv 1 = a
| (n:Nat) => congrArg ofNat (Nat.div_one _)
| -[n+1] => by simp [Int.tdiv, neg_ofNat_succ]; rfl
unseal Nat.div in
@[simp] protected theorem tdiv_neg : a b : Int, a.tdiv (-b) = -(a.tdiv b)
| ofNat m, 0 => show ofNat (m / 0) = -(m / 0) by rw [Nat.div_zero]; rfl
| ofNat _, -[_+1] | -[_+1], succ _ => (Int.neg_neg _).symm
| ofNat _, succ _ | -[_+1], 0 | -[_+1], -[_+1] => rfl
/-!
We don't give `tdiv` versions of
* `add_mul_ediv_right : c ≠ 0 → (a + b * c) / c = a / c + b`
* `add_mul_ediv_left : b ≠ 0 → (a + b * c) / b = a / b + c`
* `add_ediv_of_dvd_right : c b → (a + b) / c = a / c + b / c`
* `add_ediv_of_dvd_left : c a → (a + b) / c = a / c + b / c`
because they all involve awkward off-by-one corrections.
-/
@[simp] theorem mul_tdiv_cancel (a : Int) {b : Int} (H : b 0) : (a * b).tdiv b = a := by
rw [tdiv_eq_ediv_of_dvd (Int.dvd_mul_left a b), mul_ediv_cancel _ H]
@[simp] theorem mul_tdiv_cancel_left (b : Int) (H : a 0) : (a * b).tdiv a = b :=
Int.mul_comm .. Int.mul_tdiv_cancel _ H
-- There's no good analogues of `ediv_nonneg_iff_of_pos`, `ediv_neg'`, or `negSucc_ediv`
-- for `tdiv`.
protected theorem tdiv_nonneg {a b : Int} (Ha : 0 a) (Hb : 0 b) : 0 a.tdiv b :=
match a, b, eq_ofNat_of_zero_le Ha, eq_ofNat_of_zero_le Hb with
| _, _, _, rfl, _, rfl => ofNat_zero_le _
theorem tdiv_nonneg_of_nonpos_of_nonpos {a b : Int} (Ha : a 0) (Hb : b 0) : 0 a.tdiv b := by
rw [tdiv_eq_ediv]
split <;> rename_i h
· simpa using ediv_nonneg_of_nonpos_of_nonpos Ha Hb
· simp at h
by_cases h' : b = 0
· subst h'
simp
· replace h' : b < 0 := by omega
rw [sign_eq_neg_one_of_neg h']
have : 0 < a / b := by
by_cases h'' : a = 0
· subst h''
simp at h
· replace h'' : a < 0 := by omega
exact ediv_pos_of_neg_of_neg h'' h'
omega
protected theorem tdiv_nonpos {a b : Int} (Ha : 0 a) (Hb : b 0) : a.tdiv b 0 :=
Int.nonpos_of_neg_nonneg <| Int.tdiv_neg .. Int.tdiv_nonneg Ha (Int.neg_nonneg_of_nonpos Hb)
theorem tdiv_eq_zero_of_lt {a b : Int} (H1 : 0 a) (H2 : a < b) : a.tdiv b = 0 :=
match a, b, eq_ofNat_of_zero_le H1, eq_succ_of_zero_lt (Int.lt_of_le_of_lt H1 H2) with
| _, _, _, rfl, _, rfl => congrArg Nat.cast <| Nat.div_eq_of_lt <| ofNat_lt.1 H2
@[simp] theorem mul_tdiv_mul_of_pos {a : Int}
(b c : Int) (H : 0 < a) : (a * b).tdiv (a * c) = b.tdiv c := by
rw [tdiv_eq_ediv, mul_ediv_mul_of_pos _ _ H, tdiv_eq_ediv]
simp only [sign_mul]
by_cases h : 0 b
· rw [if_pos, if_pos (.inl h)]
left
exact Int.mul_nonneg (Int.le_of_lt H) h
· have H' : a 0 := by omega
simp only [Int.mul_dvd_mul_iff_left H']
by_cases h' : c b
· simp [h']
· rw [if_neg, if_neg]
· simp [sign_eq_one_of_pos H]
· simp [h']; omega
· simp_all only [Int.not_le, ne_eq, or_false]
exact Int.mul_neg_of_pos_of_neg H h
@[simp] theorem mul_tdiv_mul_of_pos_left
(a : Int) {b : Int} (c : Int) (H : 0 < b) : (a * b).tdiv (c * b) = a.tdiv c := by
rw [Int.mul_comm, Int.mul_comm c, mul_tdiv_mul_of_pos _ _ H]
@[simp] protected theorem tdiv_one : a : Int, a.tdiv 1 = a
| (n:Nat) => congrArg ofNat (Nat.div_one _)
| -[n+1] => by simp [Int.tdiv, neg_ofNat_succ]; rfl
protected theorem tdiv_eq_of_eq_mul_right {a b c : Int}
(H1 : b 0) (H2 : a = b * c) : a.tdiv b = c := by rw [H2, Int.mul_tdiv_cancel_left _ H1]
protected theorem eq_tdiv_of_mul_eq_right {a b c : Int}
(H1 : a 0) (H2 : a * b = c) : b = c.tdiv a :=
(Int.tdiv_eq_of_eq_mul_right H1 H2.symm).symm
protected theorem tdiv_eq_of_eq_mul_left {a b c : Int}
(H1 : b 0) (H2 : a = c * b) : a.tdiv b = c :=
Int.tdiv_eq_of_eq_mul_right H1 (by rw [Int.mul_comm, H2])
protected theorem eq_tdiv_of_mul_eq_left {a b c : Int}
(H1 : b 0) (H2 : a * b = c) : a = c.tdiv b :=
(Int.tdiv_eq_of_eq_mul_left H1 H2.symm).symm
unseal Nat.div in
@[simp] protected theorem neg_tdiv : a b : Int, (-a).tdiv b = -(a.tdiv b)
| 0, n => by simp [Int.neg_zero]
@@ -734,33 +849,6 @@ unseal Nat.div in
protected theorem neg_tdiv_neg (a b : Int) : (-a).tdiv (-b) = a.tdiv b := by
simp [Int.tdiv_neg, Int.neg_tdiv, Int.neg_neg]
protected theorem tdiv_nonneg {a b : Int} (Ha : 0 a) (Hb : 0 b) : 0 a.tdiv b :=
match a, b, eq_ofNat_of_zero_le Ha, eq_ofNat_of_zero_le Hb with
| _, _, _, rfl, _, rfl => ofNat_zero_le _
protected theorem tdiv_nonpos {a b : Int} (Ha : 0 a) (Hb : b 0) : a.tdiv b 0 :=
Int.nonpos_of_neg_nonneg <| Int.tdiv_neg .. Int.tdiv_nonneg Ha (Int.neg_nonneg_of_nonpos Hb)
theorem tdiv_eq_zero_of_lt {a b : Int} (H1 : 0 a) (H2 : a < b) : a.tdiv b = 0 :=
match a, b, eq_ofNat_of_zero_le H1, eq_succ_of_zero_lt (Int.lt_of_le_of_lt H1 H2) with
| _, _, _, rfl, _, rfl => congrArg Nat.cast <| Nat.div_eq_of_lt <| ofNat_lt.1 H2
@[simp] protected theorem mul_tdiv_cancel (a : Int) {b : Int} (H : b 0) : (a * b).tdiv b = a :=
have : {a b : Nat}, (b : Int) 0 (tdiv (a * b) b : Int) = a := fun H => by
rw [ ofNat_mul, ofNat_tdiv,
Nat.mul_div_cancel _ <| Nat.pos_of_ne_zero <| Int.ofNat_ne_zero.1 H]
match a, b, a.eq_nat_or_neg, b.eq_nat_or_neg with
| _, _, a, .inl rfl, b, .inl rfl => this H
| _, _, a, .inl rfl, b, .inr rfl => by
rw [Int.mul_neg, Int.neg_tdiv, Int.tdiv_neg, Int.neg_neg,
this (Int.neg_ne_zero.1 H)]
| _, _, a, .inr rfl, b, .inl rfl => by rw [Int.neg_mul, Int.neg_tdiv, this H]
| _, _, a, .inr rfl, b, .inr rfl => by
rw [Int.neg_mul_neg, Int.tdiv_neg, this (Int.neg_ne_zero.1 H)]
@[simp] protected theorem mul_tdiv_cancel_left (b : Int) (H : a 0) : (a * b).tdiv a = b :=
Int.mul_comm .. Int.mul_tdiv_cancel _ H
@[simp] protected theorem tdiv_self {a : Int} (H : a 0) : a.tdiv a = 1 := by
have := Int.mul_tdiv_cancel 1 H; rwa [Int.one_mul] at this
@@ -796,14 +884,7 @@ theorem tdiv_dvd_tdiv : ∀ {a b c : Int}, a b → b c → b.tdiv a
| _, _, _, .inr rfl, _, .inl rfl => by rw [Int.neg_tdiv, natAbs_neg, natAbs_neg]; rfl
| _, _, _, .inr rfl, _, .inr rfl => by rw [Int.neg_tdiv_neg, natAbs_neg, natAbs_neg]; rfl
protected theorem tdiv_eq_of_eq_mul_right {a b c : Int}
(H1 : b 0) (H2 : a = b * c) : a.tdiv b = c := by rw [H2, Int.mul_tdiv_cancel_left _ H1]
protected theorem eq_tdiv_of_mul_eq_right {a b c : Int}
(H1 : a 0) (H2 : a * b = c) : b = c.tdiv a :=
(Int.tdiv_eq_of_eq_mul_right H1 H2.symm).symm
/-! ### (t-)mod -/
/-! ### tmod -/
theorem ofNat_tmod (m n : Nat) : ((m % n) : Int) = tmod m n := rfl
@@ -878,9 +959,6 @@ protected theorem eq_mul_of_tdiv_eq_left {a b c : Int}
(H1 : b a) (H2 : a.tdiv b = c) : a = c * b := by
rw [Int.mul_comm, Int.eq_mul_of_tdiv_eq_right H1 H2]
protected theorem tdiv_eq_of_eq_mul_left {a b c : Int}
(H1 : b 0) (H2 : a = c * b) : a.tdiv b = c :=
Int.tdiv_eq_of_eq_mul_right H1 (by rw [Int.mul_comm, H2])
protected theorem eq_zero_of_tdiv_eq_zero {d n : Int} (h : d n) (H : n.tdiv d = 0) : n = 0 := by
rw [ Int.mul_tdiv_cancel' h, H, Int.mul_zero]
@@ -968,19 +1046,6 @@ theorem fmod_lt_of_pos (a : Int) {b : Int} (H : 0 < b) : a.fmod b < b :=
@[simp] theorem fmod_self {a : Int} : a.fmod a = 0 := by
have := mul_fmod_left 1 a; rwa [Int.one_mul] at this
/-! ### Theorems crossing div/mod versions -/
theorem tdiv_eq_ediv_of_dvd {a b : Int} (h : b a) : a.tdiv b = a / b := by
by_cases b0 : b = 0
· simp [b0]
· rw [Int.tdiv_eq_iff_eq_mul_left b0 h, Int.ediv_eq_iff_eq_mul_left b0 h]
theorem fdiv_eq_ediv_of_dvd : {a b : Int}, b a a.fdiv b = a / b
| _, b, c, rfl => by
by_cases bz : b = 0
· simp [bz]
· rw [mul_fdiv_cancel_left _ bz, mul_ediv_cancel_left _ bz]
/-! ### bmod -/
@[simp]

View File

@@ -46,4 +46,23 @@ theorem bmod_neg_iff {m : Nat} {x : Int} (h2 : -m ≤ x) (h1 : x < m) :
· rw [Int.emod_eq_of_lt xpos (by omega)]; omega
· rw [Int.add_emod_self.symm, Int.emod_eq_of_lt (by omega) (by omega)]; omega
@[simp] theorem natCast_le_zero : {n : Nat} (n : Int) 0 n = 0 := by omega
@[simp] theorem toNat_eq_zero : {n : Int}, n.toNat = 0 n 0 := by omega
theorem eq_zero_of_dvd_of_natAbs_lt_natAbs {d n : Int} (h : d n) (h₁ : n.natAbs < d.natAbs) :
n = 0 := by
obtain a, rfl := h
rw [natAbs_mul] at h₁
suffices ¬ 0 < a.natAbs by simp [Int.natAbs_eq_zero.1 (Nat.eq_zero_of_not_pos this)]
exact fun h => Nat.lt_irrefl _ (Nat.lt_of_le_of_lt (Nat.le_mul_of_pos_right d.natAbs h) h₁)
theorem bmod_eq_self_of_le {n : Int} {m : Nat} (hn' : -(m / 2) n) (hn : n < (m + 1) / 2) :
n.bmod m = n := by
rw [ Int.sub_eq_zero]
have := le_bmod (x := n) (m := m) (by omega)
have := bmod_lt (x := n) (m := m) (by omega)
apply eq_zero_of_dvd_of_natAbs_lt_natAbs Int.dvd_bmod_sub_self
omega
end Int

View File

@@ -9,6 +9,7 @@ import Init.Data.Prod
import Init.Data.Int.Lemmas
import Init.Data.Int.LemmasAux
import Init.Data.Int.DivMod.Bootstrap
import Init.Data.Int.Cooper
import Init.Data.Int.Gcd
import Init.Data.RArray
import Init.Data.AC
@@ -531,8 +532,9 @@ def Poly.isValidLe (p : Poly) : Bool :=
| .num k => k 0
| _ => false
attribute [-simp] Int.not_le in
theorem le_eq_false (ctx : Context) (lhs rhs : Expr) : (lhs.sub rhs).norm.isUnsatLe (lhs.denote ctx rhs.denote ctx) = False := by
simp [Poly.isUnsatLe] <;> split <;> simp
simp only [Poly.isUnsatLe] <;> split <;> simp
next p k h =>
intro h'
replace h := congrArg (Poly.denote ctx) h
@@ -820,7 +822,7 @@ def le_neg_cert (p₁ p₂ : Poly) : Bool :=
theorem le_neg (ctx : Context) (p₁ p₂ : Poly) : le_neg_cert p₁ p₂ ¬ p₁.denote' ctx 0 p₂.denote' ctx 0 := by
simp [le_neg_cert]
intro; subst p₂; simp; intro h
replace h : _ + 1 -0 := Int.neg_lt_neg <| Int.lt_of_not_ge h
replace h : _ + 1 -0 := Int.neg_lt_neg h
simp at h
exact h
@@ -846,9 +848,6 @@ theorem le_combine (ctx : Context) (p₁ p₂ p₃ : Poly)
theorem le_unsat (ctx : Context) (p : Poly) : p.isUnsatLe p.denote' ctx 0 False := by
simp [Poly.isUnsatLe]; split <;> simp
intro h₁ h₂
have := Int.lt_of_le_of_lt h₂ h₁
simp at this
theorem eq_norm (ctx : Context) (p₁ p₂ : Poly) (h : p₁.norm == p₂) : p₁.denote' ctx = 0 p₂.denote' ctx = 0 := by
simp at h
@@ -1021,6 +1020,9 @@ theorem diseq_coeff (ctx : Context) (p p' : Poly) (k : Int) : eq_coeff_cert p p'
simp [eq_coeff_cert]
intro _ _; simp [mul_eq_zero_iff, *]
theorem diseq_neg (ctx : Context) (p p' : Poly) : p' == p.mul (-1) p.denote' ctx 0 p'.denote' ctx 0 := by
simp; intro _ _; simp [mul_eq_zero_iff, *]
theorem diseq_unsat (ctx : Context) (p : Poly) : p.isUnsatDiseq p.denote' ctx 0 False := by
simp [Poly.isUnsatDiseq] <;> split <;> simp
@@ -1043,6 +1045,434 @@ theorem diseq_of_core (ctx : Context) (p₁ : Poly) (p₂ : Poly) (p₃ : Poly)
intro h; rw [ Int.sub_eq_zero] at h
rw [Int.sub_eq_add_neg]; assumption
def eq_of_le_ge_cert (p₁ p₂ : Poly) : Bool :=
p₂ == p₁.mul (-1)
theorem eq_of_le_ge (ctx : Context) (p₁ : Poly) (p₂ : Poly)
: eq_of_le_ge_cert p₁ p₂ p₁.denote' ctx 0 p₂.denote' ctx 0 p₁.denote' ctx = 0 := by
simp [eq_of_le_ge_cert]
intro; subst p₂; simp
intro h₁ h₂
replace h₂ := Int.neg_le_of_neg_le h₂; simp at h₂
simp [Int.eq_iff_le_and_ge, *]
def le_of_le_diseq_cert (p₁ : Poly) (p₂ : Poly) (p₃ : Poly) : Bool :=
-- Remark: we can generate two different certificates in the future, and avoid the `||` in the certificate.
(p₂ == p₁ || p₂ == p₁.mul (-1)) &&
p₃ == p₁.addConst 1
theorem le_of_le_diseq (ctx : Context) (p₁ : Poly) (p₂ : Poly) (p₃ : Poly)
: le_of_le_diseq_cert p₁ p₂ p₃ p₁.denote' ctx 0 p₂.denote' ctx 0 p₃.denote' ctx 0 := by
simp [le_of_le_diseq_cert]
have (a : Int) : a 0 ¬ a = 0 1 + a 0 := by
intro h₁ h₂; cases (Int.lt_or_gt_of_ne h₂)
next => apply Int.le_of_lt_add_one; rw [Int.add_comm, Int.add_lt_add_iff_right]; assumption
next h => have := Int.lt_of_le_of_lt h₁ h; simp at this
intro h; cases h <;> intro <;> subst p₂ p₃ <;> simp <;> apply this
def diseq_split_cert (p₁ p₂ p₃ : Poly) : Bool :=
p₂ == p₁.addConst 1 &&
p₃ == (p₁.mul (-1)).addConst 1
theorem diseq_split (ctx : Context) (p₁ p₂ p₃ : Poly)
: diseq_split_cert p₁ p₂ p₃ p₁.denote' ctx 0 p₂.denote' ctx 0 p₃.denote' ctx 0 := by
simp [diseq_split_cert]
intro _ _; subst p₂ p₃; simp
generalize p₁.denote ctx = p
intro h; cases Int.lt_or_gt_of_ne h
next h => have := Int.add_one_le_of_lt h; rw [Int.add_comm]; simp [*]
next h => have := Int.add_one_le_of_lt (Int.neg_lt_neg h); simp at this; simp [*]
theorem diseq_split_resolve (ctx : Context) (p₁ p₂ p₃ : Poly)
: diseq_split_cert p₁ p₂ p₃ p₁.denote' ctx 0 ¬p₂.denote' ctx 0 p₃.denote' ctx 0 := by
intro h₁ h₂ h₃
exact (diseq_split ctx p₁ p₂ p₃ h₁ h₂).resolve_left h₃
def OrOver (n : Nat) (p : Nat Prop) : Prop :=
match n with
| 0 => False
| n+1 => p n OrOver n p
theorem orOver_unsat {p} : ¬ OrOver 0 p := by simp [OrOver]
theorem orOver_resolve {n p} : OrOver (n+1) p ¬ p n OrOver n p := by
intro h₁ h₂
rw [OrOver] at h₁
cases h₁
· contradiction
· assumption
private theorem orOver_of_p {i n p} (h₁ : i < n) (h₂ : p i) : OrOver n p := by
induction n
next => simp at h₁
next n ih =>
simp [OrOver]
cases Nat.eq_or_lt_of_le <| Nat.le_of_lt_add_one h₁
next h => subst i; exact Or.inl h₂
next h => exact Or.inr (ih h)
private theorem orOver_of_exists {n p} : ( k, k < n p k) OrOver n p := by
intro k, h₁, h₂
apply orOver_of_p h₁ h₂
private theorem ofNat_toNat {a : Int} : a 0 Int.ofNat a.toNat = a := by cases a <;> simp
private theorem cast_toNat {a : Int} : a 0 a.toNat = a := by cases a <;> simp
private theorem ofNat_lt {a : Int} {n : Nat} : a 0 a < Int.ofNat n a.toNat < n := by cases a <;> simp
@[local simp] private theorem lcm_neg_left (a b : Int) : Int.lcm (-a) b = Int.lcm a b := by simp [Int.lcm]
@[local simp] private theorem lcm_neg_right (a b : Int) : Int.lcm a (-b) = Int.lcm a b := by simp [Int.lcm]
@[local simp] private theorem gcd_neg_left (a b : Int) : Int.gcd (-a) b = Int.gcd a b := by simp [Int.gcd]
@[local simp] private theorem gcd_neg_right (a b : Int) : Int.gcd a (-b) = Int.gcd a b := by simp [Int.gcd]
@[local simp] private theorem gcd_zero (a : Int) : Int.gcd a 0 = a.natAbs := by simp [Int.gcd]
@[local simp] private theorem lcm_one (a : Int) : Int.lcm a 1 = a.natAbs := by simp [Int.lcm]
private theorem cooper_dvd_left_core
{a b c d s p q x : Int} (a_neg : a < 0) (b_pos : 0 < b) (d_pos : 0 < d)
(h₁ : a * x + p 0)
(h₂ : b * x + q 0)
(h₃ : d c * x + s)
: OrOver (Int.lcm a (a * d / Int.gcd (a * d) c)) fun k =>
b * p + (-a) * q + b * k 0
a p + k
a * d c * p + (-a) * s + c * k := by
have a_pos' : 0 < -a := by apply Int.neg_pos_of_neg; assumption
have h₁' : p (-a)*x := by rw [Int.neg_mul, Lean.Omega.Int.add_le_zero_iff_le_neg']; assumption
have h₂' : b * x -q := by rw [ Lean.Omega.Int.add_le_zero_iff_le_neg', Int.add_comm]; assumption
have k, h₁, h₂, h₃, h₄, h₅ := Int.cooper_resolution_dvd_left a_pos' b_pos d_pos |>.mp x, h₁', h₂', h₃
rw [Int.neg_mul] at h₂
simp only [Int.neg_mul, neg_gcd, lcm_neg_left, Int.mul_neg, Int.neg_neg, Int.neg_dvd] at *
rw [Int.neg_ediv_of_dvd Int.gcd_dvd_left] at h₂
simp only [lcm_neg_right] at h₂
have : c * k + c * p + -(a * s) = c * p + -(a * s) + c * k := by ac_rfl
rw [this] at h₅; clear this
rw [ ofNat_toNat h₁] at h₃ h₄ h₅
rw [Int.add_comm] at h₄
have := ofNat_lt h₁ h₂
apply orOver_of_exists
replace h₃ := Int.add_le_add_right h₃ (-(a*q)); rw [Int.add_right_neg] at h₃
have : b * Int.ofNat k.toNat + b * p + -(a * q) = b * p + -(a * q) + b * Int.ofNat k.toNat := by ac_rfl
rw [this] at h₃
exists k.toNat
def cooper_dvd_left_cert (p₁ p₂ p₃ : Poly) (d : Int) (n : Nat) : Bool :=
p₁.casesOn (fun _ => false) fun a x _ =>
p₂.casesOn (fun _ => false) fun b y _ =>
p₃.casesOn (fun _ => false) fun c z _ =>
.and (x == y) <| .and (x == z) <|
.and (a < 0) <| .and (b > 0) <|
.and (d > 0) <| n == Int.lcm a (a * d / Int.gcd (a * d) c)
def Poly.tail (p : Poly) : Poly :=
match p with
| .add _ _ p => p
| _ => p
def cooper_dvd_left_split (ctx : Context) (p₁ p₂ p₃ : Poly) (d : Int) (k : Nat) : Prop :=
let p := p₁.tail
let q := p₂.tail
let s := p₃.tail
let a := p₁.leadCoeff
let b := p₂.leadCoeff
let c := p₃.leadCoeff
let p₁ := p.mul b |>.combine (q.mul (-a))
let p₂ := p.mul c |>.combine (s.mul (-a))
(p₁.addConst (b*k)).denote' ctx 0
a (p.addConst k).denote' ctx
a*d (p₂.addConst (c*k)).denote' ctx
private theorem denote'_mul_combine_mul_addConst_eq (ctx : Context) (p q : Poly) (a b c : Int)
: ((p.mul b |>.combine (q.mul a)).addConst c).denote' ctx = b*p.denote ctx + a*q.denote ctx + c := by
simp
private theorem denote'_addConst_eq (ctx : Context) (p : Poly) (a : Int)
: (p.addConst a).denote' ctx = p.denote ctx + a := by
simp
theorem cooper_dvd_left (ctx : Context) (p₁ p₂ p₃ : Poly) (d : Int) (n : Nat)
: cooper_dvd_left_cert p₁ p₂ p₃ d n
p₁.denote' ctx 0
p₂.denote' ctx 0
d p₃.denote' ctx
OrOver n (cooper_dvd_left_split ctx p₁ p₂ p₃ d) := by
unfold cooper_dvd_left_split
cases p₁ <;> cases p₂ <;> cases p₃ <;> simp [cooper_dvd_left_cert, Poly.tail, -Poly.denote'_eq_denote]
next a x p b y q c z s =>
intro _ _; subst y z
intro ha hb hd
intro; subst n
simp only [Poly.denote'_add, Poly.leadCoeff]
intro h₁ h₂ h₃
simp only [denote'_mul_combine_mul_addConst_eq]
simp only [denote'_addConst_eq]
exact cooper_dvd_left_core ha hb hd h₁ h₂ h₃
def cooper_dvd_left_split_ineq_cert (p₁ p₂ : Poly) (k : Int) (b : Int) (p' : Poly) : Bool :=
let p := p₁.tail
let q := p₂.tail
let a := p₁.leadCoeff
let p₁ := p.mul b |>.combine (q.mul (-a))
p₂.leadCoeff == b && p' == p₁.addConst (b*k)
theorem cooper_dvd_left_split_ineq (ctx : Context) (p₁ p₂ p₃ : Poly) (d : Int) (k : Nat) (b : Int) (p' : Poly)
: cooper_dvd_left_split ctx p₁ p₂ p₃ d k cooper_dvd_left_split_ineq_cert p₁ p₂ k b p' p'.denote ctx 0 := by
simp [cooper_dvd_left_split_ineq_cert, cooper_dvd_left_split]
intros; subst p' b; simp [denote'_mul_combine_mul_addConst_eq]; assumption
def cooper_dvd_left_split_dvd1_cert (p₁ p' : Poly) (a : Int) (k : Int) : Bool :=
a == p₁.leadCoeff && p' == p₁.tail.addConst k
theorem cooper_dvd_left_split_dvd1 (ctx : Context) (p₁ p₂ p₃ : Poly) (d : Int) (k : Nat) (a : Int) (p' : Poly)
: cooper_dvd_left_split ctx p₁ p₂ p₃ d k cooper_dvd_left_split_dvd1_cert p₁ p' a k a p'.denote ctx := by
simp [cooper_dvd_left_split_dvd1_cert, cooper_dvd_left_split]
intros; subst a p'; simp; assumption
def cooper_dvd_left_split_dvd2_cert (p₁ p₃ : Poly) (d : Int) (k : Nat) (d' : Int) (p' : Poly): Bool :=
let p := p₁.tail
let s := p₃.tail
let a := p₁.leadCoeff
let c := p₃.leadCoeff
let p₂ := p.mul c |>.combine (s.mul (-a))
d' == a*d && p' == p₂.addConst (c*k)
theorem cooper_dvd_left_split_dvd2 (ctx : Context) (p₁ p₂ p₃ : Poly) (d : Int) (k : Nat) (d' : Int) (p' : Poly)
: cooper_dvd_left_split ctx p₁ p₂ p₃ d k cooper_dvd_left_split_dvd2_cert p₁ p₃ d k d' p' d' p'.denote ctx := by
simp [cooper_dvd_left_split_dvd2_cert, cooper_dvd_left_split]
intros; subst d' p'; simp; assumption
private theorem cooper_left_core
{a b p q x : Int} (a_neg : a < 0) (b_pos : 0 < b)
(h₁ : a * x + p 0)
(h₂ : b * x + q 0)
: OrOver a.natAbs fun k =>
b * p + (-a) * q + b * k 0
a p + k := by
have d_pos : (0 : Int) < 1 := by decide
have h₃ : 1 0*x + 0 := Int.one_dvd _
have h := cooper_dvd_left_core a_neg b_pos d_pos h₁ h₂ h₃
simp only [Int.mul_one, gcd_zero, ofNat_natAbs_of_nonpos (Int.le_of_lt a_neg), Int.ediv_neg,
Int.ediv_self (Int.ne_of_lt a_neg), Int.reduceNeg, lcm_neg_right, lcm_one,
Int.add_left_comm, Int.zero_mul, Int.mul_zero, Int.add_zero, Int.dvd_zero,
and_true] at h
assumption
def cooper_left_cert (p₁ p₂ : Poly) (n : Nat) : Bool :=
p₁.casesOn (fun _ => false) fun a x _ =>
p₂.casesOn (fun _ => false) fun b y _ =>
.and (x == y) <| .and (a < 0) <| .and (b > 0) <|
n == a.natAbs
def cooper_left_split (ctx : Context) (p₁ p₂ : Poly) (k : Nat) : Prop :=
let p := p₁.tail
let q := p₂.tail
let a := p₁.leadCoeff
let b := p₂.leadCoeff
let p₁ := p.mul b |>.combine (q.mul (-a))
(p₁.addConst (b*k)).denote' ctx 0
a (p.addConst k).denote' ctx
theorem cooper_left (ctx : Context) (p₁ p₂ : Poly) (n : Nat)
: cooper_left_cert p₁ p₂ n
p₁.denote' ctx 0
p₂.denote' ctx 0
OrOver n (cooper_left_split ctx p₁ p₂) := by
unfold cooper_left_split
cases p₁ <;> cases p₂ <;> simp [cooper_left_cert, Poly.tail, -Poly.denote'_eq_denote]
next a x p b y q =>
intro; subst y
intro ha hb
intro; subst n
simp only [Poly.denote'_add, Poly.leadCoeff]
intro h₁ h₂
have := cooper_left_core ha hb h₁ h₂
simp only [denote'_mul_combine_mul_addConst_eq]
simp only [denote'_addConst_eq]
assumption
def cooper_left_split_ineq_cert (p₁ p₂ : Poly) (k : Int) (b : Int) (p' : Poly) : Bool :=
let p := p₁.tail
let q := p₂.tail
let a := p₁.leadCoeff
let p₁ := p.mul b |>.combine (q.mul (-a))
p₂.leadCoeff == b && p' == p₁.addConst (b*k)
theorem cooper_left_split_ineq (ctx : Context) (p₁ p₂ : Poly) (k : Nat) (b : Int) (p' : Poly)
: cooper_left_split ctx p₁ p₂ k cooper_left_split_ineq_cert p₁ p₂ k b p' p'.denote ctx 0 := by
simp [cooper_left_split_ineq_cert, cooper_left_split]
intros; subst p' b; simp [denote'_mul_combine_mul_addConst_eq]; assumption
def cooper_left_split_dvd_cert (p₁ p' : Poly) (a : Int) (k : Int) : Bool :=
a == p₁.leadCoeff && p' == p₁.tail.addConst k
theorem cooper_left_split_dvd (ctx : Context) (p₁ p₂ : Poly) (k : Nat) (a : Int) (p' : Poly)
: cooper_left_split ctx p₁ p₂ k cooper_left_split_dvd_cert p₁ p' a k a p'.denote ctx := by
simp [cooper_left_split_dvd_cert, cooper_left_split]
intros; subst a p'; simp; assumption
private theorem cooper_dvd_right_core
{a b c d s p q x : Int} (a_neg : a < 0) (b_pos : 0 < b) (d_pos : 0 < d)
(h₁ : a * x + p 0)
(h₂ : b * x + q 0)
(h₃ : d c * x + s)
: OrOver (Int.lcm b (b * d / Int.gcd (b * d) c)) fun k =>
b * p + (-a) * q + (-a) * k 0
b q + k
b * d (-c) * q + b * s + (-c) * k := by
have a_pos' : 0 < -a := by apply Int.neg_pos_of_neg; assumption
have h₁' : p (-a)*x := by rw [Int.neg_mul, Lean.Omega.Int.add_le_zero_iff_le_neg']; assumption
have h₂' : b * x -q := by rw [ Lean.Omega.Int.add_le_zero_iff_le_neg', Int.add_comm]; assumption
have k, h₁, h₂, h₃, h₄, h₅ := Int.cooper_resolution_dvd_right a_pos' b_pos d_pos |>.mp x, h₁', h₂', h₃
simp only [Int.neg_mul, neg_gcd, lcm_neg_left, Int.mul_neg, Int.neg_neg, Int.neg_dvd] at *
apply orOver_of_exists
have hlt := ofNat_lt h₁ h₂
replace h₃ := Int.add_le_add_right h₃ (-(a*q)); rw [Int.add_right_neg] at h₃
have : -(a * k) + b * p + -(a * q) = b * p + -(a * q) + -(a * k) := by ac_rfl
rw [this] at h₃; clear this
rw [Int.sub_neg, Int.add_comm] at h₄
have : -(c * k) + -(c * q) + b * s = -(c * q) + b * s + -(c * k) := by ac_rfl
rw [this] at h₅; clear this
exists k.toNat
simp only [hlt, true_and, and_true, cast_toNat h₁, h₃, h₄, h₅]
def cooper_dvd_right_cert (p₁ p₂ p₃ : Poly) (d : Int) (n : Nat) : Bool :=
p₁.casesOn (fun _ => false) fun a x _ =>
p₂.casesOn (fun _ => false) fun b y _ =>
p₃.casesOn (fun _ => false) fun c z _ =>
.and (x == y) <| .and (x == z) <|
.and (a < 0) <| .and (b > 0) <|
.and (d > 0) <| n == Int.lcm b (b * d / Int.gcd (b * d) c)
def cooper_dvd_right_split (ctx : Context) (p₁ p₂ p₃ : Poly) (d : Int) (k : Nat) : Prop :=
let p := p₁.tail
let q := p₂.tail
let s := p₃.tail
let a := p₁.leadCoeff
let b := p₂.leadCoeff
let c := p₃.leadCoeff
let p₁ := p.mul b |>.combine (q.mul (-a))
let p₂ := q.mul (-c) |>.combine (s.mul b)
(p₁.addConst ((-a)*k)).denote' ctx 0
b (q.addConst k).denote' ctx
b*d (p₂.addConst ((-c)*k)).denote' ctx
theorem cooper_dvd_right (ctx : Context) (p₁ p₂ p₃ : Poly) (d : Int) (n : Nat)
: cooper_dvd_right_cert p₁ p₂ p₃ d n
p₁.denote' ctx 0
p₂.denote' ctx 0
d p₃.denote' ctx
OrOver n (cooper_dvd_right_split ctx p₁ p₂ p₃ d) := by
unfold cooper_dvd_right_split
cases p₁ <;> cases p₂ <;> cases p₃ <;> simp [cooper_dvd_right_cert, Poly.tail, -Poly.denote'_eq_denote]
next a x p b y q c z s =>
intro _ _; subst y z
intro ha hb hd
intro; subst n
simp only [Poly.denote'_add, Poly.leadCoeff]
intro h₁ h₂ h₃
have := cooper_dvd_right_core ha hb hd h₁ h₂ h₃
simp only [denote'_mul_combine_mul_addConst_eq]
simp only [denote'_addConst_eq, Int.neg_mul]
exact cooper_dvd_right_core ha hb hd h₁ h₂ h₃
def cooper_dvd_right_split_ineq_cert (p₁ p₂ : Poly) (k : Int) (a : Int) (p' : Poly) : Bool :=
let p := p₁.tail
let q := p₂.tail
let b := p₂.leadCoeff
let p₂ := p.mul b |>.combine (q.mul (-a))
p₁.leadCoeff == a && p' == p₂.addConst ((-a)*k)
theorem cooper_dvd_right_split_ineq (ctx : Context) (p₁ p₂ p₃ : Poly) (d : Int) (k : Nat) (a : Int) (p' : Poly)
: cooper_dvd_right_split ctx p₁ p₂ p₃ d k cooper_dvd_right_split_ineq_cert p₁ p₂ k a p' p'.denote ctx 0 := by
simp [cooper_dvd_right_split_ineq_cert, cooper_dvd_right_split]
intros; subst a p'; simp [denote'_mul_combine_mul_addConst_eq]; assumption
def cooper_dvd_right_split_dvd1_cert (p₂ p' : Poly) (b : Int) (k : Int) : Bool :=
b == p₂.leadCoeff && p' == p₂.tail.addConst k
theorem cooper_dvd_right_split_dvd1 (ctx : Context) (p₁ p₂ p₃ : Poly) (d : Int) (k : Nat) (b : Int) (p' : Poly)
: cooper_dvd_right_split ctx p₁ p₂ p₃ d k cooper_dvd_right_split_dvd1_cert p₂ p' b k b p'.denote ctx := by
simp [cooper_dvd_right_split_dvd1_cert, cooper_dvd_right_split]
intros; subst b p'; simp; assumption
def cooper_dvd_right_split_dvd2_cert (p₂ p₃ : Poly) (d : Int) (k : Nat) (d' : Int) (p' : Poly): Bool :=
let q := p₂.tail
let s := p₃.tail
let b := p₂.leadCoeff
let c := p₃.leadCoeff
let p₂ := q.mul (-c) |>.combine (s.mul b)
d' == b*d && p' == p₂.addConst ((-c)*k)
theorem cooper_dvd_right_split_dvd2 (ctx : Context) (p₁ p₂ p₃ : Poly) (d : Int) (k : Nat) (d' : Int) (p' : Poly)
: cooper_dvd_right_split ctx p₁ p₂ p₃ d k cooper_dvd_right_split_dvd2_cert p₂ p₃ d k d' p' d' p'.denote ctx := by
simp [cooper_dvd_right_split_dvd2_cert, cooper_dvd_right_split]
intros; subst d' p'; simp; assumption
private theorem cooper_right_core
{a b p q x : Int} (a_neg : a < 0) (b_pos : 0 < b)
(h₁ : a * x + p 0)
(h₂ : b * x + q 0)
: OrOver b.natAbs fun k =>
b * p + (-a) * q + (-a) * k 0
b q + k := by
have d_pos : (0 : Int) < 1 := by decide
have h₃ : 1 0*x + 0 := Int.one_dvd _
have h := cooper_dvd_right_core a_neg b_pos d_pos h₁ h₂ h₃
simp only [Int.mul_one, gcd_zero, Int.natAbs_of_nonneg (Int.le_of_lt b_pos), Int.ediv_neg,
Int.ediv_self (Int.ne_of_gt b_pos), Int.reduceNeg, lcm_neg_right, lcm_one,
Int.add_left_comm, Int.zero_mul, Int.mul_zero, Int.add_zero, Int.dvd_zero,
and_true, Int.neg_zero] at h
assumption
def cooper_right_cert (p₁ p₂ : Poly) (n : Nat) : Bool :=
p₁.casesOn (fun _ => false) fun a x _ =>
p₂.casesOn (fun _ => false) fun b y _ =>
.and (x == y) <| .and (a < 0) <| .and (b > 0) <| n == b.natAbs
def cooper_right_split (ctx : Context) (p₁ p₂ : Poly) (k : Nat) : Prop :=
let p := p₁.tail
let q := p₂.tail
let a := p₁.leadCoeff
let b := p₂.leadCoeff
let p₁ := p.mul b |>.combine (q.mul (-a))
(p₁.addConst ((-a)*k)).denote' ctx 0
b (q.addConst k).denote' ctx
theorem cooper_right (ctx : Context) (p₁ p₂ : Poly) (n : Nat)
: cooper_right_cert p₁ p₂ n
p₁.denote' ctx 0
p₂.denote' ctx 0
OrOver n (cooper_right_split ctx p₁ p₂) := by
unfold cooper_right_split
cases p₁ <;> cases p₂ <;> simp [cooper_right_cert, Poly.tail, -Poly.denote'_eq_denote]
next a x p b y q =>
intro; subst y
intro ha hb
intro; subst n
simp only [Poly.denote'_add, Poly.leadCoeff]
intro h₁ h₂
have := cooper_right_core ha hb h₁ h₂
simp only [denote'_mul_combine_mul_addConst_eq]
simp only [denote'_addConst_eq, Int.neg_mul]
assumption
def cooper_right_split_ineq_cert (p₁ p₂ : Poly) (k : Int) (a : Int) (p' : Poly) : Bool :=
let p := p₁.tail
let q := p₂.tail
let b := p₂.leadCoeff
let p₂ := p.mul b |>.combine (q.mul (-a))
p₁.leadCoeff == a && p' == p₂.addConst ((-a)*k)
theorem cooper_right_split_ineq (ctx : Context) (p₁ p₂ : Poly) (k : Nat) (a : Int) (p' : Poly)
: cooper_right_split ctx p₁ p₂ k cooper_right_split_ineq_cert p₁ p₂ k a p' p'.denote ctx 0 := by
simp [cooper_right_split_ineq_cert, cooper_right_split]
intros; subst a p'; simp [denote'_mul_combine_mul_addConst_eq]; assumption
def cooper_right_split_dvd_cert (p₂ p' : Poly) (b : Int) (k : Int) : Bool :=
b == p₂.leadCoeff && p' == p₂.tail.addConst k
theorem cooper_right_split_dvd (ctx : Context) (p₁ p₂ : Poly) (k : Nat) (b : Int) (p' : Poly)
: cooper_right_split ctx p₁ p₂ k cooper_right_split_dvd_cert p₂ p' b k b p'.denote ctx := by
simp [cooper_right_split_dvd_cert, cooper_right_split]
intros; subst b p'; simp; assumption
end Int.Linear
theorem Int.not_le_eq (a b : Int) : (¬a b) = (b + 1 a) := by

View File

@@ -133,10 +133,10 @@ protected theorem lt_of_not_ge {a b : Int} (h : ¬a ≤ b) : b < a :=
protected theorem not_le_of_gt {a b : Int} (h : b < a) : ¬a b :=
(Int.lt_iff_le_not_le.mp h).right
protected theorem not_le {a b : Int} : ¬a b b < a :=
@[simp] protected theorem not_le {a b : Int} : ¬a b b < a :=
Iff.intro Int.lt_of_not_ge Int.not_le_of_gt
protected theorem not_lt {a b : Int} : ¬a < b b a :=
@[simp] protected theorem not_lt {a b : Int} : ¬a < b b a :=
by rw [ Int.not_le, Decidable.not_not]
protected theorem lt_trichotomy (a b : Int) : a < b a = b b < a :=

View File

@@ -662,6 +662,10 @@ def unattach {α : Type _} {p : α → Prop} (l : List { x // p x }) : List α :
@[simp] theorem unattach_cons {p : α Prop} {a : { x // p x }} {l : List { x // p x }} :
(a :: l).unattach = a.val :: l.unattach := rfl
@[simp] theorem mem_unattach {p : α Prop} {l : List { x // p x }} {a} :
a l.unattach h : p a, a, h l := by
simp only [unattach, mem_map, Subtype.exists, exists_and_right, exists_eq_right]
@[simp] theorem length_unattach {p : α Prop} {l : List { x // p x }} :
l.unattach.length = l.length := by
unfold unattach
@@ -766,6 +770,16 @@ and simplifies these to the function directly taking the value.
simp [hf, find?_cons]
split <;> simp [ih]
@[simp] theorem all_subtype {p : α Prop} {l : List { x // p x }} {f : { x // p x } Bool} {g : α Bool}
(hf : x h, f x, h = g x) :
l.all f = l.unattach.all g := by
simp [all_eq, hf]
@[simp] theorem any_subtype {p : α Prop} {l : List { x // p x }} {f : { x // p x } Bool} {g : α Bool}
(hf : x h, f x, h = g x) :
l.any f = l.unattach.any g := by
simp [any_eq, hf]
/-! ### Simp lemmas pushing `unattach` inwards. -/
@[simp] theorem unattach_filter {p : α Prop} {l : List { x // p x }}

View File

@@ -212,6 +212,7 @@ def mapMono (as : List α) (f : αα) : List α :=
/-! ## Additional lemmas required for bootstrapping `Array`. -/
@[simp]
theorem getElem_append_left {as bs : List α} (h : i < as.length) {h' : i < (as ++ bs).length} :
(as ++ bs)[i] = as[i] := by
induction as generalizing i with
@@ -221,6 +222,7 @@ theorem getElem_append_left {as bs : List α} (h : i < as.length) {h' : i < (as
| zero => rfl
| succ i => apply ih
@[simp]
theorem getElem_append_right {as bs : List α} {i : Nat} (h₁ : as.length i) {h₂} :
(as ++ bs)[i]'h₂ =
bs[i - as.length]'(by rw [length_append] at h₂; exact Nat.sub_lt_left_of_lt_add h₁ h₂) := by

View File

@@ -101,6 +101,12 @@ This is similar to `<|>`/`orElse`, but it is strict in the second argument. -/
| some x, some y => r x y
| _, _ => False
@[inline] protected def le (r : α β Prop) : Option α Option β Prop
| none, some _ => True
| none, none => True
| some _, none => False
| some x, some y => r x y
instance (r : α β Prop) [s : DecidableRel r] : DecidableRel (Option.lt r)
| none, some _ => isTrue trivial
| some x, some y => s x y
@@ -217,18 +223,24 @@ instance (α) [BEq α] [LawfulBEq α] : LawfulBEq (Option α) where
@[simp] theorem any_none : Option.any p none = false := rfl
@[simp] theorem any_some : Option.any p (some x) = p x := rfl
/-- The minimum of two optional values. -/
/--
The minimum of two optional values.
Note this treats `none` as the least element,
so `min none x = min x none = none` for all `x : Option α`.
Prior to nightly-2025-02-27, we instead had `min none (some x) = min (some x) none = some x`.
-/
protected def min [Min α] : Option α Option α Option α
| some x, some y => some (Min.min x y)
| some x, none => some x
| none, some y => some y
| some _, none => none
| none, some _ => none
| none, none => none
instance [Min α] : Min (Option α) where min := Option.min
@[simp] theorem min_some_some [Min α] {a b : α} : min (some a) (some b) = some (min a b) := rfl
@[simp] theorem min_some_none [Min α] {a : α} : min (some a) none = some a := rfl
@[simp] theorem min_none_some [Min α] {b : α} : min none (some b) = some b := rfl
@[simp] theorem min_some_none [Min α] {a : α} : min (some a) none = none := rfl
@[simp] theorem min_none_some [Min α] {b : α} : min none (some b) = none := rfl
@[simp] theorem min_none_none [Min α] : min (none : Option α) none = none := rfl
/-- The maximum of two optional values. -/
@@ -251,6 +263,9 @@ end Option
instance [LT α] : LT (Option α) where
lt := Option.lt (· < ·)
instance [LE α] : LE (Option α) where
le := Option.le (· ·)
@[always_inline]
instance : Functor Option where
map := Option.map

View File

@@ -673,4 +673,80 @@ theorem pmap_map (o : Option α) (f : α → β) {p : β → Prop} (g : ∀ b, p
o.pelim g (fun a h => g' (f a (H a h))) := by
cases o <;> simp
/-! ### LT and LE -/
@[simp] theorem not_lt_none [LT α] {a : Option α} : ¬ a < none := by cases a <;> simp [LT.lt, Option.lt]
@[simp] theorem none_lt_some [LT α] {a : α} : none < some a := by simp [LT.lt, Option.lt]
@[simp] theorem some_lt_some [LT α] {a b : α} : some a < some b a < b := by simp [LT.lt, Option.lt]
@[simp] theorem none_le [LE α] {a : Option α} : none a := by cases a <;> simp [LE.le, Option.le]
@[simp] theorem not_some_le_none [LE α] {a : α} : ¬ some a none := by simp [LE.le, Option.le]
@[simp] theorem some_le_some [LE α] {a b : α} : some a some b a b := by simp [LE.le, Option.le]
/-! ### min and max -/
theorem min_eq_left [LE α] [Min α] (min_eq_left : x y : α, x y min x y = x)
{a b : Option α} (h : a b) : min a b = a := by
cases a <;> cases b <;> simp_all
theorem min_eq_right [LE α] [Min α] (min_eq_right : x y : α, y x min x y = y)
{a b : Option α} (h : b a) : min a b = b := by
cases a <;> cases b <;> simp_all
theorem min_eq_left_of_lt [LT α] [Min α] (min_eq_left : x y : α, x < y min x y = x)
{a b : Option α} (h : a < b) : min a b = a := by
cases a <;> cases b <;> simp_all
theorem min_eq_right_of_lt [LT α] [Min α] (min_eq_right : x y : α, y < x min x y = y)
{a b : Option α} (h : b < a) : min a b = b := by
cases a <;> cases b <;> simp_all
theorem min_eq_or [LE α] [Min α] (min_eq_or : x y : α, min x y = x min x y = y)
{a b : Option α} : min a b = a min a b = b := by
cases a <;> cases b <;> simp_all
theorem min_le_left [LE α] [Min α] (min_le_left : x y : α, min x y x)
{a b : Option α} : min a b a := by
cases a <;> cases b <;> simp_all
theorem min_le_right [LE α] [Min α] (min_le_right : x y : α, min x y y)
{a b : Option α} : min a b b := by
cases a <;> cases b <;> simp_all
theorem le_min [LE α] [Min α] (le_min : x y z : α, x min y z x y x z)
{a b c : Option α} : a min b c a b a c := by
cases a <;> cases b <;> cases c <;> simp_all
theorem max_eq_left [LE α] [Max α] (max_eq_left : x y : α, x y max x y = y)
{a b : Option α} (h : a b) : max a b = b := by
cases a <;> cases b <;> simp_all
theorem max_eq_right [LE α] [Max α] (max_eq_right : x y : α, y x max x y = x)
{a b : Option α} (h : b a) : max a b = a := by
cases a <;> cases b <;> simp_all
theorem max_eq_left_of_lt [LT α] [Max α] (max_eq_left : x y : α, x < y max x y = y)
{a b : Option α} (h : a < b) : max a b = b := by
cases a <;> cases b <;> simp_all
theorem max_eq_right_of_lt [LT α] [Max α] (max_eq_right : x y : α, y < x max x y = x)
{a b : Option α} (h : b < a) : max a b = a := by
cases a <;> cases b <;> simp_all
theorem max_eq_or [LE α] [Max α] (max_eq_or : x y : α, max x y = x max x y = y)
{a b : Option α} : max a b = a max a b = b := by
cases a <;> cases b <;> simp_all
theorem left_le_max [LE α] [Max α] (le_refl : x : α, x x) (left_le_max : x y : α, x max x y)
{a b : Option α} : a max a b := by
cases a <;> cases b <;> simp_all
theorem right_le_max [LE α] [Max α] (le_refl : x : α, x x) (right_le_max : x y : α, y max x y)
{a b : Option α} : b max a b := by
cases a <;> cases b <;> simp_all
theorem max_le [LE α] [Max α] (max_le : x y z : α, max x y z x z y z)
{a b c : Option α} : max a b c a c b c := by
cases a <;> cases b <;> cases c <;> simp_all
end Option

View File

@@ -8,6 +8,7 @@ import Init.Data.SInt.Basic
import Init.Data.SInt.Float
import Init.Data.SInt.Float32
import Init.Data.SInt.Lemmas
import Init.Data.SInt.Bitwise
/-!
This module contains the definitions and basic theory about signed fixed width integer types.

View File

@@ -77,6 +77,9 @@ Obtain the `BitVec` that contains the 2's complement representation of the `Int8
-/
@[inline] def Int8.toBitVec (x : Int8) : BitVec 8 := x.toUInt8.toBitVec
theorem Int8.toBitVec.inj : {x y : Int8} x.toBitVec = y.toBitVec x = y
| _, _, rfl => rfl
/-- Obtains the `Int8` that is 2's complement equivalent to the `UInt8`. -/
@[inline] def UInt8.toInt8 (i : UInt8) : Int8 := Int8.ofUInt8 i
@[inline, deprecated UInt8.toInt8 (since := "2025-02-13"), inherit_doc UInt8.toInt8]
@@ -110,8 +113,8 @@ instance : ReprAtom Int8 := ⟨⟩
instance : Hashable Int8 where
hash i := i.toUInt8.toUInt64
instance : OfNat Int8 n := Int8.ofNat n
instance : Neg Int8 where
instance Int8.instOfNat : OfNat Int8 n := Int8.ofNat n
instance Int8.instNeg : Neg Int8 where
neg := Int8.neg
/-- The maximum value an `Int8` may attain, that is, `2^7 - 1 = 127`. -/
@@ -213,6 +216,9 @@ Obtain the `BitVec` that contains the 2's complement representation of the `Int1
-/
@[inline] def Int16.toBitVec (x : Int16) : BitVec 16 := x.toUInt16.toBitVec
theorem Int16.toBitVec.inj : {x y : Int16} x.toBitVec = y.toBitVec x = y
| _, _, rfl => rfl
/-- Obtains the `Int16` that is 2's complement equivalent to the `UInt16`. -/
@[inline] def UInt16.toInt16 (i : UInt16) : Int16 := Int16.ofUInt16 i
@[inline, deprecated UInt16.toInt16 (since := "2025-02-13"), inherit_doc UInt16.toInt16]
@@ -250,8 +256,8 @@ instance : ReprAtom Int16 := ⟨⟩
instance : Hashable Int16 where
hash i := i.toUInt16.toUInt64
instance : OfNat Int16 n := Int16.ofNat n
instance : Neg Int16 where
instance Int16.instOfNat : OfNat Int16 n := Int16.ofNat n
instance Int16.instNeg : Neg Int16 where
neg := Int16.neg
/-- The maximum value an `Int16` may attain, that is, `2^15 - 1 = 32767`. -/
@@ -353,6 +359,9 @@ Obtain the `BitVec` that contains the 2's complement representation of the `Int3
-/
@[inline] def Int32.toBitVec (x : Int32) : BitVec 32 := x.toUInt32.toBitVec
theorem Int32.toBitVec.inj : {x y : Int32} x.toBitVec = y.toBitVec x = y
| _, _, rfl => rfl
/-- Obtains the `Int32` that is 2's complement equivalent to the `UInt32`. -/
@[inline] def UInt32.toInt32 (i : UInt32) : Int32 := Int32.ofUInt32 i
@[inline, deprecated UInt32.toInt32 (since := "2025-02-13"), inherit_doc UInt32.toInt32]
@@ -394,8 +403,8 @@ instance : ReprAtom Int16 := ⟨⟩
instance : Hashable Int32 where
hash i := i.toUInt32.toUInt64
instance : OfNat Int32 n := Int32.ofNat n
instance : Neg Int32 where
instance Int32.instOfNat : OfNat Int32 n := Int32.ofNat n
instance Int32.instNeg : Neg Int32 where
neg := Int32.neg
/-- The maximum value an `Int32` may attain, that is, `2^31 - 1 = 2147483647`. -/
@@ -497,6 +506,9 @@ Obtain the `BitVec` that contains the 2's complement representation of the `Int6
-/
@[inline] def Int64.toBitVec (x : Int64) : BitVec 64 := x.toUInt64.toBitVec
theorem Int64.toBitVec.inj : {x y : Int64} x.toBitVec = y.toBitVec x = y
| _, _, rfl => rfl
/-- Obtains the `Int64` that is 2's complement equivalent to the `UInt64`. -/
@[inline] def UInt64.toInt64 (i : UInt64) : Int64 := Int64.ofUInt64 i
@[inline, deprecated UInt64.toInt64 (since := "2025-02-13"), inherit_doc UInt64.toInt64]
@@ -542,8 +554,8 @@ instance : ReprAtom Int64 := ⟨⟩
instance : Hashable Int64 where
hash i := i.toUInt64
instance : OfNat Int64 n := Int64.ofNat n
instance : Neg Int64 where
instance Int64.instOfNat : OfNat Int64 n := Int64.ofNat n
instance Int64.instNeg : Neg Int64 where
neg := Int64.neg
/-- The maximum value an `Int64` may attain, that is, `2^63 - 1 = 9223372036854775807`. -/
@@ -645,6 +657,9 @@ Obtain the `BitVec` that contains the 2's complement representation of the `ISiz
-/
@[inline] def ISize.toBitVec (x : ISize) : BitVec System.Platform.numBits := x.toUSize.toBitVec
theorem ISize.toBitVec.inj : {x y : ISize} x.toBitVec = y.toBitVec x = y
| _, _, rfl => rfl
/-- Obtains the `ISize` that is 2's complement equivalent to the `USize`. -/
@[inline] def USize.toISize (i : USize) : ISize := ISize.ofUSize i
@[inline, deprecated USize.toISize (since := "2025-02-13"), inherit_doc USize.toISize]
@@ -700,8 +715,8 @@ instance : ReprAtom ISize := ⟨⟩
instance : Hashable ISize where
hash i := i.toUSize.toUInt64
instance : OfNat ISize n := ISize.ofNat n
instance : Neg ISize where
instance ISize.instOfNat : OfNat ISize n := ISize.ofNat n
instance ISize.instNeg : Neg ISize where
neg := ISize.neg
/-- The maximum value an `ISize` may attain, that is, `2^(System.Platform.numBits - 1) - 1`. -/

View File

@@ -0,0 +1,57 @@
/-
Copyright (c) 2025 Lean FRO, LLC. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Markus Himmel
-/
prelude
import Init.Data.SInt.Lemmas
set_option hygiene false in
macro "declare_bitwise_int_theorems" typeName:ident bits:term:arg : command =>
`(
namespace $typeName
@[simp, int_toBitVec] protected theorem toBitVec_add {a b : $typeName} : (a + b).toBitVec = a.toBitVec + b.toBitVec := rfl
@[simp, int_toBitVec] protected theorem toBitVec_sub {a b : $typeName} : (a - b).toBitVec = a.toBitVec - b.toBitVec := rfl
@[simp, int_toBitVec] protected theorem toBitVec_mul {a b : $typeName} : (a * b).toBitVec = a.toBitVec * b.toBitVec := rfl
@[simp, int_toBitVec] protected theorem toBitVec_div {a b : $typeName} : (a / b).toBitVec = a.toBitVec.sdiv b.toBitVec := rfl
@[simp, int_toBitVec] protected theorem toBitVec_mod {a b : $typeName} : (a % b).toBitVec = a.toBitVec.srem b.toBitVec := rfl
@[simp, int_toBitVec] protected theorem toBitVec_not {a : $typeName} : (~~~a).toBitVec = ~~~a.toBitVec := rfl
@[simp, int_toBitVec] protected theorem toBitVec_and (a b : $typeName) : (a &&& b).toBitVec = a.toBitVec &&& b.toBitVec := rfl
@[simp, int_toBitVec] protected theorem toBitVec_or (a b : $typeName) : (a ||| b).toBitVec = a.toBitVec ||| b.toBitVec := rfl
@[simp, int_toBitVec] protected theorem toBitVec_xor (a b : $typeName) : (a ^^^ b).toBitVec = a.toBitVec ^^^ b.toBitVec := rfl
@[simp, int_toBitVec] protected theorem toBitVec_shiftLeft (a b : $typeName) : (a <<< b).toBitVec = a.toBitVec <<< (b.toBitVec.smod $bits) := rfl
@[simp, int_toBitVec] protected theorem toBitVec_shiftRight (a b : $typeName) : (a >>> b).toBitVec = a.toBitVec.sshiftRight' (b.toBitVec.smod $bits) := rfl
@[simp, int_toBitVec] protected theorem toBitVec_abs (a : $typeName) : a.abs.toBitVec = a.toBitVec.abs := rfl
end $typeName
)
declare_bitwise_int_theorems Int8 8
declare_bitwise_int_theorems Int16 16
declare_bitwise_int_theorems Int32 32
declare_bitwise_int_theorems Int64 64
declare_bitwise_int_theorems ISize System.Platform.numBits
@[simp, int_toBitVec]
theorem Bool.toBitVec_toInt8 {b : Bool} : b.toInt8.toBitVec = (BitVec.ofBool b).setWidth 8 := by
cases b <;> simp [toInt8]
@[simp, int_toBitVec]
theorem Bool.toBitVec_toInt16 {b : Bool} : b.toInt16.toBitVec = (BitVec.ofBool b).setWidth 16 := by
cases b <;> simp [toInt16]
@[simp, int_toBitVec]
theorem Bool.toBitVec_toInt32 {b : Bool} : b.toInt32.toBitVec = (BitVec.ofBool b).setWidth 32 := by
cases b <;> simp [toInt32]
@[simp, int_toBitVec]
theorem Bool.toBitVec_toInt64 {b : Bool} : b.toInt64.toBitVec = (BitVec.ofBool b).setWidth 64 := by
cases b <;> simp [toInt64]
@[simp, int_toBitVec]
theorem Bool.toBitVec_toISize {b : Bool} :
b.toISize.toBitVec = (BitVec.ofBool b).setWidth System.Platform.numBits := by
cases b
· simp [toISize]
· apply BitVec.eq_of_toNat_eq
simp [toISize]

View File

@@ -5,6 +5,34 @@ Authors: Markus Himmel
-/
prelude
import Init.Data.SInt.Basic
import Init.Data.BitVec.Lemmas
open Lean in
set_option hygiene false in
macro "declare_int_theorems" typeName:ident _bits:term:arg : command => do
let mut cmds Syntax.getArgs <$> `(
namespace $typeName
@[int_toBitVec] theorem le_def {a b : $typeName} : a b a.toBitVec.sle b.toBitVec := Iff.rfl
@[int_toBitVec] theorem lt_def {a b : $typeName} : a < b a.toBitVec.slt b.toBitVec := Iff.rfl
theorem toBitVec_inj {a b : $typeName} : a.toBitVec = b.toBitVec a = b :=
toBitVec.inj, (· rfl)
@[int_toBitVec] theorem eq_iff_toBitVec_eq {a b : $typeName} : a = b a.toBitVec = b.toBitVec :=
toBitVec_inj.symm
@[int_toBitVec] theorem ne_iff_toBitVec_ne {a b : $typeName} : a b a.toBitVec b.toBitVec :=
Decidable.not_iff_not.2 eq_iff_toBitVec_eq
@[simp] theorem toBitVec_ofNat {n : Nat} : toBitVec (ofNat n) = BitVec.ofNat _ n := rfl
@[simp, int_toBitVec] theorem toBitVec_ofNatOfNat {n : Nat} : toBitVec (no_index (OfNat.ofNat n)) = OfNat.ofNat n := rfl
end $typeName
)
return mkNullNode cmds
declare_int_theorems Int8 8
declare_int_theorems Int16 16
declare_int_theorems Int32 32
declare_int_theorems Int64 64
declare_int_theorems ISize System.Platform.numBits
@[simp] theorem UInt8.toBitVec_toInt8 (x : UInt8) : x.toInt8.toBitVec = x.toBitVec := rfl
@[simp] theorem UInt16.toBitVec_toInt16 (x : UInt16) : x.toInt16.toBitVec = x.toBitVec := rfl
@@ -23,3 +51,48 @@ import Init.Data.SInt.Basic
@[simp] theorem UInt32.toUInt32_toInt32 (x : UInt32) : x.toInt32.toUInt32 = x := rfl
@[simp] theorem UInt64.toUInt64_toInt64 (x : UInt64) : x.toInt64.toUInt64 = x := rfl
@[simp] theorem USize.toUSize_toISize (x : USize) : x.toISize.toUSize = x := rfl
@[simp] theorem ISize.toBitVec_neg (x : ISize) : (-x).toBitVec = -x.toBitVec := rfl
@[simp] theorem ISize.toBitVec_zero : (0 : ISize).toBitVec = 0 := rfl
@[simp] theorem ISize.toBitVec_ofInt (i : Int) : (ofInt i).toBitVec = BitVec.ofInt _ i := rfl
@[simp] theorem Int8.neg_zero : -(0 : Int8) = 0 := rfl
@[simp] theorem Int16.neg_zero : -(0 : Int16) = 0 := rfl
@[simp] theorem Int32.neg_zero : -(0 : Int32) = 0 := rfl
@[simp] theorem Int64.neg_zero : -(0 : Int64) = 0 := rfl
@[simp] theorem ISize.neg_zero : -(0 : ISize) = 0 := ISize.toBitVec.inj (by simp)
theorem ISize.toNat_toBitVec_ofNat_of_lt {n : Nat} (h : n < 2^32) :
(ofNat n).toBitVec.toNat = n :=
Nat.mod_eq_of_lt (Nat.lt_of_lt_of_le h (by cases USize.size_eq <;> simp_all +decide))
theorem ISize.toInt_ofInt {n : Int} (hn : -2^31 n) (hn' : n < 2^31) : toInt (ofInt n) = n := by
rw [toInt, toBitVec_ofInt, BitVec.toInt_ofInt_eq_self] <;> cases System.Platform.numBits_eq
<;> (simp_all; try omega)
theorem ISize.toNatClampNeg_ofInt_eq_zero {n : Int} (hn : -2^31 n) (hn' : n 0) :
toNatClampNeg (ofInt n) = 0 := by
rwa [toNatClampNeg, toInt_ofInt hn (by omega), Int.toNat_eq_zero]
theorem ISize.neg_ofInt {n : Int} : -ofInt n = ofInt (-n) :=
toBitVec.inj (by simp [BitVec.ofInt_neg])
theorem ISize.ofInt_eq_ofNat {n : Nat} : ofInt n = ofNat n :=
toBitVec.inj (by simp)
theorem ISize.neg_ofNat {n : Nat} : -ofNat n = ofInt (-n) := by
rw [ neg_ofInt, ofInt_eq_ofNat]
theorem ISize.toNatClampNeg_ofNat_of_lt {n : Nat} (h : n < 2 ^ 31) :
toNatClampNeg (ofNat n) = n := by
rw [toNatClampNeg, ofInt_eq_ofNat, toInt_ofInt (by omega) (by omega), Int.toNat_ofNat]
theorem ISize.toNatClampNeg_neg_ofNat_of_le {n : Nat} (h : n 2 ^ 31) :
toNatClampNeg (-ofNat n) = 0 := by
rw [neg_ofNat, toNatClampNeg_ofInt_eq_zero (by omega) (by omega)]
theorem ISize.toInt_ofNat_of_lt {n : Nat} (h : n < 2 ^ 31) : toInt (ofNat n) = n := by
rw [ ofInt_eq_ofNat, toInt_ofInt (by omega) (by omega)]
theorem ISize.toInt_neg_ofNat_of_le {n : Nat} (h : n 2 ^ 31) : toInt (-ofNat n) = -n := by
rw [ ofInt_eq_ofNat, neg_ofInt, toInt_ofInt (by omega) (by omega)]

View File

@@ -287,6 +287,8 @@ theorem UInt32.size_le_usizeSize : UInt32.size ≤ USize.size := by
theorem USize.size_eq_two_pow : USize.size = 2 ^ System.Platform.numBits := rfl
theorem USize.toNat_lt_two_pow_numBits (n : USize) : n.toNat < 2 ^ System.Platform.numBits := n.toFin.isLt
@[simp] theorem USize.toNat_lt (n : USize) : n.toNat < 2 ^ 64 := Nat.lt_of_lt_of_le n.toFin.isLt size_le
theorem USize.size_le_uint64Size : USize.size UInt64.size := by
cases USize.size_eq <;> simp_all +decide
theorem UInt8.toNat_lt_usizeSize (n : UInt8) : n.toNat < USize.size :=
Nat.lt_of_lt_of_le n.toNat_lt (by cases USize.size_eq <;> simp_all)
@@ -373,7 +375,7 @@ theorem USize.size_dvd_uInt64Size : USize.size UInt64.size := by cases USize
@[simp] theorem UInt32.toFin_toUSize (n : UInt32) :
n.toUSize.toFin = n.toFin.castLE size_le_usizeSize := rfl
@[simp] theorem USize.toFin_toUInt64 (n : USize) : n.toUInt64.toFin = n.toFin.castLE size_le_usizeSize := rfl
@[simp] theorem USize.toFin_toUInt64 (n : USize) : n.toUInt64.toFin = n.toFin.castLE size_le_uint64Size := rfl
@[simp] theorem UInt16.toBitVec_toUInt8 (n : UInt16) : n.toUInt8.toBitVec = n.toBitVec.setWidth 8 := rfl
@[simp] theorem UInt32.toBitVec_toUInt8 (n : UInt32) : n.toUInt8.toBitVec = n.toBitVec.setWidth 8 := rfl

View File

@@ -7,8 +7,8 @@ prelude
import Init.Data.Vector.Lemmas
import Init.Data.Array.Attach
-- set_option linter.listVariables true -- Enforce naming conventions for `List`/`Array`/`Vector` variables.
-- set_option linter.indexVariables true -- Enforce naming conventions for index variables.
set_option linter.listVariables true -- Enforce naming conventions for `List`/`Array`/`Vector` variables.
set_option linter.indexVariables true -- Enforce naming conventions for index variables.
namespace Vector
@@ -473,6 +473,10 @@ def unattach {α : Type _} {p : α → Prop} (xs : Vector { x // p x } n) : Vect
(xs.push a).unattach = xs.unattach.push a.1 := by
simp only [unattach, Vector.map_push]
@[simp] theorem mem_unattach {p : α Prop} {xs : Vector { x // p x } n} {a} :
a xs.unattach h : p a, a, h xs := by
simp only [unattach, mem_map, Subtype.exists, exists_and_right, exists_eq_right]
@[simp] theorem unattach_mk {p : α Prop} {xs : Array { x // p x }} {h : xs.size = n} :
(mk xs h).unattach = mk xs.unattach (by simpa using h) := by
simp [unattach]
@@ -552,6 +556,18 @@ and simplifies these to the function directly taking the value.
simp
rw [Array.find?_subtype hf]
@[simp] theorem all_subtype {p : α Prop} {xs : Vector { x // p x } n} {f : { x // p x } Bool} {g : α Bool}
(hf : x h, f x, h = g x) :
xs.all f = xs.unattach.all g := by
rcases xs with xs, rfl
simp [hf]
@[simp] theorem any_subtype {p : α Prop} {xs : Vector { x // p x } n} {f : { x // p x } Bool} {g : α Bool}
(hf : x h, f x, h = g x) :
xs.any f = xs.unattach.any g := by
rcases xs with xs, rfl
simp [hf]
/-! ### Simp lemmas pushing `unattach` inwards. -/
@[simp] theorem unattach_reverse {p : α Prop} {xs : Vector { x // p x } n} :

View File

@@ -8,6 +8,7 @@ prelude
import Init.Data.Array.Lemmas
import Init.Data.Array.MapIdx
import Init.Data.Array.InsertIdx
import Init.Data.Array.Range
import Init.Data.Range
import Init.Data.Stream
@@ -17,8 +18,8 @@ import Init.Data.Stream
`Vector α n` is a thin wrapper around `Array α` for arrays of fixed size `n`.
-/
-- set_option linter.listVariables true -- Enforce naming conventions for `List`/`Array`/`Vector` variables.
-- set_option linter.indexVariables true -- Enforce naming conventions for index variables.
set_option linter.listVariables true -- Enforce naming conventions for `List`/`Array`/`Vector` variables.
set_option linter.indexVariables true -- Enforce naming conventions for index variables.
/-- `Vector α n` is an `Array α` with size `n`. -/
structure Vector (α : Type u) (n : Nat) extends Array α where

View File

@@ -15,8 +15,8 @@ import Init.Data.Array.Find
We are still missing results about `idxOf?`, `findIdx`, and `findIdx?`.
-/
-- set_option linter.listVariables true -- Enforce naming conventions for `List`/`Array`/`Vector` variables.
-- set_option linter.indexVariables true -- Enforce naming conventions for index variables.
set_option linter.listVariables true -- Enforce naming conventions for `List`/`Array`/`Vector` variables.
set_option linter.indexVariables true -- Enforce naming conventions for index variables.
namespace Vector

View File

@@ -13,8 +13,8 @@ import Init.Data.Array.Find
Lemmas about `Vector α n`
-/
-- set_option linter.listVariables true -- Enforce naming conventions for `List`/`Array`/`Vector` variables.
-- set_option linter.indexVariables true -- Enforce naming conventions for index variables.
set_option linter.listVariables true -- Enforce naming conventions for `List`/`Array`/`Vector` variables.
set_option linter.indexVariables true -- Enforce naming conventions for index variables.
namespace Array
@@ -1592,9 +1592,11 @@ theorem getElem_append (xs : Vector α n) (ys : Vector α m) (i : Nat) (hi : i <
rcases ys with ys, rfl
simp [Array.getElem_append, hi]
@[simp]
theorem getElem_append_left {xs : Vector α n} {ys : Vector α m} {i : Nat} (hi : i < n) :
(xs ++ ys)[i] = xs[i] := by simp [getElem_append, hi]
@[simp]
theorem getElem_append_right {xs : Vector α n} {ys : Vector α m} {i : Nat} (h : i < n + m) (hi : n i) :
(xs ++ ys)[i] = ys[i - n] := by
rw [getElem_append, dif_neg (by omega)]
@@ -2068,6 +2070,12 @@ theorem flatMap_mkArray {β} (f : α → Vector β m) : (mkVector n a).flatMap f
rcases xs with xs, rfl
simp
theorem getElem_eq_getElem_reverse {xs : Vector α n} {i} (h : i < n) :
xs[i] = xs.reverse[n - 1 - i] := by
rw [getElem_reverse]
congr
omega
/-- Variant of `getElem?_reverse` with a hypothesis giving the linear relation between the indices. -/
theorem getElem?_reverse' {xs : Vector α n} (i j) (h : i + j + 1 = n) : xs.reverse[i]? = xs[j]? := by
rcases xs with xs, rfl
@@ -2474,6 +2482,14 @@ theorem contains_iff_mem [BEq α] [LawfulBEq α] {xs : Vector α n} {a : α} :
rcases xs with xs, rfl
simp
/--
Variant of `getElem_pop` that will sometimes fire when `getElem_pop` gets stuck because of
defeq issues in the implicit size argument.
-/
@[simp] theorem getElem_pop' (xs : Vector α (n + 1)) (i : Nat) (h : i < n + 1 - 1) :
@getElem (Vector α n) Nat α (fun _ i => i < n) instGetElemNatLt xs.pop i h = xs[i] :=
getElem_pop h
theorem getElem?_pop (xs : Vector α n) (i : Nat) :
xs.pop[i]? = if i < n - 1 then xs[i]? else none := by
rcases xs with xs, rfl
@@ -2585,6 +2601,161 @@ theorem replace_extract {xs : Vector α n} {i : Nat} :
end replace
/-! ## Logic -/
/-! ### any / all -/
theorem not_any_eq_all_not (xs : Vector α n) (p : α Bool) : (!xs.any p) = xs.all fun a => !p a := by
rcases xs with xs, rfl
simp [Array.not_any_eq_all_not]
theorem not_all_eq_any_not (xs : Vector α n) (p : α Bool) : (!xs.all p) = xs.any fun a => !p a := by
rcases xs with xs, rfl
simp [Array.not_all_eq_any_not]
theorem and_any_distrib_left (xs : Vector α n) (p : α Bool) (q : Bool) :
(q && xs.any p) = xs.any fun a => q && p a := by
rcases xs with xs, rfl
simp [Array.and_any_distrib_left]
theorem and_any_distrib_right (xs : Vector α n) (p : α Bool) (q : Bool) :
(xs.any p && q) = xs.any fun a => p a && q := by
rcases xs with xs, rfl
simp [Array.and_any_distrib_right]
theorem or_all_distrib_left (xs : Vector α n) (p : α Bool) (q : Bool) :
(q || xs.all p) = xs.all fun a => q || p a := by
rcases xs with xs, rfl
simp [Array.or_all_distrib_left]
theorem or_all_distrib_right (xs : Vector α n) (p : α Bool) (q : Bool) :
(xs.all p || q) = xs.all fun a => p a || q := by
rcases xs with xs, rfl
simp [Array.or_all_distrib_right]
theorem any_eq_not_all_not (xs : Vector α n) (p : α Bool) : xs.any p = !xs.all (!p .) := by
simp only [not_all_eq_any_not, Bool.not_not]
@[simp] theorem any_map {xs : Vector α n} {p : β Bool} : (xs.map f).any p = xs.any (p f) := by
rcases xs with xs, rfl
simp
@[simp] theorem all_map {xs : Vector α n} {p : β Bool} : (xs.map f).all p = xs.all (p f) := by
rcases xs with xs, rfl
simp
@[simp] theorem any_filter {xs : Vector α n} {p q : α Bool} :
(xs.filter p).any q = xs.any fun a => p a && q a := by
rcases xs with xs, rfl
simp
@[simp] theorem all_filter {xs : Vector α n} {p q : α Bool} :
(xs.filter p).all q = xs.all fun a => p a q a := by
rcases xs with xs, rfl
simp
@[simp] theorem any_filterMap {xs : Vector α n} {f : α Option β} {p : β Bool} :
(xs.filterMap f).any p = xs.any fun a => match f a with | some b => p b | none => false := by
rcases xs with xs, rfl
simp
rfl
@[simp] theorem all_filterMap {xs : Vector α n} {f : α Option β} {p : β Bool} :
(xs.filterMap f).all p = xs.all fun a => match f a with | some b => p b | none => true := by
rcases xs with xs, rfl
simp
rfl
@[simp] theorem any_append {xs : Vector α n} {ys : Vector α m} :
(xs ++ ys).any f = (xs.any f || ys.any f) := by
rcases xs with xs, rfl
rcases ys with ys, rfl
simp
@[simp] theorem all_append {xs : Vector α n} {ys : Vector α m} :
(xs ++ ys).all f = (xs.all f && ys.all f) := by
rcases xs with xs, rfl
rcases ys with ys, rfl
simp
@[congr] theorem anyM_congr [Monad m]
{xs ys : Vector α n} (w : xs = ys) {p q : α m Bool} (h : a, p a = q a) :
xs.anyM p = ys.anyM q := by
have : p = q := by funext a; apply h
subst this
subst w
rfl
@[congr] theorem any_congr
{xs ys : Vector α n} (w : xs = ys) {p q : α Bool} (h : a, p a = q a) :
xs.any p = ys.any q := by
unfold any
apply anyM_congr w h
@[congr] theorem allM_congr [Monad m]
{xs ys : Vector α n} (w : xs = ys) {p q : α m Bool} (h : a, p a = q a) :
xs.allM p = ys.allM q := by
have : p = q := by funext a; apply h
subst this
subst w
rfl
@[congr] theorem all_congr
{xs ys : Vector α n} (w : xs = ys) {p q : α Bool} (h : a, p a = q a) :
xs.all p = ys.all q := by
unfold all
apply allM_congr w h
@[simp] theorem any_flatten {xss : Vector (Vector α n) m} : xss.flatten.any f = xss.any (any · f) := by
cases xss using vector₂_induction
simp
@[simp] theorem all_flatten {xss : Vector (Vector α n) m} : xss.flatten.all f = xss.all (all · f) := by
cases xss using vector₂_induction
simp
@[simp] theorem any_flatMap {xs : Vector α n} {f : α Vector β m} {p : β Bool} :
(xs.flatMap f).any p = xs.any fun a => (f a).any p := by
rcases xs with xs
simp only [flatMap_mk, any_mk, Array.size_flatMap, size_toArray, Array.any_flatMap']
congr
funext
congr
simp [Vector.size_toArray]
@[simp] theorem all_flatMap {xs : Vector α n} {f : α Vector β m} {p : β Bool} :
(xs.flatMap f).all p = xs.all fun a => (f a).all p := by
rcases xs with xs
simp only [flatMap_mk, all_mk, Array.size_flatMap, size_toArray, Array.all_flatMap']
congr
funext
congr
simp [Vector.size_toArray]
@[simp] theorem any_reverse {xs : Vector α n} : xs.reverse.any f = xs.any f := by
rcases xs with xs, rfl
simp
@[simp] theorem all_reverse {xs : Vector α n} : xs.reverse.all f = xs.all f := by
rcases xs with xs, rfl
simp
@[simp] theorem any_cast {xs : Vector α n} : (xs.cast h).any f = xs.any f := by
rcases xs with xs, rfl
simp
@[simp] theorem all_cast {xs : Vector α n} : (xs.cast h).all f = xs.all f := by
rcases xs with xs, rfl
simp
@[simp] theorem any_mkVector {n : Nat} {a : α} :
(mkVector n a).any f = if n = 0 then false else f a := by
induction n <;> simp_all [mkVector_succ']
@[simp] theorem all_mkVector {n : Nat} {a : α} :
(mkVector n a).all f = if n = 0 then true else f a := by
induction n <;> simp_all +contextual [mkVector_succ']
/-! Content below this point has not yet been aligned with `List` and `Array`. -/
set_option linter.indexVariables false in
@@ -2592,14 +2763,6 @@ set_option linter.indexVariables false in
rcases xs with xs, rfl
simp
/--
Variant of `getElem_pop` that will sometimes fire when `getElem_pop` gets stuck because of
defeq issues in the implicit size argument.
-/
@[simp] theorem getElem_pop' (xs : Vector α (n + 1)) (i : Nat) (h : i < n + 1 - 1) :
@getElem (Vector α n) Nat α (fun _ i => i < n) instGetElemNatLt xs.pop i h = xs[i] :=
getElem_pop h
@[simp] theorem push_pop_back (xs : Vector α (n + 1)) : xs.pop.push xs.back = xs := by
ext i
by_cases h : i < n
@@ -2663,11 +2826,6 @@ theorem swap_comm (xs : Vector α n) {i j : Nat} {hi hj} :
simp only [swap_mk, mk.injEq]
rw [Array.swap_comm]
/-! ### range -/
@[simp] theorem getElem_range (i : Nat) (hi : i < n) : (Vector.range n)[i] = i := by
simp [Vector.range]
/-! ### take -/
@[simp] theorem getElem_take (xs : Vector α n) (j : Nat) (hi : i < min n j) :

View File

@@ -115,6 +115,9 @@ theorem range'_eq_append_iff : range' s (n + m) = xs ++ ys ↔ xs = range' s n
/-! ### range -/
@[simp] theorem getElem_range (i : Nat) (hi : i < n) : (Vector.range n)[i] = i := by
simp [Vector.range]
theorem range_eq_range' (n : Nat) : range n = range' 0 n := by
simp [range, range', Array.range_eq_range']

View File

@@ -111,9 +111,7 @@ def isExact : Constraint → Bool
theorem not_sat_of_isImpossible (h : isImpossible c) {t} : ¬ c.sat t := by
rcases c with _ | l, _ | u <;> simp [isImpossible, sat] at h
intro w
rw [Int.not_le]
exact Int.lt_of_lt_of_le h w
exact Int.lt_of_lt_of_le h
/--
Scale a constraint by multiplying by an integer.
@@ -139,17 +137,14 @@ theorem scale_sat {c : Constraint} (k) (w : c.sat t) : (scale k c).sat (k * t) :
· rcases c with _ | l, _ | u <;> split <;> rename_i h <;> simp_all [sat, flip, map]
· replace h := Int.le_of_lt h
exact Int.mul_le_mul_of_nonneg_left w h
· rw [Int.not_lt] at h
exact Int.mul_le_mul_of_nonpos_left h w
· exact Int.mul_le_mul_of_nonpos_left h w
· replace h := Int.le_of_lt h
exact Int.mul_le_mul_of_nonneg_left w h
· rw [Int.not_lt] at h
exact Int.mul_le_mul_of_nonpos_left h w
· exact Int.mul_le_mul_of_nonpos_left h w
· constructor
· exact Int.mul_le_mul_of_nonneg_left w.1 (Int.le_of_lt h)
· exact Int.mul_le_mul_of_nonneg_left w.2 (Int.le_of_lt h)
· replace h := Int.not_lt.mp h
constructor
· constructor
· exact Int.mul_le_mul_of_nonpos_left h w.2
· exact Int.mul_le_mul_of_nonpos_left h w.1
@@ -181,13 +176,13 @@ theorem combo_sat (a) (w₁ : c₁.sat x₁) (b) (w₂ : c₂.sat x₂) :
/-- The conjunction of two constraints. -/
def combine (x y : Constraint) : Constraint where
lowerBound := max x.lowerBound y.lowerBound
upperBound := min x.upperBound y.upperBound
lowerBound := Option.merge max x.lowerBound y.lowerBound
upperBound := Option.merge min x.upperBound y.upperBound
theorem combine_sat : (c : Constraint) (c' : Constraint) (t : Int)
(c.combine c').sat t = (c.sat t c'.sat t) := by
rintro _ | l₁, _ | u₁ <;> rintro _ | l₂, _ | u₂ t
<;> simp [sat, LowerBound.sat, UpperBound.sat, combine, Int.le_min, Int.max_le] at *
<;> simp [sat, LowerBound.sat, UpperBound.sat, combine, Int.le_min, Int.max_le, Option.merge] at *
· rw [And.comm]
· rw [ and_assoc, And.comm (a := l₂ t), and_assoc]
· rw [and_assoc]
@@ -210,21 +205,19 @@ theorem div_sat (c : Constraint) (t : Int) (k : Nat) (n : k ≠ 0) (h : (k : Int
· simp_all [sat, div]
· simp [sat, div] at w
apply Int.le_of_sub_nonneg
rw [ Int.sub_ediv_of_dvd _ h, ge_iff_le, Int.div_nonneg_iff_of_pos n]
rw [ Int.sub_ediv_of_dvd _ h, Int.ediv_nonneg_iff_of_pos n]
exact Int.sub_nonneg_of_le w
· simp [sat, div] at w
apply Int.le_of_sub_nonneg
rw [Int.sub_neg, Int.add_ediv_of_dvd_left h, ge_iff_le,
Int.div_nonneg_iff_of_pos n]
rw [Int.sub_neg, Int.add_ediv_of_dvd_left h, Int.ediv_nonneg_iff_of_pos n]
exact Int.sub_nonneg_of_le w
· simp [sat, div] at w
constructor
· apply Int.le_of_sub_nonneg
rw [Int.sub_neg, Int.add_ediv_of_dvd_left h, ge_iff_le,
Int.div_nonneg_iff_of_pos n]
rw [Int.sub_neg, Int.add_ediv_of_dvd_left h, Int.ediv_nonneg_iff_of_pos n]
exact Int.sub_nonneg_of_le w.1
· apply Int.le_of_sub_nonneg
rw [ Int.sub_ediv_of_dvd _ h, ge_iff_le, Int.div_nonneg_iff_of_pos n]
rw [ Int.sub_ediv_of_dvd _ h, Int.ediv_nonneg_iff_of_pos n]
exact Int.sub_nonneg_of_le w.2
/--

View File

@@ -514,7 +514,9 @@ def inferStep : InterpM Bool := do
let currentVal getFunVal idx
withReader (fun ctx => { ctx with currFnIdx := idx }) do
decl.params.forM fun p => updateVarAssignment p.fvarId .top
decl.value.forCodeM interpCode
match decl.value with
| .code code .. => interpCode code
| .extern .. => updateCurrFnSummary .top
let newVal getFunVal idx
if currentVal != newVal then
return true

View File

@@ -149,8 +149,10 @@ def Decl.reduceArity (decl : Decl) : CompilerM (Array Decl) := do
match decl.value with
| .code code =>
let used collectUsedParams decl
if used.size == decl.params.size then
return #[decl] -- Declarations uses all parameters
if used.size == decl.params.size || used.size == 0 then
-- Do nothing if all params were used, or if no params were used. In the latter case,
-- this would promote the decl to a constant, which could execute unreachable code.
return #[decl]
else
trace[Compiler.reduceArity] "{decl.name}, used params: {used.toList.map mkFVar}"
let mask := decl.params.map fun param => used.contains param.fvarId

View File

@@ -535,7 +535,9 @@ opaque compileDeclsOld (env : Environment) (opt : @& Options) (decls : @& List N
-- `ref?` is used for error reporting if available
partial def compileDecls (decls : List Name) (ref? : Option Declaration := none)
(logErrors := true) : CoreM Unit := do
if !Elab.async.get ( getOptions) then
-- When inside `realizeConst`, do compilation synchronously so that `_cstage*` constants are found
-- by the replay code
if !Elab.async.get ( getOptions) || ( getEnv).isRealizing then
doCompile
return
let env getEnv

View File

@@ -190,6 +190,26 @@ where
return (x, toExpr <| UInt64.ofBitVec (h value.bv))
else
throwError m!"Value for UInt64 was not 64 bit but {value.w} bit"
| Int8.toBitVec x =>
if h : value.w = 8 then
return (x, toExpr <| Int8.ofBitVec (h value.bv))
else
throwError m!"Value for Int8 was not 8 bit but {value.w} bit"
| Int16.toBitVec x =>
if h : value.w = 16 then
return (x, toExpr <| Int16.ofBitVec (h value.bv))
else
throwError m!"Value for Int16 was not 16 bit but {value.w} bit"
| Int32.toBitVec x =>
if h : value.w = 32 then
return (x, toExpr <| Int32.ofBitVec (h value.bv))
else
throwError m!"Value for Int32 was not 32 bit but {value.w} bit"
| Int64.toBitVec x =>
if h : value.w = 64 then
return (x, toExpr <| Int64.ofBitVec (h value.bv))
else
throwError m!"Value for Int64 was not 64 bit but {value.w} bit"
| _ =>
match var with
| .app (.const (.str p s) []) arg =>

View File

@@ -4,6 +4,7 @@ Released under Apache 2.0 license as described in the file LICENSE.
Authors: Henrik Böving
-/
prelude
import Init.Data.SInt.Basic
import Lean.Elab.Tactic.BVDecide.Frontend.Normalize.Basic
import Lean.Elab.Tactic.BVDecide.Frontend.Attr
import Lean.Elab.Tactic.Simp
@@ -14,7 +15,7 @@ This module contains the implementation of the pre processing pass for reducing
It:
1. runs the `int_toBitVec` simp set
2. If `USize.toBitVec` is used anywhere looks for equations of the form
2. If `USize.toBitVec`/`ISize.toBitVec` is used anywhere looks for equations of the form
`System.Platform.numBits = constant` (or flipped) and uses them to convert the system back to
fixed width.
-/
@@ -25,11 +26,12 @@ namespace Frontend.Normalize
open Lean.Meta
/--
Contains information for the `USize` elimination pass.
Contains information for the `USize`/`ISize` elimination pass.
-/
structure USizeState where
structure SizeState where
/--
Contains terms of the form `USize.toBitVec e` that we will translate to constant width `BitVec`.
Contains terms of the form `USize.toBitVec e` and `ISize.toBitVec e` that we will translate to
constant width `BitVec`.
-/
relevantTerms : Std.HashSet Expr := {}
/--
@@ -37,16 +39,16 @@ structure USizeState where
-/
relevantHyps : Std.HashSet FVarId := {}
private abbrev M := StateRefT USizeState MetaM
private abbrev M := StateRefT SizeState MetaM
namespace M
@[inline]
def addUSizeTerm (e : Expr) : M Unit := do
def addSizeTerm (e : Expr) : M Unit := do
modify fun s => { s with relevantTerms := s.relevantTerms.insert e }
@[inline]
def addUSizeHyp (f : FVarId) : M Unit := do
def addSizeHyp (f : FVarId) : M Unit := do
modify fun s => { s with relevantHyps := s.relevantHyps.insert f }
end M
@@ -64,30 +66,30 @@ def intToBitVecPass : Pass where
let hyps goal.getNondepPropHyps
let result?, _ simpGoal goal (ctx := simpCtx) (fvarIdsToSimp := hyps)
let some (_, goal) := result? | return none
handleUSize goal |>.run' {}
handleSize goal |>.run' {}
where
handleUSize (goal : MVarId) : M MVarId := do
if detectUSize goal then
replaceUSize goal
handleSize (goal : MVarId) : M MVarId := do
if detectSize goal then
replaceSize goal
else
return goal
detectUSize (goal : MVarId) : M Bool := do
detectSize (goal : MVarId) : M Bool := do
goal.withContext do
for hyp in getPropHyps do
( hyp.getType).forEachWhere
(stopWhenVisited := true)
(·.isAppOfArity ``USize.toBitVec 1)
(fun e => e.isAppOfArity ``USize.toBitVec 1 || e.isAppOfArity ``ISize.toBitVec 1)
fun e => do
M.addUSizeTerm e
M.addUSizeHyp hyp
M.addSizeTerm e
M.addSizeHyp hyp
return !( get).relevantTerms.isEmpty
/--
Turn `goal` into a goal containing `BitVec const` instead of `USize`.
Turn `goal` into a goal containing `BitVec const` instead of `USize`/`ISize`.
-/
replaceUSize (goal : MVarId) : M MVarId := do
replaceSize (goal : MVarId) : M MVarId := do
if let some (numBits, numBitsEq) findNumBitsEq goal then
goal.withContext do
let relevantHyps := ( get).relevantHyps.toArray.map mkFVar
@@ -138,13 +140,14 @@ where
numBitsEq
(mkMVar newGoal)
goal.assign <| mkAppN casesOn (relevantTerms ++ abstractedHyps)
-- remove all of the hold hypotheses about USize.toBitVec to prevent false counter examples
-- remove all of the hold hypotheses about USize.toBitVec/ISize.toBitVec to prevent
-- false counter examples
(newGoal, _) newGoal.tryClearMany' (abstractedHyps.map Expr.fvarId!)
-- intro both the new `BitVec const` as well as all hypotheses about them
(_, newGoal) newGoal.introN (relevantTerms.size + abstractedHyps.size)
return newGoal
else
logWarning m!"Detected USize in the goal but no hypothesis about System.Platform.numBits, consider case splitting on {mkConst ``System.Platform.numBits_eq}"
logWarning m!"Detected USize/ISize in the goal but no hypothesis about System.Platform.numBits, consider case splitting on {mkConst ``System.Platform.numBits_eq}"
return goal
/--

View File

@@ -15,7 +15,7 @@ structures containing information about supported types into individual parts re
The implementation runs cases recursively on all "interesting" types where a type is interesting if
it is a non recursive structure and at least one of the following conditions hold:
- it contains something of type `BitVec`/`UIntX`/`Bool`
- it contains something of type `BitVec`/`UIntX`/`IntX`/`Bool`
- it is parametrized by an interesting type
- it contains another interesting type
Afterwards we also:

View File

@@ -4,6 +4,7 @@ Released under Apache 2.0 license as described in the file LICENSE.
Authors: Henrik Böving
-/
prelude
import Init.Data.SInt.Basic
import Lean.Elab.Tactic.BVDecide.Frontend.Normalize.Basic
/-!
@@ -64,6 +65,11 @@ where
| UInt32 => return true
| UInt64 => return true
| USize => return true
| Int8 => return true
| Int16 => return true
| Int32 => return true
| Int64 => return true
| ISize => return true
| Bool => return true
| _ =>
let some const := expr.getAppFn.constName? | return false

View File

@@ -4,7 +4,7 @@ Released under Apache 2.0 license as described in the file LICENSE.
Authors: Henrik Böving
-/
prelude
import Lean.Data.RBMap
import Init.Data.Nat.Fold
import Std.Tactic.BVDecide.LRAT.Actions
import Std.Data.HashMap
@@ -17,7 +17,6 @@ This module implements the LRAT trimming algorithm described in section 4 of
namespace Lean.Elab.Tactic.BVDecide
namespace LRAT
open Lean (RBMap)
open Std.Tactic.BVDecide.LRAT (IntAction)
namespace trim
@@ -41,16 +40,18 @@ structure Context where
structure State where
/--
The set of used proof step ids.
For each proof step `i` contains at index `i - initialId` `0` if `i` is unused, `1` if it is
used.
-/
used : RBMap Nat Unit compare := {}
used : ByteArray
/--
A mapping from old proof step ids to new ones. Used such that the proof remains a sequence without
For each proof step `i` contains at index `i - initialId` the step that `i` maps to in the new
proof or `0` if that step is not yet set. Used such that the proof remains a sequence without
gaps.
-/
mapped : Std.HashMap Nat Nat := {}
mapped : Array Nat
abbrev M : Type Type := ReaderT Context <| ExceptT String <| StateM State
abbrev M : Type Type := ReaderT Context <| StateM State
namespace M
@@ -78,7 +79,9 @@ def run (proof : Array IntAction) (x : M α) : Except String α := do
| .addEmpty id .. | .addRup id .. | .addRat id .. => acc.insert id a
| .del .. => acc
let proof := proof.foldl (init := {}) folder
ReaderT.run x { proof, initialId, addEmptyId } |>.run |>.run' {}
let used := Nat.fold proof.size (init := ByteArray.mkEmpty proof.size) (fun _ _ acc => acc.push 0)
let mapped := Array.mkArray proof.size 0
return ReaderT.run x { proof, initialId, addEmptyId } |>.run' { used, mapped }
@[inline]
def getInitialId : M Nat := do
@@ -90,6 +93,10 @@ def getEmptyId : M Nat := do
let ctx read
return ctx.addEmptyId
@[inline]
private def idIndex (id : Nat) : M Nat := do
return id - ( M.getInitialId)
@[inline]
def getProofStep (id : Nat) : M (Option IntAction) := do
let ctx read
@@ -98,22 +105,20 @@ def getProofStep (id : Nat) : M (Option IntAction) := do
@[inline]
def isUsed (id : Nat) : M Bool := do
let s get
return s.used.contains id
return s.used[ idIndex id]! == 1
@[inline]
def markUsed (id : Nat) : M Unit := do
-- If we are referring to a proof step that is not part of the proof, it is part of the CNF.
-- We do not trim the CNF so just forget about the fact that this step was used.
if ( getProofStep id).isSome then
modify (fun s => { s with used := s.used.insert id () })
if id >= ( M.getInitialId) then
let idx idIndex id
modify (fun s => { s with used := s.used.set! idx 1 })
@[inline]
def getUsedSet : M (RBMap Nat Unit Ord.compare) := do
let s get
return s.used
def registerIdMap (oldId : Nat) (newId : Nat) : M Unit := do
modify (fun s => { s with mapped := s.mapped.insert oldId newId })
let idx idIndex oldId
modify (fun s => { s with mapped := s.mapped.set! idx newId })
def mapStep (step : IntAction) : M IntAction := do
match step with
@@ -139,8 +144,12 @@ def mapStep (step : IntAction) : M IntAction := do
where
@[inline]
mapIdent (ident : Nat) : M Nat := do
let s get
return s.mapped[ident]? |>.getD ident
if ident < ( getInitialId) then
return ident
else
let s get
let newId := s.mapped[ idIndex ident]!
return newId
end M
@@ -150,14 +159,17 @@ up with DFS.
-/
partial def useAnalysis : M Unit := do
let emptyId M.getEmptyId
go [emptyId]
go #[emptyId]
where
go (workList : List Nat) : M Unit := do
match workList with
| [] => return ()
| id :: workList =>
go (worklist : Array Nat) : M Unit := do
let mut worklist := worklist
if h : worklist.size = 0 then
return ()
else
let id := worklist.back
worklist := worklist.pop
if M.isUsed id then
go workList
go worklist
else
M.markUsed id
let step? M.getProofStep id
@@ -165,36 +177,37 @@ where
| some step =>
match step with
| .addEmpty _ hints =>
let workList := hints.toList ++ workList
go workList
worklist := worklist ++ hints
go worklist
| .addRup _ _ hints =>
let workList := hints.toList ++ workList
go workList
worklist := worklist ++ hints
go worklist
| .addRat _ _ _ rupHints ratHints =>
let folder acc a :=
a.fst :: a.snd.toList ++ acc
let ratHints := ratHints.foldl (init := []) folder
let workList := rupHints.toList ++ ratHints ++ workList
go workList
| .del .. => go workList
| none => go workList
let folder acc a := acc.push a.fst ++ a.snd
let ratHints := ratHints.foldl (init := Array.mkEmpty ratHints.size) folder
worklist := worklist ++ ratHints ++ rupHints
go worklist
| .del .. => go worklist
| none => go worklist
/--
Map the set of used proof steps to a new LRAT proof that has no holes in the sequence of proof
identifiers.
-/
def mapping : M (Array IntAction) := do
let used M.getUsedSet
let mut nextMapped M.getInitialId
let mut newProof := Array.mkEmpty used.size
for (id, _) in used do
M.registerIdMap id nextMapped
-- This should never panic as the use def analysis has already marked this step as being used
-- so it must exist.
let step := ( M.getProofStep id).get!
let newStep M.mapStep step
newProof := newProof.push newStep
nextMapped := nextMapped + 1
let emptyId M.getEmptyId
let initialId M.getInitialId
let mut nextMapped := initialId
let mut newProof := #[]
for id in [initialId:emptyId+1] do
if M.isUsed id then
M.registerIdMap id nextMapped
-- This should never panic as the use def analysis has already marked this step as being used
-- so it must exist.
let step := ( M.getProofStep id).get!
let newStep M.mapStep step
newProof := newProof.push newStep
nextMapped := nextMapped + 1
return newProof
def go : M (Array IntAction) := do
@@ -207,7 +220,7 @@ end trim
Trim the LRAT `proof` by removing all steps that are not used in reaching the empty clause
conclusion.
-/
def trim (proof : Array IntAction) : Except String (Array IntAction) :=
def trim (proof : Array IntAction) : Except String (Array IntAction) := do
trim.go.run proof
end LRAT

View File

@@ -439,11 +439,14 @@ private structure AsyncConsts where
deriving Inhabited
private def AsyncConsts.add (aconsts : AsyncConsts) (aconst : AsyncConst) : AsyncConsts :=
{ aconsts with
let normalizedName := privateToUserName aconst.constInfo.name
if let some aconst' := aconsts.normalizedTrie.find? normalizedName then
panic! s!"AsyncConsts.add: duplicate normalized declaration name {aconst.constInfo.name} vs. {aconst'.constInfo.name}"
else { aconsts with
size := aconsts.size + 1
revList := aconst :: aconsts.revList
map := aconsts.map.insert aconst.constInfo.name aconst
normalizedTrie := aconsts.normalizedTrie.insert (privateToUserName aconst.constInfo.name) aconst
normalizedTrie := aconsts.normalizedTrie.insert normalizedName aconst
}
private def AsyncConsts.find? (aconsts : AsyncConsts) (declName : Name) : Option AsyncConst :=
@@ -451,8 +454,9 @@ private def AsyncConsts.find? (aconsts : AsyncConsts) (declName : Name) : Option
/-- Finds the constant in the collection that is a prefix of `declName`, if any. -/
private def AsyncConsts.findPrefix? (aconsts : AsyncConsts) (declName : Name) : Option AsyncConst :=
-- as macro scopes are a strict suffix,
aconsts.normalizedTrie.findLongestPrefix? (privateToUserName declName.eraseMacroScopes)
-- as macro scopes are a strict suffix, we do not have to remove them before calling
-- `findLongestPrefix?`
aconsts.normalizedTrie.findLongestPrefix? (privateToUserName declName)
/-- Context for `realizeConst` established by `enableRealizationsForConst`. -/
private structure RealizationContext where
@@ -504,8 +508,8 @@ structure Environment where
/-- Information about this asynchronous branch of the environment, if any. -/
private asyncCtx? : Option AsyncContext := none
/--
Realized constants belonging to imported declarations. `none` only from `Environment.ofKernelEnv`,
which should never leak into general elaboration.
Realized constants belonging to imported declarations. Must be initialized by calling
`enableRealizationsForImports`.
-/
private realizedImportedConsts? : Option RealizationContext
/--
@@ -644,6 +648,21 @@ def findConstVal? (env : Environment) (n : Name) : Option ConstantVal := do
return asyncConst.constInfo.toConstantVal
else env.findNoAsync n |>.map (·.toConstantVal)
/--
Allows `realizeConst` calls for imported declarations in all derived environment branches.
Realizations will run using the given environment and options to ensure deterministic results.
This function should be called directly after `setMainModule` to ensure that all realized constants
use consistent private prefixes.
-/
def enableRealizationsForImports (env : Environment) (opts : Options) : BaseIO Environment :=
return { env with realizedImportedConsts? := some {
-- safety: `RealizationContext` is private
env := unsafe unsafeCast env
opts
constsRef := ( IO.mkRef {})
}
}
/--
Allows `realizeConst` calls for the given declaration in all derived environment branches.
Realizations will run using the given environment and options to ensure deterministic results. Note
@@ -893,7 +912,10 @@ def imports (env : Environment) : Array Import :=
def allImportedModuleNames (env : Environment) : Array Name :=
env.header.moduleNames
def setMainModule (env : Environment) (m : Name) : Environment :=
def setMainModule (env : Environment) (m : Name) : Environment := Id.run do
if env.realizedImportedConsts?.isSome then
panic! "Environment.setMainModule: cannot set after `enableRealizationsForImports`"
return env
env.modifyCheckedAsync ({ · with header.mainModule := m })
def mainModule (env : Environment) : Name :=
@@ -1078,9 +1100,6 @@ def modifyState {σ : Type} (ext : EnvExtension σ) (env : Environment) (f : σ
{if asyncCtx.realizing then "realization" else "async"} context '{asyncCtx.declPrefix}'"
return { env with checkedWithoutAsync.extensions := unsafe ext.modifyStateImpl env.checkedWithoutAsync.extensions f }
| .local =>
if let some asyncCtx := env.asyncCtx?.filter (·.realizing) then
panic! s!"Environment.modifyState: environment extension is marked as `local` but used in \
realization context '{asyncCtx.declPrefix}'"
return { env with checkedWithoutAsync.extensions := unsafe ext.modifyStateImpl env.checkedWithoutAsync.extensions f }
| _ =>
if ext.replay?.isNone then
@@ -1692,14 +1711,6 @@ def finalizeImport (s : ImportState) (imports : Array Import) (opts : Options) (
Safety: There are no concurrent accesses to `env` at this point. -/
env unsafe Runtime.markPersistent env
env finalizePersistentExtensions env s.moduleData opts
env := { env with
realizedImportedConsts? := some {
-- safety: `RealizationContext` is private
env := unsafe unsafeCast env
opts
constsRef := ( IO.mkRef {})
}
}
if leakEnv then
/- Ensure the final environment including environment extension states is
marked persistent as documented.
@@ -1870,6 +1881,7 @@ def realizeConst (env : Environment) (forConst : Name) (constName : Name)
-- allow realizations to recursively realize other constants for `forConst`. Do note that
-- this allows for recursive realization of `constName` itself, which will deadlock.
realizedLocalConsts := realizeEnv.realizedLocalConsts.insert forConst ctx
realizedImportedConsts? := env.realizedImportedConsts?
}
-- ensure realized constants are nested below `forConst` and that environment extension
-- modifications know they are in an async context
@@ -1882,7 +1894,8 @@ def realizeConst (env : Environment) (forConst : Name) (constName : Name)
-- find new constants incl. nested realizations, add current extension state, and compute
-- closure
let consts := realizeEnv'.asyncConsts.revList.take (realizeEnv'.asyncConsts.size - realizeEnv.asyncConsts.size)
let numNewConsts := realizeEnv'.asyncConsts.size - realizeEnv.asyncConsts.size
let consts := realizeEnv'.asyncConsts.revList.take numNewConsts |>.reverse
let consts := consts.map fun c =>
if c.exts?.isNone then
{ c with exts? := some <| .pure realizeEnv'.checkedWithoutAsync.extensions }
@@ -1892,7 +1905,11 @@ def realizeConst (env : Environment) (forConst : Name) (constName : Name)
prom.resolve (consts, replay, dyn)
pure (consts, replay, dyn)
return ({ env with
asyncConsts := consts.foldl (·.add) env.asyncConsts
asyncConsts := consts.foldl (init := env.asyncConsts) fun consts c =>
if consts.find? c.constInfo.name |>.isSome then
consts
else
consts.add c
checked := env.checked.map replay
}, dyn)
where

View File

@@ -425,6 +425,7 @@ where
return { diagnostics, result? := none }
let headerEnv := headerEnv.setMainModule setup.mainModuleName
let headerEnv headerEnv.enableRealizationsForImports setup.opts
let mut traceState := default
if trace.profiler.output.get? setup.opts |>.isSome then
traceState := {

View File

@@ -2215,16 +2215,24 @@ private partial def setAllDiagRanges (snap : Language.SnapshotTree) (pos endPos
task := ( BaseIO.mapTask (t := task.task) (setAllDiagRanges · pos endPos)) })
}
open Language
private structure RealizeConstantResult where
snap : SnapshotTree
error? : Option Exception
deriving TypeName
/--
Makes the helper constant `constName` that is derived from `forConst` available in the environment.
`enableRealizationsForConst forConst` must have been called first on this environment branch. If
this is the first environment branch requesting `constName` to be realized (atomically), `realize`
is called with the environment and options at the time of calling `enableRealizationsForConst` if
`forConst` is from the current module and the state just after importing otherwise, thus helping
achieve deterministic results despite the non-deterministic choice of which thread is tasked with
realization. In other words, the state after calling `realizeConst` is *as if* `realize` had been
called immediately after `enableRealizationsForConst forConst`, though the effects of this call are
visible only after calling `realizeConst`. See below for more details on the replayed effects.
`forConst` is from the current module and the state just after importing (when
`enableRealizationsForImports` should be called) otherwise, thus helping achieve deterministic
results despite the non-deterministic choice of which thread is tasked with realization. In other
words, the state after calling `realizeConst` is *as if* `realize` had been called immediately after
`enableRealizationsForConst forConst`, though the effects of this call are visible only after
calling `realizeConst`. See below for more details on the replayed effects.
`realizeConst` cannot check what other data is captured in the `realize` closure,
so it is best practice to extract it into a separate function and pay close attention to the passed
@@ -2241,20 +2249,25 @@ to add `constName` to the environment, an appropriate diagnostic is reported to
constants are added to the environment.
-/
def realizeConst (forConst : Name) (constName : Name) (realize : MetaM Unit) :
MetaM Unit := withTraceNode `Meta.realizeConst (fun _ => return constName) do
MetaM Unit := do
let env getEnv
let coreCtx readThe Core.Context
-- these fields should be invariant throughout the file
let coreCtx := { fileName := coreCtx.fileName, fileMap := coreCtx.fileMap }
let (env, dyn) env.realizeConst forConst constName (realizeAndReport coreCtx)
if let some snap := dyn.get? Language.SnapshotTree then
let mut snap := snap
-- localize diagnostics
if let some range := ( getRef).getRange? then
let fileMap getFileMap
snap setAllDiagRanges snap (fileMap.toPosition range.start) (fileMap.toPosition range.stop)
Core.logSnapshotTask <| .finished (stx? := none) snap
setEnv env
if env.contains constName then
return
withTraceNode `Meta.realizeConst (fun _ => return constName) do
let coreCtx readThe Core.Context
-- these fields should be invariant throughout the file
let coreCtx := { fileName := coreCtx.fileName, fileMap := coreCtx.fileMap }
let (env, dyn) env.realizeConst forConst constName (realizeAndReport coreCtx)
if let some res := dyn.get? RealizeConstantResult then
let mut snap := res.snap
-- localize diagnostics
if let some range := ( getRef).getRange? then
let fileMap getFileMap
snap setAllDiagRanges snap (fileMap.toPosition range.start) (fileMap.toPosition range.stop)
Core.logSnapshotTask <| .finished (stx? := none) snap
if let some e := res.error? then
throw e
setEnv env
where
-- similar to `wrapAsyncAsSnapshot` but not sufficiently so to share code
realizeAndReport (coreCtx : Core.Context) env opts := do
@@ -2267,14 +2280,20 @@ where
realize
if !( getEnv).contains constName then
throwError "Lean.Meta.realizeConst: {constName} was not added to the environment"
catch e : Exception =>
logError e.toMessageData
finally
addTraceAsMessages
let res? act |>.run' |>.run coreCtx { env } |>.toBaseIO
match res? with
| .ok ((output, ()), st) => pure (st.env, .mk ( Core.mkSnapshot output coreCtx st))
| .error _e => unreachable!; pure (env, .mk ({ diagnostics := .empty : Language.SnapshotLeaf}))
| .ok ((output, ()), st) => pure (st.env, .mk {
snap := ( Core.mkSnapshot output coreCtx st)
error? := none
: RealizeConstantResult
})
| .error e => pure (env, .mk {
snap := toSnapshotTree { diagnostics := .empty : Language.SnapshotLeaf}
error? := some e
: RealizeConstantResult
})
end Meta

View File

@@ -14,10 +14,13 @@ import Lean.Meta.Tactic.Grind.Arith.Cutsat.Types
import Lean.Meta.Tactic.Grind.Arith.Cutsat.Util
import Lean.Meta.Tactic.Grind.Arith.Cutsat.Var
import Lean.Meta.Tactic.Grind.Arith.Cutsat.EqCnstr
import Lean.Meta.Tactic.Grind.Arith.Cutsat.SearchM
import Lean.Meta.Tactic.Grind.Arith.Cutsat.Model
namespace Lean
builtin_initialize registerTraceClass `grind.cutsat
builtin_initialize registerTraceClass `grind.cutsat.model
builtin_initialize registerTraceClass `grind.cutsat.subst
builtin_initialize registerTraceClass `grind.cutsat.eq
builtin_initialize registerTraceClass `grind.cutsat.eq.unsat (inherited := true)
@@ -43,7 +46,12 @@ builtin_initialize registerTraceClass `grind.cutsat.le.upper (inherited := true)
builtin_initialize registerTraceClass `grind.cutsat.assign
builtin_initialize registerTraceClass `grind.cutsat.conflict
builtin_initialize registerTraceClass `grind.cutsat.diseq
builtin_initialize registerTraceClass `grind.cutsat.diseq.trivial (inherited := true)
builtin_initialize registerTraceClass `grind.debug.cutsat.eq
builtin_initialize registerTraceClass `grind.debug.cutsat.diseq
builtin_initialize registerTraceClass `grind.debug.cutsat.diseq.split
builtin_initialize registerTraceClass `grind.debug.cutsat.backtrack
end Lean

View File

@@ -17,9 +17,6 @@ private def _root_.Int.Linear.Poly.substVar (p : Poly) : GoalM (Option (Var × E
let p := p.mul (-b) |>.combine (c.p.mul a)
return some (x, c, p)
def mkEqCnstr (p : Poly) (h : EqCnstrProof) : GoalM EqCnstr := do
return { p, h, id := ( mkCnstrId) }
def EqCnstr.norm (c : EqCnstr) : GoalM EqCnstr := do
let c if c.p.isSorted then
pure c
@@ -52,8 +49,27 @@ partial def DiseqCnstr.applySubsts (c : DiseqCnstr) : GoalM DiseqCnstr := withIn
let c mkDiseqCnstr p (.subst x c₁ c)
applySubsts c
/--
Given a disequality `c`, tries to find an inequality to be refined using
`p ≤ 0 → p ≠ 0 → p + 1 ≤ 0`
-/
private def DiseqCnstr.findLe (c : DiseqCnstr) : GoalM Bool := do
let .add _ x _ := c.p | c.throwUnexpected
let s get'
let go (atLower : Bool) : GoalM Bool := do
let cs' := if atLower then s.lowers[x]! else s.uppers[x]!
for c' in cs' do
if c.p == c'.p || c.p.isNegEq c'.p then
c'.erase
let le mkLeCnstr (c'.p.addConst 1) (.ofLeDiseq c' c)
le.assert
return true
return false
go true <||> go false
def DiseqCnstr.assert (c : DiseqCnstr) : GoalM Unit := do
if ( inconsistent) then return ()
trace[grind.cutsat.assert] "{← c.pp}"
let c c.norm
let c c.applySubsts
if c.p.isUnsatDiseq then
@@ -62,8 +78,16 @@ def DiseqCnstr.assert (c : DiseqCnstr) : GoalM Unit := do
if c.isTrivial then
trace[grind.cutsat.diseq.trivial] "{← c.pp}"
return ()
let k := c.p.gcdCoeffs c.p.getConst
let c if k == 1 then
pure c
else
mkDiseqCnstr (c.p.div k) (.divCoeffs c)
if ( c.findLe) then
return ()
let .add _ x _ := c.p | c.throwUnexpected
c.p.updateOccs
trace[grind.cutsat.diseq] "{← c.pp}"
modify' fun s => { s with diseqs := s.diseqs.modify x (·.push c) }
if ( c.satisfied) == .false then
resetAssignmentFrom x
@@ -173,7 +197,8 @@ private def updateOccs (k : Int) (x : Var) (c : EqCnstr) : GoalM Unit := do
for y in ys do
updateOccsAt k x c y
def EqCnstr.assert (c : EqCnstr) : GoalM Unit := do
@[export lean_grind_cutsat_assert_eq]
def EqCnstr.assertImpl (c : EqCnstr) : GoalM Unit := do
if ( inconsistent) then return ()
trace[grind.cutsat.assert] "{← c.pp}"
let c c.norm

View File

@@ -45,6 +45,57 @@ partial def LeCnstr.applySubsts (c : LeCnstr) : GoalM LeCnstr := withIncRecDepth
let c c.applyEq a x c₁ b
applySubsts c
def _root_.Int.Linear.Poly.isNegEq (p₁ p₂ : Poly) : Bool :=
match p₁, p₂ with
| .num k₁, .num k₂ => k₁ == -k₂
| .add a₁ x p₁, .add a₂ y p₂ => a₁ == -a₂ && x == y && isNegEq p₁ p₂
| _, _ => false
def LeCnstr.erase (c : LeCnstr) : GoalM Unit := do
let .add a x _ := c.p | c.throwUnexpected
if a < 0 then
modify' fun s => { s with lowers := s.lowers.modify x fun cs' => cs'.filter fun c' => c'.p != c.p }
else
modify' fun s => { s with uppers := s.uppers.modify x fun cs' => cs'.filter fun c' => c'.p != c.p }
/--
Given a lower (upper) bound constraint `c`, tries to find
an imply equality by searching a upper (lower) bound constraint `c'` such that
`c.p == -c'.p`
-/
private def findEq (c : LeCnstr) : GoalM Bool := do
let .add a x _ := c.p | c.throwUnexpected
let s get'
let cs' := if a < 0 then s.uppers[x]! else s.lowers[x]!
for c' in cs' do
if c.p.isNegEq c'.p then
c'.erase
let eq mkEqCnstr c.p (.ofLeGe c c')
eq.assert
return true
return false
/--
Applies `p ≤ 0 → p ≠ 0 → p + 1 ≤ 0`
-/
private def refineWithDiseq (c : LeCnstr) : GoalM LeCnstr := do
let .add _ x _ := c.p | c.throwUnexpected
let mut c := c
repeat
let some c' refineWithDiseqStep? x c | return c
c := c'
return c
where
refineWithDiseqStep? (x : Var) (c : LeCnstr) : GoalM (Option LeCnstr) := do
let s get'
let cs' := s.diseqs[x]!
for c' in cs' do
if c.p == c'.p || c.p.isNegEq c'.p then
-- Remove `c'`
modify' fun s => { s with diseqs := s.diseqs.modify x fun cs' => cs'.filter fun c => c.p != c'.p }
return some ( mkLeCnstr (c.p.addConst 1) (.ofLeDiseq c c'))
return none
def LeCnstr.assert (c : LeCnstr) : GoalM Unit := do
if ( inconsistent) then return ()
let c c.norm
@@ -56,6 +107,9 @@ def LeCnstr.assert (c : LeCnstr) : GoalM Unit := do
trace[grind.cutsat.le.trivial] "{← c.pp}"
return ()
let .add a x _ := c.p | c.throwUnexpected
if ( findEq c) then
return ()
let c refineWithDiseq c
if a < 0 then
trace[grind.cutsat.le.lower] "{← c.pp}"
c.p.updateOccs

View File

@@ -0,0 +1,99 @@
/-
Copyright (c) 2025 Amazon.com, Inc. or its affiliates. All Rights Reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Leonardo de Moura
-/
prelude
import Lean.Meta.Tactic.Grind.Types
namespace Lean.Meta.Grind.Arith.Cutsat
private def isIntENode (n : ENode) : MetaM Bool :=
withDefault do isDefEq ( inferType n.self) Int.mkType
private def getCutsatAssignment? (goal : Goal) (node : ENode) : Option Rat := Id.run do
let some e := node.cutsat? | return none
let some x := goal.arith.cutsat.varMap.find? { expr := e } | return none
if h : x < goal.arith.cutsat.assignment.size then
return goal.arith.cutsat.assignment[x]
else
return none
private partial def satisfyDiseqs (goal : Goal) (a : Std.HashMap Expr Rat) (e : Expr) (v : Int) : Bool := Id.run do
let some parents := goal.parents.find? { expr := e } | return true
for parent in parents do
let_expr Eq _ lhs rhs := parent | continue
let some root := goal.getRoot? parent | continue
if root.isConstOf ``False then
let some lhsRoot := goal.getRoot? lhs | continue
let some rhsRoot := goal.getRoot? rhs | continue
if lhsRoot == e && !checkDiseq rhsRoot then return false
if rhsRoot == e && !checkDiseq lhsRoot then return false
return true
where
checkDiseq (other : Expr) : Bool :=
if let some v' := a[other]? then
v' != v
else
true
private partial def pickUnusedValue (goal : Goal) (a : Std.HashMap Expr Rat) (e : Expr) (next : Int) (alreadyUsed : Std.HashSet Int) : Int :=
go next
where
go (next : Int) : Int :=
if alreadyUsed.contains next then
go (next+1)
else if satisfyDiseqs goal a e next then
next
else
go (next + 1)
private def assignEqc (goal : Goal) (e : Expr) (v : Rat) (a : Std.HashMap Expr Rat) : Std.HashMap Expr Rat := Id.run do
let mut a := a
for e in goal.getEqc e do
a := a.insert e v
return a
private def isInterpretedTerm (e : Expr) : Bool :=
isIntNum e || e.isAppOf ``HAdd.hAdd || e.isAppOf ``HMul.hMul || e.isAppOf ``HSub.hSub
|| e.isAppOf ``Neg.neg -- TODO add missing ones
/--
Construct a model that statisfies all constraints in the cutsat model.
It also assigns values to integer terms that have not been internalized by the
cutsat model.
Remark: it uses rational numbers because cutsat may have failed to build an
integer model.
-/
def mkModel (goal : Goal) : MetaM (Array (Expr × Rat)) := do
let mut used : Std.HashSet Int := {}
let mut nextVal : Int := 0
let mut model := {}
let nodes := goal.getENodes
-- Assign on expressions associated with cutsat terms or interpreted terms
for node in nodes do
if isSameExpr node.root node.self then
if ( isIntENode node) then
if let some v := getCutsatAssignment? goal node then
model := assignEqc goal node.self v model
if v.den == 1 then used := used.insert v.num
else if let some v getIntValue? node.self then
model := assignEqc goal node.self v model
used := used.insert v
-- Assign the remaining ones with values not used by cutsat
for node in nodes do
if isSameExpr node.root node.self then
if ( isIntENode node) then
if ( getIntValue? node.self).isNone &&
(getCutsatAssignment? goal node).isNone then
let v := pickUnusedValue goal model node.self nextVal used
model := assignEqc goal node.self v model
used := used.insert v
let mut r := #[]
for (e, v) in model do
unless isInterpretedTerm e do
r := r.push (e, v)
return r
end Lean.Meta.Grind.Arith.Cutsat

View File

@@ -13,6 +13,7 @@ private def DvdCnstr.get_d_a (c : DvdCnstr) : GoalM (Int × Int) := do
let .add a _ _ := c.p | c.throwUnexpected
return (d, a)
mutual
partial def EqCnstr.toExprProof (c' : EqCnstr) : ProofM Expr := c'.caching do
match c'.h with
| .expr h =>
@@ -28,24 +29,11 @@ partial def EqCnstr.toExprProof (c' : EqCnstr) : ProofM Expr := c'.caching do
return mkApp8 (mkConst ``Int.Linear.eq_eq_subst)
( getContext) (toExpr x) (toExpr c₁.p) (toExpr c₂.p) (toExpr c'.p)
reflBoolTrue ( c₁.toExprProof) ( c₂.toExprProof)
partial def DiseqCnstr.toExprProof (c' : DiseqCnstr) : ProofM Expr := c'.caching do
match c'.h with
| .expr h =>
return h
| .core p₁ p₂ h =>
return mkApp6 (mkConst ``Int.Linear.diseq_of_core) ( getContext) (toExpr p₁) (toExpr p₂) (toExpr c'.p) reflBoolTrue h
| .norm c =>
return mkApp5 (mkConst ``Int.Linear.diseq_norm) ( getContext) (toExpr c.p) (toExpr c'.p) reflBoolTrue ( c.toExprProof)
| .divCoeffs c =>
let k := c.p.gcdCoeffs c.p.getConst
return mkApp6 (mkConst ``Int.Linear.diseq_coeff) ( getContext) (toExpr c.p) (toExpr c'.p) (toExpr k) reflBoolTrue ( c.toExprProof)
| .subst x c₁ c₂ =>
return mkApp8 (mkConst ``Int.Linear.eq_diseq_subst)
( getContext) (toExpr x) (toExpr c₁.p) (toExpr c₂.p) (toExpr c'.p)
| .ofLeGe c₁ c₂ =>
return mkApp6 (mkConst ``Int.Linear.eq_of_le_ge)
( getContext) (toExpr c.p) (toExpr c₂.p)
reflBoolTrue ( c₁.toExprProof) ( c₂.toExprProof)
mutual
partial def DvdCnstr.toExprProof (c' : DvdCnstr) : ProofM Expr := c'.caching do
match c'.h with
| .expr h =>
@@ -104,29 +92,131 @@ partial def LeCnstr.toExprProof (c' : LeCnstr) : ProofM Expr := c'.caching do
( getContext) (toExpr x) (toExpr c₁.p) (toExpr c₂.p) (toExpr c'.p)
reflBoolTrue
( c₁.toExprProof) ( c₂.toExprProof)
| .ofLeDiseq c₁ c₂ =>
return mkApp7 (mkConst ``Int.Linear.le_of_le_diseq)
( getContext) (toExpr c₁.p) (toExpr c₂.p) (toExpr c'.p)
reflBoolTrue ( c₁.toExprProof) ( c₂.toExprProof)
| .ofDiseqSplit c₁ fvarId h _ =>
let p₂ := c₁.p.addConst 1
let hFalse h.toExprProofCore
let hNot := mkLambda `h .default (mkIntLE ( p₂.denoteExpr') (mkIntLit 0)) (hFalse.abstract #[mkFVar fvarId])
return mkApp7 (mkConst ``Int.Linear.diseq_split_resolve)
( getContext) (toExpr c₁.p) (toExpr p₂) (toExpr c'.p) reflBoolTrue ( c₁.toExprProof) hNot
partial def DiseqCnstr.toExprProof (c' : DiseqCnstr) : ProofM Expr := c'.caching do
match c'.h with
| .expr h =>
return h
| .core p₁ p₂ h =>
return mkApp6 (mkConst ``Int.Linear.diseq_of_core) ( getContext) (toExpr p₁) (toExpr p₂) (toExpr c'.p) reflBoolTrue h
| .norm c =>
return mkApp5 (mkConst ``Int.Linear.diseq_norm) ( getContext) (toExpr c.p) (toExpr c'.p) reflBoolTrue ( c.toExprProof)
| .divCoeffs c =>
let k := c.p.gcdCoeffs c.p.getConst
return mkApp6 (mkConst ``Int.Linear.diseq_coeff) ( getContext) (toExpr c.p) (toExpr c'.p) (toExpr k) reflBoolTrue ( c.toExprProof)
| .neg c =>
return mkApp5 (mkConst ``Int.Linear.diseq_neg) ( getContext) (toExpr c.p) (toExpr c'.p) reflBoolTrue ( c.toExprProof)
| .subst x c₁ c₂ =>
return mkApp8 (mkConst ``Int.Linear.eq_diseq_subst)
( getContext) (toExpr x) (toExpr c₁.p) (toExpr c₂.p) (toExpr c'.p)
reflBoolTrue ( c₁.toExprProof) ( c₂.toExprProof)
partial def UnsatProof.toExprProofCore (h : UnsatProof) : ProofM Expr := do
match h with
| .le c =>
trace[grind.cutsat.le.unsat] "{← c.pp}"
return mkApp4 (mkConst ``Int.Linear.le_unsat) ( getContext) (toExpr c.p) reflBoolTrue ( c.toExprProof)
| .dvd c =>
trace[grind.cutsat.dvd.unsat] "{← c.pp}"
return mkApp5 (mkConst ``Int.Linear.dvd_unsat) ( getContext) (toExpr c.d) (toExpr c.p) reflBoolTrue ( c.toExprProof)
| .eq c =>
trace[grind.cutsat.eq.unsat] "{← c.pp}"
if c.p.isUnsatEq then
return mkApp4 (mkConst ``Int.Linear.eq_unsat) ( getContext) (toExpr c.p) reflBoolTrue ( c.toExprProof)
else
let k := c.p.gcdCoeffs'
return mkApp5 (mkConst ``Int.Linear.eq_unsat_coeff) ( getContext) (toExpr c.p) (toExpr (Int.ofNat k)) reflBoolTrue ( c.toExprProof)
| .diseq c =>
trace[grind.cutsat.diseq.unsat] "{← c.pp}"
return mkApp4 (mkConst ``Int.Linear.diseq_unsat) ( getContext) (toExpr c.p) reflBoolTrue ( c.toExprProof)
end
def setInconsistent (h : UnsatProof) : GoalM Unit := do
let hf withProofContext do
match h with
| .le c =>
trace[grind.cutsat.le.unsat] "{← c.pp}"
return mkApp4 (mkConst ``Int.Linear.le_unsat) ( getContext) (toExpr c.p) reflBoolTrue ( c.toExprProof)
| .dvd c =>
trace[grind.cutsat.dvd.unsat] "{← c.pp}"
return mkApp5 (mkConst ``Int.Linear.dvd_unsat) ( getContext) (toExpr c.d) (toExpr c.p) reflBoolTrue ( c.toExprProof)
| .eq c =>
trace[grind.cutsat.eq.unsat] "{← c.pp}"
if c.p.isUnsatEq then
return mkApp4 (mkConst ``Int.Linear.eq_unsat) ( getContext) (toExpr c.p) reflBoolTrue ( c.toExprProof)
else
let k := c.p.gcdCoeffs'
return mkApp5 (mkConst ``Int.Linear.eq_unsat_coeff) ( getContext) (toExpr c.p) (toExpr (Int.ofNat k)) reflBoolTrue ( c.toExprProof)
| .diseq c =>
trace[grind.cutsat.diseq.unsat] "{← c.pp}"
return mkApp4 (mkConst ``Int.Linear.diseq_unsat) ( getContext) (toExpr c.p) reflBoolTrue ( c.toExprProof)
def UnsatProof.toExprProof (h : UnsatProof) : GoalM Expr := do
withProofContext do h.toExprProofCore
closeGoal hf
def setInconsistent (h : UnsatProof) : GoalM Unit := do
if ( get').caseSplits then
-- Let the search procedure in `SearchM` resolve the conflict.
modify' fun s => { s with conflict? := some h }
else
let h h.toExprProof
closeGoal h
/-!
A cutsat proof may depend on decision variables.
We collect them and perform non chronological backtracking.
-/
structure CollectDecVars.State where
visited : Std.HashSet Nat := {}
found : FVarIdSet := {}
abbrev CollectDecVarsM := ReaderT FVarIdSet (StateM CollectDecVars.State)
private def alreadyVisited (id : Nat) : CollectDecVarsM Bool := do
if ( get).visited.contains id then return true
modify fun s => { s with visited := s.visited.insert id }
return false
private def markAsFound (fvarId : FVarId) : CollectDecVarsM Unit := do
modify fun s => { s with found := s.found.insert fvarId }
private def collectExpr (e : Expr) : CollectDecVarsM Unit := do
let .fvar fvarId := e | return ()
if ( read).contains fvarId then
markAsFound fvarId
mutual
partial def EqCnstr.collectDecVars (c' : EqCnstr) : CollectDecVarsM Unit := do unless ( alreadyVisited c'.id) do
match c'.h with
| .expr h => collectExpr h
| .core .. => return () -- Equalities coming from the core never contain cutsat decision variables
| .norm c | .divCoeffs c => c.collectDecVars
| .subst _ c₁ c₂ | .ofLeGe c₁ c₂ => c₁.collectDecVars; c₂.collectDecVars
partial def DvdCnstr.collectDecVars (c' : DvdCnstr) : CollectDecVarsM Unit := do unless ( alreadyVisited c'.id) do
match c'.h with
| .expr h => collectExpr h
| .norm c | .elim c | .divCoeffs c | .ofEq _ c => c.collectDecVars
| .solveCombine c₁ c₂ | .solveElim c₁ c₂ | .subst _ c₁ c₂ => c₁.collectDecVars; c₂.collectDecVars
partial def LeCnstr.collectDecVars (c' : LeCnstr) : CollectDecVarsM Unit := do unless ( alreadyVisited c'.id) do
match c'.h with
| .expr h => collectExpr h
| .notExpr .. => return () -- This kind of proof is used for connecting with the `grind` core.
| .norm c | .divCoeffs c => c.collectDecVars
| .combine c₁ c₂ | .subst _ c₁ c₂ | .ofLeDiseq c₁ c₂ => c₁.collectDecVars; c₂.collectDecVars
| .ofDiseqSplit _ _ _ decVars =>
-- Recall that we cache the decision variables used in this kind of proof
for fvar in decVars do
markAsFound fvar
partial def DiseqCnstr.collectDecVars (c' : DiseqCnstr) : CollectDecVarsM Unit := do unless ( alreadyVisited c'.id) do
match c'.h with
| .expr h => collectExpr h
| .core .. => return () -- Disequalities coming from the core never contain cutsat decision variables
| .norm c | .divCoeffs c | .neg c => c.collectDecVars
| .subst _ c₁ c₂ => c₁.collectDecVars; c₂.collectDecVars
end
def UnsatProof.collectDecVars (h : UnsatProof) : CollectDecVarsM Unit := do
match h with
| .le c | .dvd c | .eq c | .diseq c => c.collectDecVars
abbrev CollectDecVarsM.run (x : CollectDecVarsM Unit) (decVars : FVarIdSet) : FVarIdSet :=
let (_, s) := x decVars |>.run {}
s.found
end Lean.Meta.Grind.Arith.Cutsat

View File

@@ -8,16 +8,62 @@ import Lean.Meta.Tactic.Grind.Arith.Cutsat.Var
import Lean.Meta.Tactic.Grind.Arith.Cutsat.Util
import Lean.Meta.Tactic.Grind.Arith.Cutsat.DvdCnstr
import Lean.Meta.Tactic.Grind.Arith.Cutsat.LeCnstr
import Lean.Meta.Tactic.Grind.Arith.Cutsat.EqCnstr
import Lean.Meta.Tactic.Grind.Arith.Cutsat.SearchM
import Lean.Meta.Tactic.Grind.Arith.Cutsat.Model
namespace Lean.Meta.Grind.Arith.Cutsat
def getBestLower? (x : Var) : GoalM (Option (Int × LeCnstr)) := do
private def checkIsNextVar (x : Var) : GoalM Unit := do
if x != ( get').assignment.size then
throwError "`grind` internal error, assigning variable out of order"
private def traceAssignment (x : Var) (v : Rat) : GoalM Unit := do
trace[grind.cutsat.assign] "{quoteIfNotAtom (← getVar x)} := {v}"
private def setAssignment (x : Var) (v : Rat) : GoalM Unit := do
checkIsNextVar x
traceAssignment x v
modify' fun s => { s with assignment := s.assignment.push v }
private def skipAssignment (x : Var) : GoalM Unit := do
checkIsNextVar x
modify' fun s => { s with assignment := s.assignment.push 0 }
/-- Assign eliminated variables using `elimEqs` field. -/
private def assignElimVars : GoalM Unit := do
if ( inconsistent) then return ()
go ( get').elimStack
where
go (xs : List Var) : GoalM Unit := do
match xs with
| [] => return ()
| x :: xs =>
let some c := ( get').elimEqs[x]!
| throwError "`grind` internal error, eliminated variable must have equation associated with it"
-- `x` may not be the max variable
let a := c.p.coeff x
if a == 0 then c.throwUnexpected
-- ensure `x` is 0 when evaluating `c.p`
modify' fun s => { s with assignment := s.assignment.set x 0 }
let some v c.p.eval? | c.throwUnexpected
let v := (-v) / a
traceAssignment x v
modify' fun s => { s with assignment := s.assignment.set x v }
go xs
/--
Assuming all variables smaller than `x` have already been assigned,
returns the best lower bound for `x` using the given partial assignment and
inequality constraints where `x` is the maximal variable.
-/
def getBestLower? (x : Var) : GoalM (Option (Rat × LeCnstr)) := do
let s get'
let mut best? := none
for c in s.lowers[x]! do
let .add k _ p := c.p | c.throwUnexpected
let some v p.eval? | c.throwUnexpected
let lower' := Int.Linear.cdiv v (-k)
let lower' := v / (-k)
if let some (lower, _) := best? then
if lower' > lower then
best? := some (lower', c)
@@ -25,7 +71,12 @@ def getBestLower? (x : Var) : GoalM (Option (Int × LeCnstr)) := do
best? := some (lower', c)
return best?
def getBestUpper? (x : Var) : GoalM (Option (Int × LeCnstr)) := do
/--
Assuming all variables smaller than `x` have already been assigned,
returns the best upper bound for `x` using the given partial assignment and
inequality constraints where `x` is the maximal variable.
-/
def getBestUpper? (x : Var) : GoalM (Option (Rat × LeCnstr)) := do
let s get'
let mut best? := none
for c in s.uppers[x]! do
@@ -39,10 +90,40 @@ def getBestUpper? (x : Var) : GoalM (Option (Int × LeCnstr)) := do
best? := some (upper', c)
return best?
def DvdCnstr.getSolutions? (c : DvdCnstr) : GoalM (Option (Int × Int)) := do
/-- Returns values we cannot assign `x` because of disequality constraints. -/
def getDiseqValues (x : Var) : SearchM (Array (Rat × DiseqCnstr)) := do
let s get'
let mut r := #[]
for c in s.diseqs[x]! do
let .add k _ p := c.p | c.throwUnexpected
let some v p.eval? | c.throwUnexpected
if ( isApprox) then
r := r.push (((-v)/k), c)
else
-- We are building an integer model,
-- if `k` does not divide `v`, we can just ignore the disequality.
let v := v.num
if v % k == 0 then
r := r.push (v / k, c)
return r
/--
Solution space for a divisibility constraint of the form `d a*x + b`
See `DvdCnstr.getSolutions?` to understand how it is computed.
-/
structure DvdSolution where
d : Int := 1
b : Int := 0
def DvdCnstr.getSolutions? (c : DvdCnstr) : SearchM (Option DvdSolution) := do
let d := c.d
let .add a _ p := c.p | c.throwUnexpected
let some b p.eval? | c.throwUnexpected
if b.den != 1 then
-- `b` is a rational number, mark model as imprecise, and ignore the constraint
setImprecise
return none
let b := b.num
-- We must solve `d a*x + b`
let g := d.gcd a
if b % g != 0 then
@@ -58,30 +139,7 @@ def DvdCnstr.getSolutions? (c : DvdCnstr) : GoalM (Option (Int × Int)) := do
-- `a*x = -b (mod d)`
-- `x = -b*a' (mod d)`
-- `x = k*d + -b*a'` for any k
return some (d, -b*a')
private partial def setAssignment (x : Var) (v : Int) : GoalM Unit := do
if x == ( get').assignment.size then
trace[grind.cutsat.assign] "{quoteIfNotAtom (← getVar x)} := {v}"
modify' fun s => { s with assignment := s.assignment.push v }
else if x > ( get').assignment.size then
modify' fun s => { s with assignment := s.assignment.push 0 }
setAssignment x v
else
throwError "`grind` internal error, variable is already assigned"
def resolveLowerUpperConflict (c₁ c₂ : LeCnstr) : GoalM Unit := do
trace[grind.cutsat.conflict] "{← c₁.pp}, {← c₂.pp}"
let .add a₁ _ p₁ := c₁.p | c₁.throwUnexpected
let .add a₂ _ p₂ := c₂.p | c₂.throwUnexpected
let p := p₁.mul a₂.natAbs |>.combine (p₂.mul a₁.natAbs)
if ( p.satisfiedLe) == .false then
-- If current assignment does not satisfy the real shadow, we use it even if it is not precise when
-- `a₁.natAbs != 1 && a₂.natAbs != 1`
( mkLeCnstr p (.combine c₁ c₂)).assert
else
assert! a₁.natAbs != 1 && a₂.natAbs != 1
throwError "NIY"
return some { d, b := -b*a' }
def resolveDvdConflict (c : DvdCnstr) : GoalM Unit := do
trace[grind.cutsat.conflict] "{← c.pp}"
@@ -89,72 +147,267 @@ def resolveDvdConflict (c : DvdCnstr) : GoalM Unit := do
let .add a _ p := c.p | c.throwUnexpected
( mkDvdCnstr (a.gcd d) p (.elim c)).assert
def decideVar (x : Var) : GoalM Unit := do
/--
Given a divisibility constraint solution space `s := { b, d }`,
and a candidate assignment `v`, we want to find
an assignment `w` such that `w ≥ v` such that exists `k`, `w = k*d + b`
Thus,
- `k*d + b ≥ v`
- `k ≥ cdiv (v - b) d`
So, we take `w = (cdiv (v - b) d)*d + b`
-/
def DvdSolution.ge (s : DvdSolution) (v : Int) : Int :=
(Int.Linear.cdiv (v - s.b) s.d)*s.d + s.b
/--
Given a divisibility constraint solution space `s := { b, d }`,
and a candidate assignment `v`, we want to find
an assignment `w` such that `w ≤ v` such that exists `k`, `w = k*d + b`
Thus,
- `k*d + b ≤ v`
- `k ≤ (v - b) / d`
So, we take `w = ((v - b) / d)*d + b`
-/
def DvdSolution.le (s : DvdSolution) (v : Int) : Int :=
((v - s.b)/s.d)*s.d + s.b
def findDiseq? (v : Int) (dvals : Array (Rat × DiseqCnstr)) : Option DiseqCnstr :=
(·.2) <$> dvals.find? fun (d, _) =>
d.den == 1 && d.num == v
def inDiseqValues (v : Int) (dvals : Array (Rat × DiseqCnstr)) : Bool :=
Option.isSome <| findDiseq? v dvals
def findRatDiseq? (v : Rat) (dvals : Array (Rat × DiseqCnstr)) : Option DiseqCnstr :=
(·.2) <$> dvals.find? fun (d, _) => v == d
partial def DvdSolution.geAvoiding (s : DvdSolution) (v : Int) (dvals : Array (Rat × DiseqCnstr)) : Int :=
let v := s.ge v
if inDiseqValues v dvals then
geAvoiding s (v+1) dvals
else
v
partial def DvdSolution.leAvoiding (s : DvdSolution) (v : Int) (dvals : Array (Rat × DiseqCnstr)) : Int :=
let v := s.le v
if inDiseqValues v dvals then
geAvoiding s (v-1) dvals
else
v
inductive FindIntValResult where
| found (val : Int)
| diseq (c : DiseqCnstr)
| dvd
deriving Inhabited
/--
Tries to find an integer `v` s.t. `lower ≤ v ≤ upper`, `v ∉ dvals`, and `v ∈ s`.
Returns `.found v` if result was found, `.dvd` if it failed because of the divisibility constraint,
and `.diseq c` because of the disequality constraint `c`.
-/
partial def findIntVal (s : DvdSolution) (lower : Int) (upper : Int) (dvals : Array (Rat × DiseqCnstr)) : FindIntValResult :=
let v := s.ge lower
if v > upper then
.dvd
else
go v
where
go (v : Int) : FindIntValResult :=
if let some c := findDiseq? v dvals then
let v := s.ge (v+1)
if v > upper then .diseq c else go v
else
.found v
partial def findRatVal (lower upper : Rat) (diseqVals : Array (Rat × DiseqCnstr)) : Rat :=
let v := (lower + upper)/2
if (findRatDiseq? v diseqVals).isSome then
findRatVal lower v diseqVals
else
v
def resolveRealLowerUpperConflict (c₁ c₂ : LeCnstr) : GoalM Bool := do
trace[grind.cutsat.conflict] "{← c₁.pp}, {← c₂.pp}"
let .add a₁ _ p₁ := c₁.p | c₁.throwUnexpected
let .add a₂ _ p₂ := c₂.p | c₂.throwUnexpected
let p := p₁.mul a₂.natAbs |>.combine (p₂.mul a₁.natAbs)
if ( p.satisfiedLe) != .false then
return false
else
let c mkLeCnstr p (.combine c₁ c₂)
c.assert
return true
def resolveCooperLeft (c₁ c₂ : LeCnstr) : GoalM Unit := do
throwError "Cooper-left NIY {← c₁.pp} {← c₂.pp}"
def resolveCooperRight (c₁ c₂ : LeCnstr) : GoalM Unit := do
throwError "Cooper-right NIY {← c₁.pp} {← c₂.pp}"
def resolveCooper (c₁ c₂ : LeCnstr) : GoalM Unit := do
if c₁.p.leadCoeff.natAbs < c₂.p.leadCoeff.natAbs then
resolveCooperLeft c₁ c₂
else
resolveCooperRight c₁ c₂
def resolveCooperDvdLeft (c₁ c₂ : LeCnstr) (c : DvdCnstr) : GoalM Unit := do
throwError "Cooper-dvd-left NIY {← c₁.pp} {← c₂.pp} {← c.pp}"
def resolveCooperDvdRight (c₁ c₂ : LeCnstr) (c : DvdCnstr) : GoalM Unit := do
throwError "Cooper-dvd-right NIY {← c₁.pp} {← c₂.pp} {← c.pp}"
def resolveCooperDvd (c₁ c₂ : LeCnstr) (c : DvdCnstr) : GoalM Unit := do
if c₁.p.leadCoeff.natAbs < c₂.p.leadCoeff.natAbs then
resolveCooperDvdLeft c₁ c₂ c
else
resolveCooperDvdRight c₁ c₂ c
def resolveCooperDiseq (c₁ : DiseqCnstr) (c₂ : LeCnstr) (_c? : Option DvdCnstr) : GoalM Unit := do
throwError "Cooper-diseq NIY {← c₁.pp} {← c₂.pp}"
/--
Given `c₁` of the form `-a₁*x + p₁ ≤ 0`, and `c` of the form `b*x + p ≠ 0`,
splits `c` and resolve with `c₁`.
Recall that a disequality
-/
def resolveRatDiseq (c₁ : LeCnstr) (c : DiseqCnstr) : SearchM Unit := do
let c if c.p.leadCoeff < 0 then
mkDiseqCnstr (c.p.mul (-1)) (.neg c)
else
pure c
let fvarId if let some fvarId := ( get').diseqSplits.find? c.p then
trace[grind.debug.cutsat.diseq.split] "{← c.pp}, reusing {fvarId.name}"
pure fvarId
else
let fvarId mkCase (.diseq c)
trace[grind.debug.cutsat.diseq.split] "{← c.pp}, {fvarId.name}"
modify' fun s => { s with diseqSplits := s.diseqSplits.insert c.p fvarId }
pure fvarId
let p₂ := c.p.addConst 1
let c₂ mkLeCnstr p₂ (.expr (mkFVar fvarId))
let b resolveRealLowerUpperConflict c₁ c₂
assert! b
def processVar (x : Var) : SearchM Unit := do
if ( eliminated x) then
/-
Variable has been eliminated, and will be assigned later after we have assigned
variables that have not been eliminated.
-/
skipAssignment x
return ()
-- Solution space for divisibility constraint is `x = k*d + b`
let dvdSol if let some c := ( get').dvds[x]! then
if let some solutions c.getSolutions? then
pure solutions
else
resolveDvdConflict c
return ()
else
pure {}
let lower? getBestLower? x
let upper? getBestUpper? x
let dvd? := ( get').dvds[x]!
match lower?, upper?, dvd? with
| none, none, none =>
setAssignment x 0
| some (lower, _), none, none =>
setAssignment x lower
| none, some (upper, _), none =>
setAssignment x upper
| some (lower, c₁), some (upper, c₂), none =>
if lower upper then
setAssignment x lower
else
trace[grind.cutsat.conflict] "{lower} ≤ {← getVar x} ≤ {upper}"
resolveLowerUpperConflict c₁ c₂
| none, none, some c =>
if let some (_, v) c.getSolutions? then
let diseqVals getDiseqValues x
match lower?, upper? with
| none, none =>
let v := dvdSol.geAvoiding 0 diseqVals
setAssignment x v
| some (lower, _), none =>
let lower := lower.ceil
let v := dvdSol.geAvoiding lower diseqVals
setAssignment x v
| none, some (upper, _) =>
let upper := upper.floor
let v := dvdSol.leAvoiding upper diseqVals
setAssignment x v
| some (lower, c₁), some (upper, c₂) =>
if lower > upper then
let .true resolveRealLowerUpperConflict c₁ c₂
| throwError "`grind` internal error, conflict resolution failed"
return ()
-- `lower ≤ upper` here
if lower.ceil > upper.floor then
if ( resolveRealLowerUpperConflict c₁ c₂) then
-- Resolved conflict using "real" shadow
return ()
if !( isApprox) then
resolveCooper c₁ c₂
return ()
let r := findIntVal dvdSol lower.ceil upper.floor diseqVals
if let .found v := r then
setAssignment x v
return ()
if ( isApprox) then
if lower < upper then
setAssignment x <| findRatVal lower upper diseqVals
else if let some c := findRatDiseq? lower diseqVals then
resolveRatDiseq c₁ c
else
setAssignment x lower
else
resolveDvdConflict c
| some (lower, _), none, some c =>
if let some (d, b) c.getSolutions? then
/-
- `x ≥ lower ∧ x = k*d + b`
- `k*d + b ≥ lower`
- `k ≥ cdiv (lower - b) d`
- So, we take `x = (cdiv (lower - b) d)*d + b`
-/
setAssignment x ((Int.Linear.cdiv (lower - b) d)*d + b)
else
resolveDvdConflict c
| none, some (upper, _), some c =>
if let some (d, b) c.getSolutions? then
/-
- `x ≤ upper ∧ x = k*d + b`
- `k*d + b ≤ upper`
- `k ≤ (upper - b)/d`
- So, we take `x = ((upper - b)/d)*d + b`
-/
setAssignment x (((upper - b)/d)*d + b)
else
resolveDvdConflict c
| _, _, _ =>
-- TODO: cases containing a divisibility constraint.
-- TODO: remove the following
setAssignment x 0
match r with
| .dvd => resolveCooperDvd c₁ c₂ ( get').dvds[x]!.get!
| .diseq c => resolveCooperDiseq c c₂ ( get').dvds[x]!
| _ => unreachable!
/-- Returns `true` if we already have a complete assignment / model. -/
def hasAssignment : GoalM Bool := do
return ( get').vars.size == ( get').assignment.size
private def isDone : GoalM Bool := do
if ( hasAssignment) then
private def findCase (decVars : FVarIdSet) : SearchM Case := do
repeat
let numCases := ( get).cases.size
assert! numCases > 0
let case := ( get).cases[numCases-1]!
modify fun s => { s with cases := s.cases.pop }
if decVars.contains case.fvarId then
return case
-- Conflict does not depend on this case.
trace[grind.debug.cutsat.backtrack] "skipping {case.fvarId.name}"
unreachable!
def resolveConflict (h : UnsatProof) : SearchM Bool := do
let decVars := h.collectDecVars.run ( get).decVars
if decVars.isEmpty then
closeGoal ( h.toExprProof)
return false
let c findCase decVars
modify' fun _ => c.saved
match c.kind with
| .diseq c₁ =>
let decVars := decVars.erase c.fvarId |>.toArray
let p' := c₁.p.mul (-1) |>.addConst 1
let c' mkLeCnstr p' (.ofDiseqSplit c₁ c.fvarId h decVars)
trace[grind.debug.cutsat.backtrack] "resolved diseq split: {← c'.pp}"
c'.assert
return true
if ( inconsistent) then
return true
return false
| _ => throwError "NIY resolve conflict"
/-- Search for an assignment/model for the linear constraints. -/
def searchAssigment : GoalM Unit := do
def searchAssigmentMain : SearchM Unit := do
repeat
if ( isDone) then
if ( hasAssignment) then
return ()
if ( isInconsistent) then
-- `grind` state is inconsistent
return ()
if let some c := ( get').conflict? then
unless ( resolveConflict c) do
return ()
let x : Var := ( get').assignment.size
decideVar x
processVar x
def traceModel : GoalM Unit := do
if ( isTracingEnabledFor `grind.cutsat.model) then
for (x, v) in ( mkModel ( get)) do
trace[grind.cutsat.model] "{quoteIfNotAtom x} := {v}"
def searchAssigment : GoalM Unit := do
-- TODO: .int case
-- TODO:
searchAssigmentMain .rat |>.run' {}
assignElimVars
traceModel
end Lean.Meta.Grind.Arith.Cutsat

View File

@@ -0,0 +1,83 @@
/-
Copyright (c) 2025 Amazon.com, Inc. or its affiliates. All Rights Reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Leonardo de Moura
-/
prelude
import Lean.Meta.Tactic.Grind.Arith.Cutsat.Util
namespace Lean.Meta.Grind.Arith.Cutsat
/--
In principle, we only need to support two kinds of case split.
- Disequalities.
- Cooper-Left, but we have 4 different variants of this one.
-/
inductive CaseKind where
| diseq (d : DiseqCnstr)
| copperLeft
| copperDvdLeft
| cooperRight
| cooperDvdRight
deriving Inhabited
structure Case where
kind : CaseKind
/--
Decision variable used to represent the case-split.
For example, suppose we are splitting on `p ≠ 0`. Then,
we create a decision variable `h : p + 1 ≤ 0`
-/
fvarId : FVarId
/--
Snapshot of the cutsat state for backtracking purposes.
We do not use a trail stack.
-/
saved : State
deriving Inhabited
inductive Search.Kind where
| /--
Allow variables to be assigned to rational numbers during model
construction.
-/
rat
| /--
Variables must be assigned to integer numbers.
Cooper case splits are required in this mode.
-/
int
deriving Inhabited, BEq
/--
State of the model search procedure.
-/
structure Search.State where
/-- Decision stack (aka case-split stack) -/
cases : PArray Case := {}
/-- `precise := false` if not all constraints were satisfied during the search. -/
precise : Bool := true
/-- Set of decision variables in `cases`. -/
decVars : FVarIdSet := {}
abbrev SearchM := ReaderT Search.Kind (StateRefT Search.State GoalM)
/-- Returns `true` if approximations are allowed. -/
def isApprox : SearchM Bool :=
return ( read) == .rat
/-- Sets `precise` to `false` to indicate that some constraint was not satisfied. -/
def setImprecise : SearchM Unit := do
modify fun s => { s with precise := false }
def mkCase (kind : CaseKind) : SearchM FVarId := do
let fvarId mkFreshFVarId
let saved get'
modify fun s => { s with
cases := s.cases.push { saved, fvarId, kind }
decVars := s.decVars.insert fvarId
}
modify' fun s => { s with caseSplits := true }
return fvarId
end Lean.Meta.Grind.Arith.Cutsat

View File

@@ -5,6 +5,7 @@ Authors: Leonardo de Moura
-/
prelude
import Init.Data.Int.Linear
import Std.Internal.Rat
import Lean.Data.PersistentArray
import Lean.Meta.Tactic.Grind.ENodeKey
import Lean.Meta.Tactic.Grind.Arith.Util
@@ -12,6 +13,57 @@ import Lean.Meta.Tactic.Grind.Arith.Util
namespace Lean.Meta.Grind.Arith.Cutsat
export Int.Linear (Var Poly)
export Std.Internal (Rat)
deriving instance Hashable for Poly
/-!
This module implements a model-based decision procedure for linear integer arithmetic,
inspired by Section 4 of "Cutting to the Chase: Solving Linear Integer Arithmetic".
Our implementation includes several enhancements and modifications:
Key Features:
- Extended constraint support (equality and disequality)
- Optimized encoding of `Cooper-Left` rule using "big"-disjunction instead of fresh variables
- Decision variable tracking for case splits (disequalities, `Cooper-Left`, `Cooper-Right`)
Constraint Types:
We handle four categories of linear polynomial constraints (where p is a linear polynomial):
1. Equality: `p = 0`
2. Divisibility: `d p`
3. Inequality: `p ≤ 0`
4. Disequality: `p ≠ 0`
Implementation Details:
- Polynomials use `Int.Linear.Poly` with sorted linear monomials (leading monomial contains max variable)
- Equalities are eliminated eagerly
- Divisibility constraints are maintained in solved form (one constraint per variable) using `Div-Solve`
Model Construction:
The procedure builds a model incrementally, resolving conflicts through constraint generation.
For example:
Given a partial model `{x := 1}` and constraint `3 3*y + x + 1`:
- Cannot extend to `y` because `3 3*y + 2` is unsatisfiable
- Generate implied constraint `3 x + 1`
- Force model update for `x`
Variable Assignment:
When assigning a variable `y`, we consider:
- Best upper and lower bounds (inequalities)
- Divisibility constraint
- Disequality constraints
`Cooper-Left` and `Cooper-Right` rules handle the combination of inequalities and divisibility.
For unsatisfiable disequalities p ≠ 0, we generate case split: `p + 1 ≤ 0 -p + 1 ≤ 0`
Contradiction Handling:
- Check dependency on decision variables
- If independent, use contradiction to close current grind goal
- Otherwise, trigger backtracking
Optimization:
We employ rational approximation for model construction:
- Continue with rational solutions when integer solutions aren't immediately found
- Helps identify simpler unsatisfiability proofs before full integer model construction
-/
/-
Remark: we will not define a parent structure `Cnstr` with the common
@@ -31,24 +83,8 @@ inductive EqCnstrProof where
| norm (c : EqCnstr)
| divCoeffs (c : EqCnstr)
| subst (x : Var) (c₁ : EqCnstr) (c₂ : EqCnstr)
end
| ofLeGe (c₁ : LeCnstr) (c₂ : LeCnstr)
mutual
/-- A disequality constraint and its justification/proof. -/
structure DiseqCnstr where
p : Poly
h : DiseqCnstrProof
id : Nat
inductive DiseqCnstrProof where
| expr (h : Expr)
| core (p₁ p₂ : Poly) (h : Expr)
| norm (c : DiseqCnstr)
| divCoeffs (c : DiseqCnstr)
| subst (x : Var) (c₁ : EqCnstr) (c₂ : DiseqCnstr)
end
mutual
/-- A divisibility constraint and its justification/proof. -/
structure DvdCnstr where
d : Int
@@ -80,8 +116,23 @@ inductive LeCnstrProof where
| divCoeffs (c : LeCnstr)
| combine (c₁ c₂ : LeCnstr)
| subst (x : Var) (c₁ : EqCnstr) (c₂ : LeCnstr)
| ofLeDiseq (c₁ : LeCnstr) (c₂ : DiseqCnstr)
| ofDiseqSplit (c₁ : DiseqCnstr) (decVar : FVarId) (h : UnsatProof) (decVars : Array FVarId)
-- TODO: missing constructors
end
/-- A disequality constraint and its justification/proof. -/
structure DiseqCnstr where
p : Poly
h : DiseqCnstrProof
id : Nat
inductive DiseqCnstrProof where
| expr (h : Expr)
| core (p₁ p₂ : Poly) (h : Expr)
| norm (c : DiseqCnstr)
| divCoeffs (c : DiseqCnstr)
| neg (c : DiseqCnstr)
| subst (x : Var) (c₁ : EqCnstr) (c₂ : DiseqCnstr)
/--
A proof of `False`.
@@ -93,6 +144,11 @@ inductive UnsatProof where
| eq (c : EqCnstr)
| diseq (c : DiseqCnstr)
end
instance : Inhabited DvdCnstr where
default := { d := 0, p := .num 0, h := .expr default, id := 0 }
abbrev VarSet := RBTree Var compare
/-- State of the cutsat procedure. -/
@@ -142,14 +198,29 @@ structure State where
-/
occurs : PArray VarSet := {}
/-- Partial assignment being constructed by cutsat. -/
assignment : PArray Int := {}
assignment : PArray Rat := {}
/-- Next unique id for a constraint. -/
nextCnstrId : Nat := 0
/--
`caseSplits` is `true` if cutsat is searching for model and already performed case splits.
This information is used to decide whether a conflict should immediately close the
current `grind` goal or not.
-/
caseSplits : Bool := false
/--
`conflict?` is `some ..` if a contradictory constraint was derived.
This field is only set when `caseSplits` is `true`. Otherwise, we
can convert `UnsatProof` into a Lean term and close the current `grind` goal.
-/
conflict? : Option UnsatProof := none
/--
Cache decision variables used when splitting on disequalities.
This is necessary because the same disequality may be in different conflicts.
-/
diseqSplits : PHashMap Poly FVarId := {}
/-
TODO: support for storing
- Disjuctions: they come from conflict resolution, and disequalities.
- Disequalities.
- Linear integer terms appearing in the main module, and model-based equality propagation.
TODO: Model-based theory combination.
-/
deriving Inhabited

View File

@@ -46,9 +46,8 @@ def get' : GoalM State := do
/-- Returns `true` if the cutsat state is inconsistent. -/
def inconsistent : GoalM Bool := do
-- TODO: we will have a nested backtracking search in cutsat
-- and this function will have to be refined.
isInconsistent
if ( isInconsistent) then return true
return ( get').conflict?.isSome
def getVars : GoalM (PArray Expr) :=
return ( get').vars
@@ -65,11 +64,22 @@ def mkCnstrId : GoalM Nat := do
modify' fun s => { s with nextCnstrId := id + 1 }
return id
private partial def shrink (a : PArray Int) (sz : Nat) : PArray Int :=
if a.size > sz then
shrink a.pop sz
else
a
def mkEqCnstr (p : Poly) (h : EqCnstrProof) : GoalM EqCnstr := do
return { p, h, id := ( mkCnstrId) }
@[extern "lean_grind_cutsat_assert_eq"] -- forward definition
opaque EqCnstr.assert (c : EqCnstr) : GoalM Unit
-- TODO: PArray.shrink and PArray.resize
partial def shrink (a : PArray Rat) (sz : Nat) : PArray Rat :=
if a.size > sz then shrink a.pop sz else a
partial def resize (a : PArray Rat) (sz : Nat) : PArray Rat :=
if a.size > sz then shrink a sz else go a
where
go (a : PArray Rat) : PArray Rat :=
if a.size < sz then go (a.push 0) else a
/-- Resets the assingment of any variable bigger or equal to `x`. -/
def resetAssignmentFrom (x : Var) : GoalM Unit := do
@@ -210,9 +220,9 @@ abbrev withProofContext (x : ProofM Expr) : GoalM Expr := do
Tries to evaluate the polynomial `p` using the partial model/assignment built so far.
The result is `none` if the polynomial contains variables that have not been assigned.
-/
def _root_.Int.Linear.Poly.eval? (p : Poly) : GoalM (Option Int) := do
def _root_.Int.Linear.Poly.eval? (p : Poly) : GoalM (Option Rat) := do
let a := ( get').assignment
let rec go (v : Int) : Poly Option Int
let rec go (v : Rat) : Poly Option Rat
| .num k => some (v + k)
| .add k x p =>
if _ : x < a.size then
@@ -233,7 +243,8 @@ Returns `.true` if `c` is satisfied by the current partial model,
-/
def DvdCnstr.satisfied (c : DvdCnstr) : GoalM LBool := do
let some v c.p.eval? | return .undef
return decide (c.d v) |>.toLBool
if v.den != 1 then return .false
return decide (c.d v.num) |>.toLBool
def _root_.Int.Linear.Poly.satisfiedLe (p : Poly) : GoalM LBool := do
let some v p.eval? | return .undef

View File

@@ -5,3 +5,4 @@ Authors: Leonardo de Moura
-/
prelude
import Lean.Meta.Tactic.Grind.Arith.Offset.Model
import Lean.Meta.Tactic.Grind.Arith.Cutsat.Model

View File

@@ -127,6 +127,18 @@ private def ppOffset : M Unit := do
ms := ms.push <| .trace { cls := `assign } m!"{quoteIfNotAtom e} := {val}" #[]
pushMsg <| .trace { cls := `offset } "Assignment satisfying offset contraints" ms
private def ppCutsat : M Unit := do
let goal read
let s := goal.arith.cutsat
let nodes := s.varMap
if nodes.isEmpty then return ()
let model Arith.Cutsat.mkModel goal
if model.isEmpty then return ()
let mut ms := #[]
for (e, val) in model do
ms := ms.push <| .trace { cls := `assign } m!"{quoteIfNotAtom e} := {val}" #[]
pushMsg <| .trace { cls := `cutsat } "Assignment satisfying integer contraints" ms
private def ppThresholds (c : Grind.Config) : M Unit := do
let goal read
let maxGen := goal.enodes.foldl (init := 0) fun g _ n => Nat.max g n.generation
@@ -165,6 +177,7 @@ where
ppCasesTrace
ppActiveTheoremPatterns
ppOffset
ppCutsat
ppThresholds config
end Lean.Meta.Grind

View File

@@ -390,7 +390,8 @@ def setupImports (meta : DocumentMeta) (cmdlineOpts : Options) (chanOut : Std.Ch
let opts := cmdlineOpts.mergeBy (fun _ _ fileOpt => fileOpt) fileSetupResult.fileOptions
-- default to async elaboration; see also `Elab.async` docs
let opts := Elab.async.setIfNotSet opts true
-- (temporarily disabled pending #7241)
--let opts := Elab.async.setIfNotSet opts true
return .ok {
mainModuleName

View File

@@ -7,6 +7,7 @@ prelude
import Lean.Expr
import Lean.ToLevel
import Init.Data.BitVec.Basic
import Init.Data.SInt.Basic
universe u
namespace Lean
@@ -97,6 +98,71 @@ instance : ToExpr USize where
mkApp3 (.const ``OfNat.ofNat [0]) (mkConst ``USize) r
(.app (.const ``USize.instOfNat []) r)
instance : ToExpr Int8 where
toTypeExpr := mkConst ``Int8
toExpr i := if 0 i then
mkNat i.toNatClampNeg
else
mkApp3 (.const ``Neg.neg [0]) (.const ``Int8 []) (.const ``Int8.instNeg [])
(mkNat (-(i.toInt)).toNat)
where
mkNat (n : Nat) : Expr :=
let r := mkRawNatLit n
mkApp3 (.const ``OfNat.ofNat [0]) (.const ``Int8 []) r
(.app (.const ``Int8.instOfNat []) r)
instance : ToExpr Int16 where
toTypeExpr := mkConst ``Int16
toExpr i := if 0 i then
mkNat i.toNatClampNeg
else
mkApp3 (.const ``Neg.neg [0]) (.const ``Int16 []) (.const ``Int16.instNeg [])
(mkNat (-(i.toInt)).toNat)
where
mkNat (n : Nat) : Expr :=
let r := mkRawNatLit n
mkApp3 (.const ``OfNat.ofNat [0]) (.const ``Int16 []) r
(.app (.const ``Int16.instOfNat []) r)
instance : ToExpr Int32 where
toTypeExpr := mkConst ``Int32
toExpr i := if 0 i then
mkNat i.toNatClampNeg
else
mkApp3 (.const ``Neg.neg [0]) (.const ``Int32 []) (.const ``Int32.instNeg [])
(mkNat (-(i.toInt)).toNat)
where
mkNat (n : Nat) : Expr :=
let r := mkRawNatLit n
mkApp3 (.const ``OfNat.ofNat [0]) (.const ``Int32 []) r
(.app (.const ``Int32.instOfNat []) r)
instance : ToExpr Int64 where
toTypeExpr := mkConst ``Int64
toExpr i := if 0 i then
mkNat i.toNatClampNeg
else
mkApp3 (.const ``Neg.neg [0]) (.const ``Int64 []) (.const ``Int64.instNeg [])
(mkNat (-(i.toInt)).toNat)
where
mkNat (n : Nat) : Expr :=
let r := mkRawNatLit n
mkApp3 (.const ``OfNat.ofNat [0]) (.const ``Int64 []) r
(.app (.const ``Int64.instOfNat []) r)
instance : ToExpr ISize where
toTypeExpr := mkConst ``ISize
toExpr i := if 0 i then
mkNat i.toNatClampNeg
else
mkApp3 (.const ``Neg.neg [0]) (.const ``ISize []) (.const ``ISize.instNeg [])
(mkNat (-(i.toInt)).toNat)
where
mkNat (n : Nat) : Expr :=
let r := mkRawNatLit n
mkApp3 (.const ``OfNat.ofNat [0]) (.const ``ISize []) r
(.app (.const ``ISize.instOfNat []) r)
instance : ToExpr Bool where
toExpr := fun b => if b then mkConst ``Bool.true else mkConst ``Bool.false
toTypeExpr := mkConst ``Bool

View File

@@ -253,6 +253,9 @@ section LawfulEq
/--
A typeclass for comparison functions satisfying `cmp a b = .eq` if and only if the logical equality
`a = b` holds.
This typeclass distinguishes itself from `LawfulBEqCmp` by using logical equality (`=`) instead of
boolean equality (`==`).
-/
class LawfulEqCmp {α : Type u} (cmp : α α Ordering) : Prop extends ReflCmp cmp where
/-- If two values compare equal, then they are logically equal. -/
@@ -261,6 +264,9 @@ class LawfulEqCmp {α : Type u} (cmp : αα → Ordering) : Prop extends Re
/--
A typeclass for types with a comparison function that satisfies `compare a b = .eq` if and only if
the logical equality `a = b` holds.
This typeclass distinguishes itself from `LawfulBEqOrd` by using logical equality (`=`) instead of
boolean equality (`==`).
-/
abbrev LawfulEqOrd (α : Type u) [Ord α] := LawfulEqCmp (compare : α α Ordering)
@@ -276,6 +282,48 @@ theorem compare_beq_iff_eq {a b : α} : cmp a b == .eq ↔ a = b :=
end LawfulEq
section LawfulBEq
/--
A typeclass for comparison functions satisfying `cmp a b = .eq` if and only if the boolean equality
`a == b` holds.
This typeclass distinguishes itself from `LawfulEqCmp` by using boolean equality (`==`) instead of
logical equality (`=`).
-/
class LawfulBEqCmp {α : Type u} [BEq α] (cmp : α α Ordering) : Prop where
/-- If two values compare equal, then they are logically equal. -/
compare_eq_iff_beq {a b : α} : cmp a b = .eq a == b
/--
A typeclass for types with a comparison function that satisfies `compare a b = .eq` if and only if
the boolean equality `a == b` holds.
This typeclass distinguishes itself from `LawfulEqOrd` by using boolean equality (`==`) instead of
logical equality (`=`).
-/
abbrev LawfulBEqOrd (α : Type u) [BEq α] [Ord α] := LawfulBEqCmp (compare : α α Ordering)
variable {α : Type u} [BEq α] {cmp : α α Ordering}
instance [LawfulEqCmp cmp] [LawfulBEq α] :
LawfulBEqCmp cmp where
compare_eq_iff_beq := compare_eq_iff_eq.trans beq_iff_eq.symm
theorem LawfulBEqCmp.equivBEq [inst : LawfulBEqCmp cmp] [TransCmp cmp] : EquivBEq α where
refl := inst.compare_eq_iff_beq.mp ReflCmp.compare_self
symm := by
simp only [ inst.compare_eq_iff_beq]
exact OrientedCmp.eq_symm
trans := by
simp only [ inst.compare_eq_iff_beq]
exact TransCmp.eq_trans
instance LawfulBEqOrd.equivBEq [Ord α] [LawfulBEqOrd α] [TransOrd α] : EquivBEq α :=
LawfulBEqCmp.equivBEq (cmp := compare)
end LawfulBEq
namespace Internal
variable {α : Type u}
@@ -292,6 +340,16 @@ def beqOfOrd [Ord α] : BEq α where
theorem beq_eq [Ord α] {a b : α} : (a == b) = (compare a b == .eq) :=
rfl
theorem beq_iff [Ord α] {a b : α} : (a == b) = true compare a b = .eq := by
rw [beq_eq, beq_iff_eq]
theorem eq_beqOfOrd_of_lawfulBEqOrd [Ord α] (inst : BEq α) [instLawful : LawfulBEqOrd α] :
inst = beqOfOrd := by
cases inst; rename_i instBEq
congr; ext a b
rw [Bool.eq_iff_iff, beq_iff_eq, instLawful.compare_eq_iff_beq]
rfl
theorem equivBEq_of_transOrd [Ord α] [TransOrd α] : EquivBEq α where
symm {a b} h := by simp_all [OrientedCmp.eq_comm]
trans h₁ h₂ := by simp_all only [beq_eq, beq_iff_eq]; exact TransCmp.eq_trans h₁ h₂

View File

@@ -170,6 +170,7 @@ def erase [BEq α] (a : α) : AssocList α β → AssocList α β
| cons k v l => bif k == a then l else cons k v (l.erase a)
/-- Internal implementation detail of the hash map -/
@[specialize]
def modify [BEq α] [LawfulBEq α] (a : α) (f : β a β a) :
AssocList α β AssocList α β
| nil => nil
@@ -182,6 +183,7 @@ def modify [BEq α] [LawfulBEq α] (a : α) (f : β a → β a) :
cons k v (modify a f l)
/-- Internal implementation detail of the hash map -/
@[specialize]
def alter [BEq α] [LawfulBEq α] (a : α) (f : Option (β a) Option (β a)) :
AssocList α β AssocList α β
| nil => match f none with
@@ -200,6 +202,7 @@ def alter [BEq α] [LawfulBEq α] (a : α) (f : Option (β a) → Option (β a))
namespace Const
/-- Internal implementation detail of the hash map -/
@[specialize]
def modify [BEq α] {β : Type v} (a : α) (f : β β) :
AssocList α (fun _ => β) AssocList α (fun _ => β)
| nil => nil
@@ -210,6 +213,7 @@ def modify [BEq α] {β : Type v} (a : α) (f : β → β) :
cons k v (modify a f l)
/-- Internal implementation detail of the hash map -/
@[specialize]
def alter [BEq α] {β : Type v} (a : α) (f : Option β Option β) :
AssocList α (fun _ => β) AssocList α (fun _ => β)
| nil => match f none with

View File

@@ -858,7 +858,7 @@ theorem distinct_keys [EquivBEq α] [LawfulHashable α] (h : m.1.WF) :
m.1.keys.Pairwise (fun a b => (a == b) = false) := by
simp_to_model using (Raw.WF.out h).distinct.distinct
theorem map_sigma_fst_toList_eq_keys [EquivBEq α] [LawfulHashable α] :
theorem map_fst_toList_eq_keys [EquivBEq α] [LawfulHashable α] :
m.1.toList.map Sigma.fst = m.1.keys := by
simp_to_model
rw [List.keys_eq_map]
@@ -894,9 +894,9 @@ namespace Const
variable {β : Type v} (m : Raw₀ α (fun _ => β))
theorem map_prod_fst_toList_eq_keys [EquivBEq α] [LawfulHashable α] :
theorem map_fst_toList_eq_keys [EquivBEq α] [LawfulHashable α] :
(Raw.Const.toList m.1).map Prod.fst = m.1.keys := by
simp_to_model using List.map_prod_fst_map_toProd_eq_keys
simp_to_model using List.map_fst_map_toProd_eq_keys
theorem length_toList [EquivBEq α] [LawfulHashable α] (h : m.1.WF) :
(Raw.Const.toList m.1).length = m.1.size := by

View File

@@ -964,9 +964,14 @@ theorem distinct_keys [EquivBEq α] [LawfulHashable α] :
Raw₀.distinct_keys m.1, m.2.size_buckets_pos m.2
@[simp]
theorem map_fst_toList_eq_keys [EquivBEq α] [LawfulHashable α] :
m.1.toList.map Sigma.fst = m.1.keys :=
Raw₀.map_fst_toList_eq_keys m.1, m.2.size_buckets_pos
@[simp, deprecated map_fst_toList_eq_keys (since := "2025-02-28")]
theorem map_sigma_fst_toList_eq_keys [EquivBEq α] [LawfulHashable α] :
m.1.toList.map Sigma.fst = m.1.keys :=
Raw₀.map_sigma_fst_toList_eq_keys m.1, m.2.size_buckets_pos
Raw₀.map_fst_toList_eq_keys m.1, m.2.size_buckets_pos
@[simp]
theorem length_toList [EquivBEq α] [LawfulHashable α] :
@@ -1010,9 +1015,14 @@ namespace Const
variable {β : Type v} {m : DHashMap α (fun _ => β)}
@[simp]
theorem map_fst_toList_eq_keys [EquivBEq α] [LawfulHashable α] :
(toList m).map Prod.fst = m.keys :=
Raw₀.Const.map_fst_toList_eq_keys m.1, m.2.size_buckets_pos
@[simp, deprecated map_fst_toList_eq_keys (since := "2025-02-28")]
theorem map_prod_fst_toList_eq_keys [EquivBEq α] [LawfulHashable α] :
(toList m).map Prod.fst = m.keys :=
Raw₀.Const.map_prod_fst_toList_eq_keys m.1, m.2.size_buckets_pos
Raw₀.Const.map_fst_toList_eq_keys m.1, m.2.size_buckets_pos
@[simp]
theorem length_toList [EquivBEq α] [LawfulHashable α] :
@@ -1081,7 +1091,7 @@ theorem fold_eq_foldl_toList {f : δ → (a : α) → β a → δ} {init : δ} :
@[simp]
theorem forM_eq_forM [Monad m'] [LawfulMonad m'] {f : (a : α) β a m' PUnit} :
DHashMap.forM f m = ForM.forM m (fun a => f a.1 a.2):= rfl
DHashMap.forM f m = ForM.forM m (fun a => f a.1 a.2) := rfl
theorem forM_eq_forM_toList [Monad m'] [LawfulMonad m'] {f : (a : α) × β a m' PUnit} :
ForM.forM m f = ForM.forM m.toList f :=

View File

@@ -1053,9 +1053,14 @@ theorem distinct_keys [EquivBEq α] [LawfulHashable α] (h : m.WF) :
simp_to_raw using Raw₀.distinct_keys m, h.size_buckets_pos h
@[simp]
theorem map_fst_toList_eq_keys [EquivBEq α] [LawfulHashable α] (h : m.WF) :
m.toList.map Sigma.fst = m.keys := by
apply Raw₀.map_fst_toList_eq_keys m, h.size_buckets_pos
@[simp, deprecated map_fst_toList_eq_keys (since := "2025-02-28")]
theorem map_sigma_fst_toList_eq_keys [EquivBEq α] [LawfulHashable α] (h : m.WF) :
m.toList.map Sigma.fst = m.keys := by
apply Raw₀.map_sigma_fst_toList_eq_keys m, h.size_buckets_pos
apply Raw₀.map_fst_toList_eq_keys m, h.size_buckets_pos
@[simp]
theorem length_toList [EquivBEq α] [LawfulHashable α] (h : m.WF) :
@@ -1099,9 +1104,14 @@ namespace Const
variable {β : Type v} {m : Raw α (fun _ => β)}
@[simp]
theorem map_fst_toList_eq_keys [EquivBEq α] [LawfulHashable α] (h : m.WF) :
(Raw.Const.toList m).map Prod.fst = m.keys := by
apply Raw₀.Const.map_fst_toList_eq_keys m, h.size_buckets_pos
@[simp, deprecated map_fst_toList_eq_keys (since := "2025-02-28")]
theorem map_prod_fst_toList_eq_keys [EquivBEq α] [LawfulHashable α] (h : m.WF) :
(Raw.Const.toList m).map Prod.fst = m.keys := by
apply Raw₀.Const.map_prod_fst_toList_eq_keys m, h.size_buckets_pos
apply Raw₀.Const.map_fst_toList_eq_keys m, h.size_buckets_pos
@[simp]
theorem length_toList [EquivBEq α] [LawfulHashable α] (h : m.WF) :

View File

@@ -4,7 +4,7 @@ Released under Apache 2.0 license as described in the file LICENSE.
Authors: Paul Reichert
-/
prelude
import Std.Data.DTreeMap.Raw
import Std.Data.DTreeMap.Raw.Basic
import Std.Data.DTreeMap.Internal.WF.Lemmas
/-!
@@ -25,31 +25,16 @@ private local instance : Coe (Type v) (α → Type v) where coe γ := fun _ =>
namespace Std.DTreeMap
open Internal (Impl)
namespace Raw
/--
Updates the values of the map by applying the given function to all mappings, keeping
only those mappings where the function returns `some` value.
-/
def filterMap (f : (a : α) β a Option (γ a)) (t : Raw α β cmp) : Raw α γ cmp :=
letI : Ord α := cmp; t.inner.filterMap! f
/-- Updates the values of the map by applying the given function to all mappings. -/
@[inline]
def map (f : (a : α) β a γ a) (t : Raw α β cmp) : Raw α γ cmp :=
letI : Ord α := cmp; t.inner.map f
/-!
We do not provide `get*GE`, `get*GT`, `get*LE` and `get*LT` functions for the raw trees.
-/
end Raw
@[inline, inherit_doc Raw.filterMap]
def filterMap (f : (a : α) β a Option (γ a)) (t : DTreeMap α β cmp) : DTreeMap α γ cmp :=
letI : Ord α := cmp; t.inner.filterMap f t.wf.balanced |>.impl, t.wf.filterMap
@[inline, inherit_doc Raw.map]
/-- Updates the values of the map by applying the given function to all mappings. -/
@[inline]
def map (f : (a : α) β a γ a) (t : DTreeMap α β cmp) : DTreeMap α γ cmp :=
letI : Ord α := cmp; t.inner.map f, t.wf.map

View File

@@ -14,7 +14,7 @@ This file develops the type `Std.DTreeMap` of dependent tree maps.
Lemmas about the operations on `Std.DTreeMap` will be available in the
module `Std.Data.DTreeMap.Lemmas`.
See the module `Std.Data.DTreeMap.Raw` for a variant of this type which is safe to use in
See the module `Std.Data.DTreeMap.Raw.Basic` for a variant of this type which is safe to use in
nested inductive types.
-/
@@ -791,17 +791,17 @@ def fold (f : δ → (a : α) → β a → δ) (init : δ) (t : DTreeMap α β c
/-- Folds the given monadic function over the mappings in the map in descending order. -/
@[inline]
def foldrM (f : δ (a : α) β a m δ) (init : δ) (t : DTreeMap α β cmp) : m δ :=
def foldrM (f : (a : α) β a δ m δ) (init : δ) (t : DTreeMap α β cmp) : m δ :=
t.inner.foldrM f init
/-- Folds the given function over the mappings in the map in descending order. -/
@[inline]
def foldr (f : δ (a : α) β a δ) (init : δ) (t : DTreeMap α β cmp) : δ :=
def foldr (f : (a : α) β a δ δ) (init : δ) (t : DTreeMap α β cmp) : δ :=
t.inner.foldr f init
@[inline, inherit_doc foldr, deprecated foldr (since := "2025-02-12")]
def revFold (f : δ (a : α) β a δ) (init : δ) (t : DTreeMap α β cmp) : δ :=
foldr f init t
foldr (fun k v acc => f acc k v) init t
/-- Partitions a tree map into two tree maps based on a predicate. -/
@[inline] def partition (f : (a : α) β a Bool)

View File

@@ -59,7 +59,8 @@ private def queryNames : Array Name :=
``get!_eq_getValueCast!, ``Const.get!_eq_getValue!,
``getD_eq_getValueCastD, ``Const.getD_eq_getValueD,
``getKey?_eq_getKey?, ``getKey_eq_getKey,
``getKey!_eq_getKey!, ``getKeyD_eq_getKeyD]
``getKey!_eq_getKey!, ``getKeyD_eq_getKeyD,
``keys_eq_keys, ``toList_eq_toListModel, ``Const.toList_eq_toListModel_map]
private def modifyMap : Std.HashMap Name Name :=
.ofList
@@ -1465,4 +1466,109 @@ theorem getThenInsertIfNew?!_snd [TransOrd α] (h : t.WF) {k : α} {v : β} :
end Const
theorem length_keys [TransOrd α] (h : t.WF) :
t.keys.length = t.size := by
simp_to_model using List.length_keys_eq_length
theorem isEmpty_keys :
t.keys.isEmpty = t.isEmpty := by
simp_to_model using List.isEmpty_keys_eq_isEmpty
theorem contains_keys [BEq α] [beqOrd : LawfulBEqOrd α] [TransOrd α] {k : α} (h : t.WF) :
t.keys.contains k = t.contains k := by
rw [contains_eq_containsKey h.ordered, eq_beqOfOrd_of_lawfulBEqOrd]
simp_to_model using (List.containsKey_eq_keys_contains (a := k) (l := t.toListModel)).symm
theorem mem_keys [LawfulEqOrd α] [TransOrd α] {k : α} (h : t.WF) :
k t.keys k t := by
simpa only [mem_iff_contains, List.contains_iff, Bool.eq_iff_iff] using contains_keys h
theorem distinct_keys [TransOrd α] (h : t.WF) :
t.keys.Pairwise (fun a b => ¬ compare a b = .eq) := by
simp only [ not_congr beq_iff_eq, beq_eq, Bool.not_eq_true]
simp_to_model using h.ordered.distinctKeys.distinct
theorem map_fst_toList_eq_keys :
t.toList.map Sigma.fst = t.keys := by
simp_to_model using (List.keys_eq_map ..).symm
theorem length_toList [TransOrd α] (h : t.WF) :
t.toList.length = t.size := by
simp_to_model
theorem isEmpty_toList :
t.toList.isEmpty = t.isEmpty := by
simp_to_model
theorem mem_toList_iff_get?_eq_some [TransOrd α] [LawfulEqOrd α] {k : α} {v : β k} (h : t.WF) :
k, v t.toList t.get? k = some v := by
simp_to_model using List.mem_iff_getValueCast?_eq_some
theorem find?_toList_eq_some_iff_get?_eq_some [TransOrd α] [LawfulEqOrd α] {k : α} {v : β k}
(h : t.WF) :
t.toList.find? (compare ·.1 k == .eq) = some k, v t.get? k = some v := by
simp_to_model using List.find?_eq_some_iff_getValueCast?_eq_some
theorem find?_toList_eq_none_iff_contains_eq_false [TransOrd α] {k : α} (h : t.WF) :
t.toList.find? (compare ·.1 k == .eq) = none t.contains k = false := by
simp_to_model using List.find?_eq_none_iff_containsKey_eq_false
theorem find?_toList_eq_none_iff_not_mem [TransOrd α] {k : α} (h : t.WF) :
t.toList.find? (compare ·.1 k == .eq) = none ¬ k t := by
simpa only [Bool.not_eq_true, mem_iff_contains] using find?_toList_eq_none_iff_contains_eq_false h
theorem distinct_keys_toList [TransOrd α] (h : t.WF) :
t.toList.Pairwise (fun a b => ¬ compare a.1 b.1 = .eq) := by
simp only [ beq_iff, Bool.not_eq_true]
simp_to_model using List.pairwise_fst_eq_false
namespace Const
variable {β : Type v} {t : Impl α β}
theorem map_fst_toList_eq_keys :
(toList t).map Prod.fst = t.keys := by
simp_to_model using List.map_fst_map_toProd_eq_keys
theorem length_toList (h : t.WF) :
(toList t).length = t.size := by
simp_to_model using List.length_map
theorem isEmpty_toList :
(toList t).isEmpty = t.isEmpty := by
rw [Bool.eq_iff_iff, List.isEmpty_iff, isEmpty_eq_isEmpty, List.isEmpty_iff]
simp_to_model using List.map_eq_nil_iff
theorem mem_toList_iff_get?_eq_some [TransOrd α] [LawfulEqOrd α] {k : α} {v : β} (h : t.WF) :
(k, v) toList t get? t k = some v := by
simp_to_model using List.mem_map_toProd_iff_getValue?_eq_some
theorem mem_toList_iff_getKey?_eq_some_and_get?_eq_some [TransOrd α] {k : α} {v : β} (h : t.WF) :
(k, v) toList t t.getKey? k = some k get? t k = some v := by
simp_to_model using List.mem_map_toProd_iff_getKey?_eq_some_and_getValue?_eq_some
theorem get?_eq_some_iff_exists_compare_eq_eq_and_mem_toList [TransOrd α] {k : α} {v : β} (h : t.WF) :
get? t k = some v (k' : α), compare k k' = .eq (k', v) toList t := by
simp_to_model using List.getValue?_eq_some_iff_exists_beq_and_mem_toList
theorem find?_toList_eq_some_iff_getKey?_eq_some_and_get?_eq_some [TransOrd α] {k k' : α} {v : β}
(h : t.WF) : (toList t).find? (fun a => compare a.1 k == .eq) = some k', v
t.getKey? k = some k' get? t k = some v := by
simp_to_model using List.find?_map_toProd_eq_some_iff_getKey?_eq_some_and_getValue?_eq_some
theorem find?_toList_eq_none_iff_contains_eq_false [TransOrd α] {k : α} (h : t.WF) :
(toList t).find? (compare ·.1 k == .eq) = none t.contains k = false := by
simp_to_model using List.find?_map_eq_none_iff_containsKey_eq_false
theorem find?_toList_eq_none_iff_not_mem [TransOrd α] {k : α} (h : t.WF) :
(toList t).find? (compare ·.1 k == .eq) = none ¬ k t := by
simpa only [Bool.not_eq_true, mem_iff_contains] using find?_toList_eq_none_iff_contains_eq_false h
theorem distinct_keys_toList [TransOrd α] (h : t.WF) :
(toList t).Pairwise (fun a b => ¬ compare a.1 b.1 = .eq) := by
simp only [ beq_iff, Bool.not_eq_true]
simp_to_model using List.pairwise_fst_eq_false_map_toProd
end Const
end Std.DTreeMap.Internal.Impl

View File

@@ -605,6 +605,42 @@ theorem containsThenInsertIfNew!_snd_eq_insertIfNew! [Ord α] (t : Impl α β) (
rw [containsThenInsertIfNew!, insertIfNew!]
split <;> rfl
theorem insertMin_eq_insertMin! [Ord α] {a b} {t : Impl α β} (htb) :
(t.insertMin a b htb).impl = t.insertMin! a b := by
cases a, b, t using insertMin!.fun_cases
· rfl
· simp only [insertMin!, insertMin, balanceL_eq_balanceL!, insertMin_eq_insertMin! htb.left]
theorem insertMax_eq_insertMax! [Ord α] {a b} {t : Impl α β} (htb) :
(t.insertMax a b htb).impl = t.insertMax! a b := by
cases a, b, t using insertMax!.fun_cases
· rfl
· simp only [insertMax!, insertMax, balanceR_eq_balanceR!, insertMax_eq_insertMax! htb.right]
theorem link_eq_link! [Ord α] {k v} {l r : Impl α β} (hlb hrb) :
(link k v l r hlb hrb).impl = link! k v l r := by
cases k, v, l, r using link!.fun_cases <;> rw [link, link!]
· rw [insertMin_eq_insertMin!]
· rw [insertMax_eq_insertMax!]
· split <;> simp only [balanceLErase_eq_balanceL!, link_eq_link! hlb hrb.left]
· split <;> simp only [balanceRErase_eq_balanceR!, balanceLErase_eq_balanceL!,
link_eq_link! hlb hrb.left, link_eq_link! hlb.right hrb]
· split
· simp only [balanceLErase_eq_balanceL!, link_eq_link! hlb hrb.left]
· simp only [Std.Internal.tree_tac]
termination_by sizeOf l + sizeOf r
theorem link2_eq_link2! [Ord α] {l r : Impl α β} (hlb hrb) :
(link2 l r hlb hrb).impl = link2! l r := by
cases l, r using link2!.fun_cases <;> rw [link2!, link2]
· split <;> simp only [balanceLErase_eq_balanceL!, link2_eq_link2! hlb hrb.left]
· split <;> simp only [balanceRErase_eq_balanceR!, balanceLErase_eq_balanceL!,
link2_eq_link2! hlb.right hrb, link2_eq_link2! hlb hrb.left]
· split
· simp only [balanceLErase_eq_balanceL!, link2_eq_link2! hlb hrb.left]
· simp only [Std.Internal.tree_tac, glue_eq_glue!]
termination_by sizeOf l + sizeOf r
namespace Const
variable {β : Type v}

View File

@@ -243,7 +243,7 @@ def link! (k : α) (v : β k) (l r : Impl α β) : Impl α β :=
if delta * szl < szr then
balanceL! k'' v'' (link! k v l l'') r''
else if delta * szr < szl then
balanceR! k' v' l' (link! k v r r')
balanceR! k' v' l' (link! k v r' r)
else
.inner (l.size + 1 + r.size) k v l r
termination_by sizeOf l + sizeOf r

View File

@@ -198,16 +198,16 @@ def foldl (f : δ → (a : α) → β a → δ) (init : δ) (t : Impl α β) :
/-- Folds the given function over the mappings in the tree in descending order. -/
@[specialize]
def foldrM {m} [Monad m] (f : δ (a : α) β a m δ) (init : δ) : Impl α β m δ
def foldrM {m} [Monad m] (f : (a : α) β a δ m δ) (init : δ) : Impl α β m δ
| .leaf => pure init
| .inner _ k v l r => do
let right foldlM f init r
let middle f right k v
foldlM f middle l
let right foldrM f init r
let middle f k v right
foldrM f middle l
/-- Folds the given function over the mappings in the tree in descending order. -/
@[inline]
def foldr (f : δ (a : α) β a δ) (init : δ) (t : Impl α β) : δ :=
def foldr (f : (a : α) β a δ δ) (init : δ) (t : Impl α β) : δ :=
Id.run (t.foldrM f init)
/-- Applies the given function to the mappings in the tree in ascending order. -/
@@ -237,7 +237,7 @@ def forIn {m} [Monad m] (f : δ → (a : α) → β a → m (ForInStep δ)) (ini
/-- Returns a `List` of the keys in order. -/
@[inline] def keys (t : Impl α β) : List α :=
t.foldr (init := []) fun l k _ => k :: l
t.foldr (init := []) fun k _ l => k :: l
/-- Returns an `Array` of the keys in order. -/
@[inline] def keysArray (t : Impl α β) : Array α :=
@@ -245,7 +245,7 @@ def forIn {m} [Monad m] (f : δ → (a : α) → β a → m (ForInStep δ)) (ini
/-- Returns a `List` of the values in order. -/
@[inline] def values {β : Type v} (t : Impl α β) : List β :=
t.foldr (init := []) fun l _ v => v :: l
t.foldr (init := []) fun _ v l => v :: l
/-- Returns an `Array` of the values in order. -/
@[inline] def valuesArray {β : Type v} (t : Impl α β) : Array β :=
@@ -253,7 +253,7 @@ def forIn {m} [Monad m] (f : δ → (a : α) → β a → m (ForInStep δ)) (ini
/-- Returns a `List` of the key/value pairs in order. -/
@[inline] def toList (t : Impl α β) : List ((a : α) × β a) :=
t.foldr (init := []) fun l k v => k, v :: l
t.foldr (init := []) fun k v l => k, v :: l
/-- Returns an `Array` of the key/value pairs in order. -/
@[inline] def toArray (t : Impl α β) : Array ((a : α) × β a) :=
@@ -265,7 +265,7 @@ variable {β : Type v}
/-- Returns a `List` of the key/value pairs in order. -/
@[inline] def toList (t : Impl α β) : List (α × β) :=
t.foldr (init := []) fun l k v => (k, v) :: l
t.foldr (init := []) fun k v l => (k, v) :: l
/-- Returns a `List` of the key/value pairs in order. -/
@[inline] def toArray (t : Impl α β) : Array (α × β) :=

View File

@@ -797,7 +797,7 @@ theorem toListModel_insert [Ord α] [TransOrd α] {k : α} {v : β k} {l : Impl
### `insert!`
-/
theorem WF.insert! [Ord α] [TransOrd α] {k : α} {v : β k} {l : Impl α β}
theorem WF.insert! {_ : Ord α} [TransOrd α] {k : α} {v : β k} {l : Impl α β}
(h : l.WF) : (l.insert! k v).WF := by
simpa [insert_eq_insert!] using WF.insert (h := h.balanced) h
@@ -843,7 +843,7 @@ theorem toListModel_erase [Ord α] [TransOrd α] {k : α} {t : Impl α β} (htb
### `erase!`
-/
theorem WF.erase! [Ord α] [TransOrd α] {k : α} {l : Impl α β}
theorem WF.erase! {_ : Ord α} [TransOrd α] {k : α} {l : Impl α β}
(h : l.WF) : (l.erase! k).WF := by
simpa [erase_eq_erase!] using WF.erase (h := h.balanced) h
@@ -883,7 +883,7 @@ theorem toListModel_containsThenInsert [Ord α] [TransOrd α] {k : α} {v : β k
### containsThenInsert!
-/
theorem WF.containsThenInsert! [Ord α] [TransOrd α] {k : α} {v : β k} {t : Impl α β} (h : t.WF) :
theorem WF.containsThenInsert! {_ : Ord α} [TransOrd α] {k : α} {v : β k} {t : Impl α β} (h : t.WF) :
(t.containsThenInsert! k v).2.WF := by
simpa [containsThenInsert!_snd_eq_containsThenInsert_snd, h.balanced] using WF.containsThenInsert (h := h.balanced) h
@@ -919,7 +919,7 @@ theorem ordered_insertIfNew! [Ord α] [TransOrd α] {k : α} {v : β k} {l : Imp
(h : l.Balanced) (ho : l.Ordered) : (l.insertIfNew! k v).Ordered := by
simpa [insertIfNew_eq_insertIfNew!] using ordered_insertIfNew h ho
theorem WF.insertIfNew! [Ord α] [TransOrd α] {k : α} {v : β k} {l : Impl α β}
theorem WF.insertIfNew! {_ : Ord α} [TransOrd α] {k : α} {v : β k} {l : Impl α β}
(h : l.WF) : (l.insertIfNew! k v).WF := by
simpa [insertIfNew_eq_insertIfNew!] using h.insertIfNew (h := h.balanced)
@@ -950,7 +950,7 @@ theorem ordered_containsThenInsertIfNew! [Ord α] [TransOrd α] {k : α} {v : β
(h : l.Balanced) (ho : l.Ordered) : (l.containsThenInsertIfNew! k v).2.Ordered := by
simpa [containsThenInsertIfNew!_snd_eq_insertIfNew!] using ordered_insertIfNew! h ho
theorem WF.containsThenInsertIfNew! [Ord α] [TransOrd α] {k : α} {v : β k} {l : Impl α β}
theorem WF.containsThenInsertIfNew! {_ : Ord α} [TransOrd α] {k : α} {v : β k} {l : Impl α β}
(h : l.WF) : (l.containsThenInsertIfNew! k v).2.WF := by
simpa [containsThenInsertIfNew!_snd_eq_insertIfNew!] using WF.insertIfNew! (h := h)
@@ -1055,6 +1055,26 @@ theorem ordered_alter [Ord α] [TransOrd α] [LawfulEqOrd α] {t : Impl α β} {
rw [alter_eq_alterₘ htb hto, alterₘ]
exact ordered_updateAtKey htb hto
/-!
### alter!
-/
theorem alter_eq_alter! [Ord α] [LawfulEqOrd α] {t : Impl α β} {a f} (htb) :
(alter a f t htb).impl = alter! a f t := by
induction t with
| leaf =>
rw [alter, alter!]
cases f none <;> rfl
| inner sz k' v' l' r' ihl ihr =>
rw [alter, alter!]
split
case h_1 => simp only [balance_eq_balance!, ihl htb.left]
case h_2 => simp only [balance_eq_balance!, ihr htb.right]
case h_3 =>
cases f (some _)
· exact glue_eq_glue!
· rfl
/-!
### modify
-/
@@ -1086,10 +1106,88 @@ theorem ordered_mergeWith [Ord α] [TransOrd α] [LawfulEqOrd α] {t₁ t₂ : I
| leaf => exact hto
| inner sz k v l r ihl ihr => exact ihr _ (ordered_alter _ (ihl htb hto))
/-!
### foldlM
-/
theorem foldlM_eq_foldlM {t : Impl α β} {m δ} [Monad m] [LawfulMonad m]
{f : δ (a : α) β a m δ} {init} :
t.foldlM (init := init) f = t.toListModel.foldlM (init := init) fun acc p => f acc p.1 p.2 := by
induction t generalizing init with
| leaf => rfl
| inner sz k v l r ihl ihr =>
simp only [foldlM, toListModel_inner, List.foldl_append, List.foldl_cons]
simp [ihl, ihr]
/-!
### foldl
-/
theorem foldl_eq_foldl {t : Impl α β} {δ} {f : δ (a : α) β a δ} {init} :
t.foldl (init := init) f = t.toListModel.foldl (init := init) fun acc p => f acc p.1 p.2 := by
rw [foldl, foldlM_eq_foldlM, List.foldl_eq_foldlM, Id.run]
/-!
### foldrM
-/
theorem foldrM_eq_foldrM {t : Impl α β} {m δ} [Monad m] [LawfulMonad m]
{f : (a : α) β a δ m δ} {init} :
t.foldrM (init := init) f = t.toListModel.foldrM (init := init) fun p acc => f p.1 p.2 acc := by
induction t generalizing init with
| leaf => rfl
| inner sz k v l r ihl ihr =>
simp only [foldrM, toListModel_inner, List.foldr_append, List.foldr_cons]
simp [ihl, ihr]
/-!
### foldr
-/
theorem foldr_eq_foldr {t : Impl α β} {δ} {f : (a : α) β a δ δ} {init} :
t.foldr (init := init) f = t.toListModel.foldr (init := init) fun p acc => f p.1 p.2 acc := by
rw [foldr, foldrM_eq_foldrM, List.foldr_eq_foldrM, Id.run]
/-!
### toList
-/
theorem toList_eq_toListModel {t : Impl α β} :
t.toList = t.toListModel := by
rw [toList, foldr_eq_foldr]
induction t with
| leaf => rfl
| inner sz k v l r ihl ihr => simp
/-!
### keys
-/
theorem keys_eq_keys {t : Impl α β} :
t.keys = t.toListModel.keys := by
rw [keys, foldr_eq_foldr, List.keys.eq_def]
simp
induction t.toListModel with
| nil => rfl
| cons e es ih =>
simp [ih]
rw [List.keys.eq_def]
namespace Const
variable {β : Type v}
/-!
### getThenInsertIfNew?!
-/
theorem WF.getThenInsertIfNew?! [Ord α] [TransOrd α] [LawfulEqOrd α] {k : α} {v : β} {t : Impl α β}
(h : t.WF) : (getThenInsertIfNew?! t k v).2.WF := by
rw [getThenInsertIfNew?!.eq_def]
cases get? t k
· exact h.insertIfNew!
· exact h
/-!
### alter
-/
@@ -1141,6 +1239,26 @@ theorem ordered_alter [Ord α] [TransOrd α] {t : Impl α β} {a f}
rw [alter_eq_alterₘ htb hto, alterₘ]
exact ordered_updateAtKey htb hto
/-!
### alter!
-/
theorem alter_eq_alter! [Ord α] {t : Impl α β} {a f} (htb) :
(alter a f t htb).impl = alter! a f t := by
induction t with
| leaf =>
rw [alter, alter!]
cases f none <;> rfl
| inner sz k' v' l' r' ihl ihr =>
rw [alter, alter!]
cases compare a k'
case lt => simp only [balance_eq_balance!, ihl htb.left]
case gt => simp only [balance_eq_balance!, ihr htb.right]
case eq =>
cases f (some v')
· exact glue_eq_glue!
· rfl
/-!
### modify
-/
@@ -1173,6 +1291,17 @@ theorem ordered_mergeWith [Ord α] [TransOrd α] {t₁ t₂ : Impl α β} {f}
| leaf => exact hto
| inner sz k v l r ihl ihr => exact ihr _ (ordered_alter _ (ihl htb hto))
/-!
### toList
-/
theorem toList_eq_toListModel_map {t : Impl α β} :
Const.toList t = t.toListModel.map fun k, v => (k, v) := by
rw [toList, foldr_eq_foldr]
induction t with
| leaf => rfl
| inner sz k v l r ihl ihr => simp
end Const
/-!
@@ -1252,6 +1381,108 @@ theorem wf [Ord α] {t : Impl α β} {t' : Impl α β'} (hs : SameKeys t t') (h
end SameKeys
/-!
### getThenInsertIfNew?!
-/
theorem WF.getThenInsertIfNew?! {_ : Ord α} [TransOrd α] [LawfulEqOrd α] {k : α} {v : β k} {t : Impl α β}
(h : t.WF) : (t.getThenInsertIfNew?! k v).2.WF := by
rw [getThenInsertIfNew?!.eq_def]
cases get? t k
· exact h.insertIfNew!
· exact h
theorem WF.constGetThenInsertIfNew?! {β : Type v} {_ : Ord α} [TransOrd α] {k : α} {v : β} {t : Impl α β}
(h : t.WF) : (Const.getThenInsertIfNew?! t k v).2.WF := by
rw [Const.getThenInsertIfNew?!.eq_def]
cases Const.get? t k
· exact h.insertIfNew!
· exact h
/-!
### `eraseMany!`
-/
theorem WF.eraseMany! {_ : Ord α} [TransOrd α] {ρ} [ForIn Id ρ α] {l : ρ}
{t : Impl α β} (h : t.WF) : (t.eraseMany! l).1.WF :=
(t.eraseMany! l).2 h (fun _ _ h' => h'.erase!)
/-!
### `insertMany!`
-/
theorem WF.insertMany! {_ : Ord α} [TransOrd α] {ρ} [ForIn Id ρ ((a : α) × β a)] {l : ρ}
{t : Impl α β} (h : t.WF) : (t.insertMany! l).1.WF :=
(t.insertMany! l).2 h (fun _ _ _ h' => h'.insert!)
theorem WF.constInsertMany! {β : Type v} {_ : Ord α} [TransOrd α] {ρ} [ForIn Id ρ (α × β)] {l : ρ}
{t : Impl α β} (h : t.WF) : (Const.insertMany! t l).1.WF :=
(Const.insertMany! t l).2 h (fun _ _ _ h' => h'.insert!)
theorem WF.constInsertManyIfNewUnit! {_ : Ord α} [TransOrd α] {ρ} [ForIn Id ρ α] {l : ρ}
{t : Impl α Unit} (h : t.WF) : (Const.insertManyIfNewUnit! t l).1.WF :=
(Const.insertManyIfNewUnit! t l).2 h (fun _ _ h' => h'.insertIfNew!)
/-!
### alter!
-/
theorem WF.alter! {_ : Ord α} [LawfulEqOrd α] {t : Impl α β} {a f} (h : t.WF) :
(alter! a f t).WF := by
rw [ alter_eq_alter! h.balanced]
exact h.alter
theorem WF.constAlter! {_ : Ord α} {β : Type v} {t : Impl α β} {a f} (h : t.WF) :
(Const.alter! a f t).WF := by
rw [ Const.alter_eq_alter! h.balanced]
exact h.constAlter
/-!
### mergeWith!
-/
theorem mergeWith_eq_mergeWith! {_ : Ord α} [LawfulEqOrd α] {mergeFn} {t₁ t₂ : Impl α β}
(h : t₁.Balanced) :
(mergeWith mergeFn t₁ t₂ h).impl = mergeWith! mergeFn t₁ t₂ := by
rw [mergeWith, mergeWith!]
induction t₂ generalizing t₁ with
| leaf => rfl
| inner sz k v l r ihl ihr =>
simp only [foldl, foldlM, Id.run, bind]
simp only [foldl, Id.run, bind] at ihl ihr
rw [ihr]
congr
simp only [SizedBalancedTree.toBalancedTree]
rw [alter_eq_alter!]
congr
exact ihl h
theorem WF.mergeWith! {_ : Ord α} [LawfulEqOrd α] {mergeFn} {t₁ t₂ : Impl α β} (h : t₁.WF) :
(Impl.mergeWith! mergeFn t₁ t₂).WF := by
rw [ mergeWith_eq_mergeWith! h.balanced]
exact h.mergeWith
theorem Const.mergeWith_eq_mergeWith! {β : Type v} {_ : Ord α} {mergeFn} {t₁ t₂ : Impl α β}
(h : t₁.Balanced) :
(mergeWith mergeFn t₁ t₂ h).impl = mergeWith! mergeFn t₁ t₂ := by
rw [mergeWith, mergeWith!]
induction t₂ generalizing t₁ with
| leaf => rfl
| inner sz k v l r ihl ihr =>
simp only [foldl, foldlM, Id.run, bind]
simp only [foldl, Id.run, bind] at ihl ihr
rw [ihr]
congr
simp only [SizedBalancedTree.toBalancedTree]
rw [alter_eq_alter!]
congr
exact ihl h
theorem WF.constMergeWith! {β : Type v} {_ : Ord α} {mergeFn} {t₁ t₂ : Impl α β} (h : t₁.WF) :
(Impl.Const.mergeWith! mergeFn t₁ t₂).WF := by
rw [ Const.mergeWith_eq_mergeWith! h.balanced]
exact h.constMergeWith
/-!
### filterMap
-/
@@ -1260,6 +1491,44 @@ theorem WF.filterMap [Ord α] {t : Impl α β} {h} {f : (a : α) → β a → Op
(t.filterMap f h).impl.WF :=
.wf balanced_filterMap (ordered_filterMap hwf.ordered)
/-!
### filterMap!
-/
theorem filterMap_eq_filterMap! [Ord α] {t : Impl α β} {h} {f : (a : α) β a Option (γ a)} :
(t.filterMap f h).impl = t.filterMap! f := by
induction t with
| leaf => rfl
| inner sz k v _ _ ihl ihr =>
simp [filterMap, filterMap!]
cases f k v
· simp only [link2_eq_link2!, ihl, ihr, h.left, h.right]
· simp only [link_eq_link!, ihl, ihr, h.left, h.right]
theorem WF.filterMap! {_ : Ord α} {t : Impl α β} {f : (a : α) β a Option (γ a)} (h : t.WF) :
(t.filterMap! f).WF := by
rw [ filterMap_eq_filterMap! (h := h.balanced)]
exact h.filterMap
/-!
### filter!
-/
theorem filter_eq_filter! [Ord α] {t : Impl α β} {h} {f : (a : α) β a Bool} :
(t.filter f h).impl = t.filter! f := by
induction t with
| leaf => rfl
| inner sz k v l r ihl ihr =>
simp only [filter!, filter]
split
· simp only [ihl, ihr, link2_eq_link2!, h.left, h.right]
· simp only [ihl, ihr, link_eq_link!, h.left, h.right]
theorem WF.filter! {_ : Ord α} {t : Impl α β} {f : (a : α) β a Bool} (h : t.WF) :
(t.filter! f).WF := by
rw [ filter_eq_filter! (h := h.balanced)]
exact h.filter
/-!
### map
-/

View File

@@ -922,4 +922,118 @@ theorem getThenInsertIfNew?_snd [TransCmp cmp] {k : α} {v : β} :
end Const
@[simp]
theorem length_keys [TransCmp cmp] :
t.keys.length = t.size :=
Impl.length_keys t.wf
@[simp]
theorem isEmpty_keys :
t.keys.isEmpty = t.isEmpty :=
Impl.isEmpty_keys
@[simp]
theorem contains_keys [BEq α] [LawfulBEqCmp cmp] [TransCmp cmp] {k : α} :
t.keys.contains k = t.contains k :=
Impl.contains_keys t.wf
@[simp]
theorem mem_keys [LawfulEqCmp cmp] [TransCmp cmp] {k : α} :
k t.keys k t :=
Impl.mem_keys t.wf
theorem distinct_keys [TransCmp cmp] :
t.keys.Pairwise (fun a b => ¬ cmp a b = .eq) :=
Impl.distinct_keys t.wf
@[simp]
theorem map_fst_toList_eq_keys :
t.toList.map Sigma.fst = t.keys :=
Impl.map_fst_toList_eq_keys
@[simp]
theorem length_toList [TransCmp cmp] :
t.toList.length = t.size :=
Impl.length_toList t.wf
@[simp]
theorem isEmpty_toList :
t.toList.isEmpty = t.isEmpty :=
Impl.isEmpty_toList
@[simp]
theorem mem_toList_iff_get?_eq_some [TransCmp cmp] [LawfulEqCmp cmp] {k : α} {v : β k} :
k, v t.toList t.get? k = some v :=
Impl.mem_toList_iff_get?_eq_some t.wf
theorem find?_toList_eq_some_iff_get?_eq_some [TransCmp cmp] [LawfulEqCmp cmp] {k : α} {v : β k} :
t.toList.find? (cmp ·.1 k == .eq) = some k, v t.get? k = some v :=
Impl.find?_toList_eq_some_iff_get?_eq_some t.wf
theorem find?_toList_eq_none_iff_contains_eq_false [TransCmp cmp] {k : α} :
t.toList.find? (cmp ·.1 k == .eq) = none t.contains k = false :=
Impl.find?_toList_eq_none_iff_contains_eq_false t.wf
@[simp]
theorem find?_toList_eq_none_iff_not_mem [TransCmp cmp] {k : α} :
t.toList.find? (cmp ·.1 k == .eq) = none ¬ k t := by
simpa only [Bool.not_eq_true, mem_iff_contains] using find?_toList_eq_none_iff_contains_eq_false
theorem distinct_keys_toList [TransCmp cmp] :
t.toList.Pairwise (fun a b => ¬ cmp a.1 b.1 = .eq) :=
Impl.distinct_keys_toList t.wf
namespace Const
variable {β : Type v} {t : DTreeMap α β cmp}
@[simp]
theorem map_fst_toList_eq_keys :
(toList t).map Prod.fst = t.keys :=
Impl.Const.map_fst_toList_eq_keys
@[simp]
theorem length_toList :
(toList t).length = t.size :=
Impl.Const.length_toList t.wf
@[simp]
theorem isEmpty_toList :
(toList t).isEmpty = t.isEmpty :=
Impl.Const.isEmpty_toList
@[simp]
theorem mem_toList_iff_get?_eq_some [TransCmp cmp] [LawfulEqCmp cmp] {k : α} {v : β} :
(k, v) toList t get? t k = some v :=
Impl.Const.mem_toList_iff_get?_eq_some t.wf
@[simp]
theorem mem_toList_iff_getKey?_eq_some_and_get?_eq_some [TransCmp cmp] {k : α} {v : β} :
(k, v) toList t t.getKey? k = some k get? t k = some v :=
Impl.Const.mem_toList_iff_getKey?_eq_some_and_get?_eq_some t.wf
theorem get?_eq_some_iff_exists_compare_eq_eq_and_mem_toList [TransCmp cmp] {k : α} {v : β} :
get? t k = some v (k' : α), cmp k k' = .eq (k', v) toList t :=
Impl.Const.get?_eq_some_iff_exists_compare_eq_eq_and_mem_toList t.wf
theorem find?_toList_eq_some_iff_getKey?_eq_some_and_get?_eq_some [TransCmp cmp] {k k' : α} {v : β} :
(toList t).find? (cmp ·.1 k == .eq) = some k', v
t.getKey? k = some k' get? t k = some v :=
Impl.Const.find?_toList_eq_some_iff_getKey?_eq_some_and_get?_eq_some t.wf
theorem find?_toList_eq_none_iff_contains_eq_false [TransCmp cmp] {k : α} :
(toList t).find? (cmp ·.1 k == .eq) = none t.contains k = false :=
Impl.Const.find?_toList_eq_none_iff_contains_eq_false t.wf
@[simp]
theorem find?_toList_eq_none_iff_not_mem [TransCmp cmp] {k : α} :
(toList t).find? (cmp ·.1 k == .eq) = none ¬ k t :=
Impl.Const.find?_toList_eq_none_iff_not_mem t.wf
theorem distinct_keys_toList [TransCmp cmp] :
(toList t).Pairwise (fun a b => ¬ cmp a.1 b.1 = .eq) :=
Impl.Const.distinct_keys_toList t.wf
end Const
end Std.DTreeMap

View File

@@ -0,0 +1,44 @@
/-
Copyright (c) 2025 Lean FRO, LLC. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Paul Reichert
-/
prelude
import Std.Data.DTreeMap.Raw.Basic
import Std.Data.DTreeMap.AdditionalOperations
/-!
# Additional dependent raw tree map operations
This file defines more operations on `Std.DTreeMap.Raw`.
We currently do not provide lemmas for these functions.
-/
set_option autoImplicit false
set_option linter.missingDocs true
universe u v w
variable {α : Type u} {β : α Type v} {γ : α Type w} {cmp : α α Ordering}
private local instance : Coe (Type v) (α Type v) where coe γ := fun _ => γ
namespace Std.DTreeMap
open Internal (Impl)
namespace Raw
@[inline, inherit_doc DTreeMap.filterMap]
def filterMap (f : (a : α) β a Option (γ a)) (t : Raw α β cmp) : Raw α γ cmp :=
letI : Ord α := cmp; t.inner.filterMap! f
@[inline, inherit_doc DTreeMap.map]
def map (f : (a : α) β a γ a) (t : Raw α β cmp) : Raw α γ cmp :=
letI : Ord α := cmp; t.inner.map f
/-!
We do not provide `get*GE`, `get*GT`, `get*LE` and `get*LT` functions for the raw trees.
-/
end Raw
end Std.DTreeMap

View File

@@ -18,7 +18,7 @@ available as `Std.DTreeMap.Raw.WF` and we prove in this file that all operations
well-formedness. When in doubt, prefer `DTreeMap` over `DTreeMap.Raw`.
Lemmas about the operations on `Std.DTreeMap.Raw` will be available in the module
`Std.Data.DTreeMap.RawLemmas`.
`Std.Data.DTreeMap.Raw.Lemmas`.
-/
set_option autoImplicit false
@@ -38,7 +38,7 @@ open Internal (Impl)
Dependent tree maps without a bundled well-formedness invariant, suitable for use in nested
inductive types. The well-formedness invariant is called `Raw.WF`. When in doubt, prefer `DTreeMap`
over `DTreeMap.Raw`. Lemmas about the operations on `Std.DTreeMap.Raw` are available in the
module `Std.Data.DTreeMap.RawLemmas`.
module `Std.Data.DTreeMap.Raw.Lemmas`.
A tree map stores an assignment of keys to values. It depends on a comparator function that
defines an ordering on the keys and provides efficient order-dependent queries, such as retrieval
@@ -559,16 +559,16 @@ def fold (f : δ → (a : α) → β a → δ) (init : δ) (t : Raw α β cmp) :
t.foldl f init
@[inline, inherit_doc DTreeMap.foldrM]
def foldrM (f : δ (a : α) β a m δ) (init : δ) (t : Raw α β cmp) : m δ :=
def foldrM (f : (a : α) β a δ m δ) (init : δ) (t : Raw α β cmp) : m δ :=
t.inner.foldrM f init
@[inline, inherit_doc DTreeMap.foldr]
def foldr (f : δ (a : α) β a δ) (init : δ) (t : Raw α β cmp) : δ :=
def foldr (f : (a : α) β a δ δ) (init : δ) (t : Raw α β cmp) : δ :=
t.inner.foldr f init
@[inline, inherit_doc foldr, deprecated foldr (since := "2025-02-12")]
def revFold (f : δ (a : α) β a δ) (init : δ) (t : Raw α β cmp) : δ :=
foldr f init t
foldr (fun k v acc => f acc k v) init t
@[inline, inherit_doc DTreeMap.partition]
def partition (f : (a : α) β a Bool) (t : Raw α β cmp) : Raw α β cmp × Raw α β cmp :=

View File

@@ -5,13 +5,14 @@ Authors: Markus Himmel, Paul Reichert
-/
prelude
import Std.Data.DTreeMap.Internal.Lemmas
import Std.Data.DTreeMap.Raw
import Std.Data.DTreeMap.Raw.Basic
/-!
# Dependent tree map lemmas
This file contains lemmas about `Std.Data.DTreeMap.Raw`. Most of the lemmas require
`TransCmp cmp` for the comparison function `cmp`.
This file contains lemmas about `Std.Data.DTreeMap.Raw.Basic`. Most of the lemmas require
`TransCmp cmp` for the comparison function `cmp` and a proof that the involved maps are well-formed.
These proofs can be obtained from `Std.Data.DTreeMap.Raw.WF`.
-/
set_option linter.missingDocs true
@@ -929,4 +930,119 @@ theorem getThenInsertIfNew?_snd [TransCmp cmp] (h : t.WF) {k : α} {v : β} :
end Const
@[simp]
theorem length_keys [TransCmp cmp] (h : t.WF) :
t.keys.length = t.size :=
Impl.length_keys h.out
@[simp]
theorem isEmpty_keys :
t.keys.isEmpty = t.isEmpty :=
Impl.isEmpty_keys
@[simp]
theorem contains_keys [BEq α] [LawfulBEqCmp cmp] (h : t.WF) [TransCmp cmp] {k : α} :
t.keys.contains k = t.contains k :=
Impl.contains_keys h
@[simp]
theorem mem_keys [LawfulEqCmp cmp] [TransCmp cmp] (h : t.WF) {k : α} :
k t.keys k t :=
Impl.mem_keys h
theorem distinct_keys [TransCmp cmp] (h : t.WF) :
t.keys.Pairwise (fun a b => ¬ cmp a b = .eq) :=
Impl.distinct_keys h.out
@[simp]
theorem map_fst_toList_eq_keys :
t.toList.map Sigma.fst = t.keys :=
Impl.map_fst_toList_eq_keys
@[simp]
theorem length_toList [TransCmp cmp] (h : t.WF) :
t.toList.length = t.size :=
Impl.length_toList h.out
@[simp]
theorem isEmpty_toList :
t.toList.isEmpty = t.isEmpty :=
Impl.isEmpty_toList
@[simp]
theorem mem_toList_iff_get?_eq_some [TransCmp cmp] [LawfulEqCmp cmp] (h : t.WF) {k : α} {v : β k} :
k, v t.toList t.get? k = some v :=
Impl.mem_toList_iff_get?_eq_some h.out
theorem find?_toList_eq_some_iff_get?_eq_some [TransCmp cmp] [LawfulEqCmp cmp] (h : t.WF) {k : α}
{v : β k} : t.toList.find? (cmp ·.1 k == .eq) = some k, v t.get? k = some v :=
Impl.find?_toList_eq_some_iff_get?_eq_some h.out
theorem find?_toList_eq_none_iff_contains_eq_false [TransCmp cmp] (h : t.WF) {k : α} :
t.toList.find? (cmp ·.1 k == .eq) = none t.contains k = false :=
Impl.find?_toList_eq_none_iff_contains_eq_false h.out
@[simp]
theorem find?_toList_eq_none_iff_not_mem [TransCmp cmp] (h : t.WF) {k : α} :
t.toList.find? (cmp ·.1 k == .eq) = none ¬ k t := by
simpa only [Bool.not_eq_true, mem_iff_contains] using find?_toList_eq_none_iff_contains_eq_false h
theorem distinct_keys_toList [TransCmp cmp] (h : t.WF) :
t.toList.Pairwise (fun a b => ¬ cmp a.1 b.1 = .eq) :=
Impl.distinct_keys_toList h.out
namespace Const
variable {β : Type v} {t : Raw α β cmp}
@[simp]
theorem map_fst_toList_eq_keys :
(toList t).map Prod.fst = t.keys :=
Impl.Const.map_fst_toList_eq_keys
@[simp]
theorem length_toList (h : t.WF) :
(toList t).length = t.size :=
Impl.Const.length_toList h.out
@[simp]
theorem isEmpty_toList :
(toList t).isEmpty = t.isEmpty :=
Impl.Const.isEmpty_toList
@[simp]
theorem mem_toList_iff_get?_eq_some [TransCmp cmp] [LawfulEqCmp cmp] (h : t.WF) {k : α} {v : β} :
(k, v) toList t get? t k = some v :=
Impl.Const.mem_toList_iff_get?_eq_some h
@[simp]
theorem mem_toList_iff_getKey?_eq_some_and_get?_eq_some [TransCmp cmp] (h : t.WF) {k : α} {v : β} :
(k, v) toList t t.getKey? k = some k get? t k = some v :=
Impl.Const.mem_toList_iff_getKey?_eq_some_and_get?_eq_some h
theorem get?_eq_some_iff_exists_compare_eq_eq_and_mem_toList [TransCmp cmp] (h : t.WF) {k : α} {v : β} :
get? t k = some v (k' : α), cmp k k' = .eq (k', v) toList t :=
Impl.Const.get?_eq_some_iff_exists_compare_eq_eq_and_mem_toList h
theorem find?_toList_eq_some_iff_getKey?_eq_some_and_get?_eq_some [TransCmp cmp] (h : t.WF)
{k k' : α} {v : β} :
(toList t).find? (fun a => cmp a.1 k = .eq) = some k', v
t.getKey? k = some k' get? t k = some v :=
Impl.Const.find?_toList_eq_some_iff_getKey?_eq_some_and_get?_eq_some h.out
theorem find?_toList_eq_none_iff_contains_eq_false [TransCmp cmp] (h : t.WF) {k : α} :
(toList t).find? (cmp ·.1 k == .eq) = none t.contains k = false :=
Impl.Const.find?_toList_eq_none_iff_contains_eq_false h.out
@[simp]
theorem find?_toList_eq_none_iff_not_mem [TransCmp cmp] (h : t.WF) {k : α} :
(toList t).find? (cmp ·.1 k == .eq) = none ¬ k t :=
Impl.Const.find?_toList_eq_none_iff_not_mem h.out
theorem distinct_keys_toList [TransCmp cmp] (h : t.WF) :
(toList t).Pairwise (fun a b => ¬ cmp a.1 b.1 = .eq) :=
Impl.Const.distinct_keys_toList h.out
end Const
end Std.DTreeMap.Raw

View File

@@ -0,0 +1,157 @@
/-
Copyright (c) 2025 Lean FRO, LLC. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Paul Reichert
-/
prelude
import Std.Data.DTreeMap.Internal.Lemmas
import Std.Data.DTreeMap.Raw.AdditionalOperations
import Std.Data.DTreeMap.Raw.Basic
/-!
# Well-formedness proofs for raw dependent tree maps
This file contains well-formedness proofs about `Std.Data.DTreeMap.Raw.Basic`. Most of the lemmas require
`TransCmp cmp` for the comparison function `cmp`.
-/
set_option linter.missingDocs true
set_option autoImplicit false
open Std.DTreeMap.Internal
universe u v
namespace Std.DTreeMap.Raw.WF
variable {α : Type u} {β : α Type v} {cmp : α α Ordering} {t : Raw α β cmp}
private local instance : Coe (Type v) (α Type v) where coe γ := fun _ => γ
theorem empty : (empty : Raw α β cmp).WF :=
letI : Ord α := cmp; Impl.WF.empty
theorem emptyc : ( : Raw α β cmp).WF :=
empty
theorem erase [TransCmp cmp] {a} (h : t.WF) : WF (t.erase a) :=
h.out.erase!
theorem insert [TransCmp cmp] {a b} (h : t.WF) : WF (t.insert a b) :=
h.out.insert!
theorem insertIfNew [TransCmp cmp] {a b} (h : t.WF) : WF (t.insertIfNew a b) :=
h.out.insertIfNew!
theorem containsThenInsert [TransCmp cmp] {a b} (h : t.WF) : WF (t.containsThenInsert a b).2 :=
h.out.containsThenInsert!
theorem containsThenInsertIfNew [TransCmp cmp] {a b} (h : t.WF) : WF (t.containsThenInsertIfNew a b).2 :=
h.out.containsThenInsertIfNew!
theorem getThenInsertIfNew? [TransCmp cmp] [LawfulEqCmp cmp] {a b} (h : t.WF) :
WF (t.getThenInsertIfNew? a b).2 :=
h.out.getThenInsertIfNew?!
theorem filter [TransCmp cmp] {f} (h : t.WF) :
WF (t.filter f) :=
h.out.filter!
theorem filterMap [TransCmp cmp] {f : (a : α) β a Option (β a)} (h : t.WF) :
WF (t.filterMap f) :=
h.out.filterMap!
theorem partition_fst [TransCmp cmp] {f} :
WF (t.partition f).fst := by
rw [partition, foldl, Impl.foldl_eq_foldl, List.foldr_reverse]
induction t.inner.toListModel.reverse with
| nil => exact empty
| cons e es ih =>
simp only [List.foldr_cons]
split
· exact ih.insert
· exact ih
theorem partition_snd [TransCmp cmp] {f} :
WF (t.partition f).snd := by
rw [partition, foldl, Impl.foldl_eq_foldl, List.foldr_reverse]
induction t.inner.toListModel.reverse with
| nil => exact empty
| cons e es ih =>
simp only [List.foldr_cons]
split
· exact ih
· exact ih.insert
theorem eraseMany [TransCmp cmp] {ρ} [ForIn Id ρ α] {l : ρ} {t : Raw α β cmp}
(h : t.WF) : WF (t.eraseMany l) :=
h.out.eraseMany!
theorem insertMany [TransCmp cmp] {ρ} [ForIn Id ρ ((a : α) × β a)] {l : ρ} {t : Raw α β cmp}
(h : t.WF) : WF (t.insertMany l) :=
h.out.insertMany!
theorem ofList [TransCmp cmp] {l : List ((a : α) × β a)} :
(Raw.ofList l cmp).WF :=
letI : Ord α := cmp; Impl.WF.insertMany Impl.WF.empty
theorem ofArray [TransCmp cmp] {a : Array ((a : α) × β a)} :
(Raw.ofArray a cmp).WF :=
letI : Ord α := cmp; Impl.WF.insertMany Impl.WF.empty
theorem alter [LawfulEqCmp cmp] {a f} {t : Raw α β cmp} (h : t.WF) :
(t.alter a f).WF :=
h.out.alter! (t := t.inner) (a := a) (f := f)
theorem modify [LawfulEqCmp cmp] {a f} {t : Raw α β cmp} (h : t.WF) :
(t.modify a f).WF :=
letI : Ord α := cmp; h.out.modify
theorem mergeWith [LawfulEqCmp cmp] {mergeFn} {t₁ t₂ : Raw α β cmp} (h : t₁.WF) :
(t₁.mergeWith mergeFn t₂).WF :=
h.out.mergeWith!
section Const
variable {β : Type v} {t : Raw α β cmp}
theorem constGetThenInsertIfNew? [TransCmp cmp] {a b} (h : t.WF) :
WF (Raw.Const.getThenInsertIfNew? t a b).2 :=
h.out.constGetThenInsertIfNew?!
theorem constInsertMany [TransCmp cmp] {ρ} [ForIn Id ρ (α × β)] {l : ρ} {t : Raw α β cmp}
(h : t.WF) : WF (Const.insertMany t l) :=
h.out.constInsertMany!
theorem constInsertManyIfNewUnit [TransCmp cmp] {ρ} [ForIn Id ρ α] {l : ρ} {t : Raw α Unit cmp}
(h : t.WF) : WF (Const.insertManyIfNewUnit t l) :=
h.out.constInsertManyIfNewUnit!
theorem constOfList [TransCmp cmp] {l : List (α × β)} :
(Raw.Const.ofList l cmp).WF :=
letI : Ord α := cmp; Impl.WF.constInsertMany Impl.WF.empty
theorem constOfArray [TransCmp cmp] {a : Array (α × β)} :
(Raw.Const.ofArray a cmp).WF :=
letI : Ord α := cmp; Impl.WF.constInsertMany Impl.WF.empty
theorem unitOfList [TransCmp cmp] {l : List α} :
(Raw.Const.unitOfList l cmp).WF :=
letI : Ord α := cmp; Impl.WF.constInsertManyIfNewUnit Impl.WF.empty
theorem unitOfArray [TransCmp cmp] {a : Array α} :
(Raw.Const.unitOfArray a cmp).WF :=
letI : Ord α := cmp; Impl.WF.constInsertManyIfNewUnit Impl.WF.empty
theorem constAlter {a f} {t : Raw α β cmp} (h : t.WF) :
(Const.alter t a f).WF :=
h.out.constAlter!
theorem constModify {a f} {t : Raw α β cmp} (h : t.WF) :
(Const.modify t a f).WF :=
letI : Ord α := cmp; h.out.constModify
theorem constMergeWith {mergeFn} {t₁ t₂ : Raw α β cmp} (h : t₁.WF) :
(Const.mergeWith mergeFn t₁ t₂).WF :=
h.out.constMergeWith!
end Std.DTreeMap.Raw.WF.Const

View File

@@ -699,9 +699,14 @@ theorem distinct_keys [EquivBEq α] [LawfulHashable α] :
DHashMap.distinct_keys
@[simp]
theorem map_fst_toList_eq_keys [EquivBEq α] [LawfulHashable α] :
m.toList.map Prod.fst = m.keys :=
DHashMap.Const.map_fst_toList_eq_keys
@[simp, deprecated map_fst_toList_eq_keys (since := "2025-02-28")]
theorem map_prod_fst_toList_eq_keys [EquivBEq α] [LawfulHashable α] :
m.toList.map Prod.fst = m.keys :=
DHashMap.Const.map_prod_fst_toList_eq_keys
DHashMap.Const.map_fst_toList_eq_keys
@[simp]
theorem length_toList [EquivBEq α] [LawfulHashable α] :
@@ -733,7 +738,7 @@ theorem get?_eq_some_iff_exists_beq_and_mem_toList [EquivBEq α] [LawfulHashable
theorem find?_toList_eq_some_iff_getKey?_eq_some_and_getElem?_eq_some
[EquivBEq α] [LawfulHashable α] {k k' : α} {v : β} :
m.toList.find? (fun a => a.1 == k) = some k', v
m.getKey? k = some k' get? m k = some v :=
m.getKey? k = some k' m[k]? = some v :=
DHashMap.Const.find?_toList_eq_some_iff_getKey?_eq_some_and_get?_eq_some
theorem find?_toList_eq_none_iff_contains_eq_false [EquivBEq α] [LawfulHashable α]

View File

@@ -706,9 +706,14 @@ theorem distinct_keys [EquivBEq α] [LawfulHashable α] (h : m.WF) :
DHashMap.Raw.distinct_keys h.out
@[simp]
theorem map_fst_toList_eq_keys [EquivBEq α] [LawfulHashable α] (h : m.WF) :
m.toList.map Prod.fst = m.keys :=
DHashMap.Raw.Const.map_fst_toList_eq_keys h.out
@[simp, deprecated map_fst_toList_eq_keys (since := "2025-02-28")]
theorem map_prod_fst_toList_eq_keys [EquivBEq α] [LawfulHashable α] (h : m.WF) :
m.toList.map Prod.fst = m.keys :=
DHashMap.Raw.Const.map_prod_fst_toList_eq_keys h.out
DHashMap.Raw.Const.map_fst_toList_eq_keys h.out
@[simp]
theorem length_toList [EquivBEq α] [LawfulHashable α] (h : m.WF) :

View File

@@ -1997,7 +1997,7 @@ theorem pairwise_fst_eq_false [BEq α] {l : List ((a : α) × β a)} (h : Distin
rw [DistinctKeys.def] at h
assumption
theorem map_prod_fst_map_toProd_eq_keys {β : Type v} {l : List ((_ : α) × β)} :
theorem map_fst_map_toProd_eq_keys {β : Type v} {l : List ((_ : α) × β)} :
List.map Prod.fst (List.map (fun x => (x.fst, x.snd)) l) = List.keys l := by
induction l with
| nil => simp

View File

@@ -5,7 +5,7 @@ Authors: Paul Reichert
-/
prelude
import Std.Data.TreeMap.Basic
import Std.Data.TreeMap.Raw
import Std.Data.TreeMap.Raw.Basic
import Std.Data.DTreeMap.AdditionalOperations
/-!
@@ -24,22 +24,6 @@ variable {α : Type u} {β : Type v} {γ : Type w} {cmp : αα → Ordering
namespace Std.TreeMap
namespace Raw
@[inline, inherit_doc DTreeMap.Raw.filterMap]
def filterMap (f : (a : α) β Option γ) (t : Raw α β cmp) : Raw α γ cmp :=
t.inner.filterMap f
@[inline, inherit_doc DTreeMap.Raw.map]
def map (f : α β γ) (t : Raw α β cmp) : Raw α γ cmp :=
t.inner.map f
/-!
We do not provide `get*GE`, `get*GT`, `get*LE` and `get*LT` functions for the raw trees.
-/
end Raw
@[inline, inherit_doc DTreeMap.filterMap]
def filterMap (f : (a : α) β Option γ) (m : TreeMap α β cmp) : TreeMap α γ cmp :=
m.inner.filterMap f

View File

@@ -14,7 +14,7 @@ This file develops the type `Std.TreeMap` of tree maps.
Lemmas about the operations on `Std.TreeMap` will be available in the
module `Std.Data.TreeMap.Lemmas`.
See the module `Std.Data.TreeMap.Raw` for a variant of this type which is safe to use in
See the module `Std.Data.TreeMap.Raw.Basic` for a variant of this type which is safe to use in
nested inductive types.
-/
@@ -52,8 +52,8 @@ Internally, the tree maps are represented as size-bounded trees, a type of self-
search tree with efficient order statistic lookups.
These tree maps contain a bundled well-formedness invariant, which means that they cannot
be used in nested inductive types. For these use cases, `Std.Data.TreeMap.Raw` and
`Std.Data.TreeMap.Raw.WF` unbundle the invariant from the tree map. When in doubt, prefer
be used in nested inductive types. For these use cases, `Std.TreeMap.Raw` and
`Std.TreeMap.Raw.WF` unbundle the invariant from the tree map. When in doubt, prefer
`TreeMap` over `TreeMap.Raw`.
-/
structure TreeMap (α : Type u) (β : Type v) (cmp : α α Ordering := by exact compare) where
@@ -405,16 +405,16 @@ def fold (f : δ → (a : α) → β → δ) (init : δ) (t : TreeMap α β cmp)
t.foldl f init
@[inline, inherit_doc DTreeMap.foldrM]
def foldrM (f : δ (a : α) β m δ) (init : δ) (t : TreeMap α β cmp) : m δ :=
def foldrM (f : (a : α) β δ m δ) (init : δ) (t : TreeMap α β cmp) : m δ :=
t.inner.foldrM f init
@[inline, inherit_doc DTreeMap.foldr]
def foldr (f : δ (a : α) β δ) (init : δ) (t : TreeMap α β cmp) : δ :=
def foldr (f : (a : α) β δ δ) (init : δ) (t : TreeMap α β cmp) : δ :=
t.inner.foldr f init
@[inline, inherit_doc foldr, deprecated foldr (since := "2025-02-12")]
def revFold (f : δ (a : α) β δ) (init : δ) (t : TreeMap α β cmp) : δ :=
foldr f init t
foldr (fun k v acc => f acc k v) init t
@[inline, inherit_doc DTreeMap.partition]
def partition (f : (a : α) β Bool) (t : TreeMap α β cmp) : TreeMap α β cmp × TreeMap α β cmp :=

View File

@@ -655,4 +655,76 @@ theorem getThenInsertIfNew?_snd [TransCmp cmp] {k : α} {v : β} :
(getThenInsertIfNew? t k v).2 = t.insertIfNew k v :=
ext <| DTreeMap.Const.getThenInsertIfNew?_snd
@[simp]
theorem length_keys [TransCmp cmp] :
t.keys.length = t.size :=
DTreeMap.length_keys
@[simp]
theorem isEmpty_keys :
t.keys.isEmpty = t.isEmpty :=
DTreeMap.isEmpty_keys
@[simp]
theorem contains_keys [BEq α] [LawfulBEqCmp cmp] [TransCmp cmp] {k : α} :
t.keys.contains k = t.contains k :=
DTreeMap.contains_keys
@[simp]
theorem mem_keys [LawfulEqCmp cmp] [TransCmp cmp] {k : α} :
k t.keys k t :=
DTreeMap.mem_keys
theorem distinct_keys [TransCmp cmp] :
t.keys.Pairwise (fun a b => ¬ cmp a b = .eq) :=
DTreeMap.distinct_keys
@[simp]
theorem map_fst_toList_eq_keys :
(toList t).map Prod.fst = t.keys :=
DTreeMap.Const.map_fst_toList_eq_keys
@[simp]
theorem length_toList :
(toList t).length = t.size :=
DTreeMap.Const.length_toList
@[simp]
theorem isEmpty_toList :
(toList t).isEmpty = t.isEmpty :=
DTreeMap.Const.isEmpty_toList
@[simp]
theorem mem_toList_iff_getElem?_eq_some [TransCmp cmp] [LawfulEqCmp cmp] {k : α} {v : β} :
(k, v) toList t t[k]? = some v :=
DTreeMap.Const.mem_toList_iff_get?_eq_some
@[simp]
theorem mem_toList_iff_getKey?_eq_some_and_getElem?_eq_some [TransCmp cmp] {k : α} {v : β} :
(k, v) toList t t.getKey? k = some k t[k]? = some v :=
DTreeMap.Const.mem_toList_iff_getKey?_eq_some_and_get?_eq_some
theorem getElem?_eq_some_iff_exists_compare_eq_eq_and_mem_toList [TransCmp cmp] {k : α} {v : β} :
t[k]? = some v (k' : α), cmp k k' = .eq (k', v) toList t :=
DTreeMap.Const.get?_eq_some_iff_exists_compare_eq_eq_and_mem_toList
theorem find?_toList_eq_some_iff_getKey?_eq_some_and_getElem?_eq_some [TransCmp cmp] {k k' : α}
{v : β} :
t.toList.find? (cmp ·.1 k == .eq) = some k', v
t.getKey? k = some k' t[k]? = some v :=
DTreeMap.Const.find?_toList_eq_some_iff_getKey?_eq_some_and_get?_eq_some
theorem find?_toList_eq_none_iff_contains_eq_false [TransCmp cmp] {k : α} :
(toList t).find? (cmp ·.1 k == .eq) = none t.contains k = false :=
DTreeMap.Const.find?_toList_eq_none_iff_contains_eq_false
@[simp]
theorem find?_toList_eq_none_iff_not_mem [TransCmp cmp] {k : α} :
(toList t).find? (cmp ·.1 k == .eq) = none ¬ k t :=
DTreeMap.Const.find?_toList_eq_none_iff_not_mem
theorem distinct_keys_toList [TransCmp cmp] :
(toList t).Pairwise (fun a b => ¬ cmp a.1 b.1 = .eq) :=
DTreeMap.Const.distinct_keys_toList
end Std.TreeMap

View File

@@ -0,0 +1,39 @@
/-
Copyright (c) 2025 Lean FRO, LLC. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Paul Reichert
-/
prelude
import Std.Data.TreeMap.Basic
import Std.Data.TreeMap.Raw.Basic
import Std.Data.DTreeMap.Raw.AdditionalOperations
/-!
# Additional raw tree map operations
This file defines more operations on `Std.TreeMap.Raw`.
We currently do not provide lemmas for these functions.
-/
set_option autoImplicit false
set_option linter.missingDocs true
universe u v w
variable {α : Type u} {β : Type v} {γ : Type w} {cmp : α α Ordering}
namespace Std.TreeMap.Raw
@[inline, inherit_doc DTreeMap.Raw.filterMap]
def filterMap (f : (a : α) β Option γ) (t : Raw α β cmp) : Raw α γ cmp :=
t.inner.filterMap f
@[inline, inherit_doc DTreeMap.Raw.map]
def map (f : α β γ) (t : Raw α β cmp) : Raw α γ cmp :=
t.inner.map f
/-!
We do not provide `get*GE`, `get*GT`, `get*LE` and `get*LT` functions for the raw trees.
-/
end Std.TreeMap.Raw

View File

@@ -4,7 +4,7 @@ Released under Apache 2.0 license as described in the file LICENSE.
Authors: Markus Himmel, Paul Reichert
-/
prelude
import Std.Data.DTreeMap.Raw
import Std.Data.DTreeMap.Raw.Basic
/-
# Tree maps with unbundled well-formedness invariant
@@ -17,7 +17,7 @@ available as `Std.TreeMap.Raw.WF` and we prove in this file that all operations
well-formedness. When in doubt, prefer `TreeMap` over `TreeMap.Raw`.
Lemmas about the operations on `Std.TreeMap.Raw` will be available in the module
`Std.Data.TreeMap.RawLemmas`.
`Std.Data.TreeMap.Raw.Lemmas`.
-/
set_option autoImplicit false
@@ -35,7 +35,7 @@ namespace TreeMap
Tree maps without a bundled well-formedness invariant, suitable for use in nested
inductive types. The well-formedness invariant is called `Raw.WF`. When in doubt, prefer `TreeMap`
over `TreeMap.Raw`. Lemmas about the operations on `Std.TreeMap.Raw` are available in the
module `Std.Data.TreeMap.RawLemmas`.
module `Std.Data.TreeMap.Raw.Lemmas`.
A tree map stores an assignment of keys to values. It depends on a comparator function that
defines an ordering on the keys and provides efficient order-dependent queries, such as retrieval
@@ -411,16 +411,16 @@ def fold (f : δ → (a : α) → β → δ) (init : δ) (t : Raw α β cmp) :
t.foldl f init
@[inline, inherit_doc DTreeMap.Raw.foldrM]
def foldrM (f : δ (a : α) β m δ) (init : δ) (t : Raw α β cmp) : m δ :=
def foldrM (f : (a : α) β δ m δ) (init : δ) (t : Raw α β cmp) : m δ :=
t.inner.foldrM f init
@[inline, inherit_doc DTreeMap.Raw.foldr]
def foldr (f : δ (a : α) β δ) (init : δ) (t : Raw α β cmp) : δ :=
def foldr (f : (a : α) β δ δ) (init : δ) (t : Raw α β cmp) : δ :=
t.inner.foldr f init
@[inline, inherit_doc foldr, deprecated foldr (since := "2025-02-12")]
def revFold (f : δ (a : α) β δ) (init : δ) (t : Raw α β cmp) : δ :=
foldr f init t
foldr (fun k v acc => f acc k v) init t
@[inline, inherit_doc DTreeMap.Raw.partition]
def partition (f : (a : α) β Bool) (t : Raw α β cmp) : Raw α β cmp × Raw α β cmp :=

View File

@@ -4,14 +4,15 @@ Released under Apache 2.0 license as described in the file LICENSE.
Authors: Markus Himmel, Paul Reichert
-/
prelude
import Std.Data.DTreeMap.RawLemmas
import Std.Data.TreeMap.Raw
import Std.Data.DTreeMap.Raw.Lemmas
import Std.Data.TreeMap.Raw.Basic
/-!
# Tree map lemmas
This file contains lemmas about `Std.Data.TreeMap.Raw`. Most of the lemmas require
This file contains lemmas about `Std.Data.TreeMap.Raw.Basic`. Most of the lemmas require
`TransCmp cmp` for the comparison function `cmp`.
These proofs can be obtained from `Std.Data.TreeMap.Raw.WF`.
-/
set_option linter.missingDocs true
@@ -661,4 +662,77 @@ theorem getThenInsertIfNew?_snd [TransCmp cmp] (h : t.WF) {k : α} {v : β} :
(getThenInsertIfNew? t k v).2 = t.insertIfNew k v :=
ext <| DTreeMap.Raw.Const.getThenInsertIfNew?_snd h
@[simp]
theorem length_keys [TransCmp cmp] (h : t.WF) :
t.keys.length = t.size :=
DTreeMap.Raw.length_keys h
@[simp]
theorem isEmpty_keys :
t.keys.isEmpty = t.isEmpty :=
DTreeMap.Raw.isEmpty_keys
@[simp]
theorem contains_keys [BEq α] [LawfulBEqCmp cmp] [TransCmp cmp] (h : t.WF) {k : α} :
t.keys.contains k = t.contains k :=
DTreeMap.Raw.contains_keys h
@[simp]
theorem mem_keys [LawfulEqCmp cmp] [TransCmp cmp] (h : t.WF) {k : α} :
k t.keys k t :=
DTreeMap.Raw.mem_keys h
theorem distinct_keys [TransCmp cmp] (h : t.WF) :
t.keys.Pairwise (fun a b => ¬ cmp a b = .eq) :=
DTreeMap.Raw.distinct_keys h
@[simp]
theorem map_fst_toList_eq_keys :
(toList t).map Prod.fst = t.keys :=
DTreeMap.Raw.Const.map_fst_toList_eq_keys
@[simp]
theorem length_toList (h : t.WF) :
(toList t).length = t.size :=
DTreeMap.Raw.Const.length_toList h
@[simp]
theorem isEmpty_toList :
(toList t).isEmpty = t.isEmpty :=
DTreeMap.Raw.Const.isEmpty_toList
@[simp]
theorem mem_toList_iff_getElem?_eq_some [TransCmp cmp] [LawfulEqCmp cmp] (h : t.WF) {k : α} {v : β} :
(k, v) toList t t[k]? = some v :=
DTreeMap.Raw.Const.mem_toList_iff_get?_eq_some h
@[simp]
theorem mem_toList_iff_getKey?_eq_some_and_getElem?_eq_some [TransCmp cmp] (h : t.WF) {k : α} {v : β} :
(k, v) toList t t.getKey? k = some k t[k]? = some v :=
DTreeMap.Raw.Const.mem_toList_iff_getKey?_eq_some_and_get?_eq_some h
theorem getElem?_eq_some_iff_exists_compare_eq_eq_and_mem_toList [TransCmp cmp] (h : t.WF) {k : α}
{v : β} :
t[k]? = some v (k' : α), cmp k k' = .eq (k', v) toList t :=
DTreeMap.Raw.Const.get?_eq_some_iff_exists_compare_eq_eq_and_mem_toList h
theorem find?_toList_eq_some_iff_getKey?_eq_some_and_getElem?_eq_some [TransCmp cmp] (h : t.WF)
{k k' : α} {v : β} :
t.toList.find? (cmp ·.1 k == .eq) = some k', v
t.getKey? k = some k' t[k]? = some v :=
DTreeMap.Raw.Const.find?_toList_eq_some_iff_getKey?_eq_some_and_get?_eq_some h
theorem find?_toList_eq_none_iff_contains_eq_false [TransCmp cmp] (h : t.WF) {k : α} :
(toList t).find? (cmp ·.1 k == .eq) = none t.contains k = false :=
DTreeMap.Raw.Const.find?_toList_eq_none_iff_contains_eq_false h
@[simp]
theorem find?_toList_eq_none_iff_not_mem [TransCmp cmp] (h : t.WF) {k : α} :
(toList t).find? (cmp ·.1 k == .eq) = none ¬ k t :=
DTreeMap.Raw.Const.find?_toList_eq_none_iff_not_mem h
theorem distinct_keys_toList [TransCmp cmp] (h : t.WF) :
(toList t).Pairwise (fun a b => ¬ cmp a.1 b.1 = .eq) :=
DTreeMap.Raw.Const.distinct_keys_toList h
end Std.TreeMap.Raw

View File

@@ -0,0 +1,115 @@
/-
Copyright (c) 2025 Lean FRO, LLC. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Paul Reichert
-/
prelude
import Std.Data.DTreeMap.Raw.WF
import Std.Data.TreeMap.Raw.AdditionalOperations
import Std.Data.TreeMap.Raw.Basic
/-!
# Well-formedness proofs for raw tree maps
This file contains well-formedness proofs about `Std.Data.TreeMap.Raw.Basic`. Most of the lemmas require
`TransCmp cmp` for the comparison function `cmp`.
-/
set_option linter.missingDocs true
set_option autoImplicit false
universe u v
namespace Std.TreeMap.Raw.WF
open DTreeMap.Raw renaming WF InnerWF
variable {α : Type u} {β : Type v} {cmp : α α Ordering} {t : Raw α β cmp}
theorem empty : (empty : Raw α β cmp).WF :=
InnerWF.empty
theorem emptyc : ( : Raw α β cmp).WF :=
empty
theorem erase [TransCmp cmp] {a} (h : t.WF) :
WF (t.erase a) :=
InnerWF.erase h
theorem insert [TransCmp cmp] {a b} (h : t.WF) :
WF (t.insert a b) :=
InnerWF.insert h
theorem insertIfNew [TransCmp cmp] {a b} (h : t.WF) :
WF (t.insertIfNew a b) :=
InnerWF.insertIfNew h
theorem containsThenInsert [TransCmp cmp] {a b} (h : t.WF) :
WF (t.containsThenInsert a b).2 :=
InnerWF.containsThenInsert h
theorem containsThenInsertIfNew [TransCmp cmp] {a b} (h : t.WF) :
WF (t.containsThenInsertIfNew a b).2 :=
InnerWF.containsThenInsertIfNew h
theorem getThenInsertIfNew? [TransCmp cmp] {a b} (h : t.WF) :
WF (t.getThenInsertIfNew? a b).2 :=
InnerWF.constGetThenInsertIfNew? h
theorem filter [TransCmp cmp] {f} (h : t.WF) :
WF (t.filter f) :=
InnerWF.filter h
theorem filterMap [TransCmp cmp] {f : α β Option β} (h : t.WF) :
WF (t.filterMap f) :=
InnerWF.filterMap h
theorem partition_fst [TransCmp cmp] {f} :
WF (t.partition f).fst :=
InnerWF.partition_fst
theorem partition_snd [TransCmp cmp] {f} :
WF (t.partition f).snd :=
InnerWF.partition_snd
theorem eraseMany [TransCmp cmp] {ρ} [ForIn Id ρ α] {l : ρ} {t : Raw α β cmp} (h : t.WF) :
WF (t.eraseMany l) :=
InnerWF.eraseMany h
theorem insertMany [TransCmp cmp] {ρ} [ForIn Id ρ (α × β)] {l : ρ} {t : Raw α β cmp}
(h : t.WF) : WF (t.insertMany l) :=
InnerWF.constInsertMany h
theorem insertManyIfNewUnit [TransCmp cmp] {ρ} [ForIn Id ρ α] {l : ρ} {t : Raw α Unit cmp}
(h : t.WF) : WF (t.insertManyIfNewUnit l) :=
InnerWF.constInsertManyIfNewUnit h
theorem ofList [TransCmp cmp] {l : List (α × β)} :
(Raw.ofList l cmp).WF :=
InnerWF.constOfList
theorem ofArray [TransCmp cmp] {a : Array (α × β)} :
(Raw.ofArray a cmp).WF :=
InnerWF.constOfArray
theorem alter {a f} {t : Raw α β cmp} (h : t.WF) :
(t.alter a f).WF :=
InnerWF.constAlter h
theorem modify {a f} {t : Raw α β cmp} (h : t.WF) :
(t.modify a f).WF :=
InnerWF.constModify h
theorem unitOfList [TransCmp cmp] {l : List α} :
(Raw.unitOfList l cmp).WF :=
InnerWF.unitOfList
theorem unitOfArray [TransCmp cmp] {a : Array α} :
(Raw.unitOfArray a cmp).WF :=
InnerWF.unitOfArray
theorem mergeWith {mergeFn} {t₁ t₂ : Raw α β cmp} (h : t₁.WF) :
(t₁.mergeWith mergeFn t₂).WF :=
InnerWF.constMergeWith h
end Std.TreeMap.Raw.WF

View File

@@ -5,7 +5,7 @@ Authors: Paul Reichert
-/
prelude
import Std.Data.TreeSet.Basic
import Std.Data.TreeSet.Raw
import Std.Data.TreeSet.Raw.Basic
import Std.Data.TreeMap.AdditionalOperations
/-!

View File

@@ -14,7 +14,7 @@ This file develops the type `Std.TreeSet` of tree sets.
Lemmas about the operations on `Std.Data.TreeSet` will be available in the
module `Std.Data.TreeSet.Lemmas`.
See the module `Std.Data.TreeSet.Raw` for a variant of this type which is safe to use in
See the module `Std.Data.TreeSet.Raw.Basic` for a variant of this type which is safe to use in
nested inductive types.
-/
@@ -51,8 +51,8 @@ Internally, the tree sets are represented as size-bounded trees, a type of self-
search tree with efficient order statistic lookups.
These tree sets contain a bundled well-formedness invariant, which means that they cannot
be used in nested inductive types. For these use cases, `Std.Data.TreeSet.Raw` and
`Std.Data.TreeSet.Raw.WF` unbundle the invariant from the tree set. When in doubt, prefer
be used in nested inductive types. For these use cases, `Std.TreeSet.Raw` and
`Std.TreeSet.Raw.WF` unbundle the invariant from the tree set. When in doubt, prefer
`TreeSet` over `TreeSet.Raw`.
-/
structure TreeSet (α : Type u) (cmp : α α Ordering := by exact compare) where
@@ -388,17 +388,17 @@ Monadically computes a value by folding the given function over the elements in
descending order.
-/
@[inline]
def foldrM {m δ} [Monad m] (f : δ (a : α) m δ) (init : δ) (t : TreeSet α cmp) : m δ :=
t.inner.foldrM (fun c a _ => f c a) init
def foldrM {m δ} [Monad m] (f : (a : α) δ m δ) (init : δ) (t : TreeSet α cmp) : m δ :=
t.inner.foldrM (fun a _ acc => f a acc) init
/-- Folds the given function over the elements of the tree set in descending order. -/
@[inline]
def foldr (f : δ (a : α) δ) (init : δ) (t : TreeSet α cmp) : δ :=
t.inner.foldr (fun c a _ => f c a) init
def foldr (f : (a : α) δ δ) (init : δ) (t : TreeSet α cmp) : δ :=
t.inner.foldr (fun a _ acc => f a acc) init
@[inline, inherit_doc foldr, deprecated foldr (since := "2025-02-12")]
def revFold (f : δ (a : α) δ) (init : δ) (t : TreeSet α cmp) : δ :=
foldr f init t
foldr (fun a acc => f acc a) init t
/-- Partitions a tree set into two tree sets based on a predicate. -/
@[inline]
@@ -437,7 +437,7 @@ def all (t : TreeSet α cmp) (p : α → Bool) : Bool :=
/-- Transforms the tree set into a list of elements in ascending order. -/
@[inline]
def toList (t : TreeSet α cmp) : List α :=
t.inner.inner.inner.foldr (fun l a _ => a :: l)
t.inner.inner.inner.foldr (fun a _ l => a :: l)
/-- Transforms a list into a tree set. -/
def ofList (l : List α) (cmp : α α Ordering := by exact compare) : TreeSet α cmp :=

View File

@@ -334,4 +334,28 @@ theorem containsThenInsert_snd [TransCmp cmp] {k : α} :
(t.containsThenInsert k).2 = t.insert k :=
ext <| TreeMap.containsThenInsertIfNew_snd
@[simp]
theorem length_toList [TransCmp cmp] :
t.toList.length = t.size :=
DTreeMap.length_keys
@[simp]
theorem isEmpty_toList :
t.toList.isEmpty = t.isEmpty :=
DTreeMap.isEmpty_keys
@[simp]
theorem contains_toList [BEq α] [LawfulBEqCmp cmp] [TransCmp cmp] {k : α} :
t.toList.contains k = t.contains k :=
DTreeMap.contains_keys
@[simp]
theorem mem_toList [LawfulEqCmp cmp] [TransCmp cmp] {k : α} :
k t.toList k t :=
DTreeMap.mem_keys
theorem distinct_toList [TransCmp cmp] :
t.toList.Pairwise (fun a b => ¬ cmp a b = .eq) :=
DTreeMap.distinct_keys
end Std.TreeSet

View File

@@ -4,7 +4,7 @@ Released under Apache 2.0 license as described in the file LICENSE.
Authors: Markus Himmel, Paul Reichert
-/
prelude
import Std.Data.TreeMap.Raw
import Std.Data.TreeMap.Raw.Basic
import Std.Data.TreeSet.Basic
/-
@@ -18,7 +18,7 @@ available as `Std.TreeSet.Raw.WF` and we prove in this file that all operations
well-formedness. When in doubt, prefer `TreeSet` over `TreeSet.Raw`.
Lemmas about the operations on `Std.TreeSet.Raw` will be available in the module
`Std.Data.TreeSet.RawLemmas`.
`Std.Data.TreeSet.Raw.Lemmas`.
-/
set_option autoImplicit false
@@ -36,7 +36,7 @@ namespace TreeSet
Tree sets without a bundled well-formedness invariant, suitable for use in nested
inductive types. The well-formedness invariant is called `Raw.WF`. When in doubt, prefer `TreeSet`
over `TreeSet.Raw`. Lemmas about the operations on `Std.TreeSet.Raw` are available in the
module `Std.Data.TreeSet.RawLemmas`.
module `Std.Data.TreeSet.Raw.Lemmas`.
A tree set stores elements of a certain type in a certain order. It depends on a comparator function
that defines an ordering on the keys and provides efficient order-dependent queries, such as
@@ -272,16 +272,16 @@ def fold (f : δ → (a : α) → δ) (init : δ) (t : Raw α cmp) : δ :=
t.foldl f init
@[inline, inherit_doc TreeSet.empty]
def foldrM (f : δ (a : α) m δ) (init : δ) (t : Raw α cmp) : m δ :=
t.inner.foldrM (fun c a _ => f c a) init
def foldrM (f : (a : α) δ m δ) (init : δ) (t : Raw α cmp) : m δ :=
t.inner.foldrM (fun a _ acc => f a acc) init
@[inline, inherit_doc TreeSet.empty]
def foldr (f : δ (a : α) δ) (init : δ) (t : Raw α cmp) : δ :=
t.inner.foldr (fun c a _ => f c a) init
def foldr (f : (a : α) δ δ) (init : δ) (t : Raw α cmp) : δ :=
t.inner.foldr (fun a _ acc => f a acc) init
@[inline, inherit_doc foldr, deprecated foldr (since := "2025-02-12")]
def revFold (f : δ (a : α) δ) (init : δ) (t : Raw α cmp) : δ :=
foldr f init t
foldr (fun a acc => f acc a) init t
@[inline, inherit_doc TreeSet.partition]
def partition (f : (a : α) Bool) (t : Raw α cmp) : Raw α cmp × Raw α cmp :=
@@ -311,7 +311,7 @@ def all (t : Raw α cmp) (p : α → Bool) : Bool :=
@[inline, inherit_doc TreeSet.empty]
def toList (t : Raw α cmp) : List α :=
t.inner.inner.inner.foldr (fun l a _ => a :: l)
t.inner.inner.inner.foldr (fun a _ l => a :: l)
@[inline, inherit_doc TreeSet.ofList]
def ofList (l : List α) (cmp : α α Ordering := by exact compare) : Raw α cmp :=

View File

@@ -4,14 +4,15 @@ Released under Apache 2.0 license as described in the file LICENSE.
Authors: Markus Himmel, Paul Reichert
-/
prelude
import Std.Data.TreeMap.RawLemmas
import Std.Data.TreeSet.Raw
import Std.Data.TreeMap.Raw.Lemmas
import Std.Data.TreeSet.Raw.Basic
/-!
# Tree set lemmas
This file contains lemmas about `Std.Data.TreeSet.Raw`. Most of the lemmas require
This file contains lemmas about `Std.Data.TreeSet.Raw.Basic`. Most of the lemmas require
`TransCmp cmp` for the comparison function `cmp`.
These proofs can be obtained from `Std.Data.TreeSet.Raw.WF`.
-/
set_option linter.missingDocs true
@@ -334,4 +335,28 @@ theorem containsThenInsert_snd [TransCmp cmp] (h : t.WF) {k : α} :
(t.containsThenInsert k).2 = t.insert k :=
ext <| TreeMap.Raw.containsThenInsertIfNew_snd h
@[simp]
theorem length_toList [TransCmp cmp] (h : t.WF) :
t.toList.length = t.size :=
DTreeMap.Raw.length_keys h
@[simp]
theorem isEmpty_toList :
t.toList.isEmpty = t.isEmpty :=
DTreeMap.Raw.isEmpty_keys
@[simp]
theorem contains_toList [BEq α] [LawfulBEqCmp cmp] [TransCmp cmp] (h : t.WF) {k : α} :
t.toList.contains k = t.contains k :=
DTreeMap.Raw.contains_keys h
@[simp]
theorem mem_toList [LawfulEqCmp cmp] [TransCmp cmp] (h : t.WF) {k : α} :
k t.toList k t :=
DTreeMap.Raw.mem_keys h
theorem distinct_toList [TransCmp cmp] (h : t.WF) :
t.toList.Pairwise (fun a b => ¬ cmp a b = .eq) :=
DTreeMap.Raw.distinct_keys h
end Std.TreeSet.Raw

View File

@@ -0,0 +1,79 @@
/-
Copyright (c) 2025 Lean FRO, LLC. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Paul Reichert
-/
prelude
import Std.Data.TreeMap.Raw.WF
import Std.Data.TreeSet.AdditionalOperations
import Std.Data.TreeSet.Raw.Basic
/-!
# Well-formedness proofs for raw tree sets
This file contains well-formedness proofs about `Std.Data.TreeSet.Raw.Basic`. Most of the lemmas require
`TransCmp cmp` for the comparison function `cmp`.
-/
set_option linter.missingDocs true
set_option autoImplicit false
universe u v
namespace Std.TreeSet.Raw.WF
open TreeMap.Raw renaming WF InnerWF
variable {α : Type u} {cmp : α α Ordering} {t : Raw α cmp}
theorem empty : (empty : Raw α cmp).WF :=
InnerWF.empty
theorem emptyc : ( : Raw α cmp).WF :=
empty
theorem erase [TransCmp cmp] {a} (h : t.WF) :
WF (t.erase a) :=
InnerWF.erase h
theorem insert [TransCmp cmp] {a} (h : t.WF) :
WF (t.insert a) :=
InnerWF.insertIfNew h
theorem containsThenInsert [TransCmp cmp] {a} (h : t.WF) :
WF (t.containsThenInsert a).2 :=
InnerWF.containsThenInsertIfNew h
theorem filter [TransCmp cmp] {f} (h : t.WF) :
WF (t.filter f) :=
InnerWF.filter h
theorem partition_fst [TransCmp cmp] {f} :
WF (t.partition f).fst :=
InnerWF.partition_fst
theorem partition_snd [TransCmp cmp] {f} :
WF (t.partition f).snd :=
InnerWF.partition_snd
theorem eraseMany [TransCmp cmp] {ρ} [ForIn Id ρ α] {l : ρ} {t : Raw α cmp} (h : t.WF) :
WF (t.eraseMany l) :=
InnerWF.eraseMany h
theorem insertMany [TransCmp cmp] {ρ} [ForIn Id ρ α] {l : ρ} {t : Raw α cmp}
(h : t.WF) : WF (t.insertMany l) :=
InnerWF.insertManyIfNewUnit h
theorem ofList [TransCmp cmp] {l : List α} :
(Raw.ofList l cmp).WF :=
InnerWF.unitOfList
theorem ofArray [TransCmp cmp] {a : Array α} :
(Raw.ofArray a cmp).WF :=
InnerWF.unitOfArray
theorem merge {t₁ t₂ : Raw α cmp} (h : t₁.WF) :
(t₁.merge t₂).WF :=
InnerWF.mergeWith h
end Std.TreeSet.Raw.WF

View File

@@ -7,6 +7,7 @@ prelude
import Init.NotationExtra
import Init.Data.ToString.Macro
import Init.Data.Int.DivMod.Basic
import Init.Data.Int.Linear
import Init.Data.Nat.Gcd
namespace Std
namespace Internal
@@ -101,15 +102,13 @@ protected def floor (a : Rat) : Int :=
if a.den == 1 then
a.num
else
let r := a.num.tmod a.den
if a.num < 0 then r - 1 else r
a.num / a.den
protected def ceil (a : Rat) : Int :=
if a.den == 1 then
a.num
else
let r := a.num.tmod a.den
if a.num > 0 then r + 1 else r
Int.Linear.cdiv a.num a.den
instance : LT Rat where
lt a b := (Rat.lt a b) = true

View File

@@ -6,6 +6,7 @@ Authors: Henrik Böving
prelude
import Init.SimpLemmas
import Init.Data.Bool
import Init.Data.BitVec.Lemmas
/-!
This module contains the `Bool` simplifying part of the `bv_normalize` simp set.
@@ -52,7 +53,151 @@ theorem if_eq_cond {b : Bool} {x y : α} : (if b = true then x else y) = (bif b
theorem Bool.not_xor : (a b : Bool), !(a ^^ b) = (a == b) := by decide
@[bv_normalize]
theorem Bool.or_elim : (a b : Bool), (a || b) = !(!a && !b) := by decide
theorem Bool.not_beq_one : (a : BitVec 1), (!(a == 1#1)) = (a == 0#1) := by
decide
@[bv_normalize]
theorem Bool.not_beq_zero : (a : BitVec 1), (!(a == 0#1)) = (a == 1#1) := by
decide
@[bv_normalize]
theorem Bool.not_one_beq : (a : BitVec 1), (!(1#1 == a)) = (a == 0#1) := by
decide
@[bv_normalize]
theorem Bool.not_zero_beq : (a : BitVec 1), (!(0#1 == a)) = (a == 1#1) := by
decide
@[bv_normalize]
theorem Bool.ite_same_then : (c t e : Bool), ((bif c then t else e) == t) = (c || (t == e)) := by
decide
@[bv_normalize]
theorem Bool.ite_same_then' : (c t e : Bool), (t == (bif c then t else e)) = (c || (t == e)) := by
decide
@[bv_normalize]
theorem Bool.ite_same_else : (c t e : Bool), ((bif c then t else e) == e) = (!c || (t == e)) := by
decide
@[bv_normalize]
theorem Bool.ite_same_else' :
(c t e : Bool), (e == (bif c then t else e)) = (!c || (t == e)) := by
decide
@[bv_normalize]
theorem BitVec.ite_same_then :
(c : Bool) (t e : BitVec w), ((bif c then t else e) == t) = (c || (t == e)) := by
intro c t e
cases c <;> simp [BEq.comm (a := t) (b := e)]
@[bv_normalize]
theorem BitVec.ite_same_then' :
(c : Bool) (t e : BitVec w), (t == (bif c then t else e)) = (c || (t == e)) := by
intro c t e
cases c <;> simp
@[bv_normalize]
theorem BitVec.ite_same_else :
(c : Bool) (t e : BitVec w), ((bif c then t else e) == e) = (!c || (t == e)) := by
intro c t e
cases c <;> simp
@[bv_normalize]
theorem BitVec.ite_same_else' :
(c : Bool) (t e : BitVec w), (e == (bif c then t else e)) = (!c || (t == e)) := by
intro c t e
cases c <;> simp [BEq.comm (a := t) (b := e)]
@[bv_normalize]
theorem Bool.ite_then_ite (cond : Bool) {a b c : α} :
(bif cond then (bif cond then a else b) else c) = (bif cond then a else c) := by
cases cond <;> simp
@[bv_normalize]
theorem Bool.ite_then_not_ite (cond : Bool) {a b c : Bool} :
(bif cond then !(bif cond then a else b) else c) = (bif cond then !a else c) := by
cases cond <;> simp
@[bv_normalize]
theorem BitVec.ite_then_not_ite (cond : Bool) {a b c : BitVec w} :
(bif cond then ~~~(bif cond then a else b) else c) = (bif cond then ~~~a else c) := by
cases cond <;> simp
@[bv_normalize]
theorem Bool.ite_else_ite (cond : Bool) {a b c : α} :
(bif cond then a else (bif cond then b else c)) = (bif cond then a else c) := by
cases cond <;> simp
@[bv_normalize]
theorem Bool.ite_else_not_ite (cond : Bool) {a b c : Bool} :
(bif cond then a else !(bif cond then b else c)) = (bif cond then a else !c) := by
cases cond <;> simp
@[bv_normalize]
theorem BitVec.ite_else_not_ite (cond : Bool) {a b c : BitVec w} :
(bif cond then a else ~~~(bif cond then b else c)) = (bif cond then a else ~~~c) := by
cases cond <;> simp
@[bv_normalize]
theorem Bool.ite_then_ite' (c0 c1 : Bool) {a b : α} :
(bif c0 then (bif c1 then a else b) else a) = (bif c0 && !c1 then b else a) := by
cases c0 <;> cases c1 <;> simp
@[bv_normalize]
theorem Bool.ite_then_not_ite' (c0 c1 : Bool) {a b : Bool} :
(bif c0 then !(bif c1 then !a else b) else a) = (bif c0 && !c1 then !b else a) := by
cases c0 <;> cases c1 <;> simp
@[bv_normalize]
theorem BitVec.ite_then_not_ite' (c0 c1 : Bool) {a b : BitVec w} :
(bif c0 then ~~~(bif c1 then ~~~a else b) else a) = (bif c0 && !c1 then ~~~b else a) := by
cases c0 <;> cases c1 <;> simp
@[bv_normalize]
theorem Bool.ite_else_ite' (c0 c1 : Bool) {a b : α} :
(bif c0 then a else (bif c1 then a else b)) = (bif !c0 && !c1 then b else a) := by
cases c0 <;> cases c1 <;> simp
@[bv_normalize]
theorem Bool.ite_else_not_ite' (c0 c1 : Bool) {a b : Bool} :
(bif c0 then a else !(bif c1 then !a else b)) = (bif !c0 && !c1 then !b else a) := by
cases c0 <;> cases c1 <;> simp
@[bv_normalize]
theorem BitVec.ite_else_not_ite' (c0 c1 : Bool) {a b : BitVec w} :
(bif c0 then a else ~~~(bif c1 then ~~~a else b)) = (bif !c0 && !c1 then ~~~b else a) := by
cases c0 <;> cases c1 <;> simp
@[bv_normalize]
theorem Bool.ite_then_ite'' (c0 c1 : Bool) {a b : α} :
(bif c0 then (bif c1 then b else a) else a) = (bif c0 && c1 then b else a) := by
cases c0 <;> cases c1 <;> simp
@[bv_normalize]
theorem Bool.ite_then_not_ite'' (c0 c1 : Bool) {a b : Bool} :
(bif c0 then !(bif c1 then b else !a) else a) = (bif c0 && c1 then !b else a) := by
cases c0 <;> cases c1 <;> simp
@[bv_normalize]
theorem BitVec.ite_then_not_ite'' (c0 c1 : Bool) {a b : BitVec w} :
(bif c0 then ~~~(bif c1 then b else ~~~a) else a) = (bif c0 && c1 then ~~~b else a) := by
cases c0 <;> cases c1 <;> simp
@[bv_normalize]
theorem Bool.ite_else_ite'' (c0 c1 : Bool) {a b : α} :
(bif c0 then a else (bif c1 then b else a)) = (bif !c0 && c1 then b else a) := by
cases c0 <;> cases c1 <;> simp
@[bv_normalize]
theorem Bool.ite_else_not_ite'' (c0 c1 : Bool) {a b : Bool} :
(bif c0 then a else !(bif c1 then b else !a)) = (bif !c0 && c1 then !b else a) := by
cases c0 <;> cases c1 <;> simp
@[bv_normalize]
theorem BitVec.ite_else_not_ite'' (c0 c1 : Bool) {a b : BitVec w} :
(bif c0 then a else ~~~(bif c1 then b else ~~~a )) = (bif !c0 && c1 then ~~~b else a) := by
cases c0 <;> cases c1 <;> simp
theorem Bool.and_left (lhs rhs : Bool) (h : (lhs && rhs) = true) : lhs = true := by
revert lhs rhs

View File

@@ -54,9 +54,8 @@ theorem Bool.and_to_and (a b : Bool) : ((a = true) ∧ (b = true)) = ((a && b) =
simp
@[bv_normalize]
theorem Bool.iff_to_or (a b : Bool)
: ((a = true) (b = true)) = (((!a || b) && (!b || a)) = true) := by
revert a b
theorem Bool.iff_to_beq :
(a b : Bool), ((a = true) (b = true)) = ((a == b) = true) := by
decide
@[bv_normalize]
@@ -67,10 +66,6 @@ theorem Bool.eq_false (a : Bool) : ((a = true) = False) = ((!a) = true) := by
theorem Bool.decide_eq_true (a : Bool) : (decide (a = true)) = a := by
simp
@[bv_normalize]
theorem Bool.eq_true_eq_true_eq (x y : Bool) : ((x = true) = (y = true)) ((x == y) = true) := by
simp
attribute [bv_normalize] BitVec.getLsbD_cast
attribute [bv_normalize] BitVec.testBit_toNat
@@ -80,10 +75,13 @@ theorem BitVec.lt_ult (x y : BitVec w) : (x < y) = (BitVec.ult x y = true) := by
simp only [(· < ·)]
simp
@[bv_normalize]
theorem Bool.or_elim : (a b : Bool), (a || b) = !(!a && !b) := by decide
@[bv_normalize]
theorem BitVec.or_elim (x y : BitVec w) : x ||| y = ~~~(~~~x &&& ~~~y) := by
ext
simp_all
simp
attribute [bv_normalize] BitVec.natCast_eq_ofNat
attribute [bv_normalize] BitVec.append_eq

View File

@@ -92,10 +92,13 @@ def renderProgress (running unfinished : Array OpaqueJob) (h : 0 < unfinished.si
(spinnerFrames[s.spinnerIdx], {s with spinnerIdx := s.spinnerIdx + 1, by decide})
let resetCtrl modifyGet fun s => (s.resetCtrl, {s with resetCtrl := Ansi.resetLine})
let caption :=
-- Prefer the newest running job.
-- This avoids the monitor focusing too long on any one job.
-- (e.g., "Running job computation")
if _ : 0 < running.size then
s!"Running {running[0].caption} (+ {running.size - 1} more)"
s!"Running {running[running.size - 1].caption} (+ {running.size - 1} more)"
else
s!"Running {unfinished[0].caption}"
s!"Running {unfinished[unfinished.size - 1].caption}"
print s!"{resetCtrl}{spinnerIcon} [{jobNo}/{totalJobs}] {caption}"
flush

View File

@@ -10,7 +10,7 @@ open Lean
namespace Lake
initialize
builtin_initialize
registerBuiltinAttribute {
ref := by exact decl_name%
name := `test_runner

View File

@@ -9,37 +9,37 @@ import Lake.Util.OrderedTagAttribute
open Lean
namespace Lake
initialize packageAttr : OrderedTagAttribute
builtin_initialize packageAttr : OrderedTagAttribute
registerOrderedTagAttribute `package "mark a definition as a Lake package configuration"
initialize packageDepAttr : OrderedTagAttribute
builtin_initialize packageDepAttr : OrderedTagAttribute
registerOrderedTagAttribute `package_dep "mark a definition as a Lake package dependency"
initialize postUpdateAttr : OrderedTagAttribute
builtin_initialize postUpdateAttr : OrderedTagAttribute
registerOrderedTagAttribute `post_update "mark a definition as a Lake package post-update hook"
initialize scriptAttr : OrderedTagAttribute
builtin_initialize scriptAttr : OrderedTagAttribute
registerOrderedTagAttribute `script "mark a definition as a Lake script"
initialize defaultScriptAttr : OrderedTagAttribute
builtin_initialize defaultScriptAttr : OrderedTagAttribute
registerOrderedTagAttribute `default_script "mark a Lake script as the package's default"
fun name => do
unless ( getEnv <&> (scriptAttr.hasTag · name)) do
throwError "attribute `default_script` can only be used on a `script`"
initialize leanLibAttr : OrderedTagAttribute
builtin_initialize leanLibAttr : OrderedTagAttribute
registerOrderedTagAttribute `lean_lib "mark a definition as a Lake Lean library target configuration"
initialize leanExeAttr : OrderedTagAttribute
builtin_initialize leanExeAttr : OrderedTagAttribute
registerOrderedTagAttribute `lean_exe "mark a definition as a Lake Lean executable target configuration"
initialize externLibAttr : OrderedTagAttribute
builtin_initialize externLibAttr : OrderedTagAttribute
registerOrderedTagAttribute `extern_lib "mark a definition as a Lake external library target"
initialize targetAttr : OrderedTagAttribute
builtin_initialize targetAttr : OrderedTagAttribute
registerOrderedTagAttribute `target "mark a definition as a custom Lake target"
initialize defaultTargetAttr : OrderedTagAttribute
builtin_initialize defaultTargetAttr : OrderedTagAttribute
registerOrderedTagAttribute `default_target "mark a Lake target as the package's default"
fun name => do
let valid getEnv <&> fun env =>
@@ -50,7 +50,7 @@ initialize defaultTargetAttr : OrderedTagAttribute ←
unless valid do
throwError "attribute `default_target` can only be used on a target (e.g., `lean_lib`, `lean_exe`)"
initialize testDriverAttr : OrderedTagAttribute
builtin_initialize testDriverAttr : OrderedTagAttribute
registerOrderedTagAttribute `test_driver "mark a Lake script, executable, or library as package's test driver"
fun name => do
let valid getEnv <&> fun env =>
@@ -60,7 +60,7 @@ initialize testDriverAttr : OrderedTagAttribute ←
unless valid do
throwError "attribute `test_driver` can only be used on a `script`, `lean_exe`, or `lean_lib`"
initialize lintDriverAttr : OrderedTagAttribute
builtin_initialize lintDriverAttr : OrderedTagAttribute
registerOrderedTagAttribute `lint_driver "mark a Lake script or executable as package's linter"
fun name => do
let valid getEnv <&> fun env =>
@@ -69,11 +69,11 @@ initialize lintDriverAttr : OrderedTagAttribute ←
unless valid do
throwError "attribute `lint_driver` can only be used on a `script` or `lean_exe`"
initialize moduleFacetAttr : OrderedTagAttribute
builtin_initialize moduleFacetAttr : OrderedTagAttribute
registerOrderedTagAttribute `module_facet "mark a definition as a Lake module facet"
initialize packageFacetAttr : OrderedTagAttribute
builtin_initialize packageFacetAttr : OrderedTagAttribute
registerOrderedTagAttribute `package_facet "mark a definition as a Lake package facet"
initialize libraryFacetAttr : OrderedTagAttribute
builtin_initialize libraryFacetAttr : OrderedTagAttribute
registerOrderedTagAttribute `library_facet "mark a definition as a Lake library facet"

View File

@@ -28,7 +28,7 @@ during the Lakefile's elaboration.
-/
scoped syntax (name := dirConst) "__dir__" : term
@[term_elab dirConst]
@[builtin_term_elab dirConst]
def elabDirConst : TermElab := fun stx expectedType? => do
let exp :=
if let some dir := dirExt.getState ( getEnv) then
@@ -48,7 +48,7 @@ or via the `with` clause in a `require` statement.
-/
scoped syntax (name := getConfig) "get_config? " ident :term
@[term_elab getConfig]
@[builtin_term_elab getConfig]
def elabGetConfig : TermElab := fun stx expectedType? => do
tryPostponeIfNoneOrMVar expectedType?
match stx with

View File

@@ -10,8 +10,8 @@ open Lean
namespace Lake
initialize dirExt : EnvExtension (Option System.FilePath)
builtin_initialize dirExt : EnvExtension (Option System.FilePath)
registerEnvExtension (pure none)
initialize optsExt : EnvExtension (Option (NameMap String))
builtin_initialize optsExt : EnvExtension (Option (NameMap String))
registerEnvExtension (pure none)

Some files were not shown because too many files have changed in this diff Show More