qsppack 0.1.0__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.
@@ -0,0 +1,121 @@
1
+ """Main solver interface for Quantum Signal Processing optimization.
2
+
3
+ This module provides the main interface for solving QSP optimization problems,
4
+ coordinating the various optimization methods and utility functions.
5
+ """
6
+
7
+ import numpy as np
8
+ from time import time
9
+ from .utils import chebyshev_to_func
10
+ from .objective import obj_sym, grad_sym, grad_sym_real
11
+ from .optimizers import lbfgs, coordinate_minimization, newton
12
+ from .core import reduced_to_full
13
+
14
+ def solve(coef, parity, opts):
15
+ """Given coefficients of a polynomial P, yield corresponding phase factors.
16
+
17
+ The reference chose the first half of the phase factors as the
18
+ optimization variables, while in the code we used the second half of the
19
+ phase factors. These two formulations are equivalent.
20
+
21
+ To simplify the representation, a constant pi/4 is added to both sides of
22
+ the phase factors when evaluating the objective and the gradient. In the
23
+ output, the FULL phase factors with pi/4 are given.
24
+
25
+ Parameters
26
+ ----------
27
+ coef : array_like
28
+ Coefficients of polynomial P under Chebyshev basis. P should be even/odd,
29
+ only provide non-zero coefficients. Coefficients should be ranked from
30
+ low order term to high order term.
31
+ parity : int
32
+ Parity of polynomial P (0 -- even, 1 -- odd)
33
+ opts : dict, optional
34
+ Options dictionary with fields:
35
+
36
+ - criteria : float
37
+ Stop criteria
38
+ - useReal : bool
39
+ Use only real arithmetics if true
40
+ - targetPre : bool
41
+ Want Pre to be target function if true
42
+ - method : {'LBFGS', 'FPI', 'Newton'}
43
+ Optimization method to use
44
+ - typePhi : {'full', 'reduced'}
45
+ Type of phase factors to return
46
+
47
+ Returns
48
+ -------
49
+ phi_proc : ndarray
50
+ Solution of optimization problem, FULL phase factors
51
+ out : dict
52
+ Information of solving process containing:
53
+
54
+ - iter : int
55
+ Number of iterations
56
+ - time : float
57
+ Runtime in seconds
58
+ - value : float
59
+ Final error value
60
+ - parity : int
61
+ Input parity value
62
+ - targetPre : bool
63
+ Whether Pre was target function
64
+ - typePhi : str
65
+ Type of phase factors returned
66
+ """
67
+ # Setup options for L-BFGS solver
68
+ opts.setdefault('maxiter', 5e4)
69
+ opts.setdefault('criteria', 1e-12)
70
+ opts.setdefault('useReal', True)
71
+ opts.setdefault('targetPre', True)
72
+ opts.setdefault('method', 'FPI')
73
+ opts.setdefault('typePhi', 'full')
74
+
75
+ if opts['method'] == 'LBFGS':
76
+ # Initial preparation
77
+ tot_len = len(coef)
78
+ delta = np.cos((np.arange(1, 2 * tot_len, 2) * (np.pi / (2 * tot_len))))
79
+ if not opts['targetPre']:
80
+ opts['target'] = lambda x: -chebyshev_to_func(x, coef, parity, True)
81
+ else:
82
+ opts['target'] = lambda x: chebyshev_to_func(x, coef, parity, True)
83
+ opts['parity'] = parity
84
+ obj = obj_sym
85
+ grad = grad_sym_real if opts['useReal'] else grad_sym
86
+
87
+ # Solve by L-BFGS with selected initial point
88
+ start_time = time()
89
+ phi, err, iter = lbfgs(obj, grad, delta, np.zeros(tot_len), opts)
90
+ # Convert phi to reduced phase factors
91
+ if parity == 0:
92
+ phi[0] = phi[0] / 2
93
+ runtime = time() - start_time
94
+
95
+ elif opts['method'] == 'FPI':
96
+ phi, err, iter, runtime = coordinate_minimization(coef, parity, opts)
97
+
98
+ elif opts['method'] == 'Newton':
99
+ phi, err, iter, runtime = newton(coef, parity, opts)
100
+
101
+ else:
102
+ print("Assigned method doesn't exist. Please choose method from 'LBFGS', 'FPI' or 'Newton'.")
103
+ return None, None
104
+
105
+ # Output information
106
+ out = {
107
+ 'iter': iter,
108
+ 'time': runtime,
109
+ 'value': err,
110
+ 'parity': parity,
111
+ 'targetPre': opts['targetPre']
112
+ }
113
+
114
+ if opts['typePhi'] == 'full':
115
+ phi_proc = reduced_to_full(phi, parity, opts['targetPre'])
116
+ out['typePhi'] = 'full'
117
+ else:
118
+ phi_proc = phi
119
+ out['typePhi'] = 'reduced'
120
+
121
+ return phi_proc, out
@@ -0,0 +1,25 @@
1
+ """Quantum Signal Processing Optimization Package.
2
+
3
+ This package provides tools for optimizing phase factors in Quantum Signal
4
+ Processing problems.
5
+ """
6
+
7
+ from .QSP_solver import *
8
+ from .core import *
9
+ from .utils import *
10
+ from .objective import *
11
+ from .optimizers import *
12
+
13
+ __all__ = [
14
+ # QSP_solver
15
+ 'solve',
16
+ # core
17
+ 'get_unitary',
18
+ 'get_unitary_sym',
19
+ 'get_entry',
20
+ 'reduced_to_full',
21
+ # utils
22
+ 'chebyshev_to_func',
23
+ 'cvx_poly_coef',
24
+ # Add all other public functions here
25
+ ]
optimization/core.py ADDED
@@ -0,0 +1,265 @@
1
+ """Core Quantum Signal Processing functionality.
2
+
3
+ This module provides the fundamental operations needed for Quantum Signal Processing,
4
+ including unitary matrix construction and manipulation of phase factors.
5
+ """
6
+
7
+ import numpy as np
8
+ from .utils import get_unitary_sym, get_pim_sym, get_pim_sym_real, get_pim_deri_sym, get_pim_deri_sym_real
9
+
10
+ def get_unitary(phase, x):
11
+ """Compute QSP unitary matrix for given phase factors.
12
+
13
+ This function constructs the full QSP unitary matrix for a given set of phase
14
+ factors at a specific point. The unitary is built by alternating W(x) gates
15
+ and phase rotations.
16
+
17
+ Parameters
18
+ ----------
19
+ phase : array_like
20
+ Phase factors for the QSP circuit. These are used to construct the
21
+ phase rotation gates in the circuit.
22
+ x : float
23
+ Point at which to evaluate the unitary, must be in [-1, 1].
24
+
25
+ Returns
26
+ -------
27
+ float
28
+ Real part of the (1,1) element of the QSP unitary matrix, which
29
+ represents the QSP approximation of the target function.
30
+
31
+ Notes
32
+ -----
33
+ The unitary is constructed as:
34
+ U = R(phi_0) * W(x) * R(phi_1) * W(x) * ... * R(phi_n)
35
+ where R(phi) is a phase rotation gate and W(x) is the signal processing gate.
36
+ """
37
+ Wx = np.array([[x, 1j * np.sqrt(1 - x**2)], [1j * np.sqrt(1 - x**2), x]])
38
+ expphi = np.exp(1j * phase)
39
+
40
+ ret = np.array([[expphi[0], 0], [0, np.conj(expphi[0])]])
41
+
42
+ for k in range(1, len(expphi)):
43
+ temp = np.array([[expphi[k], 0], [0, np.conj(expphi[k])]])
44
+ ret = np.dot(np.dot(ret, Wx), temp)
45
+
46
+ targ = np.real(ret[0, 0])
47
+
48
+ return targ
49
+
50
+ def get_entry(xlist, phase, opts):
51
+ """Compute QSP unitary matrix entries for multiple points.
52
+
53
+ This function evaluates the QSP unitary matrix at multiple points, handling
54
+ both full and reduced phase factors. It can compute either the real or
55
+ imaginary part of the (1,1) element based on the options.
56
+
57
+ Parameters
58
+ ----------
59
+ xlist : array_like
60
+ Points at which to evaluate the QSP unitary, must be in [-1, 1].
61
+ phase : array_like
62
+ Phase factors for the QSP circuit. Can be either full or reduced
63
+ phase factors depending on opts['typePhi'].
64
+ opts : dict
65
+ Options dictionary containing:
66
+
67
+ - targetPre : bool
68
+ If True, compute real part (Pre), otherwise compute imaginary part (Pim)
69
+ - parity : int
70
+ Parity of the polynomial (0 for even, 1 for odd)
71
+ - typePhi : {'full', 'reduced'}
72
+ Type of phase factors provided:
73
+
74
+ - 'full' : complete set of phase factors
75
+ - 'reduced' : reduced set of phase factors (will be expanded)
76
+
77
+ Returns
78
+ -------
79
+ ndarray
80
+ QSP approximation values at each point in xlist. For targetPre=True,
81
+ returns real part of (1,1) element; for targetPre=False, returns
82
+ imaginary part.
83
+
84
+ Notes
85
+ -----
86
+ When typePhi='reduced', the function expands the reduced phase factors to
87
+ full phase factors using symmetry. For targetPre=False, it also adjusts
88
+ the first and last phase factors by -π/4 to compute the imaginary part.
89
+ """
90
+ typePhi = opts['typePhi']
91
+ targetPre = opts['targetPre']
92
+ parity = opts['parity']
93
+
94
+ d = len(xlist)
95
+ ret = np.zeros(d)
96
+
97
+ if typePhi == 'reduced':
98
+ dd = 2 * len(phase) - 1 + parity
99
+ phi = np.zeros(dd)
100
+ phi[(dd - len(phase)):] = phase
101
+ phi[:len(phase)] += phase[::-1]
102
+ else:
103
+ phi = phase
104
+
105
+ if not targetPre:
106
+ phi[0] -= np.pi / 4
107
+ phi[-1] -= np.pi / 4
108
+
109
+ for i in range(d):
110
+ x = xlist[i]
111
+ ret[i] = get_unitary(phi, x)
112
+
113
+ return ret
114
+
115
+ def reduced_to_full(phi_cm, parity, targetPre):
116
+ """Convert reduced phase factors to full phase factors for QSP.
117
+
118
+ This function constructs the full set of phase factors required for the
119
+ Quantum Signal Processing (QSP) unitary matrix from a reduced set. The
120
+ conversion uses symmetry properties of the phase factors and handles
121
+ both even and odd parity cases.
122
+
123
+ Parameters
124
+ ----------
125
+ phi_cm : array_like
126
+ Reduced phase factors. For even parity, these represent half the
127
+ total phase factors; for odd parity, they represent the unique
128
+ phase factors.
129
+ parity : int
130
+ Parity of the phase factors:
131
+
132
+ - 0 : even parity (full length = 2*len(phi_cm) - 1)
133
+ - 1 : odd parity (full length = 2*len(phi_cm))
134
+ targetPre : bool
135
+ Whether to adjust for target preparation:
136
+
137
+ - True : add π/4 to the last phase factor
138
+ - False : use phase factors as is
139
+
140
+ Returns
141
+ -------
142
+ ndarray
143
+ Full phase factors constructed by mirroring the reduced factors.
144
+ The length depends on parity:
145
+
146
+ - For even parity: 2*len(phi_cm) - 1
147
+ - For odd parity: 2*len(phi_cm)
148
+
149
+ Notes
150
+ -----
151
+ The full phase factors are constructed by:
152
+ 1. Copying the reduced factors to the right half
153
+ 2. Mirroring them to the left half
154
+ 3. Adjusting the last factor if targetPre is True
155
+ """
156
+ phi_right = phi_cm.copy()
157
+ if targetPre:
158
+ phi_right[-1] += np.pi / 4
159
+
160
+ dd = 2 * len(phi_right)
161
+ if parity == 0:
162
+ dd -= 1
163
+
164
+ phi_full = np.zeros(dd)
165
+ phi_full[(dd - len(phi_right)):] = phi_right
166
+ phi_full[:len(phi_right)] += phi_right[::-1]
167
+
168
+ return phi_full
169
+
170
+ def F(phi, parity, opts):
171
+ """Compute the Chebyshev coefficients of P_im.
172
+
173
+ P_im is the imaginary part of the (1,1) element of the QSP unitary matrix.
174
+
175
+ Parameters
176
+ ----------
177
+ phi : array_like
178
+ Reduced phase factors
179
+ parity : int
180
+ Parity of phi (0 for even, 1 for odd)
181
+ opts : dict
182
+ Options dictionary with fields:
183
+ - useReal : bool
184
+ Whether to use real matrix multiplication
185
+
186
+ Returns
187
+ -------
188
+ ndarray
189
+ Chebyshev coefficients of P_im w.r.t.
190
+ T_(2k) for even parity or T_(2k-1) for odd parity
191
+ """
192
+ # Setup options for CM solver
193
+ opts.setdefault('useReal', True)
194
+
195
+ # Initial preparation
196
+ d = len(phi)
197
+ dd = 2 * d
198
+ theta = np.arange(d + 1) * np.pi / dd
199
+ M = np.zeros(2 * dd)
200
+
201
+ if opts['useReal']:
202
+ f = lambda x: [get_pim_sym_real(phi, xval, parity) for xval in x]
203
+ else:
204
+ f = lambda x: [get_pim_sym(phi, xval, parity) for xval in x]
205
+
206
+ # Start Chebyshev coefficients evaluation
207
+ M[:d+1] = f(np.cos(theta))
208
+ M[d+1:dd+1] = (-1)**parity * M[d-1::-1]
209
+ M[dd+1:] = M[dd-1:0:-1]
210
+ M = np.fft.fft(M) # FFT w.r.t. columns.
211
+ M = np.real(M)
212
+ M /= (2 * dd)
213
+ M[1:-1] *= 2
214
+ coe = M[parity:2*d:2]
215
+
216
+ return coe
217
+
218
+ def F_Jacobian(phi, parity, opts):
219
+ """Compute the Jacobian matrix of Chebyshev coefficients.
220
+
221
+ Parameters
222
+ ----------
223
+ phi : array_like
224
+ Reduced phase factors
225
+ parity : int
226
+ Parity of phi (0 for even, 1 for odd)
227
+ opts : dict
228
+ Options dictionary with fields:
229
+ - useReal : bool
230
+ Whether to use real matrix multiplication
231
+
232
+ Returns
233
+ -------
234
+ ndarray
235
+ Jacobian matrix of Chebyshev coefficients
236
+ """
237
+ # Setup options
238
+ opts.setdefault('useReal', True)
239
+
240
+ # Initial preparation
241
+ if opts['useReal']:
242
+ f = lambda x: get_pim_deri_sym_real(phi, x, parity)
243
+ else:
244
+ f = lambda x: get_pim_deri_sym(phi, x, parity)
245
+
246
+ d = len(phi)
247
+ dd = 2 * d
248
+ theta = np.arange(d + 1) * np.pi / dd
249
+ M = np.zeros((2 * dd, d + 1))
250
+
251
+ for n in range(d + 1):
252
+ M[n, :] = f(np.cos(theta[n]))
253
+
254
+ M[d + 1:dd + 1, :] = (-1) ** parity * M[d - 1::-1, :]
255
+ M[dd + 1:, :] = M[dd - 1:0:-1, :]
256
+
257
+ M = np.fft.fft(M, axis=0) # FFT w.r.t. columns.
258
+ M = np.real(M[:dd + 1, :])
259
+ M[1:-1, :] *= 2
260
+ M /= (2 * dd)
261
+
262
+ f = M[parity::2, -1][:d]
263
+ df = M[parity::2, :-1][:d]
264
+
265
+ return f, df
@@ -0,0 +1,140 @@
1
+ """Objective and gradient functions for QSP optimization.
2
+
3
+ This module provides functions for computing objective values and gradients
4
+ needed in QSP optimization problems.
5
+ """
6
+
7
+ import numpy as np
8
+ from .utils import get_unitary_sym, get_pim_sym, get_pim_sym_real, get_pim_deri_sym, get_pim_deri_sym_real
9
+ from .core import get_entry
10
+
11
+ def obj_sym(phi, delta, opts):
12
+ """Compute objective function value for QSP optimization.
13
+
14
+ Parameters
15
+ ----------
16
+ phi : array_like
17
+ Phase factors for QSP circuit
18
+ delta : array_like
19
+ Samples
20
+ opts : dict
21
+ Options dictionary containing target function and parameters
22
+
23
+ Returns
24
+ -------
25
+ float
26
+ Objective function value
27
+ """
28
+ m = len(delta)
29
+ obj = np.zeros(m)
30
+ for i in range(m):
31
+ qspmat = get_unitary_sym(phi, delta[i], opts['parity'])
32
+ obj[i] = 0.5 * (np.real(qspmat[0, 0]) - opts['target']([delta[i]]))**2
33
+
34
+ return obj
35
+
36
+ def grad_sym(phi, delta, opts):
37
+ """Compute gradient of objective function.
38
+
39
+ Parameters
40
+ ----------
41
+ phi : array_like
42
+ Phase factors for QSP circuit
43
+ delta : array_like
44
+ Samples
45
+ opts : dict
46
+ Options dictionary containing target function and parameters
47
+
48
+ Returns
49
+ -------
50
+ grad : ndarray
51
+ Gradient of objective function
52
+ obj : ndarray
53
+ Objective function value
54
+ """
55
+ # Initial computation
56
+ m = len(delta)
57
+ d = len(phi)
58
+ obj = np.zeros(m)
59
+ grad = np.zeros((m, d))
60
+ gate = np.array([[np.exp(1j * np.pi / 4), 0], [0, np.conj(np.exp(1j * np.pi / 4))]])
61
+ exptheta = np.exp(1j * phi)
62
+ targetx = opts['target']
63
+ parity = opts['parity']
64
+
65
+ # Start gradient evaluation
66
+ for i in range(m):
67
+ x = delta[i]
68
+ Wx = np.array([[x, 1j * np.sqrt(1 - x**2)], [1j * np.sqrt(1 - x**2), x]])
69
+ tmp_save1 = np.zeros((2, 2, d), dtype=complex)
70
+ tmp_save2 = np.zeros((2, 2, d), dtype=complex)
71
+ tmp_save1[:, :, 0] = np.eye(2)
72
+ tmp_save2[:, :, 0] = np.dot(np.array([[exptheta[d-1], 0], [0, np.conj(exptheta[d-1])]]), gate)
73
+ for j in range(1, d):
74
+ tmp_save1[:, :, j] = np.dot(tmp_save1[:, :, j-1], np.dot(np.diag([exptheta[j-1], np.conj(exptheta[j-1])]), Wx))
75
+ tmp_save2[:, :, j] = np.dot(np.dot(np.array([[exptheta[d-j-1], 0], [0, np.conj(exptheta[d-j-1])]]), Wx), tmp_save2[:, :, j-1])
76
+ if parity == 1:
77
+ qspmat = np.dot(np.dot(tmp_save2[:, :, d-1].T, Wx), tmp_save2[:, :, d-1])
78
+ gap = np.real(qspmat[0, 0]) - targetx(x)
79
+ leftmat = np.dot(tmp_save2[:, :, d-1].T, Wx)
80
+ for j in range(d):
81
+ grad_tmp = np.dot(np.dot(leftmat, tmp_save1[:, :, j]), np.array([[1j, -1j]]).T) * tmp_save2[:, :, d-j-1]
82
+ grad[i, j] = 2 * np.real(grad_tmp[0, 0]) * gap
83
+ obj[i] = 0.5 * (np.real(qspmat[0, 0]) - targetx(x))**2
84
+ else:
85
+ qspmat = np.dot(np.dot(tmp_save2[:, :, d-2].T, Wx), tmp_save2[:, :, d-1])
86
+ gap = np.real(qspmat[0, 0]) - targetx(x)
87
+ leftmat = np.dot(tmp_save2[:, :, d-2].T, Wx)
88
+ for j in range(d):
89
+ grad_tmp = np.dot(np.dot(leftmat, tmp_save1[:, :, j]), np.array([[1j, -1j]]).T) * tmp_save2[:, :, d-j-1]
90
+ grad[i, j] = 2 * np.real(grad_tmp[0, 0]) * gap
91
+ grad[i, 0] /= 2
92
+ obj[i] = 0.5 * (np.real(qspmat[0, 0]) - targetx(x))**2
93
+
94
+ return grad, obj
95
+
96
+ def grad_sym_real(phi, delta, opts):
97
+ """Compute gradient using real arithmetic.
98
+
99
+ Similar to grad_sym but uses only real arithmetic for efficiency.
100
+
101
+ Parameters
102
+ ----------
103
+ phi : array_like
104
+ Phase factors for QSP circuit
105
+ delta : array_like
106
+ Samples
107
+ opts : dict
108
+ Options dictionary containing target function and parameters
109
+
110
+ Returns
111
+ -------
112
+ grad : ndarray
113
+ Gradient of objective function
114
+ obj : ndarray
115
+ Objective function value
116
+ """
117
+ # Initial computation
118
+ m = len(delta)
119
+ d = len(phi)
120
+ obj = np.zeros(m)
121
+ grad = np.zeros((m, d))
122
+ targetx = opts['target']
123
+ parity = opts['parity']
124
+
125
+ # Convert the phase factor used in LBFGS solver to reduced phase factors
126
+ if parity == 0:
127
+ phi[0] = phi[0] / 2
128
+
129
+ # Start gradient evaluation
130
+ for i in range(m):
131
+ x = delta[i]
132
+ y = get_pim_deri_sym_real(phi, x, parity)
133
+ if parity == 0:
134
+ y[0] = y[0] / 2
135
+ y = -y # Flip the sign
136
+ gap = y[-1] - targetx([x])
137
+ obj[i] = 0.5 * gap**2
138
+ grad[i, :] = y[:-1] * gap
139
+
140
+ return grad, obj
@@ -0,0 +1,308 @@
1
+ """Optimization methods for Quantum Signal Processing.
2
+
3
+ This module provides various optimization algorithms for finding phase factors
4
+ in Quantum Signal Processing problems.
5
+ """
6
+
7
+ import numpy as np
8
+ import time
9
+ from .objective import (
10
+ obj_sym, grad_sym, grad_sym_real,
11
+ get_pim_sym, get_pim_sym_real,
12
+ get_pim_deri_sym, get_pim_deri_sym_real
13
+ )
14
+ from .core import F, F_Jacobian
15
+
16
+ def lbfgs(obj, grad, delta, phi, opts):
17
+ """L-BFGS optimization for QSP phase factors.
18
+
19
+ This function implements the Limited-memory BFGS optimization algorithm
20
+ for finding optimal phase factors in QSP problems.
21
+
22
+ Parameters
23
+ ----------
24
+ obj : callable
25
+ Objective function to minimize
26
+ grad : callable
27
+ Gradient function of the objective
28
+ x0 : array_like
29
+ Initial points for evaluation
30
+ phi0 : array_like
31
+ Initial phase factors
32
+ opts : dict
33
+ Options dictionary containing:
34
+
35
+ - maxiter : int
36
+ Maximum number of iterations
37
+ - criteria : float
38
+ Convergence criteria
39
+ - gamma : float
40
+ Line search retraction rate (default 0.5)
41
+ - accrate : float
42
+ Line search accept ratio (default 1e-3)
43
+ - minstep : float
44
+ Minimal step size (default 1e-5)
45
+ - lmem : int
46
+ L-BFGS memory size (default 200)
47
+ - print : bool
48
+ Whether to print progress (default True)
49
+ - itprint : int
50
+ Print frequency (default 1)
51
+ - parity : int
52
+ Parity of polynomial (0 for even, 1 for odd)
53
+
54
+ Returns
55
+ -------
56
+ phi : ndarray
57
+ Optimized phase factors
58
+ obj_value : float
59
+ Objective value at optimal point
60
+ iter : int
61
+ Number of iterations performed
62
+ """
63
+ # Options for L-BFGS solver
64
+ opts.setdefault('maxiter', 50000)
65
+ opts.setdefault('gamma', 0.5)
66
+ opts.setdefault('accrate', 1e-3)
67
+ opts.setdefault('minstep', 1e-5)
68
+ opts.setdefault('criteria', 1e-12)
69
+ opts.setdefault('lmem', 200)
70
+ opts.setdefault('print', 1)
71
+ opts.setdefault('itprint', 1)
72
+
73
+ # Copy value to parameters
74
+ maxiter = opts['maxiter']
75
+ gamma = opts['gamma']
76
+ accrate = opts['accrate']
77
+ lmem = opts['lmem']
78
+ minstep = opts['minstep']
79
+ pri = opts['print']
80
+ itprint = opts['itprint']
81
+ crit = opts['criteria']
82
+
83
+ # Setup print format
84
+ str_head = "{:4s} {:13s} {:10s} {:10s}\n".format('iter', 'obj', 'stepsize', 'des_ratio')
85
+ str_num = "{:4d} {:+5.4e} {:+3.2e} {:+3.2e}\n"
86
+
87
+ # Initial computation
88
+ iter = 0
89
+ d = len(phi)
90
+ mem_size = 0
91
+ mem_now = 0
92
+ mem_grad = np.zeros((lmem, d))
93
+ mem_obj = np.zeros((lmem, d))
94
+ mem_dot = np.zeros(lmem)
95
+ grad_s, obj_s = grad(phi, delta, opts)
96
+ obj_value = np.mean(obj_s)
97
+ GRAD = np.mean(grad_s, axis=0)
98
+
99
+ # Start L-BFGS algorithm
100
+ if pri:
101
+ print('L-BFGS solver started')
102
+
103
+ while True:
104
+ iter += 1
105
+ theta_d = GRAD.copy()
106
+ alpha = np.zeros(mem_size)
107
+ for i in range(mem_size):
108
+ subsc = (mem_now - i - 1) % lmem
109
+ alpha[i] = mem_dot[subsc] * np.dot(mem_obj[subsc, :], theta_d)
110
+ theta_d -= alpha[i] * mem_grad[subsc, :]
111
+
112
+ theta_d *= 0.5
113
+ if opts['parity'] == 0:
114
+ theta_d[0] *= 2
115
+
116
+ for i in range(mem_size):
117
+ subsc = (mem_now - (mem_size - i) - 1) % lmem
118
+ beta = mem_dot[subsc] * np.dot(mem_grad[subsc, :], theta_d)
119
+ theta_d += (alpha[mem_size - i - 1] - beta) * mem_obj[subsc, :]
120
+
121
+ step = 1
122
+ exp_des = np.dot(GRAD, theta_d)
123
+ while True:
124
+ theta_new = phi - step * theta_d
125
+ obj_snew = obj(theta_new, delta, opts)
126
+ obj_valuenew = np.mean(obj_snew)
127
+ ad = obj_value - obj_valuenew
128
+ if ad > exp_des * accrate * step or step < minstep:
129
+ break
130
+ step *= gamma
131
+
132
+ phi = theta_new
133
+ obj_value = obj_valuenew
134
+ obj_max = np.max(obj_snew)
135
+ grad_s, _ = grad(phi, delta, opts)
136
+ GRAD_new = np.mean(grad_s, axis=0)
137
+ mem_size = min(lmem, mem_size + 1)
138
+ mem_now = (mem_now + 1) % lmem
139
+ mem_grad[mem_now, :] = GRAD_new - GRAD
140
+ mem_obj[mem_now, :] = -step * theta_d
141
+ mem_dot[mem_now] = 1 / np.dot(mem_grad[mem_now, :], mem_obj[mem_now, :])
142
+ GRAD = GRAD_new
143
+
144
+ if pri and iter % itprint == 0:
145
+ if iter == 1 or (iter - itprint) % (itprint * 10) == 0:
146
+ print(str_head, end='')
147
+ print(str_num.format(iter, obj_max, step, ad / (exp_des * step)), end='')
148
+
149
+ if iter >= maxiter:
150
+ print("Max iteration reached.")
151
+ break
152
+ if obj_max < crit**2:
153
+ print("Stop criteria satisfied.")
154
+ break
155
+
156
+ return phi, obj_value, iter
157
+
158
+ def coordinate_minimization(coef, parity, opts):
159
+ """Coordinate minimization optimization for QSP phase factors.
160
+
161
+ This function implements the coordinate minimization algorithm for
162
+ finding optimal phase factors in QSP problems.
163
+
164
+ Parameters
165
+ ----------
166
+ coef : array_like
167
+ Coefficients of polynomial P under Chebyshev basis
168
+ parity : int
169
+ Parity of polynomial P (0 for even, 1 for odd)
170
+ opts : dict
171
+ Options dictionary containing optimization parameters
172
+
173
+ Returns
174
+ -------
175
+ phi : ndarray
176
+ Optimized phase factors
177
+ err : float
178
+ Final error value
179
+ iter : int
180
+ Number of iterations performed
181
+ runtime : float
182
+ Total runtime in seconds
183
+ """
184
+ # Setup options for CM solver
185
+ opts.setdefault('maxiter', int(1e5))
186
+ opts.setdefault('criteria', 1e-12)
187
+ opts.setdefault('targetPre', True)
188
+ opts.setdefault('useReal', True)
189
+ opts.setdefault('print', 1)
190
+ opts.setdefault('itprint', 1)
191
+
192
+ start_time = time.time()
193
+
194
+ # Copy value to parameters
195
+ maxiter = opts['maxiter']
196
+ crit = opts['criteria']
197
+ pri = opts['print']
198
+ itprint = opts['itprint']
199
+
200
+ # Setup print format
201
+ str_head = "{:4s} {:13s}\n".format('iter', 'err')
202
+ str_num = "{:4d} {:+5.4e}\n"
203
+
204
+ # Initial preparation
205
+ if opts['targetPre']:
206
+ coef = -coef # inverse is necessary
207
+ phi = coef / 2
208
+ iter = 0
209
+
210
+ # Solve by contraction mapping algorithm
211
+ while True:
212
+ Fval = F(phi, parity, opts)
213
+ res = Fval - coef
214
+
215
+ # debugging
216
+ # Fval_j, DFval = F_Jacobian(phi, parity, opts)
217
+ # print("Fval from F:", Fval)
218
+ # print("Fval from F_Jacobian:", Fval_j)
219
+
220
+ err = np.linalg.norm(res, 1)
221
+ iter += 1
222
+ if iter >= maxiter:
223
+ print("Max iteration reached.")
224
+ break
225
+ if err < crit:
226
+ print("Stop criteria satisfied.")
227
+ break
228
+ phi = phi - res / 2
229
+ if pri and iter % itprint == 0:
230
+ if iter == 1 or (iter - itprint) % (itprint * 10) == 0:
231
+ print(str_head, end='')
232
+ print(str_num.format(iter, err), end='')
233
+
234
+ runtime = time.time() - start_time
235
+ return phi, err, iter, runtime
236
+
237
+ def newton(coef, parity, opts):
238
+ """Newton's method optimization for QSP phase factors.
239
+
240
+ This function implements Newton's method for finding optimal phase
241
+ factors in QSP problems.
242
+
243
+ Parameters
244
+ ----------
245
+ coef : array_like
246
+ Coefficients of polynomial P under Chebyshev basis
247
+ parity : int
248
+ Parity of polynomial P (0 for even, 1 for odd)
249
+ opts : dict
250
+ Options dictionary containing optimization parameters
251
+
252
+ Returns
253
+ -------
254
+ phi : ndarray
255
+ Optimized phase factors
256
+ err : float
257
+ Final error value
258
+ iter : int
259
+ Number of iterations performed
260
+ runtime : float
261
+ Total runtime in seconds
262
+ """
263
+ # Setup options for Newton solver
264
+ opts.setdefault('maxiter', int(1e5))
265
+ opts.setdefault('criteria', 1e-12)
266
+ opts.setdefault('targetPre', True)
267
+ opts.setdefault('useReal', True)
268
+ opts.setdefault('print', 1)
269
+ opts.setdefault('itprint', 1)
270
+
271
+ start_time = time.time()
272
+
273
+ # Copy value to parameters
274
+ maxiter = opts['maxiter']
275
+ crit = opts['criteria']
276
+ pri = opts['print']
277
+ itprint = opts['itprint']
278
+
279
+ # Setup print format
280
+ str_head = "{:4s} {:13s}\n".format('iter', 'err')
281
+ str_num = "{:4d} {:+5.4e}\n"
282
+
283
+ # Initial preparation
284
+ if opts['targetPre']:
285
+ coef = -coef # inverse is necessary
286
+ phi = coef / 2
287
+ iter = 0
288
+
289
+ # Solve by Newton method
290
+ while True:
291
+ Fval, DFval = F_Jacobian(phi, parity, opts)
292
+ res = Fval - coef
293
+ err = np.linalg.norm(res, 1)
294
+ iter += 1
295
+ if iter >= maxiter:
296
+ print("Max iteration reached.")
297
+ break
298
+ if err < crit:
299
+ print("Stop criteria satisfied.")
300
+ break
301
+ phi = phi - np.linalg.solve(DFval, res)
302
+ if pri and iter % itprint == 0:
303
+ if iter == 1 or (iter - itprint) % (itprint * 10) == 0:
304
+ print(str_head, end='')
305
+ print(str_num.format(iter, err), end='')
306
+
307
+ runtime = time.time() - start_time
308
+ return phi, err, iter, runtime
optimization/utils.py ADDED
@@ -0,0 +1,449 @@
1
+ """Utility functions for QSP optimization.
2
+
3
+ This module provides utility functions for working with Chebyshev polynomials
4
+ and other mathematical operations needed in QSP optimization.
5
+ """
6
+
7
+ import numpy as np
8
+ import matplotlib.pyplot as plt
9
+ from scipy.optimize import minimize, linprog
10
+ from scipy.special import chebyt
11
+ import cvxpy as cp
12
+
13
+ def chebyshev_to_func(x, coef, parity, partialcoef):
14
+ """Convert Chebyshev coefficients to function values.
15
+
16
+ This function evaluates a polynomial represented in the Chebyshev basis
17
+ at given points.
18
+
19
+ Parameters
20
+ ----------
21
+ x : array_like or float
22
+ Points at which to evaluate the polynomial. Can be a single point
23
+ or an array of points.
24
+ coef : array_like
25
+ Coefficients in Chebyshev basis, ordered from lowest to highest degree
26
+ parity : int
27
+ Parity of the polynomial (0 for even, 1 for odd)
28
+ partialcoef : bool, optional
29
+ Whether to return only coefficients of odd/even order
30
+
31
+ Returns
32
+ -------
33
+ ndarray or float
34
+ Function values at the given points. Returns a scalar if input is
35
+ scalar, array otherwise.
36
+ """
37
+ ret = np.zeros(len(x))
38
+ y = np.arccos(x)
39
+ if partialcoef:
40
+ if parity == 0:
41
+ for k in range(len(coef)):
42
+ ret += coef[k] * np.cos(2 * k * y)
43
+ else:
44
+ for k in range(len(coef)):
45
+ ret += coef[k] * np.cos((2 * k + 1) * y)
46
+ else:
47
+ if parity == 0:
48
+ for k in range(0, len(coef), 2):
49
+ ret += coef[k] * np.cos(k * y)
50
+ else:
51
+ for k in range(1, len(coef), 2):
52
+ ret += coef[k] * np.cos(k * y)
53
+ return ret
54
+
55
+ def cvx_poly_coef(func, deg, opts):
56
+ """Compute coefficients for a polynomial approximation using convex optimization.
57
+
58
+ This function computes the coefficients of a polynomial that best approximates
59
+ a target function over specified intervals in a least-squares sense. The function
60
+ is called with a target function, the degree of the polynomial, and an options
61
+ dictionary.
62
+
63
+ Parameters
64
+ ----------
65
+ func : callable
66
+ The target function to approximate.
67
+ deg : int
68
+ The degree of the polynomial.
69
+ opts : dict
70
+ Options dictionary with the following fields:
71
+
72
+ - intervals : list
73
+ [min, max] interval for x values.
74
+ - npts : int
75
+ Number of points to evaluate the function at.
76
+ - objnorm : float
77
+ Norm to use for optimization.
78
+ - epsil : float
79
+ Epsilon for numerical stability.
80
+ - fscale : float
81
+ Scale factor for function values.
82
+ - isplot : bool
83
+ Whether to plot results.
84
+
85
+ Returns
86
+ -------
87
+ ndarray
88
+ Coefficients of the best-fit polynomial in the Chebyshev basis.
89
+ """
90
+ # Set default options if not provided
91
+ opts.setdefault('npts', 200)
92
+ opts.setdefault('epsil', 0.01)
93
+ opts.setdefault('fscale', 1 - opts['epsil'])
94
+ opts.setdefault('intervals', [0, 1])
95
+ opts.setdefault('isplot', False)
96
+ opts.setdefault('objnorm', np.inf)
97
+ opts.setdefault('method', 'SLSQP')
98
+
99
+ # Check variables and assign local variables
100
+ assert len(opts['intervals']) % 2 == 0
101
+ parity = deg % 2
102
+ epsil = opts['epsil']
103
+ npts = opts['npts']
104
+
105
+ # Generate Chebyshev points
106
+ xpts = np.cos(np.pi * np.arange(2 * npts) / (2 * npts - 1))
107
+ xpts = np.union1d(xpts, opts['intervals'])
108
+ xpts = xpts[xpts >= 0]
109
+ npts = len(xpts)
110
+
111
+ n_interval = len(opts['intervals']) // 2
112
+ ind_union = np.array([], dtype=int)
113
+ ind_set = []
114
+
115
+ for i in range(n_interval):
116
+ ind = np.where((xpts >= opts['intervals'][2 * i]) & (xpts <= opts['intervals'][2 * i + 1]))[0]
117
+ ind_set.append(ind)
118
+ ind_union = np.union1d(ind_union, ind)
119
+
120
+ # Evaluate the target function
121
+ fx = np.zeros(npts)
122
+ fx[ind_union] = opts['fscale'] * func(xpts[ind_union])
123
+
124
+ # Prepare the Chebyshev polynomials
125
+ n_coef = deg // 2 + 1 if parity == 0 else (deg + 1) // 2
126
+ Ax = np.zeros((npts, n_coef))
127
+
128
+ for k in range(1, n_coef + 1):
129
+ Tcheb = chebyt(2 * (k - 1)) if parity == 0 else chebyt(2 * k - 1)
130
+ Ax[:, k-1] = Tcheb(xpts)
131
+
132
+ # Use optimization to find the Chebyshev coefficients
133
+ coef = np.zeros(n_coef)
134
+ if opts['method'] == 'SLSQP':
135
+ def objective(coef):
136
+ y = Ax @ coef
137
+ return np.linalg.norm(y[ind_union] - fx[ind_union], opts['objnorm'])
138
+
139
+ constraints = [{'type': 'ineq', 'fun': lambda coef: 1 - epsil - Ax @ coef},
140
+ {'type': 'ineq', 'fun': lambda coef: Ax @ coef + (1 - epsil)}]
141
+
142
+ result = minimize(objective, np.zeros(n_coef), constraints=constraints)
143
+ coef = result.x
144
+
145
+ elif opts['method'] == 'cvxpy':
146
+ c = cp.Variable(n_coef)
147
+ y = Ax @ c
148
+ residual = y[ind_union] - fx[ind_union]
149
+ objective = cp.Minimize(cp.norm_inf(residual))
150
+ constraints = [
151
+ y <= 1 - epsil,
152
+ y >= -(1-epsil)
153
+ ]
154
+ problem = cp.Problem(objective, constraints)
155
+ problem.solve()
156
+ coef = c.value
157
+
158
+ elif opts['method'] == 'linprog':
159
+ e0 = np.zeros(n_coef+1)
160
+ e0[0] = 1
161
+ A_prime = Ax[ind_union, :]
162
+
163
+ # Build A_ub and b_ub
164
+ neg_one = -np.ones((A_prime.shape[0], 1))
165
+ zero_one = np.zeros((Ax.shape[0], 1))
166
+
167
+ A_ub = np.vstack([
168
+ np.hstack([neg_one, A_prime]), # A'c - t <= f
169
+ np.hstack([neg_one, -A_prime]), # -A'c - t <= -f
170
+ np.hstack([zero_one, Ax]), # A c <= 1 - epsil
171
+ np.hstack([zero_one, -Ax]) # -A c <= 1 - epsil
172
+ ])
173
+
174
+ b_ub = np.concatenate([
175
+ fx[ind_union],
176
+ -fx[ind_union],
177
+ (1 - epsil) * np.ones(Ax.shape[0]),
178
+ (1 - epsil) * np.ones(Ax.shape[0])
179
+ ])
180
+
181
+ # Solve
182
+ result = linprog(c=e0, A_ub=A_ub, b_ub=b_ub, method='highs')
183
+
184
+ if result.success:
185
+ coef = result.x[1:n_coef+1]
186
+ else:
187
+ raise ValueError(f'Linear programming failed to find an optimal solution, status: {result.status}')
188
+
189
+ else:
190
+ raise ValueError(f'Method {opts["method"]} not supported')
191
+
192
+ err_inf = np.linalg.norm((Ax @ coef)[ind_union] - fx[ind_union], opts['objnorm'])
193
+ print(f'norm error = {err_inf}')
194
+
195
+ # Make sure the maximum is less than 1
196
+ coef_full = np.zeros(deg + 1)
197
+ if parity == 0:
198
+ coef_full[::2] = coef
199
+ else:
200
+ coef_full[1::2] = coef
201
+
202
+ max_sol = np.max(np.abs(np.polynomial.chebyshev.chebval(xpts, coef_full)))
203
+ print(f'max of solution = {max_sol}')
204
+ if max_sol > 1.0 - 1e-10:
205
+ raise ValueError('Solution is not bounded by 1. Increase npts')
206
+
207
+ if opts['isplot']:
208
+ plt.figure(1)
209
+ plt.clf()
210
+ plt.plot(xpts, Ax @ coef, 'ro', linewidth=1.5)
211
+ for ind in ind_set:
212
+ plt.plot(xpts[ind], fx[ind], 'b-', linewidth=2)
213
+ plt.xlabel('$x$', fontsize=15)
214
+ plt.ylabel('$f(x)$', fontsize=15)
215
+ plt.legend(['polynomial', 'target'], fontsize=15)
216
+
217
+ plt.figure(2)
218
+ plt.clf()
219
+ for ind in ind_set:
220
+ plt.plot(xpts[ind], np.abs(Ax[ind] @ coef - fx[ind]), 'k-', linewidth=1.5)
221
+ plt.xlabel('$x$', fontsize=15)
222
+ plt.ylabel('$|f_\\mathrm{poly}(x)-f(x)|$', fontsize=15)
223
+ plt.show()
224
+
225
+ return coef_full
226
+
227
+ def get_unitary_sym(phi, x, parity):
228
+ """Get the QSP unitary matrix based on given phase vector and point x.
229
+
230
+ This function constructs the full QSP unitary matrix for a given set of
231
+ phase factors at a specific point, handling both even and odd parity cases.
232
+
233
+ Parameters
234
+ ----------
235
+ phi : array_like
236
+ Phase factors for the QSP circuit:
237
+
238
+ - For parity=1: reduced phase factors
239
+ - For parity=0: phi[0] differs from reduced phase factors by factor of 2
240
+ x : float
241
+ Point at which to evaluate the unitary, must be in [-1, 1].
242
+ parity : int
243
+ Parity of the phase factors:
244
+
245
+ - 0 : even parity
246
+ - 1 : odd parity
247
+
248
+ Returns
249
+ -------
250
+ ndarray
251
+ The QSP unitary matrix constructed from the phase factors and point x.
252
+
253
+ Notes
254
+ -----
255
+ The construction of the unitary matrix differs based on parity:
256
+
257
+ - For odd parity: Uses full phase factors with a final gate transformation
258
+ - For even parity: Uses a different construction with modified first phase
259
+ """
260
+ Wx = np.array([[x, 1j * np.sqrt(1 - x**2)], [1j * np.sqrt(1 - x**2), x]])
261
+ gate = np.array([[np.exp(1j * np.pi / 4), 0], [0, np.conj(np.exp(1j * np.pi / 4))]])
262
+ expphi = np.exp(1j * phi)
263
+
264
+ if parity == 1:
265
+ ret = np.array([[expphi[0], 0], [0, np.conj(expphi[0])]])
266
+ for k in range(1, len(expphi)):
267
+ ret = np.dot(np.dot(ret, Wx), np.array([[expphi[k], 0], [0, np.conj(expphi[k])]]))
268
+ ret = np.dot(ret, gate)
269
+ qspmat = np.dot(np.dot(ret.T, Wx), ret)
270
+ else:
271
+ ret = np.eye(2)
272
+ for k in range(1, len(expphi)):
273
+ ret = np.dot(ret, Wx * np.array([[expphi[k], np.conj(expphi[k])]]))
274
+ ret = np.dot(ret, gate)
275
+ qspmat = np.dot(np.dot(ret.T, np.array([[expphi[0], 0], [0, np.conj(expphi[0])]])), ret)
276
+
277
+ return qspmat
278
+
279
+ def get_pim_sym(phi, x, parity):
280
+ """Compute imaginary part of QSP unitary matrix element.
281
+
282
+ Parameters
283
+ ----------
284
+ phi : array_like
285
+ Phase factors for QSP circuit
286
+ x : float
287
+ Point at which to evaluate
288
+ parity : int
289
+ Parity of phase factors (0 for even, 1 for odd)
290
+
291
+ Returns
292
+ -------
293
+ float
294
+ Imaginary part of (1,1) element of QSP unitary
295
+ """
296
+ qspmat = get_unitary_sym(phi, x, parity)
297
+ return np.imag(qspmat[0, 0])
298
+
299
+ def get_pim_sym_real(phi, x, parity):
300
+ """Compute imaginary part using real arithmetic.
301
+
302
+ Similar to get_pim_sym but uses only real arithmetic for efficiency.
303
+
304
+ Parameters
305
+ ----------
306
+ phi : array_like
307
+ Phase factors for QSP circuit
308
+ x : float
309
+ Point at which to evaluate
310
+ parity : int
311
+ Parity of phase factors (0 for even, 1 for odd)
312
+
313
+ Returns
314
+ -------
315
+ float
316
+ Imaginary part of (1,1) element of QSP unitary
317
+ """
318
+ n = len(phi)
319
+ theta = np.arccos(x)
320
+ B = np.array([[np.cos(2 * theta), 0, -np.sin(2 * theta)],
321
+ [0, 1, 0],
322
+ [np.sin(2 * theta), 0, np.cos(2 * theta)]])
323
+
324
+ L = np.zeros((n, 3))
325
+ L[n-1, :] = [0, 1, 0]
326
+
327
+ for k in range(n-2, -1, -1):
328
+ L[k, :] = np.dot(L[k+1, :], np.dot(np.array([[np.cos(2 * phi[k+1]), -np.sin(2 * phi[k+1]), 0],
329
+ [np.sin(2 * phi[k+1]), np.cos(2 * phi[k+1]), 0],
330
+ [0, 0, 1]]), B))
331
+
332
+ R = np.zeros((3, n))
333
+ if parity == 0:
334
+ R[:, 0] = [1, 0, 0]
335
+ else:
336
+ R[:, 0] = [np.cos(theta), 0, np.sin(theta)]
337
+
338
+ for k in range(1, n):
339
+ R[:, k] = np.dot(B, np.dot(np.array([[np.cos(2 * phi[k-1]), -np.sin(2 * phi[k-1]), 0],
340
+ [np.sin(2 * phi[k-1]), np.cos(2 * phi[k-1]), 0],
341
+ [0, 0, 1]]), R[:, k-1]))
342
+
343
+ return np.dot(L[n-1, :], np.dot(np.array([[np.cos(2 * phi[n-1]), -np.sin(2 * phi[n-1]), 0],
344
+ [np.sin(2 * phi[n-1]), np.cos(2 * phi[n-1]), 0],
345
+ [0, 0, 1]]), R[:, n-1]))
346
+
347
+ def get_pim_deri_sym(phi, x, parity):
348
+ """Compute Pim and its derivatives.
349
+
350
+ Parameters
351
+ ----------
352
+ phi : array_like
353
+ Phase factors for QSP circuit
354
+ x : float
355
+ Point at which to evaluate
356
+ parity : int
357
+ Parity of phase factors (0 for even, 1 for odd)
358
+
359
+ Returns
360
+ -------
361
+ ndarray
362
+ Array containing Pim and its derivatives
363
+ """
364
+ n = len(phi)
365
+ theta = np.arccos(x)
366
+ B = np.array([[np.cos(2 * theta), 0, -np.sin(2 * theta)],
367
+ [0, 1, 0],
368
+ [np.sin(2 * theta), 0, np.cos(2 * theta)]])
369
+
370
+ L = np.zeros((n, 3))
371
+ L[n-1, :] = [0, 1, 0]
372
+
373
+ for k in range(n-2, -1, -1):
374
+ L[k, :] = np.dot(L[k+1, :], np.dot(np.array([[np.cos(2 * phi[k+1]), -np.sin(2 * phi[k+1]), 0],
375
+ [np.sin(2 * phi[k+1]), np.cos(2 * phi[k+1]), 0],
376
+ [0, 0, 1]]), B))
377
+
378
+ R = np.zeros((3, n))
379
+ if parity == 0:
380
+ R[:, 0] = [1, 0, 0]
381
+ else:
382
+ R[:, 0] = [np.cos(theta), 0, np.sin(theta)]
383
+
384
+ for k in range(1, n):
385
+ R[:, k] = np.dot(B, np.dot(np.array([[np.cos(2 * phi[k-1]), -np.sin(2 * phi[k-1]), 0],
386
+ [np.sin(2 * phi[k-1]), np.cos(2 * phi[k-1]), 0],
387
+ [0, 0, 1]]), R[:, k-1]))
388
+
389
+ y = np.zeros(n+1)
390
+ for k in range(n):
391
+ y[k] = 2 * np.dot(L[k, :], np.dot(np.array([[-np.sin(2 * phi[k]), -np.cos(2 * phi[k]), 0],
392
+ [np.cos(2 * phi[k]), -np.sin(2 * phi[k]), 0],
393
+ [0, 0, 0]]), R[:, k]))
394
+ y[n] = np.dot(L[n-1, :], np.dot(np.array([[np.cos(2 * phi[n-1]), -np.sin(2 * phi[n-1]), 0],
395
+ [np.sin(2 * phi[n-1]), np.cos(2 * phi[n-1]), 0],
396
+ [0, 0, 1]]), R[:, n-1]))
397
+
398
+ return y
399
+
400
+ def get_pim_deri_sym_real(phi, x, parity):
401
+ """
402
+ Compute Pim and its Jacobian matrix values at a single point x using the real matrix representation of Pim.
403
+
404
+ P_im: the imaginary part of the (1,1) element of the QSP unitary matrix.
405
+
406
+ .. note::
407
+ Theta MUST be a number.
408
+
409
+ :param phi: Phase factors for QSP circuit
410
+ :type phi: array_like
411
+ :param x: Point at which to evaluate
412
+ :type x: float
413
+ :param parity: Parity of phase factors (0 for even, 1 for odd)
414
+ :type parity: int
415
+
416
+ :returns: Array containing Pim and its derivatives
417
+ :rtype: ndarray
418
+ """
419
+ n = len(phi)
420
+ theta = np.arccos(x)
421
+ B = np.array([[np.cos(2 * theta), 0, -np.sin(2 * theta)],
422
+ [0, 1, 0],
423
+ [np.sin(2 * theta), 0, np.cos(2 * theta)]])
424
+ L = np.zeros((n, 3))
425
+ L[n-1, :] = [0, 1, 0]
426
+ for k in range(n-2, -1, -1):
427
+ L[k, :] = np.dot(L[k+1, :], np.dot(np.array([[np.cos(2 * phi[k+1]), -np.sin(2 * phi[k+1]), 0],
428
+ [np.sin(2 * phi[k+1]), np.cos(2 * phi[k+1]), 0],
429
+ [0, 0, 1]]), B))
430
+ R = np.zeros((3, n))
431
+ if parity == 0:
432
+ R[:, 0] = [1, 0, 0]
433
+ else:
434
+ R[:, 0] = [np.cos(theta), 0, np.sin(theta)]
435
+ for k in range(1, n):
436
+ R[:, k] = np.dot(B, np.dot(np.array([[np.cos(2 * phi[k-1]), -np.sin(2 * phi[k-1]), 0],
437
+ [np.sin(2 * phi[k-1]), np.cos(2 * phi[k-1]), 0],
438
+ [0, 0, 1]]), R[:, k-1]))
439
+
440
+ y = np.zeros(n+1)
441
+ for k in range(n):
442
+ y[k] = 2 * np.dot(L[k, :], np.dot(np.array([[-np.sin(2 * phi[k]), -np.cos(2 * phi[k]), 0],
443
+ [np.cos(2 * phi[k]), -np.sin(2 * phi[k]), 0],
444
+ [0, 0, 0]]), R[:, k]))
445
+ y[n] = np.dot(L[n-1, :], np.dot(np.array([[np.cos(2 * phi[n-1]), -np.sin(2 * phi[n-1]), 0],
446
+ [np.sin(2 * phi[n-1]), np.cos(2 * phi[n-1]), 0],
447
+ [0, 0, 1]]), R[:, n-1]))
448
+
449
+ return y
@@ -0,0 +1,56 @@
1
+ Metadata-Version: 2.4
2
+ Name: qsppack
3
+ Version: 0.1.0
4
+ Summary: Quantum Signal Processing optimization and analysis
5
+ Author: James Larsen
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/beevus77/qsp
8
+ Project-URL: Bug Tracker, https://github.com/beevus77/qsp/issues
9
+ Project-URL: Documentation, https://qsp.readthedocs.io/
10
+ Classifier: Programming Language :: Python :: 3
11
+ Classifier: License :: OSI Approved :: MIT License
12
+ Classifier: Operating System :: OS Independent
13
+ Classifier: Development Status :: 3 - Alpha
14
+ Classifier: Intended Audience :: Science/Research
15
+ Classifier: Topic :: Scientific/Engineering :: Physics
16
+ Requires-Python: >=3.8
17
+ Description-Content-Type: text/x-rst
18
+
19
+ qsp - Quantum Signal Processing Optimization
20
+ ============================================
21
+
22
+ A Python package for Quantum Signal Processing optimization and analysis.
23
+
24
+ Installation
25
+ ------------
26
+
27
+ You can install the package using pip:
28
+
29
+ .. code-block:: bash
30
+
31
+ pip install qsppack
32
+
33
+ Or install from source:
34
+
35
+ .. code-block:: bash
36
+
37
+ git clone https://github.com/beevus77/qsp.git
38
+ cd qsp
39
+ pip install -e .
40
+
41
+ Documentation
42
+ -------------
43
+
44
+ Full documentation is available at: https://qsp.readthedocs.io/
45
+
46
+ Features
47
+ --------
48
+
49
+ * Quantum Signal Processing optimization
50
+ * Support for various optimization methods (L-BFGS, FPI, Newton)
51
+ * Utility functions for Chebyshev polynomials and phase factor manipulation
52
+
53
+ License
54
+ -------
55
+
56
+ This project is licensed under the MIT License - see the LICENSE file for details.
@@ -0,0 +1,10 @@
1
+ optimization/QSP_solver.py,sha256=Z7-HrzyL1ZTw9UN2yS70cA-3qmtvpT-SCqKH_TdVf1g,4121
2
+ optimization/__init__.py,sha256=hHIga6VRxuEy0pm80PaJ9ULMcKF1n-VJ-z4W4p2n5S4,509
3
+ optimization/core.py,sha256=fHrjqc4-Qmuod5T_MSjaZrL9_wCRJEINQzzdya-S8Hw,7848
4
+ optimization/objective.py,sha256=qrCRLS__C5IYZQ1piag0dLkhnUk2Ac9Mu_o6B5S_xoc,4550
5
+ optimization/optimizers.py,sha256=pqtHnwceV0Gi5qhaU5yzmuo-ge2HEzUh6lfWSR5BWok,9086
6
+ optimization/utils.py,sha256=CuIye-f7x38zU5HpiX74VAXU5MZ4SoNWt11WGGeVOpE,15650
7
+ qsppack-0.1.0.dist-info/METADATA,sha256=v0pPfoodYS1aOONGPRSPG19ZAcxyUxbmFSW73Wa-ADA,1484
8
+ qsppack-0.1.0.dist-info/WHEEL,sha256=Nw36Djuh_5VDukK0H78QzOX-_FQEo6V37m3nkm96gtU,91
9
+ qsppack-0.1.0.dist-info/top_level.txt,sha256=D7HS9oMRtMUQK6kRIxQd_mgO-5CsfzYsxq7rht6vXQU,13
10
+ qsppack-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (80.7.1)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1 @@
1
+ optimization