Repository: sympy/sympy

Issue:
Adjoint of matrices should apply adjoint and not conjugate to matrix elements
The following code:
```python
import sympy
from sympy.physics.quantum import Operator, Dagger
Dagger(sympy.Matrix([[Operator("a")]])) == sympy.Matrix([[Operator("a").conjugate()]])
```
returns true, however the right hand side should instead be `sympy.Matrix([[Dagger(Operator("a")]])`.

Discussion:
sympy-bot: :white_check_mark:

Hi, I am the [SymPy bot](https://github.com/sympy/sympy-bot). I'm here to help you write a release notes entry. Please read the [guide on how to write release notes](https://github.com/sympy/sympy/wiki/Writing-Release-Notes).



Your release notes are in good order.

Here is what the release notes will look like:
* matrices
  * Fixed adjoint of a matrix containing operators to apply adjoint to the operators instead of conjugate. ([#27422](https://github.com/sympy/sympy/pull/27422) by [@akhmerov](https://github.com/akhmerov))

This will be added to https://github.com/sympy/sympy/wiki/Release-Notes-for-1.14.

<details><summary>Click here to see the pull request description that was parsed.</summary>

    This changes the implementation of matrix adjoint to propagate adjoint and not conjugate to matrix elements. It also simplifies the implementation of adjoint of a BlockMatrix.

    #### References to other Issues or PRs
    Closes #25130. 
    Supersedes #25174, which takes an alternative much more fragile approach of only patching some operators occurring as matrix elements.

    #### Other comments

    The original problem from #25130 is that matrix adjoint produces invalid results if some of the matrix elements are `sympy.physics.quantum.operator.Operator`s. I believe that my fix makes the semantics of adjoints of matrices containing nontrivial entries more correct. There are still open related questions, because for example if a `matrix` has operator values, then `matrix.transpose().conjugate()` would produce something different than `matrix.adjoint()`. I believe this is OK for now because transpose and conjugate are not well defined on operators, and it is a lot less likely that someone would write this code.

    #### Release Notes

    <!-- BEGIN RELEASE NOTES -->
    * matrices
      * Fixed adjoint of a matrix containing operators to apply adjoint to the operators instead of conjugate.
    <!-- END RELEASE NOTES -->


</details><p>


Patch:
diff --git a/sympy/concrete/products.py b/sympy/concrete/products.py
index c23c7212d7..e6a51f105a 100644
--- a/sympy/concrete/products.py
+++ b/sympy/concrete/products.py
@@ -285,11 +285,6 @@ def doit(self, **hints):
         else:
             return powsimp(f)
 
-    def _eval_adjoint(self):
-        if self.is_commutative:
-            return self.func(self.function.adjoint(), *self.limits)
-        return None
-
     def _eval_conjugate(self):
         return self.func(self.function.conjugate(), *self.limits)
 
diff --git a/sympy/core/expr.py b/sympy/core/expr.py
index 4f4d63ed74..7e78484c2f 100644
--- a/sympy/core/expr.py
+++ b/sympy/core/expr.py
@@ -1059,7 +1059,7 @@ def dir(self, x, cdir):
 
     def _eval_transpose(self):
         from sympy.functions.elementary.complexes import conjugate
-        if (self.is_complex or self.is_infinite):
+        if self.is_commutative:
             return self
         elif self.is_hermitian:
             return conjugate(self)
diff --git a/sympy/matrices/expressions/blockmatrix.py b/sympy/matrices/expressions/blockmatrix.py
index 4b5b840010..0125d6233b 100644
--- a/sympy/matrices/expressions/blockmatrix.py
+++ b/sympy/matrices/expressions/blockmatrix.py
@@ -1,7 +1,6 @@
 from sympy.assumptions.ask import (Q, ask)
 from sympy.core import Basic, Add, Mul, S
 from sympy.core.sympify import _sympify
-from sympy.functions import adjoint
 from sympy.functions.elementary.complexes import re, im
 from sympy.strategies import typed, exhaust, condition, do_one, unpack
 from sympy.strategies.traverse import bottom_up
@@ -187,13 +186,9 @@ def _eval_transpose(self):
         return BlockMatrix(M)
 
     def _eval_adjoint(self):
-        # Adjoint all the individual matrices
-        matrices = [adjoint(matrix) for matrix in self.blocks]
-        # Make a copy
-        M = Matrix(self.blockshape[0], self.blockshape[1], matrices)
-        # Transpose the block structure
-        M = M.transpose()
-        return BlockMatrix(M)
+        return BlockMatrix(
+            Matrix(self.blockshape[0], self.blockshape[1], self.blocks).adjoint()
+        )
 
     def _eval_trace(self):
         if self.rowblocksizes == self.colblocksizes:
diff --git a/sympy/matrices/matrixbase.py b/sympy/matrices/matrixbase.py
index 442f8c37fe..f994f362bf 100644
--- a/sympy/matrices/matrixbase.py
+++ b/sympy/matrices/matrixbase.py
@@ -2029,7 +2029,7 @@ def iter_items(self):
         return self._eval_iter_items()
 
     def _eval_adjoint(self):
-        return self.transpose().conjugate()
+        return self.transpose().applyfunc(lambda x: x.adjoint())
 
     def _eval_applyfunc(self, f):
         cols = self.cols
@@ -2191,7 +2191,7 @@ def H(self):
         conjugate: By-element conjugation
         sympy.matrices.matrixbase.MatrixBase.D: Dirac conjugation
         """
-        return self.T.C
+        return self.adjoint()
 
     def permute(self, perm, orientation='rows', direction='forward'):
         r"""Permute the rows or columns of a matrix by the given list of


Test patch:
diff --git a/sympy/functions/elementary/tests/test_complexes.py b/sympy/functions/elementary/tests/test_complexes.py
index be2a45cd9b..699c0fef96 100644
--- a/sympy/functions/elementary/tests/test_complexes.py
+++ b/sympy/functions/elementary/tests/test_complexes.py
@@ -1,4 +1,3 @@
-from sympy.core.expr import Expr
 from sympy.core.function import (Derivative, Function, Lambda, expand, PoleError)
 from sympy.core.numbers import (E, I, Rational, comp, nan, oo, pi, zoo)
 from sympy.core.relational import Eq
@@ -695,11 +694,11 @@ def test_adjoint():
 
     x, y = symbols('x y')
     assert adjoint(adjoint(x)) == x
-    assert adjoint(x + y) == adjoint(x) + adjoint(y)
-    assert adjoint(x - y) == adjoint(x) - adjoint(y)
-    assert adjoint(x * y) == adjoint(x) * adjoint(y)
-    assert adjoint(x / y) == adjoint(x) / adjoint(y)
-    assert adjoint(-x) == -adjoint(x)
+    assert adjoint(x + y) == conjugate(x) + conjugate(y)
+    assert adjoint(x - y) == conjugate(x) - conjugate(y)
+    assert adjoint(x * y) == conjugate(x) * conjugate(y)
+    assert adjoint(x / y) == conjugate(x) / conjugate(y)
+    assert adjoint(-x) == -conjugate(x)
 
     x, y = symbols('x y', commutative=False)
     assert adjoint(adjoint(x)) == x
@@ -737,7 +736,7 @@ def test_conjugate():
 
 
 def test_conjugate_transpose():
-    x = Symbol('x')
+    x = Symbol('x', commutative=False)
     assert conjugate(transpose(x)) == adjoint(x)
     assert transpose(conjugate(x)) == adjoint(x)
     assert adjoint(transpose(x)) == conjugate(x)
@@ -745,16 +744,7 @@ def test_conjugate_transpose():
     assert adjoint(conjugate(x)) == transpose(x)
     assert conjugate(adjoint(x)) == transpose(x)
 
-    class Symmetric(Expr):
-        def _eval_adjoint(self):
-            return None
-
-        def _eval_conjugate(self):
-            return None
-
-        def _eval_transpose(self):
-            return self
-    x = Symmetric()
+    x = Symbol('x')
     assert conjugate(x) == adjoint(x)
     assert transpose(x) == x
 
@@ -766,11 +756,11 @@ def test_transpose():
 
     x, y = symbols('x y')
     assert transpose(transpose(x)) == x
-    assert transpose(x + y) == transpose(x) + transpose(y)
-    assert transpose(x - y) == transpose(x) - transpose(y)
-    assert transpose(x * y) == transpose(x) * transpose(y)
-    assert transpose(x / y) == transpose(x) / transpose(y)
-    assert transpose(-x) == -transpose(x)
+    assert transpose(x + y) == x + y
+    assert transpose(x - y) == x - y
+    assert transpose(x * y) == x * y
+    assert transpose(x / y) == x / y
+    assert transpose(-x) == -x
 
     x, y = symbols('x y', commutative=False)
     assert transpose(transpose(x)) == x
diff --git a/sympy/functions/elementary/tests/test_exponential.py b/sympy/functions/elementary/tests/test_exponential.py
index 9ababee840..ee8c311d01 100644
--- a/sympy/functions/elementary/tests/test_exponential.py
+++ b/sympy/functions/elementary/tests/test_exponential.py
@@ -161,6 +161,7 @@ def test_exp_subs():
 
 
 def test_exp_adjoint():
+    x = Symbol('x', commutative=False)
     assert adjoint(exp(x)) == exp(adjoint(x))
 
 
diff --git a/sympy/matrices/tests/test_matrices.py b/sympy/matrices/tests/test_matrices.py
index a5a1bf86ee..4fb69a9afe 100644
--- a/sympy/matrices/tests/test_matrices.py
+++ b/sympy/matrices/tests/test_matrices.py
@@ -2639,6 +2639,18 @@ def test_adjoint():
     for cls in classes:
         assert ans == cls(dat).adjoint()
 
+
+def test_adjoint_with_operator():
+    # Regression test for issue 25130: adjoint() should propagate to operators
+    import sympy.physics.quantum
+    a = sympy.physics.quantum.operator.Operator('a')
+    a_dag = sympy.physics.quantum.Dagger(a)
+    dat = [[0, I * a], [0, a_dag]]
+    ans = Matrix([[0, 0], [-I * a_dag, a]])
+    for cls in classes:
+        assert ans == cls(dat).adjoint()
+
+
 def test_simplify_immutable():
     assert simplify(ImmutableMatrix([[sin(x)**2 + cos(x)**2]])) == \
                     ImmutableMatrix([[1]])
diff --git a/sympy/physics/quantum/tests/test_dagger.py b/sympy/physics/quantum/tests/test_dagger.py
index 8aff96506d..1357c9320a 100644
--- a/sympy/physics/quantum/tests/test_dagger.py
+++ b/sympy/physics/quantum/tests/test_dagger.py
@@ -20,7 +20,7 @@ def test_scalars():
     assert Dagger(i) == i
 
     p = symbols('p')
-    assert isinstance(Dagger(p), adjoint)
+    assert isinstance(Dagger(p), conjugate)
 
     i = Integer(3)
     assert Dagger(i) == i
@@ -89,7 +89,7 @@ def test_unknown():
     Objects without adjoint or conjugate/transpose methods
     are sympified and wrapped in dagger.
     """
-    x = symbols("x")
+    x = symbols("x", commutative=False)
     result = Dagger(x)
     assert result.args == (x,) and isinstance(result, adjoint)
 
diff --git a/sympy/physics/quantum/tests/test_printing.py b/sympy/physics/quantum/tests/test_printing.py
index 9e7c485af5..ce4004cee2 100644
--- a/sympy/physics/quantum/tests/test_printing.py
+++ b/sympy/physics/quantum/tests/test_printing.py
@@ -220,7 +220,7 @@ def test_constants():
 
 
 def test_dagger():
-    x = symbols('x')
+    x = symbols('x', commutative=False)
     expr = Dagger(x)
     assert str(expr) == 'Dagger(x)'
     ascii_str = \
@@ -236,7 +236,7 @@ def test_dagger():
     assert pretty(expr) == ascii_str
     assert upretty(expr) == ucode_str
     assert latex(expr) == r'x^{\dagger}'
-    sT(expr, "Dagger(Symbol('x'))")
+    sT(expr, "Dagger(Symbol('x', commutative=False))")
 
 
 @XFAIL
diff --git a/sympy/printing/tests/test_mathml.py b/sympy/printing/tests/test_mathml.py
index 88caad269c..3d0016f471 100644
--- a/sympy/printing/tests/test_mathml.py
+++ b/sympy/printing/tests/test_mathml.py
@@ -1351,6 +1351,7 @@ def test_print_Contains():
 
 
 def test_print_Dagger():
+    x = symbols('x', commutative=False)
     assert mpp.doprint(Dagger(x)) == '<msup><mi>x</mi>&#x2020;</msup>'