def jordan_wigner_code(n_modes):
    """ The Jordan-Wigner transform as binary code.
        
    Args:
        n_modes (int): number of modes

    Returns (BinaryCode): The Jordan-Wigner BinaryCode
    """
    return BinaryCode(numpy.identity(n_modes, dtype=int),
                      linearize_decoder(numpy.identity(n_modes, dtype=int)))
def bravyi_kitaev_code(n_modes):
    """ The Bravyi-Kitaev transform as binary code. The implementation
    follows arXiv:1208.5986.
        
    Args:
        n_modes (int): number of modes

    Returns (BinaryCode): The Bravyi-Kitaev BinaryCode
    """
    return BinaryCode(_encoder_bk(n_modes),
                      linearize_decoder(_decoder_bk(n_modes)))
    def test_transform(self):
        code = BinaryCode([[1, 0, 0], [0, 1, 0]], ['W0', 'W1', '1 + W0 + W1'])
        hamiltonian = FermionOperator('0^ 2', 0.5) + FermionOperator('2^ 0',
                                                                     0.5)
        transform = binary_code_transform(hamiltonian, code)
        correct_op = QubitOperator('X0 Z1',0.25) + QubitOperator('X0',0.25)
        self.assertTrue(transform.isclose(correct_op))

        with self.assertRaises(TypeError):
            binary_code_transform('0^ 2', code)
        with self.assertRaises(TypeError):
            binary_code_transform(hamiltonian,
                                  ([[1, 0], [0, 1]], ['w0', 'w1']))
def weight_one_segment_code():
    """ Weight-1 segment code (arXiv:1712.07067). Outputs a 3-mode, 2-qubit
    code, which encodes all the vectors (states) with Hamming weight
    (occupation) 0 and 1. n_qubits = 2, n_modes = 3.
    A linear amount of qubits can be saved  appending several instances of this
    code.

    Note:
        This code is highly non-linear and might produce a lot of terms.

    Returns (BinaryCode): weight one segment code
    """
    return BinaryCode([[1, 0, 1], [0, 1, 1]],
                      ['w0 w1 + w0', 'w0 w1 + w1', ' w0 w1'])
 def test_dissolve(self):
     code = BinaryCode([[1, 0, 0], [0, 1, 0]], ['W0', 'W1', '1 + W0 W1'])
     hamiltonian = FermionOperator('0^ 2', 0.5) + FermionOperator(
         '2^ 0', 0.5)
     transform = binary_code_transform(hamiltonian, code)
     self.assertDictEqual(
         transform.terms, {
             ((0, 'X'), (1, 'Z')): 0.375,
             ((0, 'X'), ): -0.125,
             ((0, 'Y'), ): 0.125j,
             ((0, 'Y'), (1, 'Z')): 0.125j
         })
     with self.assertRaises(ValueError):
         dissolve(((1, '1'), ))
    def test_dissolve(self):
        code = BinaryCode([[1, 0, 0], [0, 1, 0]], ['W0', 'W1', '1 + W0 W1'])
        hamiltonian = FermionOperator('0^ 2', 0.5) + FermionOperator('2^ 0',
                                                                     0.5)
        transform = binary_code_transform(hamiltonian, code)

        correct_op = QubitOperator('X0 Z1', 0.375) + \
                     QubitOperator('X0', -0.125) + \
                     QubitOperator('Y0', -0.125j) + \
                     QubitOperator('Y0 Z1', -0.125j)
        self.assertTrue(transform.isclose(correct_op))

        with self.assertRaises(ValueError):
            dissolve(((1, '1'),))
def checksum_code(n_modes, odd):
    """ Checksum code for either even or odd Hamming weight. The Hamming weight
    is defined such that it yields the total occupation number for a given basis
    state. A Checksum code with odd weight will encode all states with odd
    occupation number. This code saves one qubit: n_qubits = n_modes - 1.
        
    Args:
        n_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 (BinaryCode): The checksum BinaryCode
    """
    return BinaryCode(_encoder_checksum(n_modes),
                      _decoder_checksum(n_modes, odd))
    def test_transform(self):
        code = BinaryCode([[1, 0, 0], [0, 1, 0]], ['W0', 'W1', '1 + W0 + W1'])
        hamiltonian = FermionOperator('0^ 2', 0.5) + FermionOperator(
            '2^ 0', 0.5)
        transform = binary_code_transform(hamiltonian, code)
        self.assertDictEqual(transform.terms, {
            ((0, 'X'), (1, 'Z')): 0.25,
            ((0, 'X'), ): 0.25
        })

        with self.assertRaises(TypeError):
            binary_code_transform('0^ 2', code)
        with self.assertRaises(TypeError):
            binary_code_transform(hamiltonian,
                                  ([[1, 0], [0, 1]], ['w0', 'w1']))
def parity_code(n_modes):
    """ The parity transform (arXiv:1208.5986) as binary code. This code is
    very similar to the Jordan-Wigner transform, but with long update strings
    instead of parity strings. It does not save qubits: n_qubits = n_modes.
        
    Args:
        n_modes (int): number of modes

    Returns (BinaryCode): The parity transform BinaryCode
    """
    dec_mtx = numpy.reshape(
        ([1] + [0] * (n_modes - 1)) + ([1, 1] + (n_modes - 1) * [0]) *
        (n_modes - 2) + [1, 1], (n_modes, n_modes))
    enc_mtx = numpy.tril(numpy.ones((n_modes, n_modes), dtype=int))

    return BinaryCode(enc_mtx, linearize_decoder(dec_mtx))
def weight_two_segment_code():
    """ Weight-2 segment code (arXiv:1712.07067). Outputs a 5-mode, 4-qubit
    code, which encodes all the vectors (states) with Hamming weight
    (occupation) 2 and 1. n_qubits = 4, n_modes = 5.
    A linear amount of qubits can be saved  appending several instances of this
    code.

    Note:
        This code is highly non-linear and might produce a lot of terms.

    Returns (BinaryCode): weight-2 segment code
    """
    switch = ('w0 w1 w2 + w0 w1 w3 + w0 w2 w3 + w1 w2 w3 + w0 w1 w2 +'
              ' w0 w1 w2 w3')

    return BinaryCode(
        [[1, 0, 0, 0, 1], [0, 1, 0, 0, 1], [0, 0, 1, 0, 1], [0, 0, 0, 1, 1]], [
            'w0 + ' + switch, 'w1 + ' + switch, 'w2 + ' + switch,
            'w3 + ' + switch, switch
        ])
def weight_one_binary_addressing_code(exponent):
    """ Weight-1 binary addressing code (arXiv:1712.07067). This highly
    non-linear code works for a number of modes that is an integer power
    of two. It encodes all possible vectors with Hamming weight 1, which
    corresponds to all states with total occupation one. The amount of
    qubits saved here is maximal: for a given argument 'exponent', we find
    n_modes = 2 ^ exponent, n_qubits = exponent. 

    Note:
        This code is highly non-linear and might produce a lot of terms.

    Args:
        exponent (int): exponent for the number of modes n_modes = 2 ^ exponent

    Returns (BinaryCode): the weight one binary addressing BinaryCode
    """
    encoder = numpy.zeros((exponent, 2**exponent), dtype=int)
    decoder = [0] * (2**exponent)
    for counter in numpy.arange(2**exponent):
        encoder[:, counter], decoder[counter] = \
            _binary_address(exponent, counter)
    return BinaryCode(encoder, decoder)
def interleaved_code(modes):
    """ Linear code that reorders orbitals from even-odd to up-then-down.
    In up-then-down convention, one can append two instances of the same
    code 'c' in order to have two symmetric subcodes that are symmetric for 
    spin-up and -down modes: ' c + c '.
    In even-odd, one can concatenate with the interleaved_code 
    to have the same result:' interleaved_code * (c + c)'.
    This code changes the order of modes from (0, 1 , 2, ... , modes-1 )
    to (0, modes/2, 1 modes/2+1, ... , modes-1, modes/2 - 1).  
    n_qubits = n_modes. 
    
    Args: modes (int): number of modes, must be even 
    
    Returns (BinaryCode): code that interleaves orbitals
    """
    if modes % 2 == 1:
        raise ValueError('number of modes must be even')
    else:
        mtx = numpy.zeros((modes, modes), dtype=int)
        for index in numpy.arange(modes // 2, dtype=int):
            mtx[index, 2 * index] = 1
            mtx[modes // 2 + index, 2 * index + 1] = 1
        return BinaryCode(mtx, linearize_decoder(mtx.transpose()))