def translate(self,F,clone=None): """ Return the translation by a list of operations. :param F: the list of operations to translate by :param clone: the clone within which the translation is taking place :returns: the translation """ if len(F) != self.arity: raise ArityError(self.arity,len(F)) k = F[0].arity if clone is None: clone = Clone.generate(self.ops + F,k) if k != clone[0].arity: raise ArityError(k,clone[0].arity) if clone[0].dom != self.dom: raise DomainError(clone[0].dom) row = [0 for _ in range(len(clone))] for (f,w) in self.weight_iter(): try: row[clone.get_index(f.compose(F))] += w except KeyError: raise KeyError return row
def translations(self,arity,clone=None): """ Return the set of translations by elements of a clone. :param clone: The clone we want to translate by. :type clone: :class:`Clone`, Optional :returns: The matrix of translations, with columns index by the clone. Each row corresponds to a single translation, storing the weight assigned to the i-th operation in the clone at the i-th location. :rtype: :class:`cdd.Matrix` .. note:: If no clone is passed as input, we use the smallest clone containing all the elements of self.ops. """ if clone is None: clone = Clone.generate(self.ops,arity) N = len(clone) # Each tuple of terms in the clone gives rise to a generator A = [] for t in it.product(range(N),repeat=self.arity): F = [clone[i] for i in t] row = [0 for _ in range(N)] for (f,w) in self.weight_iter():#zip(self.ops,self.weights): try: row[clone.get_index(f.compose(F))] += w except KeyError: raise KeyError # Add non-zero rows if they are are not already in A. # We keep A sorted to make this check more efficient if min(row) != 0: if len(A) == 0: A = [row] else: i = binary_search(A,row) if i == len(A) or A[i] != row: A.insert(i,row) return A
def wclone(self,k,clone=None,log=False): """ Returns the weighted clone generated by this weighted operation. :param k: The arity. :type k: integer :param clone: The supporting clone. :type clone: :class:`Clone`, optional :returns: A list of k-ary weighted operations which added together to get any k-ary element of the weighted clone. :rtype: :py:func:`list` of :class:`WeightedOperation` .. note:: At the moment, this is implemented rather crudely, requiring two calls to CDD. The first obtains a set of inequalities defining the cone generated by the translations. Then, inequalities to ensure that only projections receive positive weight are added and this new set of inequalities are used to obtain a set of generators for the cone of k-ary elements in the weighted clone. """ if clone is None: clone = Clone.generate(self.ops,k) if log: print "Computed Clone" # First, get the inequalities defining the cone generated by # the translations T = self.translations(k,clone) T = map(lambda row: [0] + row, T) trans_mat = cdd.Matrix(T) trans_mat.rep_type = cdd.RepType.GENERATOR trans_mat.canonicalize() if log: print "Computed Translations" for r in T: print r trans_poly = cdd.Polyhedron(trans_mat) A = trans_poly.get_inequalities() # Next, add inequalities to ensure that non-projections cannot # receive negitive weight. # Recall that the projections will always be the first k # elements of the clone wop_ineq = [[0 for _ in range(len(clone)+1)] for _ in range(k,len(clone))] for i in range(k,len(clone)): wop_ineq[i-k][i+1] = 1 A.extend(wop_ineq) A.canonicalize() if log: print "Computed Inequalities" for a in A: print a # Finally, compute generators for the weighted clone wclone_mat = cdd.Matrix(A) wclone_poly = cdd.Polyhedron(A) wclone_gen = wclone_poly.get_generators() if log: print "Computed Generators" wclone = [] for r in wclone_gen: ops = [] weights = [] if log: print r for i in range(1,len(r)): rval = round(r[i],self.dom) if rval != 0: ops.append(clone[i-1]) weights.append(rval) wclone.append(WeightedOperation(k,self.dom,ops,weights)) return wclone
def in_wclone(self,other,clone=None): """ Test if another weighted operation is in the weighted clone generated by this weighted operation. :param other: The other weighted operation. :type other: :class:`WeightedOperation` :param clone: The supporting clone (this must be of the same arity as other) and all operations of w must be contained in clone. :type clone: :class:`Clone`, Optional :returns: True if other is contained in the weighted clone, False otherwise. If True, we also return a certificate, which is a list of pairs of weights and translations. If False, we return a separating cost function. :rtype: (boolean,:py:func:`list`) .. note: If no clone is passed as input, we use the method Clone.generate to obtain the clone. """ if clone is None: # Use the clone generated by the operations in self.ops clone = Clone.generate(self.ops,other.arity) # All operations in other must be contained in the clone for f in other.ops: if not f in clone: return False N = len(clone) A = self.translations(other.arity,clone) prob = pulp.LpProblem() # A variable for each non-redundant translation y = pulp.LpVariable.dicts("y",range(len(A)),0) # No objective function prob += 0 for j in range(N): #k = other.ops.index(clone[j]) prob += (sum([A[i][j]*y[i] for i in range(len(A))]) == other.get_weight(clone[j])) prob.solve() if pulp.LpStatus[prob.status] == 'Optimal': cert = [] for i in range(len(A)): val = round(pulp.value(y[i]),self.dom) if val != 0: cert.append((val, [(A[i][j],str(clone[j])) for j in range(N) if A[i][j] != 0])) return (True,cert) # If no solution, then solve the dual # Need to figure out better method than this. Should be able # to use values of primal variables on termination. else: prob = pulp.LpProblem() # A variable for each non-redundant translation z = pulp.LpVariable.dicts("z",range(N),0) # No objective function prob += 0 for i in range(len(A)): prob += (pulp.lpSum([A[i][j]*z[j] for j in range(N)]) <= 0, "") prob += pulp.lpSum([w*z[clone.get_index(f)] for (f,w) in other.weight_iter()]) >= 1,"" prob.solve() costs = dict() for i in range(N): costs[clone[i].value_tuple()] = round(pulp.value(z[i]), self.dom) return (False,CostFunction(len(costs.keys()[0]),self.dom,costs))
def test_generate(self): clone = Clone.generate([self.f], 2) self.assertEqual(clone, self.clone)