Exemplo n.º 1
0
class mangler:
	def __init__(self, n, r, na, ne, pl, nrr, nisr, out):
		self.n = n
		self.r = r
		self.graph = GNR(n, r)
		self.na = na
		self.ne = ne
		self.pl = pl
		self.nrr = nrr
		self.nisr = nisr
		self.out = out

		# Start down to the induced subgraph
		self.strip()

		# Generate the CNF formula
		r = reducer()
		print >> sys.stderr, 'Creating the CNF formula'
		numVars, cnf = r.reduce(self.graph.getGraph())

		# Assign values and propagate
		print >> sys.stderr, 'Assigning the random variables'
		self.assignValues(numVars, cnf)

	def strip(self):
		for i in range(self.nrr):
			node = random.randint(0, self.n)
			self.graph.dropNode(node)
			self.n = self.n - 1
		for i in range(self.nisr):
			self.graph.removeIndependentSet()

	def assignValues(self, numVars, cnf):
		# list of random variables to remove
		varsToAssign = []
		for i in range(self.na):
			var = random.randint(0, numVars)
			if (var not in varsToAssign):
				varsToAssign.append(var)
			else:
				while (var in varsToAssign):
					var = random.randint(0, numVars)
				varsToAssign.append(var)

		# list of all possible variable assignments, start with all false
		assign = {}
		for i in range(self.na):
			assign[varsToAssign[i]] = False
		print("Assigning variables...")

		configIndex = 0
		for config in range(2 ** self.na):
			print("Config " + str(configIndex))
			newCnf = []
			for c in cnf: # for each clause
				include = True
				clause = c[:] # Copy over the clause
				for l in c: # for each literal
					if include:
						index = -1
						inVars = False
						if (l < 0 and (l * -1) in varsToAssign):
							index = (l * -1)
							inVars = True
						elif (l > 1 and l in varsToAssign):
							index = l
							inVars = True
						if inVars:
							if (assign[index] == True): # the literal is true, so drop the clauses
								include = False
							else:
								clause.remove(l) # remove the literal, it evaluated to false...
				if include:
					newCnf.append(clause)

			# Pass over the new CNF and find all unit clauses
			# This is for unit propagation...
			units = {}
			for c in newCnf:
				if (len(c) == 1):
					literal = c[0]
					if (literal < 0):
						units[literal * -1] = False
						# unitAssigns.append(False) # must be false
					else:
						units[literal] = True
						# unitAssigns.append(True)
			# Now that we have all of the unit literals with their truth values, perform the walk again...
			finalCnf = []

			# compute average time for each of the results
			# multiply by 2^20 for hardness!

			# TODO: Make this into a method? (oldCnf, variable list with assignments, ...)

			sizeTwoClauses = 0
			for c in newCnf: # for each clause
				include = True
				clause = c[:] # Copy over the clause
				for l in c: # for each literal
					if include:
						index = -1
						inVars = False
						if (l < 0 and (l * -1) in units.keys()):
							index = (l * -1)
							inVars = True
						elif (l > 1 and l in units.keys()):
							index = l
							inVars = True
						if inVars:
							if (units[index] == True): # the literal is true, so drop the clauses
								include = False
							else:
								clause.remove(l) # remove the literal, it evaluated to false...
				if include and len(clause) > 0: # do not append empty clauses, they cause immediate unsatisfiability...
					if (len(clause) == 2):
						sizeTwoClauses = sizeTwoClauses + 1
					finalCnf.append(clause)
				elif (len(clause) == 0):
					print("Found unsatisfiable clause in variable configuration " + str(configIndex) + ", discarding formula.")

			# Write the output file
			self.write(configIndex, numVars, finalCnf, sizeTwoClauses)
			configIndex = configIndex + 1

			# advance the configuration
			for k in assign.keys():
				if (assign[k] == True): # propagate to the next clause!
					assign[k] = False
				else:
					assign[k] = True
					break # hop out early...

	def write(self, index, numVars, cnf, numTwoClauses):
		print("Writing: " + str(self.out + "_" + str(index)))
		f = open(self.out + "_" + str(index), 'wb')
		header, clauses = makeDimacsCNF(numVars, cnf)
		f.write('c ' + str(numTwoClauses) + "\n")
		f.write(header + "\n")
		for c in clauses:
			for l in c:
				f.write(str(l) + " ")
			f.write("0 \n")
Exemplo n.º 2
0
class injector:
    def __init__(self, n, r, sample, fix, na, smart, saturate, ne, nrr, nerr, nisr, out, dump):
        self.n = n
        self.r = r
        self.sample = sample
        self.graph = GNR(n, r)
        self.fix = fix
        self.na = na
        self.smart = smart
        self.saturate = saturate
        self.ne = ne
        self.nrr = nrr
        self.nerr = nerr
        self.nisr = nisr
        self.out = out
        self.dump = dump

        # Preliminary error checking
        if self.graph.getGraph().edges() < self.na:
            raise Exception("Cannot remove more edges than the graph contains.")
        if n < (nrr + nisr):
            raise Exception("Cannot remove more vertices than the graph contains.")

            # Generate the CNF formula
            # r = reducer()
            # print >> sys.stderr, 'Creating the original CNF formula'
            # numVars, cnf = r.reduce(self.graph.getGraph())
            # self.write("reduced", numVars, cnf, 0)

            # Strip down to the induced subgraph
            # using the structural/random properties, as specified
            # by the command cmd line arguments
        print >>sys.stderr, "Stripping the graph"
        self.strip()
        print >>sys.stderr, "Filling out edges"
        # edgesAdded = self.fill() # Let exceptions carry up to -main-

        # Saturate by default!
        if saturate:
            print >>sys.stderr, "Saturating with edges..."
            self.graph.saturateAvoidK4()

        r = reducer()
        print >>sys.stderr, "Reducing to 3-SAT"
        numVars, cnf = r.reduce(self.graph.getGraph())
        self.write("reduced", numVars, cnf, 0)

        if self.dump:
            print >>sys.stderr, "Dumping the modified graph"
            self.dumpGraph()

            # Assign values and propagate, if fix was set to true
        print >>sys.stderr, "Assigning the random variables"
        if self.fix:
            self.assignAndWrite(numVars, cnf)

    def dumpGraph(self):
        for e in self.graph.getGraph().edges():
            print(str(e[0]) + " " + str(e[1]))

    def strip(self):
        for i in range(self.nrr):
            node = random.randint(0, self.n)
            self.graph.dropNode(node)
            self.n = self.n - 1
        for i in range(self.nisr):
            self.graph.removeIndependentSet()
        for i in range(self.nerr):
            edge = random.randint(0, len(self.graph.getGraph().edges()))
            self.graph.dropEdge(edge)

    def fill(self):
        edgesAdded = 0
        for i in range(self.ne):
            if self.graph.addRandomEdgeAvoidK4():
                edgesAdded = edgesAdded + 1
        return edgesAdded

    def edgesIntersect(self, e, vars, cnf):
        for c in cnf:
            for v in vars:
                if (v in c) and (e in c):
                    return True
        return False

    def assignAndWrite(self, numVars, cnf):
        numClauses = len(cnf)
        numFormulas = 0
        minTwoClauses = numClauses
        totalTwoClauses = 0
        maxTwoClauses = 0
        minThreeClauses = numClauses
        maxThreeClauses = 0
        totalThreeClauses = 0
        totalRatio = 0

        # list of random variables to remove
        varsToAssign = []
        if self.smart:
            loop = 0
            THRESHOlD = 100  # There has to be a way to determine the maximum number of disjoint things...
            for i in range(self.na):
                if loop > THRESHOlD:
                    break
                else:
                    var = random.randint(0, numVars)
                    loop = 0
                    while self.edgesIntersect(var, varsToAssign, cnf) and loop < THRESHOlD:
                        loop = loop + 1
                        var = random.randint(0, numVars)
                    if loop < THRESHOlD:
                        varsToAssign.append(var)
        else:
            for i in range(self.na):
                var = random.randint(0, numVars)
                if var not in varsToAssign:
                    varsToAssign.append(var)
                else:
                    while var in varsToAssign:
                        var = random.randint(0, numVars)
                    varsToAssign.append(var)

        print >>sys.stderr, "Variables to assign: " + str(varsToAssign)

        # list of all possible variable assignments, start with all false
        assign = {}
        for i in range(self.na):
            assign[varsToAssign[i]] = False
        print >>sys.stderr, "Assigning variables..."

        configSamples = []
        configIndex = 0
        # for config in range(2 ** self.na):
        numSamples = 2 ** self.na
        while configIndex < self.sample or numFormulas == 0:

            # Select a random configuration not already in the set
            c = random.randint(0, numSamples)
            while c in configSamples:
                c = random.randint(0, numSamples)
            configSamples.append(c)

            # Advance the configuration array...
            for i in range(len(assign.keys())):
                if ((1 << i) & c) > 0:
                    assign[assign.keys()[i]] = True
                else:
                    assign[assign.keys()[i]] = False

                    # Print out the configuration we're on...
            if configIndex % 500 == 0:
                print >>sys.stderr, "Config " + str(configIndex)
            newCnf = []
            for c in cnf:  # for each clause
                include = True
                clause = c[:]  # Copy over the clause
                settled = False
                # print("Examining clause: " + str(clause))
                for l in c:  # for each literal
                    if include and settled == False:
                        index = -1
                        inVars = False
                        negative = False
                        if l < 0 and (l * -1) in varsToAssign:
                            index = l * -1
                            inVars = True
                            negative = True
                        elif l > 1 and l in varsToAssign:
                            index = l
                            inVars = True
                        if inVars:
                            settled = True
                            if assign[index] == True and negative == False:  # the literal is true, so drop the clause
                                # print("Dropping clause because " + str(index) + " was assigned to " + str(assign[index]))
                                include = False
                            elif assign[index] == True and negative == True:
                                # print("Dropping literal " + str(index) + " from the clause because it was assigned to " + str(assign[index]))
                                include = True
                                clause.remove(l)  # remove the literal, it evaluated to false...
                            elif assign[index] == False and negative == False:
                                # print("Dropping literal " + str(index) + " from the clause because it was assigned to " + str(assign[index]))
                                include = True
                                clause.remove(l)  # remove the literal, it evaluated to false...
                            elif assign[index] == False and negative == True:
                                # print("Dropping clause because " + str(index) + " was assigned to " + str(assign[index]))
                                include = False
                if include:
                    # print("Resulting clause inserted: " + str(clause))
                    newCnf.append(clause)

                    # Pass over the new CNF and find all unit clauses
                    # This is for unit propagation...
            units = {}
            for c in newCnf:
                if len(c) == 1:
                    literal = c[0]
                    if literal < 0:
                        units[literal * -1] = False
                    else:
                        units[literal] = True
                        # Now that we have all of the unit literals with their truth values, perform the walk again...
            finalCnf = []

            sizeTwoClauses = 0
            sizeThreeClauses = 0
            write = True
            for c in newCnf:  # for each clause
                include = True
                clause = c[:]  # Copy over the clause
                for l in c:  # for each literal
                    if include:
                        index = -1
                        inVars = False
                        if l < 0 and (l * -1) in units.keys():
                            index = l * -1
                            inVars = True
                        elif l > 0 and l in units.keys():
                            index = l
                            inVars = True
                        if inVars:
                            if units[index] == True:  # the literal is true, so drop the clauses
                                include = False
                                print >>sys.stderr, "HOW CAN THIS HAPPEN?"
                                return
                            else:
                                clause.remove(l)  # remove the literal, it evaluated to false...
                if include and len(clause) > 0:  # do not append empty clauses, they cause immediate unsatisfiability...
                    if len(clause) == 2:
                        sizeTwoClauses = sizeTwoClauses + 1
                    elif len(clause) == 3:
                        sizeThreeClauses = sizeThreeClauses + 1
                    finalCnf.append(clause)
                elif len(clause) == 0:
                    # print >> sys.stderr, "Found unsatisfiable clause in variable configuration " + str(configIndex) + ", discarding formula."
                    write = False
                    break

            configIndex = configIndex + 1

            # Write the output file
            if write:
                # Compound total number of formulas
                numFormulas = numFormulas + 1
                self.write(configIndex, numVars, finalCnf, sizeTwoClauses)

                # Compound the statistics for the two clauses
                if sizeTwoClauses < minTwoClauses:
                    minTwoClauses = sizeTwoClauses
                if sizeTwoClauses > maxTwoClauses:
                    maxTwoClauses = sizeTwoClauses
                totalTwoClauses = totalTwoClauses + sizeTwoClauses

                # Compound stats for three clauses
                if sizeThreeClauses < minThreeClauses:
                    minThreeClauses = sizeThreeClauses
                if sizeThreeClauses > maxThreeClauses:
                    maxThreeClauses = sizeThreeClauses
                totalThreeClauses = totalThreeClauses + sizeThreeClauses

                # Compound ratio
                totalRatio = totalRatio + (float(totalTwoClauses) / float(totalThreeClauses))

                # Output the stats (if we actually wrote anything...)
        if numFormulas > 0:
            print >>sys.stderr, "Total number of formulas: " + str(numFormulas)
            print >>sys.stderr, "Minimum number of 2-clauses: " + str(minTwoClauses)
            print >>sys.stderr, "Maximum number of 2-clauses: " + str(maxTwoClauses)
            print >>sys.stderr, "Average number of 2-clauses: " + str(totalTwoClauses / numFormulas)
            print >>sys.stderr, "Minimum number of 3-clauses: " + str(minThreeClauses)
            print >>sys.stderr, "Maximum number of 3-clauses: " + str(maxThreeClauses)
            print >>sys.stderr, "Average number of 3-clauses: " + str(totalThreeClauses / numFormulas)
            print >>sys.stderr, "Average ratio of 2-to-3 clauses: " + str(totalRatio / numFormulas)

    def write(self, index, numVars, cnf, numTwoClauses):
        filename = self.out + "_" + str(index) + ".pickle"
        self.graph.dump(filename)
        print >>sys.stderr, str(self.out + "_" + str(index))
        f = open(self.out + "_" + str(index), "wb")
        header, clauses = makeDimacsCNF(numVars, cnf)
        f.write("c " + str(numTwoClauses) + "\n")
        f.write(header + "\n")
        for c in clauses:
            for l in c:
                f.write(str(l) + " ")
            f.write("0 \n")
Exemplo n.º 3
0
class injector:
    def __init__(self, n = 127, r = 3, fix = True, sample_size = 1, na = 0, smart = False, saturate = False, ne = 0, nrr = 0, nerr = 0, nisr = 0, out = ""):
        self.n = n
        self.r = r
        self.sample = sample_size
        self.graph = GNR(n, r)
        self.fix = fix
        self.na = na
        self.smart = smart
        self.saturate = saturate
        self.ne = ne
        self.nrr = nrr
        self.nerr = nerr
        self.nisr = nisr
        self.out = out
    def apply_modifications(self, pcmap = None):
        # Preliminary error checking
        if (self.graph.getGraph().edges() < self.na):
            raise Exception("Cannot remove more edges than the graph contains.")
        if (self.n < (self.nrr + self.nisr)):
            raise Exception("Cannot remove more vertices than the graph contains.")
        
        # Strip down to the induced subgraph
        # using the structural/random properties, as specified
        # by the command cmd line arguments
        print >> sys.stderr, 'Stripping the graph...'
        print >> sys.stderr, "Random vertices to remove: " + str(self.nrr)
        print >> sys.stderr, "Random edges to remove: " + str(self.nerr)
        print >> sys.stderr, "Maximal independent sets to remove: " + str(self.nisr)
        self.strip()
        print >> sys.stderr, 'Done.'

        # Saturate by default!
        if (self.saturate):
            print >> sys.stderr, "Saturating with edges..."
            self.graph.saturateAvoidK4();
            print >> sys.stderr, "Done."

        # Now reduce the graph to SAT
        r = reducer()
        print >> sys.stderr, "Reducing to 3-SAT..."
        
        numVars, edgeMap, cnf = r.reduce(self.graph.getGraph())
        print >> sys.stderr, "Done."

        # Output or not...
        print >> sys.stderr, "Writing the original CNF formula..."
        self.write("reduce", numVars, cnf, 0)
        print >> sys.stderr, "Done."
        print >> sys.stderr, "Writing reduced graph edge list..."
        self.dumpGraph()
        print >> sys.stderr, "Done."

        return numVars, edgeMap, cnf

    def inject(self):
        # Apply graph modifications
        numVars, edgeMap, cnf = self.apply_modifications()

        # Assign values and propagate, if fix was set to true
        print >> sys.stderr, "Performing edge assignment and writing the output..."
        self.assignAndWrite(numVars, edgeMap, cnf)
        print >> sys.stderr, "Done."

    def inject_arbitrary_graph_with_edge_precoloring(self, gnrG, pcmap):
        ''' For an arbitrary GNR graph G and set of precolored edges, precolor the edges and 
        perform the reduction to SAT using the same approach as the general inject() above.

        If the structure of the graph is to be modified, then the appropriate parameters
        should be passed into the injector constructor above.
        '''

        # Apply graph modifications
        self.n = gnrG.n
        self.r = gnrG.r
        self.graph = gnrG
        numVars, edgeMap, cnf = self.apply_modifications()

        print >> sys.stderr, "pcmap: " + str(pcmap)

        # Assign values and propagate, if fix was set to true
        print >> sys.stderr, "Performing edge assignment and writing the output..."
        self.assignAndWrite(numVars, edgeMap, cnf, pcmap)
        print >> sys.stderr, "Done."

    # Write the edge-list representation of the graph (after preprocessed)
    # as well as the pickled graph object
    def dumpGraph(self):
        filename = self.out + "_graph.pickle"
        self.graph.dump(filename)

    # Strip down the graph according to the specified parameters
    def strip(self):
        for i in range(self.nrr):
            node = random.randint(0, self.n)
            self.graph.dropNode(node)
            self.n = self.n - 1
        for i in range(self.nisr):
            self.graph.removeIndependentSet()
        for i in range(self.nerr):
            edge = random.randint(0, len(self.graph.getGraph().edges()))
            self.graph.dropEdge(edge)

    # Fill in the graph with the parameters specificed
    def fill(self):
        edgesAdded = 0
        for i in range(self.ne):
            if self.graph.addRandomEdgeAvoidK4():
                edgesAdded = edgesAdded + 1
        return edgesAdded


    def edgesIntersect(self, e, vars, cnf):
        for c in cnf:
            for v in vars:
                if (v in c) and (e in c):
                    return True
        return False

    def vte(self, var, edgeMap):
        for e in self.graph.edges():
            if edgeMap[e] == var:
                return e

    def pickRandomEdgeToAssign(varsToAssign, numVars, edgeMap, pcmap):
        var = random.randint(0, numVars)
        if (pcmap == None):
            if (var in varsToAssign):
                while (var in varsToAssign):
                    var = random.randint(0, numVars)
        else:
            ei = self.vte(var, edgeMap)
            if (var in varsToAssign or edgeMap[ei] in pcmap[0] or edgeMap[ei] in pcmap[1]):
                while (var in varsToAssign or edgeMap[ei] in pcmap[0] or edgeMap[ei] in pcmap[1]):
                    var = random.randint(0, numVars)
                    ei = self.vte(var, edgeMap)
        return var

    def assignAndWrite(self, numVars, edgeMap, cnf, pcmap = None):
        numClauses = len(cnf)
        numFormulas = 0
        minTwoClauses = numClauses
        totalTwoClauses = 0
        maxTwoClauses = 0
        minThreeClauses = numClauses
        maxThreeClauses = 0
        totalThreeClauses = 0
        totalRatio = 0

        # Pick the set of edges to assign
        # Smart selection of edges picks those that are disjoint (well, it tries to THRESHOLD times),
        #   and then it gives up and just picks a random one
        # "Dumb" selection just picks random edges, not caring whether or not they are disjoint
        varsToAssign = []
        if (self.smart):
            loop = 0
            THRESHOLD = 100 # There has to be a way to determine the maximum number of disjoint things...
            for i in range(self.na):
                if loop > THRESHOLD:
                    break
                else:
                    var = random.randint(0, numVars)
                    loop = 0
                    ei = self.vte(var, edgeMap)
                    while (self.edgesIntersect(var, varsToAssign, cnf) and loop < THRESHOLD or edgeMap[ei] in pcmap[0] or edgeMap[ei] in pcmap[1]):
                        loop = loop + 1
                        var = random.randint(0, numVars)
                    if (loop < THRESHOLD): # We found a disjoint one...
                        varsToAssign.append(var)
                    else: # Failed to find a disjoint one, so pick something at random
                        varsToAssign.append(pickRandomEdgeToAssign(varsToAssign, numVars, edgeMap, pcmap))
                        
        else:
            for i in range(self.na):
                varsToAssign.append(pickRandomEdgeToAssign(varsToAssign, numVars, edgeMap, pcmap))

        # debug
        print >> sys.stderr, "Variables to assign: " + str(varsToAssign)

        # list of all possible variable assignments, start with all false
        assign = {}
        for i in range(self.na):
            assign[varsToAssign[i]] = False

        print >> sys.stderr, "Assigning variables..."
        configSamples = []
        configIndex = 0
        #for config in range(2 ** self.na):
        numSamples = 2 ** self.na
        while (configIndex < self.sample or numFormulas == 0):
            # Select a random configuration not already in the set
            c = random.randint(0, numSamples)
            while (c in configSamples):
                c = random.randint(0, numSamples)
            configSamples.append(c)
            
            # Advance the configuration array...
            for i in range(len(assign.keys())):
                if (((1 << i) & c) > 0):
                    assign[assign.keys()[i]] = True
                else:
                    assign[assign.keys()[i]] = False

            # Print out the configuration we're on...
            if (configIndex % 500 == 0):
                print >> sys.stderr, "Config " + str(configIndex)
            newCnf = []

            # Quick hack so we don't throw NPEs
            if pcmap == None:  
                print >> sys.stderr, "nullifying the pcmap because one was not provided"
                pcmap = {}
                pcmap[0] = []
                pcmap[1] = []
            else:
                print >> sys.stdout, str(pcmap)

            for c in cnf: # for each clause
                include = True
                clause = c[:] # Copy over the clause
                settled = False
                # print("Examining clause: " + str(clause))
                for l in c: # for each literal
                    if include and settled == False:
                        index = -1
                        inVars = False
                        negative = False
                        ei = (0,0)
                        if (l < 0):
                            ei = self.vte(l * -1, edgeMap)
                        else:
                            ei = self.vte(l, edgeMap)

                        if (l < 0 and ((l * -1) in varsToAssign or ei in pcmap[0] or ei in pcmap[1])):
                            index = (l * -1)
                            inVars = True
                            negative = True
                        elif (l > 1 and (l in varsToAssign or ei in pcmap[0] or ei in pcmap[1])):
                            index = l
                            inVars = True

                        # Now check to see if this is a variable we're precoloring, and if so, handle accordingly
                        if inVars:
                            settled = True
                            truthValue = False
                            if ei in pcmap[0]:
                                truthValue = False
                            elif ei in pcmap[1]:
                                truthValue = True
                            else:
                                truthValue = assign[index]
                           
                            # Use the truth value assignment and value of the literal (whether it was negated or not) to determine what value to give it 
                            if (truthValue == True and negative == False): # the literal is true, so drop the clause
                                include = False
                            elif (truthValue == True and negative == True):
                                include = True
                                clause.remove(l) # remove the literal, it evaluated to false...
                            elif (truthValue == False and negative == False):
                                include = True
                                clause.remove(l) # remove the literal, it evaluated to false...
                            elif (truthValue == False and negative == True):
                                include = False
                if include:
                    # print("Resulting clause inserted: " + str(clause))
                    newCnf.append(clause)

            # Pass over the new CNF and find all unit clauses
            # This is for unit propagation...
            units = {}
            for c in newCnf:
                if (len(c) == 1):
                    literal = c[0]
                    if (literal < 0):
                        units[literal * -1] = False
                    else:
                        units[literal] = True
            # Now that we have all of the unit literals with their truth values, perform the walk again and do the substitution 
            # where we either remove clauses that evaluate to true or discard variables from clauses if the variable is false
            finalCnf = []
            sizeTwoClauses = 0
            sizeThreeClauses = 0
            write = True
            for c in newCnf: # for each clause
                include = True
                clause = c[:] # Copy over the clause
                for l in c: # for each literal
                    if include:
                        index = -1
                        inVars = False
                        if (l < 0 and (l * -1) in units.keys()):
                            index = (l * -1)
                            inVars = True
                        elif (l > 0 and l in units.keys()):
                            index = l
                            inVars = True
                        if inVars:
                            if (units[index] == True): # the literal is true, so drop the clause
                                include = False
                            else: # the literal is false, so remove the variable from the clause
                                clause.remove(l) # remove the literal, it evaluated to false...
                # Do not append empty clauses, they cause immediate unsatisfiability...
                if include and len(clause) > 0:
                    if (len(clause) == 2):
                        sizeTwoClauses = sizeTwoClauses + 1
                    elif (len(clause) == 3):
                        sizeThreeClauses = sizeThreeClauses + 1
                    finalCnf.append(clause)
                elif (len(clause) == 0): 
                    # if the clause is empty, then all literals evaluted to false, and so the 
                    #  solution is unsatisfiable, and we don't write the output...
                    write = False
                    break

            # Bump up our unique identifier
            configIndex = configIndex + 1

            # Write the output file
            if (write):
                # Compound total number of formulas collected and written so far
                numFormulas = numFormulas + 1
                self.write(configIndex, numVars, finalCnf, sizeTwoClauses)

                # Compound the statistics for the two clauses
                if (sizeTwoClauses < minTwoClauses):
                    minTwoClauses = sizeTwoClauses
                if (sizeTwoClauses > maxTwoClauses):
                    maxTwoClauses = sizeTwoClauses
                totalTwoClauses = totalTwoClauses + sizeTwoClauses

                # Compound stats for three clauses
                if (sizeThreeClauses < minThreeClauses):
                    minThreeClauses = sizeThreeClauses
                if (sizeThreeClauses > maxThreeClauses):
                    maxThreeClauses = sizeThreeClauses
                totalThreeClauses = totalThreeClauses + sizeThreeClauses


        # Output the stats (if we actually wrote anything...)
        if (numFormulas > 0):
            # Compute the 2/3 ratio first
            totalRatio = 1.0
            if totalThreeClauses != 0:
                totalRatio = totalRatio + (float(totalTwoClauses) / float(totalThreeClauses))

            # Now display the stats
            print >> sys.stderr, "Total number of formulas: " + str(numFormulas)
            print >> sys.stderr, "Minimum number of 2-clauses: " + str(minTwoClauses)
            print >> sys.stderr, "Maximum number of 2-clauses: " + str(maxTwoClauses)
            print >> sys.stderr, "Average number of 2-clauses: " + str(totalTwoClauses / numFormulas)
            print >> sys.stderr, "Minimum number of 3-clauses: " + str(minThreeClauses)
            print >> sys.stderr, "Maximum number of 3-clauses: " + str(maxThreeClauses)
            print >> sys.stderr, "Average number of 3-clauses: " + str(totalThreeClauses / numFormulas)
            print >> sys.stderr, "Average ratio of 2-to-3 clauses: " + str(totalRatio / numFormulas)

    # Write the resulting CNF file to disk
    def write(self, index, numVars, cnf, numTwoClauses):
        print >> sys.stderr, str(self.out + "_" + str(index))
        f = open(self.out + "_" + str(index), 'wb')
        header, clauses = makeDimacsCNF(numVars, cnf)
        f.write('c ' + str(numTwoClauses) + "\n")
        f.write(header + "\n")
        for c in clauses:
            for l in c:
                f.write(str(l) + " ")
            f.write("0 \n")