Ejemplo n.º 1
0
def proj_gen(outcome):
    """
    Generates the measurement projector for specified Fock state.
    """

    # input parsing
    modes = len(outcome)
    photons = sum(outcome)

    # dimension
    dim = dim = comb(modes + photons - 1, photons, exact=True)
    # compute number states
    nstates = number_states(modes, photons)
    # basis set for complete space
    basis = np.eye(dim)
    # output projector
    proj = np.zeros([
        dim,
    ])

    # find basis element
    for i in range(dim):
        if (outcome == nstates[i, :]).all():
            proj = proj + basis[:, i]

    proj = proj.reshape(dim, 1)
    return np.kron(proj, dagger(proj))
Ejemplo n.º 2
0
def M_apply(M, rhob):
    """
    Applies a map M to an input batch of states
    """
    for i in range(np.shape(rhob)[0]):
        rho = np.asarray(rhob[i, :, :])
        rhob[i, :, :] *= 0
        for j in range(np.shape(M)[0]):
            rho += M[j, :, :] @ rho @ dagger(M[j, :, :])
        rhob[i, :, :] = rho

    return rhob
Ejemplo n.º 3
0
def noon_gen(modes, photons, N=None):
    """
    Generates an NOON state on the last two modes
    """
    # basic input value check
    if N is None:
        N = photons
    else:
        if N > photons:
            raise ValueError(
                "NOON state {} cannot be generated with {} photons".format(
                    N, photons))

        if modes <= 2 and N != photons:
            raise ValueError(
                "Mode number must be greater than two to support ancilla photons"
            )

    # prelims
    nstates = number_states(modes, photons)
    dim = comb(modes + photons - 1, photons, exact=True)
    basis = np.eye(dim)
    proj = np.zeros([
        dim,
    ])

    # basis states |1001> and |0110>
    if modes > 2:
        # gross
        s1 = [0] * (modes - 2)
        s2 = [0] * (modes - 2)
        s1[0] = photons - N
        s2[0] = photons - N
        s1.append(N)
        s1.append(0)
        s2.append(0)
        s2.append(N)
        s1 = np.asarray(s1)
        s2 = np.asarray(s2)
    else:
        s1 = np.asarray([N, 0])
        s2 = np.asarray([0, N])

    # this is bad even for me
    for i in range(len(nstates)):
        if (s1 == nstates[i, :]).all():
            proj = proj + basis[:, i]

        if (s2 == nstates[i, :]).all():
            proj = proj + np.exp(1j * np.pi * N) * basis[:, i]

    proj = proj.reshape(dim, 1)
    return np.kron(proj, dagger(proj)) / 2
Ejemplo n.º 4
0
    def call(self, input_states):
        """
        Method that applies memristor element to encoded quantum state. Takes as input a single quantum
        state sequence in density operator form 
        """

        # some basic data checking to catch annoying to track errors
        if len(input_states) != self.tlen:
            print("Dimension mismatch: Specified time series length does not match data length {}!={}".format(self.tlen, len(input_states)))

        # iteratively apply memristor for each time interval and produce next output state in sequence
        for i in range(self.tlen):
            # get input for time step
            time_input = np.squeeze(input_states[i,:,:])

            # get unitary for this time step 
            unitary = self.unitary_array[i,:,:]

            # apply unitary to density operator
            output_state = unitary @ time_input @ dagger(unitary)

            # apply unitary transform as left hand operator
            #left = np.einsum('ij,jl->il', unitary, time_input)u
            # right hand operator
            #out = np.einsum('il,lj->ij', left, dagger(unitary))
            
            # apply projection operators to input state
            #compute effect of  measurement projector on modes specified by photonic projectors 
            left = np.einsum('ijk,kl->ijl', self.proj, output_state)
            # can skip adjoint calculation since projectors are all real diagonals (does that seem right?)
            right = np.einsum('ijl,ilk->ijk', left, self.proj) #TODO
            # collapse projector outcome sum into single array, taking of batch broadcasting rules
            out = np.sum(right, axis=0)

            # add state to output tensor
            input_states[i,:,:] = out

            if i+1<self.tlen:
                # compute the average number of photons in measurement mode
                self.mem_states[i+1] = np.real(np.sum([np.trace(out @ self.proj[j,:,:])*self.proj_photon[j] for j in range(len(self.proj))]))

                # now normalise and scale to total number of photons
                self.mem_states[i+1] *= 2*np.pi/self.photons

                # build next unitary operator in chain
                self.update(i+1)

        #print(self.mem_states[-1])
        # return (probably) coherent output state 
        return input_states
Ejemplo n.º 5
0
    def call(self, input_states):
        """This is where the layer's logic lives.
        Arguments:
            inputs: Input tensor, or list/tuple of input tensors.
            **kwargs: Additional keyword arguments.

        Returns:
            A tensor or list/tuple of tensors.
        """

        # apply unitary channel as left operator
        input_left = np.einsum('ij,bkjl->bkil', self.unitary, input_states)
        # now as right hand operator and return 
        input_states = np.einsum('bkil,lj->bkij', input_left, dagger(self.unitary))        

        return input_states
Ejemplo n.º 6
0
def eigen_encode_map(data, modes, photons, rand_encoding=False, density=True):
    """
    Takes as input an NxMxW data array and applies a quantum encoding in given dimension
    """

    # get shape of array and package as instance, input dimension and time series length
    N,in_dim,tlen = np.shape(data)

    # calculate system dimension
    dim = comb(modes+photons-1, photons, exact=True)

    # generate state encodings - assume modes and photons are enough
    encodings = basis_generator(dim)

    # perform in-place shuffle of encoding states if requested 
    if rand_encoding:
        np.random.shuffle(encodings)

    # assert encoding is sufficent assuming binary data
    assert 2**in_dim<=len(encodings), "Woah hey woah, your system size is not sufficent to encode all states: {}<{}".format(len(encodings), 2**in_dim) 

    # preallocate output encoding array as vector state or density operator
    if density:
        data_encode = np.zeros((N,tlen,dim,dim), dtype=np.complex64)
    else:
        data_encode = np.zeros((N,tlen,dim), dtype=np.complex64)

    # no doubt smarter way of computing this but it doesn't matter for the problem sizes we consider
    for i in range(N):
        if i % 100==0:
            print("{}/{} images encoded in quantum state space".format(i,N), end="\r")
        for j in range(tlen):
            # get classical state
            classical_state = data[i,:,j]

            # compute index of state to map it to
            ind = int(''.join(str(s) for s in classical_state), 2)

            # map classical binary data to a quantum state
            if density:
                state = encodings[ind,:].reshape([-1,1])
                data_encode[i,j,:,:] = np.kron(state, dagger(state))
            else:
                data_encode[i,j,:] = encodings[ind,:]
    print()
    return data_encode
Ejemplo n.º 7
0
    def _amplitude_map(self, data, levels=0,):
        """
        Generates a standard amplitude encoding scheme. Hard to prepare in practice
        but acceptable for prototyping.
        """

        # get shape of array and package as instance, input dimension and time series length
        N,in_dim,tlen = np.shape(data)

        # check whether to apply any kind of granulation effect
        if levels>0:
            print("Parse number set: applying logistic scaling and granulation")
            data = self._granulate(data, levels=levels)

        # preallocate output encoding array as vector state or density operator
        if self.density:
            data_encode = np.zeros((N,tlen,self.dim,self.dim), dtype=np.complex64)
        else:
            data_encode = np.zeros((N,tlen,self.dim), dtype=np.complex64)

        # generate computational basis elements
        basis = np.eye(self.dim, dtype=np.complex128)

        # convert each data element - likely a smart way to vectorise it but its not worth it here
        for i in range(N):
            if i % 100==0:
                print("{}/{} images encoded in quantum state space using amplitude map scheme".format(i,N), end="\r")
            
            # iterate over each column 
            for t in range(tlen):
                # construct a new state vector
                state = np.zeros((self.dim,), dtype=np.complex128)
                # phase encoding to random states
                state[self.vector_map] = np.exp(1j*data[i,:,t]*2*np.pi)
                # normalise state vector
                state /= np.linalg.norm(state, 2)
                
                # assign to output set
                if self.density:
                    state = state.reshape([-1,1])
                    data_encode[i,t,:,:] = np.kron(state, dagger(state))
                else:
                    data_encode[i,t,:] = state

        print()
        return data_encode
Ejemplo n.º 8
0
def Udata_gen(U, num=1000):
    """
    Generates some input/output data for unitary evolution
    """
    # compute dimension of system
    dim = np.size(U, 1)

    # generate randomly sampled pure input states
    psi = haar_sample(dim=dim, num=num, pure=True, operator=True)

    # preallocate unitary output states
    phi = np.zeros_like(psi)

    # compute output states phi subject to U*psi*U'
    for i in range(num):
        phi[i, :, :] = U @ psi[i, :, :] @ dagger(U)

    psi = tf.convert_to_tensor(psi, dtype=tf.complex64, name='psi')
    phi = tf.convert_to_tensor(phi, dtype=tf.complex64, name='phi')

    return psi, phi
Ejemplo n.º 9
0
def ent_gen(dim, vec=False):
    """
    Generates a maximally entangled bi-partite system each of dimension dim
    """ 

    # pre allocate entangled state array
    ent = np.zeros((dim**2,1),dtype=np.complex128)

    # iterate over each basis element
    for i in range(dim):
        # generate computaional basis element
        comput_el = np.zeros((dim, 1), dtype=np.complex128)
        # assign value 
        comput_el[i] = 1.0

        # add to state
        ent += np.kron(comput_el, comput_el)

    if vec:
        return ent
    else:
        return np.kron(ent, dagger(ent))/dim
Ejemplo n.º 10
0
def bell_gen(modes, photons, bell=1):
    """
    Generates a bell state of two photons using the last 4 modes
    """

    assert modes >= 4, "Need at least 4 modes for Bell state generation"
    assert photons >= 2, "Need at least two photons for Bell state generation"

    # number of ancilla photons
    aphotons = photons - 2
    # number of ancilla modes
    amodes = modes - 4
    if amodes == 0 and photons > 2:
        aphotons = 0
        photons = 2
        print(
            "Warning: Must have ancilla modes for ancilla photons, truncating")

    # prelims
    nstates = number_states(modes, photons)
    # dimension of space
    dim = comb(modes + photons - 1, photons, exact=True)
    # basis set for complete space
    basis = np.eye(dim)
    # output projector
    proj = np.zeros([
        dim,
    ])

    # ancilla output state
    if amodes > 0:
        aout = [0] * amodes
        aout[0] = aphotons
    else:
        aout = []

    # generate psi-
    if bell == 2:
        # basis states |1001> and -|0110>
        s1 = aout + [1, 0, 0, 1]
        s2 = aout + [0, 1, 1, 0]

        # this is bad even for me
        for i in range(dim):
            if (s1 == nstates[i, :]).all():
                proj = proj + basis[:, i]

            if (s2 == nstates[i, :]).all():
                proj = proj - basis[:, i]

    # generate phi+
    elif bell == 3:
        # states |1010> and |0101>
        s1 = aout + [1, 0, 1, 0]
        s2 = aout + [0, 1, 0, 1]

        for i in range(dim):
            if (s1 == nstates[i, :]).all():
                proj = proj + basis[:, i]

            if (s2 == nstates[i, :]).all():
                proj = proj + basis[:, i]

    # generate phi-
    elif bell == 4:

        # states |1010> and -|0101>
        s1 = aout + [1, 0, 1, 0]
        s2 = aout + [0, 1, 0, 1]

        for i in range(dim):
            if (s1 == nstates[i, :]).all():
                proj = proj + basis[:, i]

            if (s2 == nstates[i, :]).all():
                proj = proj - basis[:, i]

    # default to psi+
    else:
        # basis states |1001> and |0110>
        s1 = aout + [1, 0, 0, 1]
        s2 = aout + [0, 1, 1, 0]

        for i in range(dim):
            if (s1 == nstates[i, :]).all():
                proj = proj + basis[:, i]

            if (s2 == nstates[i, :]).all():
                proj = proj + basis[:, i]

    proj = proj.reshape(dim, 1)
    return np.kron(proj, dagger(proj)) / 2
Ejemplo n.º 11
0
def bellgen(num=1):
    """
    Generates an optical bell state on 4 modes
    """

    # prelims
    nstates = number_states(4, 2)
    dim = comb(4 + 2 - 1, 2, exact=True)
    basis = np.eye(dim)
    proj = np.zeros([
        dim,
    ])

    # generate psi-
    if num == 2:
        # basis states |1001> and -|0110>
        s1 = np.asarray([1, 0, 0, 1])
        s2 = np.asarray([0, 1, 1, 0])

        # this is bad even for me
        for i in range(len(nstates)):
            if (s1 == nstates[i, :]).all():
                proj = proj + basis[:, i]

            if (s2 == nstates[i, :]).all():
                proj = proj - basis[:, i]

    # generate phi+
    elif num == 3:
        # states |1010> and |0101>
        s1 = np.asarray([1, 0, 1, 0])
        s2 = np.asarray([0, 1, 0, 1])

        for i in range(len(nstates)):
            if (s1 == nstates[i, :]).all():
                proj = proj + basis[:, i]
            if (s2 == nstates[i, :]).all():
                proj = proj + basis[:, i]

    # generate phi-
    elif num == 4:
        # states |1010> and -|0101>
        s1 = np.asarray([1, 0, 1, 0])
        s2 = np.asarray([0, 1, 0, 1])

        for i in range(len(nstates)):
            if (s1 == nstates[i, :]).all():
                proj = proj + basis[:, i]

            if (s2 == nstates[i, :]).all():
                proj = proj - basis[:, i]

    # default to psi+
    else:
        # basis states |1001> and |0110>
        s1 = np.asarray([1, 0, 0, 1])
        s2 = np.asarray([0, 1, 1, 0])
        for i in range(len(nstates)):
            if (s1 == nstates[i, :]).all():
                proj = proj + basis[:, i]

            if (s2 == nstates[i, :]).all():
                proj = proj + basis[:, i]

    proj = proj.reshape(dim, 1)
    return np.kron(proj, dagger(proj)) / 2
Ejemplo n.º 12
0
def entanglement_gen(dim, num=100, partition=0.5, embed_dim=None):
    """
    Generates a set of training and testing data for bipartite entanglement witnesses.
    dim gives the dimension of the subsystem and embed dim places the generated states
    into larger Hilbert space
    """


    # generate entangled state
    ent_state = ent_gen(dim)

    # generate base seperable state
    sep_state = np.ones((dim**2, dim**2),dtype=np.complex128)/dim**2

    # check if embedding must occur
    if embed_dim is not None:
        assert embed_dim>=dim**2, "embedded dimension is of insufficient size"
        data_dim = embed_dim
    else:
        data_dim = dim**2

    # initialise data constructs
    ent_states = np.zeros((num,1, data_dim, data_dim), dtype=np.complex128)
    sep_states = np.zeros((num,1, data_dim, data_dim), dtype=np.complex128)

    # iterate over it all
    for i in range(num):
        # generate random local unitaries
        #U_ent = np.kron(np.squeeze(random_U(dim=dim, num=1)), np.squeeze(random_U(dim=dim, num=1)))
        U_sep = np.kron(np.squeeze(random_U(dim=dim, num=1)), np.squeeze(random_U(dim=dim, num=1)))
        # apply to entangled state and add to collection
        ent_states[i,0,:dim**2,:dim**2] = np.squeeze(haar_sample(dim=dim**2,num=1)) #U_ent @ ent_state @ dagger(U_ent)
        # apply seperable state and add to collection
        sep_states[i,0,:dim**2,:dim**2] = U_sep @ sep_state @ dagger(U_sep)

    # generate labels
    sep_labels = np.zeros((num, 1), dtype=int)
    ent_labels = np.ones((num, 1), dtype=int)

    # concatenate everything
    entangled_data = np.concatenate([sep_states, ent_states], axis=0)
    entangled_labels = np.concatenate([sep_labels, ent_labels], axis=0)

    # shuffle everything using repeateable rng state
    seed_state = np.random.get_state()
    np.random.shuffle(entangled_data)
    np.random.set_state(seed_state)
    np.random.shuffle(entangled_labels)

    # apply partition
    partition_index = int(partition*len(entangled_data))
    # training data
    train_data = entangled_data[:partition_index]
    train_label = entangled_labels[:partition_index]

    # testing data
    test_data = entangled_data[partition_index:]
    test_label = entangled_labels[partition_index:]

    # return it all
    return train_data,train_label,test_data,test_label
Ejemplo n.º 13
0
    def _milano_map(self, data, parse=False, masks=None, input_state=None, base=[0,2*np.pi/3,4*np.pi/3]):
        """
        A highly specific encoding scheme using Milano optical chip - use eigenstate or amplitude encoding unless you know 
        what you are doing!

        This encoding assumes a constant input state and then configures an optical map that  
        """
        # get shape of array and package as instance, input dimension and time series length
        N,in_dim,tlen = np.shape(data)

        # granulate data to be accepted to our encoding scheme

        if parse:
            print("Parse flag set: applying logistic scaling and granulation")
            data = self._granulate(data, levels=len(base))

        # generate initial input state if not defined (assumes mode and photon numbers)
        if input_state is None:
            # 12 modes, 3 photons
            nstate = [0,0,1,0,0,0,1,0,0,0,1,0]
            # make use of qfunk optical library
            input_state = qop.optical_state_gen(m_num=self.modes, p_num=self.photons, nstate=nstate, density=False)

        # generate topology if not set
        if self.topology is None:
            self.topology = self._milano_topology_gen()

        # generate a set of masks if none are given
        if masks is None:
            masks = mask_generator(in_dim, 3)

        # initialise a dictionary so generated gates don't have to be rebuilt
        gate_collector = {}

        # preallocate output encoding array as vector state or density operator
        if self.density:
            data_encode = np.empty((N,tlen,self.dim,self.dim), dtype=np.complex128)
        else:
            data_encode = np.empty((N,tlen,self.dim), dtype=np.complex128)


        # iterate over each and every input image
        for i in range(N):
            if i % 100==0:
                print("{}/{} images encoded in quantum state space using Milano scheme".format(i,N), end="\r")
            # iterate over each column
            for t in range(tlen):
                # get the classical data to be encoded
                classical_state = list(data[i,:,t])
                
                # convert to string description
                state_string = ''.join(str(s) for s in classical_state)

                # check if we have already generated the resultant gate
                if state_string in gate_collector:
                    U = gate_collector[state_string]
                else:
                    # compute trinary mapping 
                    phase_shifters = [base[el] for el in classical_state]

                    # apply weighted mask for each pre-encoder MZI
                    for mask in masks:
                        # compute maximum value possible for mask
                        max_val = np.sum(mask*(len(base)-1))
                        # compute unscaled phase value for pre-encoder
                        phase_val = (base[-1]-base[0])*mask @ classical_state
                        # add to end of phase shifters and rescale with range
                        phase_shifters.append(phase_val/max_val)


                    # now iterate over topology to generate encoder

                    # compute unitary from values
                    #T(self.targets[0], self.targets[1], theta, theta, self.modes)
                    U = np.eye(self.dim)

                    # save computed unitary for repeated use
                    gate_collector[state_string] = U

                # apply computed unitary to input state
                state = U @ input_state

                if self.density:
                    data_encode[i,t,:,:] = np.kron(state, dagger(state))
                else:
                    data_encode[i,t,:] = state

        return data_encode