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 children(self, children): if len(children) != len(set(children)): saveDataToFileWithDescription(data=self, censusYear='', stateName='', descriptionOfInfo='ErrorCase-DuplicateChildren') raise RuntimeError("Children contains duplicates: {0}".format(self)) self.__children = children self.updateBlockContainerData()
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 splitLowestEnergySeam(candidateDistrictA, candidateDistrictB, showDetailedProgress, energyRelativeToPopulation): groupsBetweenCandidates = getRedistrictingGroupsBetweenCandidates( candidateDistrictA, candidateDistrictB) groupBreakUpCandidates = [ groupToBreakUp for groupToBreakUp in groupsBetweenCandidates if groupToBreakUp not in candidateDistrictA ] groupBreakUpCandidates = [ groupBreakUpCandidate for groupBreakUpCandidate in groupBreakUpCandidates if len(groupBreakUpCandidate.children) > 1 and groupBreakUpCandidate.population > 0 ] seamsToEvaluate = [] for groupBreakUpCandidate in groupBreakUpCandidates: westernAndEasternNeighbors = groupBreakUpCandidate.westernNeighbors + groupBreakUpCandidate.easternNeighbors if any([ neighbor for neighbor in westernAndEasternNeighbors if neighbor in candidateDistrictA ]): seamsToEvaluate.append((groupBreakUpCandidate, Alignment.westEast)) northernAndSouthernNeighbors = groupBreakUpCandidate.northernNeighbors + groupBreakUpCandidate.southernNeighbors if any([ neighbor for neighbor in northernAndSouthernNeighbors if neighbor in candidateDistrictA ]): seamsToEvaluate.append( (groupBreakUpCandidate, Alignment.northSouth)) tqdm.write( ' *** Finding lowest energy seam out of {0} seams ***'.format( len(seamsToEvaluate))) if showDetailedProgress: pbar = None else: pbar = tqdm(total=len(seamsToEvaluate)) energyScores = [] backupEnergyScores = [] for seamToEvaluate in seamsToEvaluate: groupToEvaluate = seamToEvaluate[0] alignmentForEvaluation = seamToEvaluate[1] groupToEvaluate.fillPopulationEnergyGraph(alignmentForEvaluation) splitResult = groupToEvaluate.getPopulationEnergyPolygonSplit( alignmentForEvaluation) groupToEvaluate.clearPopulationEnergyGraph() polygonSplitResultType = splitResult[0] if polygonSplitResultType is SplitType.NoSplit: # if we can't split in this direction, we need to check the other direction # and if that direction can't be split, we'll call for a force split if alignmentForEvaluation is Alignment.northSouth: oppositeAlignment = Alignment.westEast else: oppositeAlignment = Alignment.northSouth groupToEvaluate.fillPopulationEnergyGraph(oppositeAlignment) oppositeSplitResult = groupToEvaluate.getPopulationEnergyPolygonSplit( oppositeAlignment) groupToEvaluate.clearPopulationEnergyGraph() oppositePolygonSplitResultType = oppositeSplitResult[0] if oppositePolygonSplitResultType is SplitType.NoSplit: seamEnergy = groupToEvaluate.population alignmentForEvaluation = Alignment.all energyScores.append((groupToEvaluate, alignmentForEvaluation, seamEnergy, polygonSplitResultType)) if len(groupToEvaluate.children) >= 10: tqdm.write( " *** Warning: Couldn't find a split for {0}. Candidate for Force Splitting. {1} blocks. {2} total pop." .format(groupToEvaluate.graphId, len(groupToEvaluate.children), groupToEvaluate.population)) saveDataToFileWithDescription( data=groupToEvaluate, censusYear='', stateName='', descriptionOfInfo= 'WarningCase-ForceSplittingWithOver10Children-{0}'. format(id(groupToEvaluate))) else: if oppositePolygonSplitResultType is SplitType.ForceSplitAllBlocks: # will need to remove any other seams in list if we ever take # more than the first seam in the sorted list below seamEnergy = groupToEvaluate.population alignmentForEvaluation = Alignment.all else: seamEnergy = oppositeSplitResult[3] alignmentForEvaluation = oppositeAlignment backupEnergyScores.append( (groupToEvaluate, alignmentForEvaluation, seamEnergy, oppositePolygonSplitResultType)) else: if polygonSplitResultType is SplitType.ForceSplitAllBlocks: # will need to remove any other seams in list if we ever take # more than the first seam in the sorted list below seamEnergy = groupToEvaluate.population alignmentForEvaluation = Alignment.all else: seamEnergy = splitResult[3] energyScores.append((groupToEvaluate, alignmentForEvaluation, seamEnergy, polygonSplitResultType)) if pbar is not None: pbar.update(1) if pbar is not None: pbar.close() if len(energyScores) == 0: tqdm.write( " *** Warning: Did not find any energy scores in this list: {0}" .format([group.graphId for group in groupBreakUpCandidates])) tqdm.write(" Switching to backup scores: {0} ***".format( backupEnergyScores)) energyScores = backupEnergyScores if energyRelativeToPopulation: relativeEnergyScores = [] for energyScore in energyScores: scoreGroup = energyScore[0] scoreAlignment = energyScore[1] currentScore = energyScore[2] scoreSplitResultType = energyScore[3] newScore = currentScore / scoreGroup.population relativeEnergyScores.append( (scoreGroup, scoreAlignment, newScore, scoreSplitResultType)) energyScores = relativeEnergyScores energyScores.sort(key=lambda x: x[2]) minimumEnergySeam = energyScores[0] groupToBreakUp = minimumEnergySeam[0] groupToBreakUpSeamAlignment = minimumEnergySeam[1] groupsToBreakUp = [(groupToBreakUp, groupToBreakUpSeamAlignment)] return groupsToBreakUp
def splitDistrict(self, numberOfDistricts, populationDeviation, weightingMethod, breakingMethod, totalSplitCount=None, shouldMergeIntoFormerRedistrictingGroups=False, shouldRefillEachPass=False, shouldDrawFillAttempts=False, shouldDrawEachStep=False, fastCalculations=True, showDetailedProgress=False, shouldSaveProgress=True): if totalSplitCount is None: tqdm.write('*** Splitting into {0} districts ***'.format( numberOfDistricts)) totalSplitCount = 0 districts = [] if numberOfDistricts == 1: return [self] aRatio = math.floor(numberOfDistricts / 2) bRatio = math.ceil(numberOfDistricts / 2) ratio = (aRatio, bRatio) districtSplitScores = [] thisSplitCount = 0 originalBreakingMethod = breakingMethod fillOriginDirection = None doneFindingSplits = False while not doneFindingSplits: tqdm.write(' *** Split starting. Using {0} and {1} ***'.format( weightingMethod, breakingMethod)) cutDistrictInfo = self.cutDistrictIntoExactRatio( ratio=ratio, populationDeviation=populationDeviation, weightingMethod=weightingMethod, breakingMethod=breakingMethod, fillOriginDirection=fillOriginDirection, shouldDrawFillAttempts=shouldDrawFillAttempts, shouldDrawEachStep=shouldDrawEachStep, shouldMergeIntoFormerRedistrictingGroups= shouldMergeIntoFormerRedistrictingGroups, shouldRefillEachPass=shouldRefillEachPass, fastCalculations=fastCalculations, showDetailedProgress=showDetailedProgress, shouldSaveProgress=shouldSaveProgress) cutDistrict = cutDistrictInfo[0] fillOriginDirection = cutDistrictInfo[1] thisSplitCount += 1 if cutDistrict is None: tqdm.write( ' *** Failed to cut district. Attempt #:{0} for {1} ***'. format(thisSplitCount, id(self))) districtSplitScores.append((None, 0, fillOriginDirection, 0)) else: tqdm.write( ' *** Cut district into exact ratio. Attempt #:{0} for {1} ***' .format(thisSplitCount, id(self))) aDistrictCandidate = District(childrenGroups=cutDistrict[0]) bDistrictCandidate = District(childrenGroups=cutDistrict[1]) isAGood = isPolygonAGoodDistrictShape( districtPolygon=aDistrictCandidate.geometry, parentPolygon=self.geometry) isBGood = isPolygonAGoodDistrictShape( districtPolygon=bDistrictCandidate.geometry, parentPolygon=self.geometry) aPolsbyPopperScore = polsbyPopperScoreOfPolygon( aDistrictCandidate.geometry) bPolsbyPopperScore = polsbyPopperScoreOfPolygon( bDistrictCandidate.geometry) minimumPolsbyPopperScore = min(aPolsbyPopperScore, bPolsbyPopperScore) splitScore = 0 if isAGood: splitScore += 1 if isBGood: splitScore += 1 saveDescription = 'SplitCandidate-{0}-{1}-{2}-'.format( id(self), fillOriginDirection, breakingMethod) saveDataToFileWithDescription( data=[aDistrictCandidate, bDistrictCandidate], censusYear='', stateName='', descriptionOfInfo=saveDescription) saveGeometry = (saveDescription, aDistrictCandidate.geometry, bDistrictCandidate.geometry, self.geometry) aDistrictCandidate = None bDistrictCandidate = None gc.collect() districtSplitScores.append({ 'saveDescription': saveDescription, 'splitScore': splitScore, 'fillOriginDirection': fillOriginDirection, 'minimumPolsbyPopperScore': minimumPolsbyPopperScore, 'breakingMethod': breakingMethod, 'geometryInfo': saveGeometry }) if splitScore is 2: doneFindingSplits = True else: tqdm.write( ' *** One or more split candidates is not a good shape! Trying again. ***' ) if shouldMergeIntoFormerRedistrictingGroups: tqdm.write( ' *** Merging district into starting groups ***' ) redistrictingGroupsInDistrict = cutDistrict[ 0] + cutDistrict[1] mergedRedistrictingGroups = mergeCandidatesIntoPreviousGroups( candidates=[redistrictingGroupsInDistrict])[0] tqdm.write( ' *** Re-attaching new Redistricting Groups to existing Groups ***' ) assignNeighboringRedistrictingGroupsToRedistrictingGroups( changedRedistrictingGroups= mergedRedistrictingGroups, allNeighborCandidates=mergedRedistrictingGroups) validateRedistrictingGroups(mergedRedistrictingGroups) self.children = mergedRedistrictingGroups splitScoresWithCurrentBreakingMethod = [ districtSplitScore for districtSplitScore in districtSplitScores if districtSplitScore['breakingMethod'] is breakingMethod ] fillOriginDirection = getOppositeDirection(fillOriginDirection) directionsTried = [ districtSplitScore['fillOriginDirection'] for districtSplitScore in splitScoresWithCurrentBreakingMethod ] if fillOriginDirection in directionsTried: fillOriginDirection = getCWDirection(fillOriginDirection) if fillOriginDirection in directionsTried: fillOriginDirection = getOppositeDirection( fillOriginDirection) if fillOriginDirection in directionsTried: if breakingMethod is BreakingMethod.splitLowestRelativeEnergySeam or breakingMethod is BreakingMethod.splitLowestEnergySeam: breakingMethod = BreakingMethod.splitGroupsOnEdge else: doneFindingSplits = True districtSplitScores.sort(key=lambda x: x['minimumPolsbyPopperScore'], reverse=True) saveDataToFileWithDescription( data=districtSplitScores, censusYear='', stateName='', descriptionOfInfo='DistrictSplitScores-{0}'.format(id(self))) bestDistrictSplitInfo = districtSplitScores[0] bestDistrictSaveDescription = bestDistrictSplitInfo['saveDescription'] if bestDistrictSaveDescription is None: plotDistrict(self, showDistrictNeighborConnections=True) raise RuntimeError( "Could not find a good split for {0} from list: {1}".format( id(self), districtSplitScores)) bestDistrictSplit = loadDataFromFileWithDescription( censusYear='', stateName='', descriptionOfInfo=bestDistrictSaveDescription) aDistrict = bestDistrictSplit[0] bDistrict = bestDistrictSplit[1] bestSplitScore = bestDistrictSplitInfo['splitScore'] bestFillDirection = bestDistrictSplitInfo['fillOriginDirection'] bestPolsbyPopperScore = bestDistrictSplitInfo[ 'minimumPolsbyPopperScore'] bestBreakingMethod = bestDistrictSplitInfo['breakingMethod'] tqdm.write( ' *** Chose a district split! Ratio: {0}***'.format(ratio)) tqdm.write( ' *** Direction: {0} Split score: {1} Polsby-Popper score: {2} Breaking method: {3} ***' .format(bestFillDirection, bestSplitScore, bestPolsbyPopperScore, bestBreakingMethod)) totalSplitCount += 1 aDistrictSplits = aDistrict.splitDistrict( numberOfDistricts=aRatio, populationDeviation=populationDeviation, weightingMethod=weightingMethod, breakingMethod=originalBreakingMethod, totalSplitCount=totalSplitCount, shouldMergeIntoFormerRedistrictingGroups= shouldMergeIntoFormerRedistrictingGroups, shouldRefillEachPass=shouldRefillEachPass, shouldDrawFillAttempts=shouldDrawFillAttempts, shouldDrawEachStep=shouldDrawEachStep, fastCalculations=fastCalculations, showDetailedProgress=showDetailedProgress, shouldSaveProgress=shouldSaveProgress) districts.extend(aDistrictSplits) bDistrictSplits = bDistrict.splitDistrict( numberOfDistricts=bRatio, populationDeviation=populationDeviation, weightingMethod=weightingMethod, breakingMethod=originalBreakingMethod, totalSplitCount=totalSplitCount, shouldMergeIntoFormerRedistrictingGroups= shouldMergeIntoFormerRedistrictingGroups, shouldRefillEachPass=shouldRefillEachPass, shouldDrawFillAttempts=shouldDrawFillAttempts, shouldDrawEachStep=shouldDrawEachStep, fastCalculations=fastCalculations, showDetailedProgress=showDetailedProgress, shouldSaveProgress=shouldSaveProgress) districts.extend(bDistrictSplits) return districts
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 getPopulationEnergyPolygonSplit(self, alignment, shouldDrawGraph=False): finishingBlocksToAvoid = [] while True: lowestEnergySeamResult = self.getLowestPopulationEnergySeam(alignment=alignment, finishingBlocksToAvoid=finishingBlocksToAvoid) if lowestEnergySeamResult is None: return SplitType.NoSplit, None lowestEnergySeam = lowestEnergySeamResult[0] energySeamFinishingBlock = lowestEnergySeamResult[1] energySeamStartingEnergy = lowestEnergySeamResult[2] seamSplitPolygon = polygonFromMultipleGeometries(geometryList=lowestEnergySeam) polygonWithoutSeam = self.geometry.difference(seamSplitPolygon) # if the polygon without the seam is empty, that means we have a small enough redistricting group where # we need to break it up completely. Because our seams can no longer break up any further. if polygonWithoutSeam.is_empty: return SplitType.ForceSplitAllBlocks, None if type(polygonWithoutSeam) is MultiPolygon: seamOnEdge = False splitPolygons = list(polygonWithoutSeam) else: seamOnEdge = True splitPolygons = [polygonWithoutSeam, seamSplitPolygon] if alignment is Alignment.northSouth: aSplitRepresentativeBlockDirection = CardinalDirection.north bSplitRepresentativeBlockDirection = CardinalDirection.south else: aSplitRepresentativeBlockDirection = CardinalDirection.west bSplitRepresentativeBlockDirection = CardinalDirection.east # Identify which polygon is in which direction # Note: Need to make sure we don't select a block in the seam so we supply a list without those blocks # If the seam is completely on the edge though, let's include the seam if seamOnEdge: borderChildrenRepresentativeCandidates = self.borderChildren else: borderChildrenRepresentativeCandidates = [child for child in self.borderChildren if child not in lowestEnergySeam] if len(borderChildrenRepresentativeCandidates) == 0: return SplitType.NoSplit, None aSplitRepresentativeBlock = mostCardinalOfGeometries(geometryList=borderChildrenRepresentativeCandidates, direction=aSplitRepresentativeBlockDirection) bSplitRepresentativeBlock = mostCardinalOfGeometries(geometryList=borderChildrenRepresentativeCandidates, direction=bSplitRepresentativeBlockDirection) aSplitPolygon = getPolygonThatContainsGeometry(polygonList=splitPolygons, targetGeometry=aSplitRepresentativeBlock, useTargetRepresentativePoint=True) bSplitPolygon = getPolygonThatContainsGeometry(polygonList=splitPolygons, targetGeometry=bSplitRepresentativeBlock, useTargetRepresentativePoint=True) leftOverPolygons = [geometry for geometry in splitPolygons if geometry is not aSplitPolygon and geometry is not bSplitPolygon] if aSplitPolygon is None or bSplitPolygon is None: plotPolygons(splitPolygons + [aSplitRepresentativeBlock.geometry, bSplitRepresentativeBlock.geometry]) saveDataToFileWithDescription(data=self, censusYear='', stateName='', descriptionOfInfo='ErrorCase-AorBSplitIsNone') raise RuntimeError('Split a or b not found') if aSplitPolygon is bSplitPolygon: finishingBlocksToAvoid.append(energySeamFinishingBlock) continue if len(leftOverPolygons) is not len(splitPolygons) - 2: saveDataToFileWithDescription(data=self, censusYear='', stateName='', descriptionOfInfo='ErrorCase-MissingPolygons') raise RuntimeError('Missing some polygons for mapping. Split polygons: {0} Left over polygon: {1}' .format(len(splitPolygons), len(leftOverPolygons))) polygonSplits = (aSplitPolygon, bSplitPolygon) if shouldDrawGraph: plotPolygons(polygonSplits) if seamOnEdge: return SplitType.SplitIncludedInSeam, polygonSplits, None, energySeamStartingEnergy else: seamSplitPolygon = polygonFromMultiplePolygons(polygonList=[seamSplitPolygon] + leftOverPolygons) return SplitType.NormalSplit, polygonSplits, seamSplitPolygon, energySeamStartingEnergy
districtGeometries = EsriDumper( url='https://tigerweb.geo.census.gov/arcgis/rest/services/Generalized_ACS2017/Legislative/MapServer/5', extra_query_args={'where': 'STATE=\'{0}\''.format(stateFIPSCode)}) # https://github.com/openaddresses/pyesridump existingDistricts = [] for districtGeometry in districtGeometries: geoJSONGeometry = districtGeometry['geometry'] districtNumber = districtGeometry['properties']['BASENAME'] existingDistrict = ExistingDistrict(districtNumber=districtNumber, geoJSONGeometry=geoJSONGeometry) existingDistricts.append(existingDistrict) return existingDistricts stateAbbreviation = 'MI' stateInfo = states.lookup(stateAbbreviation) censusYear = 2010 descriptionToWorkWith = 'All' allCongressionalDistrictGeosInState = getAllGeoDataForFederalCongressionalDistricts(stateFIPSCode=stateInfo.fips) # save county data to file saveDataToFileWithDescription(data=allCongressionalDistrictGeosInState, censusYear=censusYear, stateName=stateInfo.name, descriptionOfInfo='{0}CurrentFederalCongressionalDistricts'.format(descriptionToWorkWith)) saveGeoJSONToDirectoryWithDescription(geographyList=allCongressionalDistrictGeosInState, censusYear=censusYear, stateName=stateInfo.name, descriptionOfInfo='CurrentFederalCongressionalDistricts')
stateAbbreviation = 'MI' stateInfo = states.lookup(stateAbbreviation) censusYear = 2010 descriptionToWorkWith = 'All' censusData = loadDataFromFileWithDescription( censusYear=censusYear, stateName=stateInfo.name, descriptionOfInfo='{0}Block'.format(descriptionToWorkWith)) redistrictingGroupList = createRedistrictingGroupsWithAtomicBlocksFromCensusData( censusData=censusData) # exportGeographiesToShapefile(geographyList=AtomicBlock.atomicBlockList, descriptionOfInfo='AtomicGroups') saveDataToFileWithDescription( data=redistrictingGroupList, censusYear=censusYear, stateName=stateInfo.name, descriptionOfInfo='{0}RedistrictingGroupPreGraph'.format( descriptionToWorkWith)) redistrictingGroupList = prepareBlockGraphsForRedistrictingGroups( redistrictingGroupList) saveDataToFileWithDescription( data=redistrictingGroupList, censusYear=censusYear, stateName=stateInfo.name, descriptionOfInfo='{0}RedistrictingGroupBlockGraphsPrepared'.format( descriptionToWorkWith)) redistrictingGroupList = prepareGraphsForRedistrictingGroups( redistrictingGroupList) saveDataToFileWithDescription(
else: return None stateAbbreviation = 'MI' stateInfo = states.lookup(stateAbbreviation) censusYear = 2010 descriptionToWorkWith = 'All' censusRequest = Census(apiKeys.censusAPIKey, year=censusYear) countyInfoList = getCountiesInState(stateFIPSCode=stateInfo.fips, maxNumberOfCounties=math.inf) allCountyGeosInState = allGeoDataForEachCounty( existingCountyData=countyInfoList) # save county data to file saveDataToFileWithDescription( data=allCountyGeosInState, censusYear=censusYear, stateName=stateInfo.name, descriptionOfInfo='{0}County'.format(descriptionToWorkWith)) allBlocksInState = getAllBlocksInState(countyList=countyInfoList) allBlockGeosInState = allGeoDataForEachBlock( countyInfoList=countyInfoList, existingBlockData=allBlocksInState) # save block data to file saveDataToFileWithDescription( data=allBlockGeosInState, censusYear=censusYear, stateName=stateInfo.name, descriptionOfInfo='{0}Block'.format(descriptionToWorkWith))
descriptionToWorkWith)) initialDistrict = createDistrictFromRedistrictingGroups(redistrictingGroups=redistrictingGroups) populationDeviation = populationDeviationFromPercent(overallPercentage=overallPercentageOffIdealAllowed, numberOfDistricts=numberOfDistricts, totalPopulation=initialDistrict.population) districts = initialDistrict.splitDistrict(numberOfDistricts=numberOfDistricts, populationDeviation=populationDeviation, weightingMethod=WeightingMethod.cardinalDistance, breakingMethod=BreakingMethod.splitGroupsOnEdge, shouldMergeIntoFormerRedistrictingGroups=True, shouldDrawEachStep=False, shouldRefillEachPass=True, fastCalculations=False, showDetailedProgress=False) saveDataToFileWithDescription(data=districts, censusYear=censusYear, stateName=stateInfo, descriptionOfInfo='{0}-FederalDistricts'.format(descriptionToWorkWith)) saveGeoJSONToDirectoryWithDescription(geographyList=districts, censusYear=censusYear, stateName=stateInfo, descriptionOfInfo='FederalDistrictsGeoJSON') plotDistricts(districts=districts, showPopulationCounts=False, showDistrictNeighborConnections=False) districtPolygons = [district.geometry for district in districts] plotPolygons(districtPolygons)