def expression2CG(io_config, expression): """ Inputs: io_config - input/output configuration expression - matrix representing the coefficients s_ab|xy of the Bell expression. Eg: for io_config = [[2,2],[2,2]], | s00|00 s01|00 s00|01 s01|01 | expression = | s10|00 s11|00 s10|01 s11|01 | | s00|10 s01|10 s00|11 s01|11 | | s10|10 s11|10 s10|11 s11|11 | Returns: CG-reduction of the expression matrix. """ #Adding one to each output results in all the operators being defined A = ncp.generate_measurements([int(a + 1) for a in io_config[0]], 'A') B = ncp.generate_measurements([int(b + 1) for b in io_config[1]], 'B') op_mat = np.outer(np.array(ncp.flatten(A)), np.array(ncp.flatten(B))) #Obtain the overall expression expr = np.sum(expression * op_mat) #Removing the terms we don't want for i in range(len(A)): expr = expr.subs(A[i][-1], 1 - np.sum(A[i][:-1])) for j in range(len(B)): expr = expr.subs(B[j][-1], 1 - np.sum(B[j][:-1])) # Now we get our full expression expr = expr.expand() # Removing the additional operator for i in range(len(A)): A[i] = A[i][:-1] for i in range(len(B)): B[i] = B[i][:-1] A = np.array([1] + ncp.flatten(A)) B = np.array([1] + ncp.flatten(B)) # Construct the operator cg-matrix and then find relevant coefficients op_mat = np.outer(A, B) coefs = expr.as_coefficients_dict() for i in range(len(A)): for j in range(len(B)): if op_mat[i][j] in coefs: op_mat[i][j] = coefs[op_mat[i, j]] else: op_mat[i][j] = 0 return op_mat
def test_ground_state(self): length, n, h, U, t = 2, 0.8, 3.8, -6, 1 fu = generate_operators('fu', length) fd = generate_operators('fd', length) _b = flatten([fu, fd]) monomials = [[ci for ci in _b]] monomials[-1].extend([Dagger(ci) for ci in _b]) monomials.append([cj*ci for ci in _b for cj in _b]) monomials.append([Dagger(cj)*ci for ci in _b for cj in _b]) monomials[-1].extend([cj*Dagger(ci) for ci in _b for cj in _b]) monomials.append([Dagger(cj)*Dagger(ci) for ci in _b for cj in _b]) hamiltonian = 0 for j in range(length): hamiltonian += U * (Dagger(fu[j])*Dagger(fd[j]) * fd[j]*fu[j]) hamiltonian += -h/2*(Dagger(fu[j])*fu[j] - Dagger(fd[j])*fd[j]) for k in get_neighbors(j, len(fu), width=1): hamiltonian += -t*Dagger(fu[j])*fu[k]-t*Dagger(fu[k])*fu[j] hamiltonian += -t*Dagger(fd[j])*fd[k]-t*Dagger(fd[k])*fd[j] momentequalities = [n-sum(Dagger(br)*br for br in _b)] sdpRelaxation = SdpRelaxation(_b, verbose=0) sdpRelaxation.get_relaxation(-1, objective=hamiltonian, momentequalities=momentequalities, substitutions=fermionic_constraints(_b), extramonomials=monomials) sdpRelaxation.solve() s = 0.5*(sum((Dagger(u)*u) for u in fu) - sum((Dagger(d)*d) for d in fd)) magnetization = sdpRelaxation[s] self.assertTrue(abs(magnetization-0.021325317328560453) < 10e-5)
def test_apply_substitutions(self): def apply_correct_substitutions(monomial, substitutions): if isinstance(monomial, int) or isinstance(monomial, float): return monomial original_monomial = monomial changed = True while changed: for lhs, rhs in substitutions.items(): monomial = monomial.subs(lhs, rhs) if original_monomial == monomial: changed = False original_monomial = monomial return monomial length, h, U, t = 2, 3.8, -6, 1 fu = generate_operators('fu', length) fd = generate_operators('fd', length) _b = flatten([fu, fd]) hamiltonian = 0 for j in range(length): hamiltonian += U * (Dagger(fu[j])*Dagger(fd[j]) * fd[j]*fu[j]) hamiltonian += -h/2*(Dagger(fu[j])*fu[j] - Dagger(fd[j])*fd[j]) for k in get_neighbors(j, len(fu), width=1): hamiltonian += -t*Dagger(fu[j])*fu[k]-t*Dagger(fu[k])*fu[j] hamiltonian += -t*Dagger(fd[j])*fd[k]-t*Dagger(fd[k])*fd[j] substitutions = fermionic_constraints(_b) monomials = expand(hamiltonian).as_coeff_mul()[1][0].as_coeff_add()[1] substituted_hamiltonian = sum([apply_substitutions(monomial, substitutions) for monomial in monomials]) correct_hamiltonian = sum([apply_correct_substitutions(monomial, substitutions) for monomial in monomials]) self.assertTrue(substituted_hamiltonian == expand(correct_hamiltonian))
def test_ground_state(self): length, n, h, U, t = 2, 0.8, 3.8, -6, 1 fu = generate_operators('fu', length) fd = generate_operators('fd', length) _b = flatten([fu, fd]) monomials = [[ci for ci in _b]] monomials[-1].extend([Dagger(ci) for ci in _b]) monomials.append([cj * ci for ci in _b for cj in _b]) monomials.append([Dagger(cj) * ci for ci in _b for cj in _b]) monomials[-1].extend([cj * Dagger(ci) for ci in _b for cj in _b]) monomials.append([Dagger(cj) * Dagger(ci) for ci in _b for cj in _b]) hamiltonian = 0 for j in range(length): hamiltonian += U * (Dagger(fu[j]) * Dagger(fd[j]) * fd[j] * fu[j]) hamiltonian += -h / 2 * (Dagger(fu[j]) * fu[j] - Dagger(fd[j]) * fd[j]) for k in get_neighbors(j, len(fu), width=1): hamiltonian += -t * Dagger(fu[j]) * fu[k] - t * Dagger( fu[k]) * fu[j] hamiltonian += -t * Dagger(fd[j]) * fd[k] - t * Dagger( fd[k]) * fd[j] momentequalities = [n - sum(Dagger(br) * br for br in _b)] sdpRelaxation = SdpRelaxation(_b, verbose=0) sdpRelaxation.get_relaxation(-1, objective=hamiltonian, momentequalities=momentequalities, substitutions=fermionic_constraints(_b), extramonomials=monomials) sdpRelaxation.solve() s = 0.5 * (sum((Dagger(u) * u) for u in fu) - sum( (Dagger(d) * d) for d in fd)) magnetization = sdpRelaxation[s] self.assertTrue(abs(magnetization - 0.021325317328560453) < 10e-5)
def test_violation(self): I = [[0, -1, 0], [-1, 1, 1], [0, 1, -1]] P = Probability([2, 2], [2, 2]) objective = define_objective_with_I(I, P) sdpRelaxation = MoroderHierarchy([flatten(P.parties[0]), flatten(P.parties[1])], verbose=0, normalized=False) sdpRelaxation.get_relaxation(1, objective=objective, substitutions=P.substitutions) Problem = convert_to_picos(sdpRelaxation, duplicate_moment_matrix=True) X = Problem.get_variable("X") Y = Problem.get_variable("Y") Z = Problem.add_variable("Z", (sdpRelaxation.block_struct[0], sdpRelaxation.block_struct[0])) Problem.add_constraint(Y.partial_transpose() >> 0) Problem.add_constraint(Z.partial_transpose() >> 0) Problem.add_constraint(X - Y + Z == 0) Problem.add_constraint(Z[0, 0] == 1) solution = Problem.solve(verbose=0) self.assertTrue(abs(solution["obj"] - 0.139) < 10e-3)
def __init__(self, lattice_length, lattice_width, solver, outputDir, periodic=0, window_length=0, removeequalities=False, parallel=True): SecondQuantizedModel.__init__(self, lattice_length, lattice_width, solver, outputDir, periodic, window_length, removeequalities, parallel=parallel) self._fu = generate_variables( 'fu', lattice_length * lattice_width, commutative=False) self._fd = generate_variables( 'fd', lattice_length * lattice_width, commutative=False) self._b = flatten([self._fu, self._fd]) self.mu, self.t, self.h, self.U = 0, 0, 0, 0
def test_violation(self): I = [[0, -1, 0], [-1, 1, 1], [0, 1, -1]] P = Probability([2, 2], [2, 2]) objective = define_objective_with_I(I, P) sdpRelaxation = MoroderHierarchy([flatten(P.parties[0]), flatten(P.parties[1])], verbose=0, normalized=False) sdpRelaxation.get_relaxation(1, objective=objective, substitutions=P.substitutions) Problem = sdpRelaxation.convert_to_picos(duplicate_moment_matrix=True) X = Problem.get_variable('X') Y = Problem.get_variable('Y') Z = Problem.add_variable('Z', (sdpRelaxation.block_struct[0], sdpRelaxation.block_struct[0])) Problem.add_constraint(Y.partial_transpose() >> 0) Problem.add_constraint(Z.partial_transpose() >> 0) Problem.add_constraint(X - Y + Z == 0) Problem.add_constraint(Z[0, 0] == 1) solution = Problem.solve(verbose=0) self.assertTrue(abs(solution["obj"] - 0.1236) < 10e-3)
def optimiseQubitGP(device, starting_angles, eta=1.0, vis=1.0, tol=1e-6): """ Iteratively optimise the angle choices by trying to minimise the extracted dual functional """ a_in, b_in = device.num_inputs bounds = [[0, pi / 2]] + [[-pi, pi] for k in range(a_in + b_in)] old_ang, new_ang = starting_angles[:], starting_angles[:] cg_shift = device._cgshift old_gp = 2.0 new_gp = 1.0 # Start iteratively optimising while new_gp < old_gp - tol: old_ang = new_ang[:] old_gp = new_gp # Get the current dual solution av_curr, lv_curr, _, _, status = device.dualSolution( angles2Score(device, old_ang, eta, vis)) # If this is not a bad solve then proceed with an optimisation. if status == 'optimal': # function to optimise def f0(x): ang = [x[0], x[1:a_in + 1], x[1 + a_in:]] scr = angles2Score(device, ang, eta, vis) return av_curr + np.dot(lv_curr, scr - cg_shift) # Find minimising angles res = minimize(f0, ncp.flatten(old_ang), bounds=bounds) # record results new_gp = res.fun new_ang = [ res.x[0], res.x[1:a_in + 1].tolist(), res.x[a_in + 1:].tolist() ] else: break return old_gp, old_ang
def _relax(self): """ Creates the sdp relaxation object from ncpol2sdpa. """ if self.solver == None: self.solver = self.DEFAULT_SOLVER_PATH self._eq_cons = [] # equality constraints self._proj_cons = {} # projective constraints self._A_ops = [] # Alice's operators self._B_ops = [] # Bob's operators self._obj = 0 # Objective function self._obj_const = '' # Extra objective normalisation constant self._sdp = None # SDP object # Creating the operator constraints nrm = '' # Need as many decompositions as there are generating outcomes for k in range(np.prod(self.generation_output_size)): self._A_ops += [ ncp.generate_measurements(self.io_config[0], 'A' + str(k) + '_') ] self._B_ops += [ ncp.generate_measurements(self.io_config[1], 'B' + str(k) + '_') ] self._proj_cons.update( ncp.projective_measurement_constraints(self._A_ops[k], self._B_ops[k])) #Also building a normalisation string for next step nrm += '+' + str(k) + '[0,0]' # Adding the constraints # Normalisation constraint self._eq_cons.append(nrm + '-1') self._base_constraint_expressions = [] # Create the game expressions for game in self.games: tmp_expr = 0 for k in range(np.prod(self.generation_output_size)): tmp_expr += -ncp.define_objective_with_I( game._cgmatrix, self._A_ops[k], self._B_ops[k]) self._base_constraint_expressions.append(tmp_expr) # Specify the scores for these expressions including any shifts for i, game in enumerate(self.games): #We must account for overshifting in the score coming from the decomposition self._eq_cons.append(self._base_constraint_expressions[i] - game.score - game._cgshift * (np.prod(self.generation_output_size) - 1)) self._obj, self._obj_const = guessingProbabilityObjectiveFunction( self.io_config, self.generation_inputs, self._A_ops, self._B_ops) # Initialising SDP ops = [ ncp.flatten([self._A_ops[0], self._B_ops[0]]), ncp.flatten([self._A_ops[1], self._B_ops[1]]), ncp.flatten([self._A_ops[2], self._B_ops[2]]), ncp.flatten([self._A_ops[3], self._B_ops[3]]) ] self._sdp = ncp.SdpRelaxation(ops, verbose=self.verbose, normalized=False) self._sdp.get_relaxation(level=self._relaxation_level, momentequalities=self._eq_cons, objective=self._obj, substitutions=self._proj_cons, extraobjexpr=self._obj_const)
# We include some extra monomials in the relaxation to boost rates extra_monos = [] for v in V1 + V2: for Ax in A: for a in Ax: for By in B: for b in By: extra_monos += [a * b * v] extra_monos += [a * b * Dagger(v)] # Objective function obj = A[0][0] * (V1[0] + Dagger(V1[0])) / 2.0 + A[0][1] * (V1[1] + Dagger(V1[1])) / 2.0 ops = ncp.flatten([A, B, V1, V2]) sdp = ncp.SdpRelaxation(ops, verbose=1, normalized=True, parallel=0) sdp.get_relaxation(level=LEVEL, equalities=operator_equalities, inequalities=operator_inequalities, momentequalities=moment_equalities, momentinequalities=moment_inequalities, objective=-obj, substitutions=substitutions, extramonomials=extra_monos) sdp.solve('mosek') print( f"For detection efficiency {test_eta} the system {test_sys} achieves a DI-QKD rate of {rate(sdp,test_sys,test_eta)}" )
substitutions.update({v * b: b * v}) substitutions.update({Dagger(v) * b: b * Dagger(v)}) # We can also impose the relations coming from the dilation theorem for i in range(len(V)): substitutions.update({V[i] * Dagger(V[i]): 1}) for j in range(len(V)): if i != j: substitutions.update({V[i] * Dagger(V[j]): 0}) # V* V <= 1 operator_ineqs += [1 - (Dagger(V[0])*V[0] + Dagger(V[1])*V[1] + \ Dagger(V[2])*V[2] + Dagger(V[3])*V[3])] # We build a localizing set of monomials for the constraint V* V <= 1 # This set includes the monomials AB localizing_set = ncp.nc_utils.get_all_monomials(ncp.flatten([A, B, V]), extramonomials=None, substitutions=substitutions, degree=1) localizing_set += AB # We add this set to the localizing_monomials localizing_monos += [localizing_set] # We must also specify localizing mmonomials for the other constraints of the # problem but by specifying None ncpol2sdpa uses a default set localizing_monos += [None, None, None] moment_equalities = moment_eqs[:] moment_inequalities = moment_ineqs[:] + score_con[:] operator_equalities = operator_eqs[:] operator_inequalities = operator_ineqs[:]
This example calculates the maximum quantum violation of the CHSH inequality in the probability picture with a mixed-level relaxation of 1+AB. Created on Mon Dec 1 14:19:08 2014 @author: Peter Wittek """ from ncpol2sdpa import generate_measurements, \ projective_measurement_constraints, flatten, \ SdpRelaxation, define_objective_with_I, solve_sdp level = 1 A_configuration = [2, 2] B_configuration = [2, 2] I = [[ 0, -1, 0 ], [-1, 1, 1 ], [ 0, 1, -1 ]] A = generate_measurements(A_configuration, 'A') B = generate_measurements(B_configuration, 'B') monomial_substitutions = projective_measurement_constraints( A, B) objective = define_objective_with_I(I, A, B) AB = [Ai*Bj for Ai in flatten(A) for Bj in flatten(B)] sdpRelaxation = SdpRelaxation(flatten([A, B])) sdpRelaxation.get_relaxation(level, objective=objective, substitutions=monomial_substitutions, extramonomials=AB) print(solve_sdp(sdpRelaxation))
def get_factorization_constraints(parties, moments, substitutions, level, all_parties=False, return_column_names=False): '''Creates all the constraints in moments related to n-locality, namely the factorizations <P_i P_j...> = <P_i><P_j>..., where P_i, P_j, ... are collections of operators of spacelike-separated parties i, j... :param parties: list of measurements for each of the parties spacelike separated. The structure is [party1, party2...], where party_i are the measurements of party i, arranged in the structure [meas1, meas2...], where meas_i is a list containing the symbols of the operators of measurement i. :type parties: list of lists of lists of sympy.core.Symbol :param moments: known moments obtained with get_moment_constraints. :type moments: dict :param substitutions: Measurement constraints (commuting, projective...) for the operators involved. Required by get_all_monomials. :type substitutions: dict :param level: level of the moment matrix :type level: int :param all_parties: Optional flag for specifying whether the columns for all parties should be created instead of a minimal set. :type all_parties: bool :param return_column_names: Optional flag to return the moments to which the extra columns correspond :type return_column_names: bool :returns momentsbilocal: dict with <P_i P_j...> = <P_i><P_j>... constraints. :returns extracolumns: list of sympy.core.Symbol that correspond to the extra monomials created. :returns columnsnames: list of original monomials corresponding to the names of the extra monomials created. ''' # Factorizations will at most be of size 1|2*level-1 parties_moments = [ get_all_monomials(flatten(party), None, substitutions, 2 * level - 1)[1:] for party in parties ] # Strictly speaking, we need to build extra columns of all except one party # to sucessfully implement all factorization constraints. if not all_parties: parties_moments = parties_moments[:-1] # Generate commuting symbols for all the additional columns parties_columns = [ generate_variables( str(party_moments[0])[0].split('_')[0].lower() + '_', len(party_moments)) for party_moments in parties_moments ] # Substitute symbols whose values are known parties_columns = [[ moments.get(parties_moments[i][parties_columns[i].index(element)], element) for element in party ] for i, party in enumerate(parties_columns)] # Obtain actual additional columns extracolumns = [ extra for extra in flatten(parties_columns) if isinstance(extra, Symbol) ] columnsnames = [ symbol for i, symbol in enumerate(flatten(parties_moments)) if flatten(parties_columns)[i] in extracolumns ] # Add identities for later computations partiesplus1 = [ party_moments + [S.One] for party_moments in parties_moments ] extramomentsplus1 = [extracols + [1] for extracols in parties_columns] momentsbilocal = {} for monomial in get_all_monomials(flatten(parties), None, substitutions, 2 * level)[1:]: monomial = monomial.as_ordered_factors() party_monomials = [[ element for element in monomial if element in flatten(party) ] for party in parties] factors = [prod(party) for party in party_monomials] if len(monomial) <= level: for z in flatten([extracolumns, 1]): key = prod([factor for factor in [z] + factors if factor != 1]) if all_parties: item = prod([z] + [ extramomentsplus1[i][partiesplus1[i].index(factors[i])] for i in range(len(factors)) ]) else: item = prod([z] + [ extramomentsplus1[i][partiesplus1[i].index(factors[i])] for i in range(len(factors) - 1) ] + [moments.get(factors[-1], factors[-1])]) if key != item: momentsbilocal[key] = item else: if prod([ len(party_monomial) < 2 * level for party_monomial in party_monomials ]): key = prod([factor for factor in factors if factor != 1]) if all_parties: item = prod([ extramomentsplus1[i][partiesplus1[i].index(factors[i])] for i in range(len(factors)) ]) else: item = prod([ extramomentsplus1[i][partiesplus1[i].index(factors[i])] for i in range(len(factors) - 1) ] + [moments.get(factors[-1], factors[-1])]) if key != item: momentsbilocal[key] = item if return_column_names: return momentsbilocal, extracolumns, columnsnames return momentsbilocal, extracolumns