Source code for squanch.qubit

import numpy as np

from squanch import linalg, gates

__all__ = ["QSystem", "Qubit"]

# Computational basis and projection operators
_0 = np.array([1, 0], dtype = np.complex64)
_1 = np.array([0, 1], dtype = np.complex64)
_M0 = np.outer(_0, _0)
_M1 = np.outer(_1, _1)


[docs]class QSystem: ''' Represents a multi-body, maximally-entangleable quantum system. Contains references to constituent qubits and (if applicable) its parent ``QStream``. Quantum state is represented as a density matrix in the computational basis. '''
[docs] def __init__(self, num_qubits, index = None, state = None): ''' Instatiate the quantum state for an n-qubit system :param int num_qubits: number of qubits in the system, treated as maximally entangled :param int index: index of the QSystem within the parent QStream :param np.array state: density matrix representing the quantum state. By default, |000...0><0...000| is used ''' self.num_qubits = num_qubits self.qubits = (Qubit(self, i) for i in range(num_qubits)) # this is a generator, not a list self.index = index # Register the state or generate a new one if state is not None: self.state = state # density matrix should be passed by reference and will modify the QStream.state else: # Initialize each qubit state initial_qubit_state = np.outer(_0, _0) # each qubit is initialized as |0><0| initial_system_state = np.array([], dtype = np.complex64) # Generate the matrix representation of the initial state of the n-qubit system for _ in range(self.num_qubits): initial_system_state = linalg.tensor_product(initial_system_state, initial_qubit_state) # Assign the state self.state = initial_system_state
[docs] @classmethod def from_stream(cls, qstream, index): ''' Instantiate a QSystem from a given index in a parent QStream :param QStream qstream: the parent stream :param int index: the index in the parent stream corresponding to this system :return: the QSystem object ''' return cls(qstream.system_size, index = index, state = qstream.state[index])
[docs] def qubit(self, index): ''' Access a qubit by index; self.qubits does not instantiate all qubits unless casted to a list. Use this function to access a single qubit of a given index. :param int index: qubit index to generate a qubit instance for :return: the qubit instance ''' return Qubit(self, index)
[docs] def measure_qubit(self, index): ''' Measure the qubit at a given index, partially collapsing the state based on the observed qubit value. The state vector is modified in-place by this function. :param int index: the qubit to measure :return: the measured qubit value ''' measure0 = gates.expand(_M0, index, self.num_qubits, "0" + str(index) + str(self.num_qubits)) prob0 = np.trace(np.dot(measure0, self.state)) # Determine if qubit collapses to |0> or |1> if np.random.rand() <= prob0: # qubit collapses to |0> self.state[...] = np.linalg.multi_dot([measure0, self.state, measure0.conj().T]) / prob0 return 0 else: # qubit collapses to |1> measure1 = gates.expand(_M1, index, self.num_qubits, "1" + str(self.num_qubits)) self.state[...] = np.linalg.multi_dot([measure1, self.state, measure1]) / (1.0 - prob0) return 1
[docs] def apply(self, operator): ''' Apply an N-qubit unitary operator to this system's N-qubit quantum state :param np.array operator: the unitary N-qubit operator to apply :return: nothing, the qsystem state is mutated ''' # Apply the operator # assert linalg.isHermitian(operator), "Qubit operators must be Hermitian" self.state[...] = np.linalg.multi_dot([operator, self.state, operator.conj().T])
# self.state[...] = np.linalg.multi_dot([operator, self.state, operator])
[docs]class Qubit: ''' A wrapper class representing a single qubit in an existing quantum system. '''
[docs] def __init__(self, qsystem, index): ''' Instantiate the qubit from an existing QSystem and index :param QSystem qsystem: n-qubit quantum system that this qubit points to :param int index: particle index in the quantum system, ranging from 0 to n-1 ''' self.index = index self.qsystem = qsystem
[docs] @classmethod def from_stream(cls, qstream, system_index, qubit_index): ''' Instantiate a qubit from a parent stream (via a QSystem call) :param QStream qstream: the parent stream :param int system_index: the index corresponding to the parent QSystem :param int qubit_index: the index of the qubit to be recalled :return: the qubit ''' return qstream.system(system_index).qubit(qubit_index)
[docs] def measure(self): ''' Measure a qubit, modifying the density matrix of its parent ``QSystem`` in-place :return: the measured value ''' return self.qsystem.measure_qubit(self.index)
[docs] def apply(self, operator, id = None): ''' Apply a single-qubit operator to this qubit, tensoring with I and passing to the qsystem.apply() method :param np.array operator: a single qubit (2x2) complex-valued matrix :param str cacheID: a character or string to cache the expanded operator by (e.g. Hadamard qubit 2 -> "IHII...") ''' self.qsystem.apply(gates.expand(operator, self.index, self.qsystem.num_qubits, id))
[docs] def serialize(self): ''' Generate a reference to reconstruct this qubit from shared memory :return: qubit reference as (systemIndex, qubitIndex) ''' return self.qsystem.index, self.index