Skip to content

Device

DynamicMemorySimulator dataclass

DynamicMemorySimulator(
    options: PyQrackOptions = _default_pyqrack_args(),
    *,
    loss_m_result: Measurement = Measurement.One,
    rng_state: Generator = np.random.default_rng()
)

Bases: PyQrackSimulatorBase

PyQrack simulator device with dynamic qubit allocation.

This can be used to simulate kernels where the number of qubits is not known ahead of time.

Usage examples

# Define a kernel
@qasm2.main
def main():
    q = qasm2.qreg(2)
    c = qasm2.creg(2)

    qasm2.h(q[0])
    qasm2.cx(q[0], q[1])

    qasm2.measure(q, c)
    return q

# Create the simulator object
sim = DynamicMemorySimulator()

# Execute the kernel
qubits = sim.run(main)

You can also obtain other information from it, such as the state vector:

``` ket = sim.state_vector(main)

from pyqrack.pauli import Pauli expectation_vals = sim.pauli_expectation([Pauli.PauliX, Pauli.PauliI], qubits)

task

task(
    kernel: Method[Params, RetType],
    args: tuple[Any, ...] = (),
    kwargs: dict[str, Any] | None = None,
)

Parameters:

Name Type Description Default
kernel Method

The kernel method to run.

required
args tuple[Any, ...]

Positional arguments to pass to the kernel method.

()
kwargs dict[str, Any] | None

Keyword arguments to pass to the kernel method.

None

Returns:

Name Type Description
PyQrackSimulatorTask

The task object used to track execution.

Source code in .venv/lib/python3.12/site-packages/bloqade/pyqrack/device.py
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
def task(
    self,
    kernel: ir.Method[Params, RetType],
    args: tuple[Any, ...] = (),
    kwargs: dict[str, Any] | None = None,
):
    """
    Args:
        kernel (ir.Method):
            The kernel method to run.
        args (tuple[Any, ...]):
            Positional arguments to pass to the kernel method.
        kwargs (dict[str, Any] | None):
            Keyword arguments to pass to the kernel method.

    Returns:
        PyQrackSimulatorTask:
            The task object used to track execution.

    """
    if kwargs is None:
        kwargs = {}

    memory = DynamicMemory(self.options.copy())
    return self.new_task(kernel, args, kwargs, memory)

PyQrackSimulatorBase dataclass

PyQrackSimulatorBase(
    options: PyQrackOptions = _default_pyqrack_args(),
    *,
    loss_m_result: Measurement = Measurement.One,
    rng_state: Generator = np.random.default_rng()
)

Bases: AbstractSimulatorDevice[PyQrackSimulatorTask]

PyQrack simulation device base class.

options class-attribute instance-attribute

options: PyQrackOptions = field(
    default_factory=_default_pyqrack_args
)

options (PyQrackOptions): options passed into the pyqrack simulator.

pauli_expectation staticmethod

pauli_expectation(
    pauli: list[Pauli], qubits: list[PyQrackQubit]
) -> float

Returns the expectation value of the given Pauli operator given a list of Pauli operators and qubits.

Parameters:

Name Type Description Default
pauli list[Pauli]

List of Pauli operators to compute the expectation value for.

required
qubits list[PyQrackQubit]

List of qubits corresponding to the Pauli operators.

required

Returns:

Name Type Description
float float

The expectation value of the Pauli operator.

Source code in .venv/lib/python3.12/site-packages/bloqade/pyqrack/device.py
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
@staticmethod
def pauli_expectation(pauli: list[Pauli], qubits: list[PyQrackQubit]) -> float:
    """Returns the expectation value of the given Pauli operator given a list of Pauli operators and qubits.

    Args:
        pauli (list[Pauli]):
            List of Pauli operators to compute the expectation value for.
        qubits (list[PyQrackQubit]):
            List of qubits corresponding to the Pauli operators.

    returns:
        float:
            The expectation value of the Pauli operator.

    """

    if len(pauli) == 0:
        return 0.0

    if len(pauli) != len(qubits):
        raise ValueError("Length of Pauli and qubits must match.")

    sim_reg = qubits[0].sim_reg

    if any(qubit.sim_reg is not sim_reg for qubit in qubits):
        raise ValueError("All qubits must belong to the same simulator register.")

    qubit_ids = [qubit.addr for qubit in qubits]

    if len(qubit_ids) != len(set(qubit_ids)):
        raise ValueError("Qubits must be unique.")

    return sim_reg.pauli_expectation(qubit_ids, pauli)

quantum_state staticmethod

quantum_state(
    qubits: list[PyQrackQubit] | IList[PyQrackQubit, Any],
    tol: float = 1e-12,
) -> QuantumState

Extract the reduced density matrix representing the state of a list of qubits from a PyQRack simulator register.

Inputs

qubits: A list of PyQRack qubits to extract the reduced density matrix for tol: The tolerance for density matrix eigenvalues to be considered non-zero.

Outputs: An eigh result containing the eigenvalues and eigenvectors of the reduced density matrix.

Source code in .venv/lib/python3.12/site-packages/bloqade/pyqrack/device.py
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
@staticmethod
def quantum_state(
    qubits: list[PyQrackQubit] | IList[PyQrackQubit, Any], tol: float = 1e-12
) -> "QuantumState":
    """
    Extract the reduced density matrix representing the state of a list
    of qubits from a PyQRack simulator register.

    Inputs:
        qubits: A list of PyQRack qubits to extract the reduced density matrix for
        tol: The tolerance for density matrix eigenvalues to be considered non-zero.
    Outputs:
        An eigh result containing the eigenvalues and eigenvectors of the reduced density matrix.
    """
    if len(qubits) == 0:
        return QuantumState(
            eigenvalues=np.array([]), eigenvectors=np.array([]).reshape(0, 0)
        )
    sim_reg = qubits[0].sim_reg

    if not all([x.sim_reg is sim_reg for x in qubits]):
        raise ValueError("All qubits must be from the same simulator register.")
    inds: tuple[int, ...] = tuple(qubit.addr for qubit in qubits)

    return _pyqrack_reduced_density_matrix(inds, sim_reg, tol)

reduced_density_matrix classmethod

reduced_density_matrix(
    qubits: list[PyQrackQubit] | IList[PyQrackQubit, Any],
    tol: float = 1e-12,
) -> np.ndarray

Extract the reduced density matrix representing the state of a list of qubits from a PyQRack simulator register.

Inputs

qubits: A list of PyQRack qubits to extract the reduced density matrix for tol: The tolerance for density matrix eigenvalues to be considered non-zero.

Outputs: A dense 2^n x 2^n numpy array representing the reduced density matrix.

Source code in .venv/lib/python3.12/site-packages/bloqade/pyqrack/device.py
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
@classmethod
def reduced_density_matrix(
    cls, qubits: list[PyQrackQubit] | IList[PyQrackQubit, Any], tol: float = 1e-12
) -> np.ndarray:
    """
    Extract the reduced density matrix representing the state of a list
    of qubits from a PyQRack simulator register.

    Inputs:
        qubits: A list of PyQRack qubits to extract the reduced density matrix for
        tol: The tolerance for density matrix eigenvalues to be considered non-zero.
    Outputs:
        A dense 2^n x 2^n numpy array representing the reduced density matrix.
    """
    rdm = cls.quantum_state(qubits, tol)
    return np.einsum(
        "ax,x,bx", rdm.eigenvectors, rdm.eigenvalues, rdm.eigenvectors.conj()
    )

state_vector

state_vector(
    kernel: Method[Params, RetType],
    args: tuple[Any, ...] = (),
    kwargs: dict[str, Any] | None = None,
) -> list[complex]

Runs task and returns the state vector.

Source code in .venv/lib/python3.12/site-packages/bloqade/pyqrack/device.py
212
213
214
215
216
217
218
219
def state_vector(
    self,
    kernel: ir.Method[Params, RetType],
    args: tuple[Any, ...] = (),
    kwargs: dict[str, Any] | None = None,
) -> list[complex]:
    """Runs task and returns the state vector."""
    return self.task(kernel, args, kwargs).state_vector()

QuantumState

Bases: NamedTuple

A representation of a quantum state as a density matrix, where the density matrix is rho = sum_i eigenvalues[i] |eigenvectors[:,i]><eigenvectors[:,i]|.

This reprsentation is efficient for low-rank density matrices by only storing the non-zero eigenvalues and corresponding eigenvectors of the density matrix. For example, a pure state has only one non-zero eigenvalue equal to 1.0.

Endianness and qubit ordering of the state vector is consistent with Cirq, where eigenvectors[0,0] corresponds to the amplitude of the |00..000> element of the zeroth eigenvector; eigenvectors[1,0] corresponds to the amplitude of the |00..001> element of the zeroth eigenvector; eigenvectors[3,0] corresponds to the amplitude of the |00..011> element of the zeroth eigenvector; eigenvectors[-1,0] corresponds to the amplitude of the |11..111> element of the zeroth eigenvector. A flip of the LAST bit |00..000><00..001| corresponds to applying a PauliX gate to the FIRST qubit. A flip of the FIRST bit |00..000><10..000| corresponds to applying a PauliX gate to the LAST qubit.

Attributes:

Name Type Description
eigenvalues 1d np.ndarray

The non-zero eigenvalues of the density matrix.

eigenvectors 2d np.ndarray

The corresponding eigenvectors of the density matrix, where eigenvectors[:,i] is the i-th eigenvector.

Methods: Not Implemented, pending https://github.com/QuEraComputing/bloqade-circuit/issues/447

StackMemorySimulator dataclass

StackMemorySimulator(
    options: PyQrackOptions = _default_pyqrack_args(),
    *,
    loss_m_result: Measurement = Measurement.One,
    rng_state: Generator = np.random.default_rng(),
    min_qubits: int = 0
)

Bases: PyQrackSimulatorBase

PyQrack simulator device with preallocated stack of qubits.

This can be used to simulate kernels where the number of qubits is known ahead of time.

Usage examples

# Define a kernel
@qasm2.main
def main():
    q = qasm2.qreg(2)
    c = qasm2.creg(2)

    qasm2.h(q[0])
    qasm2.cx(q[0], q[1])

    qasm2.measure(q, c)
    return q

# Create the simulator object
sim = StackMemorySimulator(min_qubits=2)

# Execute the kernel
qubits = sim.run(main)

You can also obtain other information from it, such as the state vector:

ket = sim.state_vector(main)

from pyqrack.pauli import Pauli
expectation_vals = sim.pauli_expectation([Pauli.PauliX, Pauli.PauliI], qubits)

task

task(
    kernel: Method[Params, RetType],
    args: tuple[Any, ...] = (),
    kwargs: dict[str, Any] | None = None,
)

Parameters:

Name Type Description Default
kernel Method

The kernel method to run.

required
args tuple[Any, ...]

Positional arguments to pass to the kernel method.

()
kwargs dict[str, Any] | None

Keyword arguments to pass to the kernel method.

None

Returns:

Name Type Description
PyQrackSimulatorTask

The task object used to track execution.

Source code in .venv/lib/python3.12/site-packages/bloqade/pyqrack/device.py
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
def task(
    self,
    kernel: ir.Method[Params, RetType],
    args: tuple[Any, ...] = (),
    kwargs: dict[str, Any] | None = None,
):
    """
    Args:
        kernel (ir.Method):
            The kernel method to run.
        args (tuple[Any, ...]):
            Positional arguments to pass to the kernel method.
        kwargs (dict[str, Any] | None):
            Keyword arguments to pass to the kernel method.

    Returns:
        PyQrackSimulatorTask:
            The task object used to track execution.

    """
    if kwargs is None:
        kwargs = {}

    address_analysis = AddressAnalysis(dialects=kernel.dialects)
    frame, _ = address_analysis.run_analysis(kernel)
    if self.min_qubits == 0 and any(
        isinstance(a, AnyAddress) for a in frame.entries.values()
    ):
        raise ValueError(
            "All addresses must be resolved. Or set min_qubits to a positive integer."
        )

    num_qubits = max(address_analysis.qubit_count, self.min_qubits)
    options = self.options.copy()
    options["qubitCount"] = num_qubits
    memory = StackMemory(
        options,
        total=num_qubits,
    )

    return self.new_task(kernel, args, kwargs, memory)