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
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')])
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
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
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)
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