diff --git a/CITATION.cff b/CITATION.cff
index 38c1a373..5bf1174d 100644
--- a/CITATION.cff
+++ b/CITATION.cff
@@ -39,5 +39,5 @@ abstract: >-
keywords:
- analog quantum computing
- emulation
-version: 3.0.0
+version: 2.10.0rc1
date-released: '2026-07-31'
diff --git a/ci/emu_mps/pyproject.toml b/ci/emu_mps/pyproject.toml
index 71cc9dfa..8db8235f 100644
--- a/ci/emu_mps/pyproject.toml
+++ b/ci/emu_mps/pyproject.toml
@@ -24,7 +24,7 @@ classifiers=[
]
dynamic = ["version"]
dependencies = [
- "emu-base==3.0.0"]
+ "emu-base==2.10.0rc1"]
[project.urls]
Documentation = "https://pasqal-io.github.io/emulators/"
diff --git a/ci/emu_sv/pyproject.toml b/ci/emu_sv/pyproject.toml
index 3fa92f20..64ec9c44 100644
--- a/ci/emu_sv/pyproject.toml
+++ b/ci/emu_sv/pyproject.toml
@@ -24,7 +24,7 @@ classifiers=[
]
dynamic = ["version"]
dependencies = [
- "emu-base==3.0.0"]
+ "emu-base==2.10.0rc1"]
[project.urls]
Documentation = "https://pasqal-io.github.io/emulators/"
diff --git a/docs/emu_mps/advanced/resource_estimation.md b/docs/emu_mps/advanced/resource_estimation.md
index ec3999a9..066dec99 100644
--- a/docs/emu_mps/advanced/resource_estimation.md
+++ b/docs/emu_mps/advanced/resource_estimation.md
@@ -44,10 +44,10 @@ $$
|\mathrm{bath_{CPU}}| < 2s\chi^2\sum_{i=0}^{N/2-1}(3+i) = 4\chi^2N(N+10).
$$
-Just as for the state this is a strict over-estimation that is asymptotically correct, because it assumes all the bonds in the state are of size $\chi$. Note that the baths take up more memory than the state by a factor $O(N)$, where the prefactor depends on the interaction type (for Rydberg it is $1/4$), making this term the most dominant for large qubit numbers. For this reason those baths which are not used in the Lanczos algorithm are always stored on CPU, even when the emulator is configured to use GPU. By overlapping the data tranfers with other computations, we can avoid runtime impact at the cost of occasionally having a third bath tensor present on the GPU during the Lanczos algorithm. In this case we get
+Just as for the state this is a strict over-estimation that is asymptotically correct, because it assumes all the bonds in the state are of size $\chi$. Note that the baths take up more memory than the state by a factor $O(N)$, where the prefactor depends on the interaction type (for Rydberg it is $1/8$), making this term the most dominant for large qubit numbers. For this reason those baths which are not used in the Lanczos algorithm are always stored on CPU, even when the emulator is configured to use GPU. By overlapping the data transfers with other computations, we can avoid runtime impact at the cost of occasionally having a third bath tensor present on the GPU during the Lanczos algorithm. In this case we get
$$
-|\mathrm{bath_GPU}| \leq 2s\chi^2(3h_{max}-1) = 16\chi^2(\frac{3N}{2}+5).
+|\mathrm{bath_GPU}| \leq s\chi^2(3h_{max}-1) = 16\chi^2(\frac{3N}{2}+5).
$$
### Contribution from the Krylov space
@@ -62,10 +62,10 @@ where $k$ is the value of `max_krylov_dim`. Recall that the default value of $k=
### Contribution from temporary tensors
-Finally, to compute the above Krylov vectors, the effective two-site Hamiltonian has to be applied to the previous Krylov vector to obtain the next one. The resulting tensor network contraction cannot be done in-place, so it has to store two intermediate results that get very large. The intermediate results take the most memory at the center qubit, where the bond dimension of the Hamiltonian becomes $h$. At this point,
+Finally, to compute the above Krylov vectors, the effective two-site Hamiltonian has to be applied to the previous Krylov vector to obtain the next one. The resulting tensor network contraction cannot be done in-place, so it has to store two intermediate results that get very large. Additionally, the contraction requires storage of a permuted copy of one the two bath tensors. The intermediate results take the most memory at the center qubit, where the bond dimension of the Hamiltonian becomes $h$. At this point,
$$
-|\mathrm{intermediate}| = 2*shp^2\chi^2 = 128h\chi^2
+|\mathrm{intermediate}| = s\chi^2 (2hp^2+h) = h\chi^2(144)
$$
It should be noted that the value of $h$ cited above assumes that all qubits in the system interact via a two-body term, which is technically true for the Rydberg interaction. However, small terms in the interaction matrix can be truncated away via a configuration option, and this will save memory.
@@ -75,34 +75,37 @@ It should be noted that the value of $h$ cited above assumes that all qubits in
Putting all of this together, for the total memory consumption $m$ of the program, we can write the following bound for the CPU:
$$
- m_{CPU}(N,\chi,k) = |\psi| + |\mathrm{bath_{CPU}}| + |\mathrm{krylov}| + |\mathrm{intermediate}| < 32N\chi^2 + 4\chi^2N(N+10) + 64*k*\chi^2 + 64(N+4)\chi^2 = 4\chi^2[N(N+34) + 16k + 64].
+ m_{CPU}(N,\chi,k) = |\psi| + |\mathrm{bath_{CPU}}| + |\mathrm{krylov}| + |\mathrm{intermediate}| < 32N\chi^2 + 4\chi^2N(N+10) + 64*k*\chi^2 + 72(N+4)\chi^2 = 4\chi^2[N(N+36) + 16k + 72].
$$
The quadratic scaling in $N$ is due to the bath contribution. On the GPU, we get a linear scaling instead:
$$
- m_{GPU}(N,\chi,k) = |\psi| + |\mathrm{bath_{GPU}}| + |\mathrm{krylov}| + |\mathrm{intermediate}| < 32N\chi^2 + 16\chi^2(\frac{3N}{2}+5) + 64*k*\chi^2 + 64(N+4)\chi^2 = 8\chi^2[15N + 8k+42]
+ m_{GPU}(N,\chi,k) = |\psi| + |\mathrm{bath_{GPU}}| + |\mathrm{krylov}| + |\mathrm{intermediate}| < 32N\chi^2 + 16\chi^2(\frac{3N}{2}+5) + 64*k*\chi^2 + 72(N+4)\chi^2 = 8\chi^2[16N + 8k+46]
$$
-Note that this estimate is __pessimistic__, since not all $k$ Krylov vectors are likely to be needed, and not all tensors in $\psi$ and the baths have the maximum bond dimension $d$. On the other hand, the estimate for $|intermediate|$ is likely to be accurate, since the bond dimension of $\chi$ is probably attained at the center qubit if the sequence is long enough.
+In this case, there is of course a quadratic scaling in the consumption of CPU RAM. For large system sizes, this will outstrip the memory requirements on the GPU, and it becomes important to ensure enough RAM is available for the program to run.
+
+Note that these estimates are __pessimistic__, since not all $k$ Krylov vectors are likely to be needed, and not all tensors in $\psi$ and the baths have the maximum bond dimension $d$. On the other hand, the estimate for $|intermediate|$ is likely to be accurate, since the bond dimension of $\chi$ is probably attained at the center qubit if the sequence is long enough.
Both TDVP and DMRG rely on the same bath construction and effective Hamiltonian machinery, so their memory requirements are expected to scale similarly with $N$ and $\chi$.
-To test the accuracy of the above memory estimations, we benchmarked the __TDVP__ algorithm by fixing the bond dimension to a particular desired value.
-For different combinations of the number of atoms in a register $N$ and the fixed bond dimension $\chi$, we collect the maximum resident size, or RSS, which is expected to capture the maximum memory needed to run the emulation. We plot the RSS in the following picture (left), as a function of the number of qubits and for different bond dimensions. Notice that, once the RSS is normalized by $\chi^2$, as suggested by our estimate above, all the points fall into the same functional dependency on the number of atoms. Moreover, as we plot the normalized function $m(N,\chi,k)/\chi^2$, for a reasonable estimate of the size of the Krylov subspace ($k=30$), it is clear that our upper bound on memory occupation can be reasonably trusted on a wide range of system sizes and bond dimensions.
+To test the accuracy of the above memory estimations, we benchmarked the __TDVP__ algorithm by fixing the bond dimension to a particular desired value. We ran the benchmark on GPU, and consider the memory burden on the GPU.
+
+For different combinations of the number of atoms in a register $N$ and the fixed bond dimension $\chi$, we collect the maximum resident set size, or RSS, on the GPU, which is expected to capture the maximum memory needed to run the emulation. We plot the RSS in the following picture (left), as a function of the number of qubits and for different bond dimensions. Notice that, once the RSS is normalized by $\chi^2$, as suggested by our estimate above, all the points fall into the same functional dependency on the number of atoms. Moreover, as we plot the normalized function $m(N,\chi,k)/\chi^2$, for a reasonable estimate of the size of the Krylov subspace ($k=10$), it is clear that our upper bound on memory occupation can be reasonably trusted on a wide range of system sizes and bond dimensions.
Finally, having established an estimate for the memory consumption, it makes sense to explore what are the available regimes of qubits/bond dimension can be reached for a given hardware capability.
Since all heavy simulations will be run on an NVIDIA A100 (on Pasqal's DGX cluster), we have $40$ GB of available memory.
-Therefore, above, we show (right image) the contour lines of the RSS estimate $m(N,\chi,k=30) < 40$ GB for particular useful values of the total memory, allowing to quickly estimate the memory footprint of an emu-mps emulation.
+Therefore, above, we show (right image) the contour lines of the RSS estimate $m(N,\chi,k=10) < 40$ GB for particular useful values of the total memory, allowing to quickly estimate the memory footprint of an emu-mps emulation.
Although these results are shown for __TDVP__, a similar analysis could be performed for __DMRG__, and we expect the resulting memory scaling to be comparable.
### An example
-For example, the results from the [case study](convergence.md) were obtained using $N=49$ and $d=1600$ on 2 GPUs. Taking the above formula, and halving the contributions from $\psi$ and $|\mathrm{bath}|$ since they are split evenly on the GPUs, we reproduce the memory consumption of the program for $k=13$. Notice that the actual number of Krylov vectors required to reach convergence is likely larger than 13, but here we underestimate it since the contributions of $\psi$ and $|\mathrm{bath}|$ are over-estimated.
+For example, the results from the [case study](convergence.md) were obtained using $N=49$ and $d=1600$. Taking the above formula for GPU, we find a memory consumption of $16.998$ GB even for $k=0$, while the actual cited memory consumption on GPU is $14.659$ GB. The actual number of Krylov vectors required to reach convergence is likely around 10, but this contribution is not visible since $\psi$ and $|\mathrm{bath}|$ are over-estimated.
## Estimating the runtime of a simulation
diff --git a/docs/emu_mps/benchmarks/benchmark_plots/RSS_vs_N.png b/docs/emu_mps/benchmarks/benchmark_plots/RSS_vs_N.png
index 1943bdf5..f2249ef1 100644
Binary files a/docs/emu_mps/benchmarks/benchmark_plots/RSS_vs_N.png and b/docs/emu_mps/benchmarks/benchmark_plots/RSS_vs_N.png differ
diff --git a/docs/emu_mps/benchmarks/benchmark_plots/adiabatic_afm_state_cpu.png b/docs/emu_mps/benchmarks/benchmark_plots/adiabatic_afm_state_cpu.png
index bb5d8c78..bc511bec 100644
Binary files a/docs/emu_mps/benchmarks/benchmark_plots/adiabatic_afm_state_cpu.png and b/docs/emu_mps/benchmarks/benchmark_plots/adiabatic_afm_state_cpu.png differ
diff --git a/docs/emu_mps/benchmarks/benchmark_plots/adiabatic_afm_state_gpu.png b/docs/emu_mps/benchmarks/benchmark_plots/adiabatic_afm_state_gpu.png
index d4cccc23..1f4d06ab 100644
Binary files a/docs/emu_mps/benchmarks/benchmark_plots/adiabatic_afm_state_gpu.png and b/docs/emu_mps/benchmarks/benchmark_plots/adiabatic_afm_state_gpu.png differ
diff --git a/docs/emu_mps/benchmarks/benchmark_plots/afm_performance_big.png b/docs/emu_mps/benchmarks/benchmark_plots/afm_performance_big.png
index fb435350..89aca078 100644
Binary files a/docs/emu_mps/benchmarks/benchmark_plots/afm_performance_big.png and b/docs/emu_mps/benchmarks/benchmark_plots/afm_performance_big.png differ
diff --git a/docs/emu_mps/benchmarks/benchmark_plots/afm_state_fidelity.png b/docs/emu_mps/benchmarks/benchmark_plots/afm_state_fidelity.png
index c0aeddbb..29151c56 100644
Binary files a/docs/emu_mps/benchmarks/benchmark_plots/afm_state_fidelity.png and b/docs/emu_mps/benchmarks/benchmark_plots/afm_state_fidelity.png differ
diff --git a/docs/emu_mps/benchmarks/benchmark_plots/afm_state_fidelity_with_noise.png b/docs/emu_mps/benchmarks/benchmark_plots/afm_state_fidelity_with_noise.png
index 9706d125..67f56788 100644
Binary files a/docs/emu_mps/benchmarks/benchmark_plots/afm_state_fidelity_with_noise.png and b/docs/emu_mps/benchmarks/benchmark_plots/afm_state_fidelity_with_noise.png differ
diff --git a/docs/emu_mps/benchmarks/benchmark_plots/afm_state_gpu_with_noise.png b/docs/emu_mps/benchmarks/benchmark_plots/afm_state_gpu_with_noise.png
index 075c95ae..ab663be1 100644
Binary files a/docs/emu_mps/benchmarks/benchmark_plots/afm_state_gpu_with_noise.png and b/docs/emu_mps/benchmarks/benchmark_plots/afm_state_gpu_with_noise.png differ
diff --git a/docs/emu_mps/benchmarks/benchmark_plots/emumps_maxRSS_map.png b/docs/emu_mps/benchmarks/benchmark_plots/emumps_maxRSS_map.png
index 5da78591..2b64845e 100644
Binary files a/docs/emu_mps/benchmarks/benchmark_plots/emumps_maxRSS_map.png and b/docs/emu_mps/benchmarks/benchmark_plots/emumps_maxRSS_map.png differ
diff --git a/docs/emu_mps/benchmarks/benchmark_plots/quench_fidelity.png b/docs/emu_mps/benchmarks/benchmark_plots/quench_fidelity.png
index 2ba9db71..8275cd4c 100644
Binary files a/docs/emu_mps/benchmarks/benchmark_plots/quench_fidelity.png and b/docs/emu_mps/benchmarks/benchmark_plots/quench_fidelity.png differ
diff --git a/docs/emu_mps/benchmarks/benchmark_plots/quench_performance_cpu.png b/docs/emu_mps/benchmarks/benchmark_plots/quench_performance_cpu.png
index 421a4f8e..9d6699d1 100644
Binary files a/docs/emu_mps/benchmarks/benchmark_plots/quench_performance_cpu.png and b/docs/emu_mps/benchmarks/benchmark_plots/quench_performance_cpu.png differ
diff --git a/docs/emu_mps/benchmarks/benchmark_plots/quench_performance_gpu.png b/docs/emu_mps/benchmarks/benchmark_plots/quench_performance_gpu.png
index eed710e9..d5a08ee3 100644
Binary files a/docs/emu_mps/benchmarks/benchmark_plots/quench_performance_gpu.png and b/docs/emu_mps/benchmarks/benchmark_plots/quench_performance_gpu.png differ
diff --git a/docs/emu_mps/benchmarks/index.md b/docs/emu_mps/benchmarks/index.md
index c02830e9..ab7d6450 100644
--- a/docs/emu_mps/benchmarks/index.md
+++ b/docs/emu_mps/benchmarks/index.md
@@ -19,9 +19,9 @@ These quantities are represented, in the following plots:
-The RSS plot (left) shows the peak memory cost of the emulation.
+The RSS plot (left) shows the peak device-dependent memory cost of the emulation.
It is expected to stay constant at fixed bond dimension and thus represent the total memory occupation of the emulation of a sequence.
-As evident, the emulator is mostly limited by the available memory (40 GB on NVIDIA A100), as it restricts the maximum number of qubits/bond dimension pair allowed.
+As evident, the emulator is mostly limited by the available memory (40 GB on NVIDIA A100), as it restricts the maximum number of qubits/bond dimension pair allowed. Note that in addition to this device-dependent contribution, there is always a contribution in RAM coming from the [bath tensors](../advanced/resource_estimation.md#contribution-from-the-baths). When the simulation is run on CPU, the two contributions add. When running on GPU, the graph can be used to determine what system size can fit on the GPU, and the formula for the bath contribution can be used to determine how much RAM to allocate for the work (assuming access to computing resources where RAM is abundant).
To get the total estimated runtime instead, one should simply multiply the time estimate in the timing plot (right) by the number of steps in the emulated sequence.
Finally, given the technical nature of these estimates, they rely on some previous knowledge about [matrix product states](../advanced/mps/index.md) and the [TDVP](../advanced/algorithms.md) algorithm. We encourage anyone who might be interested into the derivation, to have a look at the [resource estimation](../advanced/resource_estimation.md) page in advanced topic section of this documentation.
diff --git a/docs/emu_mps/benchmarks/performance.md b/docs/emu_mps/benchmarks/performance.md
index 4e515f60..a6e72399 100644
--- a/docs/emu_mps/benchmarks/performance.md
+++ b/docs/emu_mps/benchmarks/performance.md
@@ -23,15 +23,15 @@ In the right column (both CPU and GPU figure), we explore the available register
### Performance for a 7x7 grid
-Let us now analyze the performance of the simulation for a much larger system. We show results for the adiabatic sequence with 49 qubits arranged in a 7x7 grid. The parameters of the simulation were `max_bond_dim = 1600`, `extra_krylov_tolerance=1e-5` and `precision=1e-6`, and we ran the simulation on 2 GPUs. The maximum bond dimension of the state, its size in memory, the total memory consumption of the program on GPU 1, and the time taken per emulation time step (there are 390 time steps of `dt=10 ns` each) are shown in the graph below.
+Let us now analyze the performance of the simulation for a much larger system. We show results for the adiabatic sequence with 49 qubits arranged in a 7x7 grid. The parameters of the simulation were `max_bond_dim = 1600`, `extra_krylov_tolerance=1e-5` and `precision=1e-6`, and we ran the simulation on GPU. The maximum bond dimension of the state, its size in memory, the total memory consumption of the program on the GPU, and the time taken per emulation time step (there are 450 time steps of `dt=10 ns` each) are shown in the graph below.
-There is a marginal runtime difference between CPU and GPU for smaller qubit numbers, which is mostly coincidental, since neither hardware is saturated with computations yet, and exponential scaling of the runtime has not yet set in. When comparing the runtimes between 19 and 20 qubits, they can be seen to roughly double for both CPU and GPU, as expected by exponential scaling. This shows that for larger qubit numbers, the GPU is about 4 times faster than CPU. Contrast this with the benchmarks of emu-mps, which show a relative factor of about 20 for larger bond-dimensions, a number much closer to the theoretical ratio of computational power. This suggests there are improvements to be made in the performance of emu-sv on gpu at least.
+There is a marginal runtime difference between CPU and GPU for smaller qubit numbers, which is mostly coincidental, since neither hardware is saturated with computations yet, and exponential scaling of the runtime has not yet set in. When comparing the runtimes between 19 and 20 qubits, they can be seen to roughly double for CPU, as expected by exponential scaling. For GPU, this exponential scaling has not yet set in even at 20 qubits. This shows that the computational resources of the GPU are not yet fully saturated and performance improvement is possible. Nevertheless, at 20 qubits, running emu-sv on GPU already reduces the runtime by about 25 times.
diff --git a/emu_base/__init__.py b/emu_base/__init__.py
index 13f01e06..a7b05e2e 100644
--- a/emu_base/__init__.py
+++ b/emu_base/__init__.py
@@ -36,4 +36,4 @@
"init_logging",
]
-__version__ = "3.0.0"
+__version__ = "2.10.0rc1"
diff --git a/emu_mps/__init__.py b/emu_mps/__init__.py
index e44497a4..b7869936 100644
--- a/emu_mps/__init__.py
+++ b/emu_mps/__init__.py
@@ -35,4 +35,4 @@
"EntanglementEntropy",
]
-__version__ = "3.0.0"
+__version__ = "2.10.0rc1"
diff --git a/emu_mps/mps_config.py b/emu_mps/mps_config.py
index 08efd3ea..f2f296a2 100644
--- a/emu_mps/mps_config.py
+++ b/emu_mps/mps_config.py
@@ -2,6 +2,7 @@
from types import MethodType
import copy
+import warnings
from emu_base import init_logging
from emu_mps.mps import MPS, DEFAULT_MAX_BOND_DIM, DEFAULT_PRECISION
@@ -53,6 +54,7 @@ class are passed on to the base class.
- if `gpu = None` (the default value), the backend internally
chooses 1 GPU based on the hardware availability
during runtime.
+ num_gpus_to_use: Deprecated. Passed to gpu as bool(num_gpus_to_use)
optimize_qubit_ordering: Optimize the register ordering. Improves
performance and accuracy, but disables certain features.
interaction_cutoff: Set interaction coefficients Uᵢⱼ below this value
@@ -96,6 +98,7 @@ def __init__(
max_krylov_dim: int = 100,
extra_krylov_tolerance: float = 1e-3,
gpu: bool | None = None,
+ num_gpus_to_use: int | None = None,
optimize_qubit_ordering: bool = True,
interaction_cutoff: float = 0.0,
log_level: int = logging.INFO,
@@ -121,6 +124,16 @@ def __init__(
solver=solver,
**kwargs,
)
+ if num_gpus_to_use is not None:
+ warnings.warn(
+ "num_gpus_to_use is deprecated, please use the gpu flag instead.",
+ DeprecationWarning,
+ )
+ if gpu is not None:
+ raise ValueError("Cannot specify both num_gpus_to_use and gpu")
+ else:
+ gpu = bool(num_gpus_to_use)
+
logger = init_logging(log_level, log_file)
MIN_AUTOSAVE_DT = 10
@@ -164,6 +177,7 @@ def _expected_kwargs(self) -> set[str]:
"max_bond_dim",
"max_krylov_dim",
"extra_krylov_tolerance",
+ "num_gpus_to_use",
"gpu",
"optimize_qubit_ordering",
"interaction_cutoff",
diff --git a/emu_sv/__init__.py b/emu_sv/__init__.py
index 08cd64d8..b5fbfa45 100644
--- a/emu_sv/__init__.py
+++ b/emu_sv/__init__.py
@@ -41,4 +41,4 @@
"Solver",
]
-__version__ = "3.0.0"
+__version__ = "2.10.0rc1"
diff --git a/test/emu_mps/test_mps_config.py b/test/emu_mps/test_mps_config.py
index b586ff2f..1223742c 100644
--- a/test/emu_mps/test_mps_config.py
+++ b/test/emu_mps/test_mps_config.py
@@ -31,7 +31,6 @@
def test_unsupported_noise() -> None:
MPSConfig(
noise_model=pulser.noise_model.NoiseModel(
- runs=1, # TODO: connect this with MCArlo
samples_per_run=1, # TODO: connect this with MCarlo or ignored
state_prep_error=0.1,
p_false_pos=0.1,
@@ -185,3 +184,14 @@ def test_default_MPSConfig_ctr() -> None:
mps_config = MPSConfig()
assert len(mps_config.observables) == 0
assert len(mps_config.callbacks) == 0
+
+
+def test_num_gpus_deprecated() -> None:
+ with pytest.raises(ValueError) as e:
+ MPSConfig(num_gpus_to_use=2, gpu=True)
+ assert e.match("Cannot specify both num_gpus_to_use and gpu")
+ with pytest.warns(
+ DeprecationWarning,
+ match="num_gpus_to_use is deprecated, please use the gpu flag instead.",
+ ):
+ MPSConfig(num_gpus_to_use=1)