API specification
The emu-sv API is based on the specification here. Concretely, the classes are as follows:
SVBackend
Section titled “SVBackend”
Bases: EmulatorBackend
A backend for emulating Pulser sequences using state vectors and sparse matrices. Noisy simulation is supported by solving the Lindblad equation and using effective noise channel or jump operators
| PARAMETER | DESCRIPTION |
|---|---|
sequence
|
The sequence to be executed.
TYPE:
|
config
|
Configuration for the SV backend.
TYPE:
|
mimic_qpu
|
Whether to enforce Register constrains imposed by the device in the Sequence.
TYPE:
|
Source code in pulser/backend/abc.py
def __init__( self, sequence: pulser.Sequence, *, config: EmulationConfig | None = None, mimic_qpu: bool = False,) -> None: """Initializes the backend.""" super().__init__(sequence, mimic_qpu=mimic_qpu) self._config = self.validate_config(config or self.default_config)
noise_model = self._config.noise_model
if noise_model is not None: is_dmm_channel = any( isinstance(ch, DMM) for ch in self._sequence.declared_channels.values() )
if ( is_dmm_channel and "register" in noise_model.noise_types and noise_model.detuning_map_spot_waist is None ): raise ValueError( "Combining register noise with a DMM requires" "`detuning_map_spot_waist` to be defined. If not defined," "atom thermal motion can lead to non-physical effects." )
if ( self._config.prefer_device_noise_model and self._sequence.device.noise_model is not None and self._sequence.device.noise_model.runs is not None and self._sequence.device.noise_model.runs != self._config.n_trajectories ): config = self._config warnings.warn( f"'{sequence.device.noise_model.runs=}' is being " f"ignored; '{config.n_trajectories=}' will be used instead.", stacklevel=2, )
run()
Section titled “
run()
”Emulates the given sequence.
| RETURNS | DESCRIPTION |
|---|---|
Results | list[Results]
|
the simulation results |
Source code in emu_sv/sv_backend.py
def run(self) -> Results | list[Results]: """ Emulates the given sequence.
Returns: the simulation results """ assert isinstance(self._config, SVConfig) assert self._sequence._in_ising, "Only Rydberg interaction is currently supported" pulser_data = PulserData( sequence=self._sequence, config=self._config, dt=self._config.dt ) results = [] for sequence_data in pulser_data.get_sequences(): results.append(self._run_from_sequence_data(sequence_data, self._config)) return Results.aggregate(results)SVConfig
Section titled “SVConfig”
Bases: EmulationConfig
The configuration of the emu-sv SVBackend. The kwargs passed to this class are passed on to the base class. See the API for that class for a list of available options.
| PARAMETER | DESCRIPTION |
|---|---|
dt
|
the timestep size that the solver uses. Note that observables are only calculated if the evaluation_times are divisible by dt.
TYPE:
|
max_krylov_dim
|
the size of the krylov subspace that the Lanczos algorithm maximally builds
TYPE:
|
krylov_tolerance
|
the Lanczos algorithm uses this as the convergence tolerance
TYPE:
|
gpu
|
whether to run the simulation on GPU
- if
TYPE:
|
interaction_cutoff
|
Set interaction coefficients below this value to
TYPE:
|
log_level
|
How much to log. Set to
TYPE:
|
log_file
|
If specified, log to this file rather than stout.
TYPE:
|
solver
|
The solver to use in the presence of Lindblad noise. By default it will use Monte Carlo quantum jumps if shot noise is present, and the Lindblad equation with density matrices otherwise.
TYPE:
|
kwargs
|
arguments that are passed to the base class
TYPE:
|
Examples:
gpu = Truedt = 1.0 #this will impact the runtimekrylov_tolerance = 1e-8 #the simulation will be faster, but less accurateSVConfig(gpu=gpu, dt=dt, krylov_tolerance=krylov_tolerance, with_modulation=True) #the last arg is taken from the base classSource code in emu_sv/sv_config.py
def __init__( self, *, dt: float = 10.0, max_krylov_dim: int = 100, krylov_tolerance: float = 1e-10, gpu: bool | None = None, interaction_cutoff: float = 0.0, log_level: int = logging.INFO, log_file: pathlib.Path | None = None, solver: Solver = Solver.DEFAULT, **kwargs: Any,): super().__init__( solver=solver, dt=dt, max_krylov_dim=max_krylov_dim, gpu=gpu, krylov_tolerance=krylov_tolerance, interaction_cutoff=interaction_cutoff, log_level=log_level, log_file=log_file, **kwargs, )
self.monkeypatch_observables() logger = init_logging(log_level, log_file)
if (self.noise_model.runs != 1 and self.noise_model.runs is not None) or ( self.noise_model.samples_per_run != 1 and self.noise_model.samples_per_run is not None ): logger.warning( "Warning: The runs and samples_per_run " "values of the NoiseModel are ignored!" )StateVector
Section titled “StateVector”
Bases: DenseState
Represents a quantum state vector in a computational basis.
This class extends the State class to handle state vectors,
providing various utilities for initialization, normalization,
manipulation, and measurement. The state vector must have a length
that is a power of 2, representing 2ⁿ basis states for n qubits.
| PARAMETER | DESCRIPTION |
|---|---|
data
|
1D tensor representation of a state vector.
TYPE:
|
gpu
|
store the vector on GPU if True and a GPU is present, otherwise on CPU
TYPE:
|
eigenstates
|
sequence of eigenstates used as basis only qubit basis are supported (default: ('r','g'))
TYPE:
|
Source code in emu_sv/dense_state.py
def __init__( self, data: torch.Tensor, *, gpu: bool = True, eigenstates: Sequence[Eigenstate] = ("r", "g"),): assert data.dim() == 1 or data.dim() == 2 super().__init__(eigenstates=eigenstates)
device = "cuda" if gpu and DEVICE_COUNT > 0 else "cpu" self.data = data.to(dtype=dtype, device=device)
__add__(other)
Section titled “
__add__(other)
”Sum of two state vectors
| PARAMETER | DESCRIPTION |
|---|---|
other
|
the vector to add to this vector
TYPE:
|
| RETURNS | DESCRIPTION |
|---|---|
StateVector
|
The summed state |
Source code in emu_sv/state_vector.py
def __add__(self, other: State) -> StateVector: """Sum of two state vectors
Args: other: the vector to add to this vector
Returns: The summed state """ assert isinstance(other, StateVector), "`Other` state can only be a StateVector" assert ( self.eigenstates == other.eigenstates ), f"`Other` state has basis {other.eigenstates} != {self.eigenstates}" return StateVector( self.data + other.data, gpu=self.data.is_cuda, eigenstates=self.eigenstates, )
__rmul__(scalar)
Section titled “
__rmul__(scalar)
”Scalar multiplication
| PARAMETER | DESCRIPTION |
|---|---|
scalar
|
the scalar to multiply with
TYPE:
|
| RETURNS | DESCRIPTION |
|---|---|
StateVector
|
The scaled state |
Source code in emu_sv/state_vector.py
def __rmul__(self, scalar: complex) -> StateVector: """Scalar multiplication
Args: scalar: the scalar to multiply with
Returns: The scaled state """ return StateVector( scalar * self.data, gpu=self.data.is_cuda, eigenstates=self.eigenstates, )
inner(other)
Section titled “
inner(other)
”| PARAMETER | DESCRIPTION |
|---|---|
other
|
the other state
TYPE:
|
| RETURNS | DESCRIPTION |
|---|---|
Tensor
|
the inner product |
Source code in emu_sv/state_vector.py
def inner(self, other: State) -> torch.Tensor: """ Compute . The type of other must be StateVector.
Args: other: the other state
Returns: the inner product """ assert isinstance(other, StateVector), "Other state must be a StateVector" assert self.data.shape == other.data.shape, "States do not have the same shape"
# by our internal convention inner and norm return to cpu return torch.vdot(self.data, other.data.to(self.data.device)).cpu()
make(num_sites, gpu=True)
classmethod
Section titled “
make(num_sites, gpu=True)
classmethod
”Returns a State vector in the ground state |00..0>.
| PARAMETER | DESCRIPTION |
|---|---|
num_sites
|
the number of qubits
TYPE:
|
gpu
|
whether gpu or cpu
TYPE:
|
| RETURNS | DESCRIPTION |
|---|---|
StateVector
|
The described state |
Examples:
StateVector.make(2,gpu=False)Output:
tensor([1.+0.j, 0.+0.j, 0.+0.j, 0.+0.j], dtype=torch.complex128)Source code in emu_sv/state_vector.py
@classmethoddef make(cls, num_sites: int, gpu: bool = True) -> StateVector: """ Returns a State vector in the ground state |00..0>.
Args: num_sites: the number of qubits gpu: whether gpu or cpu
Returns: The described state
Examples: ```python StateVector.make(2,gpu=False) ```
Output: ``` tensor([1.+0.j, 0.+0.j, 0.+0.j, 0.+0.j], dtype=torch.complex128) ``` """
result = cls.zero(num_sites=num_sites, gpu=gpu) result.data[0] = 1.0 return result
norm()
Section titled “
norm()
”Returns the norm of the state
| RETURNS | DESCRIPTION |
|---|---|
Tensor
|
the norm of the state |
Source code in emu_sv/state_vector.py
def norm(self) -> torch.Tensor: """Returns the norm of the state
Returns: the norm of the state """ nrm: torch.Tensor = torch.linalg.vector_norm(self.data).cpu() return nrm
zero(num_sites, gpu=True, eigenstates=('r', 'g'))
classmethod
Section titled “
zero(num_sites, gpu=True, eigenstates=('r', 'g'))
classmethod
”Returns a zero uninitialized "state" vector. Warning, this has no physical meaning as-is!
| PARAMETER | DESCRIPTION |
|---|---|
num_sites
|
the number of qubits
TYPE:
|
gpu
|
whether gpu or cpu
TYPE:
|
| RETURNS | DESCRIPTION |
|---|---|
StateVector
|
The zero state |
Examples:
StateVector.zero(2,gpu=False)Output:
tensor([0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j], dtype=torch.complex128)Source code in emu_sv/state_vector.py
@classmethoddef zero( cls, num_sites: int, gpu: bool = True, eigenstates: Sequence[Eigenstate] = ("r", "g"),) -> StateVector: """ Returns a zero uninitialized "state" vector. Warning, this has no physical meaning as-is!
Args: num_sites: the number of qubits gpu: whether gpu or cpu
Returns: The zero state
Examples: ```python StateVector.zero(2,gpu=False) ```
Output: ``` tensor([0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j], dtype=torch.complex128) ``` """
vector = torch.zeros(2**num_sites, dtype=dtype) return cls(vector, gpu=gpu, eigenstates=eigenstates)Wrapper around StateVector.inner.
| PARAMETER | DESCRIPTION |
|---|---|
left
|
StateVector argument
TYPE:
|
right
|
StateVector argument
TYPE:
|
| RETURNS | DESCRIPTION |
|---|---|
Tensor
|
the inner product |
Examples:
factor = math.sqrt(2.0)basis = ("r","g")nqubits=2string_state1 = {"gg":1.0,"rr":1.0}state1 = StateVector.from_state_string(basis=basis,nqubits=nqubits,strings=string_state1)string_state2 = {"gr":1.0/factor,"rr":1.0/factor}state2 = StateVector.from_state_string(basis=basis,nqubits=nqubits,strings=string_state2)state1 = StateVector.from_state_amplitudes(eigenstates=basis, amplitudes=string_state1)string_state2 = {"gr":1.0/factor,"rr":1.0/factor}state2 = StateVector.from_state_amplitudes(eigenstates=basis, amplitudes=string_state2)inner(state1,state2).item()Output:
(0.49999999144286444+0j)Source code in emu_sv/state_vector.py
def inner(left: StateVector, right: StateVector) -> torch.Tensor: """ Wrapper around StateVector.inner.
Args: left: StateVector argument right: StateVector argument
Returns: the inner product
Examples: ```python factor = math.sqrt(2.0) basis = ("r","g") nqubits=2 string_state1 = {"gg":1.0,"rr":1.0} state1 = StateVector.from_state_string(basis=basis, nqubits=nqubits,strings=string_state1) string_state2 = {"gr":1.0/factor,"rr":1.0/factor} state2 = StateVector.from_state_string(basis=basis, nqubits=nqubits,strings=string_state2) state1 = StateVector.from_state_amplitudes(eigenstates=basis, amplitudes=string_state1) string_state2 = {"gr":1.0/factor,"rr":1.0/factor} state2 = StateVector.from_state_amplitudes(eigenstates=basis, amplitudes=string_state2) inner(state1,state2).item() ```
Output: ``` (0.49999999144286444+0j) ``` """
assert (left.data.shape == right.data.shape) and (left.data.dim() == 1), ( "Shape of left.data and right.data should be", " the same and both need to be 1D tesnor", ) return left.inner(right)DenseOperator
Section titled “DenseOperator”
Bases: MatrixOperator
DenseOperator in emu-sv uses dense matrices. This class represents a quantum operator backed by a dense PyTorch tensor for state-vector simulation.
| PARAMETER | DESCRIPTION |
|---|---|
matrix
|
Square complex tensor of shape (2ⁿ, 2ⁿ) representing the operator in the computational basis.
TYPE:
|
gpu
|
If True, place the operator on a CUDA device when available. Default: True (and only 1 GPU).
TYPE:
|
Source code in emu_sv/matrix_operator.py
def __init__( self, matrix: torch.Tensor, *, gpu: bool = True,): super().__init__() device = "cuda" if gpu and DEVICE_COUNT > 0 else "cpu" self.data = matrix.to(dtype=dtype, device=device)
__matmul__(other)
Section titled “
__matmul__(other)
”Compose two DenseOperators via matrix multiplication.
| PARAMETER | DESCRIPTION |
|---|---|
other
|
a DenseOperator instance.
TYPE:
|
| RETURNS | DESCRIPTION |
|---|---|
DenseOperator
|
A new DenseOperator representing the product |
Source code in emu_sv/dense_operator.py
def __matmul__(self, other: Operator) -> DenseOperator: """ Compose two DenseOperators via matrix multiplication.
Args: other: a DenseOperator instance.
Returns: A new DenseOperator representing the product `self @ other`. """ assert isinstance( other, DenseOperator ), "DenseOperator can only be multiplied with a DenseOperator." return DenseOperator(self.data @ other.data)SparseOperator
Section titled “SparseOperator”
Bases: MatrixOperator
This operator is used to represent a sparse matrix in CSR (Compressed Sparse Row) format for efficient computation on the emu-sv emulator
| PARAMETER | DESCRIPTION |
|---|---|
matrix
|
The CSR matrix representation of the operator.
TYPE:
|
gpu
|
If True (by default), run on GPU when available; otherwise fall back to CPU. If False, always run on CPU.
TYPE:
|
Source code in emu_sv/matrix_operator.py
def __init__( self, matrix: torch.Tensor, *, gpu: bool = True,): super().__init__() device = "cuda" if gpu and DEVICE_COUNT > 0 else "cpu" self.data = matrix.to(dtype=dtype, device=device)DensityMatrix
Section titled “DensityMatrix”
Bases: DenseState
Represents an n-qubit density matrix ρ in the computational (|g⟩, |r⟩) basis. The input should be a square complex tensor with shape (2ⁿ, 2ⁿ), where n is the number of atoms. ρ must be Hermitian, positive semidefinite, and has trace 1. These checks are too expensive to actually perform, so it's up to the user to ensure these constraints are met.
| PARAMETER | DESCRIPTION |
|---|---|
data
|
Square complex tensor of shape (2ⁿ, 2ⁿ), Hermitian with trace 1, that represents the state in the computational basis.
TYPE:
|
gpu
|
If True, place the operator on a CUDA device when available. Default: True.
TYPE:
|
eigenstates
|
sequence of eigenstates used as basis only qubit basis are supported (default: ('r','g'))
TYPE:
|
Source code in emu_sv/dense_state.py
def __init__( self, data: torch.Tensor, *, gpu: bool = True, eigenstates: Sequence[Eigenstate] = ("r", "g"),): assert data.dim() == 1 or data.dim() == 2 super().__init__(eigenstates=eigenstates)
device = "cuda" if gpu and DEVICE_COUNT > 0 else "cpu" self.data = data.to(dtype=dtype, device=device)
from_state_vector(state)
classmethod
Section titled “
from_state_vector(state)
classmethod
”Convert a state vector to a density matrix. This function takes a state vector |ψ❭ and returns the corresponding density matrix ρ = |ψ❭❬ψ| representing the pure state |ψ❭.
Examples:
bell_state_vec = 0.7071 * torch.tensor([1.0, 0.0, 0.0, 1.0j],dtype=torch.complex128)bell_state = StateVector(bell_state_vec, gpu=False)density = DensityMatrix.from_state_vector(bell_state)print(density.data)Output:
tensor([[0.5000+0.0000j, 0.0000+0.0000j, 0.0000+0.0000j, 0.0000-0.5000j], [0.0000+0.0000j, 0.0000+0.0000j, 0.0000+0.0000j, 0.0000+0.0000j], [0.0000+0.0000j, 0.0000+0.0000j, 0.0000+0.0000j, 0.0000+0.0000j], [0.0000+0.5000j, 0.0000+0.0000j, 0.0000+0.0000j, 0.5000+0.0000j]], dtype=torch.complex128)Source code in emu_sv/density_matrix_state.py
@classmethoddef from_state_vector(cls, state: StateVector) -> DensityMatrix: """ Convert a state vector to a density matrix. This function takes a state vector |ψ❭ and returns the corresponding density matrix ρ = |ψ❭❬ψ| representing the pure state |ψ❭.
Examples: ```python bell_state_vec = 0.7071 * torch.tensor([1.0, 0.0, 0.0, 1.0j], dtype=torch.complex128) bell_state = StateVector(bell_state_vec, gpu=False) density = DensityMatrix.from_state_vector(bell_state) print(density.data) ```
Output: ``` tensor([[0.5000+0.0000j, 0.0000+0.0000j, 0.0000+0.0000j, 0.0000-0.5000j], [0.0000+0.0000j, 0.0000+0.0000j, 0.0000+0.0000j, 0.0000+0.0000j], [0.0000+0.0000j, 0.0000+0.0000j, 0.0000+0.0000j, 0.0000+0.0000j], [0.0000+0.5000j, 0.0000+0.0000j, 0.0000+0.0000j, 0.5000+0.0000j]], dtype=torch.complex128) ``` """
return cls(torch.outer(state.data, state.data.conj()), gpu=state.data.is_cuda)
make(n_atoms, gpu=True)
classmethod
Section titled “
make(n_atoms, gpu=True)
classmethod
”Creates the density matrix of the ground state |000...0>
Source code in emu_sv/density_matrix_state.py
@classmethoddef make(cls, n_atoms: int, gpu: bool = True) -> DensityMatrix: """Creates the density matrix of the ground state |000...0>""" result = torch.zeros(2**n_atoms, 2**n_atoms, dtype=dtype) result[0, 0] = 1.0 return cls(result, gpu=gpu)
norm()
Section titled “
norm()
”Returns the norm of the state
| RETURNS | DESCRIPTION |
|---|---|
Tensor
|
the norm of the state |
Source code in emu_sv/density_matrix_state.py
def norm(self) -> torch.Tensor: """Returns the norm of the state
Returns: the norm of the state """ nrm: torch.Tensor = torch.trace(self.data).real.cpu() return nrm