Skip to main content

View on GitHub

Open this notebook in GitHub to run it yourself
The quantum Sine and Cosine transforms functions are the quantum analog for the discrete Sine and Cosine transforms. The unitary versions of the type I and type II transforms are defined as follows: DCTjk(1)(N)=αjk2N1cos(πjkN1),αjk={12j=0,N1,12k=0,N1,1else,j,k=0,N1{\rm DCT}^{(1)}_{jk}(N) = \alpha_{jk}\sqrt{\frac{2}{N-1}} \cos\left(\frac{\pi j k}{N-1}\right), \qquad \alpha_{jk} = \left\{ \begin{array}{l l} \frac{1}{\sqrt{2}} & j = 0,N-1 ,\\ \frac{1}{\sqrt{2}} & k = 0,N-1 ,\\ 1 & \text{else} \end{array} \right., \qquad j,k = 0\dots,N-1 DSTjk(1)(N)=2N+1sin(πjkN+1),j,k=0,N1{\rm DST}^{(1)}_{jk}(N) = \sqrt{\frac{2}{N+1}} \sin\left(\frac{\pi j k}{N+1}\right), \qquad j,k = 0\dots,N-1 DCTjk(2)(N)=αjk2Ncos(π(j+1/2)kN),αjk={12k=0,1else,j,k=0,N1{\rm DCT}^{(2)}_{jk}(N) = \alpha_{jk}\sqrt{\frac{2}{N}} \cos\left(\frac{\pi (j+1/2) k }{N}\right), \qquad \alpha_{jk} = \left\{ \begin{array}{l l} \frac{1}{\sqrt{2}} & k = 0 ,\\ 1 & \text{else} \end{array} \right., \qquad j,k = 0\dots,N-1 DSTjk(2)(N)=αjk2Nsin(π(j+1/2)(k+1)N),αjk={12k=N1,1else,j,k=0,N1{\rm DST}^{(2)}_{jk}(N) = \alpha_{jk} \sqrt{\frac{2}{N}} \sin\left(\frac{\pi (j+1/2) (k+1)}{N}\right), \qquad \alpha_{jk} = \left\{ \begin{array}{l l} \frac{1}{\sqrt{2}} & k = N-1 ,\\ 1 & \text{else} \end{array} \right., \qquad j,k = 0\dots,N-1 The open library includes four functions, following the implementation in Ref. [1]:

QCT and QST of type I

Function: qct_qst_type1 Arguments:
  • x: QArray[QBit]
The x quantum argument is the quantum state on which we apply the transforms, according to the following unitary on nn\equivx.len qubits: (DCT(1)(2n1+1)00iDST(1)(2n11))\left( \begin{array}{ccc|c} {} &{} &{} \\ {}&{\rm DCT}^{(1)}(2^{n-1}+1) & {}& 0\\ {} &{} &{} \\ \hline {} & 0 & {} & i{\rm DST}^{(1)}(2^{n-1}-1) \end{array} \right)

Example

import numpy as np

from classiq import *

NUM_QUBITS = 4
execution_preferences = ExecutionPreferences(
    num_shots=1,
    backend_preferences=ClassiqBackendPreferences(
        backend_name=ClassiqSimulatorBackendNames.SIMULATOR_STATEVECTOR
    ),
)


np.random.seed(123)
cos_data = np.random.rand(2 ** (NUM_QUBITS - 1) + 1)
cos_data = cos_data / np.linalg.norm(cos_data)
sin_data = np.random.rand(2 ** (NUM_QUBITS - 1) - 1)
sin_data = sin_data / np.linalg.norm(sin_data)

combined_data = np.append(cos_data / np.sqrt(2), sin_data / np.sqrt(2))

@qfunc
def main(x: Output[QNum]):
    prepare_amplitudes(combined_data.tolist(), 0.0, x)
    qct_qst_type1(x)


qmod = create_model(main, execution_preferences=execution_preferences)

qprog = synthesize(qmod)

result = execute(qprog).result_value()

qct_data = np.zeros(2 ** (NUM_QUBITS - 1) + 1).astype(complex)
qst_data = np.zeros(2 ** (NUM_QUBITS - 1) - 1).astype(complex)
for sample in result.parsed_state_vector:
    value = int(sample.state["x"])
    if value < 2 ** (NUM_QUBITS - 1) + 1:
        qct_data[value] += sample.amplitude
    else:
        qst_data[int(value - 2 ** (NUM_QUBITS - 1) - 1)] += sample.amplitude

def dct1(n):
    dct = np.array(
        [
            [
                np.cos(np.pi * j * k / (n - 1))
                * (np.sqrt(1 / 2) if j == 0 or j == n - 1 else 1)
                * (np.sqrt(1 / 2) if k == 0 or k == n - 1 else 1)
                for j in range(n)
            ]
            for k in range(n)
        ]
    ) / np.sqrt((n - 1) / 2)
    return dct


def dst1(n):
    dst = np.array(
        [
            [np.sin(np.pi * (j + 1) * (k + 1) / (n + 1)) for j in range(n)]
            for k in range(n)
        ]
    ) / np.sqrt((n + 1) / 2)

    return dst

global_phase = np.exp(1j * np.angle(qct_data[0]))
measured_cos_res = np.real(qct_data / global_phase)
expected_cos_res = (dct1(2 ** (NUM_QUBITS - 1) + 1) @ cos_data) / np.sqrt(2)
print("measured result:", measured_cos_res)
print("expected result:", expected_cos_res)

assert np.allclose(measured_cos_res, expected_cos_res, atol=0.01)
Output:
measured result: [ 0.64936034 -0.13662084  0.02159456  0.08089956  0.06722088  0.18669631
    0.02254746 -0.01198615  0.1123771 ]
  expected result: [ 0.64936034 -0.13662084  0.02159456  0.08089956  0.06722088  0.18669631
    0.02254746 -0.01198615  0.1123771 ]
  

global_phase = np.exp(1j * np.angle(qst_data[0]))
measured_sin_res = np.real(qst_data / global_phase)
expected_sin_res = (dst1(2 ** (NUM_QUBITS - 1) - 1) @ sin_data) / np.sqrt(2)
print("measured result:", measured_sin_res)
print("expected result:", expected_sin_res)

assert np.allclose(measured_sin_res, expected_sin_res, atol=0.01)
Output:
measured result: [ 0.57556991  0.04712138  0.22433696 -0.27513447  0.17796799  0.07685908
    0.05378552]
  expected result: [ 0.57556991  0.04712138  0.22433696 -0.27513447  0.17796799  0.07685908
    0.05378552]
  

QCT and QST of type II

Function: qct_qst_type2 Arguments:
  • x: QArray[QBit],
  • q: QBit
The x quantum argument is the quantum state on which we apply the transforms, whereas the single q qubit indicates the block, according to the following unitary on n+1n+1\equiv x.len +1+1 qubits: (DCT(2)(2n1)00DST(2)(2n1))\left( \begin{array}{c|c} {\rm DCT}^{(2)}(2^{n-1}) & 0\\ \hline 0 & -{\rm DST}^{(2)}(2^{n-1}) \end{array} \right) Function: qct_type2 Arguments:
  • x: QArray[QBit]: the quantum state on which we apply DCT(2){\rm DCT}^{(2)}.
Function: qst_type2 Arguments:
  • x: QArray[QBit]: the quantum state on which we apply DST(2){\rm DST}^{(2)}.

Example

NUM_QUBITS = 4
cos_sin_data = np.random.rand(2 ** (NUM_QUBITS - 1))
cos_sin_data = cos_sin_data / np.linalg.norm(cos_sin_data)

@qfunc
def main(x: Output[QNum], q: Output[QBit]):
    prepare_amplitudes(cos_sin_data.tolist(), 0.0, x)
    allocate(q)
    H(q)
    qct_qst_type2(x, q)


qmod = create_model(main, execution_preferences=execution_preferences)

qprog = synthesize(qmod)

result = execute(qprog).result_value()

qct_data = np.zeros(2 ** (NUM_QUBITS - 1)).astype(complex)
qst_data = np.zeros(2 ** (NUM_QUBITS - 1)).astype(complex)
for sample in result.parsed_state_vector:
    if sample.state["q"] == 0:
        qct_data[int(sample.state["x"])] += sample.amplitude
    else:
        qst_data[int(sample.state["x"])] += sample.amplitude

def dct2(n):
    dct = np.array(
        [
            [
                np.cos(np.pi * j * (k + 1 / 2) / n) * (np.sqrt(1 / 2) if j == 0 else 1)
                for j in range(n)
            ]
            for k in range(n)
        ]
    ) / np.sqrt(n / 2)
    return dct.T


def dst2(n):
    dst = np.array(
        [
            [
                np.sin(np.pi * (j + 1) * (k + 1 / 2) / n)
                * (np.sqrt(1 / 2) if j == n - 1 else 1)
                for j in range(n)
            ]
            for k in range(n)
        ]
    ) / np.sqrt(n / 2)
    return dst.T

global_phase = np.exp(1j * np.angle(qct_data[0]))
measured_cos_res = np.real(qct_data / global_phase)
expected_cos_res = (dct2(2 ** (NUM_QUBITS - 1)) @ cos_sin_data) / np.sqrt(2)
print("measured result:", measured_cos_res)
print("expected result:", expected_cos_res)

assert np.allclose(measured_cos_res, expected_cos_res, atol=0.01)
Output:
measured result: [ 0.65104654 -0.23305325 -0.11473439  0.02595719 -0.04930425  0.03323495
    0.06553173  0.01252819]
  expected result: [ 0.65104654 -0.23305325 -0.11473439  0.02595719 -0.04930425  0.03323495
    0.06553173  0.01252819]
  

global_phase = np.exp(1j * np.angle(qst_data[0]))
measured_sin_res = np.real(qst_data / global_phase)
expected_sin_res = (dst2(2 ** (NUM_QUBITS - 1)) @ cos_sin_data) / np.sqrt(2)
print("measured result:", measured_sin_res)
print("expected result:", expected_sin_res)

assert np.allclose(measured_sin_res, expected_sin_res, atol=0.01)
Output:
measured result: [ 0.63981132 -0.2180174   0.13530848 -0.08552637  0.02796936 -0.03450769
    0.12370004 -0.01455965]
  expected result: [ 0.63981132 -0.2180174   0.13530848 -0.08552637  0.02796936 -0.03450769
    0.12370004 -0.01455965]
  

@qfunc
def main(x: Output[QNum]):
    prepare_amplitudes(cos_sin_data.tolist(), 0.0, x)
    qct_type2(x)


qmod = create_model(main, execution_preferences=execution_preferences)

qprog = synthesize(qmod)

result = execute(qprog).result_value()
qct_data = np.zeros(2 ** (NUM_QUBITS - 1)).astype(complex)

for sample in result.parsed_state_vector:
    qct_data[int(sample.state["x"])] += sample.amplitude

global_phase = np.exp(1j * np.angle(qct_data[0]))
measured_cos_res = np.real(qct_data / global_phase)
expected_cos_res = dct2(2 ** (NUM_QUBITS - 1)) @ cos_sin_data
print("measured result:", measured_cos_res)
print("expected result:", expected_cos_res)

assert np.allclose(measured_cos_res, expected_cos_res, atol=0.01)
Output:
measured result: [ 0.92071884 -0.32958707 -0.16225893  0.03670901 -0.06972674  0.04700132
    0.09267587  0.01771754]
  expected result: [ 0.92071884 -0.32958707 -0.16225893  0.03670901 -0.06972674  0.04700132
    0.09267587  0.01771754]
  

@qfunc
def main(x: Output[QNum]):
    prepare_amplitudes(cos_sin_data.tolist(), 0.0, x)
    qst_type2(x)


qmod = create_model(main, execution_preferences=execution_preferences)

qprog = synthesize(qmod)
result = execute(qprog).result_value()
qst_data = np.zeros(2 ** (NUM_QUBITS - 1)).astype(complex)

for sample in result.parsed_state_vector:
    qst_data[int(sample.state["x"])] += sample.amplitude

global_phase = np.exp(1j * np.angle(qst_data[0]))
measured_sin_res = np.real(qst_data / global_phase)
expected_sin_res = dst2(2 ** (NUM_QUBITS - 1)) @ cos_sin_data
print("measured result:", measured_sin_res)
print("expected result:", expected_sin_res)

assert np.allclose(measured_sin_res, expected_sin_res, atol=0.01)
Output:
measured result: [ 0.90482984 -0.30832317  0.19135509 -0.12095255  0.03955464 -0.04880124
    0.17493827 -0.02059046]
  expected result: [ 0.90482984 -0.30832317  0.19135509 -0.12095255  0.03955464 -0.04880124
    0.17493827 -0.02059046]
  

References

[1]: Klappenecker, A., & Rotteler M., “Discrete Cosine Transforms on Quantum Computers”.