def test_multiplication(self):
        a = BinaryCode([[0, 1, 0], [1, 0, 1]], [
            BinaryPolynomial(' w1 + w0 '),
            BinaryPolynomial('w0 + 1'),
            BinaryPolynomial('w1')
        ])
        d = BinaryCode([[0, 1], [1, 0]],
                       [BinaryPolynomial(' w0 '),
                        BinaryPolynomial('w0 w1')])

        b = a * d
        self.assertEqual(
            b.__repr__(), "[[[1, 0, 1], [0, 1, 0]],"
            " '[[W0] + [W0 W1],[1] +"
            " [W0],[W0 W1]]']")
        b = 2 * d
        self.assertEqual(
            str(b), "[[[0, 1, 0, 0], [1, 0, 0, 0], "
            "[0, 0, 0, 1], [0, 0, 1, 0]], "
            "'[[W0],[W0 W1],[W2],[W2 W3]]']")
        with self.assertRaises(BinaryCodeError):
            d * a
        with self.assertRaises(TypeError):
            2.0 * a
        with self.assertRaises(TypeError):
            a *= 2.0
        with self.assertRaises(ValueError):
            b = 0 * a
Beispiel #2
0
    def __init__(self, encoding, decoding):
        """ Initialization of a binary code.

        Args:
            encoding (np.ndarray or list): nested lists or binary 2D-array
            decoding (array or list): list of BinaryPolynomial(list-like or str)

        Raises:
            TypeError: non-list, array like encoding or decoding, unsuitable
                BinaryPolynomial generators,
            BinaryCodeError: in case of decoder/encoder size mismatch or
                decoder size, qubits indexed mismatch
        """
        if not isinstance(encoding, (numpy.ndarray, list)):
            raise TypeError('encoding must be a list or array.')

        if not isinstance(decoding, (numpy.ndarray, list)):
            raise TypeError('decoding must be a list or array.')

        self.encoder = scipy.sparse.csc_matrix(encoding)
        self.n_qubits, self.n_modes = numpy.shape(encoding)

        if self.n_modes != len(decoding):
            raise BinaryCodeError(
                'size mismatch, decoder and encoder should have the same'
                ' first dimension')

        decoder_qubits = set()
        self.decoder = []

        for symbolic_binary in decoding:
            if isinstance(symbolic_binary, (tuple, list, str, int,
                                            numpy.int32, numpy.int64)):
                symbolic_binary = BinaryPolynomial(symbolic_binary)
            if isinstance(symbolic_binary, BinaryPolynomial):
                self.decoder.append(symbolic_binary)
                decoder_qubits = decoder_qubits | set(
                    symbolic_binary.enumerate_qubits())
            else:
                raise TypeError(
                    'decoder component provided '
                    'is not a suitable for BinaryPolynomial',
                    symbolic_binary)

        if len(decoder_qubits) != self.n_qubits:
            raise BinaryCodeError(
                'decoder and encoder provided has different number of qubits')

        if max(decoder_qubits) + 1 > self.n_qubits:
            raise BinaryCodeError('decoder is not indexing some qubits. Qubits'
                                  'indexed are: {}'.format(decoder_qubits))
 def test_addition(self):
     a = BinaryCode([[0, 1, 0], [1, 0, 1]],
                    [BinaryPolynomial(' w1 + w0 '), BinaryPolynomial('w0 + 1'),
                     BinaryPolynomial('w1')])
     d = BinaryCode([[0, 1], [1, 0]],
                    [BinaryPolynomial(' w0 '), BinaryPolynomial('w0 w1')])
     summation = a + d
     self.assertEqual(str(summation), "[[[0, 1, 0, 0, 0],"
                                      " [1, 0, 1, 0, 0],"
                                      " [0, 0, 0, 0, 1],"
                                      " [0, 0, 0, 1, 0]],"
                                      " '[[W1] + [W0],[W0] + [1],"
                                      "[W1],[W2],[W2 W3]]']")
     with self.assertRaises(TypeError):
         summation += 5
def _binary_address(digits, address):
    """ Helper function to fill in an encoder column/decoder component of a
    certain number.

    Args:
        digits (int): number of digits, which is the qubit number
        address (int): column index, decoder component

    Returns (tuple): encoder column, decoder component
    """
    binary_expression = BinaryPolynomial('1')

    # isolate the binary number and fill up the mismatching digits
    address = bin(address)[2:]
    address = ('0' * (digits - len(address))) + address
    for index in numpy.arange(digits):
        binary_expression *= BinaryPolynomial('w' + str(index) + ' + 1 + ' +
                                              address[index])

    return list(map(int, list(address))), binary_expression
def _decoder_checksum(modes, odd):
    """ Helper function for checksum_code that outputs the decoder.

    Args:
        modes (int):  number of modes
        odd (int or bool): 1 (True) or 0 (False), if odd,
            we encode all states with odd Hamming weight

    Returns (list): list of BinaryPolynomial
    """
    if odd:
        all_in = BinaryPolynomial('1')
    else:
        all_in = BinaryPolynomial()

    for mode in range(modes - 1):
        all_in += BinaryPolynomial('w' + str(mode))

    djw = linearize_decoder(numpy.identity(modes - 1, dtype=int))
    djw.append(all_in)
    return djw
 def test_init_errors(self):
     with self.assertRaises(TypeError):
         BinaryCode(1,
                    [BinaryPolynomial(' w1 + w0 '), BinaryPolynomial('w0 + 1')])
     with self.assertRaises(TypeError):
         BinaryCode([[0, 1], [1, 0]], '1+w1')
     with self.assertRaises(BinaryCodeError):
         BinaryCode([[0, 1], [1, 0]], [BinaryPolynomial(' w1 + w0 ')])
     with self.assertRaises(TypeError):
         BinaryCode([[0, 1, 1], [1, 0, 0]], ['1 + w1',
                                             BinaryPolynomial('1 + w0'),
                                             2.0])
     with self.assertRaises(BinaryCodeError):
         BinaryCode([[0, 1], [1, 0]], [BinaryPolynomial(' w0 '),
                                       BinaryPolynomial('w0 + 1')])
     with self.assertRaises(BinaryCodeError):
         BinaryCode([[0, 1], [1, 0]], [BinaryPolynomial(' w5 '),
                                       BinaryPolynomial('w0 + 1')])
Beispiel #7
0
def make_parity_list(code):
    """Create the parity list from the decoder of the input code.
    The output parity list has a similar structure as code.decoder.

    Args:
        code (BinaryCode): the code to extract the parity list from.

    Returns (list): list of BinaryPolynomial, the parity list

    Raises:
        TypeError: argument is not BinaryCode
    """
    if not isinstance(code, BinaryCode):
        raise TypeError('argument is not BinaryCode')
    parity_binaries = [BinaryPolynomial()]
    for index in numpy.arange(code.n_modes - 1):
        parity_binaries += [parity_binaries[-1] + code.decoder[index]]
    return parity_binaries
Beispiel #8
0
def linearize_decoder(matrix):
    """ Outputs  linear decoding function from input matrix

    Args:
        matrix (np.ndarray or list): list of lists or 2D numpy array
            to derive the decoding function from

    Returns (list): list of BinaryPolynomial
    """
    matrix = numpy.array(list(map(numpy.array, matrix)))
    system_dim, code_dim = numpy.shape(matrix)
    decoder = [] * system_dim
    for row_idx in numpy.arange(system_dim):
        dec_str = ''
        for col_idx in numpy.arange(code_dim):
            if matrix[row_idx, col_idx] == 1:
                dec_str += 'W' + str(col_idx) + ' + '
        dec_str = dec_str.rstrip(' + ')
        decoder.append(BinaryPolynomial(dec_str))
    return decoder
Beispiel #9
0
def double_decoding(decoder_1, decoder_2):
    """ Concatenates two decodings

    Args:
        decoder_1 (iterable): list of BinaryPolynomial
            decoding of the outer code layer
        decoder_2 (iterable): list of BinaryPolynomial
            decoding of the inner code layer

    Returns (list): list of BinaryPolynomial the decoding defined by
        w -> decoder_1( decoder_2(w) )
    """
    doubled_decoder = []
    for entry in decoder_1:
        tmp_sum = 0
        for summand in entry.terms:
            tmp_term = BinaryPolynomial('1')
            for factor in summand:
                if isinstance(factor, (numpy.int32, numpy.int64, int)):
                    tmp_term *= decoder_2[factor]
            tmp_sum = tmp_term + tmp_sum
        doubled_decoder += [tmp_sum]
    return doubled_decoder
 def test_shift(self):
     decoder = [BinaryPolynomial('1'), BinaryPolynomial('1 + w1 w0')]
     with self.assertRaises(TypeError):
         shift_decoder(decoder, 2.5)
Beispiel #11
0
def binary_code_transform(hamiltonian, code):
    """ Transforms a Hamiltonian written in fermionic basis into a Hamiltonian
    written in qubit basis, via a binary code.

    The role of the binary code is to relate the occupation vectors (v0 v1 v2
    ... vN-1) that span the fermionic basis, to the qubit basis, spanned by
    binary vectors (w0, w1, w2, ..., wn-1).

    The binary code has to provide an analytic relation between the binary
    vectors (v0, v1, ..., vN-1) and (w0, w1, ..., wn-1), and possibly has the
    property N>n, when the Fermion basis is smaller than the fermionic Fock
    space. The binary_code_transform function can transform Fermion operators
    to qubit operators for custom- and qubit-saving mappings.

    Note:
        Logic multi-qubit operators are decomposed into Pauli-strings (e.g.
        CPhase(1,2) = 0.5 * (1 + Z1 + Z2 - Z1 Z2 ) ), which might increase
        the number of Hamiltonian terms drastically.

    Args:
        hamiltonian (FermionOperator): the fermionic Hamiltonian
        code (BinaryCode): the binary code to transform the Hamiltonian

    Returns (QubitOperator): the transformed Hamiltonian

    Raises:
        TypeError: if the hamiltonian is not a FermionOperator or code is not
        a BinaryCode
    """
    if not isinstance(hamiltonian, FermionOperator):
        raise TypeError('hamiltonian provided must be a FermionOperator'
                        'received {}'.format(type(hamiltonian)))

    if not isinstance(code, BinaryCode):
        raise TypeError('code provided must be a BinaryCode'
                        'received {}'.format(type(code)))

    new_hamiltonian = QubitOperator()
    parity_list = make_parity_list(code)

    # for each term in hamiltonian
    for term, term_coefficient in hamiltonian.terms.items():

        """ the updated parity and occupation account for sign changes due
        changed occupations mid-way in the term """
        updated_parity = 0  # parity sign exponent
        parity_term = BinaryPolynomial()
        changed_occupation_vector = [0] * code.n_modes
        transformed_term = QubitOperator(())

        # keep track of indices appeared before
        fermionic_indices = numpy.array([])

        # for each multiplier
        for op_idx, op_tuple in enumerate(reversed(term)):
            # get count exponent, parity exponent addition
            fermionic_indices = numpy.append(fermionic_indices, op_tuple[0])
            count = numpy.count_nonzero(
                fermionic_indices[:op_idx] == op_tuple[0])
            updated_parity += numpy.count_nonzero(
                fermionic_indices[:op_idx] < op_tuple[0])

            # update term
            extracted = extractor(code.decoder[op_tuple[0]])
            extracted *= (((-1) ** count) * ((-1) ** (op_tuple[1])) * 0.5)
            transformed_term *= QubitOperator((), 0.5) - extracted

            # update parity term and occupation vector
            changed_occupation_vector[op_tuple[0]] += 1
            parity_term += parity_list[op_tuple[0]]

        # the parity sign and parity term
        transformed_term *= QubitOperator((), (-1) ** updated_parity)
        transformed_term *= extractor(parity_term)

        # the update operator
        changed_qubit_vector = numpy.mod(code.encoder.dot(
            changed_occupation_vector), 2)

        update_operator = QubitOperator(())
        for index, q_vec in enumerate(changed_qubit_vector):
            if q_vec:
                update_operator *= QubitOperator('X' + str(index))

        # append new term to new hamiltonian
        new_hamiltonian += term_coefficient * update_operator * \
                           transformed_term

    new_hamiltonian.compress()
    return new_hamiltonian