Optimization with variational circuits

Hey guys

I’m solving a hybrid optimization problem using a variational circuit.

In my approach I am trying to adapt the VQE implementation that appears in the documentation (with molecules, for 3 Hamiltonians, etc.).

I wrote the Hamiltonian whose expected value I want to minimize and the variational circuit I converted from Qiskit (it was converted as a Processor).

The problem appears in the optimization block.

I get the following error message:

AttributeError: ‘Processor’ object has no attribute ‘requires_polarization’

I noticed that the VQE implementation circuit that appears in the documentation is of the Circuit class.

Maybe that’s the problem…

Could anyone give me some light? I’m new to LOQC

thank you

Hello,

Could you please provide us with some minimal code showing how you build you Perceval circuit/processor and how you call the simulation part?

From the error message only, it seems you’re giving a Processor to an algorithm which expects a Circuit, but without some code, it’s hard to give you any form of advice.

Thanks,
Eric

Hi Eric, thanks for the reply

Sorry for sending long code like this. But I think I still don’t understand how the workflow works in Perceval. Any help in this regard will be valuable.

# imports
from tqdm.auto import tqdm
import perceval as pcvl
import qutip as qt
import numpy as np
from scipy.optimize import minimize
import random
import math
import matplotlib.pyplot as plt
simulator = pcvl.Simulator(pcvl.NaiveBackend())

# variational circuit from qiskit
from qiskit import QuantumCircuit
import numpy as np
from perceval.converters import QiskitConverter
from perceval.components import catalog

def ansatz(params, qubits=4, depth=2):
    # Inicializando o circuito
    qc = QuantumCircuit(qubits)
    
    # Aplicando rotações iniciais (RY) com os parâmetros
    for q in range(qubits):
        qc.ry(params[q], q)
    
    # Construindo as camadas de entanglement e rotações
    for d in range(1, depth + 1):
        # Aplicando CNOTs para entanglement
        for q in range(qubits - 1):
            qc.cx(q, q + 1)
        
        # Aplicando rotações adicionais (RY) com novos parâmetros
        for q in range(qubits):
            qc.ry(params[d * qubits + q], q)
    
    return qc

# Exemplo de uso
qubits = 4
depth = 2
# Gerando parâmetros aleatórios para o exemplo
params = np.random.rand(depth * qubits + qubits) * 2 * np.pi

# Construindo o circuito com o ansatz
circuit = ansatz(params, qubits, depth)

# Desenhando o circuito
circuit.draw(output='mpl')

qiskit_convertor = QiskitConverter(catalog)
VQE_ansatz = qiskit_convertor.convert(circuit)
pcvl.pdisplay(VQE_ansatz)

# parameters
List_Parameters=[]

theta1 = pcvl.P("theta1", min_v=0, max_v=2*math.pi, periodic=True)
theta2 = pcvl.P("theta2", min_v=0, max_v=2*math.pi, periodic=True)
theta3 = pcvl.P("theta3", min_v=0, max_v=2*math.pi, periodic=True)
theta4 = pcvl.P("theta4", min_v=0, max_v=2*math.pi, periodic=True)
theta5 = pcvl.P("theta5", min_v=0, max_v=2*math.pi, periodic=True)
theta6 = pcvl.P("theta6", min_v=0, max_v=2*math.pi, periodic=True)
theta7 = pcvl.P("theta7", min_v=0, max_v=2*math.pi, periodic=True)
theta8 = pcvl.P("theta8", min_v=0, max_v=2*math.pi, periodic=True)
theta9 = pcvl.P("theta9", min_v=0, max_v=2*math.pi, periodic=True)
theta10 = pcvl.P("theta10", min_v=0, max_v=2*math.pi, periodic=True)
theta11 = pcvl.P("theta11", min_v=0, max_v=2*math.pi, periodic=True)
theta12 = pcvl.P("theta12", min_v=0, max_v=2*math.pi, periodic=True)

List_Parameters.append(pcvl.Parameter("theta1"))
List_Parameters.append(pcvl.Parameter("theta2"))
List_Parameters.append(pcvl.Parameter("theta3"))
List_Parameters.append(pcvl.Parameter("theta4"))
List_Parameters.append(pcvl.Parameter("theta5"))
List_Parameters.append(pcvl.Parameter("theta6"))
List_Parameters.append(pcvl.Parameter("theta7"))
List_Parameters.append(pcvl.Parameter("theta8"))
List_Parameters.append(pcvl.Parameter("theta9"))
List_Parameters.append(pcvl.Parameter("theta10"))
List_Parameters.append(pcvl.Parameter("theta11"))
List_Parameters.append(pcvl.Parameter("theta12"))

# states

#Input states of the photonic circuit
input_states = {
    pcvl.BasicState([1,0,1,0,1,0,1,0]):"|0000>"}

#Outputs in the computational basis
output_states = {
    pcvl.BasicState([1,0,1,0,1,0,1,0]):"|0000>",
    pcvl.BasicState([1,0,1,0,1,0,0,1]):"|0001>",
    pcvl.BasicState([1,0,1,0,0,1,1,0]):"|0010>",
    pcvl.BasicState([1,0,1,0,0,1,0,1]):"|0011>",
    pcvl.BasicState([1,0,0,1,1,0,1,0]):"|0100>",
    pcvl.BasicState([1,0,0,1,1,0,0,1]):"|0101>",
    pcvl.BasicState([1,0,0,1,0,1,1,0]):"|0110>",
    pcvl.BasicState([1,0,0,1,0,1,0,1]):"|0111>",
    pcvl.BasicState([0,1,1,0,1,0,1,0]):"|1000>",
    pcvl.BasicState([0,1,1,0,1,0,0,1]):"|1001>",
    pcvl.BasicState([0,1,1,0,0,1,1,0]):"|1010>",
    pcvl.BasicState([0,1,1,0,0,1,0,1]):"|1011>",
    pcvl.BasicState([0,1,0,1,1,0,1,0]):"|1100>",
    pcvl.BasicState([0,1,0,1,1,0,0,1]):"|1101>",
    pcvl.BasicState([0,1,0,1,0,1,1,0]):"|1110>",
    pcvl.BasicState([0,1,0,1,0,1,0,1]):"|1111>"}


# hamiltonian
H_coef = np.matrix([0.15678558607249998, # ZIII
                  0.028064070963899998, # IZII
                  0.056759040681399996, # IIZI
                  0.14976534056,        # IIIZ
                  0.5001297664055,      # ZZII
                  0.5000901235775,      # ZIZI
                  0.5001608621845,      # ZIIZ
                  0.5000371605536,      # IZZI
                  0.5001139557625,      # IZIZ
                  0.500068957711])      # IIZZ

# representação matricial do hamiltoniano

Z = np.array([[1,0],[0,-1]])
I = np.identity(2)

II = np.kron(I,I)
IZ = np.kron(I,Z)
ZI = np.kron(Z,I)
ZZ = np.kron(Z,Z)

Z0 = np.kron(ZI,II)
Z1 = np.kron(IZ,II)
Z2 = np.kron(II,ZI)
Z3 = np.kron(II,IZ)
Z0_Z1 = np.kron(ZZ,II)
Z0_Z2 = np.kron(ZI,ZI)
Z0_Z3 = np.kron(ZI,IZ)
Z1_Z2 = np.kron(IZ,ZI)
Z1_Z3 = np.kron(IZ,ZI)
Z2_Z3 = np.kron(II,ZZ)

H_elem = np.array([Z0, Z1, Z2, Z3, Z0_Z1, Z0_Z2, Z0_Z3, Z1_Z2, Z1_Z3, Z2_Z3])

H_coef = np.array([0.15678558607249998, 0.028064070963899998, 0.056759040681399996, 
                   0.14976534056, 0.5001297664055, 0.5000901235775, 
                   0.5001608621845, 0.5000371605536, 0.5001139557625, 
                   0.500068957711])

Z = np.array([[1, 0], [0, -1]])
I = np.identity(2)

II = np.kron(I, I)
IZ = np.kron(I, Z)
ZI = np.kron(Z, I)
ZZ = np.kron(Z, Z)

Z0 = np.kron(ZI, II)
Z1 = np.kron(IZ, II)
Z2 = np.kron(II, ZI)
Z3 = np.kron(II, IZ)
Z0_Z1 = np.kron(ZZ, II)
Z0_Z2 = np.kron(ZI, ZI)
Z0_Z3 = np.kron(ZI, IZ)
Z1_Z2 = np.kron(IZ, ZI)
Z1_Z3 = np.kron(IZ, IZ)
Z2_Z3 = np.kron(II, ZZ)

H_elem = np.array([Z0, Z1, Z2, Z3, Z0_Z1, Z0_Z2, Z0_Z3, Z1_Z2, Z1_Z3, Z2_Z3])

H = np.sum([H_coef[i] * H_elem[i] for i in range(len(H_coef))], axis=0)

# loss function
def minimize_loss(lp=None):
    # Updating the parameters on the chip
    for idx, p in enumerate(lp):
        List_Parameters[idx].set_value(p)

    #Simulation, Quantum processing part of the VQE
    simulator.set_circuit(VQE_ansatz)

    # Collecting the output state of the circuit
    psi = []
    for input_state in input_states:
        for output_state in output_states:
            psi.append(simulator.prob_amplitude(input_state,output_state))

        #Evaluating the mean value of the Hamiltonian.  # The Hamiltonians H is defined in the following block
    psi_prime=np.dot(H,psi)
    loss = np.real(sum(sum(np.conjugate(psi)*np.array(psi_prime[0]))))/(sum([i*np.conjugate(i) for i in psi]))
    loss=np.real(loss)

    tq.set_description('%g / %g  loss function=%g' % (R, len(H), loss))
    return(loss)

# optimization process
tq = tqdm(desc='Minimizing...') #Displaying progress bar

E =[]

init_param=[]

H

if len(init_param) == 0:
        init_param = [2*np.pi*random.random() for _ in List_Parameters]
else:
    for i in range(len(init_param)):
        init_param[i] = VQE_ansatz.get_parameters()[i]._value

# Finding the ground state eigen value of H
result = minimize(minimize_loss, init_param, method='Nelder-Mead')

E1.append(result.get('fun'))
tq.set_description('Finished' )

print(E)

Obrigado,
Ramses

Hello Ramses,

Sorry for the delay in response. As my colleague had guessed, the issue with your code is because the converted processor is not compatible with the simulator which needs a circuit object. The solution to your problem is to create a perceval simulator using a SimulatorFactory with a Processor to run your simulations.

vqe_proc = qiskit_convertor.convert(circuit)  # circuit -> your qiskit circuit
simulator = SimulatorFactory.build(vqe_proc, NaiveBackend)

Given your circuit has CNOT gates, the LO implementation will include additional heralding and post-selection modes to the circuit and your 8 mode input_state will not be compatible. You would have to include them in your input_state that you choose for the simulator. Furthermore, computing probability amplitude with heralds/post-selection would not be very useful; I would suggest to use evolve() method of the simulator to get the full statevector at the output of the circuit from which you can get the basicstates and their amplitudes.

Example code for how to write the input_state and the use of evolve method -

vqe_proc = qiskit_convertor.convert(circuit)  # circuit -> your qiskit circuit
simulator = SimulatorFactory.build(vqe_proc, NaiveBackend)

state_herald_sec = list(vqe_proc.heralds())  # to extract the information of the input-state in heralded modes
input_states = pcvl.BasicState([1, 0, 1, 0, 1, 0, 1, 0] + state_herald_sec)
output_state_vector = simulator.evolve(input_state)

Best,
Raksha