Skip to content

Commit

Permalink
Make QGAN run primarily on circuits (qiskit-community/qiskit-aqua#1341)
Browse files Browse the repository at this point in the history
* make quantum generator use circuits primarily

* add test and deprecation warning

* add reno

* fix lint

Co-authored-by: Manoel Marques <Manoel.Marques@ibm.com>
  • Loading branch information
Cryoris and manoelmarques authored Oct 14, 2020
1 parent 92baf1f commit f396db5
Show file tree
Hide file tree
Showing 3 changed files with 72 additions and 86 deletions.
3 changes: 1 addition & 2 deletions qiskit/aqua/components/neural_networks/generative_network.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,13 +43,12 @@ def set_seed(self, seed):
raise NotImplementedError()

@abstractmethod
def get_output(self, quantum_instance, qc_state_in, params, shots):
def get_output(self, quantum_instance, params, shots):
"""
Apply quantum/classical neural network to given input and get the respective output
Args:
quantum_instance (QuantumInstance): Quantum Instance, used to run the generator circuit.
qc_state_in (QuantumCircuit or vector): corresponding to the network input state
params (numpy.ndarray): parameters which should be used to run the generator,
if None use self._params
shots (int): if not None use a number of shots that is different from the number
Expand Down
125 changes: 48 additions & 77 deletions qiskit/aqua/components/neural_networks/quantum_generator.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,20 +13,17 @@
"""Quantum Generator."""

from typing import Optional, List, Union, Dict, Any
import warnings
from copy import deepcopy
import numpy as np

from qiskit import QuantumRegister, ClassicalRegister, QuantumCircuit
from qiskit.circuit.library import TwoLocal
from qiskit.aqua import aqua_globals
from qiskit.aqua.components.optimizers import ADAM
from qiskit.aqua.components.uncertainty_models import \
UniformDistribution, MultivariateUniformDistribution
from qiskit.aqua.components.uncertainty_models import UnivariateVariationalDistribution, \
MultivariateVariationalDistribution
from qiskit.aqua import AquaError
from qiskit.aqua.components.neural_networks.generative_network import GenerativeNetwork
from qiskit.aqua.components.initial_states import Custom

# pylint: disable=invalid-name

Expand Down Expand Up @@ -72,61 +69,32 @@ def __init__(self,
self._bounds = bounds
self._num_qubits = num_qubits
self.generator_circuit = generator_circuit
if self.generator_circuit is None:
entangler_map = []
if np.sum(num_qubits) > 2:
for i in range(int(np.sum(num_qubits))):
entangler_map.append([i, int(np.mod(i + 1, np.sum(num_qubits)))])
else:
if np.sum(num_qubits) > 1:
entangler_map.append([0, 1])

if len(num_qubits) > 1:
num_qubits = list(map(int, num_qubits))
low = bounds[:, 0].tolist()
high = bounds[:, 1].tolist()
init_dist = MultivariateUniformDistribution(num_qubits, low=low, high=high)
q = QuantumRegister(sum(num_qubits))
qc = QuantumCircuit(q)
init_dist.build(qc, q)
init_distribution = Custom(num_qubits=sum(num_qubits), circuit=qc)
# Set variational form
var_form = TwoLocal(sum(num_qubits), 'ry', 'cz', reps=1,
initial_state=init_distribution,
entanglement=entangler_map)
if init_params is None:
init_params = aqua_globals.random.random(var_form.num_parameters) * 2 * 1e-2
# Set generator circuit
self.generator_circuit = MultivariateVariationalDistribution(num_qubits, var_form,
init_params,
low=low, high=high)
else:
init_dist = UniformDistribution(sum(num_qubits), low=bounds[0], high=bounds[1])
q = QuantumRegister(sum(num_qubits), name='q')
qc = QuantumCircuit(q)
init_dist.build(qc, q)
init_distribution = Custom(num_qubits=sum(num_qubits), circuit=qc)
var_form = TwoLocal(sum(num_qubits), 'ry', 'cz', reps=1,
initial_state=init_distribution,
entanglement=entangler_map)
if init_params is None:
init_params = aqua_globals.random.random(var_form.num_parameters) * 2 * 1e-2
# Set generator circuit
self.generator_circuit = UnivariateVariationalDistribution(
int(np.sum(num_qubits)), var_form, init_params, low=bounds[0], high=bounds[1])

if len(num_qubits) > 1:
if isinstance(self.generator_circuit, MultivariateVariationalDistribution):
pass
else:
raise AquaError('Set multivariate variational distribution '
'to represent multivariate data')
if generator_circuit is None:
circuit = QuantumCircuit(sum(num_qubits))
circuit.h(circuit.qubits)
var_form = TwoLocal(sum(num_qubits), 'ry', 'cz', reps=1, entanglement='circular')
circuit.compose(var_form, inplace=True)

# Set generator circuit
self.generator_circuit = circuit

if isinstance(generator_circuit, (UnivariateVariationalDistribution,
MultivariateVariationalDistribution)):
warnings.warn('Passing a UnivariateVariationalDistribution or MultivariateVariational'
'Distribution is as ``generator_circuit`` is deprecated as of Aqua 0.8.0 '
'and the support will be removed no earlier than 3 months after the '
'release data. You should pass as QuantumCircuit instead.',
DeprecationWarning, stacklevel=2)
self._free_parameters = generator_circuit._var_form_params
self.generator_circuit = generator_circuit._var_form
else:
if isinstance(self.generator_circuit, UnivariateVariationalDistribution):
pass
else:
raise AquaError('Set univariate variational distribution '
'to represent univariate data')
self._free_parameters = list(self.generator_circuit.parameters)

if init_params is None:
init_params = aqua_globals.random.random(self.generator_circuit.num_parameters) * 2e-2

self._bound_parameters = init_params

# Set optimizer for updating the generator network
self._optimizer = ADAM(maxiter=1, tol=1e-6, lr=1e-3, beta_1=0.7,
beta_2=0.99, noise_factor=1e-6,
Expand Down Expand Up @@ -192,26 +160,28 @@ def construct_circuit(self, params=None):
Construct generator circuit.
Args:
params (numpy.ndarray): parameters which should be used to run the generator,
if None use self._params
params (list | dict): parameters which should be used to run the generator.
Returns:
Instruction: construct the quantum circuit and return as gate
"""

q = QuantumRegister(sum(self._num_qubits), name='q')
qc = QuantumCircuit(q)
if params is None:
self.generator_circuit.build(qc=qc, q=q)
else:
generator_circuit_copy = deepcopy(self.generator_circuit)
generator_circuit_copy.params = params
generator_circuit_copy.build(qc=qc, q=q)
return self.generator_circuit

# return qc.copy(name='qc')
return qc.to_instruction()
if isinstance(params, (list, np.ndarray)):
params = dict(zip(self._free_parameters, params))

def get_output(self, quantum_instance, qc_state_in=None, params=None, shots=None):
return self.generator_circuit.assign_parameters(params)
# self.generator_circuit.build(qc=qc, q=q)
# else:
# generator_circuit_copy = deepcopy(self.generator_circuit)
# generator_circuit_copy.params = params
# generator_circuit_copy.build(qc=qc, q=q)

# # return qc.copy(name='qc')
# return qc.to_instruction()

def get_output(self, quantum_instance, params=None, shots=None):
"""
Get classical data samples from the generator.
Running the quantum generator circuit results in a quantum state.
Expand All @@ -222,7 +192,6 @@ def get_output(self, quantum_instance, qc_state_in=None, params=None, shots=None
Args:
quantum_instance (QuantumInstance): Quantum Instance, used to run the generator
circuit.
qc_state_in (QuantumCircuit): deprecated
params (numpy.ndarray): array or None, parameters which should
be used to run the generator, if None use self._params
shots (int): if not None use a number of shots that is different from the
Expand All @@ -234,6 +203,8 @@ def get_output(self, quantum_instance, qc_state_in=None, params=None, shots=None
instance_shots = quantum_instance.run_config.shots
q = QuantumRegister(sum(self._num_qubits), name='q')
qc = QuantumCircuit(q)
if params is None:
params = self._bound_parameters
qc.append(self.construct_circuit(params), q)
if quantum_instance.is_statevector:
pass
Expand Down Expand Up @@ -277,7 +248,7 @@ def get_output(self, quantum_instance, qc_state_in=None, params=None, shots=None
temp.append(self._data_grid[int(bin_rep)])
generated_samples.append(temp)

self.generator_circuit._probabilities = generated_samples_weights
# self.generator_circuit._probabilities = generated_samples_weights
if shots is not None:
# Restore the initial quantum_instance configuration
quantum_instance.set_config(shots=instance_shots)
Expand Down Expand Up @@ -347,13 +318,13 @@ def train(self, quantum_instance=None, shots=None):
self._optimizer._maxiter = 1
self._optimizer._t = 0
objective = self._get_objective_function(quantum_instance, self._discriminator)
self.generator_circuit.params, loss, _ = self._optimizer.optimize(
num_vars=len(self.generator_circuit.params),
self._bound_parameters, loss, _ = self._optimizer.optimize(
num_vars=len(self._bound_parameters),
objective_function=objective,
initial_point=self.generator_circuit.params
initial_point=self._bound_parameters
)

self._ret['loss'] = loss
self._ret['params'] = self.generator_circuit.params
self._ret['params'] = self._bound_parameters

return self._ret
30 changes: 23 additions & 7 deletions test/aqua/test_qgan.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,20 +12,23 @@

"""Test the QGAN algorithm."""

import unittest
import warnings
from test.aqua import QiskitAquaTestCase
from ddt import ddt, data

import unittest
from qiskit import QuantumCircuit, QuantumRegister
from qiskit.circuit.library import RealAmplitudes
from qiskit.aqua.components.uncertainty_models import (UniformDistribution,
UnivariateVariationalDistribution)
from qiskit.aqua.algorithms import QGAN
from qiskit.aqua import aqua_globals, QuantumInstance
from qiskit.aqua import aqua_globals, QuantumInstance, MissingOptionalLibraryError
from qiskit.aqua.components.initial_states import Custom
from qiskit.aqua.components.neural_networks import NumPyDiscriminator, PyTorchDiscriminator
from qiskit import BasicAer


@ddt
class TestQGAN(QiskitAquaTestCase):
"""Test the QGAN algorithm."""

Expand Down Expand Up @@ -83,14 +86,24 @@ def setUp(self):
# Set variational form
var_form = RealAmplitudes(sum(num_qubits), reps=1, initial_state=init_distribution,
entanglement=entangler_map)
self.generator_circuit = UnivariateVariationalDistribution(sum(num_qubits), var_form,
self.generator_circuit = var_form
warnings.filterwarnings('ignore', category=DeprecationWarning)
self.generator_factory = UnivariateVariationalDistribution(sum(num_qubits), var_form,
init_params,
low=self._bounds[0],
high=self._bounds[1])
warnings.filterwarnings('always', category=DeprecationWarning)

def test_sample_generation(self):
@data('circuit', 'factory')
def test_sample_generation(self, circuit_type):
"""Test sample generation."""
self.qgan.set_generator(generator_circuit=self.generator_circuit)
if circuit_type == 'factory':
warnings.filterwarnings('ignore', category=DeprecationWarning)
self.qgan.set_generator(generator_circuit=self.generator_factory)
warnings.filterwarnings('always', category=DeprecationWarning)
else:
self.qgan.set_generator(generator_circuit=self.generator_circuit)

_, weights_statevector = self.qgan._generator.get_output(self.qi_statevector, shots=100)
samples_qasm, weights_qasm = self.qgan._generator.get_output(self.qi_qasm, shots=100)
samples_qasm, weights_qasm = zip(*sorted(zip(samples_qasm, weights_qasm)))
Expand All @@ -99,7 +112,10 @@ def test_sample_generation(self):

def test_qgan_training(self):
"""Test QGAN training."""
warnings.filterwarnings('ignore', category=DeprecationWarning)
self.qgan.set_generator(generator_circuit=self.generator_circuit)
warnings.filterwarnings('always', category=DeprecationWarning)

trained_statevector = self.qgan.run(self.qi_statevector)
trained_qasm = self.qgan.run(self.qi_qasm)
self.assertAlmostEqual(trained_qasm['rel_entr'], trained_statevector['rel_entr'], delta=0.1)
Expand Down Expand Up @@ -131,8 +147,8 @@ def test_qgan_training_run_algo_torch(self):
seed_transpiler=aqua_globals.random_seed))
self.assertAlmostEqual(trained_qasm['rel_entr'],
trained_statevector['rel_entr'], delta=0.1)
except Exception as ex: # pylint: disable=broad-except
self.skipTest(str(ex))
except MissingOptionalLibraryError:
self.skipTest('pytorch not installed, skipping test')

def test_qgan_training_run_algo_numpy(self):
"""Test QGAN training using a NumPy discriminator."""
Expand Down

0 comments on commit f396db5

Please sign in to comment.