<jupyter_start><jupyter_text>Robust randomness generation on quantum processing units Random numbers are a ubiquitous resource in computation and cryptography. For example, in security, random numbers are crucial to creating keys for encryption. Quantum random number generators (QRNGs), that make use of the inherent unpredictability in quantum physics, promise enhanced security compared to standard cryptographic pseudo-random number generators (CPRNGs) based on classical technologies.In this notebook, we implement our own QRNG. Namely, we program two separate quantum processor units (QPUs) from different suppliers in Amazon Braket to supply two streams of weakly random bits. We then show how to generate physically secure randomness from these two weak sources by means of classical post-processing based on randomness extractors. The prerequisites for the tutorial are a basic understanding of quantum states, quantum measurements, and quantum channels. For a detailed explanation of these concepts we refer to the Amazon Braket notebook [Simulating noise on Amazon Braket](https://github.com/aws/amazon-braket-examples/blob/main/examples/braket_features/Simulating_Noise_On_Amazon_Braket.ipynb).We believe that randomness generation is a practical application of nowadays available noisy and intermediate scale quantum (NISQ) technologies. Table of contents* [Amazon Braket](amazon_braket)* [Quantum circuit for randomness generation](quantum_circuit)* [Quick implementation](quick_implementation)* [Critical assessment](critical_assessment)* [Interlude randomness extractors](interlude)    * [Quantum information](quantum_information)* [Extractor construction](extractor_construction)    * [Example](example)    * [Implementation](implementation)* [Unpredictability of physical sources](physical_sources)    * [Noise as leakage](noise_leakage)    * [Numerical evaluation](numerical_evaluation)* [Putting everything together](putting-together)* [Beyond current implementation](beyond_current)* [Literature](literature)  Amazon Braket We start out with some general Amazon Braket imports, as well as some mathemtical tools needed.<jupyter_code># AWS imports: Import Braket SDK modules
from braket.circuits import Circuit
from braket.devices import LocalSimulator
from braket.aws import AwsDevice, AwsQuantumTask

# set up local simulator device
device = LocalSimulator()

# general math imports
import math, random
import numpy as np
from scipy.fft import fft, ifft

# magic word for producing visualizations in notebook
%matplotlib inline

# import convex solver
import cvxpy as cp
# set up Rigetti quantum device
rigetti = AwsDevice("arn:aws:braket:us-west-1::device/qpu/rigetti/Aspen-M-1")

# set up IonQ quantum device
ionq = AwsDevice("arn:aws:braket:::device/qpu/ionq/ionQdevice")

# simulator alternative: set up the on-demand simulator SV1
simulator = AwsDevice("arn:aws:braket:::device/quantum-simulator/amazon/sv1")<jupyter_output><empty_output><jupyter_text>Quantum circuit for randomness generation Arguably the simplest way of generating a random bit on a quantum computer is as follows:* Prepare the basis state vector $|0\rangle$* Apply the Hadamard gate $H=\frac{1}{\sqrt{2}}\begin{pmatrix} 1 & 1\\ 1 & 1 \end{pmatrix}$ leading to the state vector $|H\rangle=\frac{1}{\sqrt{2}}\big(|0\rangle+|1\rangle\big)$* Measure in the computational basis $\big\{|0\rangle\langle0|,|1\rangle\langle1|\big\}$.By the laws of quantum physics, the post-measurement probability distribution is then the uniformly distributed $(1/2,1/2)$ and leads to one random bit $0/1$.In the following, we discuss how above protocol is conceptually different from randomness obtained from classical sources and show in detail how it is implemented reliable even when the underlying quantum processing units employed are noisy. By the end of this tutorial, you will be able to create your own random bits from the quantum processing units available on Amazon Braket. Quick implementation The Hadmard gate based quantum circuit for generating one random bit can be repeated or run in parallel $n$ times, leading to a random bit string of length $n$. The corresponding circuit is easily implemented in Amazon Braket:<jupyter_code># function for Hadamard cirquit
def hadamard_circuit(n_qubits):
    """
    function to apply Hadamard gate on each qubit
    input: number of qubits
    """

    # instantiate circuit object
    circuit = Circuit()

    # apply series of Hadamard gates
    for i in range(n_qubits):
        circuit.h(i)

    return circuit

# define circuit
n_qubits = 5
state = hadamard_circuit(n_qubits)

# print circuit
print(state)<jupyter_output>T  : |0|
        
q0 : -H-
        
q1 : -H-
        
q2 : -H-
        
q3 : -H-
        
q4 : -H-

T  : |0|<jupyter_text>Let us run this Hadamard circuit with $n=5$ qubits in the local quantum simulator for $m=1$ shots:(Note: We will work on actual QPUs towards the end of this tutorial.)<jupyter_code># run circuit
m_shots = 1
result = device.run(state, shots = m_shots).result()

# get measurement shots
counts = result.measurement_counts.keys()

# print counts
list_one = list(counts)[0]
array_one = np.array([list_one])
print("The output bit string is: ",array_one)<jupyter_output>The output bit string is:  ['01000']<jupyter_text>Critical assessment The advantage of such quantum random number generators over implementations based on classical technologies is that the outcomes are intrinsically random. That is, according to the laws of quantum physics, the outcome of the measurement is not only hard to predict, but rather impossible to know before the measurement has taken place.However, since current quantum processing units are noisy to a certain degree, there are at least three potential problems that need to be addressed:* First, the noise acting on all states and operations performed might lead to a systematic bias towards the probability of getting the measurement outcomes $0$ or $1$, respectively.* Second, even if aforementioned noise is not biased towards certain measurement outcomes, the generated randomness is no longer solely based on intrinsically random quantum effects, but rather partly on the noise present.* Third, whereas by the laws of quantum physics a pure quantum state cannot be correlated to the outside world, any noisy acting on the system corresponds to information leaking to the environment. This is because no information is destroyed in quantum physics and hence, a malicious third party knowing about the noise occurring will be able to guess the generated bits (at least up to a certain degree).When the noise model acting on the quantum processor units is characterized to some degree (e.g., by means of previous benchmarking), these shortcomings can be overcome by employing two independent quantum processor units, together with an appropriate classical post-processing. The latter is based on classical algorithms from the theory of pseudo-randomness, so-called two-source extractors. This is what we discuss next.In the following, we refer a few times to the theory paper [1] that features formal cryptographic security definitions, together with mathematical proofs, as well as some statistical methods tailored to intermediate scale quantum devices. These pointers can be safely ignored when only interested in the implementation of our QRNG. Interlude randomness extractors Two-source extractors allow distillation of physically secure random bits from two independent weak sources of randomness whenever they are sufficiently unpredictable to start with. The relevant measure of unpredictability is thereby given by the min-entropy of the respective sources, defined for the probability distribution $\{p_x\}_{x\in X}$ as$$ \text{$H_{\min}(X)=-\log_2p_{\text{guess}(X)}$ with $p_{\text{guess}(X)}=\max_{x\in X}p_x$.} $$That is, the min-entropy exactly quantifies how well we can guess the value of the source, or in other words, how unpredictable the source is. For example, for $n$-bit distributions $X$ we have $H_{\min}(X)\in[0,n]$, where $0$ corresponds to a deterministic distribution containing no randomness and $n$ to the perfectly random, uniform distribution.A two-source extractor is a function $\text{Ext}:\{0,1\}^{n_1}\times\{0,1\}^{n_2}\to\{0,1\}^m$ such that for any two independent sources with min-entropy at least $H_{\min}(X_1)\geq k_1$ and $H_{\min}(X_2)\geq k_2$, respectively, the output of length $m$ is $\epsilon\in[0,1]$ close in variational distance to the perfectly random, uniform distribution $U_M$ of size $m$:$$ \frac{1}{2}\left\|\text{Ext}(X_1,X_2)-U_M\right\|_1\leq\epsilon. $$So, two inpedendent sources that are only weakly random get condensed by these algorithms to one output that is (nearly) perfectly random! Importantly, the output becomes truly physically random with no computational assumptions introduced. Quantum information For our setting we need an extension of this concept, as a potentially malicious third party can collect quantum information $Q$ about the weak source of randomness $X$. The corresponding conditional min-entropy is defined as$$ H_{\min}(X|Q)=-\log_2p_{\text{guess}(X|Q)}, $$where $p_{\text{guess}(X|Q)}$ denotes the maximal probability allowed by quantum physics to guess the classical value $X$ by applying any measurements on the quantum information $Q$. We notice that even though $p_{\text{guess}(X|Q)}$ does not have a closed form expression, it is efficiently computed by means of a semidefinite program (see the theory notes [1] for details). This is also how we will evaluate the conditional min-entropy quantity later on.Accordingly, a quantum-proof two-source extractor is then function $\text{Ext}:\{0,1\}^{n_1}\times\{0,1\}^{n_2}\to\{0,1\}^m$ such that for any two independent sources with quantum conditional min-entropy at least $H_{\min}(X_1|Q_1)\geq k_1$ and $H_{\min}(X_2|Q_2)\geq k_2$, respectively, we have for $\epsilon\in[0,1]$ in quantum variational distance$$ \frac{1}{2}\left\|\rho_{\text{Ext}(X_1,X_2)Q_1Q_2}-\tau_{M}\otimes\rho_{Q_1}\otimes\rho_{Q_2}\right\|_1\leq\epsilon,$$where $\tau_M$ denotes the fully mixed $m$ qubit state. That is, the extractor should not only make the $m$ output bits perfectly random, but also decouple them from any outside correlations initially present - up to the security parameter $\epsilon\in[0,1]$.For more details about these concepts, we refer to the theory notes [1] and references therein. All that is important to us here, is that there exist quantum-proof two-source extractors with good parameters. Next, we discuss one particular such construction that we subsequently implement in an efficient manner. Extractor construction In this paragraph, we provide an explicit construction of a quantum-proof two-source extractor that efficiently provides non-zero output $M$ for a wide range of sizes of the inputs $X_1$ and $X_2$. Namely, we employ a Toeplitz matrices based construction originally discussed in [2]:For the security parameter $\epsilon\in(0,1]$ and inputs $X_1,X_2$ of size $n$ and $n-1$, respectively, the function $\text{Ext}:\{0,1\}^n\times\{0,1\}^{n-1}\to\{0,1\}^m$ defined below is a quantum-proof two-source randomness extractor with output size$$ m=\left\lfloor(k_1+k_2-n)+1-2\log\left(1/\epsilon\right)\right\rfloor. $$The function is explicitly given via the vector-matrix multiplication$$ (x,y)\mapsto \text{Ext}(x,y)=x\cdot(T(y)|1_m)^T \mod{2}$$featuring the Toeplitz matrix$$ T(y)=\begin{pmatrix}y_0 & y_1 & \ldots & y_{n-m-1}\\y_{-1} & y_0 & \ldots & y_{n-m-2}\\\vdots & \vdots & \vdots & \vdots\\y_{1-m} & y_{2-m} & \ldots & y_{n-2m}\end{pmatrix}\;\text{from}\;y=(y_{1-m},\ldots,y_0,\ldots,y_{n-m-1})\in\{0,1\}^{n-1}, $$The quantum-proof property of this construction, as well as its complexity, is explicitly disccused in the theory notes [1].For our setting, we will have sources with linear min-entropy rates $k_i=\alpha_i\cdot n$ for $i=1,2$. The Toeplitz construction then works whenever $\alpha_1+\alpha_2-1>0$ and we can compute the required input size for fixed output size as$$ n=\left\lfloor\frac{m-1+2\log(1/\epsilon)}{\alpha_1+\alpha_2-1}\right\rfloor.$$A simple example shows that these numbers work well in practice, even for very small input sizes around $n\approx100$. Example Let us set the security parameter to $\epsilon=10^{-8}$, say that we have min-entropy sources with linear rates $k_1=n\cdot0.8$ and $k_2=(n-1)\cdot0.8$, and ask for $m=100$ fully random bits. According to the formulae above, $n=253$ together with $n-1=252$ weakly random bits can be condensed into $100$ fully random bits (up to the security parameter $\epsilon=10^{-8}$).Next, we give an efficient implementation of this Toeplitz based construction. Implementation The vector-matrix multiplication $x\cdot(T(y)|1_m)^T$ a priori has asymptotic complexity $O(n^2)$ in big O-notation, which is prohibitive for larger input sizes $n\geq10^4$. However, we discuss in the theory notes [1] that the operation is actually implemented in asymptotic complexity $O(n\log n)$ by first embedding the problem into circulant matrices and then making use of the Fast Fourier Transform (FFT). The corresponding code then performs well up to input sizes $n\geq10^7$. The following example demonstrates this implementation:<jupyter_code># work with local simulator for testing Toeplitz construction
device = LocalSimulator()

# set security parameter
power = 8
eps = 10**(-power)
print(f"Security parameter: {eps}.")

# set number of output bits 
m = 10
print(f"Desired output length: {m} bits.")

# set min-entropy rates for sources
k_1 = 0.8
print(f"Min-entropy rate of first source: {k_1}.")
k_2 = 0.8
print(f"Min-entropy rate of second source: {k_2}")

# required number of input bits (for each source)
n = math.floor((m-1-2*math.log2(eps))
    /(k_1+k_2-1))
print(f"Required length of each input source: {n} bits.")

# quantum circuit for generating weakly random bit string one
n1_qubits = 1
m1_shots = n
state1 = hadamard_circuit(n1_qubits)
result1 = device.run(state1, shots=m1_shots).result()
array_one = result1.measurements.reshape(1,m1_shots*n1_qubits)
# print(array_one)

# quantum circuit for generating weakly random bit string two
n2_qubits = 1
m2_shots = n
state2 = hadamard_circuit(n2_qubits)
result2 = device.run(state2, shots=m2_shots).result()
array_two = result2.measurements.reshape(1,m2_shots*n2_qubits)
# print(array_two)

###
# alternative for generating two bit strings when no quantum source is available:

# create first list of pseudo-random bits
# alternative when no quantum source is available
# list_one = []
# for number in range(n):
#    b = int(random.randint(0, 1))
#    list_one.append(b)
# array_one = np.array([list_one])

# create second list of pseudo-random bits
# list_two = []
# for number in range(n):
#    b = int(random.randint(0, 1))
#    list_two.append(b)
# array_two = np.array([list_two])
###

# computing output of Toeplitz extractor by vector-matrix multiplication
# via efficient Fast Fourier Transform (FFT) as discussed in [1]

# setting up arrays for FFT implementation of Toeplitz
array_two_under = np.array(array_two[0,0:n-m])[np.newaxis]
zero_vector = np.zeros((1,n+m-3), dtype=int)
array_two_zeros = np.hstack((array_two_under,zero_vector))
array_two_over = array_two[0,n-m:n][np.newaxis]
array_one_merged = np.zeros((1,2*n-3), dtype=int)
for i in range(m):
    array_one_merged[0,i] = array_one[0,m-1-i]
for j in range(n-m-1):
    array_one_merged[0,n+m-2+j] = array_one[0,n-2-j]

# FFT multplication output of Toeplitz
output_fft = np.around(ifft(fft(array_one_merged)*fft(array_two_zeros)).real)
output_addition = output_fft[0,0:m] + array_two_over
output_final = output_addition.astype(int) % 2
print(f"The {m} random output bits are:\n{output_final}.")<jupyter_output>Security parameter: 1e-08.
Desired output length: 10 bits.
Min-entropy rate of first source: 0.8.
Min-entropy rate of second source: 0.8
Required length of each input source: 103 bits.
The 10 random output bits are:
[[0 1 0 0 1 0 1 0 0 1]].<jupyter_text>As an alternative, we note that efficient implementations of other quantum-proof two-source extractors are discussed in [3]. Unpredictability of physical sources Given above methods on randomness extraction, the next step is to give lower bounds on the min-entropy present in the output distributions generated from our $n$-fold Hadamard circuit. For that, we need to model the noise present in the quantum processing units.Generally, for any given quantum processing unit, the supplier typically publishes some type of noise specification with it. This includes both, the noise characterization of state preparation, as well as the read-out measurements. In case such specifications are not available, or if one wants to double check them, it is in principle possible to benchmark the device. We refer to the theory notes [1] for more details on this and just mention here that we do not need a full characterization of the device, but rather only conservative upper bounds on the noise strength. This then translates into lower bounds on the min-entropy present in the system.For our case, since we are only applying single qubit gates for our Hadamard circuit, the noise is captured well by single qubit noise models. Moreover, for the state preparation step via Hadamard gates, the typical noise in quantum architectures is uniform, depolarizing noise of some strength $\lambda\in[0,1]$. That is, all possible single qubit errors such as bit flip or phase flip errors are equally likely, leading to the effective evolution$$ \psi=|\psi\rangle\langle\psi|\mapsto\text{Dep}^\lambda(\psi)=(1-\lambda)\cdot\psi+\lambda\cdot\frac{|0\rangle\langle0|+|1\rangle\langle1|}{2}, $$mapping any input qubit state $\psi$ onto a linear combination of itself and the maximally mixed qubit state $\frac{|0\rangle\langle0|+|1\rangle\langle1|}{2}$. Note that we now work with general mixed states in order to model classical statistical uncertainty coming from the noise model.So effectively, before the measurement step, instead of the perfect pure state $|H\rangle\langle H|_A$ as defined by the vector $|H\rangle_A=\frac{1}{\sqrt{2}}\big(|0\rangle_A+|1\rangle_A\big)$, we have the mixed state$$ \rho_A^\lambda=\frac{1}{2}\big(|0\rangle\langle0|_A+(1-\lambda)|0\rangle\langle1|_A+(1-\lambda)|1\rangle\langle0|_A+|1\rangle\langle1|_A\big) $$at hand.Next, we note that instead of the ideal measurement device given by $\mathcal{M}=\big\{|0\rangle\langle0|_A,|1\rangle\langle1|_A\big\}$, the typical noisy measurement device is described by $$ \mathcal{N}^\mu=\big\{1_A-\mu|1\rangle\langle1|_A,\mu|1\rangle\langle1|_A\big\} $$with some bias $\mu\in(0,1)$ towards reading-out the ground state $|0\rangle\langle0|_A$ over $|1\rangle\langle1|_A$. The post-measurement probability distribution is then given as$$ Q^{\lambda,\mu}=(q_0=1-\mu/2,q_1=\mu/2) $$instead of the perfectly uniform distribution $P=(p_0=1/2,p_1=1/2)$. Note that the former distribution has non-maximal min-entropy$$ H_{\min}(X)_{Q^{\lambda,\mu}}=1-\log\mu\leq1=H_{\min}(X)_P. $$More generally, as measurement devices are the most sensitive element of quantum randomness generation, we discuss in the theory notes [1] methods for benchmarking them (even when only noisy state preparation is available). Noise as leakage It is, however, crucial to realize that $H_{\min}(X)_{Q^{\lambda,\mu}}$ is not yet the quantity relevant for secure quantum randomness generation. As information is never lost in quantum mechanics, all the noise carries information to the environment, where it can in principle be picked up by an attacker. That is, we need to estimate the conditional min-entropy of the post-measurement probability distribution $X$ given any complementary information that leaked into the environment [5].This is worked out in detail in the theory notes [1] by means of so-called purifications of both, the noisy qubit state $\rho_A^\lambda$, as well as the noisy measurement device $\mathcal{N}^\mu$, leading to the additional qubit registers $A'$ and $E_2$, respectively. The relevant so-called classical-quantum state to consider then takes the form$$ \omega_{XA'E_2}^{\lambda,\mu}= q_0\cdot|0\rangle\langle0|_X\otimes\omega_{A'E_2}^{\lambda,\mu}(0) + q_1\cdot|1\rangle\langle1|_X\otimes\omega_{A'E_2}^{\lambda,\mu}(1) $$with a classing part $X$ and the quantum parts $A'E_2$ depending on both noise parameters $\lambda,\mu$. The corresponding conditional min-entropy is$$ H_{\min}(X|A'E_2)_{\omega^{\lambda,\mu}}\leq H_{\min}(X)_{Q^{\lambda,\mu}} $$with the gap between the conditional and the unconditional term typically being strict.We reiterate that the reason for mathematically introducing the purification registers $AE_2$ and working with the conditional min-entropy, is to make sure that the output bits generated are random even conditioned on the knowledge of the noise. This ensures that the randomness created is from a purely quantum origin and is secure against any eavesdropping from a malicious third party.We mention that a more detailed discussion of this point is given in the work [5]. Numerical evaluation In the following, we use the noise parameters $\lambda=0.02$ and $\mu=0.98$ for the quantum processing units and this immediately gives$$H_{\min}(X)\approx0.944. $$The next step is to numerically evaluate the conditional min-entropy$$ H_{\min}(X|A'E_2)=-\log p_{\text{guess}}(X|A'E_2). $$This is done by means of a standard semidefinite program (sdp) solver as follows:<jupyter_code># fix noise parameters
lamb = 0.02
mu = 0.98

# purification of rho input state
rho = 0.5*np.array([[1,1-lamb],[1-lamb,1]])
eigvals, eigvecs = np.linalg.eig(rho)

rho_vector =\
    math.sqrt(eigvals[0])*np.kron(eigvecs[:,0],eigvecs[:,0])[np.newaxis]\
    +math.sqrt(eigvals[1])*np.kron(eigvecs[:,1],eigvecs[:,1])[np.newaxis]
rho_pure = np.kron(rho_vector,rho_vector.T)

# sigma state of noisy measurement device
sigma_vector = np.array([[math.sqrt(1-mu),0,0,math.sqrt(mu)]])
sigma_pure = np.kron(sigma_vector,sigma_vector.T)

# omega state relevant for conditional min-entropy
rho_sigma = np.kron(rho_pure,sigma_pure)

id_2 = np.identity(2)
zero = np.array([[1,0]])
one = np.array([[0,1]])

zerozero = np.kron(np.kron(zero,id_2),np.kron(zero,id_2))
zeroone = np.kron(np.kron(zero,id_2),np.kron(one,id_2))
onezero = np.kron(np.kron(one,id_2),np.kron(zero,id_2))
oneone = np.kron(np.kron(one,id_2),np.kron(one,id_2))

omega_0 = zerozero@rho_sigma@zerozero.T+zeroone@rho_sigma@zeroone.T+onezero@rho_sigma@onezero.T
omega_1 = oneone@rho_sigma@oneone.T

omega = []
omega.append(omega_0)
omega.append(omega_1)

# sdp solver
m = 4 # dimension of quantum side information states
c = 2 # number of classical measurement outcomes

sigma = cp.Variable((m,m), complex=True) # complex variable
constraints = [sigma >> 0] # positive semi-definite
constraints += [sigma >> omega[i] for i in range(c)] # min-entropy constraints
obj = cp.Minimize(cp.real(cp.trace(sigma))) # objective function

prob = cp.Problem(obj,constraints) # set up sdp problem
prob.solve(solver=cp.SCS, verbose=True) # solve sdp problem using splitting conic solver (SCS)
guess = prob.value
qmin_entropy = (-1)*math.log2(guess)
min_entropy = 1-math.log2(2-mu*(1-lamb))

print("\033[1m" + "The coditional min-entropy is: ", qmin_entropy)
print("As a comparison, the unconditional min-entropy is: ", min_entropy)<jupyter_output>===============================================================================
                                     CVXPY                                     
                                    v1.1.15                                    
===============================================================================
(CVXPY) Aug 26 08:35:40 PM: Your problem has 16 variables, 3 constraints, and 0 parameters.
(CVXPY) Aug 26 08:35:40 PM: It is compliant with the following grammars: DCP, DQCP
(CVXPY) Aug 26 08:35:40 PM: (If you need to solve this problem multiple times, but with different data, consider using parameters.)
(CVXPY) Aug 26 08:35:40 PM: CVXPY will first compile your problem; then, it will invoke a numerical solver to obtain a solution.
-------------------------------------------------------------------------------
                                  Compilation                                  
-------------------------------------------------------------------------------
(CVX[...]<jupyter_text>That is, for the chosen noise parameters $\lambda=0.02$ and $\mu=0.98$ we find$$ H_{\min}(X|A'E_2)\approx0.719<0.944\approx H_{\min}(X).$$By varying the noise parameter to other values, one also sees by inspection that the conditional min-entropy is monotone in both $\lambda$ and $\mu$. Importantly, this ensures that the outputted randomness will be safe to use whenever we put a conservative estimate on the noisy strength, even in the absence of an exact characterization of the underlying quantum processing units.Finally, whenever we run our Hadamard circuit $n$ times or on $n$ qubits in parallel, the overall conditional min-entropy is just given by$$ H_{\min}(X|RE_2)_{\omega^{\otimes n}} = n\cdot H_{\min}(X|RE_2)_\omega. $$This reason is that we only consider single qubit product noise together with the min-entropy being additive on product states. This then leads to the promised linear min-entropy rates $k_i=\alpha_i\cdot n$ for $i=1,2$ going into the Toeplitz two-source extractor.As an added bonus, the Toeplitz two-source extractor used has the so-called strong property. That is, even conditioned on the knowledge of one input string of weakly random bits, the output bits are still fully random. We refer to the technical notes [1] for discussion, a consequence being that if one provider builds in some backdoors in the provided unit, the random generation scheme is still not broken unless the two providers come together to cooperate. Putting everything together Now that we have determined the conditional min-entropy of our physical sources and have an efficient quantum-proof two-source extractor in place, all that remains is to put the two pieces together.1. First, we specify how many random bits we want to generate, the desired security parameter, and the conditional min-entropy of our weak sources of randomness from the quantum processing units:<jupyter_code># set security parameter
power = 8
eps = 10**(-power)
print(f"Security parameter: {eps}.")

# set number of output bits 
m = 10
print(f"Desired output length: {m} bits.")

# set min-entropy rates for sources - qmin_entropy from above
k_one = qmin_entropy
print(f"Min-entropy rate of first source: {k_1}.")
k_two = qmin_entropy
print(f"Min-entropy rate of second source: {k_2}.")

# required number of input bits (for each source)
n = math.floor((m-1-2*math.log2(eps))
    /(k_one+k_two-1))
print(f"Required length of each input source: {n} bits.")<jupyter_output>Security parameter: 1e-08.
Desired output length: 10 bits.
Min-entropy rate of first source: 0.8.
Min-entropy rate of second source: 0.8.
Required length of each input source: 141 bits.<jupyter_text>2. At the beginning of the notebook, we loaded two separate QPUs as available in Amazon Braket. We now run on the respective QPUs the Hadamard circuit followed by measurements in the computational basis:    (Note: If preferred, one can alternatively use the pre-loaded on-demand simulator SV1. In this case, please comment out the QPU code in the cell below and instead uncomment the provided SV1 code.)<jupyter_code># Rigetti: quantum circuit for generating weakly random bit string one
n1_q = 1 # alternatively use more than one qubit (attention: 32 max + lower bounds on number of shots)
m1_s = int(math.ceil(n/n1_q))
state_rigetti = hadamard_circuit(n1_q)
rigetti_task = rigetti.run(state_rigetti, shots=m1_s, poll_timeout_seconds=5*24*60*60)

rigetti_task_id = rigetti_task.id
rigetti_status = rigetti_task.state()
print("Status of Rigetti task:", rigetti_status)

# IonQ: quantum circuit for generating weakly random bit string two
n2_q = 1 # alternatively use more than one qubit (attention: 11 max + lower bounds on number of shots)
m2_s = int(math.ceil(n/n2_q))
state_ionq = hadamard_circuit(n2_q)
ionq_task = ionq.run(state_ionq, shots=m2_s, poll_timeout_seconds=5*24*60*60)

ionq_task_id = ionq_task.id
ionq_status = ionq_task.state()
print("Status of IonQ task:", ionq_status)

###
# alternative via on-demand simulator SV1

# quantum circuit for generating weakly random bit string one (simulate Rigetti source)
# n1_q = 1 # alternatively run multiple qubits in parallel
# m1_s = int(math.ceil(n/n1_q))
# state1 = hadamard_circuit(n1_q)
# result1 = simulator.run(state1, shots=m1_s).result()
# array_rigetti = result1.measurements.reshape(1,m1_s*n1_q)
# print("The first raw bit string is: ",array_one)

# quantum circuit for generating weakly random bit string two (simulate IonQ source)
# n2_q = 1 # alternatively run multiple qubits in parallel
# m2_s = int(math.ceil(n/n2_q))
# state2 = hadamard_circuit(n2_q)
# result2 = simulator.run(state2, shots=m2_s).result()
# array_ionq = result2.measurements.reshape(1,m2_s*n2_q)
# print("The second raw bit string is: ",array_two)
###<jupyter_output>Status of Rigetti task: CREATED
Status of IonQ task: CREATED<jupyter_text>3. The tasks have now been sent to the respective QPUs and we can recover the results at any point in time once the status is completed:    (Note: In case you opted to use the on-demand simulator SV1 instead of the QPUs, please do not run the following cell.)<jupyter_code># recover Rigetti task
task_load_rigetti = AwsQuantumTask(arn=rigetti_task_id)

# print status
status_rigetti = task_load_rigetti.state()
print('Status of Rigetti task:', status_rigetti)
# wait for job to complete
# terminal_states = ['COMPLETED', 'FAILED', 'CANCELLED']
if status_rigetti == 'COMPLETED':
    # get results
    rigetti_results = task_load_rigetti.result()
    
    # array
    array_rigetti = rigetti_results.measurements.reshape(1,m1_s*n1_q)
    print("The first raw bit string is: ",array_rigetti)
    
elif status_rigetti in ['FAILED', 'CANCELLED']:
    # print terminal message 
    print('Your Rigetti task is in terminal status, but has not completed.')

else:
    # print current status
    print('Sorry, your Rigetti task is still being processed and has not been finalized yet.')
    
# recover IonQ task
task_load_ionq = AwsQuantumTask(arn=ionq_task_id)

# print status
status_ionq = task_load_ionq.state()
print('Status of IonQ task:', status_ionq)
# wait for job to complete
# terminal_states = ['COMPLETED', 'FAILED', 'CANCELLED']
if status_ionq == 'COMPLETED':
    # get results
    ionq_results = task_load_ionq.result()
    
    # array
    #print(m1_shots,n1_qubits)
    #print(m2_shots,n2_qubits)
    #print(ionq_results.measurements)
    array_ionq = ionq_results.measurements.reshape(1,m2_s*n2_q)
    print("The second raw bit string is: ",array_ionq)
    
elif status_ionq in ['FAILED', 'CANCELLED']:
    # print terminal message 
    print('Your IonQ task is in terminal status, but has not completed.')

else:
    # print current status
    print('Sorry, your IonQ task is still being processed and has not been finalized yet.')<jupyter_output>Status of Rigetti task: QUEUED
Sorry, your Rigetti task is still being processed and has not been finalized yet.
Status of IonQ task: COMPLETED
The second raw bit string is:  [[0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
  0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
  1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
  1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1]]<jupyter_text>4. We run the Toeplitz two-source extractor on the two sequences of raw random input bits:<jupyter_code># setting up arrays for fft implementation of Toeplitz
if status_ionq == 'COMPLETED':
    array_two_under = np.array(array_ionq[0,0:n-m])[np.newaxis]
    zero_vector = np.zeros((1,n+m-3), dtype=int)
    array_two_zeros = np.hstack((array_two_under,zero_vector))
    array_two_over = array_ionq[0,n-m:n][np.newaxis]
    array_one_merged = np.zeros((1,2*n-3), dtype=int)
    if status_rigetti == 'COMPLETED':
        for i in range(m):
            array_one_merged[0,i] = array_rigetti[0,m-1-i]
        for j in range(n-m-1):
            array_one_merged[0,n+m-2+j] = array_rigetti[0,n-2-j]

        # fft multplication output of Toeplitz
        output_fft = np.around(ifft(fft(array_one_merged)*fft(array_two_zeros)).real)
        output_addition = output_fft[0,0:m] + array_two_over
        output_final = output_addition.astype(int) % 2
        print(f"The {m} random output bits are:\n{output_final}.")
    else: 
        print(f"Your Rigetti task is in {status_rigetti} state.")
else: 
    print(f"Your IonQ task is in {status_ionq} state.")<jupyter_output>Your Rigetti task is in QUEUED state.
{'fraction_non_alphanumeric': 0.10616053247424247, 'fraction_numerical': 0.022338388596784487, 'mean_word_length': 4.704576976421636, 'pattern_counts': {'":': 0, '<': 30, '<?xml version=': 0, '>': 34, 'https://': 1, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 27, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '9', 'dir': 'jupyter-structured-clean-dedup', 'id': '346431', 'license': 'Apache-2.0', 'n_tokens_mistral': 10210, 'n_tokens_neox': 9563, 'n_words': 3880, 'path': 'examples/advanced_circuits_algorithms/Randomness/Randomness_Generation.ipynb', 'repo_name': 'virajvchaudhari/amazon-braket-examples', 'size': '32903'}