|
| 1 | +/- |
| 2 | +Copyright (c) 2026 Newell Jensen. All rights reserved. |
| 3 | +Released under Apache 2.0 license as described in the file LICENSE. |
| 4 | +Authors: Newell Jensen |
| 5 | +-/ |
| 6 | +module |
| 7 | + |
| 8 | +public import Mathlib.Topology.MetricSpace.Cover |
| 9 | + |
| 10 | +/-! |
| 11 | +# Delone sets |
| 12 | +
|
| 13 | +A **Delone set** `D ⊆ X` in a metric space is a set which is both: |
| 14 | +
|
| 15 | +* **Uniformly Discrete**: there exists `packingRadius > 0` such that distinct points of `D` |
| 16 | + are separated by a distance strictly greater than `packingRadius`; |
| 17 | +* **Relatively Dense**: there exists `coveringRadius > 0` such that every point of `X` |
| 18 | + lies within distance `coveringRadius` of some point of `D`. |
| 19 | +
|
| 20 | +The `DeloneSet` structure stores the set together with explicit radii witnessing |
| 21 | +these properties. The definitions use metric entourages so that the theory fits |
| 22 | +naturally into the uniformity framework. |
| 23 | +
|
| 24 | +Delone sets appear in discrete geometry, crystallography, aperiodic order, and tiling theory. |
| 25 | +
|
| 26 | +## Main definitions |
| 27 | +
|
| 28 | +* `Delone.DeloneSet X`: The main structure representing a Delone set in a metric space `X`. |
| 29 | +* `DeloneSet.mapBilipschitz`: Transports a Delone set along a bilipschitz equivalence, |
| 30 | + scaling the radii. |
| 31 | +* `DeloneSet.mapIsometry` Preserves the packing and covering radii exactly. |
| 32 | +
|
| 33 | +## Basic properties |
| 34 | +
|
| 35 | +* `packingRadius_lt_dist_of_mem_ne` : Distinct points in a Delone set are further apart than |
| 36 | + the packing radius. |
| 37 | +* `exists_dist_le_coveringRadius` : Every point of the space lies within the covering radius |
| 38 | + of the set. |
| 39 | +* `subset_ball_singleton` : Any ball of sufficiently small radius contains at most one point of |
| 40 | + the set. |
| 41 | +
|
| 42 | +## Implementation notes |
| 43 | +
|
| 44 | +* **Bundled Structure**: `DeloneSet` is bundled as a structure rather than a predicate |
| 45 | + (e.g., `IsDelone`). This facilitates dynamical systems constructions like hulls and patches by |
| 46 | + ensuring operations automatically preserve the required properties, eliminating the need to |
| 47 | + manually pass around proofs that the set remains Delone. |
| 48 | +* **Explicit Data**: Since radii are stored as explicit data, the map from `DeloneSet X` to `Set X` |
| 49 | + is not injective. We provide a `Membership` instance and `mem_carrier` to allow the convenience |
| 50 | + of `∈` notation while ensuring radii remain bundled, computationally accessible, and tracked by |
| 51 | + extensionality. |
| 52 | +-/ |
| 53 | + |
| 54 | +@[expose] public section |
| 55 | + |
| 56 | +open Metric |
| 57 | +open scoped NNReal |
| 58 | + |
| 59 | +variable {X Y : Type*} [MetricSpace X] [MetricSpace Y] |
| 60 | + |
| 61 | +namespace Delone |
| 62 | + |
| 63 | +/-- A **Delone set** consists of a set together with explicit radii |
| 64 | +witnessing uniform discreteness and relative denseness. -/ |
| 65 | +@[ext] |
| 66 | +structure DeloneSet (X : Type*) [MetricSpace X] where |
| 67 | + /-- The underlying set. -/ |
| 68 | + carrier : Set X |
| 69 | + /-- Radius such that distinct points of `carrier` are separated by more than `r`. -/ |
| 70 | + packingRadius : ℝ≥0 |
| 71 | + packingRadius_pos : 0 < packingRadius |
| 72 | + isSeparated_packingRadius : IsSeparated packingRadius carrier |
| 73 | + /-- Radius such that every point of the space is within `R` of `carrier`. -/ |
| 74 | + coveringRadius : ℝ≥0 |
| 75 | + coveringRadius_pos : 0 < coveringRadius |
| 76 | + isCover_coveringRadius : IsCover coveringRadius .univ carrier |
| 77 | + |
| 78 | +namespace DeloneSet |
| 79 | + |
| 80 | +/-- The underlying set of points of a Delone set. -/ |
| 81 | +@[coe] def toSet (D : DeloneSet X) : Set X := D.carrier |
| 82 | + |
| 83 | +instance : Coe (DeloneSet X) (Set X) where |
| 84 | + coe := DeloneSet.toSet |
| 85 | + |
| 86 | +instance : Membership X (DeloneSet X) where |
| 87 | + mem D x := x ∈ (D : Set X) |
| 88 | + |
| 89 | +@[simp, norm_cast] |
| 90 | +lemma mem_coe {D : DeloneSet X} {x : X} : x ∈ (D : Set X) ↔ x ∈ D := .rfl |
| 91 | + |
| 92 | +@[simp] lemma mem_carrier {D : DeloneSet X} {x : X} : |
| 93 | + x ∈ D.carrier ↔ x ∈ D := .rfl |
| 94 | + |
| 95 | +lemma nonempty [Nonempty X] (D : DeloneSet X) : (D : Set X).Nonempty := |
| 96 | + D.isCover_coveringRadius.nonempty Set.univ_nonempty |
| 97 | + |
| 98 | +/-- Copy of a Delone set with new fields equal to the old ones. |
| 99 | +Useful to fix definitional equalities. -/ |
| 100 | +protected def copy (D : DeloneSet X) (carrier : Set X) (packingRadius coveringRadius : ℝ≥0) |
| 101 | + (h_carrier : carrier = D.carrier) (h_packing : packingRadius = D.packingRadius) |
| 102 | + (h_covering : coveringRadius = D.coveringRadius) : |
| 103 | + DeloneSet X where |
| 104 | + carrier := carrier |
| 105 | + packingRadius := packingRadius |
| 106 | + packingRadius_pos := by simpa [h_packing] using D.packingRadius_pos |
| 107 | + isSeparated_packingRadius := by |
| 108 | + simpa [h_carrier, h_packing] using D.isSeparated_packingRadius |
| 109 | + coveringRadius := coveringRadius |
| 110 | + coveringRadius_pos := by simpa [h_covering] using D.coveringRadius_pos |
| 111 | + isCover_coveringRadius := by |
| 112 | + simpa [h_carrier, h_covering] using D.isCover_coveringRadius |
| 113 | + |
| 114 | +theorem copy_eq (D : DeloneSet X) |
| 115 | + (carrier packingRadius coveringRadius h_carrier h_packing h_covering) : |
| 116 | + D.copy carrier packingRadius coveringRadius h_carrier h_packing h_covering = D := |
| 117 | + DeloneSet.ext h_carrier h_packing h_covering |
| 118 | + |
| 119 | +lemma packingRadius_lt_dist_of_mem_ne (D : DeloneSet X) {x y : X} |
| 120 | + (hx : x ∈ D) (hy : y ∈ D) (hne : x ≠ y) : |
| 121 | + D.packingRadius < dist x y := by |
| 122 | + have hsep : ENNReal.ofReal D.packingRadius < ENNReal.ofReal (dist x y) := by |
| 123 | + simpa [edist_dist] using D.isSeparated_packingRadius hx hy hne |
| 124 | + exact (ENNReal.ofReal_lt_ofReal_iff (h := dist_pos.mpr hne)).1 hsep |
| 125 | + |
| 126 | +lemma exists_dist_le_coveringRadius (D : DeloneSet X) (x : X) : |
| 127 | + ∃ y ∈ D, dist x y ≤ D.coveringRadius := by |
| 128 | + obtain ⟨y, hy, hdist⟩ := D.isCover_coveringRadius (x := x) (by trivial) |
| 129 | + exact ⟨y, hy, by simpa [edist_dist] using hdist⟩ |
| 130 | + |
| 131 | +lemma eq_of_mem_ball (D : DeloneSet X) {r : ℝ≥0} (hr : r ≤ D.packingRadius / 2) |
| 132 | + {x y z : X} (hx : x ∈ D) (hy : y ∈ D) (hxz : x ∈ ball z r) (hyz : y ∈ ball z r) : |
| 133 | + x = y := by |
| 134 | + by_contra hne |
| 135 | + exact (D.packingRadius_lt_dist_of_mem_ne hx hy hne).not_gt <| calc |
| 136 | + dist x y ≤ dist x z + dist y z := dist_triangle_right x y z |
| 137 | + _ < r + r := by gcongr <;> simpa |
| 138 | + _ ≤ D.packingRadius := by rw [← add_halves D.packingRadius, NNReal.coe_add]; gcongr |
| 139 | + |
| 140 | +/-- There exists a radius `r > 0` such that any ball of radius `r` |
| 141 | +centered at a point of `D` contains at most one point of `D`. -/ |
| 142 | +lemma subset_ball_singleton (D : DeloneSet X) : |
| 143 | + ∃ r > 0, ∀ {x y z}, x ∈ D → y ∈ D → x ∈ ball z r → y ∈ ball z r → x = y := |
| 144 | + ⟨D.packingRadius / 2, half_pos D.packingRadius_pos, fun hx hy => D.eq_of_mem_ball le_rfl hx hy⟩ |
| 145 | + |
| 146 | +/-- Bilipschitz maps send Delone sets to Delone sets. -/ |
| 147 | +@[simps] |
| 148 | +noncomputable def mapBilipschitz (f : X ≃ Y) (K₁ K₂ : ℝ≥0) (hK₁ : 0 < K₁) (hK₂ : 0 < K₂) |
| 149 | + (hf₁ : AntilipschitzWith K₁ f) (hf₂ : LipschitzWith K₂ f) (D : DeloneSet X) : DeloneSet Y where |
| 150 | + carrier := f '' D.carrier |
| 151 | + packingRadius := D.packingRadius / K₁ |
| 152 | + packingRadius_pos := div_pos D.packingRadius_pos hK₁ |
| 153 | + isSeparated_packingRadius := D.isSeparated_packingRadius.image_antilipschitz hf₁ hK₁ |
| 154 | + coveringRadius := K₂ * D.coveringRadius |
| 155 | + coveringRadius_pos := mul_pos hK₂ D.coveringRadius_pos |
| 156 | + isCover_coveringRadius := D.isCover_coveringRadius.image_lipschitz_of_surjective hf₂ f.surjective |
| 157 | + |
| 158 | +@[simp] lemma mapBilipschitz_refl (D : DeloneSet X) (hK1 hK2 hA hL) : |
| 159 | + D.mapBilipschitz (.refl X) 1 1 hK1 hK2 hA hL = D := by |
| 160 | + ext <;> simp only [mapBilipschitz, Equiv.refl_apply, Set.image_id', div_one, one_mul] |
| 161 | + |
| 162 | +lemma mapBilipschitz_trans {Z : Type*} [MetricSpace Z] (D : DeloneSet X) |
| 163 | + (f : X ≃ Y) (g : Y ≃ Z) (K₁f K₂f K₁g K₂g : ℝ≥0) |
| 164 | + (hf₁_pos : 0 < K₁f) (hf₂_pos : 0 < K₂f) |
| 165 | + (hg₁_pos : 0 < K₁g) (hg₂_pos : 0 < K₂g) |
| 166 | + (hf_anti : AntilipschitzWith K₁f f) (hf_lip : LipschitzWith K₂f f) |
| 167 | + (hg_anti : AntilipschitzWith K₁g g) (hg_lip : LipschitzWith K₂g g) : |
| 168 | + (D.mapBilipschitz f K₁f K₂f hf₁_pos hf₂_pos hf_anti hf_lip).mapBilipschitz |
| 169 | + g K₁g K₂g hg₁_pos hg₂_pos hg_anti hg_lip = |
| 170 | + D.mapBilipschitz (f.trans g) (K₁f * K₁g) (K₂g * K₂f) |
| 171 | + (mul_pos hf₁_pos hg₁_pos) (mul_pos hg₂_pos hf₂_pos) |
| 172 | + (hg_anti.comp hf_anti) (hg_lip.comp hf_lip) := by |
| 173 | + ext |
| 174 | + · simp only [mapBilipschitz_carrier, Equiv.trans_apply, Set.mem_image] |
| 175 | + exact exists_exists_and_eq_and |
| 176 | + · simp only [mapBilipschitz_packingRadius, NNReal.coe_div, div_div] |
| 177 | + · simp only [mapBilipschitz_coveringRadius, NNReal.coe_mul, mul_assoc] |
| 178 | + |
| 179 | +/-- The image of a Delone set under an isometry. This is a specialization of |
| 180 | +`DeloneSet.mapBilipschitz` where the packing and covering radii are preserved because the |
| 181 | +Lipschitz constants are both 1. -/ |
| 182 | +@[simps!] |
| 183 | +noncomputable def mapIsometry (f : X ≃ᵢ Y) : DeloneSet X ≃ DeloneSet Y where |
| 184 | + toFun D := (D.mapBilipschitz f.toEquiv 1 1 zero_lt_one zero_lt_one |
| 185 | + f.isometry.antilipschitz f.isometry.lipschitz).copy (f '' D.carrier) |
| 186 | + D.packingRadius D.coveringRadius rfl (by simp [mapBilipschitz]) (by simp [mapBilipschitz]) |
| 187 | + invFun D := (D.mapBilipschitz f.symm.toEquiv 1 1 zero_lt_one zero_lt_one |
| 188 | + f.symm.isometry.antilipschitz f.symm.isometry.lipschitz).copy (f.symm '' D.carrier) |
| 189 | + D.packingRadius D.coveringRadius rfl (by simp [mapBilipschitz]) (by simp [mapBilipschitz]) |
| 190 | + left_inv D := by ext <;> simp [copy_eq] |
| 191 | + right_inv D := by ext <;> simp [copy_eq] |
| 192 | + |
| 193 | +@[simp] lemma mapIsometry_refl (D : DeloneSet X) : D.mapIsometry (.refl X) = D := by |
| 194 | + ext <;> simp [mapIsometry, IsometryEquiv.refl, DeloneSet.copy] |
| 195 | + |
| 196 | +lemma mapIsometry_symm (f : X ≃ᵢ Y) : (mapIsometry f).symm = mapIsometry f.symm := rfl |
| 197 | + |
| 198 | +lemma mapIsometry_trans {Z : Type*} [MetricSpace Z] (D : DeloneSet X) (f : X ≃ᵢ Y) (g : Y ≃ᵢ Z) : |
| 199 | + D.mapIsometry (f.trans g) = (D.mapIsometry f).mapIsometry g := by |
| 200 | + ext <;> simp [mapIsometry, DeloneSet.copy] |
| 201 | + |
| 202 | +end DeloneSet |
| 203 | + |
| 204 | +end Delone |
0 commit comments