'''Opcode simulator, a fundamental simulator for UnifiedQuantum.
It simulates from a basic opcode
'''
__all__ = ["backend_alias", "OpcodeSimulator"]
from typing import List, Optional, Tuple, TYPE_CHECKING, Union
import numpy as np
from uniqc_cpp import *
from uniqc.circuit_builder.qcircuit import OpcodeType
if TYPE_CHECKING:
from .uniqc_cpp import *
[docs]
def backend_alias(backend_type):
"""Resolve backend type aliases to canonical names.
Supported backends: statevector, density_matrix
Note: Uppercase and lowercase are both supported.
Returns:
Canonical backend type string ("statevector" or "density_operator").
"""
statevector_alias = ['statevector', 'state_vector']
density_operator_alias = ['density_matrix', 'density_operator',
'densitymatrix', 'densityoperator']
density_operator_qutip_alias = ['density_matrix_qutip', 'density_operator_qutip']
backend_type = backend_type.lower()
if backend_type in statevector_alias:
return 'statevector'
elif backend_type in density_operator_alias:
return 'density_operator'
elif backend_type in density_operator_qutip_alias:
return 'density_operator_qutip'
else:
raise ValueError(f'Unknown backend type: {backend_type}')
[docs]
class OpcodeSimulator:
"""A quantum circuit simulator based on C++ that runs locally.
Args:
backend_type: Backend type for simulation. Supported: 'statevector',
'density_matrix', 'density_matrix_qutip'. Defaults to 'statevector'.
Attributes:
simulator: The underlying C++ simulator instance.
typestr: Human-readable backend type string.
"""
def __init__(self, backend_type = 'statevector'):
"""Initialize the OpcodeSimulator.
Args:
backend_type: The backend type for simulation ("statevector" or "density_matrix").
Supported aliases: statevector, state_vector, density_matrix,
density_operator, density_matrix_qutip, density_operator_qutip.
"""
backend_type = backend_alias(backend_type)
if backend_type =='statevector':
self.SimulatorType = StatevectorSimulator
self.simulator_typestr = 'statevector'
elif backend_type == 'density_operator':
self.SimulatorType = DensityOperatorSimulator
self.simulator_typestr = 'density_operator'
elif backend_type == 'density_operator_qutip':
try:
from .qutip_sim_impl import DensityOperatorSimulatorQutip
except ImportError as exc:
raise ImportError(
"backend_type='density_operator_qutip' requires the optional "
"'simulation' dependencies. Install unified-quantum[simulation]."
) from exc
self.SimulatorType = DensityOperatorSimulatorQutip
self.simulator_typestr = 'density_operator'
else:
raise ValueError(f'Unknown backend type: {backend_type}')
self.simulator = self.SimulatorType()
def _clear(self):
"""Reset the simulator by creating a fresh simulator instance."""
self.simulator = self.SimulatorType()
def _simulate_common_gate(self, operation, qubit, cbit, parameter, is_dagger, control_qubits_set):
"""Dispatch a single gate to the underlying simulator."""
if operation == 'RX':
self.simulator.rx(qubit, parameter, control_qubits_set, is_dagger)
elif operation == 'RY':
self.simulator.ry(qubit, parameter, control_qubits_set, is_dagger)
elif operation == 'RZ':
self.simulator.rz(qubit, parameter, control_qubits_set, is_dagger)
elif operation == 'U1':
self.simulator.u1(qubit, parameter, control_qubits_set, is_dagger)
elif operation == 'U2':
self.simulator.u2(qubit, parameter[0], parameter[1], control_qubits_set, is_dagger)
elif operation == 'H':
self.simulator.hadamard(qubit, control_qubits_set, is_dagger)
elif operation == 'X':
self.simulator.x(qubit, control_qubits_set, is_dagger)
elif operation == 'SX':
self.simulator.sx(qubit, control_qubits_set, is_dagger)
elif operation == 'Y':
self.simulator.y(qubit, control_qubits_set, is_dagger)
elif operation == 'Z':
self.simulator.z(qubit, control_qubits_set, is_dagger)
elif operation == 'S':
self.simulator.s(qubit, control_qubits_set, is_dagger)
elif operation == 'T':
self.simulator.t(qubit, control_qubits_set, is_dagger)
elif operation == 'CZ':
self.simulator.cz(qubit[0],
qubit[1], control_qubits_set, is_dagger)
elif operation == 'SWAP':
self.simulator.swap(qubit[0],
qubit[1], control_qubits_set, is_dagger)
elif operation == 'ISWAP':
self.simulator.iswap(qubit[0],
qubit[1], control_qubits_set, is_dagger)
elif operation == 'TOFFOLI':
self.simulator.toffoli(qubit[0],
qubit[1],
qubit[2], control_qubits_set, is_dagger)
elif operation == 'CSWAP':
self.simulator.cswap(qubit[0],
qubit[1],
qubit[2], control_qubits_set, is_dagger)
elif operation == 'XY':
self.simulator.xy(qubit[0], qubit[1],
parameter, control_qubits_set, is_dagger)
elif operation == 'CNOT':
self.simulator.cnot(qubit[0],
qubit[1], control_qubits_set, is_dagger)
elif operation == 'RPhi':
self.simulator.rphi(qubit, parameter[0], parameter[1], control_qubits_set, is_dagger)
elif operation == 'RPhi90':
self.simulator.rphi90(qubit, parameter, control_qubits_set, is_dagger)
elif operation == 'RPhi180':
self.simulator.rphi180(qubit, parameter, control_qubits_set, is_dagger)
elif operation == 'U3':
self.simulator.u3(qubit, parameter[0], parameter[1], parameter[2],
control_qubits_set, is_dagger)
elif operation == 'XX':
self.simulator.xx(qubit[0],
qubit[1],
parameter, control_qubits_set, is_dagger)
elif operation == 'YY':
self.simulator.yy(qubit[0],
qubit[1],
parameter, control_qubits_set, is_dagger)
elif operation == 'ZZ':
self.simulator.zz(qubit[0],
qubit[1],
parameter, control_qubits_set, is_dagger)
elif operation == 'UU15':
self.simulator.uu15(qubit[0],
qubit[1],
parameter, control_qubits_set, True)
elif operation == 'PHASE2Q':
self.simulator.phase2q(qubit[0], qubit[1],
parameter[0], parameter[1], parameter[2],
control_qubits_set, is_dagger)
elif operation == 'PauliError1Q':
# parameter[0]: probability of X error
# parameter[1]: probability of Y error
# parameter[2]: probability of Z error
self.simulator.pauli_error_1q(qubit, parameter[0], parameter[1], parameter[2])
elif operation == 'Depolarizing':
# parameter: depolarizing probability
self.simulator.depolarizing(qubit, parameter)
elif operation == 'BitFlip':
# parameter: bit flip probability
self.simulator.bitflip(qubit, parameter)
elif operation == 'PhaseFlip':
# parameter: phase flip probability
self.simulator.phaseflip(qubit, parameter)
elif operation == 'AmplitudeDamping':
# parameter: phase flip probability
self.simulator.amplitude_damping(qubit, parameter)
elif operation == 'PauliError2Q':
# parameter: List[float]
self.simulator.pauli_error_2q(qubit[0], qubit[1], parameter)
elif operation == 'TwoQubitDepolarizing':
# parameter: depolarizing probability
self.simulator.twoqubit_depolarizing(qubit[0], qubit[1], parameter)
elif operation == 'Kraus1Q':
# parameter: List[List[complex]]
if not isinstance(parameter, list):
raise ValueError('Kraus1Q parameter should be a list of U22 matrices.')
def build_u22(arr):
# the cases include the following:
# 1. a 2x2 np.ndarray
# 2. a 4 elements np.ndarray[complex]
# 3. a 2x2 List[List[complex]]
# 4. a 4-element List[complex]
# Final target is to transform into case (4), a 4-element List[complex]
if isinstance(arr, np.ndarray):
if arr.shape == (2, 2):
return [arr[0][0], arr[0][1], arr[1][0], arr[1][1]]
elif arr.shape == (4,):
return arr.tolist()
else:
raise ValueError('Kraus1Q parameter should be a 2x2 or 4-element list or numpy array.')
elif isinstance(arr, list):
if len(arr) == 4:
return arr
elif len(arr) == 2 and len(arr[0]) == 2 and len(arr[1]) == 2:
return [arr[0][0], arr[0][1], arr[1][0], arr[1][1]]
else:
raise ValueError('Kraus1Q parameter should be a 2x2 or 4-element list or numpy array.')
else:
raise ValueError('Kraus1Q parameter should be a 2x2 or 4-element list or numpy array.')
parameters_ = [build_u22(arr) for arr in parameter]
self.simulator.kraus1q(qubit, parameters_)
elif operation == 'AmplitudeDamping':
# parameter: gamma
self.simulator.amplitude_damping(qubit, parameter)
elif operation == 'I':
pass
elif operation == None:
pass
elif operation == 'QINIT':
pass
elif operation == 'CREG':
pass
elif operation == 'BARRIER':
pass
else:
raise RuntimeError('Unknown Opcode operation. '
f'Operation: {operation}.'
f'Full opcode: {(operation, qubit, cbit, parameter, control_qubits_set, is_dagger)}')
[docs]
def simulate_gate(self, operation, qubit, cbit, parameter, is_dagger, control_qubits_set):
"""Apply a single gate opcode to the simulator.
Args:
operation: Gate name string.
qubit: Qubit index or list of indices.
cbit: Classical bit index.
parameter: Gate parameters.
is_dagger: Whether to apply the dagger version of the gate.
control_qubits_set: Set of control qubits.
"""
# convert from set to list (to adapt to C++ input)
if control_qubits_set:
control_qubits_set = list(control_qubits_set)
else:
control_qubits_set = list()
self._simulate_common_gate(operation, qubit, cbit, parameter, is_dagger, control_qubits_set)
[docs]
def simulate_opcodes_pmeasure(self, n_qubit, program_body, measure_qubits):
"""Compute measurement probabilities for a list of opcodes.
Args:
n_qubit: Number of qubits.
program_body: List of opcodes to simulate.
measure_qubits: Qubits to measure.
Returns:
List of probabilities for each measurement outcome.
"""
self.simulator.init_n_qubit(n_qubit)
for opcode in program_body:
operation, qubit, cbit, parameter, is_dagger, control_qubits_set = opcode
self.simulate_gate(operation, qubit, cbit, parameter, is_dagger, control_qubits_set)
prob_list = self.simulator.pmeasure(measure_qubits)
return prob_list
[docs]
def simulate_opcodes_statevector(self, n_qubit, program_body):
"""Compute the final statevector after executing a list of opcodes.
Args:
n_qubit: Number of qubits.
program_body: List of opcodes to simulate.
Returns:
Statevector as a complex numpy array.
Raises:
ValueError: If backend is density_matrix type.
"""
if self.simulator_typestr == 'density_matrix':
raise ValueError('Density matrix is not supported for statevector simulation.')
self.simulator.init_n_qubit(n_qubit)
for opcode in program_body:
operation, qubit, cbit, parameter, is_dagger, control_qubits_set = opcode
self.simulate_gate(operation, qubit, cbit, parameter, is_dagger, control_qubits_set)
statevector = self.simulator.state
return statevector
[docs]
def simulate_opcodes_stateprob(self, n_qubit, program_body):
"""Compute state probabilities ``(|amplitude|^2)`` for all basis states.
Args:
n_qubit: Number of qubits.
program_body: List of opcodes to simulate.
Returns:
Array of probabilities for each basis state.
Raises:
ValueError: If simulator type is unknown.
"""
if self.simulator_typestr == 'statevector':
statevector = self.simulate_opcodes_statevector(n_qubit, program_body)
statevector = np.array(statevector)
return np.abs(statevector) ** 2
if self.simulator_typestr == 'density_operator':
self.simulator.init_n_qubit(n_qubit)
for opcode in program_body:
operation, qubit, cbit, parameter, is_dagger, control_qubits_set = opcode
self.simulate_gate(operation, qubit, cbit, parameter, is_dagger, control_qubits_set)
return self.simulator.stateprob()
raise ValueError('Unknown simulator type.')
[docs]
def simulate_opcodes_density_operator(self, n_qubit, program_body):
"""Compute the density matrix after executing a list of opcodes.
Args:
n_qubit: Number of qubits.
program_body: List of opcodes to simulate.
Returns:
Density matrix as a 2D numpy array.
Raises:
ValueError: If simulator type is unknown.
"""
if self.simulator_typestr == 'density_operator':
self.simulator.init_n_qubit(n_qubit)
for opcode in program_body:
operation, qubit, cbit, parameter, is_dagger, control_qubits_set = opcode
self.simulate_gate(operation, qubit, cbit, parameter, is_dagger, control_qubits_set)
state = self.simulator.state
state = np.array(state)
density_matrix = np.reshape(state, (2 ** n_qubit, 2 ** n_qubit), order='C')
return density_matrix
if self.simulator_typestr =='statevector':
statevector = self.simulate_opcodes_statevector(n_qubit, program_body)
statevector = np.array(statevector)
density_matrix = np.outer(statevector, np.conj(statevector))
return density_matrix
raise ValueError('Unknown simulator type.')
[docs]
def simulate_opcodes_shot(self, n_qubit, program_body : List[OpcodeType], measure_qubits):
"""Execute a list of opcodes and return a single measurement sample.
Args:
n_qubit: Number of qubits.
program_body: List of opcodes to simulate.
measure_qubits: Qubits to measure.
Returns:
Integer representing the measured bitstring (decimal).
Raises:
NotImplementedError: If backend is density_operator type.
"""
if self.simulator_typestr == 'density_operator':
raise NotImplementedError('Density matrix is not supported for shot simulation.')
self.simulator.init_n_qubit(n_qubit)
for opcode in program_body:
operation, qubit, cbit, parameter, is_dagger, control_qubits_set = opcode
self.simulate_gate(operation, qubit, cbit, parameter, is_dagger, control_qubits_set)
return self.simulator.measure_single_shot(measure_qubits)