Example #1
0
class TestClone(unittest.TestCase):
    def setUp(self):
        cf = dict()
        for i in range(5):
            for j in range(5):
                cf[i, j] = (3 * i + 3 * j) % 5
        self.f = ExplicitOperation(2, 5, cf)

        cg = dict()
        for i in range(5):
            for j in range(5):
                cg[i, j] = (2 * i + 4 * j) % 5
        self.g = ExplicitOperation(2, 5, cg)

        ch = dict()
        for i in range(5):
            for j in range(5):
                ch[i, j] = (4 * i + 2 * j) % 5
        self.h = ExplicitOperation(2, 5, ch)

        self.clone = Clone([Projection(2, 5, 0), Projection(2, 5, 1), self.f, self.g, self.h])

    def test_get(self):
        self.assertEqual(self.clone[0], Projection(2, 5, 0))
        self.assertEqual(self.clone[1], Projection(2, 5, 1))
        self.assertEqual(self.clone[2], self.f)
        self.assertEqual(self.clone[3], self.g)
        self.assertEqual(self.clone[4], self.h)
        self.assertNotEqual(self.clone[0], self.f)
        self.assertNotEqual(self.clone[4], Projection(2, 5, 1))

    def test_eq(self):
        self.assertEqual(self.clone, Clone([Projection(2, 5, 0), Projection(2, 5, 1), self.h, self.f, self.g]))
        self.assertNotEqual(self.clone, Clone([Projection(2, 5, 0), Projection(2, 5, 1), self.h, self.f, self.f]))
        self.assertNotEqual(Clone([Projection(2, 5, 0), Projection(2, 5, 1), self.h, self.f, self.f]), self.clone)

    def test_index(self):
        self.assertEqual(self.clone.get_index(Projection(2, 5, 0)), 0)
        self.assertEqual(self.clone.get_index(Projection(2, 5, 1)), 1)
        self.assertEqual(self.clone.get_index(self.f), 2)
        self.assertEqual(self.clone.get_index(self.g), 3)
        self.assertEqual(self.clone.get_index(self.h), 4)

    def test_len(self):
        self.assertEqual(len(self.clone), 5)

    def test_generate(self):
        clone = Clone.generate([self.f], 2)
        self.assertEqual(clone, self.clone)

    def test_repr(self):
        self.assertEqual(self.clone, eval(repr(self.clone)))
Example #2
0
    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
Example #3
0
    def wpol(self,arity,clone=None,multimorphisms=False):
        """ Return the weighted polymorphisms.

        This method obtains the matrix of inequalities defining the
        weighted polymorphisms and uses CDD to compute a set of
        generators for the cone defined by these inequalities.
        
        :param arity: The arity.
        :type arity: integer
        :param clone: The supporting clone.
        :type clone: :class:`Clone`, Optional
        :param multimorphisms: Flag to request we only generate
            multimorphisms.
        :type multimorphisms: boolean, optional

        .. note:: We implicitly assume that any clone passed in to the
            function is a subset of the set of feasibility polymorphisms.

        .. note:: The part of the code which automatically computes the
            clone of feasibility polymorphisms is not yet implemented, so
            a user will have to compute the clone separately and pass it
            as input.
        """
        if clone is None:
            clone = Clone.all_operations(arity,self.dom)
        N = len(clone)
        A = self.wop_ineq(arity,clone)
        
        # Get the weighted polymorphism inequalities
        for row in self.wpol_ineq(arity,clone):
            if not row in A:
                A.append(row)

        poly = cdd.Polyhedron(cdd.Matrix(A))
        ray_mat = poly.get_generators()
        W = []
        for i in range(ray_mat.row_size):
            weights = []
            ops = []
            for j in range(N):
                rval = round(ray_mat[i][j+1],self.dom)
                if rval != 0:
                    weights.append(-rval)
                    ops.append(clone[j])
            W.append(wpolyanna.wop.WeightedOperation(arity,
                                                     self.dom,
                                                     ops,
                                                     weights))
        return W
Example #4
0
    def wpol_separate(self,Gamma,arity,clone=None):
        """ Test if this cost function can be expressed over a set of cost functions.

        :param Gamma: the other cost functions
        :returns: A separating weighted polymorphism if it exists and
        false otherwise.
        """
        if clone is None:
            clone = Clone.all_operations(arity,self.dom)
        N = len(clone)
        
        A = self.wop_ineq(arity,clone)
        for gamma in Gamma:
            for a in gamma.wpol_ineq(arity,clone):
                if not a in A:
                    A.append(a)

        # For each wpol inequality of this cost function, check if
        # there exists a wpol of Gamma violating it
        for c in self.wpol_ineq(arity,clone):

            prob = pulp.LpProblem()
            
            # One variable for each operation in the clone
            y = pulp.LpVariable.dicts("y",xrange(N))

            # Must satisfy all inequalities in A
            for a in A:
                prob += sum(y[i]*a[i+1] for i in xrange(N)) <= 0

            # Must violate c
            prob += sum(y[i]*c[i+1] for i in range(N)) >= 1
            
            prob.solve()

            if pulp.LpStatus[prob.status] == 'Optimal':
                op = []
                w = []
                for i in xrange(N):
                    yval = round(pulp.value(y[i]),self.dom)
                    if yval != 0:
                        op.append(clone[i])
                        w.append(yval)
                return wpolyanna.wop.WeightedOperation(arity,self.dom,op,w)
            
        # Return false if we couldn't find a separating wop
        return False
Example #5
0
    def setUp(self):
        cf = dict()
        for i in range(5):
            for j in range(5):
                cf[i, j] = (3 * i + 3 * j) % 5
        self.f = ExplicitOperation(2, 5, cf)

        cg = dict()
        for i in range(5):
            for j in range(5):
                cg[i, j] = (2 * i + 4 * j) % 5
        self.g = ExplicitOperation(2, 5, cg)

        ch = dict()
        for i in range(5):
            for j in range(5):
                ch[i, j] = (4 * i + 2 * j) % 5
        self.h = ExplicitOperation(2, 5, ch)

        self.clone = Clone([Projection(2, 5, 0), Projection(2, 5, 1), self.f, self.g, self.h])
Example #6
0
    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
Example #7
0
    def wpol_ineq(self,arity,clone=None):
        """ Return the set of inequalities the weighted polymorphisms
        must satisfy.

        This method returns a matrix A, such that the set of weighted
        operations satisfying Aw <= 0 are the weighted polymorphisms
        of this cost function. 
        :param arity: The arity of the weighted polymorphisms.
        :type arity: integer
        :param clone: The supporting clone.
        :type clone: :class:`Clone`, Optional
        :returns: A set of inequalities defining the weighted
            polymorphisms.
        :rtype: :class:`cdd.Matrix`
        
        ..note:: We implicity assume that clone is a subset of the set
            of feasibility polymorphisms. If no clone is passed as an
            argument, then we use the clone containing all operations.
        """
        if clone is None:
            clone = Clone.all_operations(arity,self.dom)
            
        # N is the number of operations in the clone. We will need a
        # variable for each of these
        N = len(clone)
        A = []

        # Divide the tuples into sets of zero and non-zero cost 
        r = self.arity
        D = range(self.dom)
        T = [[],[]]
        neg,pos = False,False
        for t in it.product(D,repeat=r):
            if self[t] == 0:
                T[0].append(t)
            else:
                T[1].append(t)
                if self[t] > 0:
                    pos = True
                elif self[t] < 0:
                    neg = True
        
        # Tableaus containing at least one non-zero tuple
        for comb in it.product([0,1],repeat=arity):
            if sum(comb) > 0:
                for X in it.product(*[T[i] for i in comb]):
                    row = [0 for _ in range(N+1)]   
                    for i in xrange(0,N):
                        row[i+1] = self[clone[i].apply_to_tableau(X)]
                    # Insert row if not already in A
                    # We keep A sorted to make this check more efficient
                    if len(A) == 0:
                        A = [row]
                    else:
                        i = binary_search(A,row)
                        if i == len(A) or A[i] != row:
                            A.insert(i,row)

        # We only need tableaus with all zero tuples if there are some
        # positive weighted tuples
        if pos:
            for X in it.product(T[0],repeat=arity):
                row = [0 for _ in range(N+1)]
                trivial = True
                for i in xrange(0,N):
                    row[i+1] = self[clone[i].apply_to_tableau(X)]
                    if row[i+1] != 0:
                        trivial = False
                if not trivial:
                    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
Example #8
0
def wpol(cost_functions,arity,clone=None,multimorphisms=False):
    """ Return the weighted polymorphisms.

    This method obtains the matrix of inequalities defining the
    weighted polymorphisms and uses CDD to compute a set of
    generators for the cone defined by these inequalities.
        
    :param arity: The arity.
    :type arity: integer
    :param clone: The supporting clone.
    :type clone: :class:`Clone`, optional
    :param multimorphisms: Flag to request we only generate
        multimorphisms.
    :type multimorphisms: boolean, optional

    .. note:: We implicitly assume that any clone passed in to the
        function is a subset of the set of feasibility polymorphisms.

    .. note:: The part of the code which automatically computes the
        clone of feasibility polymorphisms is not yet implemented, so
        a user will have to compute the clone separately and pass it
        as input.
    """
    if len(cost_functions) == 0:
        return []
    d = cost_functions[0].dom
    if clone is None:
        clone = Clone.all_operations(arity,d)
    N = len(clone)
    A = cost_functions[0].wop_ineq(arity,clone)

    # If we are only looking for multimorphisms, then we
    # have all projections with weight exactly 1
    if multimorphisms:
        for i in range(arity):
            row = [1] + [0 for _ in range(N)]
            row[i+1] = 1
            A.append(row)
            row = [-1] + [0 for _ in range(N)]
            row[i+1] = -1
            A.append(row)

    # Get the weighted polymorphism inequalities for each
    # cost function
    for cf in cost_functions:
        # Get the weighted polymorphism inequalities
        for row in cf.wpol_ineq(arity,clone):
            if not row in A:
                A.append(row)
                          
    poly = cdd.Polyhedron(cdd.Matrix(A))
    ray_mat = poly.get_generators()
    W = []
    for i in range(ray_mat.row_size):
        weights = []
        ops = []
        for j in range(N):
            rval = round(ray_mat[i][j+1],d)
            if rval != 0:
                weights.append(-rval)
                ops.append(clone[j])
        W.append(wpolyanna.wop.WeightedOperation(arity,d,ops,weights))
    return W
Example #9
0
    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
Example #10
0
    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))
Example #11
0
 def test_generate(self):
     clone = Clone.generate([self.f], 2)
     self.assertEqual(clone, self.clone)