Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
116 changes: 73 additions & 43 deletions qrng/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,35 +2,41 @@
from qiskit import IBMQ
import math
import struct
from typing import Union

_circuit = None
_bitCache = ''
def set_provider_as_IBMQ(token: str = None):
_bitCache = ""


def set_provider_as_IBMQ(token: str = str(None)):
"""
Sets the backend provider to IBMQ with the given account token. Will fall back to a local (simulated) provider if no token is given.

Parameters:
token (string): Account token on IBMQ. If no token is given, will fall back to a local provider.
"""
global provider
if token == None or '':
provider = qiskit.BasicAer
else:
if token == None or "":
provider = qiskit.BasicAer
else:
IBMQ.save_account(token)
IBMQ.load_account()
provider = IBMQ.get_provider('ibm-q')

provider = IBMQ.get_provider("ibm-q")


def _set_qubits(n):
global _circuit
qr = qiskit.QuantumRegister(n)
cr = qiskit.ClassicalRegister(n)
_circuit = qiskit.QuantumCircuit(qr, cr)
_circuit.h(qr) # Apply Hadamard gate to qubits
_circuit.measure(qr,cr) # Collapses qubit to either 1 or 0 w/ equal prob.
_circuit.h(qr) # Apply Hadamard gate to qubits
_circuit.measure(qr, cr) # Collapses qubit to either 1 or 0 w/ equal prob.


_set_qubits(8) # Default Circuit is 8 Qubits

def set_backend(backend: str = 'qasm_simulator'):
_set_qubits(8) # Default Circuit is 8 Qubits


def set_backend(backend: str = "qasm_simulator"):
"""
Sets the backend to one of the provider's available backends (quantum computers/simulators).

Expand All @@ -39,26 +45,34 @@ def set_backend(backend: str = 'qasm_simulator'):
"""
global _backend
global provider
available_backends = provider.backends(backend, filters = lambda x: x.status().operational == True)
if (backend != '') and (backend in str(available_backends)):
available_backends = provider.backends(
backend, filters=lambda x: x.status().operational == True
)
if (backend != "") and (backend in str(available_backends)):
_backend = provider.get_backend(backend)
else:
print(str(backend)+' is not available. Backend is set to qasm_simulator.')
_backend = qiskit.BasicAer.get_backend('qasm_simulator')
print(str(backend) + " is not available. Backend is set to qasm_simulator.")
_backend = qiskit.BasicAer.get_backend("qasm_simulator")
_set_qubits(_backend.configuration().n_qubits)


# Strips QISKit output to just a bitstring.
def _bit_from_counts(counts):
return [k for k, v in counts.items() if v == 1][0]


# Populates the bitCache with at least n more bits.
def _request_bits(n):
global _bitCache
iterations = math.ceil(n/_circuit.width()*2)
for _ in range(iterations):
# Create new job and run the quantum circuit
job = qiskit.execute(_circuit, _backend, shots=1)
_bitCache += _bit_from_counts(job.result().get_counts())
if _circuit == None:
print("Circuit is None")
else:
iterations = math.ceil(n / _circuit.width() * 2)
for _ in range(iterations):
# Create new job and run the quantum circuit
job = qiskit.execute(_circuit, _backend, shots=1)
_bitCache += _bit_from_counts(job.result().get_counts())


def get_bit_string(n: int) -> str:
"""
Expand All @@ -69,11 +83,12 @@ def get_bit_string(n: int) -> str:
"""
global _bitCache
if len(_bitCache) < n:
_request_bits(n-len(_bitCache))
_request_bits(n - len(_bitCache))
bitString = _bitCache[0:n]
_bitCache = _bitCache[n:]
return bitString


# Running time is probabalistic but complexity is still O(n)
def get_random_int(min: int, max: int) -> int:
"""
Expand All @@ -83,23 +98,27 @@ def get_random_int(min: int, max: int) -> int:
min (int): The minimum possible returned integer.
max (int): The maximum possible returned integer.
"""
delta = max-min
n = math.floor(math.log(delta,2))+1
result = int(get_bit_string(n),2)
while(result > delta):
result = int(get_bit_string(n),2)
delta = max - min
n = math.floor(math.log(delta, 2)) + 1
result = int(get_bit_string(n), 2)
while result > delta:
result = int(get_bit_string(n), 2)
result += min
return result


# def getRandomIntEntaglement(min,max):


def get_random_int32() -> int:
"""Returns a uniformly random 32 bit integer."""
return int(get_bit_string(32),2)
return int(get_bit_string(32), 2)


def get_random_int64() -> int:
"""Returns a uniformly random 64 bit integer."""
return int(get_bit_string(64),2)
return int(get_bit_string(64), 2)


def get_random_float(min: float = 0, max: float = 1) -> float:
"""
Expand All @@ -112,9 +131,10 @@ def get_random_float(min: float = 0, max: float = 1) -> float:
Algorithm provided by: https://experilous.com/1/blog/post/perfect-fast-random-floating-point-numbers.
"""
unpacked = 0x3F800000 | get_random_int32() >> 9
packed = struct.pack('I',unpacked)
value = struct.unpack('f',packed)[0] - 1.0
return (max-min)*value+min # Scale float to given range
packed = struct.pack("I", unpacked)
value = struct.unpack("f", packed)[0] - 1.0
return (max - min) * value + min # Scale float to given range


def get_random_double(min: float = 0, max: float = 1) -> float:
"""
Expand All @@ -127,13 +147,22 @@ def get_random_double(min: float = 0, max: float = 1) -> float:
Algorithm provided by: https://experilous.com/1/blog/post/perfect-fast-random-floating-point-numbers.
"""
unpacked = 0x3FF0000000000000 | get_random_int64() >> 12
packed = struct.pack('Q',unpacked)
value = struct.unpack('d',packed)[0] - 1.0
return (max-min)*value+min # Scale double to given range
packed = struct.pack("Q", unpacked)
value = struct.unpack("d", packed)[0] - 1.0
return (max - min) * value + min # Scale double to given range


# Returns a random complex with both real and imaginary parts
# from the given ranges. If no imaginary range specified, real range used.
def get_random_complex_rect(real_min: float = 0, real_max: float = 0, img_min: float | None = None, img_max: float | None = None) -> complex:
from typing import Union


def get_random_complex_rect(
real_min: float = 0,
real_max: float = 0,
img_min: Union[float, None] = None,
img_max: Union[float, None] = None,
) -> complex:
"""
Returns a random complex number with:
Real-part: a uniformly sampled single-precision float from the range [real_min,real_max).
Expand All @@ -149,18 +178,19 @@ def get_random_complex_rect(real_min: float = 0, real_max: float = 0, img_min: f
img_min = real_min
if img_max is None:
img_max = real_max
re = get_random_float(real_min,real_max)
im = get_random_float(img_min,img_min)
return re+im*1j
re = get_random_float(real_min, real_max)
im = get_random_float(img_min, img_max)
return re + im * 1j


def get_random_complex_polar(r: float = 1, theta: float = 2*math.pi) -> complex:
def get_random_complex_polar(r: float = 1, theta: float = 2 * math.pi) -> complex:
"""
Returns a random complex uniformly sampled from an arc sweeping across the complex plane, starting at theta = 0.

Parameters:
r (float): The radius of the arc being sampled. Default is 1.
theta (float): The ending angle of the arc.
"""
r0 = r * math.sqrt(get_random_float(0,1))
theta0 = get_random_float(0,theta)
return r0*math.cos(theta0)+r0*math.sin(theta0)*1j
r0 = r * math.sqrt(get_random_float(0, 1))
theta0 = get_random_float(0, theta)
return r0 * math.cos(theta0) + r0 * math.sin(theta0) * 1j