This repository contains the code shown during the You don't need a PhD to try Quantum Computing presentation at DevOxx Belgium 2025.
In particular, it defines four different projects using just as many quantum simulators:
cirq: Google-provided, Python-based quantum simulator based on Noisy Intermediate-Scale Quantum computing (see the official documentation);Q#: Microsoft-provided quantum programming language for hybrid quantum/classical computing (see the official documentation);qiskit: IBM-provided, Python-based quantum software suite for research and algorithm optimisation (see the official documentation);strange: JVM-based simulator for rapid prototyping (see the official repository).
The repository contains a solution for the following problem, implemented using the four quantum simulators listed above:
Generate a truly random number in [0, max]
That is, generate a positive random number lower than max (input).
This problem is currently unsolvable on classical computers, where random numbers are actually "pseudo-random".
The algorithm to solve it on a hybrid quantum-classical application is as follows:
- Determine how many bits are needed to represent
max;nbits = floor(ln(max) / ln(2) + 1)
- Generate a binary string with length =
nbits(quantum part);result(base 2)
- If
result(base 10)> max, retry step #2; - Return
result(base 10).
The circuit for our quantum algorithm is as follows:
In particular, to generate a random bit, we just need a single qubit and a single Hadamard gate to put the qubit into superposition. After that, we just measure the qubit to obtain either 0 (with 50% probability) or 1 (with 50% probability). To be more precise, we have 50% of measuring either |0> or |1>, which are the basis states for the qubit.
The probability histogram for 100 runs of the Q# implementation (see below) is as follows:
Note that the number generation will not be truly random when we simulate our quantum code on a classical computer. This is because the |0> and |1> basis states (which then lead to a 0 or 1 classical bit, respectively) is "measured" with pseudo-random techniques. However, running the code on real quantum hardware will get us truly random measurements.
The set-up and the code is different based on the simulator.
This is a Python project. To run the code, install the following packages:
pip install qiskit-ibm-runtime qiskit qiskit-aer
The qiskit_random_number_generator.py (namely the quantum_random_number_generator function) file contains the Qiskit code to solve the problem above. It makes use of the Aer IBM simulator and needs 1 qubit and 1 classical bit (qc = QuantumCircuit(1, 1)). The code applies the Hadamard gate to the only qubit in the system (qc.h(0)) and then measures it, copying the result to the only classical bit (qc.measure(0, 0)).
The program runs the simulator as many times as needed to represent max (i.e. nbits times). The memory=True flag ensures the qubit measurements are all kept in memory (as we need them to generate the binary string representing result (base 2)). It then uses classical Python code to compose the bit string and to turn it into an integer.
As a side note, with the Aer simulator, we could model noise as well, thus making our algorithm closer to real quantum hardware.
This is a Python project. To run the code, install the following packages:
pip install cirq
The cirq_random_number_generator.py (namely the quantum_random_number_generator function) file contains the Cirq code to solve the problem above. It needs 1 qubit (created here as a LineQubit, but a NamedQubit would also be ok). The circuit is composed of the Hadamard gate as a first step (cirq.H(qubit)), and then of a measurement (cirq.measure(qubit)).
The program runs the simulator as many times as needed to represent max (i.e. nbits times). It then accesses the measurement results of the qubit (simulation_result.measurements["q(0)"]) and uses classical Python code to compose the bit string and to turn it into an integer.
Refer to the official documentation to install the QDK extension for Visual Studio Code.
The Q# code is much simpler compared to the other implementations, see the RandomNumberGenerator.qs file. In particular, the GenerateRandomBit() function allocates a qubit (use q = Qubit()), applies an Hadamard gate (H(q)) and then a measurement operator (M(q)) on it, returning the result after releasing the qubit itself. Resetting the qubit is necessary:
In Q#, qubits must be in the state when they're released to avoid errors in the quantum hardware.
The GenerateRandomNumberInRange() function simply allocates an array and fills it with random bits generated by GenerateRandomBit(). Notice that here we're using functions from Q# Math and Convert libraries, namely BitSizeI and ResultArrayAsInt. This shows that Q# defines some built-in utilities to let us work with bits and to convert bit strings into decimal numbers.
We can run the example using Microsoft's QDK extension, and we can also generate histograms showing the distribution of the generated bits (or that of the final numbers).
This is an SBT project. To open the SBT shell, just run the sbt command in the strange directory. Once sbt is ready, run the run task to execute the application.
The project contains a single Scala file, strange/src/main/scala/mdipirro/devoxxbelgium/RandomNumberGenerator.scala with the implementation of the algorithm. In this case, the example showcases a greater interaction between the quantum code and the classical application.
The RandomNumberGenerator trait defines a single method to generate a positive number lower than or equal to max. ClassicalNumberGenerator is a classical implementation that leverages the Random.nextLong method from the scala.util library. However, as its Scaladoc reads, Random.nextLong:
Returns a pseudorandom, uniformly distributed long value between 0 (inclusive) and the specified value (exclusive), drawn from this random number generator's sequence.
This means the generated value is not truly random at all. Instead, it's taken from a sequence of numbers generated using a seed. If we know the seed, we know the whole sequence. There are more secure implementations of Random in Java, but they all involve some kind of pseudo-randomness.
QuantumNumberGenerator, on the other hand, leverages quantum principles to generate a "truly" random number (remember the simulation argument we made above). It defines a simulator simulator = SimpleQuantumExecutionEnvironment() and a program (program = Program(...)). The latter is, in Strange, a sequence of steps. As we saw before, we just need a single qubit (that's the meaning of the first argument to the Program constructor) and a single step (with a Hadamard gate applied to the only qubit in the system, Step(Hadamard(0)).
The quantumRandomBitGeneration() function simply runs the quantum program and gets the measurement of the qubit. generateNumberWithNBits() calls it as many times as needed to generate nbits measurements and then uses a fold and bitwise Scala operators to compose the bit string ((acc << 1) | bit):
Example:
If `n = 4`, the function might generate:
- Iteration 1: bit = 1 → acc = `1` (base 2) = 1
- Iteration 2: bit = 0 → acc = `10` (base 2) = 2
- Iteration 3: bit = 1 → acc = `101` (base 2) = 5
- Iteration 4: bit = 0 → acc = `1010` (base 2) = 10
The rest of the code consists of Scala functions that repeatedly invoke generateNumberWithNBits() until a random number is generated that is lower than or equal to the max. The @tailrec annotation shows we can even mix recursion with quantum simulation, achieving a true hybrid application.
Lastly, note how more verbose the Strange code is, even if we removed some boilerplate thanks to Scala constructs.
Watch my talk at Devoxx Belgium about quantum computing for classical software developers: https://www.youtube.com/watch?v=5Y370b8iTq0

