Skip to content

Commit 1c1dadb

Browse files
committed
feat(UpperHalfPlane): define PGL action on the upper half-plane (leanprover-community#36330)
1 parent f06f028 commit 1c1dadb

8 files changed

Lines changed: 326 additions & 11 deletions

File tree

Mathlib.lean

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1864,6 +1864,7 @@ public import Mathlib.Analysis.Complex.Trigonometric
18641864
public import Mathlib.Analysis.Complex.UnitDisc.Basic
18651865
public import Mathlib.Analysis.Complex.UpperHalfPlane.Basic
18661866
public import Mathlib.Analysis.Complex.UpperHalfPlane.Exp
1867+
public import Mathlib.Analysis.Complex.UpperHalfPlane.FixedPoints
18671868
public import Mathlib.Analysis.Complex.UpperHalfPlane.FunctionsBoundedAtInfty
18681869
public import Mathlib.Analysis.Complex.UpperHalfPlane.Manifold
18691870
public import Mathlib.Analysis.Complex.UpperHalfPlane.Measure
Lines changed: 214 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,214 @@
1+
/-
2+
Copyright (c) 2026 Yury G. Kudryashov. All rights reserved.
3+
Released under Apache 2.0 license as described in the file LICENSE.
4+
Authors: Yury G. Kudryashov
5+
-/
6+
module
7+
8+
public import Mathlib.Analysis.Complex.UpperHalfPlane.MoebiusAction
9+
public import Mathlib.LinearAlgebra.Matrix.GeneralLinearGroup.FinTwo
10+
import Mathlib.LinearAlgebra.Matrix.GeneralLinearGroup.Basic
11+
import Mathlib.Algebra.QuadraticDiscriminant
12+
13+
/-!
14+
# Fixed points of isometries of the upper half-plane
15+
16+
In this file we show that the scalar multiplication by an element `g : GL (Fin 2) ℝ`
17+
has the following set of fixed points, depending on `g`.
18+
19+
- if `g` preserves orientation (i.e., has positive determinant) and is an elliptic matrix,
20+
then `z ↦ g • z` has a unique fixed point;
21+
- if `g` is a scalar matrix, then it acts by the identity map (proved upstream of this file);
22+
- if `g` preserves orientation, and is a parabolic or a hyperbolic matrix,
23+
then it has no fixed points;
24+
- if `g` reverses orientation and has zero trace, then it has a geodesic line of fixed points;
25+
- if `g 1 0 = 0`, then this is the vertical line `re z = g 0 1 / (2 * g 1 1)`;
26+
- otherwise, it's a half-circle with its center on the real axis;
27+
- if `g` reverses orientation and has nonzero trace, then it has no fixed points.
28+
29+
As a corollary of this classification, we conclude that `PSL(2, ℝ)` acts faithfully
30+
on the upper half-plane.
31+
-/
32+
33+
open Matrix
34+
open scoped MatrixGroups ComplexConjugate
35+
36+
public noncomputable section
37+
38+
namespace UpperHalfPlane
39+
40+
section GLAction
41+
42+
variable {g : GL (Fin 2) ℝ} {z w : ℍ}
43+
44+
theorem gl_smul_eq_iff_num_eq :
45+
g • z = w ↔ num g z = σ g w * denom g z := by
46+
rw [← (σ g).injective.eq_iff]
47+
simp [UpperHalfPlane.ext_iff, coe_smul, div_eq_iff]
48+
49+
/-- If `g` is an upper triangular matrix with trace zero,
50+
then `g` fixes the vertical line `re z = b / (2 * d)`. -/
51+
theorem gl_smul_eq_self_iff_re_eq (htrace : g.val.trace = 0) (hc : g 1 0 = 0) :
52+
g • z = z ↔ z.re = g 0 1 / (2 * g 1 1) := by
53+
rw [Matrix.trace_fin_two, add_eq_zero_iff_eq_neg] at htrace
54+
have h₀ : g 1 10 := by
55+
intro h₀
56+
simpa [Matrix.det_fin_two, hc, h₀] using g.det_ne_zero
57+
have h : g.val.det < 0 := by simp [Matrix.det_fin_two, *]
58+
simp [gl_smul_eq_iff_num_eq, Complex.ext_iff, htrace, hc, num, denom, σ, h.not_gt, mul_comm,
59+
eq_div_iff, h₀]
60+
grind
61+
62+
/-- If `g` is an orientation reversing matrix with trace zero and `c ≠ 0`,
63+
then its action on the upper half plane has a half-circle of fixed points.
64+
In the hyperbolic geometry, this half-circle is a line.
65+
If `c = 0`, then this line is a vertical half-line in the usual geometry,
66+
see `gl_smul_eq_self_iff_re_eq`. -/
67+
theorem gl_smul_eq_self_iff_dist_sq_eq (h : g.val.det < 0) (htrace : g.val.trace = 0)
68+
(hc : g 1 00) :
69+
g • z = z ↔ dist (z : ℂ) (-g 1 1 / g 1 0) ^ 2 = (-g.val.det) / g 1 0 ^ 2 := by
70+
rw [Matrix.trace_fin_two, ← eq_neg_iff_add_eq_zero] at htrace
71+
rw [eq_div_iff (by positivity), dist_eq_norm, ← Complex.normSq_eq_norm_sq, Complex.normSq_apply,
72+
gl_smul_eq_iff_num_eq, σ, g.val_det_apply, if_neg h.not_gt]
73+
simp [num, denom, Complex.ext_iff, htrace, Matrix.det_fin_two, field]
74+
grind
75+
76+
/-- If `g` is an orientation reversing matrix with trace zero and `c ≠ 0`,
77+
then its action on the upper half plane has a half-circle of fixed points.
78+
In the hyperbolic geometry, this half-circle is a line. -/
79+
theorem gl_smul_eq_self_iff_dist_eq (h : g.val.det < 0) (htrace : g.val.trace = 0)
80+
(hc : g 1 00) :
81+
g • z = z ↔ dist (z : ℂ) (-g 1 1 / g 1 0) = √(-g.val.det) / |g 1 0| := by
82+
rw [gl_smul_eq_self_iff_dist_sq_eq h htrace hc, eq_comm, ← Real.sqrt_eq_iff_eq_sq, eq_comm,
83+
Real.sqrt_div', Real.sqrt_sq_eq_abs] <;> positivity [neg_pos.mpr h]
84+
85+
/-- An orientation-reversing isometry of the hyperbolic plane has a fixed point
86+
iff the corresponding matrix has zero trace. -/
87+
theorem exists_gl_smul_eq_self_iff_trace_eq_zero (h : g.val.det < 0) :
88+
(∃ z : ℍ, g • z = z) ↔ g.val.trace = 0 := by
89+
constructor
90+
· rintro ⟨z, hz⟩
91+
linear_combination
92+
(norm := { simp [σ, h.not_gt, num, denom, z.im_ne_zero, Matrix.trace_fin_two, field] })
93+
congr($(gl_smul_eq_iff_num_eq.mp hz).im / z.im)
94+
· intro hadd
95+
by_cases hc : g 1 0 = 0
96+
· use ⟨⟨g 0 1 / (2 * g 1 1), 1⟩, one_pos⟩
97+
simp [gl_smul_eq_self_iff_re_eq, *]
98+
· use ⟨⟨-g 1 1 / g 1 0, √(-g.val.det) / |g 1 0|⟩, by simp [*]⟩
99+
simp [gl_smul_eq_self_iff_dist_sq_eq, *, dist_eq_norm, ← Complex.normSq_eq_norm_sq,
100+
Complex.normSq_apply, ← pow_two, div_pow, h.le]
101+
102+
/-- If `g` is an orientation-preserving map,
103+
then the fixed points of its action on the upper half-plane
104+
can be found from a quadratic equation.
105+
106+
If `c ≠ 0`, then this equation has a unique solution in the upper half-plane
107+
given by `UpperHalfPlane.fixedPt`.
108+
If `c = 0`, then the equation degenerates to a linear equation,
109+
which has no solutions in the upper half-plane unless `g` is a scalar matrix.
110+
111+
See also `Matrix.GeneralLinearGroup.fixpointPolynomial_aeval_eq_zero_iff`
112+
for a similar lemma about the action on the projective line,
113+
encoded as `OnePoint R`, where `R` is the ring of coefficients.
114+
-/
115+
theorem gl_smul_eq_self_iff_quadratic (h : 0 < g.val.det) :
116+
g • z = z ↔ (g 1 0 * (z * z) + (g 1 1 - g 0 0) * z + -g 0 1 : ℂ) = 0 := by
117+
simp [gl_smul_eq_iff_num_eq, σ, h, num, denom]
118+
grind
119+
120+
/-- If `g` is a non-scalar orientation perserving matrix with a fixed point in `ℍ`,
121+
then it's an elliptic matrix. -/
122+
theorem isElliptic_of_exists_smul_eq_self (h : 0 < g.val.det) (hgc : g ∉ Subgroup.center _)
123+
(hfix : ∃ z : ℍ, g • z = z) : g.IsElliptic := by
124+
rcases hfix with ⟨z, hz⟩
125+
have hc : g 1 00 := by
126+
intro hc
127+
simp [GeneralLinearGroup.mem_center_iff_val_mem_range_scalar, ← Matrix.ext_iff, hc] at hgc
128+
simp [gl_smul_eq_iff_num_eq, Complex.ext_iff, σ, h, num, denom, hc, mul_comm, z.im_ne_zero]
129+
at hz
130+
grind
131+
refine lt_of_not_ge fun hge ↦ ?_
132+
have hd : discrim (g 1 0 : ℂ) (g 1 1 - g 0 0) (-g 0 1) = √g.val.discr * √g.val.discr := by
133+
rw [← Complex.ofReal_mul, Real.mul_self_sqrt hge]
134+
simp [discrim, Matrix.discr_fin_two, Matrix.trace_fin_two, Matrix.det_fin_two]
135+
ring
136+
rw [gl_smul_eq_self_iff_quadratic h, quadratic_eq_zero_iff (mod_cast hc) hd] at hz
137+
norm_cast at hz
138+
simp only [z.ne_ofReal, false_or] at hz
139+
140+
/-- The unique fixed point of an orientation-preserving elliptic matrix acting on `ℍ`. -/
141+
def fixedPt (g : GL (Fin 2) ℝ) (hell : g.IsElliptic) : ℍ :=
142+
⟨(g 0 0 - g 1 1) / (2 * g 1 0) + .I * (√(-g.val.discr) / (2 * |g 1 0|)), by
143+
simpa [div_pos, Complex.div_re, Complex.div_im, hell.c_ne_zero]⟩
144+
145+
@[simp]
146+
theorem fixedPt_neg (hg : (-g).IsElliptic) :
147+
fixedPt (-g) hg = fixedPt g (isElliptic_neg_iff.mp hg) := by
148+
ext
149+
simp [fixedPt, Matrix.discr_fin_two, Matrix.det_neg]
150+
ring
151+
152+
/-- The action of an elliptic orientation preserving matrix on `ℍ`
153+
has a unique fixed point given by `fixedPt`. -/
154+
theorem gl_smul_eq_self_iff_eq_fixedPt (hpos : 0 < g.val.det) (hell : g.IsElliptic) :
155+
g • z = z ↔ z = fixedPt g hell := by
156+
wlog hc : 0 < g 1 0 generalizing g
157+
· replace hc := hell.c_ne_zero.lt_or_gt.resolve_right hc
158+
simpa using @this (-g) (by simpa [Matrix.det_neg]) hell.neg (by simpa)
159+
have hd : discrim (g 1 0 : ℂ) (g 1 1 - g 0 0) (-g 0 1) = (.I * √(-g.val.discr)) ^ 2 := by
160+
rw [mul_pow, ← Complex.ofReal_pow, Real.sq_sqrt]
161+
· simp [discrim, Matrix.discr_fin_two, Matrix.trace_fin_two, Matrix.det_fin_two]
162+
grind
163+
· simpa using hell.le
164+
rw [gl_smul_eq_self_iff_quadratic hpos, quadratic_eq_zero_iff (mod_cast hell.c_ne_zero)
165+
(hd.trans (pow_two _))]
166+
rw [or_iff_left]
167+
· simp [fixedPt, UpperHalfPlane.ext_iff, abs_of_pos hc, field]
168+
· intro h
169+
refine z.im_pos.not_ge ?_
170+
rw [← coe_im, h]
171+
simp [Complex.div_im, div_nonpos_iff, hc.le, mul_nonneg]
172+
173+
theorem gl_smul_I_eq_I_iff_of_pos {g : GL (Fin 2) ℝ} (hg : 0 < g.det.val) :
174+
g • I = I ↔ g 0 0 = g 1 1 ∧ g 0 1 = -g 1 0 := by
175+
rw [gl_smul_eq_iff_num_eq, σ, if_pos hg]
176+
simp [Complex.ext_iff, num, denom, and_comm]
177+
178+
theorem gl_smul_I_eq_I_iff_of_neg {g : GL (Fin 2) ℝ} (hg : g.det.val < 0) :
179+
g • I = I ↔ g 0 0 = -g 1 1 ∧ g 0 1 = g 1 0 := by
180+
rw [gl_smul_eq_iff_num_eq, σ, if_neg (not_lt_of_gt hg)]
181+
simp [num, denom, Complex.ext_iff, and_comm]
182+
183+
/-- A matrix acts trivially on `ℍ` iff it belongs to the center of `GL(2, ℝ)`,
184+
i.e., it's a diagonal matrix. -/
185+
theorem forall_smul_eq_self_iff_mem_center {g : GL (Fin 2) ℝ} :
186+
(∀ z : ℍ, g • z = z) ↔ g ∈ Subgroup.center _ := by
187+
constructor
188+
· intro hg
189+
by_contra! hgc
190+
rcases g.det_ne_zero.lt_or_gt with hlt | hgt
191+
· obtain ⟨ha, hb⟩ := (gl_smul_I_eq_I_iff_of_neg hlt).mp (hg _)
192+
rw [eq_neg_iff_add_eq_zero, ← Matrix.trace_fin_two] at ha
193+
rcases eq_or_ne (g 1 0) 0 with hc | hc
194+
· specialize hg ⟨1 + .I, by simp⟩
195+
rw [gl_smul_eq_self_iff_re_eq ha hc] at hg
196+
simp_all
197+
· have : 0 < 1 + √(-g.val.det) / |g 1 0| := by simp [add_pos, *]
198+
specialize hg ⟨⟨-g 1 1 / g 1 0, 1 + √(-g.val.det) / |g 1 0|⟩, this⟩
199+
simp [gl_smul_eq_self_iff_dist_eq hlt ha hc, Complex.dist_eq_re_im, Real.sqrt_sq this.le]
200+
at hg
201+
· have := isElliptic_of_exists_smul_eq_self hgt hgc ⟨.I, hg _⟩
202+
contrapose! hg
203+
simp [gl_smul_eq_self_iff_eq_fixedPt hgt this, exists_ne]
204+
· aesop (add simp GeneralLinearGroup.center_eq_range_scalar)
205+
206+
end GLAction
207+
208+
instance : FaithfulSMul PGL(2, ℝ) ℍ := by
209+
rw [faithfulSMul_iff]
210+
intro g
211+
cases g
212+
simp [forall_smul_eq_self_iff_mem_center]
213+
214+
end UpperHalfPlane

Mathlib/Analysis/Complex/UpperHalfPlane/MoebiusAction.lean

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ module
77

88
public import Mathlib.Analysis.Complex.UpperHalfPlane.Basic
99
public import Mathlib.LinearAlgebra.Matrix.GeneralLinearGroup.Defs
10+
public import Mathlib.LinearAlgebra.Matrix.GeneralLinearGroup.Projective
1011

1112
/-!
1213
# Group action on the upper half-plane
@@ -59,6 +60,7 @@ theorem denom_ne_zero_of_im (g : GL (Fin 2) ℝ) {z : ℂ} (hz : z.im ≠ 0) : d
5960
refine linear_ne_zero_of_im hz fun H ↦ g.det.ne_zero ?_
6061
simp [Matrix.det_fin_two, H]
6162

63+
@[simp]
6264
theorem denom_ne_zero (g : GL (Fin 2) ℝ) (z : ℍ) : denom g z ≠ 0 :=
6365
denom_ne_zero_of_im g z.im_ne_zero
6466

@@ -206,6 +208,7 @@ lemma glPos_smul_def {g : GL (Fin 2) ℝ} (hg : 0 < g.det.val) (z : ℍ) :
206208
g • z = ⟨num g z / denom g z, coe_smul_of_det_pos hg z ▸ (g • z).im_pos⟩ := by
207209
ext; simp [coe_smul_of_det_pos hg]
208210

211+
section GLAction
209212
variable (g : GL (Fin 2) ℝ) (z : ℍ)
210213

211214
theorem re_smul : (g • z).re = (num g z / denom g z).re := by
@@ -235,9 +238,52 @@ theorem neg_smul : -g • z = g • z := by
235238
ext1
236239
simp [coe_smul]
237240

241+
@[simp]
242+
lemma num_one : num 1 z = z := by simp [num]
243+
244+
@[simp]
238245
lemma denom_one : denom 1 z = 1 := by
239246
simp [denom]
240247

248+
@[simp]
249+
theorem num_scalar (u : ℝˣ) (z : ℍ) : num (.scalar (Fin 2) u) z = u * z := by
250+
simp [num]
251+
252+
@[simp]
253+
theorem denom_scalar (u : ℝˣ) (z : ℍ) : denom (.scalar (Fin 2) u) z = u := by
254+
simp [denom]
255+
256+
@[simp]
257+
theorem glScalar_smul (u : ℝˣ) (z : ℍ) :
258+
GeneralLinearGroup.scalar (Fin 2) u • z = z := by
259+
rw [glPos_smul_def]
260+
· simp
261+
· simp [sq_pos_iff]
262+
263+
instance : MulAction.IsPretransitive (GL (Fin 2) ℝ) ℍ where
264+
exists_smul_eq z w := by
265+
set m : Matrix (Fin 2) (Fin 2) ℝ := !![w.im, z.im * w.re - w.im * z.re; 0, z.im]
266+
refine ⟨.mkOfDetNeZero m <| by simp [m, im_ne_zero], ?_⟩
267+
ext
268+
simp [coe_smul_of_det_pos, im_pos, num, denom, m, Complex.ext_iff, im_ne_zero]
269+
270+
end GLAction
271+
272+
section PGLAction
273+
274+
instance : MulAction PGL(2, ℝ) ℍ :=
275+
Matrix.ProjGenLinGroup.mulActionOfGL glScalar_smul
276+
277+
@[simp]
278+
theorem pglMk_smul (g : GL (Fin 2) ℝ) (z : ℍ) :
279+
ProjGenLinGroup.mk g • z = g • z :=
280+
ProjGenLinGroup.mk_smul ..
281+
282+
instance : MulAction.IsPretransitive PGL(2, ℝ) ℍ :=
283+
.of_smul_eq .mk <| pglMk_smul _ _
284+
285+
end PGLAction
286+
241287
section SLAction
242288

243289
noncomputable instance SLAction {R : Type*} [CommRing R] [Algebra R ℝ] : MulAction SL(2, R) ℍ :=

Mathlib/LinearAlgebra/Matrix/Charpoly/Basic.lean

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -275,14 +275,16 @@ theorem charpoly_vecMulVec (u v : n → R) :
275275
rw [vecMulVec_eq (ι := Unit), charpoly_mul_comm_of_le (n := Unit) _ _ h, charpoly, charmatrix]
276276
simp [-Matrix.map_mul, mul_sub, ← pow_succ, h, dotProduct_comm, smul_eq_C_mul]
277277

278+
@[simp]
278279
theorem charpoly_units_conj (M : (Matrix n n R)ˣ) (N : Matrix n n R) :
279-
(M.val * N * M⁻¹.val).charpoly = N.charpoly := by
280+
(M.val * N * M.val⁻¹).charpoly = N.charpoly := by
280281
rw [Matrix.charpoly_mul_comm, ← mul_assoc]
281282
simp
282283

284+
@[simp]
283285
theorem charpoly_units_conj' (M : (Matrix n n R)ˣ) (N : Matrix n n R) :
284-
(M⁻¹.val * N * M.val).charpoly = N.charpoly :=
285-
charpoly_units_conj M⁻¹ N
286+
(M.val⁻¹ * N * M.val).charpoly = N.charpoly := by
287+
simpa using charpoly_units_conj M⁻¹ N
286288

287289
set_option backward.isDefEq.respectTransparency false in
288290
theorem charpoly_sub_scalar (M : Matrix n n R) (μ : R) :

Mathlib/LinearAlgebra/Matrix/Charpoly/Disc.lean

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,14 @@ variable {R n : Type*} [CommRing R] [Fintype n] [DecidableEq n]
2525
polynomial. -/
2626
noncomputable def discr (A : Matrix n n R) : R := A.charpoly.discr
2727

28+
@[simp]
29+
lemma discr_conj (g : GL n R) (m : Matrix n n R) : (g.val * m * g.val⁻¹).discr = m.discr := by
30+
simp [discr]
31+
32+
@[simp]
33+
lemma discr_conj' (g : GL n R) (m : Matrix n n R) : (g.val⁻¹ * m * g.val).discr = m.discr := by
34+
simp [discr]
35+
2836
lemma discr_of_card_eq_two (A : Matrix n n R) (hn : Fintype.card n = 2) :
2937
A.discr = A.trace ^ 2 - 4 * A.det := by
3038
nontriviality R

0 commit comments

Comments
 (0)