diff --git a/qiskit/aqua/components/neural_networks/generative_network.py b/qiskit/aqua/components/neural_networks/generative_network.py index 80382d10d6..d4eaf579ed 100644 --- a/qiskit/aqua/components/neural_networks/generative_network.py +++ b/qiskit/aqua/components/neural_networks/generative_network.py @@ -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 diff --git a/qiskit/aqua/components/neural_networks/quantum_generator.py b/qiskit/aqua/components/neural_networks/quantum_generator.py index ab9a9038a8..1cd212cc39 100644 --- a/qiskit/aqua/components/neural_networks/quantum_generator.py +++ b/qiskit/aqua/components/neural_networks/quantum_generator.py @@ -13,6 +13,7 @@ """Quantum Generator.""" from typing import Optional, List, Union, Dict, Any +import warnings from copy import deepcopy import numpy as np @@ -20,13 +21,9 @@ 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 @@ -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, @@ -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. @@ -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 @@ -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 @@ -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) @@ -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 diff --git a/releasenotes/notes/qgan-on-circuits-9bd57bd707b31897.yaml b/releasenotes/notes/qgan-on-circuits-9bd57bd707b31897.yaml new file mode 100644 index 0000000000..ac85f3cece --- /dev/null +++ b/releasenotes/notes/qgan-on-circuits-9bd57bd707b31897.yaml @@ -0,0 +1,11 @@ +--- +features: + - | + Support passing ``QuantumCircuit`` objects as generator circuits into + the ``QuantumGenerator``. +deprecations: + - | + Deprecate the ``UnivariateVariationalDistribution`` and + ``MultivariateVariationalDistribution`` as input + to the ``QuantumGenerator``. Instead, plain ``QuantumCircuit`` objects can + be used. diff --git a/test/aqua/test_qgan.py b/test/aqua/test_qgan.py index 77919c7b00..52fff67439 100644 --- a/test/aqua/test_qgan.py +++ b/test/aqua/test_qgan.py @@ -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.""" @@ -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))) @@ -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) @@ -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."""