def equivalent(self, css): """ :param css: A simple CSS object :returns: True iff the object represents the same CSS file as css. That is, contains the same edges and respects the order. """ edgeSet = { Sim.simpleRule(s, p) for (ss, pp) in self.cliques for (s, p) in product(ss, pp) } if edgeSet != css.edgeSet: if __DEBUG__: print 'There are ' + str(len(edgeSet)) +' edges in edgeSet' print 'There are ' + str(len(css.edgeSet)) +\ ' edges in css.edgeSet' print 'There are ' + str(len(edgeSet - css.edgeSet)) +\ ' edges in edgeSet - css.edgeSet' print 'There are ' + str(len(css.edgeSet - edgeSet)) +\ ' edges in css.edgeSet - edgeSet' print 'Edges missing from clique: ' +\ str(css.edgeSet - edgeSet) print 'Edges missing from simple css: ' +\ str(edgeSet - css.edgeSet) return False last_occurence = dict() i = 0 lo_to_bucket = dict() j = 0 for (ss, pp) in self.cliques: for p in pp: for s in ss: e = Sim.simpleRule(s, p) last_occurence[e] = i lo_to_bucket[i] = j i += 1 j += 1 for (e1, e2) in css.edgeOrder: # TODO: fix so edges can be in same bucket as long as they can be # ordered if not last_occurence[e1] < last_occurence[e2]: print "Edge ", e1, " last appears at property position", last_occurence[e1] print "Edge ", e1, " last appears at bucket", \ lo_to_bucket[last_occurence[e1]] print self.cliques[lo_to_bucket[last_occurence[e1]]] print "Edge ", e2, " last appears at property position", last_occurence[e2] print "Edge ", e2, " last appears at bucket", \ lo_to_bucket[last_occurence[e2]] print self.cliques[lo_to_bucket[last_occurence[e2]]] print "But the first should appear before the second!" return False return True
def trim_file(clique): """Removes nodes (selectors or properties) from cliques if all incident edges are not the final instance of that edge in the file. :param clique: A cliqueCSS that will be trimmed in place :returns: Number of nodes removed """ last_index = clique.build_last_index_map() removed = 0 # remove nodes for (i, (ss, pp)) in enumerate(clique): s_to_del = [] for s in ss: removable = True for p in pp: if last_index[simpleRule(s, p)] == i: removable = False break if removable: s_to_del.append(s) p_to_del = [] for p in pp: removable = True for s in ss: if last_index[simpleRule(s, p)] == i: removable = False break if removable: p_to_del.append(p) for s in s_to_del: ss.remove(s) for p in p_to_del: pp.remove(p) removed += len(s_to_del) + len(p_to_del) # remove empty buckets to_del = [] for (i, (ss, pp)) in enumerate(clique): if len(ss) == 0 or len(pp) == 0: bisect.insort(to_del, i) for i in reversed(to_del): clique.remove_rule(i) return removed
def __add_clique_count_size(self, i, optimizer): """Adds soft constraint counting the size of each clique i after the refactoring. Not quite accurate since we don't detect savings from emptied bicliques. :param i: An index in range(num rules in clique) :param optimizer: A Z3 Optimize to add soft_constraints to :returns: A Z3 handle to the soft_constraints, else None if none created """ h = None (ss, pp) = self.clique.cliques[i] # now subtract any nodes we can remove for s in ss: # can remove if all incident edges appearing here last appear later # or in new bucket (if new bucket appears later) can_remove = [ self.__has_edge(s, p, optimizer) for p in pp if self.last_index[simpleRule(s, p)] == i ] can_remove.append(i < self.idx) can_remove_v = _z3.Bool("can_remove_s_" + str(s) + "_from_bucket_" + str(i)) cnf_v_iff_conj(can_remove_v, can_remove, optimizer, _z3) # if edge appears after bucket, or can't remove, cost is len(s) + 1 # for trailing , h = optimizer.add_soft(can_remove_v, len(s) + 1) for p in pp: can_remove = [ self.__has_edge(s, p, optimizer) for s in ss if self.last_index[simpleRule(s, p)] == i ] can_remove.append(i < self.idx) can_remove_v = _z3.Bool("can_remove_p_" + str(p) + "_from_bucket_" + str(i)) cnf_v_iff_conj(can_remove_v, can_remove, optimizer, _z3) h = optimizer.add_soft(can_remove_v, len(p) + 1) return h
def __add_biclique_position_range(self, optimizer): """Adds to optimizer a formula asserting that biclique i cannot appear before any of its edges have appeared, or more than one bucket after the last of its edges appears in the current CSS. Also asserts that the ith insertion appears after the (i-1)th :param optimizer: A Z3 Optimize instance to add to """ first_index = self.clique.build_first_index_map() for (ss, pp) in self.bicliques: # have to be able to remove from at least two rules to make a # difference min_idx = self.second_index[(ss, pp)] max_idx = 1 + max(self.last_index[simpleRule(s, p)] for (s, p) in product(ss, pp)) # is biclique => idx > min && idx < max optimizer.add( _z3.Or(_z3.Not(self.__is_biclique((ss, pp))), self.idx > min_idx)) optimizer.add( _z3.Or(_z3.Not(self.__is_biclique((ss, pp))), self.idx <= max_idx))
def __edge_order_respected(self, edgeOrder): """ :param edgeOrder: A list of pairs of simpleRules (e1, e2) giving order e1 < e2 :returns: True iff the cliqueCSS object respects the edgeOrder """ last_occurence = dict() i = 0 for (ss, pp) in self.cliques: for p in pp: for s in ss: e = Sim.simpleRule(s, p) last_occurence[e] = i i += 1 for (e1, e2) in edgeOrder: if (e1 in last_occurence and e2 in last_occurence and not last_occurence[e1] < last_occurence[e2]): print "Edge ", e1, " last appears at property position", last_occurence[e1] print "Edge ", e2, " last appears at property position", last_occurence[e2] print "But the first should appear before the second!" return False return True
def build_last_index_map(self): """Note: constructs map anew each call. :returns: A map where last_index[simpleRule(s, p)] returns the index of the last bucket containing that edge """ last_index = dict() for (i, (ss, pp)) in enumerate(self): for (s, p) in product(ss, pp): simple = Sim.simpleRule(s,p) last_index[simple] = i return last_index
def get_edge_set(self, min_pos = 0, max_pos = -1): """Note: computes each time :param min_pos: For getting rules from a portion of the file, begin at min_pos. Must be in range(num_rules()) :param max_pos: The last position to get edges from. Can be -1 to denote end of file :returns: a set of all simpleRules covered by the cliqueCSS """ if max_pos < 0: max_pos = self.num_rules() return { Sim.simpleRule(s, p) for i in xrange(min_pos, max_pos) for (s, p) in product(self.cliques[i][0], self.cliques[i][1]) }
def __build_second_index_map(self, bicliques): """ :param bicliques: Set of bicliques (ss, pp) to build map for :returns: A map from max bicliques of simple to the second bucket in clique that contains an edge of the max biclique. Only defined for max bicliques satisfying such a property """ second_index_map = dict() first_index = self.clique.build_first_index_map() for (ss, pp) in bicliques: min_idxs = set(first_index[simpleRule(s, p)] for (s, p) in product(ss, pp)) if len(min_idxs) > 1: min_idxs.remove(min(min_idxs)) second_index_map[(ss, pp)] = min(min_idxs) return second_index_map
def __add_edges_exist(self, optimizer): """Adds a formula asserting that edges in the refactoring actually exist""" for (s, p) in product(self.sels, self.props): e = simpleRule(s, p) if e not in self.last_index: optimizer.add(_z3.Not(self.__has_edge_e(e)))
def main(): r1 = Sim.simpleRule('A','1') r2 = Sim.simpleRule('A','4') r3 = Sim.simpleRule('A','7') r4 = Sim.simpleRule('B','1') r5 = Sim.simpleRule('B','4') r6 = Sim.simpleRule('C','4') r7 = Sim.simpleRule('C','5') r8 = Sim.simpleRule('C','7') r9 = Sim.simpleRule('D','3') r10 = Sim.simpleRule('D','5') r11 = Sim.simpleRule('D','6') r12 = Sim.simpleRule('E','2') r13 = Sim.simpleRule('E','3') r14 = Sim.simpleRule('E','6') r15 = Sim.simpleRule('E','7') r16 = Sim.simpleRule('F','2') r17 = Sim.simpleRule('F','7') CSS = Sim.simpleCSS([ eval('r'+str(i)) for i in range(1,18)],[(r1,r2),(r2,r3)]) clique = cliqueCSS() clique.add_rule({r1.getSelector()},[r1.getProperty()]) clique.add_rule({r3.getSelector()},[r3.getProperty()]) clique.add_rule({r2.getSelector()},[r2.getProperty()]) assert clique.is_sub_css(CSS), 'something wrong'
def equivalent_masked(self, css): """ :param css: A simple CSS object :returns: True iff the object represents the same CSS file as css if property masking is taken into account. E.g. s {p: 1; p: 2} has p: 1 masked so not counted. Requires prop_names to have been given. """ edgeSet = set() pnEdgeSet = set() for (ss, pp) in reversed(self.cliques): for s in ss: for p in reversed(pp): pn = self.prop_names[p] if (s, pn) not in pnEdgeSet: pnEdgeSet.add((s, pn)) edgeSet.add(Sim.simpleRule(s, p)) simple_trcl = css.getTrClEdgeOrder() simple_masked = { r1 for (r1, r2) in css.getTrClEdgeOrder() if ((r1.getSelector() == r2.getSelector()) and (self.prop_names[r1.getProperty()] == self.prop_names[r2.getProperty()])) } cssEdgeSet = css.edgeSet - simple_masked if edgeSet != cssEdgeSet: if __DEBUG__: print 'There are ' + str(len(edgeSet)) +' edges in edgeSet' print 'There are ' + str(len(cssEdgeSet)) +\ ' edges in cssEdgeSet' print 'There are ' + str(len(edgeSet - cssEdgeSet)) +\ ' edges in edgeSet - cssEdgeSet' print 'There are ' + str(len(cssEdgeSet - edgeSet)) +\ ' edges in cssEdgeSet - edgeSet' print 'Edges missing from clique: ' +\ str(cssEdgeSet - edgeSet) print 'Edges missing from simple css: ' +\ str(edgeSet - cssEdgeSet) return False last_occurence = dict() i = 0 lo_to_bucket = dict() j = 0 for (ss, pp) in self.cliques: for p in pp: for s in ss: e = Sim.simpleRule(s, p) last_occurence[e] = i lo_to_bucket[i] = j i += 1 j += 1 for (e1, e2) in css.edgeOrder: if e1 in cssEdgeSet and e2 in cssEdgeSet: # TODO: fix so edges can be in same bucket as long as they can be # ordered if not last_occurence[e1] < last_occurence[e2]: print "Edge ", e1, " last appears at property position", last_occurence[e1] print "Edge ", e1, " last appears at bucket", \ lo_to_bucket[last_occurence[e1]] print self.cliques[lo_to_bucket[last_occurence[e1]]] print "Edge ", e2, " last appears at property position", last_occurence[e2] print "Edge ", e2, " last appears at bucket", \ lo_to_bucket[last_occurence[e2]] print self.cliques[lo_to_bucket[last_occurence[e2]]] print "But the first should appear before the second!" return False return True