Source code for mqt.ddsim.pathqasmsimulator

"""Backend for DDSIM Task-Based Simulator."""

from __future__ import annotations

import pathlib
import time
from typing import TYPE_CHECKING, Any, cast

if TYPE_CHECKING:
    from quimb.tensor import Tensor, TensorNetwork

from qiskit import QuantumCircuit
from qiskit.providers import Options
from qiskit.result.models import ExperimentResult, ExperimentResultData
from qiskit.transpiler import Target

from .header import DDSIMHeader
from .pyddsim import PathCircuitSimulator, PathSimulatorConfiguration, PathSimulatorMode
from .qasmsimulator import QasmSimulatorBackend
from .target import DDSIMTargetBuilder


[docs] def read_tensor_network_file(filename: str) -> list[Tensor]: import numpy as np import pandas as pd import quimb.tensor as qtn df = pd.read_json(filename) tensors = [] for i in range(len(df["tensors"])): tens_data = [complex(real, imag) for [real, imag] in df["tensors"][i][3]] tens_shape = df["tensors"][i][2] data = np.array(tens_data).reshape(tens_shape) inds = df["tensors"][i][1] tags = df["tensors"][i][0] tensors.append(qtn.Tensor(data, inds, tags, left_inds=inds[: len(inds) // 2])) return tensors
[docs] def create_tensor_network(qc: QuantumCircuit) -> TensorNetwork: import quimb.tensor as qtn import sparse from mqt.ddsim import dump_tensor_network if isinstance(qc, QuantumCircuit): filename = qc.name + "_" + str(qc.num_qubits) + ".tensor" nqubits = qc.num_qubits else: filename = "tensor.tensor" nqubits = qc.header.n_qubits dump_tensor_network(qc, filename) tensors = read_tensor_network_file(filename) pathlib.Path(filename).unlink() # add the zero state tensor |0...0> at the beginning shape = [2] * nqubits data = sparse.COO(coords=[[0]] * nqubits, data=1, shape=shape, sorted=True, has_duplicates=False) inds = ["q" + str(i) + "_0" for i in range(nqubits)] tags = ["Q" + str(i) for i in range(nqubits)] tensors.insert(0, qtn.Tensor(data=data, inds=inds, tags=tags)) # using the following lines instead would allow much greater flexibility, # but is not supported for DD-based simulation at the moment # add the zero state tensor |0>...|0> at the beginning # shape = [2] # data = np.zeros(2) # data[0] = 1.0 # for i in range(qc.num_qubits): # inds = ['q'+str(i)+'_0'] # tags = ['Q'+str(i)] # tensors.insert(0, qtn.Tensor(data=data.reshape(shape), inds=inds, tags=tags)) return qtn.TensorNetwork(tensors)
[docs] def get_simulation_path( qc: QuantumCircuit, max_time: int = 60, max_repeats: int = 1024, parallel_runs: int = 1, dump_path: bool = True, plot_ring: bool = False, ) -> list[tuple[int, int]]: import cotengra as ctg from opt_einsum.paths import linear_to_ssa tn = create_tensor_network(qc) opt = ctg.HyperOptimizer( max_time=max_time, max_repeats=max_repeats, progbar=True, parallel=parallel_runs, minimize="flops", ) info = tn.contract(all, get="path-info", optimize=opt) path = cast(list[tuple[int, int]], linear_to_ssa(info.path)) if dump_path: filename = qc.name + "_" + str(qc.num_qubits) + ".path" if isinstance(qc, QuantumCircuit) else "simulation.path" with pathlib.Path(filename).open("w") as file: file.write(str(path)) if plot_ring: fig, _ = opt.get_tree().plot_ring() fig.savefig("simulation_ring.svg", bbox_inches="tight") return path
[docs] class PathQasmSimulatorBackend(QasmSimulatorBackend): """Python interface to MQT DDSIM Simulation Path Framework.""" _PATH_TARGET = Target(description="MQT DDSIM Simulation Path Framework Target", num_qubits=128) @staticmethod def _add_operations_to_target(target: Target) -> None: DDSIMTargetBuilder.add_0q_gates(target) DDSIMTargetBuilder.add_1q_gates(target) DDSIMTargetBuilder.add_2q_gates(target) DDSIMTargetBuilder.add_3q_gates(target) DDSIMTargetBuilder.add_multi_qubit_gates(target) DDSIMTargetBuilder.add_barrier(target) DDSIMTargetBuilder.add_measure(target) def __init__( self, name: str = "path_sim_qasm_simulator", description: str = "MQT DDSIM Simulation Path Framework", ) -> None: super().__init__(name=name, description=description) @classmethod def _default_options(cls) -> Options: return Options( shots=None, parameter_binds=None, simulator_seed=None, pathsim_configuration=PathSimulatorConfiguration(), mode=None, bracket_size=None, alternating_start=None, gate_cost=None, seed=None, cotengra_max_time=60, cotengra_max_repeats=1024, cotengra_plot_ring=False, cotengra_dump_path=True, ) @property def target(self) -> Target: return self._PATH_TARGET def _run_experiment(self, qc: QuantumCircuit, **options: Any) -> ExperimentResult: start_time = time.time() pathsim_configuration = options.get("pathsim_configuration", PathSimulatorConfiguration()) mode = options.get("mode", None) if mode is not None: pathsim_configuration.mode = PathSimulatorMode(mode) bracket_size = options.get("bracket_size", None) if bracket_size is not None: pathsim_configuration.bracket_size = bracket_size alternating_start = options.get("alternating_start", None) if alternating_start is not None: pathsim_configuration.alternating_start = alternating_start gate_cost = options.get("gate_cost", None) if gate_cost is not None: pathsim_configuration.gate_cost = gate_cost seed: int | None = options.get("seed", None) if seed is not None: pathsim_configuration.seed = seed sim = PathCircuitSimulator(qc, config=pathsim_configuration) # determine the contraction path using cotengra in case this is requested if pathsim_configuration.mode == PathSimulatorMode.cotengra: max_time = options.get("cotengra_max_time", 60) max_repeats = options.get("cotengra_max_repeats", 1024) dump_path = options.get("cotengra_dump_path", False) plot_ring = options.get("cotengra_plot_ring", False) path = get_simulation_path( qc, max_time=max_time, max_repeats=max_repeats, dump_path=dump_path, plot_ring=plot_ring, ) sim.set_simulation_path(path, False) shots = options.get("shots", 1024) setup_time = time.time() counts = sim.simulate(shots) end_time = time.time() data = ExperimentResultData( counts={hex(int(result, 2)): count for result, count in counts.items()}, statevector=None if not self._SHOW_STATE_VECTOR else sim.get_vector(), time_taken=end_time - start_time, time_setup=setup_time - start_time, time_sim=end_time - setup_time, ) return ExperimentResult( shots=shots, success=True, status="DONE", config=pathsim_configuration, seed=seed, data=data, metadata=qc.metadata, header=DDSIMHeader(qc), )