def __init__(self, num_qubits: Optional[int] = None, rotation_blocks: Optional[Union[QuantumCircuit, List[QuantumCircuit], Instruction, List[Instruction]]] = None, entanglement_blocks: Optional[Union[QuantumCircuit, List[QuantumCircuit], Instruction, List[Instruction]]] = None, entanglement: Optional[Union[List[int], List[List[int]]]] = None, reps: int = 1, insert_barriers: bool = False, parameter_prefix: str = 'θ', overwrite_block_parameters: Union[bool, List[List[Parameter]]] = True, skip_final_rotation_layer: bool = False, skip_unentangled_qubits: bool = False, initial_state: Optional[Any] = None, name: Optional[str] = 'nlocal') -> None: """Create a new n-local circuit. Args: num_qubits: The number of qubits of the circuit. rotation_blocks: The blocks used in the rotation layers. If multiple are passed, these will be applied one after another (like new sub-layers). entanglement_blocks: The blocks used in the entanglement layers. If multiple are passed, these will be applied one after another. To use different entanglements for the sub-layers, see :meth:`get_entangler_map`. entanglement: The indices specifying on which qubits the input blocks act. If None, the entanglement blocks are applied at the top of the circuit. reps: Specifies how often the rotation blocks and entanglement blocks are repeated. insert_barriers: If True, barriers are inserted in between each layer. If False, no barriers are inserted. parameter_prefix: The prefix used if default parameters are generated. overwrite_block_parameters: If the parameters in the added blocks should be overwritten. If False, the parameters in the blocks are not changed. skip_final_rotation_layer: Whether a final rotation layer is added to the circuit. skip_unentangled_qubits: If ``True``, the rotation gates act only on qubits that are entangled. If ``False``, the rotation gates act on all qubits. initial_state: A `qiskit.aqua.components.initial_states.InitialState` object which can be used to describe an initial state prepended to the NLocal circuit. This is primarily for compatibility with algorithms in Qiskit Aqua, which leverage this object to prepare input states. name: The name of the circuit. Examples: TODO Raises: ImportError: If an ``initial_state`` is specified but Qiskit Aqua is not installed. TypeError: If an ``initial_state`` is specified but not of the correct type, ``qiskit.aqua.components.initial_states.InitialState``. ValueError: If reps parameter is less than or equal to 0. """ super().__init__(name=name) self._num_qubits = None self._insert_barriers = insert_barriers self._reps = reps self._entanglement_blocks = [] self._rotation_blocks = [] self._prepended_blocks = [] self._prepended_entanglement = [] self._appended_blocks = [] self._appended_entanglement = [] self._entanglement = None self._entangler_maps = None self._ordered_parameters = ParameterVector(name=parameter_prefix) self._overwrite_block_parameters = overwrite_block_parameters self._skip_final_rotation_layer = skip_final_rotation_layer self._skip_unentangled_qubits = skip_unentangled_qubits self._initial_state, self._initial_state_circuit = None, None self._data = None self._bounds = None if reps <= 0: raise ValueError('The value of reps should be larger than or equal to 1') if num_qubits is not None: self.num_qubits = num_qubits if entanglement_blocks is not None: self.entanglement_blocks = entanglement_blocks if rotation_blocks is not None: self.rotation_blocks = rotation_blocks if entanglement is not None: self.entanglement = entanglement if initial_state is not None: try: from qiskit.aqua.components.initial_states import InitialState if not isinstance(initial_state, InitialState): raise TypeError('initial_state must be of type InitialState, but is ' '{}.'.format(type(initial_state))) except ImportError as ex: raise ImportError( 'Could not import the qiskit.aqua.components.initial_states.' 'InitialState. To use this feature Qiskit Aqua must be installed.' ) from ex self.initial_state = initial_state
def setUp(self): super().setUp() self.seed = 1376 aqua_globals.random_seed = self.seed self.training_data = { 'A': np.asarray([[2.95309709, 2.51327412], [3.14159265, 4.08407045]]), 'B': np.asarray([[4.08407045, 2.26194671], [4.46106157, 2.38761042]]) } self.testing_data = { 'A': np.asarray([[3.83274304, 2.45044227]]), 'B': np.asarray([[3.89557489, 0.31415927]]) } self.ref_opt_params = np.array([ 10.03814083, -12.22048954, -7.58026833, -2.42392954, 12.91555293, 13.44064652, -2.89951454, -10.20639406, 0.81414546, -1.00551752, -4.7988307, 14.00831419, 8.26008064, -7.07543736, 11.43368677, -5.74857438 ]) self.ref_train_loss = 0.69366523 self.ref_prediction_a_probs = [[0.79882812, 0.20117188]] self.ref_prediction_a_label = [0] # ignore warnings from creating VariationalForm and FeatureMap objects warnings.filterwarnings('ignore', category=DeprecationWarning) var_form_ryrz = RYRZ(2, depth=3) feature_map = SecondOrderExpansion(2, depth=2) warnings.filterwarnings('always', category=DeprecationWarning) library_ryrz = TwoLocal(2, ['ry', 'rz'], 'cz', reps=3, insert_barriers=True) theta = ParameterVector('theta', var_form_ryrz.num_parameters) circuit_ryrz = var_form_ryrz.construct_circuit(theta) resorted = [] for i in range(4): layer = library_ryrz.ordered_parameters[4 * i:4 * (i + 1)] resorted += layer[::2] resorted += layer[1::2] library_ryrz.assign_parameters(dict(zip(resorted, theta)), inplace=True) self._sorted_wavefunction_params = list(theta) self.ryrz_wavefunction = { 'wrapped': var_form_ryrz, 'circuit': circuit_ryrz, 'library': library_ryrz } library_circuit = ZZFeatureMap(2, reps=2) x = ParameterVector('x', 2) circuit = feature_map.construct_circuit(x) self._sorted_data_params = list(x) library_circuit.assign_parameters(x, inplace=True) self.data_preparation = { 'wrapped': feature_map, 'circuit': circuit, 'library': library_circuit }
class TestNLocal(QiskitTestCase): """Test the n-local circuit class.""" def assertCircuitEqual(self, qc1, qc2, visual=False, transpiled=True): """An equality test specialized to circuits.""" if transpiled: basis_gates = ['id', 'u1', 'u3', 'cx'] qc1_transpiled = transpile(qc1, basis_gates=basis_gates, optimization_level=0) qc2_transpiled = transpile(qc2, basis_gates=basis_gates, optimization_level=0) qc1, qc2 = qc1_transpiled, qc2_transpiled if visual: self.assertEqual(qc1.draw(), qc2.draw()) else: self.assertEqual(qc1, qc2) def test_empty_nlocal(self): """Test the creation of an empty NLocal.""" nlocal = NLocal() self.assertEqual(nlocal.num_qubits, 0) self.assertEqual(nlocal.num_parameters_settable, 0) self.assertEqual(nlocal.reps, 1) self.assertEqual(nlocal, QuantumCircuit()) for attribute in [nlocal.rotation_blocks, nlocal.entanglement_blocks]: self.assertEqual(len(attribute), 0) @data((XGate(), [[0], [2], [1]]), (XGate(), [[0]]), (CRXGate(-0.2), [[2, 0], [1, 3]]), ) @unpack def test_add_layer_to_empty_nlocal(self, block, entangler_map): """Test appending gates to an empty nlocal.""" nlocal = NLocal() nlocal.add_layer(block, entangler_map) max_num_qubits = max(max(indices) for indices in entangler_map) reference = QuantumCircuit(max_num_qubits + 1) for indices in entangler_map: reference.append(block, indices) self.assertCircuitEqual(nlocal, reference) @data([5, 3], [1, 5], [1, 1], [1, 2, 3, 10]) def test_append_circuit(self, num_qubits): """Test appending circuits to an nlocal works normally.""" # fixed depth of 3 gates per circuit depth = 3 # keep track of a reference circuit reference = QuantumCircuit(max(num_qubits)) # construct the NLocal from the first circuit first_circuit = random_circuit(num_qubits[0], depth) # TODO Terra bug: if this is to_gate it fails, since the QC adds an instruction not gate nlocal = NLocal(max(num_qubits), entanglement_blocks=first_circuit.to_instruction(), reps=1) reference.append(first_circuit, list(range(num_qubits[0]))) # append the rest for num in num_qubits[1:]: circuit = random_circuit(num, depth) nlocal.append(circuit, list(range(num))) reference.append(circuit, list(range(num))) self.assertCircuitEqual(nlocal, reference) @data([5, 3], [1, 5], [1, 1], [1, 2, 3, 10]) def test_add_nlocal(self, num_qubits): """Test adding an nlocal to an nlocal (using add_layer).""" # fixed depth of 3 gates per circuit depth = 3 # keep track of a reference circuit reference = QuantumCircuit(max(num_qubits)) # construct the NLocal from the first circuit first_circuit = random_circuit(num_qubits[0], depth) # TODO Terra bug: if this is to_gate it fails, since the QC adds an instruction not gate nlocal = NLocal(max(num_qubits), entanglement_blocks=first_circuit.to_instruction(), reps=1) reference.append(first_circuit, list(range(num_qubits[0]))) # append the rest for num in num_qubits[1:]: circuit = random_circuit(num, depth) nlocal.add_layer(NLocal(num, entanglement_blocks=circuit, reps=1)) reference.append(circuit, list(range(num))) self.assertCircuitEqual(nlocal, reference) @unittest.skip('Feature missing') def test_iadd_overload(self): """Test the overloaded + operator.""" num_qubits, depth = 2, 2 # construct two circuits for adding first_circuit = random_circuit(num_qubits, depth) circuit = random_circuit(num_qubits, depth) # get a reference reference = first_circuit + circuit # convert the object to be appended to different types others = [circuit, circuit.to_instruction(), circuit.to_gate(), NLocal(circuit)] # try adding each type for other in others: nlocal = NLocal(num_qubits, entanglement_blocks=first_circuit, reps=1) nlocal += other with self.subTest(msg='type: {}'.format(type(other))): self.assertCircuitEqual(nlocal, reference) def test_parameter_getter_from_automatic_repetition(self): """Test getting and setting of the nlocal parameters.""" circuit = QuantumCircuit(2) circuit.ry(Parameter('a'), 0) circuit.crx(Parameter('b'), 0, 1) # repeat circuit and check that parameters are duplicated reps = 3 nlocal = NLocal(2, entanglement_blocks=circuit, reps=reps) self.assertTrue(nlocal.num_parameters, 6) self.assertTrue(len(nlocal.parameters), 6) @data(list(range(6)), ParameterVector('θ', length=6), [0, 1, Parameter('theta'), 3, 4, 5]) def test_parameter_setter_from_automatic_repetition(self, params): """Test getting and setting of the nlocal parameters.""" circuit = QuantumCircuit(2) circuit.ry(Parameter('a'), 0) circuit.crx(Parameter('b'), 0, 1) # repeat circuit and check that parameters are duplicated reps = 3 nlocal = NLocal(2, entanglement_blocks=circuit, reps=reps) nlocal.assign_parameters(params, inplace=True) param_set = set(p for p in params if isinstance(p, ParameterExpression)) with self.subTest(msg='Test the parameters of the non-transpiled circuit'): # check the parameters of the final circuit self.assertEqual(nlocal.parameters, param_set) with self.subTest(msg='Test the parameters of the transpiled circuit'): basis_gates = ['id', 'u1', 'u2', 'u3', 'cx'] transpiled_circuit = transpile(nlocal, basis_gates=basis_gates) self.assertEqual(transpiled_circuit.parameters, param_set) @data(list(range(6)), ParameterVector('θ', length=6), [0, 1, Parameter('theta'), 3, 4, 5]) def test_parameters_setter(self, params): """Test setting the parameters via list.""" # construct circuit with some parameters initial_params = ParameterVector('p', length=6) circuit = QuantumCircuit(1) for i, initial_param in enumerate(initial_params): circuit.ry(i * initial_param, 0) # create an NLocal from the circuit and set the new parameters nlocal = NLocal(1, entanglement_blocks=circuit, reps=1) nlocal.assign_parameters(params, inplace=True) param_set = set(p for p in params if isinstance(p, ParameterExpression)) with self.subTest(msg='Test the parameters of the non-transpiled circuit'): # check the parameters of the final circuit self.assertEqual(nlocal.parameters, param_set) with self.subTest(msg='Test the parameters of the transpiled circuit'): basis_gates = ['id', 'u1', 'u2', 'u3', 'cx'] transpiled_circuit = transpile(nlocal, basis_gates=basis_gates) self.assertEqual(transpiled_circuit.parameters, param_set) def test_repetetive_parameter_setting(self): """Test alternate setting of parameters and circuit construction.""" x = Parameter('x') circuit = QuantumCircuit(1) circuit.rx(x, 0) nlocal = NLocal(1, entanglement_blocks=circuit, reps=3, insert_barriers=True) with self.subTest(msg='immediately after initialization'): self.assertEqual(len(nlocal.parameters), 3) with self.subTest(msg='after circuit construction'): self.assertEqual(len(nlocal.parameters), 3) q = Parameter('q') nlocal.assign_parameters([x, q, q], inplace=True) with self.subTest(msg='setting parameter to Parameter objects'): self.assertEqual(nlocal.parameters, set({x, q})) nlocal.assign_parameters([0, -1], inplace=True) with self.subTest(msg='setting parameter to numbers'): self.assertEqual(nlocal.parameters, set()) def test_skip_unentangled_qubits(self): """Test skipping the unentangled qubits.""" num_qubits = 6 entanglement_1 = [[0, 1, 3], [1, 3, 5], [0, 1, 5]] skipped_1 = [2, 4] entanglement_2 = [ entanglement_1, [[0, 1, 2], [2, 3, 5]] ] skipped_2 = [4] for entanglement, skipped in zip([entanglement_1, entanglement_2], [skipped_1, skipped_2]): with self.subTest(entanglement=entanglement, skipped=skipped): nlocal = NLocal(num_qubits, rotation_blocks=XGate(), entanglement_blocks=CCXGate(), entanglement=entanglement, reps=3, skip_unentangled_qubits=True) skipped_set = set(nlocal.qubits[i] for i in skipped) dag = circuit_to_dag(nlocal) idle = set(dag.idle_wires()) self.assertEqual(skipped_set, idle) @data('linear', 'full', 'circular', 'sca', ['linear', 'full'], ['circular', 'linear', 'sca']) def test_entanglement_by_str(self, entanglement): """Test setting the entanglement of the layers by str.""" reps = 3 nlocal = NLocal(5, rotation_blocks=XGate(), entanglement_blocks=CCXGate(), entanglement=entanglement, reps=reps) def get_expected_entangler_map(rep_num, mode): if mode == 'linear': return [(0, 1, 2), (1, 2, 3), (2, 3, 4)] elif mode == 'full': return [(0, 1, 2), (0, 1, 3), (0, 1, 4), (0, 2, 3), (0, 2, 4), (0, 3, 4), (1, 2, 3), (1, 2, 4), (1, 3, 4), (2, 3, 4)] else: circular = [(3, 4, 0), (0, 1, 2), (1, 2, 3), (2, 3, 4)] if mode == 'circular': return circular sca = circular[-rep_num:] + circular[:-rep_num] if rep_num % 2 == 1: sca = [tuple(reversed(indices)) for indices in sca] return sca for rep_num in range(reps): entangler_map = nlocal.get_entangler_map(rep_num, 0, 3) if isinstance(entanglement, list): mode = entanglement[rep_num % len(entanglement)] else: mode = entanglement expected = get_expected_entangler_map(rep_num, mode) with self.subTest(rep_num=rep_num): # using a set here since the order does not matter self.assertEqual(set(entangler_map), set(expected)) def test_entanglement_by_list(self): """Test setting the entanglement by list. This is the circuit we test (times 2, with final X layer) ┌───┐ ┌───┐┌───┐ ┌───┐ q_0: |0>┤ X ├──■────■───X────┤ X ├┤ X ├──■───X─────── .. ┤ X ├ ├───┤ │ │ │ ├───┤└─┬─┘ │ │ ├───┤ q_1: |0>┤ X ├──■────┼───┼──X─┤ X ├──■────┼───X──X──── .. ┤ X ├ ├───┤┌─┴─┐ │ │ │ ├───┤ │ │ │ x2 ├───┤ q_2: |0>┤ X ├┤ X ├──■───┼──X─┤ X ├──■────■──────X──X─ .. ┤ X ├ ├───┤└───┘┌─┴─┐ │ ├───┤ ┌─┴─┐ │ ├───┤ q_3: |0>┤ X ├─────┤ X ├─X────┤ X ├─────┤ X ├───────X─ .. ┤ X ├ └───┘ └───┘ └───┘ └───┘ └───┘ """ circuit = QuantumCircuit(4) for _ in range(2): circuit.x([0, 1, 2, 3]) circuit.barrier() circuit.ccx(0, 1, 2) circuit.ccx(0, 2, 3) circuit.swap(0, 3) circuit.swap(1, 2) circuit.barrier() circuit.x([0, 1, 2, 3]) circuit.barrier() circuit.ccx(2, 1, 0) circuit.ccx(0, 2, 3) circuit.swap(0, 1) circuit.swap(1, 2) circuit.swap(2, 3) circuit.barrier() circuit.x([0, 1, 2, 3]) layer_1_ccx = [(0, 1, 2), (0, 2, 3)] layer_1_swap = [(0, 3), (1, 2)] layer_1 = [layer_1_ccx, layer_1_swap] layer_2_ccx = [(2, 1, 0), (0, 2, 3)] layer_2_swap = [(0, 1), (1, 2), (2, 3)] layer_2 = [layer_2_ccx, layer_2_swap] entanglement = [layer_1, layer_2] nlocal = NLocal(4, rotation_blocks=XGate(), entanglement_blocks=[CCXGate(), SwapGate()], reps=4, entanglement=entanglement, insert_barriers=True) self.assertCircuitEqual(nlocal, circuit)
class NLocal(BlueprintCircuit): """The n-local circuit class. The structure of the n-local circuit are alternating rotation and entanglement layers. In both layers, parameterized circuit-blocks act on the circuit in a defined way. In the rotation layer, the blocks are applied stacked on top of each other, while in the entanglement layer according to the ``entanglement`` strategy. The circuit blocks can have arbitrary sizes (smaller equal to the number of qubits in the circuit). Each layer is repeated ``reps`` times, and by default a final rotation layer is appended. For instance, a rotation block on 2 qubits and an entanglement block on 4 qubits using ``'linear'`` entanglement yields the following circuit. .. parsed-literal:: ┌──────┐ ░ ┌──────┐ ░ ┌──────┐ ┤0 ├─░─┤0 ├──────────────── ... ─░─┤0 ├ │ Rot │ ░ │ │┌──────┐ ░ │ Rot │ ┤1 ├─░─┤1 ├┤0 ├──────── ... ─░─┤1 ├ ├──────┤ ░ │ Ent ││ │┌──────┐ ░ ├──────┤ ┤0 ├─░─┤2 ├┤1 ├┤0 ├ ... ─░─┤0 ├ │ Rot │ ░ │ ││ Ent ││ │ ░ │ Rot │ ┤1 ├─░─┤3 ├┤2 ├┤1 ├ ... ─░─┤1 ├ ├──────┤ ░ └──────┘│ ││ Ent │ ░ ├──────┤ ┤0 ├─░─────────┤3 ├┤2 ├ ... ─░─┤0 ├ │ Rot │ ░ └──────┘│ │ ░ │ Rot │ ┤1 ├─░─────────────────┤3 ├ ... ─░─┤1 ├ └──────┘ ░ └──────┘ ░ └──────┘ | | +---------------------------------+ repeated reps times If specified, barriers can be inserted in between every block. If an initial state object of Qiskit Aqua is provided, it is added in front of the NLocal. """ def __init__(self, num_qubits: Optional[int] = None, rotation_blocks: Optional[Union[QuantumCircuit, List[QuantumCircuit], Instruction, List[Instruction]]] = None, entanglement_blocks: Optional[Union[QuantumCircuit, List[QuantumCircuit], Instruction, List[Instruction]]] = None, entanglement: Optional[Union[List[int], List[List[int]]]] = None, reps: int = 1, insert_barriers: bool = False, parameter_prefix: str = 'θ', overwrite_block_parameters: Union[bool, List[List[Parameter]]] = True, skip_final_rotation_layer: bool = False, skip_unentangled_qubits: bool = False, initial_state: Optional[Any] = None, name: Optional[str] = 'nlocal') -> None: """Create a new n-local circuit. Args: num_qubits: The number of qubits of the circuit. rotation_blocks: The blocks used in the rotation layers. If multiple are passed, these will be applied one after another (like new sub-layers). entanglement_blocks: The blocks used in the entanglement layers. If multiple are passed, these will be applied one after another. To use different entanglements for the sub-layers, see :meth:`get_entangler_map`. entanglement: The indices specifying on which qubits the input blocks act. If None, the entanglement blocks are applied at the top of the circuit. reps: Specifies how often the rotation blocks and entanglement blocks are repeated. insert_barriers: If True, barriers are inserted in between each layer. If False, no barriers are inserted. parameter_prefix: The prefix used if default parameters are generated. overwrite_block_parameters: If the parameters in the added blocks should be overwritten. If False, the parameters in the blocks are not changed. skip_final_rotation_layer: Whether a final rotation layer is added to the circuit. skip_unentangled_qubits: If ``True``, the rotation gates act only on qubits that are entangled. If ``False``, the rotation gates act on all qubits. initial_state: A `qiskit.aqua.components.initial_states.InitialState` object which can be used to describe an initial state prepended to the NLocal circuit. This is primarily for compatibility with algorithms in Qiskit Aqua, which leverage this object to prepare input states. name: The name of the circuit. Examples: TODO Raises: ImportError: If an ``initial_state`` is specified but Qiskit Aqua is not installed. TypeError: If an ``initial_state`` is specified but not of the correct type, ``qiskit.aqua.components.initial_states.InitialState``. ValueError: If reps parameter is less than or equal to 0. """ super().__init__(name=name) self._num_qubits = None self._insert_barriers = insert_barriers self._reps = reps self._entanglement_blocks = [] self._rotation_blocks = [] self._prepended_blocks = [] self._prepended_entanglement = [] self._appended_blocks = [] self._appended_entanglement = [] self._entanglement = None self._entangler_maps = None self._ordered_parameters = ParameterVector(name=parameter_prefix) self._overwrite_block_parameters = overwrite_block_parameters self._skip_final_rotation_layer = skip_final_rotation_layer self._skip_unentangled_qubits = skip_unentangled_qubits self._initial_state, self._initial_state_circuit = None, None self._data = None self._bounds = None if reps <= 0: raise ValueError('The value of reps should be larger than or equal to 1') if num_qubits is not None: self.num_qubits = num_qubits if entanglement_blocks is not None: self.entanglement_blocks = entanglement_blocks if rotation_blocks is not None: self.rotation_blocks = rotation_blocks if entanglement is not None: self.entanglement = entanglement if initial_state is not None: try: from qiskit.aqua.components.initial_states import InitialState if not isinstance(initial_state, InitialState): raise TypeError('initial_state must be of type InitialState, but is ' '{}.'.format(type(initial_state))) except ImportError as ex: raise ImportError( 'Could not import the qiskit.aqua.components.initial_states.' 'InitialState. To use this feature Qiskit Aqua must be installed.' ) from ex self.initial_state = initial_state @property def num_qubits(self) -> int: """Returns the number of qubits in this circuit. Returns: The number of qubits. """ return self._num_qubits if self._num_qubits is not None else 0 @num_qubits.setter def num_qubits(self, num_qubits: int) -> None: """Set the number of qubits for the n-local circuit. Args: The new number of qubits. """ if self._num_qubits != num_qubits: # invalidate the circuit self._invalidate() self._num_qubits = num_qubits self.qregs = [QuantumRegister(num_qubits, name='q')] def _convert_to_block(self, layer: Any) -> QuantumCircuit: """Try to convert ``layer`` to a QuantumCircuit. Args: layer: The object to be converted to an NLocal block / Instruction. Returns: The layer converted to a circuit. Raises: TypeError: If the input cannot be converted to a circuit. """ if isinstance(layer, QuantumCircuit): return layer if isinstance(layer, Instruction): circuit = QuantumCircuit(layer.num_qubits) circuit.append(layer, list(range(layer.num_qubits))) return circuit try: circuit = QuantumCircuit(layer.num_qubits) circuit.append(layer.to_instruction(), list(range(layer.num_qubits))) return circuit except AttributeError: pass raise TypeError('Adding a {} to an NLocal is not supported.'.format(type(layer))) @property def rotation_blocks(self) -> List[Instruction]: """The blocks in the rotation layers. Returns: The blocks in the rotation layers. """ return self._rotation_blocks @rotation_blocks.setter def rotation_blocks(self, blocks: Union[QuantumCircuit, List[QuantumCircuit], Instruction, List[Instruction]]) -> None: """Set the blocks in the rotation layers. Args: blocks: The new blocks for the rotation layers. """ # cannot check for the attribute ``'__len__'`` because a circuit also has this attribute if not isinstance(blocks, (list, numpy.ndarray)): blocks = [blocks] self._invalidate() self._rotation_blocks = [self._convert_to_block(block) for block in blocks] @property def entanglement_blocks(self) -> List[Instruction]: """The blocks in the entanglement layers. Returns: The blocks in the entanglement layers. """ return self._entanglement_blocks @entanglement_blocks.setter def entanglement_blocks(self, blocks: Union[QuantumCircuit, List[QuantumCircuit], Instruction, List[Instruction]]) -> None: """Set the blocks in the entanglement layers. Args: blocks: The new blocks for the entanglement layers. """ # cannot check for the attribute ``'__len__'`` because a circuit also has this attribute if not isinstance(blocks, (list, numpy.ndarray)): blocks = [blocks] self._invalidate() self._entanglement_blocks = [self._convert_to_block(block) for block in blocks] @property def entanglement(self) -> Union[str, List[str], List[List[str]], List[int], List[List[int]], List[List[List[int]]], List[List[List[List[int]]]], Callable[[int], str], Callable[[int], List[List[int]]]]: """Get the entanglement strategy. Returns: The entanglement strategy, see :meth:`get_entangler_map` for more detail on how the format is interpreted. """ return self._entanglement @entanglement.setter def entanglement(self, entanglement: Optional[Union[str, List[str], List[List[str]], List[int], List[List[int]], List[List[List[int]]], List[List[List[List[int]]]], Callable[[int], str], Callable[[int], List[List[int]]]]]) -> None: """Set the entanglement strategy. Args: entanglement: The entanglement strategy. See :meth:`get_entangler_map` for more detail on the supported formats. """ self._invalidate() self._entanglement = entanglement @property def num_layers(self) -> int: """Return the number of layers in the n-local circuit. Returns: The number of layers in the circuit. """ return 2 * self._reps + int(not self._skip_final_rotation_layer) def _check_configuration(self, raise_on_failure: bool = True) -> bool: """Check if the configuration of the NLocal class is valid. Args: raise_on_failure: Whether to raise on failure. Returns: True, if the configuration is valid and the circuit can be constructed. Otherwise an ValueError is raised. Raises: ValueError: If the blocks are not set. ValueError: If the number of repetitions is not set. ValueError: If the qubit indices are not set. ValueError: If the number of qubit indices does not match the number of blocks. ValueError: If an index in the repetitions list exceeds the number of blocks. ValueError: If the number of repetitions does not match the number of block-wise parameters. ValueError: If a specified qubit index is larger than the (manually set) number of qubits. """ valid = True if self.num_qubits is None: valid = False if raise_on_failure: raise ValueError('No number of qubits specified.') # check no needed parameters are None if self.entanglement_blocks is None and self.rotation_blocks is None: valid = False if raise_on_failure: raise ValueError('The blocks are not set.') return valid @property def ordered_parameters(self) -> List[Parameter]: """The parameters used in the underlying circuit. This includes float values and duplicates. Examples: >>> # prepare circuit ... >>> print(nlocal) ┌───────┐┌──────────┐┌──────────┐┌──────────┐ q_0: ┤ Ry(1) ├┤ Ry(θ[1]) ├┤ Ry(θ[1]) ├┤ Ry(θ[3]) ├ └───────┘└──────────┘└──────────┘└──────────┘ >>> nlocal.parameters {Parameter(θ[1]), Parameter(θ[3])} >>> nlocal.ordered_parameters [1, Parameter(θ[1]), Parameter(θ[1]), Parameter(θ[3])] Returns: The parameters objects used in the circuit. """ if isinstance(self._ordered_parameters, ParameterVector): self._ordered_parameters.resize(self.num_parameters_settable) return list(self._ordered_parameters) return self._ordered_parameters @ordered_parameters.setter def ordered_parameters(self, parameters: Union[ParameterVector, List[Parameter]]) -> None: """Set the parameters used in the underlying circuit. Args: The parameters to be used in the underlying circuit. Raises: ValueError: If the length of ordered parameters does not match the number of parameters in the circuit and they are not a ``ParameterVector`` (which could be resized to fit the number of parameters). """ if not isinstance(parameters, ParameterVector) \ and len(parameters) != self.num_parameters_settable: raise ValueError('The length of ordered parameters must be equal to the number of ' 'settable parameters in the circuit ({}), but is {}'.format( self.num_parameters_settable, len(parameters) )) self._ordered_parameters = parameters self._invalidate() @property def insert_barriers(self) -> bool: """If barriers are inserted in between the layers or not. Returns: True, if barriers are inserted in between the layers, False if not. """ return self._insert_barriers @insert_barriers.setter def insert_barriers(self, insert_barriers: bool) -> None: """Specify whether barriers should be inserted in between the layers or not. Args: insert_barriers: If True, barriers are inserted, if False not. """ # if insert_barriers changes, we have to invalidate the circuit definition, # if it is the same as before we can leave the NLocal instance as it is if insert_barriers is not self._insert_barriers: self._invalidate() self._insert_barriers = insert_barriers def get_unentangled_qubits(self) -> Set[int]: """Get the indices of unentangled qubits in a set. Returns: The unentangled qubits. """ entangled_qubits = set() for i in range(self._reps): for j, block in enumerate(self.entanglement_blocks): entangler_map = self.get_entangler_map(i, j, block.num_qubits) entangled_qubits.update([idx for indices in entangler_map for idx in indices]) unentangled_qubits = set(range(self.num_qubits)) - entangled_qubits return unentangled_qubits @property def num_parameters_settable(self) -> int: """The number of total parameters that can be set to distinct values. This does not change when the parameters are bound or exchanged for same parameters, and therefore is different from ``num_parameters`` which counts the number of unique :class:`~qiskit.circuit.Parameter` objects currently in the circuit. Returns: The number of parameters originally available in the circuit. Note: This quantity does not require the circuit to be built yet. """ num = 0 for i in range(self._reps): for j, block in enumerate(self.entanglement_blocks): entangler_map = self.get_entangler_map(i, j, block.num_qubits) num += len(entangler_map) * len(get_parameters(block)) if self._skip_unentangled_qubits: unentangled_qubits = self.get_unentangled_qubits() num_rot = 0 for block in self.rotation_blocks: block_indices = [ list(range(j * block.num_qubits, (j + 1) * block.num_qubits)) for j in range(self.num_qubits // block.num_qubits) ] if self._skip_unentangled_qubits: block_indices = [indices for indices in block_indices if set(indices).isdisjoint(unentangled_qubits)] num_rot += len(block_indices) * len(get_parameters(block)) num += num_rot * (self._reps + int(not self._skip_final_rotation_layer)) return num @property def parameters(self) -> Set[Parameter]: """Get the :class:`~qiskit.circuit.Parameter` objects in the circuit. Returns: A set containing the unbound circuit parameters. """ self._build() return super().parameters @property def reps(self) -> int: """The number of times rotation and entanglement block are repeated. Returns: The number of repetitions. """ return self._reps @reps.setter def reps(self, repetitions: int) -> None: """Set the repetitions. Args: repetitions: The new repetitions. Raises: ValueError: If reps setter has parameter repetitions <= 0. """ if repetitions <= 0: raise ValueError('The repetitions should be larger than or equal to 1') if repetitions != self._reps: self._invalidate() self._reps = repetitions def print_settings(self) -> str: """Returns information about the setting. Returns: The class name and the attributes/parameters of the instance as ``str``. """ ret = 'NLocal: {}\n'.format(self.__class__.__name__) params = '' for key, value in self.__dict__.items(): if key[0] == '_': params += '-- {}: {}\n'.format(key[1:], value) ret += '{}'.format(params) return ret @property def preferred_init_points(self) -> Optional[List[float]]: """The initial points for the parameters. Can be stored as initial guess in optimization. Returns: The initial values for the parameters, or None, if none have been set. """ return None # pylint: disable=too-many-return-statements def get_entangler_map(self, rep_num: int, block_num: int, num_block_qubits: int ) -> List[List[int]]: """Get the entangler map for in the repetition ``rep_num`` and the block ``block_num``. The entangler map for the current block is derived from the value of ``self.entanglement``. Below the different cases are listed, where ``i`` and ``j`` denote the repetition number and the block number, respectively, and ``n`` the number of qubits in the block. entanglement type | entangler map -------------------------------+-------------------------------------------------------- None | [[0, ..., n - 1]] str (e.g 'full') | the specified connectivity on ``n`` qubits List[int] | [``entanglement``] List[List[int]] | ``entanglement`` List[List[List[int]]] | ``entanglement[i]`` List[List[List[List[int]]]] | ``entanglement[i][j]`` List[str] | the connectivity specified in ``entanglement[i]`` List[List[str]] | the connectivity specified in ``entanglement[i][j]`` Callable[int, str] | same as List[str] Callable[int, List[List[int]]] | same as List[List[List[int]]] Note that all indices are to be taken modulo the length of the array they act on, i.e. no out-of-bounds index error will be raised but we re-iterate from the beginning of the list. Args: rep_num: The current repetition we are in. block_num: The block number within the entanglement layers. num_block_qubits: The number of qubits in the block. Returns: The entangler map for the current block in the current repetition. Raises: ValueError: If the value of ``entanglement`` could not be cast to a corresponding entangler map. """ i, j, n = rep_num, block_num, num_block_qubits entanglement = self._entanglement # entanglement is None if entanglement is None: return [list(range(n))] # entanglement is callable if callable(entanglement): entanglement = entanglement(i) # entanglement is str if isinstance(entanglement, str): return get_entangler_map(n, self.num_qubits, entanglement, offset=i) # check if entanglement is list of something if not isinstance(entanglement, (tuple, list)): raise ValueError('Invalid value of entanglement: {}'.format(entanglement)) num_i = len(entanglement) # entanglement is List[str] if all(isinstance(e, str) for e in entanglement): return get_entangler_map(n, self.num_qubits, entanglement[i % num_i], offset=i) # entanglement is List[int] if all(isinstance(e, int) for e in entanglement): return [entanglement] # check if entanglement is List[List] if not all(isinstance(e, (tuple, list)) for e in entanglement): raise ValueError('Invalid value of entanglement: {}'.format(entanglement)) num_j = len(entanglement[i % num_i]) # entanglement is List[List[str]] if all(isinstance(e2, str) for e in entanglement for e2 in e): return get_entangler_map(n, self.num_qubits, entanglement[i % num_i][j % num_j], offset=i) # entanglement is List[List[int]] if all(isinstance(e2, int) for e in entanglement for e2 in e): return entanglement # check if entanglement is List[List[List]] if not all(isinstance(e2, (tuple, list)) for e in entanglement for e2 in e): raise ValueError('Invalid value of entanglement: {}'.format(entanglement)) # entanglement is List[List[List[int]]] if all(isinstance(e3, int) for e in entanglement for e2 in e for e3 in e2): return entanglement[i % num_i] # check if entanglement is List[List[List[List]]] if not all(isinstance(e3, (tuple, list)) for e in entanglement for e2 in e for e3 in e2): raise ValueError('Invalid value of entanglement: {}'.format(entanglement)) # entanglement is List[List[List[List[int]]]] if all(isinstance(e4, int) for e in entanglement for e2 in e for e3 in e2 for e4 in e3): return entanglement[i % num_i][j % num_j] raise ValueError('Invalid value of entanglement: {}'.format(entanglement)) @property def initial_state(self) -> Any: """Return the initial state that is added in front of the n-local circuit. Returns: The initial state. """ return self._initial_state @initial_state.setter def initial_state(self, initial_state: Any) -> None: """Set the initial state. Args: initial_state: The new initial state. Raises: ValueError: If the number of qubits has been set before and the initial state does not match the number of qubits. """ # If there is an initial state object, check that the number of qubits is compatible # construct the circuit immediately. If the InitialState could modify the number of qubits # we could also do this later at circuit construction. self._initial_state = initial_state # construct the circuit of the initial state self._initial_state_circuit = initial_state.construct_circuit(mode='circuit') # the initial state dictates the number of qubits since we do not have information # about on which qubits the initial state acts if self._num_qubits is not None and \ self._initial_state_circuit.num_qubits != self._num_qubits: raise ValueError('Mismatching number of qubits in initial state and n-local circuit.') self._invalidate() @property def parameter_bounds(self) -> Optional[List[Tuple[float, float]]]: """The parameter bounds for the unbound parameters in the circuit. Returns: A list of pairs indicating the bounds, as (lower, upper). None indicates an unbounded parameter in the corresponding direction. If None is returned, problem is fully unbounded. """ self._build() return self._bounds @parameter_bounds.setter def parameter_bounds(self, bounds: List[Tuple[float, float]]) -> None: """Set the parameter bounds. Args: bounds: The new parameter bounds. """ self._bounds = bounds def _invalidate(self): """Invalidate the current circuit build.""" self._data = None self._parameter_table = ParameterTable() def add_layer(self, other: Union['NLocal', Instruction, QuantumCircuit], entanglement: Optional[Union[List[int], str, List[List[int]]]] = None, front: bool = False, ) -> 'NLocal': """Append another layer to the NLocal. Args: other: The layer to compose, can be another NLocal, an Instruction or Gate, or a QuantumCircuit. entanglement: The entanglement or qubit indices. front: If True, ``other`` is appended to the front, else to the back. Returns: self, such that chained composes are possible. Raises: TypeError: If `other` is not compatible, i.e. is no Instruction and does not have a `to_instruction` method. """ block = self._convert_to_block(other) if entanglement is None: entanglement = [list(range(block.num_qubits))] elif isinstance(entanglement, list) and not isinstance(entanglement[0], list): entanglement = [entanglement] if front: self._prepended_blocks += [block] self._prepended_entanglement += [entanglement] else: self._appended_blocks += [block] self._appended_entanglement += [entanglement] if isinstance(entanglement, list): num_qubits = 1 + max(max(indices) for indices in entanglement) if num_qubits > self.num_qubits: self._invalidate() # rebuild circuit self.num_qubits = num_qubits # modify the circuit accordingly if self._data and front is False: if self._insert_barriers and len(self._data) > 0: self.barrier() if isinstance(entanglement, str): entangler_map = get_entangler_map(block.num_qubits, self.num_qubits, entanglement) else: entangler_map = entanglement layer = QuantumCircuit(self.num_qubits) for i in entangler_map: params = self.ordered_parameters[-len(get_parameters(block)):] parameterized_block = self._parameterize_block(block, params=params) layer.compose(parameterized_block, i) self += layer else: # cannot prepend a block currently, just rebuild self._invalidate() return self def assign_parameters(self, param_dict: Union[dict, List[float], List[Parameter], ParameterVector], inplace: bool = False) -> Optional[QuantumCircuit]: """Assign parameters to the n-local circuit. This method also supports passing a list instead of a dictionary. If a list is passed, the list must have the same length as the number of unbound parameters in the circuit. The parameters are assigned in the order of the parameters in :meth:`ordered_parameters`. Returns: A copy of the NLocal circuit with the specified parameters. Raises: AttributeError: If the parameters are given as list and do not match the number of parameters. """ if self._data is None: self._build() if not isinstance(param_dict, dict): if len(param_dict) != self.num_parameters: raise AttributeError('If the parameters are provided as list, the size must match ' 'the number of parameters ({}), but {} are given.'.format( self.num_parameters, len(param_dict) )) unbound_params = [param for param in self._ordered_parameters if isinstance(param, ParameterExpression)] # to get a sorted list of unique parameters, keep track of the already used parameters # in a set and add the parameters to the unique list only if not existing in the set used = set() unbound_unique_params = [] for param in unbound_params: if param not in used: unbound_unique_params.append(param) used.add(param) param_dict = dict(zip(unbound_unique_params, param_dict)) if inplace: new = [param_dict.get(param, param) for param in self.ordered_parameters] self._ordered_parameters = new return super().assign_parameters(param_dict, inplace=inplace) def _parameterize_block(self, block, param_iter=None, rep_num=None, block_num=None, indices=None, params=None): """Convert ``block`` to a circuit of correct width and parameterized using the iterator.""" if self._overwrite_block_parameters: # check if special parameters should be used # pylint: disable=assignment-from-none if params is None: params = self._parameter_generator(rep_num, block_num, indices) if params is None: params = [next(param_iter) for _ in range(len(get_parameters(block)))] update = dict(zip(block.parameters, params)) return block.assign_parameters(update) return block.copy() def _build_rotation_layer(self, param_iter, i): """Build a rotation layer.""" # if the unentangled qubits are skipped, compute the set of qubits that are not entangled if self._skip_unentangled_qubits: unentangled_qubits = self.get_unentangled_qubits() # iterate over all rotation blocks for j, block in enumerate(self.rotation_blocks): # create a new layer layer = QuantumCircuit(*self.qregs) # we apply the rotation gates stacked on top of each other, i.e. # if we have 4 qubits and a rotation block of width 2, we apply two instances block_indices = [ list(range(k * block.num_qubits, (k + 1) * block.num_qubits)) for k in range(self.num_qubits // block.num_qubits) ] # if unentangled qubits should not be acted on, remove all operations that # touch an unentangled qubit if self._skip_unentangled_qubits: block_indices = [indices for indices in block_indices if set(indices).isdisjoint(unentangled_qubits)] # apply the operations in the layer for indices in block_indices: parameterized_block = self._parameterize_block(block, param_iter, i, j, indices) layer.compose(parameterized_block, indices, inplace=True) # add the layer to the circuit self += layer def _build_entanglement_layer(self, param_iter, i): """Build an entanglement layer.""" # iterate over all entanglement blocks for j, block in enumerate(self.entanglement_blocks): # create a new layer and get the entangler map for this block layer = QuantumCircuit(*self.qregs) entangler_map = self.get_entangler_map(i, j, block.num_qubits) # apply the operations in the layer for indices in entangler_map: parameterized_block = self._parameterize_block(block, param_iter, i, j, indices) layer.compose(parameterized_block, indices, inplace=True) # add the layer to the circuit self += layer def _build_additional_layers(self, which): if which == 'appended': blocks = self._appended_blocks entanglements = self._appended_entanglement elif which == 'prepended': blocks = reversed(self._prepended_blocks) entanglements = reversed(self._prepended_entanglement) else: raise ValueError('`which` must be either `appended` or `prepended`.') for block, ent in zip(blocks, entanglements): layer = QuantumCircuit(*self.qregs) if isinstance(ent, str): ent = get_entangler_map(block.num_qubits, self.num_qubits, ent) for indices in ent: layer.compose(block, indices, inplace=True) self += layer def _build(self) -> None: """Build the circuit.""" if self._data: return _ = self._check_configuration() self._data = [] if self.num_qubits == 0: return # use the initial state circuit if it is not None if self._initial_state: circuit = self._initial_state.construct_circuit('circuit', register=self.qregs[0]) self += circuit param_iter = iter(self.ordered_parameters) # build the prepended layers self._build_additional_layers('prepended') # main loop to build the entanglement and rotation layers for i in range(self.reps): # insert barrier if specified and there is a preceding layer if self._insert_barriers and (i > 0 or len(self._prepended_blocks) > 0): self.barrier() # build the rotation layer self._build_rotation_layer(param_iter, i) # barrier in between rotation and entanglement layer if self._insert_barriers and len(self._rotation_blocks) > 0: self.barrier() # build the entanglement layer self._build_entanglement_layer(param_iter, i) # add the final rotation layer if not self._skip_final_rotation_layer: if self.insert_barriers: self.barrier() self._build_rotation_layer(param_iter, self.reps) # add the appended layers self._build_additional_layers('appended') # pylint: disable=unused-argument def _parameter_generator(self, rep: int, block: int, indices: List[int]) -> Optional[Parameter]: """If certain blocks should use certain parameters this method can be overriden.""" return None def __str__(self) -> str: """Draw this NLocal in circuit format using the standard gates. Returns: A single string representing this NLocal. """ from qiskit.compiler import transpile basis_gates = ['id', 'x', 'y', 'z', 'h', 's', 't', 'sdg', 'tdg', 'rx', 'ry', 'rz', 'rxx', 'ryy', 'cx', 'cy', 'cz', 'ch', 'crx', 'cry', 'crz', 'swap', 'cswap', 'ccx', 'cu1', 'cu3', 'u1', 'u2', 'u3'] return transpile(self, basis_gates=basis_gates, optimization_level=0).draw(output='text').single_string()
class RawFeatureVector(BlueprintCircuit): """The raw feature vector circuit. This circuit acts as parameterized initialization for statevectors with ``feature_dimension`` dimensions, thus with ``log2(feature_dimension)`` qubits. The circuit contains a placeholder instruction that can only be synthesized/defined when all parameters are bound. In ML, this circuit can be used to load the training data into qubit amplitudes. It does not apply an kernel transformation (therefore, it is a "raw" feature vector). Since initialization is implemented via a ``QuantumCircuit.initialize()`` call, this circuit can't be used with gradient based optimizers, one can see a warning that gradients can't be computed. Examples: .. code-block:: from qiskit_machine_learning.circuit.library import RawFeatureVector circuit = RawFeatureVector(4) print(circuit.num_qubits) # prints: 2 print(circuit.draw(output='text')) # prints: # ┌───────────────────────────────────────────────┐ # q_0: ┤0 ├ # │ PARAMETERIZEDINITIALIZE(x[0],x[1],x[2],x[3]) │ # q_1: ┤1 ├ # └───────────────────────────────────────────────┘ print(circuit.ordered_parameters) # prints: [Parameter(p[0]), Parameter(p[1]), Parameter(p[2]), Parameter(p[3])] import numpy as np state = np.array([1, 0, 0, 1]) / np.sqrt(2) bound = circuit.assign_parameters(state) print(bound.draw()) # prints: # ┌───────────────────────────────────────────────┐ # q_0: ┤0 ├ # │ PARAMETERIZEDINITIALIZE(0.70711,0,0,0.70711) │ # q_1: ┤1 ├ # └───────────────────────────────────────────────┘ """ def __init__(self, feature_dimension: Optional[int]) -> None: """ Args: feature_dimension: The feature dimension from which the number of qubits is inferred as ``n_qubits = log2(feature_dim)`` """ super().__init__() self._ordered_parameters = ParameterVector("x") if feature_dimension is not None: self.feature_dimension = feature_dimension def _build(self): super()._build() placeholder = ParameterizedInitialize(self._ordered_parameters[:]) self.append(placeholder, self.qubits) def _unsorted_parameters(self): if self.data is None: self._build() return super()._unsorted_parameters() def _check_configuration(self, raise_on_failure=True): if isinstance(self._ordered_parameters, ParameterVector): self._ordered_parameters.resize(self.feature_dimension) elif len(self._ordered_parameters) != self.feature_dimension: if raise_on_failure: raise ValueError( "Mismatching number of parameters and feature dimension.") return False return True @property def num_qubits(self) -> int: """Returns the number of qubits in this circuit. Returns: The number of qubits. """ return super().num_qubits @num_qubits.setter def num_qubits(self, num_qubits: int) -> None: """Set the number of qubits for the n-local circuit. Args: The new number of qubits. """ if self.num_qubits != num_qubits: # invalidate the circuit self._invalidate() self.qregs: List[QuantumRegister] = [] if num_qubits is not None and num_qubits > 0: self.qregs = [QuantumRegister(num_qubits, name="q")] @property def feature_dimension(self) -> int: """Return the feature dimension. Returns: The feature dimension, which is ``2 ** num_qubits``. """ return 2**self.num_qubits @feature_dimension.setter def feature_dimension(self, feature_dimension: int) -> None: """Set the feature dimension. Args: feature_dimension: The new feature dimension. Must be a power of 2. Raises: ValueError: If ``feature_dimension`` is not a power of 2. """ num_qubits = np.log2(feature_dimension) if int(num_qubits) != num_qubits: raise ValueError("feature_dimension must be a power of 2!") if num_qubits != self.num_qubits: self._invalidate() self.num_qubits = int(num_qubits)
def _build(self): if self._data is not None: return self._check_configuration() self._data = [] # get the evolved operators as circuits from qiskit.opflow import PauliOp coeff = Parameter("c") circuits = [] is_evolved_operator = [] for op in self.operators: # if the operator is already the evolved circuit just append it if isinstance(op, QuantumCircuit): circuits.append(op) is_evolved_operator.append(False) # has no time coeff else: # check if the operator is just the identity, if yes, skip it if isinstance(op, PauliOp): sig_qubits = np.logical_or(op.primitive.x, op.primitive.z) if sum(sig_qubits) == 0: continue evolved_op = self.evolution.convert( (coeff * op).exp_i()).reduce() circuits.append(evolved_op.to_circuit()) is_evolved_operator.append(True) # has time coeff # set the registers num_qubits = circuits[0].num_qubits try: qr = QuantumRegister(num_qubits, "q") self.add_register(qr) except CircuitError: # the register already exists, probably because of a previous composition pass # build the circuit times = ParameterVector("t", self.reps * sum(is_evolved_operator)) times_it = iter(times) first = True for _ in range(self.reps): for is_evolved, circuit in zip(is_evolved_operator, circuits): if first: first = False else: if self._insert_barriers: self.barrier() if is_evolved: bound = circuit.assign_parameters({coeff: next(times_it)}) else: bound = circuit self.compose(bound, inplace=True) if self.initial_state: self.compose(self.initial_state, front=True, inplace=True)
class TestNLocal(QiskitTestCase): """Test the n-local circuit class.""" def test_if_reps_is_negative(self): """Test to check if error is raised for negative value of reps""" with self.assertRaises(ValueError): _ = NLocal(reps=-1) def test_if_reps_is_str(self): """Test to check if proper error is raised for str value of reps""" with self.assertRaises(TypeError): _ = NLocal(reps="3") def test_if_reps_is_float(self): """Test to check if proper error is raised for float value of reps""" with self.assertRaises(TypeError): _ = NLocal(reps=5.6) def test_if_reps_is_npint32(self): """Equality test for reps with int value and np.int32 value""" self.assertEqual(NLocal(reps=3), NLocal(reps=np.int32(3))) def test_if_reps_is_npint64(self): """Equality test for reps with int value and np.int64 value""" self.assertEqual(NLocal(reps=3), NLocal(reps=np.int64(3))) def test_reps_setter_when_negative(self): """Test to check if setter raises error for reps < 0""" nlocal = NLocal(reps=1) with self.assertRaises(ValueError): nlocal.reps = -1 def assertCircuitEqual(self, qc1, qc2, visual=False, transpiled=True): """An equality test specialized to circuits.""" if transpiled: basis_gates = ["id", "u1", "u3", "cx"] qc1_transpiled = transpile(qc1, basis_gates=basis_gates, optimization_level=0) qc2_transpiled = transpile(qc2, basis_gates=basis_gates, optimization_level=0) qc1, qc2 = qc1_transpiled, qc2_transpiled if visual: self.assertEqual(qc1.draw(), qc2.draw()) else: self.assertEqual(qc1, qc2) def test_empty_nlocal(self): """Test the creation of an empty NLocal.""" nlocal = NLocal() self.assertEqual(nlocal.num_qubits, 0) self.assertEqual(nlocal.num_parameters_settable, 0) self.assertEqual(nlocal.reps, 1) self.assertEqual(nlocal, QuantumCircuit()) for attribute in [nlocal.rotation_blocks, nlocal.entanglement_blocks]: self.assertEqual(len(attribute), 0) @data( (XGate(), [[0], [2], [1]]), (XGate(), [[0]]), (CRXGate(-0.2), [[2, 0], [1, 3]]), ) @unpack def test_add_layer_to_empty_nlocal(self, block, entangler_map): """Test appending gates to an empty nlocal.""" nlocal = NLocal() nlocal.add_layer(block, entangler_map) max_num_qubits = max(max(indices) for indices in entangler_map) reference = QuantumCircuit(max_num_qubits + 1) for indices in entangler_map: reference.append(block, indices) self.assertCircuitEqual(nlocal, reference) @data([5, 3], [1, 5], [1, 1], [1, 2, 3, 10]) def test_append_circuit(self, num_qubits): """Test appending circuits to an nlocal works normally.""" # fixed depth of 3 gates per circuit depth = 3 # keep track of a reference circuit reference = QuantumCircuit(max(num_qubits)) # construct the NLocal from the first circuit first_circuit = random_circuit(num_qubits[0], depth, seed=4200) # TODO Terra bug: if this is to_gate it fails, since the QC adds an instruction not gate nlocal = NLocal(max(num_qubits), entanglement_blocks=first_circuit.to_instruction(), reps=1) reference.append(first_circuit, list(range(num_qubits[0]))) # append the rest for num in num_qubits[1:]: circuit = random_circuit(num, depth, seed=4200) nlocal.append(circuit, list(range(num))) reference.append(circuit, list(range(num))) self.assertCircuitEqual(nlocal, reference) @data([5, 3], [1, 5], [1, 1], [1, 2, 3, 10]) def test_add_nlocal(self, num_qubits): """Test adding an nlocal to an nlocal (using add_layer).""" # fixed depth of 3 gates per circuit depth = 3 # keep track of a reference circuit reference = QuantumCircuit(max(num_qubits)) # construct the NLocal from the first circuit first_circuit = random_circuit(num_qubits[0], depth, seed=4220) # TODO Terra bug: if this is to_gate it fails, since the QC adds an instruction not gate nlocal = NLocal(max(num_qubits), entanglement_blocks=first_circuit.to_instruction(), reps=1) reference.append(first_circuit, list(range(num_qubits[0]))) # append the rest for num in num_qubits[1:]: circuit = random_circuit(num, depth, seed=4220) nlocal.add_layer(NLocal(num, entanglement_blocks=circuit, reps=1)) reference.append(circuit, list(range(num))) self.assertCircuitEqual(nlocal, reference) @unittest.skip("Feature missing") def test_iadd_overload(self): """Test the overloaded + operator.""" num_qubits, depth = 2, 2 # construct two circuits for adding first_circuit = random_circuit(num_qubits, depth, seed=4242) circuit = random_circuit(num_qubits, depth, seed=4242) # get a reference reference = first_circuit + circuit # convert the object to be appended to different types others = [ circuit, circuit.to_instruction(), circuit.to_gate(), NLocal(circuit) ] # try adding each type for other in others: nlocal = NLocal(num_qubits, entanglement_blocks=first_circuit, reps=1) nlocal += other with self.subTest(msg=f"type: {type(other)}"): self.assertCircuitEqual(nlocal, reference) def test_parameter_getter_from_automatic_repetition(self): """Test getting and setting of the nlocal parameters.""" circuit = QuantumCircuit(2) circuit.ry(Parameter("a"), 0) circuit.crx(Parameter("b"), 0, 1) # repeat circuit and check that parameters are duplicated reps = 3 nlocal = NLocal(2, entanglement_blocks=circuit, reps=reps) self.assertTrue(nlocal.num_parameters, 6) self.assertTrue(len(nlocal.parameters), 6) @data(list(range(6)), ParameterVector("θ", length=6), [0, 1, Parameter("theta"), 3, 4, 5]) def test_parameter_setter_from_automatic_repetition(self, params): """Test getting and setting of the nlocal parameters.""" circuit = QuantumCircuit(2) circuit.ry(Parameter("a"), 0) circuit.crx(Parameter("b"), 0, 1) # repeat circuit and check that parameters are duplicated reps = 3 nlocal = NLocal(2, entanglement_blocks=circuit, reps=reps) nlocal.assign_parameters(params, inplace=True) param_set = {p for p in params if isinstance(p, ParameterExpression)} with self.subTest( msg="Test the parameters of the non-transpiled circuit"): # check the parameters of the final circuit self.assertEqual(nlocal.parameters, param_set) with self.subTest(msg="Test the parameters of the transpiled circuit"): basis_gates = ["id", "u1", "u2", "u3", "cx"] transpiled_circuit = transpile(nlocal, basis_gates=basis_gates) self.assertEqual(transpiled_circuit.parameters, param_set) @data(list(range(6)), ParameterVector("θ", length=6), [0, 1, Parameter("theta"), 3, 4, 5]) def test_parameters_setter(self, params): """Test setting the parameters via list.""" # construct circuit with some parameters initial_params = ParameterVector("p", length=6) circuit = QuantumCircuit(1) for i, initial_param in enumerate(initial_params): circuit.ry(i * initial_param, 0) # create an NLocal from the circuit and set the new parameters nlocal = NLocal(1, entanglement_blocks=circuit, reps=1) nlocal.assign_parameters(params, inplace=True) param_set = {p for p in params if isinstance(p, ParameterExpression)} with self.subTest( msg="Test the parameters of the non-transpiled circuit"): # check the parameters of the final circuit self.assertEqual(nlocal.parameters, param_set) with self.subTest(msg="Test the parameters of the transpiled circuit"): basis_gates = ["id", "u1", "u2", "u3", "cx"] transpiled_circuit = transpile(nlocal, basis_gates=basis_gates) self.assertEqual(transpiled_circuit.parameters, param_set) def test_repetetive_parameter_setting(self): """Test alternate setting of parameters and circuit construction.""" x = Parameter("x") circuit = QuantumCircuit(1) circuit.rx(x, 0) nlocal = NLocal(1, entanglement_blocks=circuit, reps=3, insert_barriers=True) with self.subTest(msg="immediately after initialization"): self.assertEqual(len(nlocal.parameters), 3) with self.subTest(msg="after circuit construction"): self.assertEqual(len(nlocal.parameters), 3) q = Parameter("q") nlocal.assign_parameters([x, q, q], inplace=True) with self.subTest(msg="setting parameter to Parameter objects"): self.assertEqual(nlocal.parameters, set({x, q})) nlocal.assign_parameters([0, -1], inplace=True) with self.subTest(msg="setting parameter to numbers"): self.assertEqual(nlocal.parameters, set()) def test_skip_unentangled_qubits(self): """Test skipping the unentangled qubits.""" num_qubits = 6 entanglement_1 = [[0, 1, 3], [1, 3, 5], [0, 1, 5]] skipped_1 = [2, 4] entanglement_2 = [entanglement_1, [[0, 1, 2], [2, 3, 5]]] skipped_2 = [4] for entanglement, skipped in zip([entanglement_1, entanglement_2], [skipped_1, skipped_2]): with self.subTest(entanglement=entanglement, skipped=skipped): nlocal = NLocal( num_qubits, rotation_blocks=XGate(), entanglement_blocks=CCXGate(), entanglement=entanglement, reps=3, skip_unentangled_qubits=True, ) skipped_set = {nlocal.qubits[i] for i in skipped} dag = circuit_to_dag(nlocal) idle = set(dag.idle_wires()) self.assertEqual(skipped_set, idle) @data("linear", "full", "circular", "sca", ["linear", "full"], ["circular", "linear", "sca"]) def test_entanglement_by_str(self, entanglement): """Test setting the entanglement of the layers by str.""" reps = 3 nlocal = NLocal( 5, rotation_blocks=XGate(), entanglement_blocks=CCXGate(), entanglement=entanglement, reps=reps, ) def get_expected_entangler_map(rep_num, mode): if mode == "linear": return [(0, 1, 2), (1, 2, 3), (2, 3, 4)] elif mode == "full": return [ (0, 1, 2), (0, 1, 3), (0, 1, 4), (0, 2, 3), (0, 2, 4), (0, 3, 4), (1, 2, 3), (1, 2, 4), (1, 3, 4), (2, 3, 4), ] else: circular = [(3, 4, 0), (0, 1, 2), (1, 2, 3), (2, 3, 4)] if mode == "circular": return circular sca = circular[-rep_num:] + circular[:-rep_num] if rep_num % 2 == 1: sca = [tuple(reversed(indices)) for indices in sca] return sca for rep_num in range(reps): entangler_map = nlocal.get_entangler_map(rep_num, 0, 3) if isinstance(entanglement, list): mode = entanglement[rep_num % len(entanglement)] else: mode = entanglement expected = get_expected_entangler_map(rep_num, mode) with self.subTest(rep_num=rep_num): # using a set here since the order does not matter self.assertEqual(set(entangler_map), set(expected)) def test_pairwise_entanglement(self): """Test pairwise entanglement.""" nlocal = NLocal( 5, rotation_blocks=XGate(), entanglement_blocks=CXGate(), entanglement="pairwise", reps=1, ) entangler_map = nlocal.get_entangler_map(0, 0, 2) pairwise = [(0, 1), (2, 3), (1, 2), (3, 4)] self.assertEqual(pairwise, entangler_map) def test_pairwise_entanglement_raises(self): """Test choosing pairwise entanglement raises an error for too large blocks.""" nlocal = NLocal(3, XGate(), CCXGate(), entanglement="pairwise", reps=1) # pairwise entanglement is only defined if the entangling gate has 2 qubits with self.assertRaises(ValueError): print(nlocal.draw()) def test_entanglement_by_list(self): """Test setting the entanglement by list. This is the circuit we test (times 2, with final X layer) ┌───┐ ┌───┐┌───┐ ┌───┐ q_0: |0>┤ X ├──■────■───X────┤ X ├┤ X ├──■───X─────── .. ┤ X ├ ├───┤ │ │ │ ├───┤└─┬─┘ │ │ ├───┤ q_1: |0>┤ X ├──■────┼───┼──X─┤ X ├──■────┼───X──X──── .. ┤ X ├ ├───┤┌─┴─┐ │ │ │ ├───┤ │ │ │ x2 ├───┤ q_2: |0>┤ X ├┤ X ├──■───┼──X─┤ X ├──■────■──────X──X─ .. ┤ X ├ ├───┤└───┘┌─┴─┐ │ ├───┤ ┌─┴─┐ │ ├───┤ q_3: |0>┤ X ├─────┤ X ├─X────┤ X ├─────┤ X ├───────X─ .. ┤ X ├ └───┘ └───┘ └───┘ └───┘ └───┘ """ circuit = QuantumCircuit(4) for _ in range(2): circuit.x([0, 1, 2, 3]) circuit.barrier() circuit.ccx(0, 1, 2) circuit.ccx(0, 2, 3) circuit.swap(0, 3) circuit.swap(1, 2) circuit.barrier() circuit.x([0, 1, 2, 3]) circuit.barrier() circuit.ccx(2, 1, 0) circuit.ccx(0, 2, 3) circuit.swap(0, 1) circuit.swap(1, 2) circuit.swap(2, 3) circuit.barrier() circuit.x([0, 1, 2, 3]) layer_1_ccx = [(0, 1, 2), (0, 2, 3)] layer_1_swap = [(0, 3), (1, 2)] layer_1 = [layer_1_ccx, layer_1_swap] layer_2_ccx = [(2, 1, 0), (0, 2, 3)] layer_2_swap = [(0, 1), (1, 2), (2, 3)] layer_2 = [layer_2_ccx, layer_2_swap] entanglement = [layer_1, layer_2] nlocal = NLocal( 4, rotation_blocks=XGate(), entanglement_blocks=[CCXGate(), SwapGate()], reps=4, entanglement=entanglement, insert_barriers=True, ) self.assertCircuitEqual(nlocal, circuit) def test_initial_state_as_circuit_object(self): """Test setting `initial_state` to `QuantumCircuit` object""" ref = QuantumCircuit(2) ref.cx(0, 1) ref.x(0) ref.h(1) ref.x(1) ref.cx(0, 1) ref.x(0) ref.x(1) qc = QuantumCircuit(2) qc.cx(0, 1) qc.h(1) expected = NLocal( num_qubits=2, rotation_blocks=XGate(), entanglement_blocks=CXGate(), initial_state=qc, reps=1, ) self.assertCircuitEqual(ref, expected)
def _build(self): if self._data is not None: return self._check_configuration() self._data = [] # get the evolved operators as circuits from qiskit.opflow import PauliOp coeff = Parameter("c") circuits = [] is_evolved_operator = [] for op in self.operators: # if the operator is already the evolved circuit just append it if isinstance(op, QuantumCircuit): circuits.append(op) is_evolved_operator.append(False) # has no time coeff else: # check if the operator is just the identity, if yes, skip it if isinstance(op, PauliOp): sig_qubits = np.logical_or(op.primitive.x, op.primitive.z) if sum(sig_qubits) == 0: continue evolved_op = self.evolution.convert((coeff * op).exp_i()).reduce() circuits.append(evolved_op.to_circuit()) is_evolved_operator.append(True) # has time coeff # set the registers num_qubits = circuits[0].num_qubits try: qr = QuantumRegister(num_qubits, "q") self.add_register(qr) except CircuitError: # the register already exists, probably because of a previous composition pass # build the circuit times = ParameterVector("t", self.reps * sum(is_evolved_operator)) times_it = iter(times) evolution = QuantumCircuit(*self.qregs, name=self.name) first = True for _ in range(self.reps): for is_evolved, circuit in zip(is_evolved_operator, circuits): if first: first = False else: if self._insert_barriers: evolution.barrier() if is_evolved: bound = circuit.assign_parameters({coeff: next(times_it)}) else: bound = circuit evolution.compose(bound, inplace=True) if self.initial_state: evolution.compose(self.initial_state, front=True, inplace=True) # cast global phase to float if it has no free parameters if isinstance(evolution.global_phase, ParameterExpression): try: evolution.global_phase = float(evolution.global_phase._symbol_expr) # RuntimeError is raised if symengine is used, for SymPy it is a TypeError except (RuntimeError, TypeError): # expression contains free parameters pass try: instr = evolution.to_gate() except QiskitError: instr = evolution.to_instruction() self.append(instr, self.qubits)
def __init__(self, initial_state: InitialState, operator: Optional[BaseOperator] = None, q: Optional[float] = 0.5, num_ancillae: Optional[int] = 0, var_form: Optional[VariationalForm] = None, optimizer: Optional[Optimizer] = None, initial_point: Optional[np.ndarray] = None, max_evals_grouped: int = 1, callback: Optional[Callable[[int, np.ndarray, float, float], None]] = None, quantum_instance: Optional[Union[QuantumInstance, BaseBackend]] = None) -> None: """ Constructor. Args: initial_state (InitialState): The state to be diagonalized operator (BaseOperator): The density matrix of the initial state q (int): Free parameter that ones to tailer the VQSD method num_ancillae (int): The number of ancillae qubits if the initial state is a mixed state var_form: A parameterized variational form (ansatz). optimizer: A classical optimizer. initial_point: An optional initial point (i.e. initial parameter values) for the optimizer. If ``None`` then VQE will look to the variational form for a preferred point and if not will simply compute a random one. max_evals_grouped: Max number of evaluations performed simultaneously. Signals the given optimizer that more than one set of parameters can be supplied so that potentially the expectation values can be computed in parallel. Typically this is possible when a finite difference gradient is used by the optimizer such that multiple points to compute the gradient can be passed and if computed in parallel improve overall execution time. callback: a callback that can access the intermediate data during the optimization. Four parameter values are passed to the callback as follows during each evaluation by the optimizer for its current set of parameters as it works towards the minimum. These are: the evaluation count, the optimizer parameters for the variational form, the evaluated global cost, local cost and weighted cost quantum_instance: Quantum Instance or Backend """ validate_min('max_evals_grouped', max_evals_grouped, 1) validate_range('num_ancillae', num_ancillae, 0, initial_state._num_qubits - 1) validate_range('q', q, 0.0, 1.0) if var_form is None: # TODO after ansatz refactor num qubits can be set later so we do not have to have # an operator to create a default if initial_state is not None: var_form = RY(initial_state._num_qubits - num_ancillae) if optimizer is None: optimizer = SLSQP() if operator is None: initial_state_vector = initial_state.construct_circuit(mode='vector') mat = np.outer(initial_state_vector, np.conj(initial_state_vector)) operator = DensityMatrix(mat) # TODO after ansatz refactor we may still not be able to do this # if num qubits is not set on var form if initial_point is None and var_form is not None: initial_point = var_form.preferred_init_points self._max_evals_grouped = max_evals_grouped super().__init__(var_form=var_form, optimizer=optimizer, cost_fn=self._cost_evaluation, initial_point=initial_point, quantum_instance=quantum_instance) self._callback = callback self._use_simulator_snapshot_mode = None self._ret = None self._eval_time = None self._eval_count = 0 logger.info(self.print_settings()) self._var_form_params = None if self.var_form is not None: self._var_form_params = ParameterVector('θ', self.var_form.num_parameters) self._parameterized_circuits = None self._initial_state = initial_state self._q = q self._num_ancillae = num_ancillae self._num_working_qubits = initial_state._num_qubits - num_ancillae self._operator = operator self.initial_state = initial_state # TODO : Verify that if the ancillae qubits form an orthonormal basis # Compute state purity if self._num_ancillae > 0: # pylint: disable=import-outside-toplevel from qiskit.quantum_info import purity, partial_trace rho = self._operator.data ancillae_idx = list(set(range(self._initial_state._num_qubits)) - set(range(self._num_ancillae))) self._operator = partial_trace(rho, ancillae_idx) self._purity = purity(self._operator) else: self._purity = 1.0
def var_form(self, var_form: VariationalForm): """ Sets variational form """ self._var_form = var_form if var_form: self._var_form_params = ParameterVector('θ', var_form.num_parameters)
def __init__(self,name,layer): self.backend = Aer.get_backend('statevector_simulator'); self.circ = QuantumCircuit(layer); self.name = name; self.seed = 14256; self.layer = layer; np.random.seed(self.seed); if self.name == "rcz": self.theta = Parameter('θ'); for index in range(layer): self.circ.h(index); c = QuantumCircuit(1,name="Rz"); c.rz(self.theta,0); temp = c.to_gate().control(1); self.circ.append(temp,[0,1]); if self.name == "circ4": self.theta1 = ParameterVector('θ1', layer); self.theta2 = ParameterVector('θ2', layer); self.theta3 = ParameterVector('θ3', layer-1); # print(self.theta1) for i in range(self.layer): self.circ.rx(self.theta1[i],i); for i in range(self.layer): self.circ.rz(self.theta2[i],i); for i in range(self.layer-1): c = QuantumCircuit(1, name="Rx"); c.rx(self.theta3[i],0); temp = c.to_gate().control(1); self.circ.append(temp,[i,i+1]); if self.name == "circ4c": self.theta1 = ParameterVector('θ1', layer); self.theta2 = ParameterVector('θ2', layer); self.theta3 = ParameterVector('θ3', layer); # print(self.theta1) for i in range(self.layer): self.circ.rx(self.theta1[i],i); for i in range(self.layer): self.circ.rz(self.theta2[i],i); for i in range(self.layer-1): c = QuantumCircuit(1, name="Rx"); c.rx(self.theta3[i],0); temp = c.to_gate().control(1); self.circ.append(temp,[i,i+1]); c = QuantumCircuit(1, name="Rx"); c.rx(self.theta3[self.layer-1],0); temp = c.to_gate().control(1); self.circ.append(temp,[self.layer-1,0]); if self.name == "circ19": self.theta1 = ParameterVector('θ1', 3*layer); for i in range(self.layer): self.circ.rx(self.theta1[i],i); for i in range(self.layer): self.circ.rz(self.theta1[self.layer+i],i); for i in range(self.layer-1): c = QuantumCircuit(1, name="Rx"); c.rx(self.theta1[self.layer*2+i],0); temp = c.to_gate().control(1); self.circ.append(temp,[i,i+1]); c = QuantumCircuit(1, name="Rx"); c.rx(self.theta1[self.layer*3-1],0); temp = c.to_gate().control(1); self.circ.append(temp,[self.layer-1,0]); if self.name == "2circ19": self.theta1 = ParameterVector('θ1', 3*layer); self.theta2 = ParameterVector('θ2', 3*layer); for i in range(self.layer): self.circ.rx(self.theta1[i],i); for i in range(self.layer): self.circ.rz(self.theta1[self.layer+i],i); for i in range(self.layer-1): c = QuantumCircuit(1, name="Rx"); c.rx(self.theta1[self.layer*2+i],0); temp = c.to_gate().control(1); self.circ.append(temp,[i,i+1]); c = QuantumCircuit(1, name="Rx"); c.rx(self.theta1[self.layer*3-1],0); temp = c.to_gate().control(1); self.circ.append(temp,[self.layer-1,0]); # self.circ.barrier(); for i in range(self.layer): self.circ.rx(self.theta2[i],i); for i in range(self.layer): self.circ.rz(self.theta2[self.layer+i],i); for i in range(self.layer-1): c = QuantumCircuit(1, name="Rx"); c.rx(self.theta2[self.layer*2+i],0); temp = c.to_gate().control(1); self.circ.append(temp,[i,i+1]); c = QuantumCircuit(1, name="Rx"); c.rx(self.theta2[self.layer*3-1],0); temp = c.to_gate().control(1); self.circ.append(temp,[self.layer-1,0]); if self.name == "2circ19x": self.theta1 = ParameterVector('θ1', 3*layer); self.theta2 = ParameterVector('θ2', 3*layer); for i in range(self.layer): self.circ.rx(self.theta1[i],i); for i in range(self.layer): self.circ.rz(self.theta1[self.layer+i],i); for i in range(self.layer-1): c = QuantumCircuit(1, name="Rx"); c.rx(self.theta1[self.layer*2+i],0); temp = c.to_gate().control(1); self.circ.append(temp,[i,i+1]); c = QuantumCircuit(1, name="Rx"); c.rx(self.theta1[self.layer*3-1],0); temp = c.to_gate().control(1); self.circ.append(temp,[self.layer-1,0]); # self.circ.barrier(); for i in range(self.layer): self.circ.x(i); # self.circ.barrier(); for i in range(self.layer): self.circ.rx(self.theta2[i],i); for i in range(self.layer): self.circ.rz(self.theta2[self.layer+i],i); for i in range(self.layer-1): c = QuantumCircuit(1, name="Rx"); c.rx(self.theta2[self.layer*2+i],0); temp = c.to_gate().control(1); self.circ.append(temp,[i,i+1]); c = QuantumCircuit(1, name="Rx"); c.rx(self.theta2[self.layer*3-1],0); temp = c.to_gate().control(1); self.circ.append(temp,[self.layer-1,0]); if self.name == "circ6": self.theta1 = ParameterVector('θ1', layer); self.theta2 = ParameterVector('θ2', layer); self.theta3 = ParameterVector('θ3', layer*(layer-1)); self.theta4 = ParameterVector('θ4', layer); self.theta5 = ParameterVector('θ5', layer); # print(self.theta1) for i in range(self.layer): self.circ.rx(self.theta1[i],i); for i in range(self.layer): self.circ.rz(self.theta2[i],i); for i in range(self.layer): for j in range(self.layer): if i<j: c = QuantumCircuit(1, name="Rx"); c.rx(self.theta3[(self.layer-1)*i+j-1],0); temp = c.to_gate().control(1); self.circ.append(temp,[i,j]); elif i>j: c = QuantumCircuit(1, name="Rx"); c.rx(self.theta3[(self.layer-1)*i+j],0); temp = c.to_gate().control(1); self.circ.append(temp,[i,j]); self.circ.barrier(); for i in range(self.layer): self.circ.rx(self.theta4[i],i); for i in range(self.layer): self.circ.rz(self.theta5[i],i); if self.name == "circ19half": self.theta1 = ParameterVector('θ1', layer); self.theta2 = ParameterVector('θ2', layer); self.theta3 = ParameterVector('θ3', layer); # print(self.theta1) for i in range(self.layer): self.circ.rx(self.theta1[i],i); for i in range(self.layer): self.circ.rz(self.theta2[i],i); for i in range(self.layer-1): c = QuantumCircuit(1, name="Rx"); c.rx(self.theta3[i]/2,0); temp = c.to_gate().control(1); self.circ.append(temp,[i,i+1]); c = QuantumCircuit(1, name="Rx"); c.rx(self.theta3[self.layer-1]/2,0); temp = c.to_gate().control(1); self.circ.append(temp,[self.layer-1,0]); for i in range(self.layer-1): c = QuantumCircuit(1, name="Rx"); c.rx(self.theta3[i]/2,0); temp = c.to_gate().control(1); self.circ.append(temp,[i,i+1]); c = QuantumCircuit(1, name="Rx"); c.rx(self.theta3[self.layer-1]/2,0); temp = c.to_gate().control(1); self.circ.append(temp,[self.layer-1,0]); if self.name == "circ4ca": self.theta1 = ParameterVector('θ1', layer); self.theta2 = ParameterVector('θ2', layer); self.theta3 = ParameterVector('θ3', layer); self.theta4 = ParameterVector('θ4', layer); self.theta5 = ParameterVector('θ5', layer); # print(self.theta1) for i in range(self.layer): self.circ.rx(self.theta1[i],i); for i in range(self.layer): self.circ.rz(self.theta2[i],i); for i in range(self.layer-1): c = QuantumCircuit(1, name="Rx"); c.rx(self.theta3[i],0); temp = c.to_gate().control(1); self.circ.append(temp,[i,i+1]); c = QuantumCircuit(1, name="Rx"); c.rx(self.theta3[self.layer-1],0); temp = c.to_gate().control(1); self.circ.append(temp,[self.layer-1,0]); for i in range(self.layer): self.circ.rx(self.theta4[i],i); for i in range(self.layer): self.circ.rz(self.theta5[i],i); if self.name == "V": self.theta = ParameterVector('θ',layer); self.phi = ParameterVector('φ',layer); self.alpha = ParameterVector('⍺',layer); for i in range(layer): self.circ.h(i); for i in range(layer): V(self.circ,self.theta[i],self.phi[i],self.alpha[i],i); if self.name == "V1": self.theta0 = ParameterVector('θ0',layer); self.theta1 = ParameterVector('θ1',layer); self.theta = ParameterVector('θ',layer); self.phi = ParameterVector('φ',layer); self.alpha = ParameterVector('⍺',layer); for i in range(layer): self.circ.rx(self.theta0[i],i); for i in range(layer): self.circ.rz(self.theta1[i],i); for i in range(layer): V(self.circ,self.theta[i],self.phi[i],self.alpha[i],i); if self.name == "circularcV": self.theta0 = ParameterVector('θ0',layer); self.theta1 = ParameterVector('θ1',layer); self.theta = ParameterVector('θ',layer); self.phi = ParameterVector('φ',layer); self.alpha = ParameterVector('⍺',layer); for i in range(layer): self.circ.rx(self.theta0[i],i); for i in range(layer): self.circ.rz(self.theta1[i],i); for i in range(layer-1): c = QuantumCircuit(1,name="V"); V(c,self.theta[i],self.phi[i],self.alpha[i],0); temp = c.to_gate().control(1); self.circ.append(temp,[i,i+1]); c = QuantumCircuit(1,name="V"); V(c,self.theta[layer-1],self.phi[layer-1],self.alpha[layer-1],0); temp = c.to_gate().control(1); self.circ.append(temp,[layer-1,0]); if self.name == "new0": # U gate가 진짜 uniform한지 확인하기 위함입니다. self.y = ParameterVector('θ_i',layer); self.z = ParameterVector('ϕ_i',layer); self.eta = ParameterVector('η', layer); self.phi = ParameterVector('ϕ', layer); self.t = ParameterVector('t',layer); for i in range(self.layer): self.circ.ry(self.y[i],i); self.circ.rz(self.z[i],i); for i in range(self.layer): self.circ.u3(self.eta[i],self.phi[i],self.t[i],i); if self.name == "new1": self.eta = ParameterVector('η', layer-1); self.phi = ParameterVector('ϕ', layer-1); self.t = ParameterVector('t',layer-1); for i in range(self.layer): self.circ.h(i); for i in range(self.layer-1): c = QuantumCircuit(1,name="U"); # 1 qubit짜리 회로를 생성합니다 c.u3(self.eta[i],self.phi[i],self.t[i],0); # U gate를 적용시킵니다. temp = c.to_gate().control(1); # 윗 두 줄에서 만든 회로를 1개의 qubit이 control할 것이라고 말해줍니다 self.circ.append(temp,[i,i+1]); # 원래의 회로에 위치를 지정해서 추가합니다 if self.name == "new2": self.eta1 = ParameterVector('η1', layer-1); self.phi1 = ParameterVector('ϕ1', layer-1); self.t1 = ParameterVector('t1',layer-1); self.eta2 = ParameterVector('η2', layer-1); self.phi2 = ParameterVector('ϕ2', layer-1); self.t2 = ParameterVector('t2',layer-1); for i in range(self.layer): self.circ.h(i); for i in range(self.layer-1): c = QuantumCircuit(1,name="U"); c.u3(self.eta1[i],self.phi1[i],self.t1[i],0); temp = c.to_gate().control(1); self.circ.append(temp,[i,i+1]); for i in range(self.layer-1): c = QuantumCircuit(1,name="U"); c.u3(self.eta2[i],self.phi2[i],self.t2[i],0); temp = c.to_gate().control(1); self.circ.append(temp,[i,i+1]); if self.name == "new3": self.eta = ParameterVector('η', layer); self.phi = ParameterVector('ϕ', layer); self.t = ParameterVector('t',layer); for i in range(self.layer): self.circ.h(i); for i in range(self.layer-1): c = QuantumCircuit(1,name="U"); c.u3(self.eta[i],self.phi[i],self.t[i],0); temp = c.to_gate().control(1); self.circ.append(temp,[i,i+1]); c = QuantumCircuit(1,name="U"); c.u3(self.eta[self.layer-1],self.phi[self.layer-1],self.t[self.layer-1],0); temp = c.to_gate().control(1); self.circ.append(temp,[self.layer-1,0]); if self.name == "new4": self.eta1 = ParameterVector('η1', layer); self.phi1 = ParameterVector('ϕ1', layer); self.t1 = ParameterVector('t1',layer); self.eta2 = ParameterVector('η2', layer); self.phi2 = ParameterVector('ϕ2', layer); self.t2 = ParameterVector('t2',layer); for i in range(self.layer): self.circ.h(i); for i in range(self.layer-1): c = QuantumCircuit(1,name="U"); c.u3(self.eta1[i],self.phi1[i],self.t1[i],0); temp = c.to_gate().control(1); self.circ.append(temp,[i,i+1]); c = QuantumCircuit(1,name="U"); c.u3(self.eta1[self.layer-1],self.phi1[self.layer-1],self.t1[self.layer-1],0); temp = c.to_gate().control(1); self.circ.append(temp,[self.layer-1,0]); for i in range(self.layer-1): c = QuantumCircuit(1,name="U"); c.u3(self.eta2[i],self.phi2[i],self.t2[i],0); temp = c.to_gate().control(1); self.circ.append(temp,[i,i+1]); c = QuantumCircuit(1,name="U"); c.u3(self.eta2[self.layer-1],self.phi2[self.layer-1],self.t2[self.layer-1],0); temp = c.to_gate().control(1); self.circ.append(temp,[self.layer-1,0]); if self.name == "new5": self.y = ParameterVector('θ_i',layer); self.z = ParameterVector('ϕ_i',layer); self.eta = ParameterVector('η', layer); self.phi = ParameterVector('ϕ', layer); self.t = ParameterVector('t',layer); for i in range(self.layer): self.circ.ry(self.y[i],i); self.circ.rz(self.z[i],i); for i in range(self.layer-1): c = QuantumCircuit(1,name="U"); c.u3(self.eta[i],self.phi[i],self.t[i],0); temp = c.to_gate().control(1); self.circ.append(temp,[i,i+1]); c = QuantumCircuit(1,name="U"); c.u3(self.eta[self.layer-1],self.phi[self.layer-1],self.t[self.layer-1],0); temp = c.to_gate().control(1); self.circ.append(temp,[self.layer-1,0]); if self.name == "new6": self.eta1 = ParameterVector('η1', layer-1); self.phi1 = ParameterVector('ϕ1', layer-1); self.t1 = ParameterVector('t1',layer-1); for i in range(self.layer): self.circ.h(i); for i in range(self.layer-1): self.circ.cx(i,i+1); for i in range(self.layer-1): c = QuantumCircuit(1,name="U"); c.u3(self.eta1[i],self.phi1[i],self.t1[i],0); temp = c.to_gate().control(1); self.circ.append(temp,[i,i+1]);
def test_save_and_load_model(self, use_circuits): """ save and load model test """ aqua_globals.random_seed = self.seed backend = BasicAer.get_backend('qasm_simulator') num_qubits = 2 optimizer = SPSA(max_trials=10, save_steps=1, c0=4.0, skip_calibration=True) feature_map = SecondOrderExpansion(feature_dimension=num_qubits, depth=2) var_form = RYRZ(num_qubits=num_qubits, depth=3) # convert to circuit if circuits should be used if use_circuits: x = ParameterVector('x', feature_map.feature_dimension) feature_map = feature_map.construct_circuit(x) theta = ParameterVector('theta', var_form.num_parameters) var_form = var_form.construct_circuit(theta) # set up algorithm vqc = VQC(optimizer, feature_map, var_form, self.training_data, self.testing_data) # sort parameters for reproducibility if use_circuits: vqc._feature_map_params = list(x) vqc._var_form_params = list(theta) quantum_instance = QuantumInstance(backend, shots=1024, seed_simulator=self.seed, seed_transpiler=self.seed) result = vqc.run(quantum_instance) np.testing.assert_array_almost_equal(result['opt_params'], self.ref_opt_params, decimal=4) np.testing.assert_array_almost_equal(result['training_loss'], self.ref_train_loss, decimal=8) self.assertEqual(1.0, result['testing_accuracy']) file_path = self.get_resource_path('vqc_test.npz') vqc.save_model(file_path) self.assertTrue(os.path.exists(file_path)) loaded_vqc = VQC(optimizer, feature_map, var_form, self.training_data, None) # sort parameters for reproducibility if use_circuits: loaded_vqc._feature_map_params = list(x) loaded_vqc._var_form_params = list(theta) loaded_vqc.load_model(file_path) np.testing.assert_array_almost_equal(loaded_vqc.ret['opt_params'], self.ref_opt_params, decimal=4) loaded_test_acc = loaded_vqc.test(vqc.test_dataset[0], vqc.test_dataset[1], quantum_instance) self.assertEqual(result['testing_accuracy'], loaded_test_acc) predicted_probs, predicted_labels = loaded_vqc.predict( self.testing_data['A'], quantum_instance) np.testing.assert_array_almost_equal(predicted_probs, self.ref_prediction_a_probs, decimal=8) np.testing.assert_array_equal(predicted_labels, self.ref_prediction_a_label) if os.path.exists(file_path): try: os.remove(file_path) except Exception: # pylint: disable=broad-except pass
def _build(self) -> None: """ Construct the ansatz, given its parameters. Returns: QuantumCircuit: a quantum circuit with given `parameters` Raises: ValueError: only supports single and double excitations at the moment. """ if self._is_built: return super()._build() self.data.clear() parameters = ParameterVector("θ", self._num_parameters) q = self.qubits if isinstance(self._initial_state, QuantumCircuit): self.append(self._initial_state.to_gate(), range(self._initial_state.num_qubits)) count = 0 for _ in range(self._reps): for exc in self.excitations: occ, unocc = exc if len(occ) == 1: i = occ[0] r = unocc[0] self.p(-parameters[count] / 4 + np.pi / 4, q[i]) self.p(-parameters[count] / 4 - np.pi / 4, q[r]) self.h(q[i]) self.h(q[r]) if self._ladder: for qubit in range(i, r): self.cx(q[qubit], q[qubit + 1]) else: self.cx(q[i], q[r]) self.p(parameters[count], q[r]) if self._ladder: for qubit in range(r, i, -1): self.cx(q[qubit - 1], q[qubit]) else: self.cx(q[i], q[r]) self.h(q[i]) self.h(q[r]) self.p(-parameters[count] / 4 - np.pi / 4, q[i]) self.p(-parameters[count] / 4 + np.pi / 4, q[r]) elif len(occ) == 2: i = occ[0] r = unocc[0] j = occ[1] s = unocc[1] # pylint: disable=invalid-name self.sdg(q[r]) self.h(q[i]) self.h(q[r]) self.h(q[j]) self.h(q[s]) if self._ladder: for qubit in range(i, r): self.cx(q[qubit], q[qubit + 1]) self.barrier(q[qubit], q[qubit + 1]) else: self.cx(q[i], q[r]) self.cx(q[r], q[j]) if self._ladder: for qubit in range(j, s): self.cx(q[qubit], q[qubit + 1]) self.barrier(q[qubit], q[qubit + 1]) else: self.cx(q[j], q[s]) self.p(parameters[count], q[s]) if self._ladder: for qubit in range(s, j, -1): self.cx(q[qubit - 1], q[qubit]) self.barrier(q[qubit - 1], q[qubit]) else: self.cx(q[j], q[s]) self.cx(q[r], q[j]) if self._ladder: for qubit in range(r, i, -1): self.cx(q[qubit - 1], q[qubit]) self.barrier(q[qubit - 1], q[qubit]) else: self.cx(q[i], q[r]) self.h(q[i]) self.h(q[r]) self.h(q[j]) self.h(q[s]) self.p(-parameters[count] / 2 + np.pi / 2, q[i]) self.p(-parameters[count] / 2 + np.pi, q[r]) else: raise ValueError( "Limited to single and double excitations, " "higher order is not implemented") count += 1
def get_kernel_matrix(quantum_instance, feature_map, x1_vec, x2_vec=None, enforce_psd=True): """ Construct kernel matrix, if x2_vec is None, self-innerproduct is conducted. Notes: When using `statevector_simulator`, we only build the circuits for Psi(x1)|0> rather than Psi(x2)^dagger Psi(x1)|0>, and then we perform the inner product classically. That is, for `statevector_simulator`, the total number of circuits will be O(N) rather than O(N^2) for `qasm_simulator`. Args: quantum_instance (QuantumInstance): quantum backend with all settings feature_map (FeatureMap): a feature map that maps data to feature space x1_vec (numpy.ndarray): data points, 2-D array, N1xD, where N1 is the number of data, D is the feature dimension x2_vec (numpy.ndarray): data points, 2-D array, N2xD, where N2 is the number of data, D is the feature dimension enforce_psd (bool): enforces that the kernel matrix is positive semi-definite by setting negative eigenvalues to zero. This is only applied in the symmetric case, i.e., if `x2_vec == None`. Returns: numpy.ndarray: 2-D matrix, N1xN2 """ if isinstance(feature_map, QuantumCircuit): use_parameterized_circuits = True else: use_parameterized_circuits = feature_map.support_parameterized_circuit if x2_vec is None: is_symmetric = True x2_vec = x1_vec else: is_symmetric = False is_statevector_sim = quantum_instance.is_statevector measurement = not is_statevector_sim measurement_basis = '0' * feature_map.num_qubits mat = np.ones((x1_vec.shape[0], x2_vec.shape[0])) # get all indices if is_symmetric: mus, nus = np.triu_indices(x1_vec.shape[0], k=1) # remove diagonal term else: mus, nus = np.indices((x1_vec.shape[0], x2_vec.shape[0])) mus = np.asarray(mus.flat) nus = np.asarray(nus.flat) if is_statevector_sim: if is_symmetric: to_be_computed_data = x1_vec else: to_be_computed_data = np.concatenate((x1_vec, x2_vec)) if use_parameterized_circuits: # build parameterized circuits, it could be slower for building circuit # but overall it should be faster since it only transpile one circuit feature_map_params = ParameterVector( 'x', feature_map.feature_dimension) parameterized_circuit = QSVM._construct_circuit( (feature_map_params, feature_map_params), feature_map, measurement, is_statevector_sim=is_statevector_sim) parameterized_circuit = quantum_instance.transpile( parameterized_circuit)[0] circuits = [ parameterized_circuit.assign_parameters( {feature_map_params: x}) for x in to_be_computed_data ] else: # the second x is redundant to_be_computed_data_pair = [(x, x) for x in to_be_computed_data] if logger.isEnabledFor(logging.DEBUG): logger.debug("Building circuits:") TextProgressBar(sys.stderr) circuits = parallel_map( QSVM._construct_circuit, to_be_computed_data_pair, task_args=(feature_map, measurement, is_statevector_sim), num_processes=aqua_globals.num_processes) results = quantum_instance.execute( circuits, had_transpiled=use_parameterized_circuits) if logger.isEnabledFor(logging.DEBUG): logger.debug("Calculating overlap:") TextProgressBar(sys.stderr) offset = 0 if is_symmetric else len(x1_vec) matrix_elements = parallel_map( QSVM._compute_overlap, list(zip(mus, nus + offset)), task_args=(results, is_statevector_sim, measurement_basis), num_processes=aqua_globals.num_processes) for i, j, value in zip(mus, nus, matrix_elements): mat[i, j] = value if is_symmetric: mat[j, i] = mat[i, j] else: for idx in range(0, len(mus), QSVM.BATCH_SIZE): to_be_computed_data_pair = [] to_be_computed_index = [] for sub_idx in range(idx, min(idx + QSVM.BATCH_SIZE, len(mus))): i = mus[sub_idx] j = nus[sub_idx] x1 = x1_vec[i] x2 = x2_vec[j] if not np.all(x1 == x2): to_be_computed_data_pair.append((x1, x2)) to_be_computed_index.append((i, j)) if use_parameterized_circuits: # build parameterized circuits, it could be slower for building circuit # but overall it should be faster since it only transpile one circuit feature_map_params_x = ParameterVector( 'x', feature_map.feature_dimension) feature_map_params_y = ParameterVector( 'y', feature_map.feature_dimension) parameterized_circuit = QSVM._construct_circuit( (feature_map_params_x, feature_map_params_y), feature_map, measurement, is_statevector_sim=is_statevector_sim) parameterized_circuit = quantum_instance.transpile( parameterized_circuit)[0] circuits = [ parameterized_circuit.assign_parameters({ feature_map_params_x: x, feature_map_params_y: y }) for x, y in to_be_computed_data_pair ] else: if logger.isEnabledFor(logging.DEBUG): logger.debug("Building circuits:") TextProgressBar(sys.stderr) circuits = parallel_map( QSVM._construct_circuit, to_be_computed_data_pair, task_args=(feature_map, measurement), num_processes=aqua_globals.num_processes) results = quantum_instance.execute( circuits, had_transpiled=use_parameterized_circuits) if logger.isEnabledFor(logging.DEBUG): logger.debug("Calculating overlap:") TextProgressBar(sys.stderr) matrix_elements = parallel_map( QSVM._compute_overlap, range(len(circuits)), task_args=(results, is_statevector_sim, measurement_basis), num_processes=aqua_globals.num_processes) for (i, j), value in zip(to_be_computed_index, matrix_elements): mat[i, j] = value if is_symmetric: mat[j, i] = mat[i, j] if enforce_psd and is_symmetric and not is_statevector_sim: # Find the closest positive semi-definite approximation to kernel matrix, in case it is # symmetric. The (symmetric) matrix should always be positive semi-definite by # construction, but this can be violated in case of noise, such as sampling noise, thus, # the adjustment is only done if NOT using the statevector simulation. D, U = np.linalg.eig(mat) mat = U @ np.diag(np.maximum(0, D)) @ U.transpose() return mat
def pauli_block(self, pauli_string): """Get the Pauli block for the feature map circuit.""" params = ParameterVector("_", length=len(pauli_string)) time = self._data_map_func(np.asarray(params)) return self.pauli_evolution(pauli_string, time)
def __init__( self, feature_map: Union[QuantumCircuit, FeatureMap], training_dataset: Optional[Dict[str, np.ndarray]] = None, test_dataset: Optional[Dict[str, np.ndarray]] = None, datapoints: Optional[np.ndarray] = None, multiclass_extension: Optional[MulticlassExtension] = None, quantum_instance: Optional[Union[QuantumInstance, BaseBackend, Backend]] = None ) -> None: """ Args: feature_map: Feature map module, used to transform data training_dataset: Training dataset. test_dataset: Testing dataset. datapoints: Prediction dataset. multiclass_extension: If number of classes is greater than 2 then a multiclass scheme must be supplied, in the form of a multiclass extension. quantum_instance: Quantum Instance or Backend Raises: AquaError: Multiclass extension not supplied when number of classes > 2 """ super().__init__(quantum_instance) # check the validity of provided arguments if possible if training_dataset is not None: is_multiclass = get_num_classes(training_dataset) > 2 if is_multiclass: if multiclass_extension is None: raise AquaError('Dataset has more than two classes. ' 'A multiclass extension must be provided.') else: if multiclass_extension is not None: logger.warning( "Dataset has just two classes. " "Supplied multiclass extension will be ignored") self.training_dataset = None self.test_dataset = None self.datapoints = None self.class_to_label = None self.label_to_class = None self.num_classes = None self.setup_training_data(training_dataset) self.setup_test_data(test_dataset) self.setup_datapoint(datapoints) self.feature_map = feature_map self.num_qubits = self.feature_map.num_qubits if isinstance(feature_map, QuantumCircuit): # patch the feature dimension attribute to the circuit self.feature_map.feature_dimension = len(feature_map.parameters) if not hasattr(feature_map, 'ordered_parameters'): self.feature_map.ordered_parameters = sorted( feature_map.parameters, key=lambda p: p.name) self.feature_map_params_x = ParameterVector( 'x', self.feature_map.feature_dimension) self.feature_map_params_y = ParameterVector( 'y', self.feature_map.feature_dimension) else: if not isinstance(feature_map, RawFeatureVector): warnings.warn(""" The {} object as input for the QSVM is deprecated as of 0.7.0 and will be removed no earlier than 3 months after the release. You should pass a QuantumCircuit object instead. See also qiskit.circuit.library.data_preparation for a collection of suitable circuits.""".format(type(feature_map)), DeprecationWarning, stacklevel=2) self.feature_map_params_x = ParameterVector( 'x', feature_map.feature_dimension) self.feature_map_params_y = ParameterVector( 'y', feature_map.feature_dimension) qsvm_instance = None # type: Optional[Union[_QSVM_Binary, _QSVM_Multiclass]] if multiclass_extension is None: qsvm_instance = _QSVM_Binary(self) else: multiclass_extension.set_estimator(_QSVM_Estimator, [feature_map]) qsvm_instance = _QSVM_Multiclass(self, multiclass_extension) self.instance = qsvm_instance
def __init__( self, num_qubits: Optional[int] = None, rotation_blocks: Optional[ Union[QuantumCircuit, List[QuantumCircuit], Instruction, List[Instruction]] ] = None, entanglement_blocks: Optional[ Union[QuantumCircuit, List[QuantumCircuit], Instruction, List[Instruction]] ] = None, entanglement: Optional[Union[List[int], List[List[int]]]] = None, reps: int = 1, insert_barriers: bool = False, parameter_prefix: str = "θ", overwrite_block_parameters: Union[bool, List[List[Parameter]]] = True, skip_final_rotation_layer: bool = False, skip_unentangled_qubits: bool = False, initial_state: Optional[Any] = None, name: Optional[str] = "nlocal", ) -> None: """Create a new n-local circuit. Args: num_qubits: The number of qubits of the circuit. rotation_blocks: The blocks used in the rotation layers. If multiple are passed, these will be applied one after another (like new sub-layers). entanglement_blocks: The blocks used in the entanglement layers. If multiple are passed, these will be applied one after another. To use different entanglements for the sub-layers, see :meth:`get_entangler_map`. entanglement: The indices specifying on which qubits the input blocks act. If None, the entanglement blocks are applied at the top of the circuit. reps: Specifies how often the rotation blocks and entanglement blocks are repeated. insert_barriers: If True, barriers are inserted in between each layer. If False, no barriers are inserted. parameter_prefix: The prefix used if default parameters are generated. overwrite_block_parameters: If the parameters in the added blocks should be overwritten. If False, the parameters in the blocks are not changed. skip_final_rotation_layer: Whether a final rotation layer is added to the circuit. skip_unentangled_qubits: If ``True``, the rotation gates act only on qubits that are entangled. If ``False``, the rotation gates act on all qubits. initial_state: A `QuantumCircuit` object which can be used to describe an initial state prepended to the NLocal circuit. name: The name of the circuit. Examples: TODO Raises: ImportError: If an ``initial_state`` is specified but Qiskit Aqua is not installed. ValueError: If reps parameter is less than or equal to 0. TypeError: If reps parameter is not an int value. """ super().__init__(name=name) self._num_qubits = None self._insert_barriers = insert_barriers self._reps = reps self._entanglement_blocks = [] self._rotation_blocks = [] self._prepended_blocks = [] self._prepended_entanglement = [] self._appended_blocks = [] self._appended_entanglement = [] self._entanglement = None self._entangler_maps = None self._ordered_parameters = ParameterVector(name=parameter_prefix) self._overwrite_block_parameters = overwrite_block_parameters self._skip_final_rotation_layer = skip_final_rotation_layer self._skip_unentangled_qubits = skip_unentangled_qubits self._initial_state, self._initial_state_circuit = None, None self._bounds = None if int(reps) != reps: raise TypeError("The value of reps should be int") if reps < 0: raise ValueError("The value of reps should be larger than or equal to 0") if num_qubits is not None: self.num_qubits = num_qubits if entanglement_blocks is not None: self.entanglement_blocks = entanglement_blocks if rotation_blocks is not None: self.rotation_blocks = rotation_blocks if entanglement is not None: self.entanglement = entanglement if initial_state is not None: self.initial_state = initial_state
def get_loss( self, hamiltonian: OperatorBase, ansatz: QuantumCircuit, dt: float, current_parameters: np.ndarray, ) -> Tuple[Callable[[np.ndarray], float], Optional[Callable[[np.ndarray], np.ndarray]]]: """Get a function to evaluate the infidelity between Trotter step and ansatz. Args: hamiltonian: The Hamiltonian under which to evolve. ansatz: The parameterized quantum circuit which attempts to approximate the time-evolved state. dt: The time step. current_parameters: The current parameters. Returns: A callable to evaluate the infidelity and, if gradients are supported and required, a second callable to evaluate the gradient of the infidelity. """ self._validate_setup(skip={"optimizer"}) # use Trotterization to evolve the current state trotterized = ansatz.bind_parameters(current_parameters) if isinstance(hamiltonian, MatrixOp): evolution_gate = HamiltonianGate(hamiltonian.primitive, time=dt) else: evolution_gate = PauliEvolutionGate(hamiltonian, time=dt, synthesis=self.evolution) trotterized.append(evolution_gate, ansatz.qubits) # define the overlap of the Trotterized state and the ansatz x = ParameterVector("w", ansatz.num_parameters) shifted = ansatz.assign_parameters(current_parameters + x) overlap = StateFn(trotterized).adjoint() @ StateFn(shifted) converted = self.expectation.convert(overlap) def evaluate_loss( displacement: Union[np.ndarray, List[np.ndarray]] ) -> Union[float, List[float]]: """Evaluate the overlap of the ansatz with the Trotterized evolution. Args: displacement: The parameters for the ansatz. Returns: The fidelity of the ansatz with parameters ``theta`` and the Trotterized evolution. """ if isinstance(displacement, list): displacement = np.asarray(displacement) value_dict = {x_i: displacement[:, i].tolist() for i, x_i in enumerate(x)} else: value_dict = dict(zip(x, displacement)) sampled = self._sampler.convert(converted, params=value_dict) # in principle we could add different loss functions here, but we're currently # not aware of a use-case for a different one than in the paper return 1 - np.abs(sampled.eval()) ** 2 if _is_gradient_supported(ansatz) and self.use_parameter_shift: def evaluate_gradient(displacement: np.ndarray) -> np.ndarray: """Evaluate the gradient with the parameter-shift rule. This is hardcoded here since the gradient framework does not support computing gradients for overlaps. Args: displacement: The parameters for the ansatz. Returns: The gradient. """ # construct lists where each element is shifted by plus (or minus) pi/2 dim = displacement.size plus_shifts = (displacement + np.pi / 2 * np.identity(dim)).tolist() minus_shifts = (displacement - np.pi / 2 * np.identity(dim)).tolist() evaluated = evaluate_loss(plus_shifts + minus_shifts) gradient = (evaluated[:dim] - evaluated[dim:]) / 2 return gradient else: evaluate_gradient = None return evaluate_loss, evaluate_gradient
def __init__( self, optimizer=None, feature_map=None, var_form=None, training_dataset=None, test_dataset=None, datapoints=None, max_evals_grouped=1, minibatch_size=-1, callback=None ): """Initialize the object Args: optimizer (Optimizer): The classical optimizer to use. feature_map (FeatureMap): The FeatureMap instance to use. var_form (VariationalForm): The variational form instance. training_dataset (dict): The training dataset, in the format: {'A': np.ndarray, 'B': np.ndarray, ...}. test_dataset (dict): The test dataset, in same format as `training_dataset`. datapoints (np.ndarray): NxD array, N is the number of data and D is data dimension. max_evals_grouped (int): The maximum number of evaluations to perform simultaneously. minibatch_size (int): The size of a mini-batch. callback (Callable): a callback that can access the intermediate data during the optimization. Internally, four arguments are provided as follows the index of data batch, the index of evaluation, parameters of variational form, evaluated value. Notes: We use `label` to denotes numeric results and `class` the class names (str). Raises: AquaError: invalid input """ self.validate(locals()) super().__init__( var_form=var_form, optimizer=optimizer, cost_fn=self._cost_function_wrapper ) self._batches = None self._label_batches = None self._batch_index = None self._eval_time = None self.batch_num = None self._optimizer.set_max_evals_grouped(max_evals_grouped) self._callback = callback if feature_map is None: raise AquaError('Missing feature map.') if training_dataset is None: raise AquaError('Missing training dataset.') self._training_dataset, self._class_to_label = split_dataset_to_data_and_labels( training_dataset) self._label_to_class = {label: class_name for class_name, label in self._class_to_label.items()} self._num_classes = len(list(self._class_to_label.keys())) if test_dataset is not None: self._test_dataset = split_dataset_to_data_and_labels(test_dataset, self._class_to_label) else: self._test_dataset = test_dataset if datapoints is not None and not isinstance(datapoints, np.ndarray): datapoints = np.asarray(datapoints) if len(datapoints) == 0: # pylint: disable=len-as-condition datapoints = None self._datapoints = datapoints self._minibatch_size = minibatch_size self._eval_count = 0 self._ret = {} self._feature_map = feature_map self._num_qubits = feature_map.num_qubits self._var_form_params = ParameterVector('θ', self._var_form.num_parameters) self._feature_map_params = ParameterVector('x', self._feature_map.feature_dimension) self._parameterized_circuits = None
def _compose_transforms(basis_transforms, source_basis, source_dag): """Compose a set of basis transforms into a set of replacements. Args: basis_transforms (List[Tuple[gate_name, params, equiv]]): List of transforms to compose. source_basis (Set[Tuple[gate_name: str, gate_num_qubits: int]]): Names of gates which need to be translated. source_dag (DAGCircuit): DAG with example gates from source_basis. (Used to determine num_params for gate in source_basis.) Returns: Dict[gate_name, Tuple(params, dag)]: Dictionary mapping between each gate in source_basis and a DAGCircuit instance to replace it. Gates in source_basis but not affected by basis_transforms will be included as a key mapping to itself. """ example_gates = {(node.op.name, node.op.num_qubits): node.op for node in source_dag.op_nodes()} mapped_instrs = {} for gate_name, gate_num_qubits in source_basis: # Need to grab a gate instance to find num_qubits and num_params. # Can be removed following https://github.com/Qiskit/qiskit-terra/pull/3947 . example_gate = example_gates[gate_name, gate_num_qubits] num_params = len(example_gate.params) placeholder_params = ParameterVector(gate_name, num_params) placeholder_gate = Gate(gate_name, gate_num_qubits, list(placeholder_params)) placeholder_gate.params = list(placeholder_params) dag = DAGCircuit() qr = QuantumRegister(gate_num_qubits) dag.add_qreg(qr) dag.apply_operation_back(placeholder_gate, qr[:], []) mapped_instrs[gate_name, gate_num_qubits] = placeholder_params, dag for gate_name, gate_num_qubits, equiv_params, equiv in basis_transforms: logger.debug('Composing transform step: %s/%s %s =>\n%s', gate_name, gate_num_qubits, equiv_params, equiv) for mapped_instr_name, (dag_params, dag) in mapped_instrs.items(): doomed_nodes = [ node for node in dag.op_nodes() if (node.op.name, node.op.num_qubits) == (gate_name, gate_num_qubits) ] if doomed_nodes and logger.isEnabledFor(logging.DEBUG): from qiskit.converters import dag_to_circuit logger.debug( 'Updating transform for mapped instr %s %s from \n%s', mapped_instr_name, dag_params, dag_to_circuit(dag)) for node in doomed_nodes: from qiskit.converters import circuit_to_dag replacement = equiv.assign_parameters( dict(zip_longest(equiv_params, node.op.params))) replacement_dag = circuit_to_dag(replacement) dag.substitute_node_with_dag(node, replacement_dag) if doomed_nodes and logger.isEnabledFor(logging.DEBUG): from qiskit.converters import dag_to_circuit logger.debug('Updated transform for mapped instr %s %s to\n%s', mapped_instr_name, dag_params, dag_to_circuit(dag)) return mapped_instrs
entangler_map = [] for i in range(sum(num_qubits)): entangler_map.append([i, int(np.mod(i+1, sum(num_qubits)))]) # Load the trained circuit parameters g_params = [0.29399714, 0.38853322, 0.9557694, 0.07245791, 6.02626428, 0.13537225] # Set an initial state for the generator circuit init_dist = NormalDistribution(sum(num_qubits), mu=1., sigma=1., low=bounds[0], high=bounds[1]) init_distribution = np.sqrt(init_dist.probabilities) init_distribution = Custom(num_qubits=sum(num_qubits), state_vector=init_distribution) # construct the variational form var_form = RealAmplitudes(sum(num_qubits), entanglement=entangler_map, reps=1, initial_state=init_distribution) var_form.entanglement_blocks = 'cz' theta = ParameterVector('θ', var_form.num_parameters) var_form = var_form.assign_parameters(theta) # Set generator circuit g_circuit = UnivariateVariationalDistribution(sum(num_qubits), var_form, g_params, low=bounds[0], high=bounds[1]) g_circuit._var_form_params = theta # construct circuit factory for uncertainty model uncertainty_model = g_circuit # set the strike price (should be within the low and the high value of the uncertainty) strike_price = 2 # set the approximation scaling for the payoff function