|
| 1 | +import pennylane as qml |
| 2 | +import numpy as np |
| 3 | +# Number of visible and hidden qubits |
| 4 | +n_v, n_h = 2, 1 |
| 5 | +dev = qml.device('default.qubit', wires=n_v+n_h) |
| 6 | +# Define a variational circuit (QNode) |
| 7 | +@qml.qnode(dev, interface='autograd') |
| 8 | +def circuit(params): |
| 9 | +# params is a vector of rotation angles |
| 10 | +# Prepare all qubits in |0> |
| 11 | +# Example ansatz: one layer of rotations + entangling gates |
| 12 | + for i in range(n_v + n_h): |
| 13 | + qml.RY(params[i], wires=i) |
| 14 | +# entangle visible to hidden |
| 15 | + for i in range(n_v): |
| 16 | + qml.CNOT(wires=[i, n_v]) # connect each visible i to hidden n_v |
| 17 | + return qml.probs(wires=list(range(n_v))) |
| 18 | + |
| 19 | +# Example target distribution over 2 visible bits |
| 20 | +target = np.array([0.3, 0.2, 0.1, 0.4]) # must sum to 1 |
| 21 | +def loss(params): |
| 22 | + probs = circuit(params) # model probabilities for visible states |
| 23 | +# Add small epsilon to avoid log(0) |
| 24 | + return np.sum(target * np.log((target + 1e-9) / probs)) |
| 25 | +def parameter_shift_grad(params): |
| 26 | + grads = np.zeros_like(params) |
| 27 | + shift = np.pi/2 |
| 28 | + for idx in range(len(params)): |
| 29 | + shift_vector = np.zeros_like(params) |
| 30 | + shift_vector[idx] = shift |
| 31 | + probs_plus = circuit(params + shift_vector) |
| 32 | + probs_minus = circuit(params - shift_vector) |
| 33 | + loss_plus = np.sum(target * np.log((target + 1e-9) / probs_plus)) |
| 34 | + loss_minus = np.sum(target * np.log((target + 1e-9) / probs_minus)) |
| 35 | + grads[idx] = 0.5 * (loss_plus - loss_minus) |
| 36 | + return grads |
| 37 | + |
| 38 | +#Initialize parameters and perform a simple gradient descent |
| 39 | +params = np.random.normal(0, 0.1, size=(n_v+n_h,)) |
| 40 | +learning_rate = 0.1 |
| 41 | +for epoch in range(100): |
| 42 | + grads = parameter_shift_grad(params) |
| 43 | + params -= learning_rate * grads |
0 commit comments