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)
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)
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)
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_)
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_)