def test_init_raising_lowering_ops(self): """Test __init__ for +_i -_i pattern""" with self.subTest("one reg"): actual = SpinOp("+_0 -_0", spin=1, register_length=1) expected = SpinOp([("X_0^2", 1), ("Y_0^2", 1), ("Z_0", 1)], spin=1, register_length=1) self.assertSpinEqual(actual, expected) with self.subTest("two reg"): actual = SpinOp("+_1 -_1 +_0 -_0", spin=3 / 2, register_length=2) expected = SpinOp( [ ("X_0^2 X_1^2", 1), ("X_0^2 Y_1^2", 1), ("X_0^2 Z_1", 1), ("Y_0^2 X_1^2", 1), ("Y_0^2 Y_1^2", 1), ("Y_0^2 Z_1", 1), ("Z_0 X_1^2", 1), ("Z_0 Y_1^2", 1), ("Z_0 Z_1", 1), ], spin=3 / 2, register_length=2, ) self.assertSpinEqual(actual, expected)
def test_init_multiple_digits(self): """Test __init__ for sparse label with multiple digits""" actual = SpinOp([("X_10^20", 1 + 2j), ("X_12^34", 56)], Fraction(5, 2), register_length=13) desired = [("X_10^20", 1 + 2j), ("X_12^34", 56)] self.assertListEqual(actual.to_list(), desired)
def test_init_label(self, label, pre_processing): """Test __init__""" spin = SpinOp(pre_processing(label), register_length=len(label) // 3) expected_label = " ".join(lb for lb in label.split() if lb[0] != "I") if not expected_label: expected_label = f"I_{len(label) // 3 - 1}" self.assertListEqual(spin.to_list(), [(expected_label, 1)]) self.assertSpinEqual(eval(repr(spin)), spin) # pylint: disable=eval-used
def test_add(self): """Test __add__""" with self.subTest("sum of heisenberg"): actual = self.heisenberg + self.heisenberg desired = SpinOp( (self.heisenberg_spin_array, 2 * self.heisenberg_coeffs), spin=1) self.assertSpinEqual(actual, desired) with self.subTest("raising operator"): plus = SpinOp("+", 3 / 2) x = SpinOp("X", 3 / 2) y = SpinOp("Y", 3 / 2) self.assertSpinEqual(x + 1j * y, plus)
def second_q_ops(self, display_format: Optional[str] = None) -> SpinOp: """Return the Hamiltonian of the Ising model in terms of `SpinOp`. Args: display_format: Not supported for Spin operators. If specified, it will be ignored. Returns: SpinOp: The Hamiltonian of the Ising model. """ if display_format is not None: logger.warning( "Spin operators do not support display-format. Provided display-format " "parameter will be ignored." ) ham = [] weighted_edge_list = self._lattice.weighted_edge_list register_length = self._lattice.num_nodes # kinetic terms for node_a, node_b, weight in weighted_edge_list: if node_a == node_b: index = node_a ham.append((f"X_{index}", weight)) else: index_left = node_a index_right = node_b coupling_parameter = weight ham.append((f"Z_{index_left} Z_{index_right}", coupling_parameter)) return SpinOp(ham, spin=Fraction(1, 2), register_length=register_length)
def test_flatten_ladder_ops(self): """Test _flatten_ladder_ops""" actual = SpinOp._flatten_ladder_ops([("+-", 2j)]) self.assertSetEqual( frozenset(actual), frozenset([("XX", 2j), ("XY", 2), ("YX", -2), ("YY", 2j)]), )
def test_adjoint(self): """Test adjoint method and dagger property""" with self.subTest("heisenberg adjoint"): actual = self.heisenberg.adjoint() desired = SpinOp((self.heisenberg_spin_array, self.heisenberg_coeffs.conjugate().T), spin=1) self.assertSpinEqual(actual, desired) with self.subTest("imag heisenberg adjoint"): actual = ~((3 + 2j) * self.heisenberg) desired = SpinOp( (self.heisenberg_spin_array, ((3 + 2j) * self.heisenberg_coeffs).conjugate().T), spin=1, ) self.assertSpinEqual(actual, desired)
def test_hermiticity(self): """test is_hermitian""" # deliberately define test operator with X and Y which creates duplicate terms in .to_list() # in case .adjoint() simplifies terms with self.subTest("operator hermitian"): test_op = SpinOp("+ZXY") + SpinOp("-ZXY") self.assertTrue(test_op.is_hermitian()) with self.subTest("operator not hermitian"): test_op = SpinOp("+ZXY") - SpinOp("-ZXY") self.assertFalse(test_op.is_hermitian())
def test_init_heisenberg(self): """Test __init__ for Heisenberg model.""" actual = SpinOp( [ ("XX", -1), ("YY", -1), ("ZZ", -1), ("ZI", -0.3), ("IZ", -0.3), ], spin=1, ) self.assertSpinEqual(actual, self.heisenberg)
def map(self, second_q_op: SpinOp) -> PauliSumOp: """Map spins to qubits using the Logarithmic encoding. Args: second_q_op: Spins mapped to qubits. Returns: Qubit operators generated by the Logarithmic encoding """ qubit_ops_list: List[PauliSumOp] = [] # get logarithmic encoding of the general spin matrices. spinx, spiny, spinz, identity = self._logarithmic_encoding( second_q_op.spin) for idx, (_, coeff) in enumerate(second_q_op.to_list()): operatorlist: List[PauliSumOp] = [] for n_x, n_y, n_z in zip(second_q_op.x[idx], second_q_op.y[idx], second_q_op.z[idx]): operator_on_spin_i: List[PauliSumOp] = [] if n_x > 0: operator_on_spin_i.append( reduce(operator.matmul, [spinx] * int(n_x))) if n_y > 0: operator_on_spin_i.append( reduce(operator.matmul, [spiny] * int(n_y))) if n_z > 0: operator_on_spin_i.append( reduce(operator.matmul, [spinz] * int(n_z))) if operator_on_spin_i: single_operator_on_spin_i = reduce(operator.matmul, operator_on_spin_i) operatorlist.append(single_operator_on_spin_i) else: # If n_x=n_y=n_z=0, simply add the embedded Identity operator. operatorlist.append(identity) # Now, we can tensor all operators in this list qubit_ops_list.append(coeff * reduce(operator.xor, reversed(operatorlist))) qubit_op = reduce(operator.add, qubit_ops_list) return qubit_op
def setUp(self): super().setUp() self.heisenberg_spin_array = np.array([ [[1, 1], [0, 0], [0, 0], [0, 0], [0, 0]], [[0, 0], [1, 1], [0, 0], [0, 0], [0, 0]], [[0, 0], [0, 0], [1, 1], [1, 0], [0, 1]], ], ) self.heisenberg_coeffs = np.array([-1, -1, -1, -0.3, -0.3]) self.heisenberg = SpinOp( (self.heisenberg_spin_array, self.heisenberg_coeffs), spin=1, ) self.zero_op = SpinOp( (np.array([[[0, 0]], [[0, 0]], [[0, 0]]]), np.array([0])), spin=1, ) self.spin_1_matrix = { "I": np.array([[1, 0, 0], [0, 1, 0], [0, 0, 1]]), "X": np.array([[0, 1, 0], [1, 0, 1], [0, 1, 0]]) / np.sqrt(2), "Y": np.array([[0, -1j, 0], [1j, 0, -1j], [0, 1j, 0]]) / np.sqrt(2), "Z": np.array([[1, 0, 0], [0, 0, 0], [0, 0, -1]]), }
def test_simplify(self): """Test simplify""" with self.subTest("trivial reduce"): actual = (self.heisenberg - self.heisenberg).simplify() self.assertListEqual(actual.to_list(), [("I_1", 0)]) with self.subTest("nontrivial reduce"): test_op = SpinOp( ( np.array([[[0, 1], [0, 1]], [[0, 0], [0, 0]], [[1, 0], [1, 0]]]), np.array([1.5, 2.5]), ), spin=3 / 2, ) actual = test_op.simplify() self.assertListEqual(actual.to_list(), [("Z_0 X_1", 4)]) with self.subTest("nontrivial reduce 2"): test_op = SpinOp( ( np.array([ [[0, 1], [0, 1], [1, 1]], [[0, 0], [0, 0], [0, 0]], [[1, 0], [1, 0], [0, 0]], ]), np.array([1.5, 2.5, 2]), ), spin=3 / 2, ) actual = test_op.simplify() self.assertListEqual(actual.to_list(), [("Z_0 X_1", 4), ("X_0 X_1", 2)]) with self.subTest("nontrivial reduce 3"): test_op = SpinOp([("+_0 -_0", 1)], register_length=4) actual = test_op.simplify() self.assertListEqual(actual.to_list(), [("Z_0", 1), ("Y_0^2", 1), ("X_0^2", 1)])
class TestLinearMapper(QiskitNatureTestCase): """Test Linear Mapper""" spin_op1 = SpinOp([("Y_0^2", -0.432 + 1.32j)], 0.5, 1) ref_qubit_op1 = (-0.054 + 0.165j) * (I ^ I) + (0.054 - 0.165j) * (Z ^ Z) spin_op2 = SpinOp([("X_0 Z_0 I_1", -1.139 + 0.083j)], 0.5, 2) ref_qubit_op2 = (0.010375 + 0.142375j) * (I ^ I ^ Y ^ X) + (-0.010375 - 0.142375j) * ( I ^ I ^ X ^ Y ) spin_op3 = SpinOp([("X_0 Y_0^2 Z_0 X_1 Y_1 Y_2 Z_2", -0.18 + 1.204j)], 0.5, 3) ref_qubit_op3 = ( (0.000587890625 + 8.7890625e-05j) * (Y ^ Y ^ I ^ Z ^ Y ^ X) + (0.000587890625 + 8.7890625e-05j) * (X ^ X ^ I ^ Z ^ Y ^ X) + (-0.000587890625 - 8.7890625e-05j) * (Y ^ Y ^ Z ^ I ^ Y ^ X) + (-0.000587890625 - 8.7890625e-05j) * (X ^ X ^ Z ^ I ^ Y ^ X) + (-0.000587890625 - 8.7890625e-05j) * (Y ^ Y ^ I ^ Z ^ X ^ Y) + (-0.000587890625 - 8.7890625e-05j) * (X ^ X ^ I ^ Z ^ X ^ Y) + (0.000587890625 + 8.7890625e-05j) * (Y ^ Y ^ Z ^ I ^ X ^ Y) + (0.000587890625 + 8.7890625e-05j) * (X ^ X ^ Z ^ I ^ X ^ Y) ) spin_op4 = SpinOp([("I_0 Z_1", -0.875 - 0.075j)], 1.5, 2) ref_qubit_op4 = ( (-0.65625 - 0.05625j) * (Z ^ I ^ I ^ I ^ I ^ I ^ I ^ I) + (-0.21875 - 0.01875j) * (I ^ Z ^ I ^ I ^ I ^ I ^ I ^ I) + (0.21875 + 0.01875j) * (I ^ I ^ Z ^ I ^ I ^ I ^ I ^ I) + (0.65625 + 0.05625j) * (I ^ I ^ I ^ Z ^ I ^ I ^ I ^ I) ) spin_op5 = SpinOp([("X_0 I_1 I_2 I_3 I_4 I_5 I_6 I_7", 4 + 0j)], 0.5, 8) + SpinOp( [("I_0^2 I_2 I_3 I_4 I_5 I_6 I_7 I_8", 8 + 0j)], 0.5, 8 ) ref_qubit_op5 = ( (8.0 + 0j) * (I ^ I ^ I ^ I ^ I ^ I ^ I ^ I ^ I ^ I ^ I ^ I ^ I ^ I ^ I ^ I) + (1.0 + 0j) * (I ^ I ^ I ^ I ^ I ^ I ^ I ^ I ^ I ^ I ^ I ^ I ^ I ^ I ^ X ^ X) + (1.0 + 0j) * (I ^ I ^ I ^ I ^ I ^ I ^ I ^ I ^ I ^ I ^ I ^ I ^ I ^ I ^ Y ^ Y) ) @data( (spin_op1, ref_qubit_op1), (spin_op2, ref_qubit_op2), (spin_op3, ref_qubit_op3), (spin_op4, ref_qubit_op4), (spin_op5, ref_qubit_op5), ) @unpack def test_mapping(self, spin_op, ref_qubit_op): """Test mapping to qubit operator""" mapper = LinearMapper() qubit_op = mapper.map(spin_op) self.assertEqual(qubit_op, ref_qubit_op)
def map(self, second_q_op: SpinOp) -> PauliSumOp: qubit_ops_list: List[PauliSumOp] = [] # get linear encoding of the general spin matrices spinx, spiny, spinz, identity = self._linear_encoding(second_q_op.spin) for idx, (_, coeff) in enumerate(second_q_op.to_list()): operatorlist: List[PauliSumOp] = [] for n_x, n_y, n_z in zip(second_q_op.x[idx], second_q_op.y[idx], second_q_op.z[idx]): operator_on_spin_i: List[PauliSumOp] = [] if n_x > 0: operator_on_spin_i.append( reduce(operator.matmul, [spinx] * int(n_x))) if n_y > 0: operator_on_spin_i.append( reduce(operator.matmul, [spiny] * int(n_y))) if n_z > 0: operator_on_spin_i.append( reduce(operator.matmul, [spinz] * int(n_z))) if np.any([n_x, n_y, n_z]) > 0: single_operator_on_spin_i = reduce(operator.matmul, operator_on_spin_i) operatorlist.append(single_operator_on_spin_i.reduce()) else: # If n_x=n_y=n_z=0, simply add the embedded Identity operator. operatorlist.append(identity) # Now, we can tensor all operators in this list # NOTE: in Qiskit's opflow the `XOR` (i.e. `^`) operator does the tensor product qubit_ops_list.append(coeff * reduce(operator.xor, reversed(operatorlist))) qubit_op = reduce(operator.add, qubit_ops_list) return qubit_op
class TestLogarithmicMapper(QiskitNatureTestCase): """Test Logarithmic Mapper""" spin_op1 = SpinOp([("Y_0", -0.432 + 1.32j)], 0.5, 1) ref_qubit_op1 = (-0.216 + 0.66j) * (Y) spin_op2 = SpinOp([("X_0 Z_0 I_1", -1.139 + 0.083j)], 0.5, 2) ref_qubit_op2 = (0.02075 + 0.28475j) * (I ^ Y) spin_op3 = SpinOp([("X_0 Y_0^2 Z_0 X_1 Y_1 Y_2 Z_2", -0.18 + 1.204j)], 0.5, 3) ref_qubit_op3 = (-0.004703125 - 0.000703125j) * (X ^ Z ^ Y) spin_op4 = SpinOp([("I_0 Z_1", -0.875 - 0.075j)], 1.5, 2) ref_qubit_op4 = (-0.4375 - 0.0375j) * (I ^ Z ^ I ^ I) + ( -0.875 - 0.075j) * (Z ^ I ^ I ^ I) spin_op5 = SpinOp( [("X_0 I_1 I_2 I_3 I_4 I_5 I_6 I_7", 4 + 0j)], 0.5, 8) + SpinOp( [("I_0^2 I_2 I_3 I_4 I_5 I_6 I_7 I_8", 8 + 0j)], 0.5, 8) ref_qubit_op5 = (8.0 + 0j) * (I ^ I ^ I ^ I ^ I ^ I ^ I ^ I) + ( 2.0 + 0j) * (I ^ I ^ I ^ I ^ I ^ I ^ I ^ X) spin_op6 = SpinOp([("Z_0", -0.875 - 0.075j)], 1, 1) ref_qubit_op6 = ((-0.4375 - 0.0375j) * (I ^ I) + (0.4375 + 0.0375j) * (I ^ Z) + (-0.875 - 0.075j) * (Z ^ Z)) ref_qubit_op7 = ((-0.4375 - 0.0375j) * (I ^ I) + (-0.4375 - 0.0375j) * (I ^ Z) + (-0.875 - 0.075j) * (Z ^ I)) @data( (spin_op1, ref_qubit_op1), (spin_op2, ref_qubit_op2), (spin_op3, ref_qubit_op3), (spin_op4, ref_qubit_op4), (spin_op5, ref_qubit_op5), (spin_op6, ref_qubit_op6, 2), (spin_op6, ref_qubit_op7, 2, False), ) @unpack def test_mapping(self, spin_op, ref_qubit_op, padding=1, embed_upper=True): """Test mapping to qubit operator""" mapper = LogarithmicMapper(padding, embed_upper) qubit_op = mapper.map(spin_op) self.assertEqual(qubit_op, ref_qubit_op)
def _logarithmic_encoding( self, spin: Union[Fraction, int] ) -> Tuple[PauliSumOp, PauliSumOp, PauliSumOp, PauliSumOp]: """The logarithmic encoding. Args: spin: Positive half-integer (integer or half-odd-integer) that represents spin. Returns: A tuple containing four PauliSumOp. """ spin_op_encoding: List[PauliSumOp] = [] dspin = int(2 * spin + 1) num_qubits = int(np.ceil(np.log2(dspin))) # Get the spin matrices spin_matrices = [ SpinOp(symbol, spin=spin).to_matrix() for symbol in "XYZ" ] # Append the identity spin_matrices.append(np.eye(dspin)) # Embed the spin matrices in a larger matrix of size 2**num_qubits x 2**num_qubits embedded_spin_matrices = [ self._embed_matrix(matrix, num_qubits) for matrix in spin_matrices ] # Generate operators from these embedded spin matrices embedded_operators = [ Operator(matrix) for matrix in embedded_spin_matrices ] for op in embedded_operators: op = SparsePauliOp.from_operator(op) op.chop() spin_op_encoding.append(PauliSumOp(1.0 * op)) return tuple(spin_op_encoding) # type: ignore
def test_init_dense_label(self, labels, pre_processing): """Test __init__ for dense label""" dense_label, sparse_label = labels actual = SpinOp(pre_processing(dense_label)) desired = SpinOp([(sparse_label, 1)], register_length=len(dense_label)) self.assertSpinEqual(actual, desired)
def _linear_encoding(self, spin: Union[Fraction, float]) -> List[PauliSumOp]: """ Generates a 'linear_encoding' of the spin S operators 'X', 'Y', 'Z' and 'identity' to qubit operators (linear combinations of pauli strings). In this 'linear_encoding' each individual spin S system is represented via 2S+1 qubits and the state |s> is mapped to the state |00...010..00>, where the s-th qubit is in state 1. Returns: The 4-element list of transformed spin S 'X', 'Y', 'Z' and 'identity' operators. I.e. spin_op_encoding[0]` corresponds to the linear combination of pauli strings needed to represent the embedded 'X' operator """ spin_op_encoding: List[PauliSumOp] = [] dspin = int(2 * spin + 1) nqubits = dspin # quick functions to generate a pauli with X / Y / Z at location `i` pauli_id = Pauli('I' * nqubits) def pauli_x(i): return Pauli('I' * i + 'X' + 'I' * (nqubits - i - 1)) def pauli_y(i): return Pauli('I' * i + 'Y' + 'I' * (nqubits - i - 1)) def pauli_z(i): return Pauli('I' * i + 'Z' + 'I' * (nqubits - i - 1)) # 1. build the non-diagonal X operator x_summands = [] for i, coeff in enumerate( np.diag(SpinOp("X", spin=spin).to_matrix(), 1)): x_summands.append( PauliSumOp(coeff / 2. * SparsePauliOp(pauli_x(i).dot(pauli_x(i + 1))) + coeff / 2. * SparsePauliOp(pauli_y(i).dot(pauli_y(i + 1))))) spin_op_encoding.append(reduce(operator.add, x_summands)) # 2. build the non-diagonal Y operator y_summands = [] for i, coeff in enumerate( np.diag(SpinOp("Y", spin=spin).to_matrix(), 1)): y_summands.append( PauliSumOp(-1j * coeff / 2. * SparsePauliOp(pauli_x(i).dot(pauli_y(i + 1))) + 1j * coeff / 2. * SparsePauliOp(pauli_y(i).dot(pauli_x(i + 1))))) spin_op_encoding.append(reduce(operator.add, y_summands)) # 3. build the diagonal Z z_summands = [] for i, coeff in enumerate(np.diag(SpinOp("Z", spin=spin).to_matrix())): # get the first upper diagonal of coeff. z_summands.append( PauliSumOp(coeff / 2. * SparsePauliOp(pauli_z(i)) + coeff / 2. * SparsePauliOp(pauli_id))) z_operator = reduce(operator.add, z_summands) spin_op_encoding.append(z_operator) # 4. add the identity operator spin_op_encoding.append(PauliSumOp(1. * SparsePauliOp(pauli_id))) # return the lookup table for the transformed XYZI operators return spin_op_encoding
def test_init_invalid_label(self, label): """Test __init__ for invalid label""" with self.assertRaises(ValueError): SpinOp(label)
def test_init_pm_label(self): """Test __init__ with plus and minus label""" with self.subTest("plus"): plus = SpinOp([("+_0", 2)], register_length=1) desired = SpinOp([("X_0", 2), ("Y_0", 2j)], register_length=1) self.assertSpinEqual(plus, desired) with self.subTest("dense plus"): plus = SpinOp([("+", 2)]) desired = SpinOp([("X_0", 2), ("Y_0", 2j)], register_length=1) self.assertSpinEqual(plus, desired) with self.subTest("minus"): minus = SpinOp([("-_0", 2)], register_length=1) desired = SpinOp([("X_0", 2), ("Y_0", -2j)], register_length=1) self.assertSpinEqual(minus, desired) with self.subTest("minus"): minus = SpinOp([("-", 2)]) desired = SpinOp([("X_0", 2), ("Y_0", -2j)], register_length=1) self.assertSpinEqual(minus, desired) with self.subTest("plus tensor minus"): plus_tensor_minus = SpinOp([("+_0 -_1", 3)], register_length=2) desired = SpinOp( [("X_0 X_1", 3), ("Y_0 X_1", 3j), ("X_0 Y_1", -3j), ("Y_0 Y_1", 3)], register_length=2, ) self.assertSpinEqual(plus_tensor_minus, desired) with self.subTest("dense plus tensor minus"): plus_tensor_minus = SpinOp([("+-", 3)]) desired = SpinOp( [("X_0 X_1", 3), ("Y_0 X_1", 3j), ("X_0 Y_1", -3j), ("Y_0 Y_1", 3)], register_length=2, ) self.assertSpinEqual(plus_tensor_minus, desired)
def assertSpinEqual(first: SpinOp, second: SpinOp): """Fail if two SpinOps have different matrix representations.""" np.testing.assert_array_almost_equal(first.to_matrix(), second.to_matrix())
def test_neg(self): """Test __neg__""" actual = -self.heisenberg desired = SpinOp((self.heisenberg_spin_array, -self.heisenberg_coeffs), spin=1) self.assertSpinEqual(actual, desired)
class TestSpinOp(QiskitNatureTestCase): """SpinOp tests.""" def setUp(self): super().setUp() self.heisenberg_spin_array = np.array([ [[1, 1], [0, 0], [0, 0], [0, 0], [0, 0]], [[0, 0], [1, 1], [0, 0], [0, 0], [0, 0]], [[0, 0], [0, 0], [1, 1], [1, 0], [0, 1]], ], ) self.heisenberg_coeffs = np.array([-1, -1, -1, -0.3, -0.3]) self.heisenberg = SpinOp( (self.heisenberg_spin_array, self.heisenberg_coeffs), spin=1, ) self.zero_op = SpinOp( (np.array([[[0, 0]], [[0, 0]], [[0, 0]]]), np.array([0])), spin=1, ) self.spin_1_matrix = { "I": np.array([[1, 0, 0], [0, 1, 0], [0, 0, 1]]), "X": np.array([[0, 1, 0], [1, 0, 1], [0, 1, 0]]) / np.sqrt(2), "Y": np.array([[0, -1j, 0], [1j, 0, -1j], [0, 1j, 0]]) / np.sqrt(2), "Z": np.array([[1, 0, 0], [0, 0, 0], [0, 0, -1]]), } @staticmethod def assertSpinEqual(first: SpinOp, second: SpinOp): """Fail if two SpinOps have different matrix representations.""" np.testing.assert_array_almost_equal(first.to_matrix(), second.to_matrix()) @data(*product( (*sparse_labels(1), *sparse_labels(2), *sparse_labels(3)), (str2str, str2tuple, str2list), )) @unpack def test_init_label(self, label, pre_processing): """Test __init__""" spin = SpinOp(pre_processing(label), register_length=len(label) // 3) expected_label = " ".join(lb for lb in label.split() if lb[0] != "I") if not expected_label: expected_label = f"I_{len(label) // 3 - 1}" self.assertListEqual(spin.to_list(), [(expected_label, 1)]) self.assertSpinEqual(eval(repr(spin)), spin) # pylint: disable=eval-used @data(*product( ( *zip(dense_labels(1), sparse_labels(1)), *zip(dense_labels(2), sparse_labels(2)), *zip(dense_labels(3), sparse_labels(3)), ), (str2str, str2tuple, str2list), )) @unpack def test_init_dense_label(self, labels, pre_processing): """Test __init__ for dense label""" dense_label, sparse_label = labels actual = SpinOp(pre_processing(dense_label)) desired = SpinOp([(sparse_label, 1)], register_length=len(dense_label)) self.assertSpinEqual(actual, desired) def test_init_pm_label(self): """Test __init__ with plus and minus label""" with self.subTest("plus"): plus = SpinOp([("+_0", 2)], register_length=1) desired = SpinOp([("X_0", 2), ("Y_0", 2j)], register_length=1) self.assertSpinEqual(plus, desired) with self.subTest("dense plus"): plus = SpinOp([("+", 2)]) desired = SpinOp([("X_0", 2), ("Y_0", 2j)], register_length=1) self.assertSpinEqual(plus, desired) with self.subTest("minus"): minus = SpinOp([("-_0", 2)], register_length=1) desired = SpinOp([("X_0", 2), ("Y_0", -2j)], register_length=1) self.assertSpinEqual(minus, desired) with self.subTest("minus"): minus = SpinOp([("-", 2)]) desired = SpinOp([("X_0", 2), ("Y_0", -2j)], register_length=1) self.assertSpinEqual(minus, desired) with self.subTest("plus tensor minus"): plus_tensor_minus = SpinOp([("+_0 -_1", 3)], register_length=2) desired = SpinOp( [("X_0 X_1", 3), ("Y_0 X_1", 3j), ("X_0 Y_1", -3j), ("Y_0 Y_1", 3)], register_length=2, ) self.assertSpinEqual(plus_tensor_minus, desired) with self.subTest("dense plus tensor minus"): plus_tensor_minus = SpinOp([("+-", 3)]) desired = SpinOp( [("X_0 X_1", 3), ("Y_0 X_1", 3j), ("X_0 Y_1", -3j), ("Y_0 Y_1", 3)], register_length=2, ) self.assertSpinEqual(plus_tensor_minus, desired) def test_init_heisenberg(self): """Test __init__ for Heisenberg model.""" actual = SpinOp( [ ("XX", -1), ("YY", -1), ("ZZ", -1), ("ZI", -0.3), ("IZ", -0.3), ], spin=1, ) self.assertSpinEqual(actual, self.heisenberg) def test_init_multiple_digits(self): """Test __init__ for sparse label with multiple digits""" actual = SpinOp([("X_10^20", 1 + 2j), ("X_12^34", 56)], Fraction(5, 2), register_length=13) desired = [("X_10^20", 1 + 2j), ("X_12^34", 56)] self.assertListEqual(actual.to_list(), desired) @data("IJX", "Z_0 X_0", "Z_0 +_0", "+_0 X_0") def test_init_invalid_label(self, label): """Test __init__ for invalid label""" with self.assertRaises(ValueError): SpinOp(label) def test_init_raising_lowering_ops(self): """Test __init__ for +_i -_i pattern""" with self.subTest("one reg"): actual = SpinOp("+_0 -_0", spin=1, register_length=1) expected = SpinOp([("X_0^2", 1), ("Y_0^2", 1), ("Z_0", 1)], spin=1, register_length=1) self.assertSpinEqual(actual, expected) with self.subTest("two reg"): actual = SpinOp("+_1 -_1 +_0 -_0", spin=3 / 2, register_length=2) expected = SpinOp( [ ("X_0^2 X_1^2", 1), ("X_0^2 Y_1^2", 1), ("X_0^2 Z_1", 1), ("Y_0^2 X_1^2", 1), ("Y_0^2 Y_1^2", 1), ("Y_0^2 Z_1", 1), ("Z_0 X_1^2", 1), ("Z_0 Y_1^2", 1), ("Z_0 Z_1", 1), ], spin=3 / 2, register_length=2, ) self.assertSpinEqual(actual, expected) def test_neg(self): """Test __neg__""" actual = -self.heisenberg desired = SpinOp((self.heisenberg_spin_array, -self.heisenberg_coeffs), spin=1) self.assertSpinEqual(actual, desired) def test_mul(self): """Test __mul__, and __rmul__""" actual = self.heisenberg * 2 desired = SpinOp( (self.heisenberg_spin_array, 2 * self.heisenberg_coeffs), spin=1) self.assertSpinEqual(actual, desired) def test_div(self): """Test __truediv__""" actual = self.heisenberg / 3 desired = SpinOp( (self.heisenberg_spin_array, self.heisenberg_coeffs / 3), spin=1) self.assertSpinEqual(actual, desired) def test_add(self): """Test __add__""" with self.subTest("sum of heisenberg"): actual = self.heisenberg + self.heisenberg desired = SpinOp( (self.heisenberg_spin_array, 2 * self.heisenberg_coeffs), spin=1) self.assertSpinEqual(actual, desired) with self.subTest("raising operator"): plus = SpinOp("+", 3 / 2) x = SpinOp("X", 3 / 2) y = SpinOp("Y", 3 / 2) self.assertSpinEqual(x + 1j * y, plus) def test_sub(self): """Test __sub__""" actual = self.heisenberg - self.heisenberg self.assertSpinEqual(actual, self.zero_op) def test_adjoint(self): """Test adjoint method""" with self.subTest("heisenberg adjoint"): actual = self.heisenberg.adjoint() desired = SpinOp( (self.heisenberg_spin_array, self.heisenberg_coeffs.conjugate().T), spin=1, ) self.assertSpinEqual(actual, desired) with self.subTest("imag heisenberg adjoint"): actual = ~((3 + 2j) * self.heisenberg) desired = SpinOp( ( self.heisenberg_spin_array, ((3 + 2j) * self.heisenberg_coeffs).conjugate().T, ), spin=1, ) self.assertSpinEqual(actual, desired) # TODO: implement adjoint for same register operators. # with self.sub Test("adjoint same register op"): # actual = SpinOp("X_0 Y_0 Z_0").adjoint() # print(actual.to_matrix()) # print(SpinOp("X_0 Y_0 Z_0").to_matrix().T.conjugate()) def test_reduce(self): """Test reduce""" with self.assertWarns(DeprecationWarning): actual = (self.heisenberg - self.heisenberg).reduce() self.assertListEqual(actual.to_list(), [("I_1", 0)]) def test_simplify(self): """Test simplify""" with self.subTest("trivial reduce"): actual = (self.heisenberg - self.heisenberg).simplify() self.assertListEqual(actual.to_list(), [("I_1", 0)]) with self.subTest("nontrivial reduce"): test_op = SpinOp( ( np.array([[[0, 1], [0, 1]], [[0, 0], [0, 0]], [[1, 0], [1, 0]]]), np.array([1.5, 2.5]), ), spin=3 / 2, ) actual = test_op.simplify() self.assertListEqual(actual.to_list(), [("Z_0 X_1", 4)]) with self.subTest("nontrivial reduce 2"): test_op = SpinOp( ( np.array([ [[0, 1], [0, 1], [1, 1]], [[0, 0], [0, 0], [0, 0]], [[1, 0], [1, 0], [0, 0]], ]), np.array([1.5, 2.5, 2]), ), spin=3 / 2, ) actual = test_op.simplify() self.assertListEqual(actual.to_list(), [("Z_0 X_1", 4), ("X_0 X_1", 2)]) with self.subTest("nontrivial reduce 3"): test_op = SpinOp([("+_0 -_0", 1)], register_length=4) actual = test_op.simplify() self.assertListEqual(actual.to_list(), [("Z_0", 1), ("Y_0^2", 1), ("X_0^2", 1)]) @data(*dense_labels(1)) def test_to_matrix_single_qutrit(self, label): """Test to_matrix for single qutrit op""" actual = SpinOp(label, 1).to_matrix() np.testing.assert_array_almost_equal(actual, self.spin_1_matrix[label]) @data(*product(dense_labels(1), dense_labels(1))) @unpack def test_to_matrix_sum_single_qutrit(self, label1, label2): """Test to_matrix for sum qutrit op""" actual = (SpinOp(label1, 1) + SpinOp(label2, 1)).to_matrix() np.testing.assert_array_almost_equal( actual, self.spin_1_matrix[label1] + self.spin_1_matrix[label2]) @data(*dense_labels(2)) def test_to_matrix_two_qutrit(self, label): """Test to_matrix for two qutrit op""" actual = SpinOp(label, 1).to_matrix() desired = np.kron(self.spin_1_matrix[label[0]], self.spin_1_matrix[label[1]]) np.testing.assert_array_almost_equal(actual, desired) @data(*dense_labels(1), *dense_labels(2), *dense_labels(3)) def test_consistency_with_pauli(self, label): """Test consistency with pauli""" actual = SpinOp(label).to_matrix() desired = Pauli(label).to_matrix() / (2**(len(label) - label.count("I"))) np.testing.assert_array_almost_equal(actual, desired) def test_flatten_ladder_ops(self): """Test _flatten_ladder_ops""" actual = SpinOp._flatten_ladder_ops([("+-", 2j)]) self.assertSetEqual( frozenset(actual), frozenset([("XX", 2j), ("XY", 2), ("YX", -2), ("YY", 2j)]), ) def test_hermiticity(self): """test is_hermitian""" # deliberately define test operator with X and Y which creates duplicate terms in .to_list() # in case .adjoint() simplifies terms with self.subTest("operator hermitian"): test_op = SpinOp("+ZXY") + SpinOp("-ZXY") self.assertTrue(test_op.is_hermitian()) with self.subTest("operator not hermitian"): test_op = SpinOp("+ZXY") - SpinOp("-ZXY") self.assertFalse(test_op.is_hermitian())
def test_mul(self): """Test __mul__, and __rmul__""" actual = self.heisenberg * 2 desired = SpinOp( (self.heisenberg_spin_array, 2 * self.heisenberg_coeffs), spin=1) self.assertSpinEqual(actual, desired)
def test_div(self): """Test __truediv__""" actual = self.heisenberg / 3 desired = SpinOp( (self.heisenberg_spin_array, self.heisenberg_coeffs / 3), spin=1) self.assertSpinEqual(actual, desired)
def test_to_matrix_single_qutrit(self, label): """Test to_matrix for single qutrit op""" actual = SpinOp(label, 1).to_matrix() np.testing.assert_array_almost_equal(actual, self.spin_1_matrix[label])
def test_consistency_with_pauli(self, label): """Test consistency with pauli""" actual = SpinOp(label).to_matrix() desired = Pauli(label).to_matrix() / (2**(len(label) - label.count("I"))) np.testing.assert_array_almost_equal(actual, desired)
def test_to_matrix_two_qutrit(self, label): """Test to_matrix for two qutrit op""" actual = SpinOp(label, 1).to_matrix() desired = np.kron(self.spin_1_matrix[label[0]], self.spin_1_matrix[label[1]]) np.testing.assert_array_almost_equal(actual, desired)
def test_to_matrix_sum_single_qutrit(self, label1, label2): """Test to_matrix for sum qutrit op""" actual = (SpinOp(label1, 1) + SpinOp(label2, 1)).to_matrix() np.testing.assert_array_almost_equal( actual, self.spin_1_matrix[label1] + self.spin_1_matrix[label2])