def setUp(self):
        super().setUp()

        # specify "run configuration"
        backend = Aer.get_backend('statevector_simulator')
        quantum_instance = QuantumInstance(backend)

        # specify how to evaluate expected values and gradients
        expval = PauliExpectation()
        gradient = Gradient()

        # construct parametrized circuit
        params = [Parameter('input1'), Parameter('weight1')]
        qc = QuantumCircuit(1)
        qc.h(0)
        qc.ry(params[0], 0)
        qc.rx(params[1], 0)
        qc_sfn = StateFn(qc)

        # construct cost operator
        cost_operator = StateFn(PauliSumOp.from_list([('Z', 1.0), ('X', 1.0)]))

        # combine operator and circuit to objective function
        op = ~cost_operator @ qc_sfn

        # define QNN
        self.qnn = OpflowQNN(op, [params[0]], [params[1]],
                             expval,
                             gradient,
                             quantum_instance=quantum_instance)
    def test_opflow_qnn_2_1(self, config):
        """Test Opflow QNN with input/output dimension 2/1."""
        q_i, input_grad_required = config

        # construct QNN
        if q_i == STATEVECTOR:
            quantum_instance = self.sv_quantum_instance
        elif q_i == QASM:
            quantum_instance = self.qasm_quantum_instance
        else:
            quantum_instance = None

        # specify how to evaluate expected values and gradients
        expval = PauliExpectation()
        gradient = Gradient()

        # construct parametrized circuit
        params = [
            Parameter("input1"),
            Parameter("input2"),
            Parameter("weight1"),
            Parameter("weight2"),
        ]
        qc = QuantumCircuit(2)
        qc.h(0)
        qc.ry(params[0], 0)
        qc.ry(params[1], 1)
        qc.rx(params[2], 0)
        qc.rx(params[3], 1)
        qc_sfn = StateFn(qc)

        # construct cost operator
        cost_operator = StateFn(PauliSumOp.from_list([("ZZ", 1.0), ("XX", 1.0)]))

        # combine operator and circuit to objective function
        op = ~cost_operator @ qc_sfn

        # define QNN
        qnn = OpflowQNN(
            op,
            params[:2],
            params[2:],
            expval,
            gradient,
            quantum_instance=quantum_instance,
        )
        qnn.input_gradients = input_grad_required

        test_data = [np.array([1, 2]), np.array([[1, 2]]), np.array([[1, 2], [3, 4]])]

        # test model
        self.validate_output_shape(qnn, test_data)

        # test the qnn after we set a quantum instance
        if quantum_instance is None:
            qnn.quantum_instance = self.qasm_quantum_instance
            self.validate_output_shape(qnn, test_data)
    def test_product_rule(self, method):
        x, y = Parameter('x'), Parameter('y')
        circuit = QuantumCircuit(1)
        circuit.rx(x, 0)
        circuit.ry(x, 0)
        circuit.rz(y, 0)
        circuit.rx(x, 0)
        circuit.h(0)
        circuit.rx(y, 0)

        state_in = Statevector.from_int(1, dims=(2,))

        parameter_binds = {x: 1, y: 2}

        grad = StateGradient(Z, circuit, state_in, [x, y])
        grads = getattr(grad, method)(parameter_binds)

        ref_grad = Gradient().convert(~StateFn(Z) @ StateFn(circuit), params=[x, y])
        ref = ref_grad.bind_parameters(parameter_binds).eval()

        np.testing.assert_array_almost_equal(grads, ref)
    def __init__(self,
                 operator: OperatorBase,
                 input_params: Optional[List[Parameter]] = None,
                 weight_params: Optional[List[Parameter]] = None,
                 exp_val: Optional[ExpectationBase] = None,
                 gradient: Optional[Gradient] = None,
                 quantum_instance: Optional[Union[QuantumInstance, BaseBackend,
                                                  Backend]] = None):
        """Initializes the Opflow Quantum Neural Network.

        Args:
            operator: The parametrized operator that represents the neural network.
            input_params: The operator parameters that correspond to the input of the network.
            weight_params: The operator parameters that correspond to the trainable weights.
            exp_val: The Expected Value converter to be used for the operator.
            gradient: The Gradient converter to be used for the operator's backward pass.
            quantum_instance: The quantum instance to evaluate the network.
        """
        self._input_params = list(input_params) or []
        self._weight_params = list(weight_params) or []

        if isinstance(quantum_instance, (BaseBackend, Backend)):
            quantum_instance = QuantumInstance(quantum_instance)

        if quantum_instance:
            self._quantum_instance = quantum_instance
            self._circuit_sampler = CircuitSampler(
                self._quantum_instance,
                param_qobj=is_aer_provider(self._quantum_instance.backend),
                caching="all")
        else:
            self._quantum_instance = None
            self._circuit_sampler = None

        self._operator = operator
        self._forward_operator = exp_val.convert(
            operator) if exp_val else operator
        self._gradient_operator: OperatorBase = None
        try:
            gradient = gradient or Gradient()
            self._gradient_operator = gradient.convert(
                operator, self._input_params + self._weight_params)
        except (ValueError, TypeError, OpflowError, QiskitError):
            logger.warning(
                'Cannot compute gradient operator! Continuing without gradients!'
            )

        output_shape = self._get_output_shape_from_op(operator)
        super().__init__(len(self._input_params),
                         len(self._weight_params),
                         sparse=False,
                         output_shape=output_shape)
Exemple #5
0
    def test_opflow_qnn_2_1(self, q_i):
        """ Test Opflow QNN with input/output dimension 2/1."""

        # construct QNN
        if q_i == 'sv':
            quantum_instance = self.sv_quantum_instance
        else:
            quantum_instance = self.qasm_quantum_instance

        # specify how to evaluate expected values and gradients
        expval = PauliExpectation()
        gradient = Gradient()

        # construct parametrized circuit
        params = [
            Parameter('input1'),
            Parameter('input2'),
            Parameter('weight1'),
            Parameter('weight2')
        ]
        qc = QuantumCircuit(2)
        qc.h(0)
        qc.ry(params[0], 0)
        qc.ry(params[1], 1)
        qc.rx(params[2], 0)
        qc.rx(params[3], 1)
        qc_sfn = StateFn(qc)

        # construct cost operator
        cost_operator = StateFn(
            PauliSumOp.from_list([('ZZ', 1.0), ('XX', 1.0)]))

        # combine operator and circuit to objective function
        op = ~cost_operator @ qc_sfn

        # define QNN
        qnn = OpflowQNN(op,
                        params[:2],
                        params[2:],
                        expval,
                        gradient,
                        quantum_instance=quantum_instance)

        test_data = [
            np.array([1, 2]),
            np.array([[1, 2]]),
            np.array([[1, 2], [3, 4]])
        ]

        # test model
        self.validate_output_shape(qnn, test_data)
    def _construct_gradient_operator(self):
        self._gradient_operator: OperatorBase = None
        try:
            gradient = self._gradient or Gradient()
            if self._input_gradients:
                params = self._input_params + self._weight_params
            else:
                params = self._weight_params

            self._gradient_operator = gradient.convert(self._operator, params)
        except (ValueError, TypeError, OpflowError, QiskitError):
            logger.warning(
                "Cannot compute gradient operator! Continuing without gradients!"
            )
 def test_with_gradient(self, optimizer):
     """Test VQE using Gradient()."""
     quantum_instance = QuantumInstance(
         backend=Aer.get_backend("qasm_simulator"),
         shots=1,
         seed_simulator=algorithm_globals.random_seed,
         seed_transpiler=algorithm_globals.random_seed,
     )
     vqe = VQE(
         ansatz=self.ry_wavefunction,
         optimizer=optimizer,
         gradient=Gradient(),
         expectation=AerPauliExpectation(),
         quantum_instance=quantum_instance,
         max_evals_grouped=1000,
     )
     vqe.compute_minimum_eigenvalue(operator=self.h2_op)
Exemple #8
0
    def __init__(self, operator: OperatorBase,
                 input_params: Optional[List[Parameter]] = None,
                 weight_params: Optional[List[Parameter]] = None,
                 exp_val: Optional[ExpectationBase] = None,
                 gradient: Optional[Gradient] = None,
                 quantum_instance: Optional[Union[QuantumInstance, BaseBackend, Backend]] = None):
        """Initializes the Opflow Quantum Neural Network.

        Args:
            operator: The parametrized operator that represents the neural network.
            input_params: The operator parameters that correspond to the input of the network.
            weight_params: The operator parameters that correspond to the trainable weights.
            exp_val: The Expected Value converter to be used for the operator.
            gradient: The Gradient converter to be used for the operator's backward pass.
            quantum_instance: The quantum instance to evaluate the network.
        """
        self.operator = operator
        self.input_params = list(input_params or [])
        self.weight_params = list(weight_params or [])
        self.exp_val = exp_val  # TODO: currently not used by Gradient!
        self.gradient = gradient or Gradient()

        if isinstance(quantum_instance, (BaseBackend, Backend)):
            quantum_instance = QuantumInstance(quantum_instance)

        if quantum_instance:
            self.quantum_instance = quantum_instance
            self.circuit_sampler = CircuitSampler(
                self.quantum_instance,
                param_qobj=is_aer_provider(self.quantum_instance.backend)
            )
            # TODO: replace by extended caching in circuit sampler after merged: "caching='all'"
            self.gradient_sampler = deepcopy(self.circuit_sampler)
        else:
            self.quantum_instance = None
            self.circuit_sampler = None
            self.gradient_sampler = None

        self.forward_operator = self.exp_val.convert(operator) if exp_val else operator
        self.gradient_operator = self.gradient.convert(operator,
                                                       self.input_params + self.weight_params)
        output_shape = self._get_output_shape_from_op(operator)
        super().__init__(len(self.input_params), len(self.weight_params),
                         sparse=False, output_shape=output_shape)
Exemple #9
0
    def __init__(self, circuit: QuantumCircuit,
                 input_params: Optional[List[Parameter]] = None,
                 weight_params: Optional[List[Parameter]] = None,
                 sparse: bool = False,
                 sampling: bool = False,
                 interpret: Optional[Callable[[int], Union[int, Tuple[int, ...]]]] = None,
                 output_shape: Union[int, Tuple[int, ...]] = None,
                 gradient: Gradient = None,
                 quantum_instance: Optional[Union[QuantumInstance, BaseBackend, Backend]] = None
                 ) -> None:
        """Initializes the Circuit Quantum Neural Network.

        Args:
            circuit: The parametrized quantum circuit that generates the samples of this network.
            input_params: The parameters of the circuit corresponding to the input.
            weight_params: The parameters of the circuit corresponding to the trainable weights.
            sparse: Returns whether the output is sparse or not.
            sampling: Determines whether the network returns a batch of samples or (possibly
                sparse) array of probabilities in its forward pass. In case of probabilities,
                the backward pass returns the probability gradients, while it returns (None, None)
                in the case of samples. Note that sampling==True will always result in a
                dense return array independent of the other settings.
            interpret: A callable that maps the measured integer to another unsigned integer or
                tuple of unsigned integers. These are used as new indices for the (potentially
                sparse) output array. If this is used, the output shape of the output needs to be
                given as a separate argument.
            output_shape: The output shape of the custom interpretation. The output shape is
                automatically determined in case of sampling==True.
            gradient: The gradient converter to be used for the probability gradients.
            quantum_instance: The quantum instance to evaluate the circuits.

        Raises:
            QiskitMachineLearningError: if `interpret` is passed without `output_shape`.
        """

        # TODO: need to handle case without a quantum instance
        if isinstance(quantum_instance, (BaseBackend, Backend)):
            quantum_instance = QuantumInstance(quantum_instance)
        self._quantum_instance = quantum_instance
        self._sampler = CircuitSampler(quantum_instance, param_qobj=False, caching='all')

        # copy circuit and add measurements in case non are given
        # TODO: need to be able to handle partial measurements! (partial trace...)
        self._circuit = circuit.copy()
        if quantum_instance.is_statevector:
            if len(self._circuit.clbits) > 0:
                self._circuit.remove_final_measurements()
        elif len(self._circuit.clbits) == 0:
            self._circuit.measure_all()

        self._input_params = list(input_params or [])
        self._weight_params = list(weight_params or [])
        self._interpret = interpret if interpret else lambda x: x
        sparse_ = False if sampling else sparse
        output_shape_ = self._compute_output_shape(interpret, output_shape, sampling)

        # use given gradient or default
        self._gradient = gradient if gradient else Gradient()

        # construct probability gradient opflow object
        self._grad_circuit: QuantumCircuit = None
        try:
            grad_circuit = circuit.copy()
            grad_circuit.remove_final_measurements()  # ideally this would not be necessary
            params = list(input_params) + list(weight_params)
            self._grad_circuit = self._gradient.convert(StateFn(grad_circuit), params)
        except (ValueError, TypeError, OpflowError, QiskitError):
            logger.warning('Cannot compute gradient operator! Continuing without gradients!')

        super().__init__(len(self._input_params), len(self._weight_params), sparse_, sampling,
                         output_shape_)
Exemple #10
0
    def __init__(
        self,
        circuit: QuantumCircuit,
        input_params: Optional[List[Parameter]] = None,
        weight_params: Optional[List[Parameter]] = None,
        sparse: bool = False,
        sampling: bool = False,
        interpret: Optional[Callable[[int], Union[int, Tuple[int, ...]]]] = None,
        output_shape: Union[int, Tuple[int, ...]] = None,
        gradient: Gradient = None,
        quantum_instance: Optional[Union[QuantumInstance, Backend]] = None,
        input_gradients: bool = False,
    ) -> None:
        """
        Args:
            circuit: The parametrized quantum circuit that generates the samples of this network.
                There will be an attempt to transpile this circuit and cache the transpiled circuit
                for subsequent usages by the network. If for some reasons the circuit can't be
                transpiled, e.g. it originates from
                :class:`~qiskit_machine_learning.circuit.library.RawFeatureVector`, the circuit
                will be transpiled every time it is required to be executed and only when all
                parameters are bound. This may impact overall performance on the network.
            input_params: The parameters of the circuit corresponding to the input.
            weight_params: The parameters of the circuit corresponding to the trainable weights.
            sparse: Returns whether the output is sparse or not.
            sampling: Determines whether the network returns a batch of samples or (possibly
                sparse) array of probabilities in its forward pass. In case of probabilities,
                the backward pass returns the probability gradients, while it returns
                ``(None, None)`` in the case of samples. Note that ``sampling==True`` will always
                result in a dense return array independent of the other settings.
            interpret: A callable that maps the measured integer to another unsigned integer or
                tuple of unsigned integers. These are used as new indices for the (potentially
                sparse) output array. If this is used, and ``sampling==False``, the output shape of
                the output needs to be given as a separate argument. If no interpret function is
                passed, then an identity function will be used by this neural network.
            output_shape: The output shape of the custom interpretation, only used in the case
                where an interpret function is provided and ``sampling==False``. Note that in the
                remaining cases, the output shape is automatically inferred by: ``2^num_qubits`` if
                ``sampling==False`` and ``interpret==None``, ``(num_samples,1)``
                if ``sampling==True`` and ``interpret==None``, and
                ``(num_samples, interpret_shape)`` if ``sampling==True`` and an interpret function
                is provided.
            gradient: The gradient converter to be used for the probability gradients.
            quantum_instance: The quantum instance to evaluate the circuits. Note that
                if ``sampling==True``, a statevector simulator is not a valid backend for the
                quantum instance.
            input_gradients: Determines whether to compute gradients with respect to input data.
                Note that this parameter is ``False`` by default, and must be explicitly set to
                ``True`` for a proper gradient computation when using ``TorchConnector``.
        Raises:
            QiskitMachineLearningError: if ``interpret`` is passed without ``output_shape``.

        """
        self._input_params = list(input_params or [])
        self._weight_params = list(weight_params or [])
        self._input_gradients = input_gradients
        sparse = False if sampling else sparse
        if sparse:
            _optionals.HAS_SPARSE.require_now("DOK")

        # copy circuit and add measurements in case non are given
        # TODO: need to be able to handle partial measurements! (partial trace...)
        self._circuit = circuit.copy()
        # we have not transpiled the circuit yet
        self._circuit_transpiled = False
        # these original values may be re-used when a quantum instance is set,
        # but initially it was None
        self._original_output_shape = output_shape
        # next line is required by pylint only
        self._interpret = interpret
        self._original_interpret = interpret

        # we need this property in _set_quantum_instance despite it is initialized
        # in the super class later on, review of SamplingNN is required.
        self._sampling = sampling

        # set quantum instance and derive target output_shape and interpret
        self._set_quantum_instance(quantum_instance, output_shape, interpret)

        # init super class
        super().__init__(
            len(self._input_params),
            len(self._weight_params),
            sparse,
            sampling,
            self._output_shape,
            self._input_gradients,
        )

        self._original_circuit = circuit
        # use given gradient or default
        self._gradient = gradient or Gradient()

        # prepare probability gradient opflow object
        self._construct_gradient_circuit()
    def __init__(self, circuit: QuantumCircuit,
                 input_params: Optional[List[Parameter]] = None,
                 weight_params: Optional[List[Parameter]] = None,
                 sparse: bool = False,
                 sampling: bool = False,
                 interpret: Optional[Callable[[int], Union[int, Tuple[int, ...]]]] = None,
                 output_shape: Union[int, Tuple[int, ...]] = None,
                 gradient: Gradient = None,
                 quantum_instance: Optional[Union[QuantumInstance, BaseBackend, Backend]] = None
                 ) -> None:
        """Initializes the Circuit Quantum Neural Network.

        Args:
            circuit: The (parametrized) quantum circuit that generates the samples of this network.
            input_params: The parameters of the circuit corresponding to the input.
            weight_params: The parameters of the circuit corresponding to the trainable weights.
            sparse: Returns whether the output is sparse or not.
            sampling: Determines whether the network returns a batch of samples or (possibly
                sparse) array of probabilities in its forward pass. In case of probabilities,
                the backward pass returns the probability gradients, while it returns (None, None)
                in the case of samples. Note that sampling==True will always result in a
                dense return array independent of the other settings.
            interpret: A callable that maps the measured integer to another unsigned integer or
                tuple of unsigned integers. These are used as new indices for the (potentially
                sparse) output array. If this is used, the output shape of the output needs to be
                given as a separate argument.
            output_shape: The output shape of the custom interpretation. The output shape is
                automatically determined in case of sampling==True.
            gradient: The gradient converter to be used for the probability gradients.
            quantum_instance: The quantum instance to evaluate the circuits.

        Raises:
            QiskitMachineLearningError: if `interpret` is passed without `output_shape`.
        """

        # copy circuit and add measurements in case non are given
        self._circuit = circuit.copy()
        if quantum_instance.is_statevector:
            if len(self._circuit.clbits) > 0:
                self._circuit.remove_final_measurements()
        elif len(self._circuit.clbits) == 0:
            self._circuit.measure_all()

        self._input_params = list(input_params or [])
        self._weight_params = list(weight_params or [])
        self._interpret = interpret if interpret else lambda x: x
        sparse_ = sparse
        output_shape_: Union[int, Tuple[int, ...]] = -1
        if sampling:
            num_samples = quantum_instance.run_config.shots
            sparse_ = False
            # infer shape from function
            ret = self._interpret(0)
            result = np.array(ret)
            output_shape_ = (num_samples, *result.shape)
            if len(result.shape) == 0:
                output_shape_ = (num_samples, 1)
        else:
            if interpret:
                if output_shape is None:
                    raise QiskitMachineLearningError(
                        'No output shape given, but required in case of custom interpret!')
                output_shape_ = output_shape
            else:
                output_shape_ = (2**circuit.num_qubits,)

        self._gradient = gradient

        if isinstance(quantum_instance, (BaseBackend, Backend)):
            quantum_instance = QuantumInstance(quantum_instance)
        self._quantum_instance = quantum_instance
        self._sampler = CircuitSampler(quantum_instance, param_qobj=False, caching='all')

        # construct probability gradient opflow object
        grad_circuit = circuit.copy()
        grad_circuit.remove_final_measurements()  # TODO: ideally this would not be necessary
        params = list(input_params) + list(weight_params)
        self._grad_circuit = Gradient().convert(CircuitStateFn(grad_circuit), params)

        super().__init__(len(self._input_params), len(self._weight_params), sparse_, sampling,
                         output_shape_)