class TestUVCC(QiskitNatureTestCase): """Tests for the UVCC Ansatz.""" @unpack @data( ("s", [2], [VibrationalOp([("+-", 1j), ("-+", -1j)], 1, 2)]), ( "s", [2, 2], [ VibrationalOp([("+-II", 1j), ("-+II", -1j)], 2, 2), VibrationalOp([("II+-", 1j), ("II-+", -1j)], 2, 2), ], ), ) def test_ucc_ansatz(self, excitations, num_modals, expect): """Tests the UVCC Ansatz.""" converter = QubitConverter(DirectMapper()) ansatz = UVCC(qubit_converter=converter, num_modals=num_modals, excitations=excitations) assert_ucc_like_ansatz(self, ansatz, num_modals, expect) def test_transpile_no_parameters(self): """Test transpilation without parameters""" qubit_converter = QubitConverter(mapper=DirectMapper()) ansatz = UVCC(qubit_converter=qubit_converter, num_modals=[2], excitations="s") ansatz = transpile(ansatz, optimization_level=3) self.assertEqual(ansatz.num_qubits, 2)
def test_simplify(self): """Test simplify""" test_op = (1j * VibrationalOp("+-", 2, 1) + 1j * VibrationalOp("+-", 2, 1) - 1j * VibrationalOp("-+", 2, 1) - 1j * VibrationalOp("-+", 2, 1)) expected = [("+-", 2j), ("-+", -2j)] self.assertEqual(test_op.simplify().to_list(), expected)
def test_equiv(self): """test equiv""" op1 = VibrationalOp("+-", 2, 1) + VibrationalOp("-+", 2, 1) op2 = VibrationalOp("+-", 2, 1) op3 = VibrationalOp("+-", 2, 1) + (1 + 1e-7) * VibrationalOp("-+", 2, 1) self.assertFalse(op1.equiv(op2)) self.assertFalse(op1.equiv(op3)) self.assertTrue(op1.equiv(op3, atol=1e-6))
def _build_vibration_excitation_ops(self, excitations: Sequence) -> list[VibrationalOp]: """Builds all possible excitation operators with the given number of excitations for the specified number of particles distributed in the number of orbitals. Args: excitations: the list of excitations. Returns: The list of excitation operators in the second quantized formalism. """ operators = [] sum_modes = sum(self.num_modals) for exc in excitations: label = ["I"] * sum_modes for occ in exc[0]: label[occ] = "+" for unocc in exc[1]: label[unocc] = "-" op = VibrationalOp("".join(label), len(self.num_modals), self.num_modals) op -= op.adjoint() # we need to account for an additional imaginary phase in the exponent (see also # `PauliTrotterEvolution.convert`) op *= 1j # type: ignore operators.append(op) return operators
def to_second_q_op(self) -> VibrationalOp: """Creates the operator representing the Hamiltonian defined by these vibrational integrals. Returns: The :class:`~qiskit_nature.second_q.operators.VibrationalOp` given by these vibrational integrals. Raises: QiskitNatureError: if no basis has been set yet. """ try: matrix = self.to_basis() except QiskitNatureError as exc: raise QiskitNatureError() from exc num_modals_per_mode = self.basis._num_modals_per_mode num_modes = len(num_modals_per_mode) nonzero = np.nonzero(matrix) if not np.any(np.asarray(nonzero)): return VibrationalOp.zero(num_modes, num_modals_per_mode) labels = [] for coeff, indices in zip(matrix[nonzero], zip(*nonzero)): # the indices need to be grouped into triplets of the form: (mode, modal_1, modal_2) grouped_indices = [ tuple(int(j) for j in indices[i : i + 3]) for i in range(0, len(indices), 3) ] # the index groups need to processed in sorted order to produce a valid label coeff_label = self._create_label_for_coeff(sorted(grouped_indices)) labels.append((coeff_label, coeff)) return VibrationalOp(labels, num_modes, num_modals_per_mode)
def test_uvcc_vscf(self): """uvcc vscf test""" co2_2modes_2modals_2body = [ [ [[[0, 0, 0]], 320.8467332810141], [[[0, 1, 1]], 1760.878530705873], [[[1, 0, 0]], 342.8218290247543], [[[1, 1, 1]], 1032.396323618631], ], [ [[[0, 0, 0], [1, 0, 0]], -57.34003649795117], [[[0, 0, 1], [1, 0, 0]], -56.33205925807966], [[[0, 1, 0], [1, 0, 0]], -56.33205925807966], [[[0, 1, 1], [1, 0, 0]], -60.13032761856809], [[[0, 0, 0], [1, 0, 1]], -65.09576309934431], [[[0, 0, 1], [1, 0, 1]], -62.2363839133389], [[[0, 1, 0], [1, 0, 1]], -62.2363839133389], [[[0, 1, 1], [1, 0, 1]], -121.5533969109279], [[[0, 0, 0], [1, 1, 0]], -65.09576309934431], [[[0, 0, 1], [1, 1, 0]], -62.2363839133389], [[[0, 1, 0], [1, 1, 0]], -62.2363839133389], [[[0, 1, 1], [1, 1, 0]], -121.5533969109279], [[[0, 0, 0], [1, 1, 1]], -170.744837386338], [[[0, 0, 1], [1, 1, 1]], -167.7433236025723], [[[0, 1, 0], [1, 1, 1]], -167.7433236025723], [[[0, 1, 1], [1, 1, 1]], -179.0536532281924], ], ] num_modes = 2 num_modals = [2, 2] vibrational_op_labels = _create_labels(co2_2modes_2modals_2body) vibr_op = VibrationalOp(vibrational_op_labels, num_modes, num_modals) converter = QubitConverter(DirectMapper()) qubit_op = converter.convert_match(vibr_op) init_state = VSCF(num_modals) uvcc_ansatz = UVCC(converter, num_modals, "sd", initial_state=init_state) q_instance = QuantumInstance( BasicAer.get_backend("statevector_simulator"), seed_transpiler=90, seed_simulator=12, ) optimizer = COBYLA(maxiter=1000) algo = VQE(uvcc_ansatz, optimizer=optimizer, quantum_instance=q_instance) vqe_result = algo.compute_minimum_eigenvalue(qubit_op) energy = vqe_result.optimal_value self.assertAlmostEqual(energy, self.reference_energy, places=4)
def test_init_pm_label(self): """Test __init__ with plus and minus label""" with self.subTest("minus plus"): result = VibrationalOp([("+_0*0 -_0*1", 2)], 1, 2) desired = [("+-", (2 + 0j))] self.assertEqual(result.to_list(), desired) with self.subTest("plus minus"): result = VibrationalOp([("-_0*0 +_0*1", 2)], 1, 2) desired = [("-+", (2 + 0j))] self.assertEqual(result.to_list(), desired) with self.subTest("plus minus minus plus"): result = VibrationalOp([("+_0*0 -_0*1 -_1*0 +_1*1", 3)], 2, 2) desired = [("+--+", (3 + 0j))] # Note: the order of list is irrelevant. self.assertSetEqual(frozenset(result.to_list()), frozenset(desired))
def _build_single_hopping_operator( excitation: Tuple[Tuple[int, ...], Tuple[int, ...]], num_modals: List[int], qubit_converter: QubitConverter, ) -> PauliSumOp: sum_modes = sum(num_modals) label = ["I"] * sum_modes for occ in excitation[0]: label[occ] = "+" for unocc in excitation[1]: label[unocc] = "-" vibrational_op = VibrationalOp("".join(label), len(num_modals), num_modals) qubit_op: PauliSumOp = qubit_converter.convert_match(vibrational_op) return qubit_op
def test_hermiticity(self): """test is_hermitian""" with self.subTest("operator hermitian"): # deliberately define test operator with duplicate terms in case .adjoint() simplifies terms test_op = (1j * VibrationalOp("+-", 2, 1) + 1j * VibrationalOp("+-", 2, 1) - 1j * VibrationalOp("-+", 2, 1) - 1j * VibrationalOp("-+", 2, 1)) self.assertTrue(test_op.is_hermitian()) with self.subTest("operator not hermitian"): test_op = (1j * VibrationalOp("+-", 2, 1) + 1j * VibrationalOp("+-", 2, 1) - 1j * VibrationalOp("-+", 2, 1)) self.assertFalse(test_op.is_hermitian()) with self.subTest("test passing atol"): test_op = 1j * VibrationalOp( "+-", 2, 1) - (1 + 1e-7) * 1j * VibrationalOp("-+", 2, 1) self.assertFalse(test_op.is_hermitian()) self.assertFalse(test_op.is_hermitian(atol=1e-8)) self.assertTrue(test_op.is_hermitian(atol=1e-6))
def setUp(self): super().setUp() self.labels = [("+_1*0 -_1*1", 1215.375), ("+_2*0 -_2*1 +_3*0 -_3*0", -6.385)] self.labels_double = [ ("+_1*0 -_1*1", 2 * 1215.375), ("+_2*0 -_2*1 +_3*0 -_3*0", -2 * 6.385), ] self.labels_divided_3 = [ ("+_1*0 -_1*1", 1215.375 / 3), ("+_2*0 -_2*1 +_3*0 -_3*0", -6.385 / 3), ] self.labels_neg = [ ("+_1*0 -_1*1", -1215.375), ("+_2*0 -_2*1 +_3*0 -_3*0", 6.385), ] self.vibr_spin_op = VibrationalOp(self.labels, 4, 2)
def _get_mode_op(self, mode: int) -> VibrationalOp: """Constructs an operator to evaluate which modal of a given mode is occupied. Args: mode: the mode index. Returns: The operator to evaluate which modal of the given mode is occupied. """ num_modals_per_mode = self.basis._num_modals_per_mode labels: list[tuple[str, complex]] = [] for modal in range(num_modals_per_mode[mode]): labels.append((f"+_{mode}*{modal} -_{mode}*{modal}", 1.0)) return VibrationalOp(labels, len(num_modals_per_mode), num_modals_per_mode)
def __init__( self, num_modals: list[int], qubit_converter: QubitConverter | None = None, ) -> None: """ Args: num_modals: Is a list defining the number of modals per mode. E.g. for a 3 modes system with 4 modals per mode num_modals = [4,4,4] qubit_converter: a QubitConverter instance. This argument is currently being ignored because only a single use-case is supported at the time of release: that of the :class:`DirectMapper`. However, for future-compatibility of this functions signature, the argument has already been inserted. """ # get the bitstring encoding initial state bitstr = vscf_bitstring(num_modals) # encode the bitstring in a `VibrationalOp` label = ["+" if bit else "I" for bit in bitstr] bitstr_op = VibrationalOp("".join(label), num_modes=len(num_modals), num_modals=num_modals) # map the `VibrationalOp` to a qubit operator if qubit_converter is not None: logger.warning( "The only supported `QubitConverter` is one with a `DirectMapper` as the mapper " "instance. However you specified %s as an input, which will be ignored until more " "variants will be supported.", str(qubit_converter), ) qubit_converter = QubitConverter(DirectMapper()) qubit_op: PauliSumOp = qubit_converter.convert_match(bitstr_op) # construct the circuit qr = QuantumRegister(qubit_op.num_qubits, "q") super().__init__(qr, name="VSCF") # add gates in the right positions for i, bit in enumerate(qubit_op.primitive.paulis.x[0]): if bit: self.x(i)
def test_add(self): """Test __add__""" actual = (self.vibr_spin_op + self.vibr_spin_op).simplify() desired = VibrationalOp(self.labels_double, 4, 2) self.assertSpinEqual(actual, desired)
def test_div(self): """Test __truediv__""" actual = self.vibr_spin_op / 3 desired = VibrationalOp(self.labels_divided_3, 4, 2) self.assertSpinEqual(actual, desired)
def test_neg(self): """Test __neg__""" actual = -self.vibr_spin_op desired = VibrationalOp(self.labels_neg, 4, 2) self.assertSpinEqual(actual, desired)
def test_init_invalid_label(self, label): """Test __init__ for invalid label""" with self.assertRaises(ValueError): VibrationalOp(label, 1, 1)
def test_mul(self): """Test __mul__, and __rmul__""" actual = self.vibr_spin_op * 2 desired = VibrationalOp(self.labels_double, 4, 2) self.assertSpinEqual(actual, desired)
# # Any modifications or derivative works of this code must retain this # copyright notice, and modified files need to carry a notice indicating # that they have been altered from the originals. """Some expected VibrationalOps.""" from qiskit_nature.second_q.operators import VibrationalOp _truncation_order_1_op = VibrationalOp( [ ("NIIIIIII", 1268.0676746875001), ("INIIIIII", 3813.8767834375008), ("IINIIIII", 705.8633818750001), ("II+-IIII", -46.025705898886045), ("II-+IIII", -46.025705898886045), ("IIINIIII", 2120.1145593750007), ("IIIINIII", 238.31540750000005), ("IIIIINII", 728.9613775000003), ("IIIIIINI", 238.31540750000005), ("IIIIIIIN", 728.9613775000003), ], num_modes=4, num_modals=2, ) _truncation_order_2_op = VibrationalOp( [ ("NIIIIIII", 1268.0676746875001), ("INIIIIII", 3813.8767834375008), ("IINIIIII", 705.8633818750001), ("II+-IIII", -46.025705898886045), ("II-+IIII", -46.025705898886045),