Repository: sympy/sympy

Issue:
Dagger of constants breaks
```python
import sympy.physics.quantum
sympy.physics.quantum.Dagger(1)
```
raises 
```python
---------------------------------------------------------------------------
UnboundLocalError                         Traceback (most recent call last)
Cell In[6], line 2
      1 import sympy.physics.quantum
----> 2 sympy.physics.quantum.Dagger(1)

File /opt/conda/lib/python3.10/site-packages/sympy/physics/quantum/dagger.py:85, in Dagger.__new__(cls, arg)
     83 elif hasattr(arg, 'conjugate') and hasattr(arg, 'transpose'):
     84     obj = arg.conjugate().transpose()
---> 85 if obj is not None:
     86     return obj
     87 return Expr.__new__(cls, arg)

UnboundLocalError: local variable 'obj' referenced before assignment
```
I believe that either a more human-readable error or falling back to e.g. only complex conjugation would be better.

Do people have any preference for how this should be handled?

Related issue #17403

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:
* physics.quantum
  * Allowed `Dagger` to treat unrecognized inputs. ([#24850](https://github.com/sympy/sympy/pull/24850) by [@akhmerov](https://github.com/akhmerov))

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

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

    I've made `sympy.physics.quantum.Dagger` wrap unrecognized objects without complaining.

    #### References to other Issues or PRs
    closes #24664

    #### Brief description of what is fixed or changed

    Mainly just clean up a runaway `UnboundLocalError`

    #### Release Notes

    <!-- BEGIN RELEASE NOTES -->
    * physics.quantum
      * Allowed `Dagger` to treat unrecognized inputs.
    <!-- END RELEASE NOTES -->


</details><p>

bjodah: This looks like an improvement, but we should fix another thing while we are at it:

Since `Dagger` subclasses `Expr`, all objects stored in `.args` need to be instances of subclasses of `Basic`. We typically enforce this by calling `sympify` on the arguments.

So once fixed, this would actually hold:
```pycon
>>> arg, = Dagger(any_reasonable_argument).args
>>> assert isinstance(arg, Basic)
```

Then I wonder: would adding a keyword argument `evaluate=True` to `__new__` be useful? (right now there's no way of constructing a symbolic `Dagger` instance if the object being passed in has the method `.adjoint` (or `.transpose` + `.conjugate`).

Patch:
diff --git a/sympy/physics/quantum/dagger.py b/sympy/physics/quantum/dagger.py
index 44d3742689..6305a656c3 100644
--- a/sympy/physics/quantum/dagger.py
+++ b/sympy/physics/quantum/dagger.py
@@ -1,6 +1,6 @@
 """Hermitian conjugation."""
 
-from sympy.core import Expr, Mul
+from sympy.core import Expr, Mul, sympify
 from sympy.functions.elementary.complexes import adjoint
 
 __all__ = [
@@ -22,6 +22,8 @@ class Dagger(adjoint):
 
     arg : Expr
         The SymPy expression that we want to take the dagger of.
+    evaluate : bool
+        Whether the resulting expression should be directly evaluated.
 
     Examples
     ========
@@ -77,14 +79,12 @@ class Dagger(adjoint):
     .. [2] https://en.wikipedia.org/wiki/Hermitian_transpose
     """
 
-    def __new__(cls, arg):
-        if hasattr(arg, 'adjoint'):
-            obj = arg.adjoint()
-        elif hasattr(arg, 'conjugate') and hasattr(arg, 'transpose'):
-            obj = arg.conjugate().transpose()
-        if obj is not None:
-            return obj
-        return Expr.__new__(cls, arg)
+    def __new__(cls, arg, evaluate=True):
+        if hasattr(arg, 'adjoint') and evaluate:
+            return arg.adjoint()
+        elif hasattr(arg, 'conjugate') and hasattr(arg, 'transpose') and evaluate:
+            return arg.conjugate().transpose()
+        return Expr.__new__(cls, sympify(arg))
 
     def __mul__(self, other):
         from sympy.physics.quantum import IdentityOperator


Test patch:
diff --git a/sympy/physics/quantum/tests/test_dagger.py b/sympy/physics/quantum/tests/test_dagger.py
index 8c99575262..0d379095de 100644
--- a/sympy/physics/quantum/tests/test_dagger.py
+++ b/sympy/physics/quantum/tests/test_dagger.py
@@ -81,3 +81,22 @@ def test_scipy_sparse_dagger():
     a = sparse.csr_matrix([[1.0 + 0.0j, 2.0j], [-1.0j, 2.0 + 0.0j]])
     adag = a.copy().transpose().conjugate()
     assert np.linalg.norm((Dagger(a) - adag).todense()) == 0.0
+
+
+def test_unknown():
+    """Check treatment of unknown objects.
+    Objects without adjoint or conjugate/transpose methods
+    are sympified and wrapped in dagger.
+    """
+    x = symbols("x")
+    result = Dagger(x)
+    assert result.args == (x,) and isinstance(result, adjoint)
+
+
+def test_unevaluated():
+    """Check that evaluate=False returns unevaluated Dagger.
+    """
+    x = symbols("x", real=True)
+    assert Dagger(x) == x
+    result = Dagger(x, evaluate=False)
+    assert result.args == (x,) and isinstance(result, adjoint)