Optimization of Purity without measurement using qml.SpecialUnitary

I would like to optimize the purity of input user noisy density matrix using qml.SpecialUnitary after adding ancilla. However, I would like to evaluate purity of the density matrix of the system after tracing out ancilla after evolution of Unitary. Here is my code. My point is shall I have to define a Hamiltonian ? Can It would not be possible after discarding ancilla we evaluate system density matrix then optimize using Gradientdescent. ? I am attching my code

## Designing the quantum circuit for purity
import pennylane as qml
from pennylane import numpy as np
import jax
import qutip as qtp
from scipy.optimize import minimize
dev = qml.device('default.mixed', wires = 2)
@qml.qnode(dev)
def circuit(rho,U):
  qml.QubitDensityMatrix(rho,wires = [0,1])
  # qml.adjoint(qml.QubitUnitary(U,wires = [0,1]))
  qml.QubitUnitary(U,wires = [0,1])
  return qml.state()

x = 0.412
theta = x * np.array([0, 1, 2, 0, -1, 1, 0, 0, 0, 1, 1, 1, 0, 0, -1])
U = qml.matrix(qml.SpecialUnitary(theta,wires = [0,1]))
rho = sigma


# print("A special unitary rotation gives :")
print(circuit(rho,U).round(2))


@qml.qnode(dev)
def purity(theta):
    '''
    Initializes a density matrix and takes its purity.
    '''
    qml.QubitDensityMatrix(circuit(rho,U), wires= [0,1])
    return qml.purity(0)


def cost(theta):
    '''
    Computes linear entropy from purity.
    '''
    return 1 - purity(theta)

In the circuit(), function Is it necessary to define a Hamiltonian like. Here I do not want to measure the system density matrix so I am doing indirect measurement to the ancilla.

H = 0.6 * qml.PauliZ(1) - 0.8 * qml.PauliZ(1)

Then return its expectation value and optimize?

Hey @SUDHIR_KUMAR_SAHOO,

I’m not sure I understand your application completely :thinking:. But, one thing I can suggest to simplify your code is to make use of the purity transform.

import pennylane as qml
import jax
from jax import numpy as jnp

dev = qml.device("default.mixed", wires=1)

@qml.qnode(dev)
def circuit(x):
    qml.SpecialUnitary(x, wires=[0])
    return qml.state()


def cost(x):
    return 1 - qml.qinfo.transforms.purity(circuit, wires=[0])(x)

x = jnp.array([0.1, 0, 0])

print(circuit(x))
print(cost(x))

exact_grad = jax.grad(cost)(x)
print(exact_grad)
[[0.99003345+0.j         0.        -0.09933469j]
 [0.        +0.09933469j 0.00996671+0.j        ]]
-3.5762787e-07
[1.4901161e-08 0.0000000e+00 0.0000000e+00]

Using qml.qinfo.transforms.purity lets you define only one QNode instead of two (like what you’re doing).

There’s no need to define a Hamiltonian when using SpecialUnitary like how I think you want to use it :).

1 Like

Hello @isaacdevlugt Thank you so much.

1 Like

My pleasure! Do let me know if your problem has been solved :slight_smile:

Hello @isaacdevlugt , one question is in my mind If we know the Unitary matrix can we know the paraemeters ? For example we can write every unitary as the (\exp{i\sum_m \theta_m G_m }), so for example lets consider CNOT gate what are the value of theta_m so that it exactly matches with CNOT matrix.

Hey @SUDHIR_KUMAR_SAHOO, great question!

The term used to describe what you’re talking about is a generator (the generator is the operator G in your equation). For CNOT, I’m not sure what the generator is :thinking:… but PennyLane lets you access what they are with the generator() method. Here’s an example with RX, whose generator we know is PauliX:

>>> op = qml.RX(0.1, wires=0)
>>> op.generator()
-0.5 * X(0)
>>> op.generator().terms()
([-0.5], [X(0)])
1 Like