Skip to content

API specification

The emu-sv API is based on the specification here. Concretely, the classes are as follows:

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

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)
    config = config or self.default_config
    if not isinstance(config, EmulationConfig):
        raise TypeError(
            "'config' must be an instance of 'EmulationConfig', "
            f"not {type(config)}."
        )
    # See the BackendConfig definition to see why this works
    self._config = type(self.default_config)(**config._backend_options)

run()

Emulates the given sequence.

RETURNS DESCRIPTION
Results

the simulation results

Source code in emu_sv/sv_backend.py
def run(self) -> Results:
    """
    Emulates the given sequence.

    Returns:
        the simulation results
    """
    assert isinstance(self._config, SVConfig)

    impl = create_impl(self._sequence, self._config)
    return impl._run()

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: int DEFAULT: 10

max_krylov_dim

the size of the krylov subspace that the Lanczos algorithm maximally builds

TYPE: int DEFAULT: 100

krylov_tolerance

the Lanczos algorithm uses this as the convergence tolerance

TYPE: float DEFAULT: 1e-10

gpu

Use 1 gpu if True, and a GPU is available, otherwise, cpu. Will cause errors if True when a gpu is not available

TYPE: bool DEFAULT: True

interaction_cutoff

Set interaction coefficients below this value to 0. Potentially improves runtime and memory consumption.

TYPE: float DEFAULT: 0.0

log_level

How much to log. Set to logging.WARN to get rid of the timestep info.

TYPE: int DEFAULT: INFO

log_file

If specified, log to this file rather than stout.

TYPE: Path | None DEFAULT: None

kwargs

arguments that are passed to the base class

TYPE: Any DEFAULT: {}

Examples:

>>> gpu = True
>>> dt = 1 #this will impact the runtime
>>> krylov_tolerance = 1e-8 #the simulation will be faster, but less accurate
>>> SVConfig(gpu=gpu, dt=dt, krylov_tolerance=krylov_tolerance,
>>>     with_modulation=True) #the last arg is taken from the base class
Source code in emu_sv/sv_config.py
def __init__(
    self,
    *,
    dt: int = 10,
    max_krylov_dim: int = 100,
    krylov_tolerance: float = 1e-10,
    gpu: bool = True,
    interaction_cutoff: float = 0.0,
    log_level: int = logging.INFO,
    log_file: pathlib.Path | None = None,
    **kwargs: Any,
):
    kwargs.setdefault("observables", [BitStrings(evaluation_times=[1.0])])
    super().__init__(
        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()

    self.logger = logging.getLogger("global_logger")
    if log_file is None:
        logging.basicConfig(
            level=log_level, format="%(message)s", stream=sys.stdout, force=True
        )  # default to stream = sys.stderr
    else:
        logging.basicConfig(
            level=log_level,
            format="%(message)s",
            filename=str(log_file),
            filemode="w",
            force=True,
        )
    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
    ):
        self.logger.warning(
            "Warning: The runs and samples_per_run "
            "values of the NoiseModel are ignored!"
        )
    if "SPAM" in self.noise_model.noise_types:
        raise NotImplementedError(
            "SPAM errors are currently not supported in emu-sv."
        )

StateVector

Bases: State[complex, Tensor]

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.

ATTRIBUTE DESCRIPTION
vector

1D tensor representation of a state vector.

gpu

store the vector on GPU if True, otherwise on CPU

Source code in emu_sv/state_vector.py
def __init__(
    self,
    vector: torch.Tensor,
    *,
    gpu: bool = True,
    eigenstates: Sequence[Eigenstate] = ("r", "g"),
):
    # NOTE: this accepts also zero vectors.

    assert math.log2(
        len(vector)
    ).is_integer(), "The number of elements in the vector should be power of 2"

    super().__init__(eigenstates=eigenstates)
    device = "cuda" if gpu and DEVICE_COUNT > 0 else "cpu"
    self.vector = vector.to(dtype=dtype, device=device)

n_qudits property

The number of qudits in the state.

__add__(other)

Sum of two state vectors

PARAMETER DESCRIPTION
other

the vector to add to this vector

TYPE: State

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.vector + other.vector,
        gpu=self.vector.is_cuda,
        eigenstates=self.eigenstates,
    )

__rmul__(scalar)

Scalar multiplication

PARAMETER DESCRIPTION
scalar

the scalar to multiply with

TYPE: complex

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.vector,
        gpu=self.vector.is_cuda,
        eigenstates=self.eigenstates,
    )

inner(other)

Compute . The type of other must be StateVector.

PARAMETER DESCRIPTION
other

the other state

TYPE: State

RETURNS DESCRIPTION
Tensor

the inner product

Source code in emu_sv/state_vector.py
def inner(self, other: State) -> torch.Tensor:
    """
    Compute <self|other>. 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.vector.shape == other.vector.shape
    ), "States do not have the same shape"

    # by our internal convention inner and norm return to cpu
    return torch.vdot(self.vector, other.vector).cpu()

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: int

gpu

whether gpu or cpu

TYPE: bool DEFAULT: True

RETURNS DESCRIPTION
StateVector

The described state

Examples:

>>> StateVector.make(2,gpu=False)
tensor([1.+0.j, 0.+0.j, 0.+0.j, 0.+0.j], dtype=torch.complex128)
Source code in emu_sv/state_vector.py
@classmethod
def 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:
        >>> StateVector.make(2,gpu=False)
        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.vector[0] = 1.0
    return result

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.vector).cpu()
    return nrm

sample(*, num_shots=1000, one_state=None, p_false_pos=0.0, p_false_neg=0.0)

Samples bitstrings, taking into account the specified error rates.

PARAMETER DESCRIPTION
num_shots

how many bitstrings to sample

TYPE: int DEFAULT: 1000

p_false_pos

the rate at which a 0 is read as a 1

TYPE: float DEFAULT: 0.0

p_false_neg

teh rate at which a 1 is read as a 0

TYPE: float DEFAULT: 0.0

RETURNS DESCRIPTION
Counter[str]

the measured bitstrings, by count

Source code in emu_sv/state_vector.py
def sample(
    self,
    *,
    num_shots: int = 1000,
    one_state: Eigenstate | None = None,
    p_false_pos: float = 0.0,
    p_false_neg: float = 0.0,
) -> Counter[str]:
    """
    Samples bitstrings, taking into account the specified error rates.

    Args:
        num_shots: how many bitstrings to sample
        p_false_pos: the rate at which a 0 is read as a 1
        p_false_neg: teh rate at which a 1 is read as a 0

    Returns:
        the measured bitstrings, by count
    """
    assert p_false_neg == p_false_pos == 0.0, "Error rates must be 0.0"

    probabilities = torch.abs(self.vector) ** 2

    outcomes = torch.multinomial(probabilities, num_shots, replacement=True)

    # Convert outcomes to bitstrings and count occurrences
    counts = Counter(
        [index_to_bitstring(self.n_qudits, outcome) for outcome in outcomes]
    )

    # NOTE: false positives and negatives
    return counts

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: int

gpu

whether gpu or cpu

TYPE: bool DEFAULT: True

RETURNS DESCRIPTION
StateVector

The zero state

Examples:

>>> StateVector.zero(2,gpu=False)
tensor([0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j], dtype=torch.complex128)
Source code in emu_sv/state_vector.py
@classmethod
def 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:
        >>> StateVector.zero(2,gpu=False)
        tensor([0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j], dtype=torch.complex128)
    """

    device = "cuda" if gpu and DEVICE_COUNT > 0 else "cpu"
    vector = torch.zeros(2**num_sites, dtype=dtype, device=device)
    return cls(vector, gpu=gpu, eigenstates=eigenstates)

inner

Wrapper around StateVector.inner.

PARAMETER DESCRIPTION
left

StateVector argument

TYPE: StateVector

right

StateVector argument

TYPE: StateVector

RETURNS DESCRIPTION
Tensor

the inner product

Examples:

>>> factor = math.sqrt(2.0)
>>> basis = ("r","g")
>>> 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()
(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:
        >>> factor = math.sqrt(2.0)
        >>> basis = ("r","g")
        >>> 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()
        (0.49999999144286444+0j)
    """

    assert (left.vector.shape == right.vector.shape) and (left.vector.dim() == 1), (
        "Shape of left.vector and right.vector should be",
        " the same and both need to be 1D tesnor",
    )
    return left.inner(right)

DenseOperator

Bases: Operator[complex, Tensor, StateVector]

DenseOperator in EMU-SV use dense matrices

Source code in emu_sv/dense_operator.py
def __init__(
    self,
    matrix: torch.Tensor,
    *,
    gpu: bool = True,
):
    device = "cuda" if gpu and DEVICE_COUNT > 0 else "cpu"
    self.matrix = matrix.to(dtype=dtype, device=device)

__add__(other)

Element-wise addition of two DenseOperators.

PARAMETER DESCRIPTION
other

a DenseOperator instance.

TYPE: Operator

RETURNS DESCRIPTION
DenseOperator

A new DenseOperator representing the sum.

Source code in emu_sv/dense_operator.py
def __add__(self, other: Operator) -> DenseOperator:
    """
    Element-wise addition of two DenseOperators.

    Args:
        other: a DenseOperator instance.

    Returns:
        A new DenseOperator representing the sum.
    """
    assert isinstance(
        other, DenseOperator
    ), "DenseOperator can only be added to another DenseOperator."
    return DenseOperator(self.matrix + other.matrix)

__matmul__(other)

Compose two DenseOperators via matrix multiplication.

PARAMETER DESCRIPTION
other

a DenseOperator instance.

TYPE: Operator

RETURNS DESCRIPTION
DenseOperator

A new DenseOperator representing the product self @ other.

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.matrix @ other.matrix)

__rmul__(scalar)

Scalar multiplication of the DenseOperator.

PARAMETER DESCRIPTION
scalar

a number to scale the operator.

TYPE: complex

RETURNS DESCRIPTION
DenseOperator

A new DenseOperator scaled by the given scalar.

Source code in emu_sv/dense_operator.py
def __rmul__(self, scalar: complex) -> DenseOperator:
    """
    Scalar multiplication of the DenseOperator.

    Args:
        scalar: a number to scale the operator.

    Returns:
        A new DenseOperator scaled by the given scalar.
    """

    return DenseOperator(scalar * self.matrix)

apply_to(other)

Apply the DenseOperator to a given StateVector.

PARAMETER DESCRIPTION
other

a StateVector instance.

TYPE: State

RETURNS DESCRIPTION
StateVector

A new StateVector after applying the operator.

Source code in emu_sv/dense_operator.py
def apply_to(self, other: State) -> StateVector:
    """
    Apply the DenseOperator to a given StateVector.

    Args:
        other: a StateVector instance.

    Returns:
        A new StateVector after applying the operator.
    """
    assert isinstance(
        other, StateVector
    ), "DenseOperator can only be applied to a StateVector."

    return StateVector(self.matrix @ other.vector)

expect(state)

Compute the expectation value of the operator with respect to a state.

PARAMETER DESCRIPTION
state

a StateVector instance.

TYPE: State

RETURNS DESCRIPTION
Tensor

The expectation value as a float or complex number.

Source code in emu_sv/dense_operator.py
def expect(self, state: State) -> torch.Tensor:
    """
    Compute the expectation value of the operator with respect to a state.

    Args:
        state: a StateVector instance.

    Returns:
        The expectation value as a float or complex number.
    """
    assert isinstance(
        state, StateVector
    ), "Only expectation values of StateVectors are supported."

    return torch.vdot(state.vector, self.apply_to(state).vector).cpu()