qsppack 0.1.0__tar.gz
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.
- qsppack-0.1.0/PKG-INFO +56 -0
- qsppack-0.1.0/README.rst +38 -0
- qsppack-0.1.0/optimization/QSP_solver.py +121 -0
- qsppack-0.1.0/optimization/__init__.py +25 -0
- qsppack-0.1.0/optimization/core.py +265 -0
- qsppack-0.1.0/optimization/objective.py +140 -0
- qsppack-0.1.0/optimization/optimizers.py +308 -0
- qsppack-0.1.0/optimization/utils.py +449 -0
- qsppack-0.1.0/pyproject.toml +30 -0
- qsppack-0.1.0/qsppack.egg-info/PKG-INFO +56 -0
- qsppack-0.1.0/qsppack.egg-info/SOURCES.txt +12 -0
- qsppack-0.1.0/qsppack.egg-info/dependency_links.txt +1 -0
- qsppack-0.1.0/qsppack.egg-info/top_level.txt +1 -0
- qsppack-0.1.0/setup.cfg +4 -0
qsppack-0.1.0/PKG-INFO
ADDED
|
@@ -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.
|
qsppack-0.1.0/README.rst
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
qsp - Quantum Signal Processing Optimization
|
|
2
|
+
============================================
|
|
3
|
+
|
|
4
|
+
A Python package for Quantum Signal Processing optimization and analysis.
|
|
5
|
+
|
|
6
|
+
Installation
|
|
7
|
+
------------
|
|
8
|
+
|
|
9
|
+
You can install the package using pip:
|
|
10
|
+
|
|
11
|
+
.. code-block:: bash
|
|
12
|
+
|
|
13
|
+
pip install qsppack
|
|
14
|
+
|
|
15
|
+
Or install from source:
|
|
16
|
+
|
|
17
|
+
.. code-block:: bash
|
|
18
|
+
|
|
19
|
+
git clone https://github.com/beevus77/qsp.git
|
|
20
|
+
cd qsp
|
|
21
|
+
pip install -e .
|
|
22
|
+
|
|
23
|
+
Documentation
|
|
24
|
+
-------------
|
|
25
|
+
|
|
26
|
+
Full documentation is available at: https://qsp.readthedocs.io/
|
|
27
|
+
|
|
28
|
+
Features
|
|
29
|
+
--------
|
|
30
|
+
|
|
31
|
+
* Quantum Signal Processing optimization
|
|
32
|
+
* Support for various optimization methods (L-BFGS, FPI, Newton)
|
|
33
|
+
* Utility functions for Chebyshev polynomials and phase factor manipulation
|
|
34
|
+
|
|
35
|
+
License
|
|
36
|
+
-------
|
|
37
|
+
|
|
38
|
+
This project is licensed under the MIT License - see the LICENSE file for details.
|
|
@@ -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
|
+
]
|
|
@@ -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
|