def fillPopulationEnergyGraph(self, alignment): remainingObjects = self.children.copy() if alignment is Alignment.northSouth: blocksToActOn = self.westernChildBlocks else: blocksToActOn = self.northernChildBlocks # work west to east or north to south if len(blocksToActOn) > 0: for blockToActOn in blocksToActOn: blockToActOn.populationEnergy = blockToActOn.population remainingObjects.remove(blockToActOn) filledBlocks = blocksToActOn.copy() while len(remainingObjects) > 0: neighborsOfBlocks = getNeighborsForGraphObjectsInList(graphObjects=blocksToActOn, inList=remainingObjects) if len(neighborsOfBlocks) > 0: blocksToActOn = neighborsOfBlocks else: saveDataToFileWithDescription(data=self, censusYear='', stateName='', descriptionOfInfo='ErrorCase-NoNeighborsForGraphGroups') plotGraphObjectGroups([self.children, blocksToActOn], showDistrictNeighborConnections=True) plotBlocksForRedistrictingGroup(self, showBlockNeighborConnections=True) raise RuntimeError("Can't find neighbors for graph objects") blocksToActOnThisRound = blocksToActOn.copy() while len(blocksToActOnThisRound) > 0: blocksActedUpon = [] for blockToActOn in blocksToActOnThisRound: previousNeighbors = getNeighborsForGraphObjectsInList(graphObjects=[blockToActOn], inList=filledBlocks) if len(previousNeighbors) is not 0: lowestPopulationEnergyNeighbor = min(previousNeighbors, key=lambda block: block.populationEnergy) blockToActOn.populationEnergy = lowestPopulationEnergyNeighbor.populationEnergy + blockToActOn.population remainingObjects.remove(blockToActOn) blocksActedUpon.append(blockToActOn) filledBlocks.append(blockToActOn) blocksToActOnThisRound = [block for block in blocksToActOnThisRound if block not in blocksActedUpon] if len(blocksActedUpon) == 0: saveDataToFileWithDescription(data=self, censusYear='', stateName='', descriptionOfInfo='ErrorCase-BlocksCanNotFindPreviousNeighbor') plotGraphObjectGroups([filledBlocks, blocksToActOnThisRound]) plotBlocksForRedistrictingGroup(self, showBlockNeighborConnections=True, showGraphHeatmap=True, showBlockGraphIds=True) raise ReferenceError("Can't find previous neighbor for {0}".format(blocksToActOnThisRound)) # add population to surrounding neighbors population energy # this is to help create smoother graphs in urban areas for block in self.children: for neighborBlock in block.allNeighbors: neighborBlock.populationEnergy += block.population
def getPopulationEnergySplit(self, alignment, shouldDrawGraph=False): polygonSplitResult = self.getPopulationEnergyPolygonSplit(alignment=alignment, shouldDrawGraph=shouldDrawGraph) polygonSplitResultType = polygonSplitResult[0] if polygonSplitResultType is SplitType.NoSplit: return SplitType.NoSplit, None elif polygonSplitResultType is SplitType.ForceSplitAllBlocks: return SplitType.ForceSplitAllBlocks, None polygonSplits = polygonSplitResult[1] aSplitPolygon = polygonSplits[0] bSplitPolygon = polygonSplits[1] if polygonSplitResultType is SplitType.SplitIncludedInSeam: seamOnEdge = True seamSplitPolygon = None else: seamOnEdge = False seamSplitPolygon = polygonSplitResult[2] aSplit = [] bSplit = [] seamSplit = [] for block in self.children: if doesPolygonContainTheOther(container=aSplitPolygon, target=block.geometry, ignoreInteriors=False): aSplit.append(block) elif doesPolygonContainTheOther(container=bSplitPolygon, target=block.geometry, ignoreInteriors=False): bSplit.append(block) elif not seamOnEdge and doesPolygonContainTheOther(container=seamSplitPolygon, target=block.geometry, ignoreInteriors=False): seamSplit.append(block) # elif allIntersectingPolygons(seamSplitPolygon, block.geometry): # seamSplit.append(block) # plotPolygons([block.geometry]) else: saveDataToFileWithDescription(data=[self, alignment, aSplitPolygon, bSplitPolygon, seamSplitPolygon, block.geometry, seamOnEdge, polygonSplitResultType], censusYear='', stateName='', descriptionOfInfo='ErrorCase-CouldNotFindContainerForBlock') plotPolygons([aSplitPolygon, bSplitPolygon, seamSplitPolygon, block.geometry]) raise RuntimeError("Couldn't find a container for block: {0}".format(block.geometry)) aSplitPopulation = sum(block.population for block in aSplit) bSplitPopulation = sum(block.population for block in bSplit) if aSplitPopulation < bSplitPopulation: aSplit += seamSplit else: bSplit += seamSplit if shouldDrawGraph: plotGraphObjectGroups(graphObjectGroups=[aSplit, bSplit]) return SplitType.NormalSplit, (aSplit, bSplit)
def validateContiguousRedistrictingGroups(groupList): contiguousRegions = findContiguousGroupsOfGraphObjects(groupList) if len(contiguousRegions) > 1: saveDataToFileWithDescription(data=contiguousRegions, censusYear='', stateName='', descriptionOfInfo='ErrorCase-GroupsAreNotContiguous') plotGraphObjectGroups(contiguousRegions, showDistrictNeighborConnections=True) raise ValueError("Don't have a contiguous set of RedistrictingGroups. There are {0} distinct groups".format( len(contiguousRegions)))
def validateBlockNeighbors(self): contiguousRegions = findContiguousGroupsOfGraphObjects(self.children) if len(contiguousRegions) > 1: saveDataToFileWithDescription(data=[self, contiguousRegions], censusYear='', stateName='', descriptionOfInfo='ErrorCase-BlocksNotContiguous') plotGraphObjectGroups(contiguousRegions, showDistrictNeighborConnections=True) plotBlocksForRedistrictingGroup(self, showBlockNeighborConnections=True, showBlockGraphIds=True) raise RuntimeError("Don't have a contiguous set of AtomicBlocks. There are {0} distinct groups.".format( len(contiguousRegions))) for block in self.children: neighborBlocksNotInGroup = [neighborBlock for neighborBlock in block.allNeighbors if neighborBlock not in self.children] if len(neighborBlocksNotInGroup): saveDataToFileWithDescription(data=[self, block], censusYear='', stateName='', descriptionOfInfo='ErrorCase-BlockHasNeighborOutsideRedistrictingGroup') plotBlocksForRedistrictingGroup(self, showBlockNeighborConnections=True, showBlockGraphIds=True) raise RuntimeError("Some blocks have neighbor connections with block outside the redistricting group")
def cutDistrictIntoExactRatio( self, ratio, populationDeviation, weightingMethod, breakingMethod, fillOriginDirection=None, shouldDrawFillAttempts=False, shouldDrawEachStep=False, shouldMergeIntoFormerRedistrictingGroups=False, shouldRefillEachPass=False, fastCalculations=True, showDetailedProgress=False, shouldSaveProgress=True): ratioTotal = ratio[0] + ratio[1] idealDistrictASize = int(self.population / (ratioTotal / ratio[0])) idealDistrictBSize = int(self.population / (ratioTotal / ratio[1])) candidateDistrictA = [] candidateDistrictB = [] districtStillNotExactlyCut = True tqdm.write( ' *** Attempting forest fire fill for a {0} to {1} ratio on: ***' .format(ratio[0], ratio[1], id(self))) districtAStartingGroup = None count = 1 while districtStillNotExactlyCut: tqdm.write( ' *** Starting forest fire fill pass #{0} ***'.format( count)) if districtAStartingGroup is None: if len(candidateDistrictA) == 0: districtAStartingGroup = None else: if shouldRefillEachPass: districtAStartingGroup = None else: districtAStartingGroup = candidateDistrictA if breakingMethod is BreakingMethod.splitBestCandidateGroup: returnBestCandidateGroup = True else: returnBestCandidateGroup = False districtCandidateResult = self.cutDistrictIntoRoughRatio( idealDistrictASize=idealDistrictASize, weightingMethod=weightingMethod, districtAStartingGroup=districtAStartingGroup, fillOriginDirection=fillOriginDirection, shouldDrawEachStep=shouldDrawEachStep, returnBestCandidateGroup=returnBestCandidateGroup, fastCalculations=fastCalculations) districtCandidates = districtCandidateResult[0] nextBestGroupForCandidateDistrictA = districtCandidateResult[1] fillOriginDirection = districtCandidateResult[2] districtAStartingGroup = districtCandidateResult[3] candidateDistrictA = districtCandidates[0] candidateDistrictB = districtCandidates[1] if shouldDrawFillAttempts: if nextBestGroupForCandidateDistrictA is None: nextBestGroupForCandidateDistrictA = [] plotGraphObjectGroups( graphObjectGroups=[ candidateDistrictA, candidateDistrictB, nextBestGroupForCandidateDistrictA ], showDistrictNeighborConnections=True, saveImages=True, saveDescription='DistrictSplittingIteration-{0}-{1}'. format(id(self), count)) candidateDistrictAPop = sum(group.population for group in candidateDistrictA) candidateDistrictBPop = sum(group.population for group in candidateDistrictB) if idealDistrictASize - populationDeviation <= candidateDistrictAPop <= idealDistrictASize + populationDeviation and \ idealDistrictBSize - populationDeviation <= candidateDistrictBPop <= idealDistrictBSize + populationDeviation: districtStillNotExactlyCut = False else: tqdm.write( ' *** Unsuccessful fill attempt. {0} off the count. ***' .format(abs(idealDistrictASize - candidateDistrictAPop))) if len(self.children) == 1: # this means that the candidate couldn't fill because there a single redistricting group # likely because there was a single county groupsToBreakUp = [(self.children[0], Alignment.all)] elif len(candidateDistrictA) == 0: # we didn't get anything in candidateA, which means none of the children met the conditions # so, we won't get anything to break up, let's break the first starting candidate with instead breakupCandidates = [ startingCandidateTuple[0] for startingCandidateTuple in self.getCutStartingCandidates() ] breakupCandidates = [ breakupCandidate for breakupCandidate in breakupCandidates if len(breakupCandidate.children) > 1 ] if len(breakupCandidates) == 0: saveDataToFileWithDescription( data=[ self, districtAStartingGroup, ratio, candidateDistrictA, candidateDistrictB, nextBestGroupForCandidateDistrictA, breakupCandidates ], censusYear='', stateName='', descriptionOfInfo= 'ErrorCase-NoGroupsCandidatesCapableOfBreaking') tqdm.write( ' *** Failed fill attempt!!! *** <------------------------------------------------------' ) tqdm.write( "Couldn't fill and all breakup candidates have too few children!!!! For {0}" .format(id(self))) return None, fillOriginDirection groupsToBreakUp = [(breakupCandidates[0], Alignment.all)] else: if len(candidateDistrictB) == 1: groupsToBreakUp = [(candidateDistrictB[0], Alignment.all)] else: if breakingMethod is BreakingMethod.splitBestCandidateGroup: groupsToBreakUp = [ (nextBest, Alignment.all) for nextBest in nextBestGroupForCandidateDistrictA ] elif breakingMethod is BreakingMethod.splitGroupsOnEdge: groupsToBreakUp = splitGroupsOnEdge( candidateDistrictA, candidateDistrictB, shouldMergeIntoFormerRedistrictingGroups, shouldRefillEachPass) elif breakingMethod is BreakingMethod.splitLowestEnergySeam: groupsToBreakUp = splitLowestEnergySeam( candidateDistrictA, candidateDistrictB, showDetailedProgress, energyRelativeToPopulation=False) elif breakingMethod is BreakingMethod.splitLowestRelativeEnergySeam: groupsToBreakUp = splitLowestEnergySeam( candidateDistrictA, candidateDistrictB, showDetailedProgress, energyRelativeToPopulation=True) else: raise RuntimeError( '{0} is not supported'.format(breakingMethod)) groupsCapableOfBreaking = [ groupToBreakUp for groupToBreakUp in groupsToBreakUp if len(groupToBreakUp[0].children) > 1 ] if len(groupsCapableOfBreaking) == 0: saveDataToFileWithDescription( data=[ self, districtAStartingGroup, ratio, candidateDistrictA, candidateDistrictB, nextBestGroupForCandidateDistrictA ], censusYear='', stateName='', descriptionOfInfo='ErrorCase-NoGroupsCapableOfBreaking' ) plotGraphObjectGroups( [self.children, districtAStartingGroup]) raise RuntimeError( "Groups to break up don't meet criteria. Groups: {0}". format([ groupToBreakUp[0].graphId for groupToBreakUp in groupsToBreakUp ])) tqdm.write( ' *** Graph splitting {0} redistricting groups ***'. format(len(groupsCapableOfBreaking))) updatedChildren = self.children.copy() newRedistrictingGroups = [] if showDetailedProgress: pbar = None else: pbar = tqdm(total=len(groupsCapableOfBreaking)) for groupToBreakUpItem in groupsCapableOfBreaking: if showDetailedProgress: countForProgress = groupsCapableOfBreaking.index( groupToBreakUpItem) + 1 else: countForProgress = None groupToBreakUp = groupToBreakUpItem[0] alignmentForSplits = groupToBreakUpItem[1] smallerRedistrictingGroups = groupToBreakUp.getGraphSplits( shouldDrawGraph=shouldDrawEachStep, alignment=alignmentForSplits, countForProgress=countForProgress) updatedChildren.extend(smallerRedistrictingGroups) updatedChildren.remove(groupToBreakUp) # assign the previous parent graphId so that we can combine the parts again after the exact split for smallerRedistrictingGroup in smallerRedistrictingGroups: if groupToBreakUp.previousParentId is None: previousParentId = groupToBreakUp.graphId else: previousParentId = groupToBreakUp.previousParentId smallerRedistrictingGroup.previousParentId = previousParentId newRedistrictingGroups.extend(smallerRedistrictingGroups) if pbar is not None: pbar.update(1) if pbar is not None: pbar.close() tqdm.write( ' *** Re-attaching new Redistricting Groups to existing Groups ***' ) assignNeighboringRedistrictingGroupsToRedistrictingGroups( changedRedistrictingGroups=newRedistrictingGroups, allNeighborCandidates=updatedChildren) validateRedistrictingGroups(updatedChildren) tqdm.write(' *** Updating District Candidate Data ***') self.children = updatedChildren # need to make sure the starting group still is in the district if districtAStartingGroup not in self.children: districtAStartingGroup = None shouldSaveThisPass = True if breakingMethod is BreakingMethod.splitLowestEnergySeam: if count % 10 != 0: shouldSaveThisPass = False if shouldSaveProgress: if shouldSaveThisPass: saveDataToFileWithDescription( data=(self, candidateDistrictA, ratio, fillOriginDirection), censusYear='', stateName='', descriptionOfInfo='DistrictSplitLastIteration-{0}'. format(id(self))) count += 1 if shouldMergeIntoFormerRedistrictingGroups: tqdm.write( ' *** Merging candidates into remaining starting groups ***' ) mergedCandidates = mergeCandidatesIntoPreviousGroups( candidates=[candidateDistrictA, candidateDistrictB]) candidateDistrictA = mergedCandidates[0] candidateDistrictB = mergedCandidates[1] tqdm.write( ' *** Re-attaching new Redistricting Groups to existing Groups ***' ) assignNeighboringRedistrictingGroupsToRedistrictingGroups( changedRedistrictingGroups=candidateDistrictA, allNeighborCandidates=candidateDistrictA) assignNeighboringRedistrictingGroupsToRedistrictingGroups( changedRedistrictingGroups=candidateDistrictB, allNeighborCandidates=candidateDistrictB) validateRedistrictingGroups(candidateDistrictA) validateRedistrictingGroups(candidateDistrictB) tqdm.write( ' *** Successful fill attempt!!! *** <------------------------------------------------------------' ) return (candidateDistrictA, candidateDistrictB), fillOriginDirection
def getLowestPopulationEnergySeam(self, alignment, shouldDrawGraph=False, finishingBlocksToAvoid=None): if alignment is Alignment.northSouth: startingCandidates = self.easternChildBlocks borderBlocksToAvoid = self.northernChildBlocks + self.southernChildBlocks + startingCandidates finishCandidates = self.westernChildBlocks else: startingCandidates = self.southernChildBlocks borderBlocksToAvoid = self.westernChildBlocks + self.easternChildBlocks + startingCandidates finishCandidates = self.northernChildBlocks if finishingBlocksToAvoid: finishCandidates = [candidate for candidate in finishCandidates if candidate not in finishingBlocksToAvoid] if len(startingCandidates) == 0 or len(finishCandidates) == 0: return None startingBlock = min(startingCandidates, key=lambda block: block.populationEnergy) startingBlockEnergy = startingBlock.populationEnergy blockToActOn = startingBlock lowestPopulationEnergySeam = [blockToActOn] failedStartingBlocks = [] avoidingAdjacentBorderBlocks = True finishedSeam = False finishingBlock = None count = 1 while not finishedSeam: if alignment is Alignment.northSouth: primaryNeighborCandidates = blockToActOn.westernNeighbors else: primaryNeighborCandidates = blockToActOn.northernNeighbors neighborCandidates = [block for block in primaryNeighborCandidates if block not in lowestPopulationEnergySeam and block not in borderBlocksToAvoid] if len(neighborCandidates) is 0: neighborCandidates = [block for block in blockToActOn.allNeighbors if block not in lowestPopulationEnergySeam and block not in borderBlocksToAvoid] # If we don't have any neighbors in the direction we're headed, # we need to find the next best block candidate if len(neighborCandidates) is 0: failedStartingBlocks.append(startingBlock) remainingStartingCandidates = [startingCandidate for startingCandidate in startingCandidates if startingCandidate not in failedStartingBlocks] # If there are no more starting candidates, remove the adjacent border blocks to avoid rule if len(remainingStartingCandidates) is 0: if avoidingAdjacentBorderBlocks: avoidingAdjacentBorderBlocks = False borderBlocksToAvoid = startingCandidates failedStartingBlocks = [] startingBlock = min(startingCandidates, key=lambda block: block.populationEnergy) blockToActOn = startingBlock lowestPopulationEnergySeam = [blockToActOn] continue else: # not sure there is anything more we can do but split everything up # plotGraphObjectGroups([self.children, failedStartingBlocks, finishingBlocksToAvoid]) tqdm.write("Can't find a {0} path through {1}. Tried and failed on {2} starting blocks" .format(alignment, self.graphId, len(failedStartingBlocks))) return None startingBlock = min(remainingStartingCandidates, key=lambda block: block.populationEnergy) blockToActOn = startingBlock lowestPopulationEnergySeam = [blockToActOn] continue lowestPopulationEnergyNeighbor = min(neighborCandidates, key=lambda block: block.populationEnergy) if shouldDrawGraph: plotGraphObjectGroups(graphObjectGroups=[self.children, lowestPopulationEnergySeam, neighborCandidates, [lowestPopulationEnergyNeighbor], failedStartingBlocks], showGraphHeatmapForFirstGroup=True, saveImages=True, saveDescription='SeamFinding{0}-{1}-{2}'.format(count, alignment, id(self))) blockToActOn = lowestPopulationEnergyNeighbor lowestPopulationEnergySeam.append(blockToActOn) if blockToActOn in finishCandidates: finishedSeam = True finishingBlock = blockToActOn count += 1 return lowestPopulationEnergySeam, finishingBlock, startingBlockEnergy
def weightedForestFireFillGraphObject(candidateObjects, startingObjects=None, condition=lambda x, y: (True, 0), weightingScore=lambda w, x, y, z: 1, shouldDrawEachStep=False, returnBestCandidateGroup=True, fastCalculations=True): bestGraphObjectCandidateGroupThisPass = None offCount = 0 candidateGroupsThatDidNotMeetConditionThisPass = [] fireFilledObjects = [] fireQueue = [] remainingObjects = candidateObjects.copy() if not startingObjects: # this doesn't occur during the forest fire fill when creating districts startingObjects = [remainingObjects[0]] fireQueue.append(startingObjects) count = 1 with tqdm() as pbar: while len(fireQueue) > 0: pbar.update(1) pbar.set_description( 'FireFilled: {0} - FireQueue: {1} - Remaining: {2} - Off count: {3}'.format( len(fireFilledObjects), len(fireQueue), len(remainingObjects), offCount)) # pull from the top of the queue graphObjectCandidateGroup = fireQueue.pop(0) # remove objects that we pulled from the queue from the remaining list remainingObjects = [object for object in remainingObjects if object not in graphObjectCandidateGroup] if shouldDrawEachStep: plotGraphObjectGroups([fireFilledObjects, graphObjectCandidateGroup, remainingObjects], showDistrictNeighborConnections=True, saveImages=True, saveDescription='WeightedForestFireFillGraphObject-{0}-{1}'.format( id(candidateObjects), count)) count += 1 potentiallyIsolatedGroups = findContiguousGroupsOfGraphObjects(remainingObjects) if len(potentiallyIsolatedGroups) <= 1: # candidate won't block any other groups conditionResult = condition(fireFilledObjects, graphObjectCandidateGroup) if conditionResult[0]: offCount = conditionResult[1] fireFilledObjects.extend(graphObjectCandidateGroup) bestGraphObjectCandidateGroupThisPass = None # set this back to none when we add something candidateGroupsThatDidNotMeetConditionThisPass = [] # clear this when we add something # find any of objects just added and remove them from the queue remainingItemsFromGroups = [] groupsToRemove = [] for queueItemGroup in fireQueue: if any([queueItem for queueItem in queueItemGroup if queueItem in graphObjectCandidateGroup]): remainingItems = [queueItem for queueItem in queueItemGroup if queueItem not in graphObjectCandidateGroup] remainingItemsFromGroups.extend(remainingItems) groupsToRemove.append(queueItemGroup) # remove duplicates from the lists remainingItemsFromGroups = set(remainingItemsFromGroups) # crazy way to remove duplicates from a list of lists groupsToRemove = [list(item) for item in set(tuple(row) for row in groupsToRemove)] for groupToRemove in groupsToRemove: fireQueue.remove(groupToRemove) neighborsOfFireFilledObjects = [group.allNeighbors for group in fireFilledObjects] for remainingItemFromGroups in remainingItemsFromGroups: if remainingItemFromGroups in neighborsOfFireFilledObjects: fireQueue.append([remainingItemFromGroups]) # add neighbors to the queue for graphObjectCandidate in graphObjectCandidateGroup: for neighborObject in graphObjectCandidate.allNeighbors: flatFireQueue = [graphObject for graphObjectGroup in fireQueue for graphObject in graphObjectGroup] if neighborObject in remainingObjects and neighborObject not in flatFireQueue: fireQueue.append([neighborObject]) # if we don't need to return the next best candidate, we can remove groups from the queue # that don't meet the condition right now to speed up processing if not returnBestCandidateGroup: fireQueue = [fireQueueGroup for fireQueueGroup in fireQueue if condition(fireFilledObjects, fireQueueGroup)[0]] else: if returnBestCandidateGroup and bestGraphObjectCandidateGroupThisPass is None: if all([len(graphObjectCandidate.children) > 1 for graphObjectCandidate in graphObjectCandidateGroup]): bestGraphObjectCandidateGroupThisPass = graphObjectCandidateGroup remainingObjects.extend(graphObjectCandidateGroup) # add candidate back to the queue candidateGroupsThatDidNotMeetConditionThisPass.append(graphObjectCandidateGroup) else: # find the contiguous group with largest population and remove. # This everything else and will be handled by subsequent fire fill passes potentiallyIsolatedGroups.sort(key=lambda x: sum(group.population for group in x), reverse=True) potentiallyIsolatedGroups.remove(potentiallyIsolatedGroups[0]) potentiallyIsolatedObjects = [group for groupList in potentiallyIsolatedGroups for group in groupList] conditionResult = condition(fireFilledObjects, potentiallyIsolatedObjects + graphObjectCandidateGroup) if conditionResult[0]: if shouldDrawEachStep: plotGraphObjectGroups( [fireFilledObjects, graphObjectCandidateGroup, remainingObjects, potentiallyIsolatedObjects], showDistrictNeighborConnections=True, saveImages=True, saveDescription='WeightedForestFireFillGraphObject-{0}-{1}'.format(id(candidateObjects), count)) count += 1 groupAndIsolatedObjects = potentiallyIsolatedObjects + graphObjectCandidateGroup if groupAndIsolatedObjects not in candidateGroupsThatDidNotMeetConditionThisPass: fireQueue.append(groupAndIsolatedObjects) else: candidateGroupsThatDidNotMeetConditionThisPass.append(graphObjectCandidateGroup) remainingObjects.extend(graphObjectCandidateGroup) # add candidate back to the queue # remove duplicates from the list fireQueue.sort() fireQueue = list(fireQueueItem for fireQueueItem, _ in groupby(fireQueue)) # apply weights for sorting weightedQueue = [] fireFilledObjectsShape = polygonFromMultipleGeometries(fireFilledObjects) for queueObjectGroup in fireQueue: weightScore = weightingScore(fireFilledObjectsShape, remainingObjects, queueObjectGroup, fastCalculations) weightedQueue.append((queueObjectGroup, weightScore)) # sort queue weightedQueue.sort(key=lambda x: x[1], reverse=True) fireQueue = [x[0] for x in weightedQueue] if shouldDrawEachStep: plotGraphObjectGroups( [fireFilledObjects, [], remainingObjects], showDistrictNeighborConnections=True, saveImages=True, saveDescription='WeightedForestFireFillGraphObject-{0}-{1}'.format(id(candidateObjects), count)) return fireFilledObjects, bestGraphObjectCandidateGroupThisPass