Childs-Kothari-Somma (CKS)#
- CKS(A, b, eps, kappa=None, max_beta=None)[source]#
Performs the Childs–Kothari–Somma (CKS) quantum algorithm to solve the Quantum Linear Systems Problem (QLSP) \(A \vec{x} = \vec{b}\), using the Chebyshev approximation of \(1/x\).
The algorithm prepares a quantum state \(\ket{x} \propto A^{-1} \ket{b}\) within target precision \(\epsilon\) of the ideal solution \(\ket{x}\).
The asymptotic complexity is \(\mathcal{O}\!\left(\log(N)s \kappa^2 \text{polylog}\!\frac{s\kappa}{\epsilon}\right)\), where \(N\) is the matrix size, \(s\) its sparsity, and \(\kappa\) its condition number. This represents an exponentially better precision scaling compared to the HHL algorithm.
This function integrates all core components of the CKS approach: Chebyshev polynomial approximation, linear combination of unitaries (LCU), qubitization with reflection operators, and the Repeat-Until-Success (RUS) protocol. The semantics of the approach can be illustrated with the following circuit schematics:
- Implementation overview:
Compute the CKS parameters \(j_0\) and \(\beta\) (
CKS_parameters()).Generate Chebyshev coefficients and the auxiliary unary state (
cheb_coefficients(),unary_prep()).Build the core LCU structure and qubitization operator (
inner_CKS()).Apply the RUS post-selection step to project onto the valid success outcome.
This function supports interchangeable and independent input types for both the matrix
Aand vectorbfrom the QLSP \(A\vec{x} = \vec{b}\).Case distinctions for A
- Matrix input:
Ais either a NumPy array (Hermitian matrix), or SciPy Compressed Sparse Row matrix (Hermitian matrix). The block encoding unitary \(SEL\) and state preparation unitary \(PREP\) are constructed internally fromAviaPauli decomposition.
- Block-encoding input:
Ais a 3-tuple(U, state_prep, n)representing a block-encoding:- Ucallable
Callable
U(operand, case)applies the block-encoding unitary \(SEL\).
- state_prepcallable
Callable
state_prep(case)applies the block-encoding state preparatiion unitary \(PREP\) to an auxiliarycaseQuantumVariable .
- nint
The size of the auxiliary
caseQuantumVariable.
In this case, (an upper bound for) the condition number
kappamust be specified. Additionally, the block-encoding unitary \(U\) supplied must satisfy the property \(U^2 = I\), i.e., it is self-inverse. This condition is required for the correctness of the Chebyshev polynomial block encoding and qubitization step. Further details can be found here.
Case distinctions for b
- Vector input:
If
bis a NumPy array, a newoperandQuantumFloat is constructed, and state preparation is performed viaprepare(operand, b).
- Callable input:
If
bis a callable, it is assumed to prepare the operand state when called. TheoperandQuantumFloat is generated directly viaoperand = b().
- Parameters:
- Anumpy.ndarray or scipy.sparse.csr_matrix or tuple
Either the Hermitian matrix \(A\) of size \(N \times N\) from the linear system \(A \vec{x} = \vec{b}\), or a 3-tuple
(U, state_prep, n)representing a preconstructed block-encoding.- bnumpy.ndarray or callable
Either a vector \(\vec{b}\) of the linear system, or a callable that prepares the corresponding quantum state
operand.- epsfloat
Target precision \(\epsilon\), such that the prepared state \(\ket{\tilde{x}}\) is within error \(\epsilon\) of \(\ket{x}\).
- kappafloat, optional
Condition number \(\kappa\) of \(A\). Required when
Ais a block-encoding tuple(U, state_prep, n)rather than a matrix.- max_betafloat, optional
Optional upper bound on the complexity parameter \(\beta\).
- Returns:
- operandQuantumVariable
Quantum variable containing the final (approximate) solution state \(\ket{\tilde{x}} \propto A^{-1}\ket{b}\). When the internal RUS decorator reports success (
success_bool = True), this variable contains the valid post-selected solution. Ifsuccess_boolisFalse, the simulation automatically repeats until success.
Examples
The following examples demonstrate how the CKS algorithm can be applied to solve the quantum linear systems problem (QLSP) \(A \vec{x} = \vec{b}\), using either a direct Hermitian matrix input or a preconstructed block-encoding representation.
Example 1: Solving a 4×4 Hermitian system
First, we define a small Hermitian matrix \(A\) and a right-hand side vector \(\vec{b}\):
import numpy as np A = np.array([[0.73255474, 0.14516978, -0.14510851, -0.0391581], [0.14516978, 0.68701415, -0.04929867, -0.00999921], [-0.14510851, -0.04929867, 0.76587818, -0.03420339], [-0.0391581, -0.00999921, -0.03420339, 0.58862043]]) b = np.array([0, 1, 1, 1])
Next, we solve this linear system using the CKS quantum algorithm:
from qrisp.algorithms.cks import CKS from qrisp.jasp import terminal_sampling @terminal_sampling def main(): x = CKS(A, b, 0.01) return x res_dict = main()
The resulting dictionary contains the measurement probabilities for each computational basis state. To extract the corresponding quantum amplitudes (up to sign):
for k, v in res_dict.items(): res_dict[k] = v**0.5 q = np.array([res_dict.get(key, 0) for key in range(len(b))]) print("QUANTUM SIMULATION\n", q) # QUANTUM SIMULATION # [0.02714082 0.55709921 0.53035395 0.63845794]
We compare this to the classical normalized solution:
c = (np.linalg.inv(A) @ b) / np.linalg.norm(np.linalg.inv(A) @ b) print("CLASSICAL SOLUTION\n", c) # CLASSICAL SOLUTION # [0.02944539 0.55423278 0.53013239 0.64102936]
We see that we obtained the same result in our quantum simulation up to precision \(\epsilon\)!
To perform quantum resource estimation, replace the
@terminal_samplingdecorator with@count_ops(meas_behavior="0"):from qrisp.jasp import count_ops @count_ops(meas_behavior="0") def main(): x = CKS(A, b, 0.01) return x res_dict = main() print(res_dict) # {'cx': 3628, 't': 1288, 't_dg': 1702, 'p': 178, 'cz': 138, 'cy': 46, 'h': 960, 'x': 324, 's': 66, 's_dg': 66, 'u3': 723, 'gphase': 49, 'ry': 2, 'z': 11, 'measure': 16}
The printed dictionary lists the estimated quantum gate counts required for the CKS algorithm. Since the simulation itself is not executed, this approach enables scalable gate count estimation for linear systems of arbitrary size.
Example 2: Using a custom block encoding
The previous example displays how to solve the linear system for any Hermitian matrix \(A\), where the block-encoding is constructed from the Pauli decomposition of \(A\). While this approach is efficient when \(A\) corresponds to, e.g., an Ising or a Heisenberg Hamiltonian, the number of Pauli terms may not scale favorably in general. For certain sparse matrices, their structure can be exploited to construct more efficient block-encodings.
In this example, we construct a block-encoding representation for the following tridiagonal sparse matrix:
import numpy as np def tridiagonal_shifted(n, mu=1.0, dtype=float): I = np.eye(n, dtype=dtype) return (2 + mu) * I - 2*np.eye(n, k=n//2, dtype=dtype) - 2*np.eye(n, k=-n//2, dtype=dtype) N = 8 A = tridiagonal_shifted(N, mu=3) b = np.random.randint(0, 2, size=N) print(A) # [[ 5. 0. 0. 0. -2. 0. 0. 0.] # [ 0. 5. 0. 0. 0. -2. 0. 0.] # [ 0. 0. 5. 0. 0. 0. -2. 0.] # [ 0. 0. 0. 5. 0. 0. 0. -2.] # [-2. 0. 0. 0. 5. 0. 0. 0.] # [ 0. -2. 0. 0. 0. 5. 0. 0.] # [ 0. 0. -2. 0. 0. 0. 5. 0.] # [ 0. 0. 0. -2. 0. 0. 0. 5.]]
This matrix can be decomposed using three unitaries: the identity \(I\), and two shift operators \(V\colon\ket{k}\rightarrow-\ket{k+N/2 \mod N}\) and \(V^{\dagger}\colon\ket{k}\rightarrow-\ket{k-N/2 \mod N}\). We define their corresponding functions:
from qrisp import gphase, prepare, qswitch def I(qv): pass def V(qv): qv += N//2 gphase(np.pi, qv[0]) def V_dg(qv): qv -= N//2 gphase(np.pi, qv[0]) unitaries = [I, V, V_dg]
Additionally, the block encoding unitary \(U\) supplied must satisfy the property \(U^2 = I\), i.e., it is self-inverse. This condition is required for the correctness of the Chebyshev polynomial block encoding and qubitization step. Further details can be found here. In this case, the fact that \(V^2=(V^{\dagger})^2=I\) ensures that defining the block encoding unitary via a quantum switch case satsifies \(U^2 = I\).
We now define the block_encoding
(U, state_prep, n):coeffs = np.array([5,1,1]) alpha = np.sum(coeffs) def U(case, operand): qswitch(operand, case, unitaries) def state_prep(case): prepare(case, np.sqrt(coeffs/alpha)) block_encoding = (U, state_prep, 2)
We solve the linear system by passing this block-encoding tuple as
Ainto the CKS function:@terminal_sampling def main(): x = CKS(block_encoding, b, 0.01, np.linalg.cond(A)) return x res_dict = main()
We, again, compare the solution obtained by quantum simulation with the classical solution
for k, v in res_dict.items(): res_dict[k] = v**0.5 q = np.array([res_dict.get(key, 0) for key in range(N)]) c = (np.linalg.inv(A) @ b) / np.linalg.norm(np.linalg.inv(A) @ b) print("QUANTUM SIMULATION\n", q, "\nCLASSICAL SOLUTION\n", c) # QUANTUM SIMULATION # [0.43917486 0.31395935 0.12522139 0.43917505 0.43917459 0.12522104 0.31395943 0.43917502] # CLASSICAL SOLUTION # [0.43921906 0.3137279 0.12549116 0.43921906 0.43921906 0.12549116 0.3137279 0.43921906]
Quantum resource estimation can be performed in the same way as in the first example:
@count_ops(meas_behavior="0") def main(): x = CKS(block_encoding, b, 0.01, np.linalg.cond(A)) return x res_dict = main() print(res_dict) # {'h': 676, 't_dg': 806, 'cx': 1984, 't': 651, 's': 152, 's_dg': 90, 'u3': 199, 'gphase': 65, 'p': 149, 'z': 15, 'x': 126, 'measure': 142, 'ry': 2}
Finally, note that it is also possible to solve a linear system without using the Repeat-Until-Success decorator, by performing the necessary post-selection manually. Such an example can be found in
inner_CKS().
Circuit construction#
|
Miscellaneous#
|
Computes the Chebyshev-series complexity parameter \(\beta\) and the truncation order \(j_0\) for the truncated Chebyshev approximation of \(1/x\) used in the Childs–Kothari–Somma quantum algorithm. |
|
Calculates the positive coefficients \(\alpha_i\) for the truncated Chebyshev expansion of \(1/x\) up to order \(2j_0+1\), as described in the Childs–Kothari–Somma paper. |
|
Prepares the unary-encoded state \(\ket{\text{unary}}\) of Chebyshev coefficients. |