Cost functions: Multiple wire measurements + backends for Hermitians


#1

Hey!

I would like to construct a cost function to minimise that looks like this

C = < z1 > + < z2 > + < z1z2 >

Where is the expectation over the Hermitian observable
zz_observable = (1/2)*np.array([[1,0,0,0],[0,-1,0,0],[0,0,-1,0],[0,0,0,1]])

  1. The Hermitian observable is not supported by the qiskit.basicaer backend. Do you know what might be a good replacement? I was hoping to use the noise functionality of the qiskit simulator.

  2. If inside the qnode you perform the < z1 > and < z1z2 > computations the error returned is
    QuantumFunctionError: Each wire in the quantum circuit can only be measured once.
    Does this mean that we can’t compute both < z1 > and < z1z2 >?

Full code + errors reproduced below.
CODE

Conversion between problem idxs (with signs) to qubits idxs (without signs)

sign = lambda x: np.sign(x)

def sign_qubit(term): # s denotes the sign and a and b refer to the idx. Minus 1 from idx to return to standard indexing
  a = abs(term[0]) - 1
  b = abs(term[1]) - 1
  sa = sign(term[0])
  sb = sign(term[1])
  return sa, a, sb, b

N = 2
dev = qml.device('default.qubit', wires=N)

zz_observable = (1/2)*np.array([[1,0,0,0],[0,-1,0,0],[0,0,-1,0],[0,0,0,1]])

problem  = [[2, 1]]
qubits  = ['zz1', 'zz2']

QAOA phases

def mixer(beta):
  for q in range(N): qml.RX(beta, wires=q)
  return None

def separator(gamma, problem):
  for term in problem:
    sa, a, sb, b = sign_qubit(term)
    qml.RZ(sa * gamma, wires=a)
    qml.RZ(sb * gamma, wires=b)
    ## ZZ operation
    qml.CNOT(wires=[a, b])
    qml.RZ(sb * gamma * 2., wires=b)
    qml.CNOT(wires=[a, b])
return None

@qml.qnode(dev)
def qaoa(params):
  for i in range(N): qml.Hadamard(wires=i) 

  gamma = params[0]
  beta = params[1]
  separator(gamma, problem)
  mixer(beta)

# Cost
  single_qubit_expectations = [qml.expval.PauliZ(i) for i in range(N)]
  two_qubit_expectations = [qml.expval.Hermitian(zz_observable, 
wires=[abs(a)-1,abs(b)-1]) for a,b in problem]
  single_qubit_expectations.extend(two_qubit_expectations)
  return single_qubit_expectations

initialise the optimizer

opt = qml.GradientDescentOptimizer(stepsize=0.4)

def cost(params):
  return np.sum(qaoa(params))

set the number of steps

steps = 100

set the initial parameter values

params = np.array([0.01, 0.01])

for i in range(steps):
  print('here')
  # update the circuit parameters
  params = opt.step(cost, params)
  print('here')


if (i+1) % 5 == 0:
    print('Cost after step {:5d}: {: .7f}'.format(i+1, cost(params)))

print('Optimized rotation angles: {}'.format(params))

ERROR
here

QuantumFunctionError Traceback (most recent call last)
in
64 print(‘here’)
65 # update the circuit parameters
—> 66 params = opt.step(cost, params)
67 print(‘here’)
68

~/anaconda3/envs/vqa/lib/python3.6/site-packages/PennyLane-0.3.1-py3.6.egg/pennylane/optimize/gradient_descent.py in step(self, objective_fn, x, grad_fn)
61 “”"
62
—> 63 g = self.compute_grad(objective_fn, x, grad_fn=grad_fn)
64
65 x_out = self.apply_grad(g, x)

~/anaconda3/envs/vqa/lib/python3.6/site-packages/PennyLane-0.3.1-py3.6.egg/pennylane/optimize/gradient_descent.py in compute_grad(objective_fn, x, grad_fn)
85 else:
86 # default is autograd
—> 87 g = autograd.grad(objective_fn)(x) # pylint: disable=no-value-for-parameter
88 return g
89

~/anaconda3/envs/vqa/lib/python3.6/site-packages/autograd-1.2-py3.6.egg/autograd/wrap_util.py in nary_f(*args, **kwargs)
18 else:
19 x = tuple(args[i] for i in argnum)
—> 20 return unary_operator(unary_f, x, *nary_op_args, **nary_op_kwargs)
21 return nary_f
22 return nary_operator

~/anaconda3/envs/vqa/lib/python3.6/site-packages/autograd-1.2-py3.6.egg/autograd/differential_operators.py in grad(fun, x)
22 arguments as fun, but returns the gradient instead. The function fun
23 should be scalar-valued. The gradient has the same type as the argument."""
—> 24 vjp, ans = _make_vjp(fun, x)
25 if not vspace(ans).size == 1:
26 raise TypeError("Grad only applies to real scalar-output functions. "

~/anaconda3/envs/vqa/lib/python3.6/site-packages/autograd-1.2-py3.6.egg/autograd/core.py in make_vjp(fun, x)
8 def make_vjp(fun, x):
9 start_node = VJPNode.new_root(x)
—> 10 end_value, end_node = trace(start_node, fun, x)
11 if end_node is None:
12 def vjp(g): return vspace(x).zeros()

~/anaconda3/envs/vqa/lib/python3.6/site-packages/autograd-1.2-py3.6.egg/autograd/tracer.py in trace(start_node, fun, x)
8 with trace_stack.new_trace() as t:
9 start_box = new_box(x, t, start_node)
—> 10 end_box = fun(start_box)
11 if isbox(end_box) and end_box._trace == start_box._trace:
12 return end_box._value, end_box._node

~/anaconda3/envs/vqa/lib/python3.6/site-packages/autograd-1.2-py3.6.egg/autograd/wrap_util.py in unary_f(x)
13 else:
14 subargs = subvals(args, zip(argnum, x))
—> 15 return fun(*subargs, **kwargs)
16 if isinstance(argnum, int):
17 x = args[argnum]

in cost(params)
54
55 def cost(params):
—> 56 return np.sum(qaoa(params))
57
58 # set the number of steps

~/anaconda3/envs/vqa/lib/python3.6/site-packages/PennyLane-0.3.1-py3.6.egg/pennylane/decorator.py in wrapper(*args, **kwargs)
151 def wrapper(*args, **kwargs):
152 “”“Wrapper function”""
–> 153 return qnode(*args, **kwargs)
154
155 # bind the jacobian method to the wrapped function

~/anaconda3/envs/vqa/lib/python3.6/site-packages/PennyLane-0.3.1-py3.6.egg/pennylane/qnode.py in call(self, *args, **kwargs)
455 # pylint: disable=no-member
456 args = autograd.builtins.tuple(args) # prevents autograd boxed arguments from going through to evaluate
–> 457 return self.evaluate(args, **kwargs) # args as one tuple
458
459 @ae.primitive

~/anaconda3/envs/vqa/lib/python3.6/site-packages/autograd-1.2-py3.6.egg/autograd/tracer.py in f_wrapped(*args, **kwargs)
42 parents = tuple(box._node for _ , box in boxed_args)
43 argnums = tuple(argnum for argnum, _ in boxed_args)
—> 44 ans = f_wrapped(*argvals, **kwargs)
45 node = node_constructor(ans, f_wrapped, argvals, kwargs, argnums, parents)
46 return new_box(ans, trace, node)

~/anaconda3/envs/vqa/lib/python3.6/site-packages/autograd-1.2-py3.6.egg/autograd/tracer.py in f_wrapped(*args, **kwargs)
46 return new_box(ans, trace, node)
47 else:
—> 48 return f_raw(*args, **kwargs)
49 f_wrapped.fun = f_raw
50 f_wrapped._is_autograd_primitive = True

~/anaconda3/envs/vqa/lib/python3.6/site-packages/PennyLane-0.3.1-py3.6.egg/pennylane/qnode.py in evaluate(self, args, **kwargs)
497 m_wires = list(w for ex in self.ev for w in ex.wires)
498 if len(m_wires) != len(set(m_wires)):
–> 499 raise QuantumFunctionError(‘Each wire in the quantum circuit can only be measured once.’)
500
501 def check_op(op):

QuantumFunctionError: Each wire in the quantum circuit can only be measured once.


#2

The Hermitian observable is not supported by the qiskit.basicaer backend. Do you know what might be a good replacement? I was hoping to use the noise functionality of the qiskit simulator.

We are currently in the process of refactoring the Qiskit plugin, due to the recent release of Qiskit v0.9 and 0.10 this week, so this is definitely on the list of features we will be adding!

In the meantime, you could check out the PennyLane-Forest plugin, which also allows qubit simulations with noise, and supports all core PennyLane expectation values.

Note that the Forest plugin currently must be installed by cloning from the GitHub repository directly:

git clone https://github.com/rigetti/pennylane-forest.git
cd pennylane-forest
pip install -e .

If inside the qnode you perform the \langle z_1\rangle and \langle z_1z_2\rangle computations the error returned is

QuantumFunctionError: Each wire in the quantum circuit can only be measured once.

Does this mean that we can’t compute both \langle z_1\rangle and \langle z_1z_2\rangle?

Yes - you cannot compute both \langle z_1\rangle and \langle z_1z_2\rangle from within the same QNode. The reason for this is that hardware is a first-class citizen in PennyLane — and since measurement affects the state of a qubit in hardware, it cannot be measured twice at the end of a computation.

One way around this is to define two QNodes, that both use the same ansatz, but with different measurements:

def ansatz(params):
  for i in range(N):
    qml.Hadamard(wires=i) 
  
  gamma = params[0]
  beta = params[1]
  separator(gamma, problem)
  mixer(beta)

@qml.qnode(dev)
def qaoa_one_qubit(params):
  ansatz(params)
  return [qml.expval.PauliZ(i) for i in range(N)]

@qml.qnode(dev)
def qaoa_two_qubit(params):
  ansatz(params)
  wires =[[abs(a)-1, abs(b)-1] for a, b in problem]
  return [qml.expval.Hermitian(zz_observable, wires=w) for w in wires]

You can then sum the results from the two QNodes using a classical cost function:

def cost(params):
  one_qubit_sum = np.sum(qaoa_one_qubit(params))
  two_qubit_sum = np.sum(qaoa_two_qubit(params))
  return one_qubit_sum + two_qubit_sum 

Hope that helps! Let me know if you have any other questions.


#3

Hey Josh, thanks for the good info & the ansatz tip.

Does the pennylane-forest plugin support >1 qubit Hermitian observables?
If no, is there a plugin that supports >1 qubit Hermitian observables and noisy simulation?

The pennylane-forest plugin is now complaining about a ‘2x2 matrix required’. Code + Error below. I think this is the limitation on zz observables, but I have installed the master branch of pennylane which accepts >2d Hermitians. This is still related to ‘backends for Hermitians’ but I can open a new topic if you prefer.

PENNYLANE VERSION
import pennylane
pennylane.version
‘0.3.1’

CODE

dev = qml.device(‘forest.numpy_wavefunction’, wires=N)

zz_observable = (1/2)*np.array([[1,0,0,0],[0,-1,0,0],[0,0,-1,0],[0,0,0,1]])

QAOA phases

def mixer(beta):
for q in range(N): qml.RX(beta, wires=q)
return None

def separator(gamma, problem):
for term in problem:
sa, a, sb, b = sign_qubit(term)
qml.RZ(sa * gamma, wires=a)
qml.RZ(sb * gamma, wires=b)
## ZZ operation
qml.CNOT(wires=[a, b])
qml.RZ(sb * gamma * 2., wires=b)
qml.CNOT(wires=[a, b])
return None

def ansatz(params):
for i in range(N):
qml.Hadamard(wires=i)

gamma = params[0]
beta = params[1]
separator(gamma, problem)
mixer(beta)

@qml.qnode(dev)
def qaoa_one_qubit(params):
ansatz(params)
return [qml.expval.PauliZ(i) for i in range(N)]

@qml.qnode(dev)
def qaoa_two_qubit(params):
ansatz(params)
wires =[[abs(a)-1, abs(b)-1] for a, b in problem]
return [qml.expval.Hermitian(zz_observable, wires=w) for w in wires]

initialise the optimizer

opt = qml.GradientDescentOptimizer(stepsize=0.01)

def cost(params):
return np.sum(qaoa_two_qubit(params)) #+ np.sum(qaoa_two_qubit(params))

set the number of steps

steps = 10000

set the initial parameter values

params = np.array([0.01, 0.01])

for i in range(steps):
# update the circuit parameters
params = opt.step(cost, params)

if (i+1) % 5 == 0:
    print('Cost after step {:5d}: {: .7f}'.format(i+1, cost(params)))

print(‘Optimized rotation angles: {}’.format(params))

ERROR

ValueError Traceback (most recent call last)
in
56 for i in range(steps):
57 # update the circuit parameters
—> 58 params = opt.step(cost, params)
59
60 if (i+1) % 5 == 0:

~/anaconda3/envs/vqa/lib/python3.6/site-packages/PennyLane-0.3.1-py3.6.egg/pennylane/optimize/gradient_descent.py in step(self, objective_fn, x, grad_fn)
61 “”"
62
—> 63 g = self.compute_grad(objective_fn, x, grad_fn=grad_fn)
64
65 x_out = self.apply_grad(g, x)

~/anaconda3/envs/vqa/lib/python3.6/site-packages/PennyLane-0.3.1-py3.6.egg/pennylane/optimize/gradient_descent.py in compute_grad(objective_fn, x, grad_fn)
85 else:
86 # default is autograd
—> 87 g = autograd.grad(objective_fn)(x) # pylint: disable=no-value-for-parameter
88 return g
89

~/anaconda3/envs/vqa/lib/python3.6/site-packages/autograd-1.2-py3.6.egg/autograd/wrap_util.py in nary_f(*args, **kwargs)
18 else:
19 x = tuple(args[i] for i in argnum)
—> 20 return unary_operator(unary_f, x, *nary_op_args, **nary_op_kwargs)
21 return nary_f
22 return nary_operator

~/anaconda3/envs/vqa/lib/python3.6/site-packages/autograd-1.2-py3.6.egg/autograd/differential_operators.py in grad(fun, x)
22 arguments as fun, but returns the gradient instead. The function fun
23 should be scalar-valued. The gradient has the same type as the argument."""
—> 24 vjp, ans = _make_vjp(fun, x)
25 if not vspace(ans).size == 1:
26 raise TypeError("Grad only applies to real scalar-output functions. "

~/anaconda3/envs/vqa/lib/python3.6/site-packages/autograd-1.2-py3.6.egg/autograd/core.py in make_vjp(fun, x)
8 def make_vjp(fun, x):
9 start_node = VJPNode.new_root(x)
—> 10 end_value, end_node = trace(start_node, fun, x)
11 if end_node is None:
12 def vjp(g): return vspace(x).zeros()

~/anaconda3/envs/vqa/lib/python3.6/site-packages/autograd-1.2-py3.6.egg/autograd/tracer.py in trace(start_node, fun, x)
8 with trace_stack.new_trace() as t:
9 start_box = new_box(x, t, start_node)
—> 10 end_box = fun(start_box)
11 if isbox(end_box) and end_box._trace == start_box._trace:
12 return end_box._value, end_box._node

~/anaconda3/envs/vqa/lib/python3.6/site-packages/autograd-1.2-py3.6.egg/autograd/wrap_util.py in unary_f(x)
13 else:
14 subargs = subvals(args, zip(argnum, x))
—> 15 return fun(*subargs, **kwargs)
16 if isinstance(argnum, int):
17 x = args[argnum]

in cost(params)
47
48 def cost(params):
—> 49 return np.sum(qaoa_two_qubit(params)) #+ np.sum(qaoa_two_qubit(params))
50
51 # set the number of steps

~/anaconda3/envs/vqa/lib/python3.6/site-packages/PennyLane-0.3.1-py3.6.egg/pennylane/decorator.py in wrapper(*args, **kwargs)
151 def wrapper(*args, **kwargs):
152 “”“Wrapper function”""
–> 153 return qnode(*args, **kwargs)
154
155 # bind the jacobian method to the wrapped function

~/anaconda3/envs/vqa/lib/python3.6/site-packages/PennyLane-0.3.1-py3.6.egg/pennylane/qnode.py in call(self, *args, **kwargs)
455 # pylint: disable=no-member
456 args = autograd.builtins.tuple(args) # prevents autograd boxed arguments from going through to evaluate
–> 457 return self.evaluate(args, **kwargs) # args as one tuple
458
459 @ae.primitive

~/anaconda3/envs/vqa/lib/python3.6/site-packages/autograd-1.2-py3.6.egg/autograd/tracer.py in f_wrapped(*args, **kwargs)
42 parents = tuple(box._node for _ , box in boxed_args)
43 argnums = tuple(argnum for argnum, _ in boxed_args)
—> 44 ans = f_wrapped(*argvals, **kwargs)
45 node = node_constructor(ans, f_wrapped, argvals, kwargs, argnums, parents)
46 return new_box(ans, trace, node)

~/anaconda3/envs/vqa/lib/python3.6/site-packages/autograd-1.2-py3.6.egg/autograd/tracer.py in f_wrapped(*args, **kwargs)
46 return new_box(ans, trace, node)
47 else:
—> 48 return f_raw(*args, **kwargs)
49 f_wrapped.fun = f_raw
50 f_wrapped._is_autograd_primitive = True

~/anaconda3/envs/vqa/lib/python3.6/site-packages/PennyLane-0.3.1-py3.6.egg/pennylane/qnode.py in evaluate(self, args, **kwargs)
510 check_op(op)
511
–> 512 ret = self.device.execute(self.queue, self.ev)
513 return self.output_type(ret)
514

~/anaconda3/envs/vqa/lib/python3.6/site-packages/PennyLane-0.3.1-py3.6.egg/pennylane/_device.py in execute(self, queue, expectation)
210
211 self.pre_expval()
–> 212 expectations = [self.expval(e.name, e.wires, e.parameters) for e in expectation]
213 self.post_expval()
214

~/anaconda3/envs/vqa/lib/python3.6/site-packages/PennyLane-0.3.1-py3.6.egg/pennylane/_device.py in (.0)
210
211 self.pre_expval()
–> 212 expectations = [self.expval(e.name, e.wires, e.parameters) for e in expectation]
213 self.post_expval()
214

~/projects/vqa_optimisation/qiskit_pennylane/packages/pennylane-forest/pennylane-forest/pennylane_forest/numpy_wavefunction.py in expval(self, expectation, wires, par)
69 if self.shots == 0:
70 # exact expectation value
—> 71 ev = self.ev(A, wires)
72 else:
73 # estimate the ev

~/projects/vqa_optimisation/qiskit_pennylane/packages/pennylane-forest/pennylane-forest/pennylane_forest/numpy_wavefunction.py in ev(self, A, wires)
91 “”"
92 # Expand the Hermitian observable over the entire subsystem
—> 93 A = self.expand_one(A, wires)
94 return np.vdot(self.state, A @ self.state).real
95

~/projects/vqa_optimisation/qiskit_pennylane/packages/pennylane-forest/pennylane-forest/pennylane_forest/numpy_wavefunction.py in expand_one(self, U, wires)
105 “”"
106 if U.shape != (2, 2):
–> 107 raise ValueError(‘2x2 matrix required.’)
108 if len(wires) != 1:
109 raise ValueError(‘One target subsystem required.’)

ValueError: 2x2 matrix required.


#4

Hi @mxn.wls, thanks for pointing this out.

Since the multi-wire qml.expval.Hermitian support was only added to the main PennyLane library last week, we still need to add support to the plugins.

I have just submitted a PR which adds this to the PennyLane-Forest plugin, see here: https://github.com/rigetti/pennylane-forest/pull/13

Until it is merged, you can clone and install this branch directly to get it working:

git clone https://github.com/rigetti/pennylane-forest.git
cd pennylane-forest
git checkout multi_qubit_observables
pip install -e .