11"""
22Fermion Gaussian state simulator
33"""
4- from typing import Any , List , Optional , Tuple
4+ from typing import Any , Dict , List , Optional , Tuple
55
66import numpy as np
77
@@ -74,8 +74,10 @@ def __init__(
7474 _ , _ , self .alpha = self .fermion_diagonalization (hc , L )
7575 else :
7676 self .alpha = alpha
77+ self .alpha0 = self .alpha
7778 self .wtransform = self .wmatrix (L )
7879 self .cmatrix = cmatrix
80+ self .otcmatrix : Dict [Tuple [int , int ], Tensor ] = {}
7981
8082 @classmethod
8183 def fermion_diagonalization (
@@ -141,13 +143,35 @@ def init_alpha(filled: List[int], L: int) -> Tensor:
141143 def get_alpha (self ) -> Tensor :
142144 return self .alpha
143145
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 :
146152 return self .cmatrix
147- else :
153+ elif now_i is True and now_j is True :
148154 cmatrix = self .alpha @ backend .adjoint (self .alpha )
149155 self .cmatrix = cmatrix
150156 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
151175
152176 def get_reduced_cmatrix (self , subsystems_to_trace_out : List [int ]) -> Tensor :
153177 m = self .get_cmatrix ()
@@ -216,6 +240,7 @@ def evol_hamiltonian(self, h: Tensor) -> None:
216240 h = backend .cast (h , dtype = dtypestr )
217241 self .alpha = backend .expm (- 1.0j * h ) @ self .alpha
218242 self .cmatrix = None
243+ self .otcmatrix = {}
219244
220245 def evol_ihamiltonian (self , h : Tensor ) -> None :
221246 r"""
@@ -229,6 +254,7 @@ def evol_ihamiltonian(self, h: Tensor) -> None:
229254 self .alpha = backend .expm (h ) @ self .alpha
230255 self .orthogonal ()
231256 self .cmatrix = None
257+ self .otcmatrix = {}
232258
233259 def evol_ghamiltonian (self , h : Tensor ) -> None :
234260 r"""
@@ -242,6 +268,7 @@ def evol_ghamiltonian(self, h: Tensor) -> None:
242268 self .alpha = backend .expm (- 1.0j * backend .adjoint (h )) @ self .alpha
243269 self .orthogonal ()
244270 self .cmatrix = None
271+ self .otcmatrix = {}
245272
246273 def orthogonal (self ) -> None :
247274 q , _ = backend .qr (self .alpha )
@@ -350,7 +377,9 @@ def get_covariance_matrix(self) -> Tensor:
350377 m = self .get_cmatrix_majorana ()
351378 return - 1.0j * (2 * m - backend .eye (self .L * 2 ))
352379
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 :
354383 """
355384 expectation of two fermion terms
356385 convention: (c, c^\dagger)
@@ -363,7 +392,7 @@ def expectation_2body(self, i: int, j: int) -> Tensor:
363392 :return: _description_
364393 :rtype: Tensor
365394 """
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 )]
367396
368397 def expectation_4body (self , i : int , j : int , k : int , l : int ) -> Tensor :
369398 """
@@ -520,6 +549,7 @@ def __init__(
520549 self .state = self .fermion_diagonalization (hc , L )
521550 else :
522551 self .state = self .init_state (filled , L )
552+ self .state0 = self .state
523553
524554 @staticmethod
525555 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:
621651 def orthogonal (self ) -> None :
622652 self .state /= backend .norm (self .state )
623653
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+
624788 def get_cmatrix (self ) -> Tensor :
625789 alpha1_jw = self .state
626790 cmatrix = np .zeros ([2 * self .L , 2 * self .L ], dtype = complex )
0 commit comments