View on GitHub
Open this notebook in GitHub to run it yourself
- Phase statements
- Local variables, permutations, and auto-uncomputation
- Within-apply
- Hamiltonians, Pauli operators, and exponentiation
- Higher-order functions
- Execution parameters and hybrid execution
# TODO, then synthesize and run it. All solutions are
collected at the end of the notebook - try each exercise before peeking.
Note: If you are working in your own SDK environment, make sure Classiq is installed (pip install -U classiq) and that you have authenticated once by runningauthenticate()in a Python session. See the registration and installation guide.
- Phase Statements
phase statement applies phases to computational-basis states, where the Z-axis rotation can be a function of quantum numeric variables. In ket notation the operator maps to , where is an expression over the variables
For example, the following rotates each state by :
QNum in the range it rotates by , by (i.e., half turn), and by (coming back to ).
Note: An optional second argument multiplies the expression by a classical coefficient (Part I, Section 3) - typically an execution parameter, as in a QAOA cost layer.When the phase expression contains no quantum variables, the same phase is applied across all states. Still, this can be restricted to certain states by an enclosing
control statement.
For example, if is a QNum, the following rotates all states other than by :
Note: Phases are invisible to ordinary sampling - they don’t change measurement probabilities. To “see” them, use the top-level calculate_state_vector(qprog), which returns the amplitude (including phase) of each state.
Exercise A: Put two 2-qubit numbers x and y into uniform superposition, then encode their product x * y into the phase, scaled by a coefficient of (with the total number of qubits).
This divides the full turn into equal steps - one per unit of the product - so distinct product values land on distinct phases.
Use calculate_state_vector to inspect the resulting phases.
Expected result: every basis state has probability 1/16, but theExercise B: A phase oracle flips the phase (a rotation by ) of the basis states that satisfy some condition - the marking step at the heart of Grover’s algorithm. Using two 2-qubit numbersphasecolumn shows a rotation of for each state - e.g.x=2, y=1gives , andx=3, y=3gives (shown as , i.e. modulo ).
a and b in uniform superposition, complete mark_solutions to flip the phase of every state satisfying (similar to the exercise in Part I, Section 6).
Then inspect the statevector to confirm which states were marked.
Expected result: all 16 basis states keep probability 1/16, but exactly two of them - the assignments satisfying :(a, b) = (3, 0)and(2, 3)- show a phase of ; every other state shows0.
- Local Variables, Permutations, and Auto-Uncomputation
v and assigns it the computational-basis value
X and CX, as well as higher level arithmetic (Part I, Section 5). If a local can’t be uncomputed automatically the compiler will issue an error.
You can discard the variable explicitly with drop or manually uncompute it and call free.
See uncomputation for the full rules.
Exercise: Put a 2-qubit number x into uniform superposition.
Use a local variable tmp to store , and depending on tmp > 3, flip res. tmp is a scratch variable - it is uncomputed automatically at the end of the function, so it never appears in the output.
Expected result:xis uniform over 0-3, andresis1exactly whentmp = 2x + 1 > 3- that is, whenx > 1(xis 2 or 3). The scratch variabletmpwas uncomputed automatically, so the output contains onlyxandres.
- Within-Apply
apply block (the operation) actually needs to be controlled.
For example, the following applies Z to a qubit q in the Hadamard basis (which is equivalent to applying X to q):
Note: Initializing a variable in the within block implies that its inverse uncomputes and frees it. Hence, such a variable is subject to the same rules as local variables we discussed in Section 8.
Exercise A: A classic example of conjugation is performing addition in the Fourier basis by modifying relative phases.
Complete the within_apply so it computes y += x.
Variable x starts in uniform superposition and y at the fixed value 2, so each measured y should come out as x + 2.
Expected result:Exercise B: Now perform the same addition, but only when a control qubitxis uniform over 0-7, and each row’syequalsx + 2(soyranges 2-9) - the in-place addition carried out entirely through phase rotations in the Fourier basis.
ctrl is - put the whole within_apply under a control on ctrl.
Then run show(qprog) and inspect the quantum program: notice that the qft and its inverse are not controlled - only the apply (phase) block is.
Expected result: whenctrlis0,ystays2; whenctrlis1,yequalsx + 2. In the visualization, theqftand inverse-qftsurround the phase block but sit outside the control.
1
- Hamiltonians, Pauli Operators, and Exponentiation
I, X, Y, Z are the building blocks of qubit operators. A Hamiltonian - a system’s energy operator - can be represented as a weighted sum of Pauli matrix products. In Qmod this is called Pauli operator, and you build it from the Pauli enum, where Pauli.X(0) is X on qubit 0; multiply to form a product (unmentioned qubits are implicitly I), and add to sum terms.
Here is an example Hamiltonian:
r repeated short steps over the individual terms.
The built-in function suzuki_trotter applies it, given the Hamiltonian, the evolution time, the order, and the repetitions r.
A Pauli operator can also serve as an observable - a quantity whose expectation value we measure.
Where sample returns the distribution over basis states, the SDK function observe returns the expectation value of a given observable.
Exercise A: Use suzuki_trotter to simulate evolution under
on 4 qubits, with evolution time , 2nd order, and repetitions.
Tip: Avoid declaring a Python variable with the nameHin the global scope, because it would eclipse the functionH(the Hadamard function)
hadamard_transform), then use observe to compute the expectation value of the observable .
Expected result:observereturns2.0- each qubit is in , the eigenstate ofX, so .
1
- Higher-Order Functions
QCallable (the Qmod analog of Python’s Callable).
When calling a higher-order function, you pass a function or a lambda expression, whose signature must match the one specified by the QCallable type.
For example, the following declares foo with a function-type parameter op; calling foo requires passing a function that takes a classical real and a qubit:
Note: Passing a lambda to a higher-order function works just like passing one to a built-in statement - e.g.See more under operators. Exercise A: Definerepeat(qarr.len, lambda i: H(qarr[i])), whereiis the lambda’s CInt parameter (Part I, Section 4).
my_apply_to_all - a higher-order function that applies a single-qubit operation op to every qubit of qarr.
Then use it to apply H to all three qubits, producing a uniform superposition.
Expected result: all eight bit strings appear with roughly equal ~1/8 probability - H was applied to every qubit.
Exercise B: qpe (quantum phase estimation) is a built-in higher-order function: given a unitary U as its operand, it estimates the phase of an eigenstate, where , and writes it into a QNum.
Here is an eigenstate of CRZ(pi, state[0], state[1]) with .
Complete the qpe call to estimate it.
Expected result:thetacomes out0.25with near-certainty - the phase ofCRZ(pi)on its eigenstate .
1
- Execution Parameters and Hybrid Execution
main parameters (CReal, CArray[CReal], …) that stay symbolic through synthesis.
You synthesize once and execute the same quantum program multiple times.
The classical logic can be a variational optimizer minimizing a cost (VQE, QAOA), an iterative scheme adapting parameters from measured outcomes (IQAE), or a sweep over values fixed by classical processing (such as Shor).
Values are supplied at execution through the parameters argument of sample or observe, a dict mapping each parameter name to its value.
For example, if main declares a parameter params of type CArray[CReal, 2], the following runs the synthesized quantum program at specified values:
sample for the measured distribution, or observe for an expectation value of a Hamiltonian (Section 10) - the cost a variational algorithm minimizes.
See execution for all options.
Note: Execution parameters are ordinary numbers, so use Python floats (e.g.Exercise A: The parametric program below rotates two qubits bymath.pi), not the symbolicpiused inside function bodies.
params[0] and params[1], then entangles them (one layer of a typical ansatz).
Synthesize it once, then - reusing that same qprog
sampleit at two different settings,[0.2, 1.0]and[1.5, 0.5], and compare the distributions.
Expected result: two distributions from the same synthesizedExercise B: Using the sameqprog.[0.2, 1.0]is dominated by00(10next);[1.5, 0.5]splits roughly evenly between00and11.
qprog from Exercise A, compute the expectation value of at parameter values with observe.
Remember to pass the angles as numbers.
Expected result: observe returns a single number, - the expectation value on the state prepared at those parameters.
Solutions
Try each exercise before checking the solution below.Solution 7
- Phase Statements
Solution 8
- Local Variables, Permutations, and Auto-Uncomputation
Solution 9
- Within-Apply
Solution 10
- Hamiltonians, Pauli Operators, and Exponentiation
- Suzuki-Trotter evolution**
observe
Solution 11
- Higher-Order Functions
qpe
Solution 12
- Execution Parameters and Hybrid Execution
observe