1
1
"""
2
2
Fermion Gaussian state simulator
3
3
"""
4
- from typing import Any , List , Optional , Tuple
4
+ from typing import Any , Dict , List , Optional , Tuple
5
5
6
6
import numpy as np
7
7
@@ -74,8 +74,10 @@ def __init__(
74
74
_ , _ , self .alpha = self .fermion_diagonalization (hc , L )
75
75
else :
76
76
self .alpha = alpha
77
+ self .alpha0 = self .alpha
77
78
self .wtransform = self .wmatrix (L )
78
79
self .cmatrix = cmatrix
80
+ self .otcmatrix : Dict [Tuple [int , int ], Tensor ] = {}
79
81
80
82
@classmethod
81
83
def fermion_diagonalization (
@@ -141,13 +143,35 @@ def init_alpha(filled: List[int], L: int) -> Tensor:
141
143
def get_alpha (self ) -> Tensor :
142
144
return self .alpha
143
145
144
- def get_cmatrix (self ) -> Tensor :
145
- if self .cmatrix is not None :
146
+ def get_cmatrix (self , now_i : bool = True , now_j : bool = True ) -> Tensor :
147
+ # otoc in FGS language, see: https://arxiv.org/pdf/1908.03292.pdf
148
+ # https://journals.aps.org/prb/pdf/10.1103/PhysRevB.99.054205
149
+ # otoc for non=hermitian system is more subtle due to the undefined normalization
150
+ # of operator and not considered here, see: https://arxiv.org/pdf/2305.12054.pdf
151
+ if self .cmatrix is not None and now_i is True and now_j is True :
146
152
return self .cmatrix
147
- else :
153
+ elif now_i is True and now_j is True :
148
154
cmatrix = self .alpha @ backend .adjoint (self .alpha )
149
155
self .cmatrix = cmatrix
150
156
return cmatrix
157
+ elif now_i is True : # new i old j
158
+ if (1 , 0 ) in self .otcmatrix :
159
+ return self .otcmatrix [(1 , 0 )]
160
+ cmatrix = self .alpha @ backend .adjoint (self .alpha0 )
161
+ self .otcmatrix [(1 , 0 )] = cmatrix
162
+ return cmatrix
163
+ elif now_j is True : # old i new j
164
+ if (0 , 1 ) in self .otcmatrix :
165
+ return self .otcmatrix [(0 , 1 )]
166
+ cmatrix = self .alpha0 @ backend .adjoint (self .alpha )
167
+ self .otcmatrix [(0 , 1 )] = cmatrix
168
+ return cmatrix
169
+ else : # initial cmatrix
170
+ if (0 , 0 ) in self .otcmatrix :
171
+ return self .otcmatrix [(0 , 0 )]
172
+ cmatrix = self .alpha0 @ backend .adjoint (self .alpha0 )
173
+ self .otcmatrix [(0 , 0 )] = cmatrix
174
+ return cmatrix
151
175
152
176
def get_reduced_cmatrix (self , subsystems_to_trace_out : List [int ]) -> Tensor :
153
177
m = self .get_cmatrix ()
@@ -216,6 +240,7 @@ def evol_hamiltonian(self, h: Tensor) -> None:
216
240
h = backend .cast (h , dtype = dtypestr )
217
241
self .alpha = backend .expm (- 1.0j * h ) @ self .alpha
218
242
self .cmatrix = None
243
+ self .otcmatrix = {}
219
244
220
245
def evol_ihamiltonian (self , h : Tensor ) -> None :
221
246
r"""
@@ -229,6 +254,7 @@ def evol_ihamiltonian(self, h: Tensor) -> None:
229
254
self .alpha = backend .expm (h ) @ self .alpha
230
255
self .orthogonal ()
231
256
self .cmatrix = None
257
+ self .otcmatrix = {}
232
258
233
259
def evol_ghamiltonian (self , h : Tensor ) -> None :
234
260
r"""
@@ -242,6 +268,7 @@ def evol_ghamiltonian(self, h: Tensor) -> None:
242
268
self .alpha = backend .expm (- 1.0j * backend .adjoint (h )) @ self .alpha
243
269
self .orthogonal ()
244
270
self .cmatrix = None
271
+ self .otcmatrix = {}
245
272
246
273
def orthogonal (self ) -> None :
247
274
q , _ = backend .qr (self .alpha )
@@ -350,7 +377,9 @@ def get_covariance_matrix(self) -> Tensor:
350
377
m = self .get_cmatrix_majorana ()
351
378
return - 1.0j * (2 * m - backend .eye (self .L * 2 ))
352
379
353
- def expectation_2body (self , i : int , j : int ) -> Tensor :
380
+ def expectation_2body (
381
+ self , i : int , j : int , now_i : bool = True , now_j : bool = True
382
+ ) -> Tensor :
354
383
"""
355
384
expectation of two fermion terms
356
385
convention: (c, c^\dagger)
@@ -363,7 +392,7 @@ def expectation_2body(self, i: int, j: int) -> Tensor:
363
392
:return: _description_
364
393
:rtype: Tensor
365
394
"""
366
- return self .get_cmatrix ()[i ][(j + self .L ) % (2 * self .L )]
395
+ return self .get_cmatrix (now_i , now_j )[i ][(j + self .L ) % (2 * self .L )]
367
396
368
397
def expectation_4body (self , i : int , j : int , k : int , l : int ) -> Tensor :
369
398
"""
@@ -520,6 +549,7 @@ def __init__(
520
549
self .state = self .fermion_diagonalization (hc , L )
521
550
else :
522
551
self .state = self .init_state (filled , L )
552
+ self .state0 = self .state
523
553
524
554
@staticmethod
525
555
def init_state (filled : List [int ], L : int ) -> Tensor :
@@ -621,6 +651,140 @@ def evol_sp(self, i: int, j: int, chi: Tensor = 0) -> None:
621
651
def orthogonal (self ) -> None :
622
652
self .state /= backend .norm (self .state )
623
653
654
+ def get_ot_cmatrix (self , h : Tensor , now_i : bool = True ) -> Tensor :
655
+ alpha1_jw = self .state
656
+ cmatrix = np .zeros ([2 * self .L , 2 * self .L ], dtype = complex )
657
+ for i in range (self .L ):
658
+ for j in range (self .L ):
659
+ op1 = openfermion .FermionOperator (f"{ str (i )} " )
660
+ op2 = openfermion .FermionOperator (f"{ str (j )} ^" )
661
+
662
+ m1 = openfermion .get_sparse_operator (op1 , n_qubits = self .L ).todense ()
663
+ m2 = openfermion .get_sparse_operator (op2 , n_qubits = self .L ).todense ()
664
+ eh = npb .expm (- 1 / 2 * 1.0j * h )
665
+ eh1 = npb .expm (1 / 2 * 1.0j * h )
666
+ if now_i is True :
667
+ cmatrix [i , j ] = backend .item (
668
+ (
669
+ backend .reshape (backend .adjoint (alpha1_jw ), [1 , - 1 ])
670
+ @ eh1
671
+ @ m1
672
+ @ eh
673
+ @ m2
674
+ @ backend .reshape (alpha1_jw , [- 1 , 1 ])
675
+ )[0 , 0 ]
676
+ )
677
+ else :
678
+ cmatrix [i , j ] = backend .item (
679
+ (
680
+ backend .reshape (backend .adjoint (alpha1_jw ), [1 , - 1 ])
681
+ @ m1
682
+ @ eh1
683
+ @ m2
684
+ @ eh
685
+ @ backend .reshape (alpha1_jw , [- 1 , 1 ])
686
+ )[0 , 0 ]
687
+ )
688
+ for i in range (self .L , 2 * self .L ):
689
+ for j in range (self .L ):
690
+ op1 = openfermion .FermionOperator (f"{ str (i - self .L )} ^" )
691
+ op2 = openfermion .FermionOperator (f"{ str (j )} ^" )
692
+
693
+ m1 = openfermion .get_sparse_operator (op1 , n_qubits = self .L ).todense ()
694
+ m2 = openfermion .get_sparse_operator (op2 , n_qubits = self .L ).todense ()
695
+ eh = npb .expm (- 1 / 2 * 1.0j * h )
696
+ eh1 = npb .expm (1 / 2 * 1.0j * h )
697
+
698
+ if now_i is True :
699
+ cmatrix [i , j ] = backend .item (
700
+ (
701
+ backend .reshape (backend .adjoint (alpha1_jw ), [1 , - 1 ])
702
+ @ eh1
703
+ @ m1
704
+ @ eh
705
+ @ m2
706
+ @ backend .reshape (alpha1_jw , [- 1 , 1 ])
707
+ )[0 , 0 ]
708
+ )
709
+ else :
710
+ cmatrix [i , j ] = backend .item (
711
+ (
712
+ backend .reshape (backend .adjoint (alpha1_jw ), [1 , - 1 ])
713
+ @ m1
714
+ @ eh1
715
+ @ m2
716
+ @ eh
717
+ @ backend .reshape (alpha1_jw , [- 1 , 1 ])
718
+ )[0 , 0 ]
719
+ )
720
+
721
+ for i in range (self .L ):
722
+ for j in range (self .L , 2 * self .L ):
723
+ op1 = openfermion .FermionOperator (f"{ str (i )} " )
724
+ op2 = openfermion .FermionOperator (f"{ str (j - self .L )} " )
725
+
726
+ m1 = openfermion .get_sparse_operator (op1 , n_qubits = self .L ).todense ()
727
+ m2 = openfermion .get_sparse_operator (op2 , n_qubits = self .L ).todense ()
728
+ eh = npb .expm (- 1 / 2 * 1.0j * h )
729
+ eh1 = npb .expm (1 / 2 * 1.0j * h )
730
+
731
+ if now_i is True :
732
+ cmatrix [i , j ] = backend .item (
733
+ (
734
+ backend .reshape (backend .adjoint (alpha1_jw ), [1 , - 1 ])
735
+ @ eh1
736
+ @ m1
737
+ @ eh
738
+ @ m2
739
+ @ backend .reshape (alpha1_jw , [- 1 , 1 ])
740
+ )[0 , 0 ]
741
+ )
742
+ else :
743
+ cmatrix [i , j ] = backend .item (
744
+ (
745
+ backend .reshape (backend .adjoint (alpha1_jw ), [1 , - 1 ])
746
+ @ m1
747
+ @ eh1
748
+ @ m2
749
+ @ eh
750
+ @ backend .reshape (alpha1_jw , [- 1 , 1 ])
751
+ )[0 , 0 ]
752
+ )
753
+ for i in range (self .L , 2 * self .L ):
754
+ for j in range (self .L , 2 * self .L ):
755
+ op1 = openfermion .FermionOperator (f"{ str (i - self .L )} ^ " )
756
+ op2 = openfermion .FermionOperator (f"{ str (j - self .L )} " )
757
+
758
+ m1 = openfermion .get_sparse_operator (op1 , n_qubits = self .L ).todense ()
759
+ m2 = openfermion .get_sparse_operator (op2 , n_qubits = self .L ).todense ()
760
+ eh = npb .expm (- 1 / 2 * 1.0j * h )
761
+ eh1 = npb .expm (1 / 2 * 1.0j * h )
762
+
763
+ if now_i is True :
764
+ cmatrix [i , j ] = backend .item (
765
+ (
766
+ backend .reshape (backend .adjoint (alpha1_jw ), [1 , - 1 ])
767
+ @ eh1
768
+ @ m1
769
+ @ eh
770
+ @ m2
771
+ @ backend .reshape (alpha1_jw , [- 1 , 1 ])
772
+ )[0 , 0 ]
773
+ )
774
+ else :
775
+ cmatrix [i , j ] = backend .item (
776
+ (
777
+ backend .reshape (backend .adjoint (alpha1_jw ), [1 , - 1 ])
778
+ @ m1
779
+ @ eh1
780
+ @ m2
781
+ @ eh
782
+ @ backend .reshape (alpha1_jw , [- 1 , 1 ])
783
+ )[0 , 0 ]
784
+ )
785
+
786
+ return cmatrix
787
+
624
788
def get_cmatrix (self ) -> Tensor :
625
789
alpha1_jw = self .state
626
790
cmatrix = np .zeros ([2 * self .L , 2 * self .L ], dtype = complex )
0 commit comments