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(l for l in label.split() if l[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 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) # TODO: implement adjoint for same register operators. # with self.sub Test("adjoint same register op"): # actual = SpinOp("X_0 Y_0 Z_0").dagger # print(actual.to_matrix()) # print(SpinOp("X_0 Y_0 Z_0").to_matrix().T.conjugate()) def test_reduce(self): """Test reduce""" with self.subTest("trivial reduce"): actual = (self.heisenberg - self.heisenberg).reduce() 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.reduce() 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.reduce() 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.reduce() 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)]), )
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(*spin_labels(1)) def test_init_label(self, label): """Test __init__""" spin = SpinOp(f"{label}_0") self.assertListEqual(spin.to_list(), [(f"{label}_0", 1)]) @data(*spin_labels(2)) def test_init_len2_label(self, label): """Test __init__""" spin = SpinOp(f"{label[1]}_1 {label[0]}_0") self.assertListEqual(spin.to_list(), [(f"{label[1]}_1 {label[0]}_0", 1)]) def test_init_pm_label(self): """Test __init__ with plus and minus label""" with self.subTest("plus"): plus = SpinOp([("+_0", 2)]) desired = SpinOp([("X_0", 2), ("Y_0", 2j)]) self.assertSpinEqual(plus, desired) with self.subTest("dense plus"): plus = SpinOp([("+", 2)]) desired = SpinOp([("X_0", 2), ("Y_0", 2j)]) self.assertSpinEqual(plus, desired) with self.subTest("minus"): minus = SpinOp([("-_0", 2)]) desired = SpinOp([("X_0", 2), ("Y_0", -2j)]) self.assertSpinEqual(minus, desired) with self.subTest("minus"): minus = SpinOp([("-", 2)]) desired = SpinOp([("X_0", 2), ("Y_0", -2j)]) self.assertSpinEqual(minus, desired) with self.subTest("plus tensor minus"): plus_tensor_minus = SpinOp([("+_1 -_0", 3)]) desired = SpinOp([("X_1 X_0", 3), ("X_1 Y_0", -3j), ("Y_1 X_0", 3j), ("Y_1 Y_0", 3)]) self.assertSpinEqual(plus_tensor_minus, desired) with self.subTest("dense plus tensor minus"): plus_tensor_minus = SpinOp([("+-", 3)]) desired = SpinOp([("X_1 X_0", 3), ("X_1 Y_0", -3j), ("Y_1 X_0", 3j), ("Y_1 Y_0", 3)]) 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) @data(*spin_labels(1), *spin_labels(2)) def test_init_dense_label(self, label): """Test __init__ for dense label""" if len(label) == 1: actual = SpinOp([(f"{label}", 1 + 1j)]) desired = SpinOp([(f"{label}_0", 1 + 1j)]) elif len(label) == 2: actual = SpinOp([(f"{label}", 1)]) desired = SpinOp([(f"{label[0]}_1 {label[1]}_0", 1)]) self.assertSpinEqual(actual, 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_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 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) # TODO: implement adjoint for same register operators. # with self.sub Test("adjoint same register op"): # actual = SpinOp("X_0 Y_0 Z_0").dagger # print(actual.to_matrix()) # print(SpinOp("X_0 Y_0 Z_0").to_matrix().T.conjugate()) def test_reduce(self): """Test reduce""" with self.subTest("trivial reduce"): actual = (self.heisenberg - self.heisenberg).reduce() self.assertListEqual(actual.to_list(), [("I_1 I_0", 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.reduce() self.assertListEqual(actual.to_list(), [("Z_1 X_0", 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.reduce() self.assertListEqual(actual.to_list(), [("Z_1 X_0", 4), ("X_1 X_0", 2)]) @data(*spin_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(spin_labels(1), spin_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(*spin_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(*spin_labels(1), *spin_labels(2), *spin_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)]), )