Repository: vprusso/toqito

Issue:
Add a check for an anti-hermitian in `matrix_props`
A square matrix $A$ is said to be anti-hermitian when $A^* = -A$.

We could add a new `is_anti_hermitian` function in `matrix_props` or make changes to `is_hermitian` to allow for a new `check_type = None or "Anti"` parameter. The latter helps us avoid code duplication. 

https://github.com/vprusso/toqito/blob/15ac78ac29efe737b8cb1d85216de25cba227c76/toqito/matrix_props/is_hermitian.py#L68


@vprusso Do you have a preference?

Hints from issue comments:
@vprusso: Hmm, I could see arguments for both based on the way that we do things now. I suppose the string arguments do make sense when the requested object of computation is in some sense "bucketed" in the same category. Here it's a bit different as "Hermitian" and "anti-Hermitian" strike me as related but different. 

I'm also wondering whether it makes sense to include these functions at all. While I think having them in the arsenal is cool, I wonder if they are useful for anything specifically quantum-themed. I know you found these from Nic Higham's work (who I'm a big fan of), but I wonder if including these would have quantum application. Any thoughts on that?@purva-thakre: > I know you found these from Nic Higham's work

Not really. These matrices usually appear in lie theory which is something I am utilizing for an ongoing project. 

![image](https://github.com/user-attachments/assets/6552729b-543f-42fc-9174-60dd868b8da3)
http://scipp.ucsc.edu/~haber/ph251/gellmann23.pdf@vprusso: Ah, I see. I think in terms of preference of implementation I would take `is_anti_hermitian.py` over arguments for `is_hermitian.py`. Any preferences/thoughts on your side?@tnemoz: Potato! :D

Solution patch:
diff --git a/docs/refs.bib b/docs/refs.bib
index 1a6dd31e2..5df631a7c 100644
--- a/docs/refs.bib
+++ b/docs/refs.bib
@@ -1165,6 +1165,13 @@ @misc{WikiHerm
   
 }
 
+@misc{WikiAntiHerm,
+  author  = "Wikipedia",
+  title   = "Skew-Hermitian matrix",
+  howpublished = {https://en.wikipedia.org/wiki/Skew-Hermitian_matrix}
+
+}
+
 
 @misc{WikiHilbSchOp,
   author  = "Wikipedia",
diff --git a/poetry.lock b/poetry.lock
index 7a4a953ce..15d9ed942 100644
--- a/poetry.lock
+++ b/poetry.lock
@@ -13,13 +13,13 @@ files = [
 
 [[package]]
 name = "astroid"
-version = "3.3.2"
+version = "3.3.4"
 description = "An abstract syntax tree for Python with inference support."
 optional = false
 python-versions = ">=3.9.0"
 files = [
-    {file = "astroid-3.3.2-py3-none-any.whl", hash = "sha256:9f8136ce9770e0f912401b25a0f15d5c2ec20b50e99b1b413ac0778fe53ff6f1"},
-    {file = "astroid-3.3.2.tar.gz", hash = "sha256:99e9b5b602cbb005434084309213d6af32bf7a9b743c836749168b8e2b330cbd"},
+    {file = "astroid-3.3.4-py3-none-any.whl", hash = "sha256:5eba185467253501b62a9f113c263524b4f5d55e1b30456370eed4cdbd6438fd"},
+    {file = "astroid-3.3.4.tar.gz", hash = "sha256:e73d0b62dd680a7c07cb2cd0ce3c22570b044dd01bd994bc3a2dd16c6cbba162"},
 ]
 
 [package.dependencies]
@@ -1490,22 +1490,22 @@ scipy = ">=0.13.2"
 
 [[package]]
 name = "qiskit"
-version = "1.2.1"
+version = "1.2.2"
 description = "An open-source SDK for working with quantum computers at the level of extended quantum circuits, operators, and primitives."
 optional = false
 python-versions = ">=3.8"
 files = [
-    {file = "qiskit-1.2.1-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:b5ded55318c19f6810bf343ffca2260523538fc51381a1649de7ca13e6a49cc8"},
-    {file = "qiskit-1.2.1-cp38-abi3-macosx_10_9_x86_64.whl", hash = "sha256:205fe6edad8e708d3e5ca1488ff509109dbbc7083ec2bc1caee6da9432c981df"},
-    {file = "qiskit-1.2.1-cp38-abi3-macosx_11_0_arm64.whl", hash = "sha256:ae61858a7b18ecb5661d25a30a99942efa369cb4460fa2a4fa014720accbeee9"},
-    {file = "qiskit-1.2.1-cp38-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a13059794763c51199787bef1fe22f82f644567b009f39eda1ea8ddfccd955fd"},
-    {file = "qiskit-1.2.1-cp38-abi3-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:03c72dff7b2189da8ff1920c5fea9933e95bac5550ee532c994d73e5abeacd4e"},
-    {file = "qiskit-1.2.1-cp38-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d79f9c8a564156cc2b2ec89f51e4969567d27ab4f7460206a96ce1ec4c0e82c3"},
-    {file = "qiskit-1.2.1-cp38-abi3-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c06e4d994b0258d21727a3544d5950c58f44e7c15459d87eff575b4191e3b471"},
-    {file = "qiskit-1.2.1-cp38-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c011a5db14d305fbd23a24fbdb71107d07a570a01d14e1314e66d9c0c617703d"},
-    {file = "qiskit-1.2.1-cp38-abi3-win32.whl", hash = "sha256:7384b1ffe86b3ebbca8adab7e06fe827c432bde728bca6ac0a3e157359c87180"},
-    {file = "qiskit-1.2.1-cp38-abi3-win_amd64.whl", hash = "sha256:1a13e6281b6cad07724a3a3d08434837bc0783e5f50021f98f85d9071e8932ad"},
-    {file = "qiskit-1.2.1.tar.gz", hash = "sha256:da69c84ab052416af35a43f0695845797de6d3207760354b7c8c8b93cbb19f47"},
+    {file = "qiskit-1.2.2-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:d62c834b3da9c033d5c705ba0f479e70fdf9c78d1d6f058bf350f54bd305cf57"},
+    {file = "qiskit-1.2.2-cp38-abi3-macosx_10_9_x86_64.whl", hash = "sha256:8ea12858fa3425809c939d3cedfef48fb2f2b5cfe47e2576a5d04ec35358ebcf"},
+    {file = "qiskit-1.2.2-cp38-abi3-macosx_11_0_arm64.whl", hash = "sha256:2942f4b47bbd251aafc1b006cdab823f1977d6219136901dd6084abb0205ed6c"},
+    {file = "qiskit-1.2.2-cp38-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cd8e8071b3311ba6debfc31bb103469f863b12c0080db82bd49f9b6ae93e82d1"},
+    {file = "qiskit-1.2.2-cp38-abi3-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3944796a619c8c789e7c0ec9fad3c0ab2a0eb3990fee7da6da652ba83c7a5f8e"},
+    {file = "qiskit-1.2.2-cp38-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a4d5b544f82dc34e822c51ab762038c9ba47c76d3c9a510f78389859fd800c88"},
+    {file = "qiskit-1.2.2-cp38-abi3-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:fd4c25135d4ebdff0ca084d52e20d1040c073c39632a073c7c9a3149871d73c0"},
+    {file = "qiskit-1.2.2-cp38-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3f37e6acc465874f070c499b67cfccac9eac671c12cc8d304251fd8ddaadba1a"},
+    {file = "qiskit-1.2.2-cp38-abi3-win32.whl", hash = "sha256:a2317ef3bfbe49d71dd10077b3712001b8017453247bfe43ad0b6280f9a0fd6d"},
+    {file = "qiskit-1.2.2-cp38-abi3-win_amd64.whl", hash = "sha256:9e54c253713ae06e1aadb3399ff39ccc4fda0826741595514f7b489a957e4f33"},
+    {file = "qiskit-1.2.2.tar.gz", hash = "sha256:e16dd0f99de8caaec85a6620fccc9acf3ac2c9368f465e4d9e7ab445d3c7428a"},
 ]
 
 [package.dependencies]
@@ -1565,29 +1565,29 @@ use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"]
 
 [[package]]
 name = "ruff"
-version = "0.6.6"
+version = "0.6.7"
 description = "An extremely fast Python linter and code formatter, written in Rust."
 optional = false
 python-versions = ">=3.7"
 files = [
-    {file = "ruff-0.6.6-py3-none-linux_armv6l.whl", hash = "sha256:f5bc5398457484fc0374425b43b030e4668ed4d2da8ee7fdda0e926c9f11ccfb"},
-    {file = "ruff-0.6.6-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:515a698254c9c47bb84335281a170213b3ee5eb47feebe903e1be10087a167ce"},
-    {file = "ruff-0.6.6-py3-none-macosx_11_0_arm64.whl", hash = "sha256:6bb1b4995775f1837ab70f26698dd73852bbb82e8f70b175d2713c0354fe9182"},
-    {file = "ruff-0.6.6-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:69c546f412dfae8bb9cc4f27f0e45cdd554e42fecbb34f03312b93368e1cd0a6"},
-    {file = "ruff-0.6.6-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:59627e97364329e4eae7d86fa7980c10e2b129e2293d25c478ebcb861b3e3fd6"},
-    {file = "ruff-0.6.6-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:94c3f78c3d32190aafbb6bc5410c96cfed0a88aadb49c3f852bbc2aa9783a7d8"},
-    {file = "ruff-0.6.6-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:704da526c1e137f38c8a067a4a975fe6834b9f8ba7dbc5fd7503d58148851b8f"},
-    {file = "ruff-0.6.6-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:efeede5815a24104579a0f6320660536c5ffc1c91ae94f8c65659af915fb9de9"},
-    {file = "ruff-0.6.6-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e368aef0cc02ca3593eae2fb8186b81c9c2b3f39acaaa1108eb6b4d04617e61f"},
-    {file = "ruff-0.6.6-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2653fc3b2a9315bd809725c88dd2446550099728d077a04191febb5ea79a4f79"},
-    {file = "ruff-0.6.6-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:bb858cd9ce2d062503337c5b9784d7b583bcf9d1a43c4df6ccb5eab774fbafcb"},
-    {file = "ruff-0.6.6-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:488f8e15c01ea9afb8c0ba35d55bd951f484d0c1b7c5fd746ce3c47ccdedce68"},
-    {file = "ruff-0.6.6-py3-none-musllinux_1_2_i686.whl", hash = "sha256:aefb0bd15f1cfa4c9c227b6120573bb3d6c4ee3b29fb54a5ad58f03859bc43c6"},
-    {file = "ruff-0.6.6-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:a4c0698cc780bcb2c61496cbd56b6a3ac0ad858c966652f7dbf4ceb029252fbe"},
-    {file = "ruff-0.6.6-py3-none-win32.whl", hash = "sha256:aadf81ddc8ab5b62da7aae78a91ec933cbae9f8f1663ec0325dae2c364e4ad84"},
-    {file = "ruff-0.6.6-py3-none-win_amd64.whl", hash = "sha256:0adb801771bc1f1b8cf4e0a6fdc30776e7c1894810ff3b344e50da82ef50eeb1"},
-    {file = "ruff-0.6.6-py3-none-win_arm64.whl", hash = "sha256:4b4d32c137bc781c298964dd4e52f07d6f7d57c03eae97a72d97856844aa510a"},
-    {file = "ruff-0.6.6.tar.gz", hash = "sha256:0fc030b6fd14814d69ac0196396f6761921bd20831725c7361e1b8100b818034"},
+    {file = "ruff-0.6.7-py3-none-linux_armv6l.whl", hash = "sha256:08277b217534bfdcc2e1377f7f933e1c7957453e8a79764d004e44c40db923f2"},
+    {file = "ruff-0.6.7-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:c6707a32e03b791f4448dc0dce24b636cbcdee4dd5607adc24e5ee73fd86c00a"},
+    {file = "ruff-0.6.7-py3-none-macosx_11_0_arm64.whl", hash = "sha256:533d66b7774ef224e7cf91506a7dafcc9e8ec7c059263ec46629e54e7b1f90ab"},
+    {file = "ruff-0.6.7-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:17a86aac6f915932d259f7bec79173e356165518859f94649d8c50b81ff087e9"},
+    {file = "ruff-0.6.7-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b3f8822defd260ae2460ea3832b24d37d203c3577f48b055590a426a722d50ef"},
+    {file = "ruff-0.6.7-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9ba4efe5c6dbbb58be58dd83feedb83b5e95c00091bf09987b4baf510fee5c99"},
+    {file = "ruff-0.6.7-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:525201b77f94d2b54868f0cbe5edc018e64c22563da6c5c2e5c107a4e85c1c0d"},
+    {file = "ruff-0.6.7-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8854450839f339e1049fdbe15d875384242b8e85d5c6947bb2faad33c651020b"},
+    {file = "ruff-0.6.7-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2f0b62056246234d59cbf2ea66e84812dc9ec4540518e37553513392c171cb18"},
+    {file = "ruff-0.6.7-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6b1462fa56c832dc0cea5b4041cfc9c97813505d11cce74ebc6d1aae068de36b"},
+    {file = "ruff-0.6.7-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:02b083770e4cdb1495ed313f5694c62808e71764ec6ee5db84eedd82fd32d8f5"},
+    {file = "ruff-0.6.7-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:0c05fd37013de36dfa883a3854fae57b3113aaa8abf5dea79202675991d48624"},
+    {file = "ruff-0.6.7-py3-none-musllinux_1_2_i686.whl", hash = "sha256:f49c9caa28d9bbfac4a637ae10327b3db00f47d038f3fbb2195c4d682e925b14"},
+    {file = "ruff-0.6.7-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:a0e1655868164e114ba43a908fd2d64a271a23660195017c17691fb6355d59bb"},
+    {file = "ruff-0.6.7-py3-none-win32.whl", hash = "sha256:a939ca435b49f6966a7dd64b765c9df16f1faed0ca3b6f16acdf7731969deb35"},
+    {file = "ruff-0.6.7-py3-none-win_amd64.whl", hash = "sha256:590445eec5653f36248584579c06252ad2e110a5d1f32db5420de35fb0e1c977"},
+    {file = "ruff-0.6.7-py3-none-win_arm64.whl", hash = "sha256:b28f0d5e2f771c1fe3c7a45d3f53916fc74a480698c4b5731f0bea61e52137c8"},
+    {file = "ruff-0.6.7.tar.gz", hash = "sha256:44e52129d82266fa59b587e2cd74def5637b730a69c4542525dfdecfaae38bd5"},
 ]
 
 [[package]]
@@ -2093,4 +2093,4 @@ files = [
 [metadata]
 lock-version = "2.0"
 python-versions = ">=3.10,<4"
-content-hash = "53dabd3d164bff085c8d74c9280d96a89fb1427a37ac73ddcf5a1ef60b6fb275"
+content-hash = "327ac4f25e518449baf590ae028422cbc778cf3c4cdb110063686accc4170c16"
diff --git a/pyproject.toml b/pyproject.toml
index 12fce1cec..2c5292d19 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -24,7 +24,7 @@ numpy = "2.1.1"
 scipy = "1.14.1"
 scs = "3.2.7"
 picos = "2.4.17"
-qiskit = "1.2.1"
+qiskit = "1.2.2"
 
 [tool.poetry.group.dev.dependencies]
 pytest = "8.3.3"
@@ -41,7 +41,7 @@ flake8-docstrings = "1.7.0"
 isort = "5.13.2"
 myst-parser = "4.0.0"
 pep8 = "1.7.1"
-ruff = "0.6.6"
+ruff = "0.6.7"
 mypy = "1.11.2"
 black = "*"
 
diff --git a/toqito/matrix_props/__init__.py b/toqito/matrix_props/__init__.py
index cca648a09..2647e0f25 100644
--- a/toqito/matrix_props/__init__.py
+++ b/toqito/matrix_props/__init__.py
@@ -3,6 +3,7 @@
 from toqito.matrix_props.has_same_dimension import has_same_dimension
 from toqito.matrix_props.is_square import is_square
 from toqito.matrix_props.kp_norm import kp_norm
+from toqito.matrix_props.is_anti_hermitian import is_anti_hermitian
 from toqito.matrix_props.is_hermitian import is_hermitian
 from toqito.matrix_props.is_positive_semidefinite import is_positive_semidefinite
 from toqito.matrix_props.is_density import is_density
diff --git a/toqito/matrix_props/is_anti_hermitian.py b/toqito/matrix_props/is_anti_hermitian.py
new file mode 100644
index 000000000..b9937b9ea
--- /dev/null
+++ b/toqito/matrix_props/is_anti_hermitian.py
@@ -0,0 +1,67 @@
+"""Is matrix an anti-Hermitian matrix."""
+
+import numpy as np
+
+from toqito.matrix_props.is_hermitian import is_hermitian
+
+
+def is_anti_hermitian(mat: np.ndarray, rtol: float = 1e-05, atol: float = 1e-08) -> bool:
+    r"""Check if matrix is anti-Hermitian (a.k.a. skew-Hermitian) :cite:`WikiAntiHerm`.
+
+    An anti-Hermitian matrix is a complex square matrix that is equal to the negative of its own
+    conjugate transpose.
+
+    Examples
+    ==========
+
+    Consider the following matrix:
+
+    .. math::
+        A = \begin{pmatrix}
+                2j & -1 + 2j & 4j \\
+                1 + 2j & 3j & -1 \\
+                4j & 1 & 1j
+            \end{pmatrix}
+
+    our function indicates that this is indeed an anti-Hermitian matrix as it holds that
+
+    .. math::
+        A = -A^*.
+
+    >>> from toqito.matrix_props import is_anti_hermitian
+    >>> import numpy as np
+    >>> mat = np.array([[2j, -1 + 2j, 4j], [1 + 2j, 3j, -1], [4j, 1, 1j]])
+    >>> is_anti_hermitian(mat)
+    True
+
+    Alternatively, the following example matrix :math:`B` defined as
+
+    .. math::
+        B = \begin{pmatrix}
+                1 & 2 & 3 \\
+                4 & 5 & 6 \\
+                7 & 8 & 9
+            \end{pmatrix}
+
+    is not anti-Hermitian.
+
+    >>> from toqito.matrix_props import is_anti_hermitian
+    >>> import numpy as np
+    >>> mat = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
+    >>> is_anti_hermitian(mat)
+    False
+
+    References
+    ==========
+    .. bibliography::
+        :filter: docname in docnames
+
+
+
+    :param mat: Matrix to check.
+    :param rtol: The relative tolerance parameter (default 1e-05).
+    :param atol: The absolute tolerance parameter (default 1e-08).
+    :return: Return True if matrix is anti-Hermitian, and False otherwise.
+
+    """
+    return is_hermitian(mat * 1j, rtol, atol)


Test patch:
diff --git a/toqito/matrix_props/tests/test_is_anti_hermitian.py b/toqito/matrix_props/tests/test_is_anti_hermitian.py
new file mode 100644
index 000000000..49b1175bf
--- /dev/null
+++ b/toqito/matrix_props/tests/test_is_anti_hermitian.py
@@ -0,0 +1,20 @@
+"""Test is_anti_hermitian."""
+
+import numpy as np
+import pytest
+
+from toqito.matrix_props import is_anti_hermitian
+
+data = [
+    # Test with anti-Hermitian matrix
+    (np.array([[2j, -1 + 2j, 4j], [1 + 2j, 3j, -1], [4j, 1, 1j]]), True),
+    # Test with non-anti-Hermitian matrix
+    (np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]]), False),
+    # Test with non-square matrix
+    (np.array([[-1, 1, 1], [1, 2, 3]]), False),
+]
+
+@pytest.mark.parametrize("mat,expected_bool", data)
+def test_is_anti_hermitian(mat, expected_bool):
+    """Test if matrix is anti-Hermitian."""
+    np.testing.assert_equal(is_anti_hermitian(mat), expected_bool)
diff --git a/toqito/matrix_props/tests/test_is_hermitian.py b/toqito/matrix_props/tests/test_is_hermitian.py
index 9f571cb4c..bb1a61032 100644
--- a/toqito/matrix_props/tests/test_is_hermitian.py
+++ b/toqito/matrix_props/tests/test_is_hermitian.py
@@ -1,23 +1,20 @@
 """Test is_hermitian."""
 
 import numpy as np
+import pytest
 
 from toqito.matrix_props import is_hermitian
 
-
-def test_is_hermitian():
+data = [
+    # Test with Hermitian matrix
+    (np.array([[2, 2 + 1j, 4], [2 - 1j, 3, 1j], [4, -1j, 1]]), True),
+    # Test with non-Hermitian matrix
+    (np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]]), False),
+    # Test with non-square matrix
+    (np.array([[-1, 1, 1], [1, 2, 3]]), False),
+]
+
+@pytest.mark.parametrize("mat,expected_bool", data)
+def test_is_hermitian(mat, expected_bool):
     """Test if matrix is Hermitian."""
-    mat = np.array([[2, 2 + 1j, 4], [2 - 1j, 3, 1j], [4, -1j, 1]])
-    np.testing.assert_equal(is_hermitian(mat), True)
-
-
-def test_is_non_hermitian():
-    """Test non-Hermitian matrix."""
-    mat = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
-    np.testing.assert_equal(is_hermitian(mat), False)
-
-
-def test_is_hermitian_not_square():
-    """Input must be a square matrix."""
-    mat = np.array([[-1, 1, 1], [1, 2, 3]])
-    np.testing.assert_equal(is_hermitian(mat), False)
+    np.testing.assert_equal(is_hermitian(mat), expected_bool)