We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Propagator solvers are cached on slighlty changing `delta_t`
Both the `sesolve` and `mesolve` propagator solvers are cached on the time step `delta_t` to take, which should be constant for linearly spaced `t_save`. Thus, the propagator should be computed only once. However, due to numerical imprecisions, the `delta_t` changes slightly even when `t_save` is linearly spaced, resulting in frequent recomputations of the same quantity.

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `dynamiqs/solvers/propagator.py`
Content:
```
1 from abc import abstractmethod
2 
3 from torch import Tensor
4 
5 from .solver import AutogradSolver
6 from .utils.td_tensor import ConstantTDTensor
7 from .utils.utils import tqdm
8 
9 
10 class Propagator(AutogradSolver):
11     def __init__(self, *args, **kwargs):
12         super().__init__(*args, **kwargs)
13 
14         # check that Hamiltonian is time-independent
15         if not isinstance(self.H, ConstantTDTensor):
16             raise TypeError(
17                 'Solver `Propagator` requires a time-independent Hamiltonian.'
18             )
19         self.H = self.H(0.0)
20 
21     def run_autograd(self):
22         y, t1 = self.y0, 0.0
23         for t2 in tqdm(self.t_stop.cpu().numpy(), disable=not self.options.verbose):
24             y = self.forward(t1, t2 - t1, y)
25             self.save(y)
26             t1 = t2
27 
28     @abstractmethod
29     def forward(self, t: float, delta_t: float, y: Tensor):
30         pass
31
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/dynamiqs/solvers/propagator.py b/dynamiqs/solvers/propagator.py
--- a/dynamiqs/solvers/propagator.py
+++ b/dynamiqs/solvers/propagator.py
@@ -1,5 +1,8 @@
+from __future__ import annotations
+
 from abc import abstractmethod
 
+import numpy as np
 from torch import Tensor
 
 from .solver import AutogradSolver
@@ -7,6 +10,19 @@
 from .utils.utils import tqdm
 
 
+def round_truncate(x: np.float32 | np.float64) -> np.float32 | np.float64:
+    # round a strictly positive-valued float to remove numerical errors, and enable
+    # comparing floats for equality
+
+    # The mantissa of a float32 is stored using 23 bits. The following code rounds and
+    # truncates the float value to the 18 most significant bits of its mantissa. This
+    # removes any numerical error that may have accumulated in the 5 least significant
+    # bits of the mantissa.
+    leading = abs(int(np.log2(x)))
+    keep = leading + 18
+    return (x * 2**keep).round() / 2**keep
+
+
 class Propagator(AutogradSolver):
     def __init__(self, *args, **kwargs):
         super().__init__(*args, **kwargs)
@@ -21,7 +37,10 @@
     def run_autograd(self):
         y, t1 = self.y0, 0.0
         for t2 in tqdm(self.t_stop.cpu().numpy(), disable=not self.options.verbose):
-            y = self.forward(t1, t2 - t1, y)
+            if t2 != 0.0:
+                # round time difference to avoid numerical errors when comparing floats
+                delta_t = round_truncate(t2 - t1)
+                y = self.forward(t1, delta_t, y)
             self.save(y)
             t1 = t2

{"golden_diff": "diff --git a/dynamiqs/solvers/propagator.py b/dynamiqs/solvers/propagator.py\n--- a/dynamiqs/solvers/propagator.py\n+++ b/dynamiqs/solvers/propagator.py\n@@ -1,5 +1,8 @@\n+from __future__ import annotations\n+\n from abc import abstractmethod\n \n+import numpy as np\n from torch import Tensor\n \n from .solver import AutogradSolver\n@@ -7,6 +10,19 @@\n from .utils.utils import tqdm\n \n \n+def round_truncate(x: np.float32 | np.float64) -> np.float32 | np.float64:\n+    # round a strictly positive-valued float to remove numerical errors, and enable\n+    # comparing floats for equality\n+\n+    # The mantissa of a float32 is stored using 23 bits. The following code rounds and\n+    # truncates the float value to the 18 most significant bits of its mantissa. This\n+    # removes any numerical error that may have accumulated in the 5 least significant\n+    # bits of the mantissa.\n+    leading = abs(int(np.log2(x)))\n+    keep = leading + 18\n+    return (x * 2**keep).round() / 2**keep\n+\n+\n class Propagator(AutogradSolver):\n     def __init__(self, *args, **kwargs):\n         super().__init__(*args, **kwargs)\n@@ -21,7 +37,10 @@\n     def run_autograd(self):\n         y, t1 = self.y0, 0.0\n         for t2 in tqdm(self.t_stop.cpu().numpy(), disable=not self.options.verbose):\n-            y = self.forward(t1, t2 - t1, y)\n+            if t2 != 0.0:\n+                # round time difference to avoid numerical errors when comparing floats\n+                delta_t = round_truncate(t2 - t1)\n+                y = self.forward(t1, delta_t, y)\n             self.save(y)\n             t1 = t2\n", "issue": "Propagator solvers are cached on slighlty changing `delta_t`\nBoth the `sesolve` and `mesolve` propagator solvers are cached on the time step `delta_t` to take, which should be constant for linearly spaced `t_save`. Thus, the propagator should be computed only once. However, due to numerical imprecisions, the `delta_t` changes slightly even when `t_save` is linearly spaced, resulting in frequent recomputations of the same quantity.\n", "before_files": [{"content": "from abc import abstractmethod\n\nfrom torch import Tensor\n\nfrom .solver import AutogradSolver\nfrom .utils.td_tensor import ConstantTDTensor\nfrom .utils.utils import tqdm\n\n\nclass Propagator(AutogradSolver):\n    def __init__(self, *args, **kwargs):\n        super().__init__(*args, **kwargs)\n\n        # check that Hamiltonian is time-independent\n        if not isinstance(self.H, ConstantTDTensor):\n            raise TypeError(\n                'Solver `Propagator` requires a time-independent Hamiltonian.'\n            )\n        self.H = self.H(0.0)\n\n    def run_autograd(self):\n        y, t1 = self.y0, 0.0\n        for t2 in tqdm(self.t_stop.cpu().numpy(), disable=not self.options.verbose):\n            y = self.forward(t1, t2 - t1, y)\n            self.save(y)\n            t1 = t2\n\n    @abstractmethod\n    def forward(self, t: float, delta_t: float, y: Tensor):\n        pass\n", "path": "dynamiqs/solvers/propagator.py"}], "after_files": [{"content": "from __future__ import annotations\n\nfrom abc import abstractmethod\n\nimport numpy as np\nfrom torch import Tensor\n\nfrom .solver import AutogradSolver\nfrom .utils.td_tensor import ConstantTDTensor\nfrom .utils.utils import tqdm\n\n\ndef round_truncate(x: np.float32 | np.float64) -> np.float32 | np.float64:\n    # round a strictly positive-valued float to remove numerical errors, and enable\n    # comparing floats for equality\n\n    # The mantissa of a float32 is stored using 23 bits. The following code rounds and\n    # truncates the float value to the 18 most significant bits of its mantissa. This\n    # removes any numerical error that may have accumulated in the 5 least significant\n    # bits of the mantissa.\n    leading = abs(int(np.log2(x)))\n    keep = leading + 18\n    return (x * 2**keep).round() / 2**keep\n\n\nclass Propagator(AutogradSolver):\n    def __init__(self, *args, **kwargs):\n        super().__init__(*args, **kwargs)\n\n        # check that Hamiltonian is time-independent\n        if not isinstance(self.H, ConstantTDTensor):\n            raise TypeError(\n                'Solver `Propagator` requires a time-independent Hamiltonian.'\n            )\n        self.H = self.H(0.0)\n\n    def run_autograd(self):\n        y, t1 = self.y0, 0.0\n        for t2 in tqdm(self.t_stop.cpu().numpy(), disable=not self.options.verbose):\n            if t2 != 0.0:\n                # round time difference to avoid numerical errors when comparing floats\n                delta_t = round_truncate(t2 - t1)\n                y = self.forward(t1, delta_t, y)\n            self.save(y)\n            t1 = t2\n\n    @abstractmethod\n    def forward(self, t: float, delta_t: float, y: Tensor):\n        pass\n", "path": "dynamiqs/solvers/propagator.py"}]}