Repository: pasqal-io/Pulser

Issue:
Observable Raised a ValueError when leakage is turned on
Hey Pulser team, 

I found the following `raise ValueError(
ValueError: Incompatible shape of observable.Expected (4, 4), got (9, 9).` 

in the following code script

[pulser_issue.py](https://github.com/user-attachments/files/22408611/pulser_issue.py)

What I would like to do:
 - run a sequence with leakage and measure an observable at the end of the sequence, but it is telling me that the observable has a wrong shape. But in the previous line, I use `qutip.expect` without any issue. 


Discussion:

Fix (patch):
diff --git a/pulser-simulation/pulser_simulation/simresults.py b/pulser-simulation/pulser_simulation/simresults.py
index e3e490e..2ed70f1 100644
--- a/pulser-simulation/pulser_simulation/simresults.py
+++ b/pulser-simulation/pulser_simulation/simresults.py
@@ -59,13 +59,15 @@ class SimulationResults(ABC, ResultsSequence[ResultType]):
             sim_times: Array of times (in µs) when simulation results are
                 returned.
         """
-        self._dim = 3 if basis_name == "all" else 2
         self._size = size
         bases = ["ground-rydberg", "digital", "all", "XY"]
         bases += [basis + "_with_error" for basis in bases]
         if basis_name not in bases:
             raise ValueError(f"`basis_name` must be in {bases}")
         self._basis_name = basis_name
+        self._dim = 3 if self._basis_name == "all" else 2
+        if "_with_error" in self._basis_name:
+            self._dim += 1
         self._sim_times = sim_times
 
     @property


Test changes:
diff --git a/tests/pulser_simulation/test_simresults.py b/tests/pulser_simulation/test_simresults.py
index 7be23f5..a974bd0 100644
--- a/tests/pulser_simulation/test_simresults.py
+++ b/tests/pulser_simulation/test_simresults.py
@@ -286,7 +286,7 @@ def test_expect(results, pi_pulse, reg):
         results_single._calc_pseudo_density(-1).full(),
         np.array([[1, 0], [0, 0]]),
     )
-
+    # With SPAM errors
     noise_model = NoiseModel(p_false_pos=0.01, p_false_neg=0.05)
     sim_single = QutipEmulator.from_sequence(
         seq_single, noise_model=noise_model
@@ -310,6 +310,27 @@ def test_expect(results, pi_pulse, reg):
             [[1 - noise_model.p_false_neg, 0], [0, noise_model.p_false_neg]]
         ),
     )
+    # With leakage
+    eff_rate = [0.5]
+    eff_ops = [qutip.basis(3, 2) @ qutip.basis(3, 1).dag()]
+    noise_model = NoiseModel(
+        eff_noise_rates=eff_rate,
+        eff_noise_opers=eff_ops,
+        with_leakage=True,
+    )
+    sim_single = QutipEmulator.from_sequence(
+        seq_single,
+        noise_model=noise_model,
+        sampling_rate=0.1,
+    )
+    sim_single.set_evaluation_times(0.5)
+    res = sim_single.run()
+    assert isinstance(res, CoherentResults)
+    # define an observable for the state
+    assert np.isclose(
+        res.expect([qutip.basis(3, 0).proj()])[0][-1], 0.7804005, atol=1e-6
+    )  # ground state projector
+
     seq3dim = Sequence(reg, DigitalAnalogDevice)
     seq3dim.declare_channel("ryd", "rydberg_global")
     seq3dim.declare_channel("ram", "raman_local", initial_target="A")