Repository: PennyLaneAI/pennylane
Pull request #5535 (2024-04-17 20:42:07)

Bugfix: Cannot initialize `ParticleConservingU1/2` with default `init_state`
**Context:** `qml.ParticleConservingU1` and `qml.ParticleConservingU2` cannot be initialized with their default value for the `initial_state` argument (the default value is `None`). 

**Description of the Change:** If the `initial_state` argument is not specified, it is set to a tuple of zeros, which seems a reasonable choice for an initial state in physics. This modification occurs only if the initial state is not specified and the information is reported in the class's docstring.

**Benefits:** The error is no longer raised when the initial state is not specified, and the class works with the default argument.

**Possible Drawbacks:** As mentioned above, this modification only occurs if the initial state is not specified and should not cause any issues.

**Related GitHub Issues:** #5522 

[sc-61431]


github-actions[bot]: Hello. You may have forgotten to update the changelog!
Please edit [`doc/releases/changelog-dev.md`](/PennyLaneAI/pennylane/blob/master/doc/releases/changelog-dev.md) with:
* A one-to-two sentence description of the change. You may include a small working example for new features.
* A link back to this PR.
* Your name (or GitHub username) in the contributors section.
codecov[bot]: ## [Codecov](https://app.codecov.io/gh/PennyLaneAI/pennylane/pull/5535?dropdown=coverage&src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI) Report
All modified and coverable lines are covered by tests :white_check_mark:
> Project coverage is 99.66%. Comparing base [(`a47d9bc`)](https://app.codecov.io/gh/PennyLaneAI/pennylane/commit/a47d9bc8298e7ed8e4813b4707299d8d327c6a1b?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI) to head [(`d80b7d3`)](https://app.codecov.io/gh/PennyLaneAI/pennylane/pull/5535?dropdown=coverage&src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI).
> Report is 2 commits behind head on master.


<details><summary>Additional details and impacted files</summary>


```diff
@@            Coverage Diff             @@
##           master    #5535      +/-   ##
==========================================
- Coverage   99.67%   99.66%   -0.01%     
==========================================
  Files         406      406              
  Lines       37881    37625     -256     
==========================================
- Hits        37758    37500     -258     
- Misses        123      125       +2     
```



</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/PennyLaneAI/pennylane/pull/5535?dropdown=coverage&src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI).   
:loudspeaker: Have feedback on the report? [Share it here](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI).

PietropaoloFrisoni: > Would there be any benefit in creating a numpy array directly, rather than a tuple, as the default value? Either way works for me.

@dwierichs That's what I did at first [(see this commit)](https://github.com/PennyLaneAI/pennylane/pull/5535/commits/2e418672fdf2eb5ea40b9891c8912e2aad29e7a0), but I ended up changing my mind since the initial state is then converted to a tuple anyway

Patch:
diff --git a/doc/releases/changelog-dev.md b/doc/releases/changelog-dev.md
index 1f4ba35cd..780c64a2a 100644
--- a/doc/releases/changelog-dev.md
+++ b/doc/releases/changelog-dev.md
@@ -380,6 +380,9 @@
 
 <h3>Bug fixes 🐛</h3>
 
+* `qml.ParticleConservingU1` and `qml.ParticleConservingU2` no longer raise an error when the initial state is not specified but default to the all-zeros state.
+  [(#5535)](https://github.com/PennyLaneAI/pennylane/pull/5535)
+
 * `qml.counts` no longer returns negative samples when measuring 8 or more wires.
   [(#5544)](https://github.com/PennyLaneAI/pennylane/pull/5544)
 
diff --git a/pennylane/templates/layers/particle_conserving_u1.py b/pennylane/templates/layers/particle_conserving_u1.py
index 3b6430e3d..51a825d3b 100644
--- a/pennylane/templates/layers/particle_conserving_u1.py
+++ b/pennylane/templates/layers/particle_conserving_u1.py
@@ -183,9 +183,9 @@ class ParticleConservingU1(Operation):
         weights (tensor_like): Array of weights of shape ``(D, M, 2)``.
             ``D`` is the number of entangler block layers and :math:`M=N-1`
             is the number of exchange gates :math:`U_{1,\mathrm{ex}}` per layer.
-        wires (Iterable): wires that the template acts on
+        wires (Iterable): wires that the template acts on.
         init_state (tensor_like): iterable or shape ``(len(wires),)`` tensor representing the Hartree-Fock state
-            used to initialize the wires
+            used to initialize the wires. If ``None``, a tuple of zeros is selected as initial state.
 
     .. details::
         :title: Usage Details
@@ -266,6 +266,8 @@ class ParticleConservingU1(Operation):
                 f"Weights tensor must have third dimension of length 2; got {shape[2]}"
             )
 
+        init_state = tuple(0 for _ in wires) if init_state is None else init_state
+
         self._hyperparameters = {"init_state": tuple(init_state)}
 
         super().__init__(weights, wires=wires, id=id)
diff --git a/pennylane/templates/layers/particle_conserving_u2.py b/pennylane/templates/layers/particle_conserving_u2.py
index 70fa3f12c..47a5ee0ab 100644
--- a/pennylane/templates/layers/particle_conserving_u2.py
+++ b/pennylane/templates/layers/particle_conserving_u2.py
@@ -93,9 +93,9 @@ class ParticleConservingU2(Operation):
         weights (tensor_like): Weight tensor of shape ``(D, M)`` where ``D`` is the number of
             layers and ``M`` = ``2N-1`` is the total number of rotation ``(N)`` and exchange
             ``(N-1)`` gates per layer.
-        wires (Iterable): wires that the template acts on
+        wires (Iterable): wires that the template acts on.
         init_state (tensor_like): iterable or shape ``(len(wires),)`` tensor representing the Hartree-Fock state
-            used to initialize the wires.
+            used to initialize the wires. If ``None``, a tuple of zeros is selected as initial state.
 
     .. details::
         :title: Usage Details
@@ -171,6 +171,8 @@ class ParticleConservingU2(Operation):
                 f"Weights tensor must have a second dimension of length {2 * len(wires) - 1}; got {shape[1]}"
             )
 
+        init_state = tuple(0 for _ in wires) if init_state is None else init_state
+
         self._hyperparameters = {"init_state": tuple(init_state)}
 
         super().__init__(weights, wires=wires, id=id)


Test patch:
diff --git a/tests/templates/test_layers/test_particle_conserving_u1.py b/tests/templates/test_layers/test_particle_conserving_u1.py
index facc6c242..443bb2e8b 100644
--- a/tests/templates/test_layers/test_particle_conserving_u1.py
+++ b/tests/templates/test_layers/test_particle_conserving_u1.py
@@ -20,11 +20,11 @@ import pennylane as qml
 from pennylane import numpy as pnp
 
 
-def test_standard_validity():
+@pytest.mark.parametrize("init_state", [np.array([1, 1, 0]), None])
+def test_standard_validity(init_state):
     """Check the operation using the assert_valid function."""
 
     weights = np.random.random(size=(1, 2, 2))
-    init_state = np.array([1, 1, 0])
     op = qml.ParticleConservingU1(weights, wires=range(3), init_state=init_state)
 
     qml.ops.functions.assert_valid(op)
diff --git a/tests/templates/test_layers/test_particle_conserving_u2.py b/tests/templates/test_layers/test_particle_conserving_u2.py
index baa40438b..feef54b4d 100644
--- a/tests/templates/test_layers/test_particle_conserving_u2.py
+++ b/tests/templates/test_layers/test_particle_conserving_u2.py
@@ -20,14 +20,14 @@ import pennylane as qml
 from pennylane import numpy as pnp
 
 
-def test_standard_validity():
+@pytest.mark.parametrize("init_state", [np.array([1, 1, 0, 0]), None])
+def test_standard_validity(init_state):
     """Run standard checks with the assert_valid function."""
 
     layers = 2
     qubits = 4
 
     weights = np.random.normal(0, 2 * np.pi, (layers, 2 * qubits - 1))
-    init_state = np.array([1, 1, 0, 0])
     op = qml.ParticleConservingU2(weights, wires=range(qubits), init_state=init_state)
     qml.ops.functions.assert_valid(op)