@@ -3,6 +3,7 @@ import SPG.Algebra.Group
33import SPG.Geometry.SpatialOps
44import SPG.Geometry.SpinOps
55import SPG.Interface.Notation
6+ import Mathlib.LinearAlgebra.Matrix.Determinant.Basic
67
78namespace Demo.Altermagnet
89
@@ -15,16 +16,18 @@ open SPG.Algebra
1516-- 1. Generators for D4h altermagnet
1617def mat_4_z : Matrix (Fin 3 ) (Fin 3 ) ℚ := ![![0 , -1 , 0 ], ![1 , 0 , 0 ], ![0 , 0 , 1 ]]
1718def mat_inv : Matrix (Fin 3 ) (Fin 3 ) ℚ := ![![ -1 , 0 , 0 ], ![0 , -1 , 0 ], ![0 , 0 , -1 ]]
19+ -- mat_2_xy defined explicitly for clarity (rotation by 180 degrees about x+y axis)
20+ def mat_2_xy : Matrix (Fin 3 ) (Fin 3 ) ℚ := ![![0 , 1 , 0 ], ![1 , 0 , 0 ], ![0 , 0 , -1 ]]
1821
1922-- Generators
2023-- C4z * T : The key altermagnetic symmetry
21- -- C2x : Standard rotation
24+ -- C2xy : Rotation about x+y axis
2225-- I : Inversion
2326def gen_C4z_TR : SPGElement := Op[mat_4_z, ^-1 ]
24- def gen_C2x : SPGElement := Op[mat_2_x , ^1 ]
27+ def gen_C2xy : SPGElement := Op[mat_2_xy , ^1 ]
2528def gen_Inv : SPGElement := Op[mat_inv, ^1 ]
2629
27- def Altermagnet_Group : List SPGElement := generate_group [gen_C4z_TR, gen_C2x , gen_Inv]
30+ def Altermagnet_Group : List SPGElement := generate_group [gen_C4z_TR, gen_C2xy , gen_Inv]
2831
2932-- 2. Hamiltonian Analysis
3033-- We want to find H(k) ~ c_ij k_i k_j terms allowed by symmetry.
@@ -72,56 +75,72 @@ def act_on_k (g : SPGElement) (k : Vec3) : Vec3 :=
7275-- So if g.spin = -I (Time Reversal), the spin vector flips.
7376-- If g.spin = I, spin vector stays.
7477
75- def act_on_spin (g : SPGElement) (s : SpinComp) : SpinComp :=
76- if g.spin == spin_neg_I then -- Time Reversal
77- match s with
78- | .I => .I -- Identity matrix is T-even
79- | _ => s -- This is wrong. T sigma T^-1 = -sigma.
80- -- But we represent "basis elements". We need a coefficient sign.
81- -- Let's handle sign separately.
82- -- s -- Just return component, sign handled in `check_symmetry`
83- else
84- s
78+ def act_on_spin (g : SPGElement) (s : SpinComp) : Vec3 :=
79+ let s_vec : Vec3 := match s with
80+ | .x => ![1 , 0 , 0 ]
81+ | .y => ![0 , 1 , 0 ]
82+ | .z => ![0 , 0 , 1 ]
83+ | .I => ![0 , 0 , 0 ] -- Handle I separately
8584
86- def spin_sign (g : SPGElement) (s : SpinComp) : ℚ :=
87- if g.spin == spin_neg_I then
88- match s with
89- | .I => 1
90- | _ => -1 -- Spin flips under Time Reversal
85+ if s == .I then ![0 , 0 , 0 ] -- I transforms to I (scalar)
9186 else
92- 1
93-
94- -- Check if a term (Quad * Spin) is invariant under the group
95- -- Term: C * Q(k) * S
96- -- Transform: C * Q(g^-1 k) * (g S g^-1)
97- -- We need Q(g^-1 k) * (spin_sign) == Q(k) for all g.
98- -- Actually, let's just checking specific terms.
87+ -- Apply spatial rotation R to the axial vector sigma
88+ -- sigma' = (det R) * R * sigma
89+ -- If spin part has T (-I), then sigma' = - sigma'
90+ let detR := Matrix.det g.spatial
91+ let rotated := Matrix.mulVec g.spatial s_vec
92+ let axial_rotated := detR • rotated -- Scalar multiplication
93+
94+ if g.spin == spin_neg_I then
95+ -axial_rotated
96+ else
97+ axial_rotated
98+
99+ -- Helper to check if a transformed spin vector matches a target basis component (with sign)
100+ -- Returns 0 if orthogonal, 1 or -1 if parallel/antiparallel
101+ def project_spin (v : Vec3) (target : SpinComp) : ℚ :=
102+ match target with
103+ | .x => v 0
104+ | .y => v 1
105+ | .z => v 2
106+ | .I => 0
99107
100108def check_invariant (q : QuadTerm) (s : SpinComp) : Bool :=
101109 Altermagnet_Group.all fun g =>
102110 -- Symmetry constraint: H(k) = U H(g^-1 k) U^dagger
103- -- H(k) = f(k) * sigma
104- -- U f(g^-1 k) sigma U^dagger = f(g^-1 k) * (U sigma U^dagger)
105- -- So we need: f(k) * sigma = f(g^-1 k) * (sigma_transformed)
106- -- Here sigma_transformed = s * spin_sign(g)
107- -- And f(k) is quadratic form.
111+ -- H(k) = f(k) * sigma_s
112+ -- Transform: f(g^-1 k) * (g sigma_s g^-1)
113+ -- We check: f(g k) * (transformed sigma) == f(k) * sigma_s
114+ -- Note: using g k instead of g^-1 k for group average equivalence.
108115
109- -- Let's test invariance on a set of random k-points to avoid accidental zeros
110116 let test_ks : List Vec3 := [![1 , 0 , 0 ], ![0 , 1 , 0 ], ![0 , 0 , 1 ], ![1 , 1 , 0 ], ![1 , 0 , 1 ], ![0 , 1 , 1 ], ![1 , 2 , 3 ]]
111117
112118 test_ks.all fun k =>
113- -- Calculate g^-1 k. Since our group is finite and closed, g^-1 is in group.
114- -- But for checking invariance, checking H(g k) = g H(k) g^-1 is equivalent.
115- -- Let's check: H(g k) = g H(k) g^-1
116- -- LHS: f(g k) * sigma
117- -- RHS: g (f(k) * sigma) g^-1 = f(k) * (g sigma g^-1) = f(k) * sigma * spin_sign(g)
118-
119119 let val_gk := eval_quad q (act_on_k g k)
120120 let val_k := eval_quad q k
121- let s_sgn := spin_sign g s
122121
123- -- We need: val_gk == val_k * s_sgn
124- val_gk == val_k * s_sgn
122+ if s == .I then
123+ -- Scalar term: val_gk == val_k
124+ val_gk == val_k
125+ else
126+ -- Vector term: val_gk * (transformed sigma) == val_k * sigma_s
127+ -- We only support cases where transformed sigma is parallel to sigma_s
128+ -- (i.e. no mixing like x -> y).
129+ -- Let's project transformed sigma onto s.
130+ let s_prime := act_on_spin g s
131+ let coeff := project_spin s_prime s
132+
133+ -- Check if it stays in the same direction (possibly with sign change)
134+ -- And check if orthogonal components are zero (no mixing)
135+ let is_eigen :=
136+ (s == .x && s_prime 1 == 0 && s_prime 2 == 0 ) ||
137+ (s == .y && s_prime 0 == 0 && s_prime 2 == 0 ) ||
138+ (s == .z && s_prime 0 == 0 && s_prime 1 == 0 )
139+
140+ if is_eigen then
141+ val_gk * coeff == val_k
142+ else
143+ false -- If mixing occurs, this single term is not an invariant by itself.
125144
126145def find_invariants : List (QuadTerm × SpinComp) :=
127146 let terms := (all_quads.product [.I, .x, .y, .z])
@@ -170,14 +189,33 @@ def main : IO Unit := do
170189 Demo.Altermagnet.Altermagnet_Group.all fun g =>
171190 let val_gk := f (Demo.Altermagnet.act_on_k g k)
172191 let val_k := f k
173- let s_sgn := Demo.Altermagnet.spin_sign g s
174- val_gk == val_k * s_sgn
192+ -- Re-use projection logic from check_invariant
193+ if s == .I then
194+ val_gk == val_k
195+ else
196+ let s_prime := Demo.Altermagnet.act_on_spin g s
197+ let coeff := Demo.Altermagnet.project_spin s_prime s
198+
199+ -- Check eigenstate property (no mixing)
200+ let is_eigen :=
201+ (s == .x && s_prime 1 == 0 && s_prime 2 == 0 ) ||
202+ (s == .y && s_prime 0 == 0 && s_prime 2 == 0 ) ||
203+ (s == .z && s_prime 0 == 0 && s_prime 1 == 0 )
204+
205+ if is_eigen then
206+ val_gk * coeff == val_k
207+ else
208+ false
175209
176210 let kx2_minus_ky2 (k : SPG.Vec3) : ℚ := k 0 * k 0 - k 1 * k 1
177211 let kx2_plus_ky2 (k : SPG.Vec3) : ℚ := k 0 * k 0 + k 1 * k 1
212+ let kx_ky (k : SPG.Vec3) : ℚ := k 0 * k 1
178213
179214 if check_custom kx2_minus_ky2 .z then
180- IO.println " (kx^2 - ky^2) * σz [Altermagnetic d-wave term!]"
215+ IO.println " (kx^2 - ky^2) * σz [d-wave altermagnetism (x^2-y^2 type)]"
216+
217+ if check_custom kx_ky .z then
218+ IO.println " kx ky * σz [d-wave altermagnetism (xy type)]"
181219
182220 if check_custom kx2_plus_ky2 .I then
183221 IO.println " (kx^2 + ky^2) * I [Standard kinetic term]"
0 commit comments