qflux 0.0.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of qflux might be problematic. Click here for more details.

Files changed (38) hide show
  1. qflux/GQME/__init__.py +7 -0
  2. qflux/GQME/dynamics_GQME.py +438 -0
  3. qflux/GQME/params.py +62 -0
  4. qflux/GQME/readwrite.py +119 -0
  5. qflux/GQME/tdvp.py +233 -0
  6. qflux/GQME/tt_tfd.py +448 -0
  7. qflux/__init__.py +5 -0
  8. qflux/closed_systems/__init__.py +17 -0
  9. qflux/closed_systems/classical_methods.py +427 -0
  10. qflux/closed_systems/custom_execute.py +22 -0
  11. qflux/closed_systems/hamiltonians.py +88 -0
  12. qflux/closed_systems/qubit_methods.py +266 -0
  13. qflux/closed_systems/spin_dynamics_oo.py +371 -0
  14. qflux/closed_systems/spin_propagators.py +300 -0
  15. qflux/closed_systems/utils.py +205 -0
  16. qflux/open_systems/__init__.py +2 -0
  17. qflux/open_systems/dilation_circuit.py +183 -0
  18. qflux/open_systems/numerical_methods.py +303 -0
  19. qflux/open_systems/params.py +29 -0
  20. qflux/open_systems/quantum_simulation.py +360 -0
  21. qflux/open_systems/trans_basis.py +121 -0
  22. qflux/open_systems/walsh_gray_optimization.py +311 -0
  23. qflux/typing/__init__.py +0 -0
  24. qflux/typing/examples.py +24 -0
  25. qflux/utils/__init__.py +0 -0
  26. qflux/utils/io.py +16 -0
  27. qflux/utils/logging_config.py +61 -0
  28. qflux/variational_methods/__init__.py +1 -0
  29. qflux/variational_methods/qmad/__init__.py +0 -0
  30. qflux/variational_methods/qmad/ansatz.py +64 -0
  31. qflux/variational_methods/qmad/ansatzVect.py +61 -0
  32. qflux/variational_methods/qmad/effh.py +75 -0
  33. qflux/variational_methods/qmad/solver.py +356 -0
  34. qflux-0.0.1.dist-info/METADATA +144 -0
  35. qflux-0.0.1.dist-info/RECORD +38 -0
  36. qflux-0.0.1.dist-info/WHEEL +5 -0
  37. qflux-0.0.1.dist-info/licenses/LICENSE +674 -0
  38. qflux-0.0.1.dist-info/top_level.txt +1 -0
@@ -0,0 +1,121 @@
1
+ import numpy as np
2
+ import scipy.fft as sfft
3
+
4
+ #the module in the current package
5
+ from . import params as pa
6
+
7
+ def x2k_wave(dx,psi):
8
+ """
9
+ transform the wavefunction from x space to k space
10
+ dx: the interval of the x-space grid point
11
+ psi: the wavefunction in the x-space
12
+ """
13
+ pre_fac = dx/(2*np.pi)**0.5
14
+ psik = sfft.fft(psi)*pre_fac
15
+ return psik
16
+
17
+ def k2x_wave(dx,psik):
18
+ """
19
+ transform the wavefunction from x space to k space
20
+ dx: the interval of the x-space grid point
21
+ psi: the wavefunction in the k-space
22
+ """
23
+ pre_fac = (2*np.pi)**0.5/dx
24
+ psix = sfft.ifft(psik.copy())*pre_fac
25
+ return psix
26
+
27
+ def nested_kronecker_product(pauli_str):
28
+ '''
29
+ Handles Kronecker Products for list (i.e., pauli_str = 'ZZZ' will evaluate Z Z Z).
30
+ Given string 'pauli_str' this evaluates the kronecker product of all elements.
31
+ '''
32
+
33
+ # Define a dictionary with the four Pauli matrices:
34
+ pms = {'I': pa.I,'X': pa.X,'Y': pa.Y,'Z': pa.Z}
35
+
36
+ result = 1
37
+ for i in range(len(pauli_str)):
38
+ result = np.kron(result,pms[pauli_str[i]])
39
+ return result
40
+
41
+
42
+ def pauli_to_ham(pauli_dict, Nqb):
43
+ '''
44
+ Function that Assembles the Hamiltonian based on their Pauli string
45
+
46
+ pauli_dict: A dictionary that contain all the pauli string and the value of the Hamiltonian
47
+ (the key is pauli string and the value is coefficient)
48
+
49
+ Nqb: number of qubits, should match the length of the pauli string
50
+
51
+ return: Hamiltonian matrix
52
+ '''
53
+
54
+ Hmat = np.zeros((2**Nqb,2**Nqb),dtype=np.complex128)
55
+ for key in pauli_dict:
56
+ Hmat += pauli_dict[key]*nested_kronecker_product(key)
57
+
58
+ return Hmat
59
+
60
+ def ham_to_pauli(Ham_arr, Nqb, tol=1E-5):
61
+ '''
62
+ Function that decomposes `Ham_arr` into a sum of Pauli strings.
63
+ result: a dictionary with the key is pauli string and the value is coefficient
64
+ '''
65
+ import itertools
66
+
67
+ pauli_keys = ['I','X','Y','Z'] # Keys of the dictionary
68
+
69
+ if(2**Nqb != Ham_arr.shape[0]):
70
+ print('Nqb and Matrix size not matched!')
71
+
72
+ # Make all possible tensor products of Pauli matrices sigma
73
+ sigma_combinations = list(itertools.product(pauli_keys, repeat=Nqb))
74
+
75
+ result = {} # Initialize an empty dictionary to the results
76
+ for ii in range(len(sigma_combinations)):
77
+ pauli_str = ''.join(sigma_combinations[ii])
78
+
79
+ # Evaluate the Kronecker product of the matrix array
80
+ tmp_p_matrix = nested_kronecker_product(pauli_str)
81
+
82
+ # Compute the coefficient for each Pauli string
83
+ a_coeff = (1/(2**Nqb)) * np.trace(tmp_p_matrix @ Ham_arr)
84
+
85
+ # If the coefficient is non-zero, we want to use it!
86
+ if abs(a_coeff) > tol:
87
+ result[pauli_str] = a_coeff.real
88
+
89
+ return result
90
+
91
+ def trans_basis(operator,nbasis,psi_newbasis):
92
+ """
93
+ operator: the operator matrix in the old basis
94
+ nbasis: the truncation in the new basis
95
+ psi_newbasis: the new basis wave function expressed in the old basis
96
+ psi_newbasis[i,j] = <i|psi_newbasis|j>, with i is old basis, j new basis
97
+ """
98
+
99
+ operator_new = np.zeros((nbasis,nbasis),dtype=np.complex128)
100
+
101
+ for i in range(nbasis):
102
+ for j in range(nbasis):
103
+ operator_new[i,j] = np.dot(np.dot(psi_newbasis[:,i].conj(),operator),psi_newbasis[:,j])
104
+
105
+ return operator_new
106
+
107
+ def trans_basis_diag(diag_opr,nbasis,psi_newbasis):
108
+ """
109
+ diag_opr: the array represent the diagnoal operator in the old basis
110
+ nbasis: the truncation in the new basis
111
+ psi_newbasis: the new basis wave function expressed in the old basis
112
+ psi_newbasis[i,j] = <i|psi_newbasis|j>, with i is old basis, j new basis
113
+ """
114
+
115
+ operator_new = np.zeros((nbasis,nbasis),dtype=np.complex128)
116
+
117
+ for i in range(nbasis):
118
+ for j in range(nbasis):
119
+ operator_new[i,j] = np.dot(np.multiply(psi_newbasis[:,i].conj(),diag_opr),psi_newbasis[:,j])
120
+
121
+ return operator_new
@@ -0,0 +1,311 @@
1
+ import numpy as np
2
+ from qiskit import QuantumRegister, QuantumCircuit
3
+
4
+ from .params import I,X,Y,Z
5
+
6
+ ##=======the Walsh operator scheme===========
7
+ #1. functions for Walsh representation
8
+ #(a). the binary and qubit state representation of a integer
9
+ #(b). the function to calculate walsh coefficient
10
+ #this is binary representation of a integer j!
11
+ #j=[j1,j2,j3] for example: 3=[1,1,0]
12
+ def binary(j,nbits):
13
+ arr = np.zeros(nbits,dtype=int)
14
+ res = j
15
+ for i in range(nbits):
16
+ if(res>1):
17
+ arr[i] = res%2
18
+ res=res//2
19
+ else:
20
+ arr[i] = res
21
+ break
22
+ return arr
23
+
24
+ #input: array of 0 or 1
25
+ #output: max index of 1
26
+ #e.g.: j=4 binary is [0,0,1,0], max_bit is 2
27
+ def max_bit(arr):
28
+ iflag = 0
29
+ for i in range(len(arr)-1,-1,-1):
30
+ if(arr[i]==1):
31
+ iflag = i
32
+ break
33
+ return iflag
34
+
35
+ #j-th gray code:
36
+ #first do the right shift of the binary representation
37
+ #then do the bit wise XOR
38
+ def gray(j,nbits):
39
+ arr_n1 = binary(j//2,nbits) #
40
+ arr_j = binary(j,nbits) #
41
+ arr_res = np.zeros_like(arr_n1)
42
+ for i in range(nbits):
43
+ if(arr_n1[i]==arr_j[i]):
44
+ arr_res[i] = 0
45
+ else:
46
+ arr_res[i] = 1
47
+ return arr_res
48
+
49
+ # return the decimal of arr_j
50
+ # [1,1,0] is j0=1,j1=1, which is 3
51
+ def decimal(arr_j):
52
+ res = 0
53
+ nbits = len(arr_j)
54
+ for i in range(nbits):
55
+ res+= 2**i*arr_j[i]
56
+ return res
57
+
58
+ #2. The Walsh coefficient and operator
59
+ #input an fk array
60
+ #(that's the discretize array in qubit statevector) |qn,...,q1>
61
+ #e.g. f3 is the coefficient of |0,1,1>, which is q0=1, q1=1
62
+ #this just match the definition of function binary
63
+ #output the coefficient in the walsh function representation
64
+ def walsh_coef(fk,nbits):
65
+ nlen = 2**nbits
66
+ if(nlen!=len(fk)): print('walsh_coef: err in array length')
67
+
68
+ a_arr = np.zeros(nlen)
69
+ for i in range(nlen):
70
+ arr_j = binary(i,nbits)
71
+ for k in range(nlen):
72
+ fac = (-1)**(np.dot(arr_j,binary(k,nbits)))
73
+ a_arr[i] += fk[k]*fac
74
+ a_arr[i] /= nlen
75
+ return a_arr
76
+
77
+ #walsh operator, \Prod \otimes Z^(j)
78
+ #j=[1,1,0] return IZZ (q0,q1 is Z, q2 is I, in qiskit convention order)
79
+ def walsh_oprQ(arr_j,nbits):
80
+ res = 1.0
81
+ for i in range(nbits-1,-1,-1):
82
+ if(arr_j[i]==0):
83
+ res = np.kron(res,I)
84
+ elif(arr_j[i]==1):
85
+ res = np.kron(res,Z)
86
+ else:
87
+ print('err')
88
+ return res
89
+
90
+
91
+ #3. Walsh circuit list and Gray code optimize for diagonal unitaries
92
+ # functions to generate the quantum circuit of the diagnoal unitaries in the Walsh Operator representation
93
+ #input an walsh coefficient (arra) of f_k array,
94
+ #output the list that contain all the parameters to construct the circuit for U=diag(e^(i f_k))
95
+ def cirq_list_walsh(arra,Nqb,epsilon):
96
+ if(len(arra)!=2**Nqb): print('err')
97
+
98
+ U=[]
99
+
100
+ for j in range(1,2**Nqb):
101
+
102
+ #encoding: Gray Code or Binary Code
103
+ #arr_j = binary(j,Nqb)
104
+ arr_j = gray(j,Nqb)
105
+ j_code = decimal(gray(j,Nqb))
106
+
107
+ if(abs(arra[j_code])<epsilon): continue
108
+
109
+ max_index = max_bit(arr_j) #the R gate will located at the max index
110
+
111
+ for ibit in range(max_index):
112
+ if(arr_j[ibit]==1):
113
+ U.append(('C',ibit,max_index))
114
+
115
+ U.append(('R',max_index,-2.0*arra[j_code]))
116
+
117
+ for ibit in range(max_index-1,-1,-1):
118
+ if(arr_j[ibit]==1):
119
+ U.append(('C',ibit,max_index))
120
+ return U
121
+
122
+ #generate circuit from list of turple U
123
+ def cirq_from_U(U,Nqb):
124
+ nlen = len(U)
125
+
126
+ #the quantum circuit
127
+ qr = QuantumRegister(Nqb)
128
+ #cr = ClassicalRegister(1)
129
+ qc = QuantumCircuit(qr)
130
+
131
+ for i in range(nlen):
132
+ gate_str = U[i]
133
+ if(gate_str[0]=='C'):
134
+ qc.cx(gate_str[1],gate_str[2])
135
+ elif(gate_str[0]=='R'):
136
+ qc.rz(gate_str[2],gate_str[1])
137
+ else:
138
+ print('err in reading circuit')
139
+ return qc
140
+
141
+
142
+ #Optimize the quantum circuit
143
+ def optimize(U):
144
+ U0,iflag0 = scanI(U)
145
+ U0,iflag1 = scanRule2(U0)
146
+
147
+ #some possible exchange and check
148
+ i = 0
149
+ while(i<len(U0)-1):
150
+
151
+ if(U0[i+1][0] == 'C' and U0[i][0] =='C'):
152
+
153
+ ictrl = U0[i][1]; itarg = U0[i][2]
154
+ if(ictrl == U0[i+1][1] or itarg == U0[i+1][2]):
155
+ Utmp = U0.copy()
156
+ Utmp[i] = U0[i+1]
157
+ Utmp[i+1] = U0[i]
158
+ Utmp,iflag0 = scanI(Utmp)
159
+ Utmp,iflag1 = scanRule2(Utmp)
160
+
161
+ #if(iflag0 or iflag1): U0 = Utmp
162
+ U0 = Utmp
163
+
164
+ i += 1
165
+ return U0
166
+
167
+ #scan if there are identical adjacent CNOT gate
168
+ def scanI(U):
169
+ nlen = len(U)
170
+ newU = []
171
+ i=0
172
+ iflag = 0
173
+ while(i<nlen-1):
174
+ if(U[i]==U[i+1] and U[i][0] =='C'):
175
+ i += 2
176
+ iflag = 1
177
+ else:
178
+ newU.append(U[i])
179
+ i += 1
180
+ #end
181
+ if(i==nlen-1): newU.append(U[nlen-1])
182
+ return newU,iflag
183
+
184
+ #scan if there have 3 gate can reduced to:
185
+ #the target of one CNOT is the control of another.
186
+ #CijCjk = CjkCikCij
187
+ def scanRule2(U):
188
+ nlen = len(U)
189
+ newU = []
190
+ i=0
191
+ iflag = 0
192
+ while(i<nlen-2):
193
+
194
+ if(U[i][0] == U[i+1][0] == U[i+2][0] =='C'):
195
+ sj = U[i][1]
196
+ sk = U[i][2]
197
+ si = U[i+1][1]
198
+ if(sj == U[i+2][2] and si == U[i+2][1] and sk == U[i+1][2]):
199
+ i += 3
200
+ newU.append(('C',si,sj))
201
+ newU.append(('C',sj,sk))
202
+ print('Rule2')
203
+ iflag = 1
204
+ else:
205
+ newU.append(U[i])
206
+ i += 1
207
+ else:
208
+ newU.append(U[i])
209
+ i += 1
210
+ #end
211
+ if(i==nlen-2):
212
+ newU.append(U[nlen-2])
213
+ newU.append(U[nlen-1])
214
+ return newU,iflag
215
+ ##=======end the Walsh operator scheme===========
216
+
217
+ ##=======test functions for Walsh operator scheme=========
218
+ #extented matrix of the quantum gate
219
+ #note that this is the reverse order with qiskit convention
220
+ def ext_cx(ictrl,itarg,Nqb):
221
+ if(ictrl>=Nqb or itarg>=Nqb): print('err')
222
+ Proj00 = np.array([[1.0,0],[0,0]])
223
+ Proj11 = np.array([[0.0,0],[0,1]])
224
+
225
+ res00 = 1
226
+ res11 = 1
227
+ for i in range(Nqb-1,-1,-1):
228
+ #for i in range(Nqb):
229
+ if(i!=ictrl and i!=itarg):
230
+ res00 = np.kron(res00,I)
231
+ res11 = np.kron(res11,I)
232
+ if(i==ictrl):
233
+ res00 = np.kron(res00,Proj00)
234
+ res11 = np.kron(res11,Proj11)
235
+ if(i==itarg):
236
+ res00 = np.kron(res00,I)
237
+ res11 = np.kron(res11,X)
238
+ return res00+res11
239
+
240
+ def ext_Rz(phi,iqb,Nqb):
241
+ if(iqb>=Nqb): print('err')
242
+ mat_rz = np.array([[np.exp(-1j*phi/2),0],[0,np.exp(1j*phi/2)]])
243
+ res = 1
244
+ for i in range(Nqb-1,-1,-1):
245
+ #for i in range(Nqb):
246
+ if(i!=iqb):
247
+ res = np.kron(res,I)
248
+ if(i==iqb):
249
+ res = np.kron(res,mat_rz)
250
+ return res
251
+
252
+ def ext_Z(iqb,Nqb):
253
+ if(iqb>=Nqb): print('err')
254
+ res = 1
255
+ for i in range(Nqb-1,-1,-1):
256
+ #for i in range(Nqb):
257
+ if(i!=iqb):
258
+ res = np.kron(res,I)
259
+ if(i==iqb):
260
+ res = np.kron(res,Z)
261
+ return res
262
+
263
+ #generate matrix of quantum circuit from list of turple U
264
+ #convient for verify the result
265
+ def cirqmat_from_U(U,Nqb):
266
+ nlen = len(U)
267
+
268
+ Umat = []
269
+
270
+ for i in range(nlen):
271
+ gate_str = U[i]
272
+ if(gate_str[0]=='C'):
273
+ Umat.append(ext_cx(gate_str[1],gate_str[2],Nqb))
274
+ elif(gate_str[0]=='R'):
275
+ Umat.append(ext_Rz(gate_str[2],gate_str[1],Nqb))
276
+ else:
277
+ print('err in reading circuit')
278
+
279
+ #connect all the gates
280
+ res = Umat[-1]
281
+ for i in range(nlen-2,-1,-1):
282
+ res = res@Umat[i]
283
+ return res
284
+
285
+ #input an walsh coefficient (arra) of f_k array, output the circuit matrix for U=diag(e^(i f_k))
286
+ def cirqmat_walsh(arra,Nqb,epsilon):
287
+ if(len(arra)!=2**Nqb): print('err')
288
+
289
+ U = []
290
+
291
+ for j in range(1,2**Nqb):
292
+ if(abs(arra[j])<epsilon): continue
293
+ arr_j = binary(j,Nqb)
294
+ max_index = max_bit(arr_j) #the R gate will located at the max index
295
+
296
+ for ibit in range(max_index):
297
+ if(arr_j[ibit]==1):
298
+ U.append(ext_cx(ibit,max_index,Nqb))
299
+
300
+ U.append(ext_Rz(-2*arra[j],max_index,Nqb))
301
+
302
+ for ibit in range(max_index-1,-1,-1):
303
+ if(arr_j[ibit]==1):
304
+ U.append(ext_cx(ibit,max_index,Nqb))
305
+
306
+ #connect all the gates
307
+ res = U[-1]
308
+ for i in range(len(U)-2,-1,-1):
309
+ res = res@U[i]
310
+ return res
311
+ #============================
File without changes
@@ -0,0 +1,24 @@
1
+ from typing import Annotated
2
+
3
+ import numpy as np
4
+ from numpy.typing import NDArray
5
+
6
+ # Basic numerical types
7
+ type Real = float | np.float64
8
+ type Complex = complex | np.complex128
9
+
10
+ # General vector and matrix types
11
+ type FloatVector = NDArray[np.float64] # Shape: (n,)
12
+ type ComplexVector = NDArray[np.complex128] # Shape: (n,)
13
+ type Float2D = NDArray[np.float64] # Shape: (n, m)
14
+ type Complex2D = NDArray[np.complex128] # Shape: (n, m)
15
+
16
+ type AtomicCoordinates = NDArray[np.float64] # Shape: (n_atoms, 3)
17
+ type AtomicNumbers = NDArray[np.int32] # Shape: (n_atoms,)
18
+
19
+ # Type for atomic symbols with validation
20
+ type AtomicSymbol = Annotated[str, "Valid chemical element symbol"]
21
+
22
+ # Energy types with units (keeping these as they're fundamental)
23
+ type Energy = Annotated[float, "Energy in Hartree"]
24
+ type EnergyEV = Annotated[float, "Energy in electron volts"]
File without changes
qflux/utils/io.py ADDED
@@ -0,0 +1,16 @@
1
+ from pathlib import Path
2
+
3
+
4
+ def load_dataset(ds_path: Path) -> None:
5
+ """Loads a dataset from the specified path.
6
+
7
+ This function is intended to load a dataset from the given file path.
8
+ Currently, it is not implemented and always raises a NotImplementedError.
9
+
10
+ Args:
11
+ ds_path: The path to the dataset file.
12
+
13
+ Raises:
14
+ NotImplementedError: Always raised as the function is not yet implemented.
15
+ """
16
+ raise NotImplementedError("Not implemented")
@@ -0,0 +1,61 @@
1
+ import logging
2
+ import logging.config
3
+ import os
4
+ from pathlib import Path
5
+
6
+ import tomli
7
+
8
+
9
+ def setup_logging() -> None:
10
+ """Configures logging for the application.
11
+
12
+ Reads the logging configuration from the 'pyproject.toml' file
13
+ and applies it using `logging.config.dictConfig`.
14
+
15
+ The log level can be overridden by setting the
16
+ 'QFLUX_LOG_LEVEL' environment variable. If the environment
17
+ variable is not set or set to an invalid level, it defaults to 'INFO'.
18
+
19
+ Raises:
20
+ FileNotFoundError: If 'pyproject.toml' is not found.
21
+ tomli.TOMLDecodeError: If 'pyproject.toml' is not a valid TOML file.
22
+ KeyError: If the 'tool.logging' section is missing in 'pyproject.toml'.
23
+ """
24
+ # Construct the path to pyproject.toml, assuming it's 3 levels up from this file.
25
+ pyproject_path = Path(__file__).parents[3] / "pyproject.toml"
26
+
27
+ try:
28
+ with open(pyproject_path, "rb") as f:
29
+ config = tomli.load(f)
30
+ except FileNotFoundError as e:
31
+ raise FileNotFoundError(f"pyproject.toml not found at {pyproject_path}: {e}") from e
32
+ except tomli.TOMLDecodeError as e:
33
+ raise tomli.TOMLDecodeError(f"Failed to decode pyproject.toml: {e}") from e
34
+
35
+ # Determine the log level:
36
+ # 1. Check for QFLUX_LOG_LEVEL environment variable.
37
+ # 2. Default to 'INFO' if not set or invalid.
38
+ log_level_env = os.getenv("QFLUX_LOG_LEVEL", "INFO").upper()
39
+
40
+ valid_levels = {"DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"}
41
+ if log_level_env not in valid_levels:
42
+ print(f"Invalid log level '{log_level_env}' from environment, defaulting to INFO")
43
+ log_level = "INFO"
44
+ else:
45
+ log_level = log_level_env
46
+
47
+ try:
48
+ # Extract logging configuration from pyproject.toml
49
+ logging_config = config["tool"]["logging"]
50
+ except KeyError as e:
51
+ raise KeyError(f"'tool.logging' section not found in pyproject.toml: {e}") from e
52
+
53
+ # Override the log level specified in pyproject.toml with the determined log level.
54
+ logging_config["loggers"]["qflux"]["level"] = log_level
55
+
56
+ # Configure logging using the dictionary configuration.
57
+ logging.config.dictConfig(logging_config)
58
+
59
+
60
+ # Get the logger for the 'qflux' application.
61
+ logger = logging.getLogger("qflux")
@@ -0,0 +1 @@
1
+ # initialization
File without changes
@@ -0,0 +1,64 @@
1
+ import numpy as np
2
+ from itertools import combinations, product
3
+ from scipy.linalg import expm, kron
4
+
5
+ # Ansatz Class and Operators
6
+
7
+ class Ansatz_class:
8
+ def __init__(self, nqbit, u0, relrcut, pool, theta=[], ansatz=[]):
9
+ self.theta = np.array(theta)
10
+ self.A = ansatz
11
+ self.state = u0.copy()
12
+ self.ref = u0.copy()
13
+ self.relrcut = relrcut
14
+ self.nqbit = nqbit
15
+ self.pool = pool
16
+
17
+ class AnsatzOperatorBase:
18
+ pass
19
+
20
+ class PauliOperator_class(AnsatzOperatorBase):
21
+ def __init__(self, mat, tag, nqbit):
22
+ self.mat = mat
23
+ self.tag = tag
24
+ self.nqbit = nqbit
25
+
26
+ def single_clause(ops, q_ind, weight, num_qubit):
27
+ si = np.eye(2)
28
+ res = weight * np.eye(1)
29
+ for i in range(1, num_qubit + 1):
30
+ if i in q_ind:
31
+ op2 = eval(f"{ops[q_ind.index(i)]}")
32
+ res = kron(res, op2)
33
+ else:
34
+ res = kron(res, si)
35
+ return res
36
+
37
+ def PauliOperator(ops, idx, w, nqbit):
38
+ sortedRep = sorted(zip(ops, idx), key=lambda x: x[1])
39
+ tag = ''.join([f"{o}{i}" for o, i in sortedRep])
40
+ mat = single_clause(ops, idx, w, nqbit)
41
+ return PauliOperator_class(mat, tag, nqbit)
42
+
43
+ # Defining pool of operators/gates
44
+ sx = np.array([[0, 1], [1, 0]])
45
+ sy = np.array([[0, -1j], [1j, 0]])
46
+ sz = np.array([[1, 0], [0, -1]])
47
+ # S = np.array([[1, 0], [0, 1j]])
48
+
49
+
50
+ def build_pool(nqbit):
51
+ pauliStr = ["sx", "sz", "sy"]
52
+ res = []
53
+ for order in range(1, nqbit+1):
54
+ for idx in combinations(range(1, nqbit + 1), order):
55
+ for op in product(pauliStr, repeat=order):
56
+ res.append(PauliOperator(op, list(idx), 1, nqbit))
57
+ return res
58
+
59
+ def Ansatz(u0, relrcut, theta=[], ansatz=[]):
60
+ nqbit = int(np.log2(len(u0)))
61
+ pool_qubit = nqbit
62
+ # u0 = np.outer(u0, u0).flatten()
63
+ pool = build_pool(pool_qubit)
64
+ return Ansatz_class(nqbit, u0, relrcut, pool, theta, ansatz)
@@ -0,0 +1,61 @@
1
+ import numpy as np
2
+ from itertools import combinations, product
3
+ from scipy.linalg import expm, kron
4
+
5
+ # Ansatz Class and Operators
6
+ class Ansatz_class:
7
+ def __init__(self, nqbit, u0, relrcut, pool, theta=[], ansatz=[]):
8
+ self.theta = np.array(theta)
9
+ self.A = ansatz
10
+ self.state = u0.copy()
11
+ self.ref = u0.copy()
12
+ self.relrcut = relrcut
13
+ self.nqbit = nqbit
14
+ self.pool = pool
15
+
16
+ class AnsatzOperatorBase:
17
+ pass
18
+
19
+ class PauliOperator_class(AnsatzOperatorBase):
20
+ def __init__(self, mat, tag, nqbit):
21
+ self.mat = mat
22
+ self.tag = tag
23
+ self.nqbit = nqbit
24
+
25
+ def single_clause(ops, q_ind, weight, num_qubit):
26
+ si = np.eye(2)
27
+ res = weight * np.eye(1)
28
+ for i in range(1, num_qubit + 1):
29
+ if i in q_ind:
30
+ op2 = eval(f"{ops[q_ind.index(i)]}")
31
+ res = kron(res, op2)
32
+ else:
33
+ res = kron(res, si)
34
+ return res
35
+
36
+ def PauliOperator(ops, idx, w, nqbit):
37
+ sortedRep = sorted(zip(ops, idx), key=lambda x: x[1])
38
+ tag = ''.join([f"{o}{i}" for o, i in sortedRep])
39
+ mat = single_clause(ops, idx, w, nqbit)
40
+ return PauliOperator_class(mat, tag, nqbit)
41
+
42
+ # Defining pool of operators/gates
43
+ sx = np.array([[0, 1], [1, 0]])
44
+ sy = np.array([[0, -1j], [1j, 0]])
45
+ sz = np.array([[1, 0], [0, -1]])
46
+
47
+ def build_pool(nqbit):
48
+ pauliStr = ["sx", "sz", "sy"]
49
+ res = []
50
+ for order in range(1, nqbit+1):
51
+ for idx in combinations(range(1, nqbit + 1), order):
52
+ for op in product(pauliStr, repeat=order):
53
+ res.append(PauliOperator(op, list(idx), 1, nqbit))
54
+ return res
55
+
56
+ def Ansatz(u0, relrcut, theta=[], ansatz=[]):
57
+ nqbit = int(np.log2(len(u0)))
58
+ pool_qubit = 2 * nqbit
59
+ u0 = np.outer(u0, u0).flatten()
60
+ pool = build_pool(pool_qubit)
61
+ return Ansatz_class(nqbit, u0, relrcut, pool, theta, ansatz)