# Quantum Sine and Cosine Transforms

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:

${\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$
${\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$
${\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$
${\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 $$n\equiv$$x.len qubits:

$\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 (
H,
Output,
QNum,
allocate,
create_model,
execute,
qct_qst_type1,
qfunc,
)
from classiq.execution import (
ClassiqBackendPreferences,
ClassiqSimulatorBackendNames,
ExecutionPreferences,
)

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))

from classiq import prepare_amplitudes

@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)

from classiq import synthesize, write_qmod

write_qmod(qmod, "qct_qst_type1", decimal_precision=15)
qprog = synthesize(qmod)

res = execute(qprog).result()

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 res[0].value.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)

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)

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+1\equiv$$ x.len $$+1$$ qubits:

$\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 $${\rm DCT}^{(2)}$$.

Function: qst_type2

Arguments:

• x: QArray[QBit]: the quantum state on which we apply $${\rm DST}^{(2)}$$.

### Example

import numpy as np

from classiq import (
Output,
QArray,
QBit,
create_model,
execute,
invert,
prepare_amplitudes,
qct_qst_type2,
qfunc,
)
from classiq.execution import (
ClassiqBackendPreferences,
ClassiqSimulatorBackendNames,
ExecutionPreferences,
)

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(1, q)
H(q)
qct_qst_type2(x, q)

qmod = create_model(main, execution_preferences=execution_preferences)

write_qmod(qmod, "qct_qst_type2", decimal_precision=15)
qprog = synthesize(qmod)

res = execute(qprog).result()

qct_data = np.zeros(2 ** (NUM_QUBITS - 1)).astype(complex)
qst_data = np.zeros(2 ** (NUM_QUBITS - 1)).astype(complex)
for sample in res[0].value.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)

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)

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]

from classiq import qct_type2

@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)

write_qmod(qmod, "qct_type2", decimal_precision=15)
qprog = synthesize(qmod)
res = execute(qprog).result()
qct_data = np.zeros(2 ** (NUM_QUBITS - 1)).astype(complex)

for sample in res[0].value.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)

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]

from classiq import qst_type2

@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)

write_qmod(qmod, "qst_type2", decimal_precision=15)
qprog = synthesize(qmod)
res = execute(qprog).result()
qst_data = np.zeros(2 ** (NUM_QUBITS - 1)).astype(complex)

for sample in res[0].value.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)

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]