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 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 getGraphSplits(self, alignment=Alignment.all, shouldDrawGraph=False, countForProgress=None):
        if len(self.children) == 1:
            raise RuntimeError("Can't split RedistrictingGroup with a single child. GraphId: {0}".format(self.graphId))

        if countForProgress is None:
            pbar = None
        else:
            tqdm.write('         *** Finding seams for graph split {0} - GraphId: {1} - Block count: {2} ***'
                       .format(countForProgress, self.graphId, len(self.children)))
            if alignment is Alignment.all:
                progressTotal = 4
            else:
                progressTotal = 2
            pbar = tqdm(total=progressTotal)

        northSouthSplit = None
        westEastSplit = None

        if alignment is Alignment.all or alignment is Alignment.northSouth:
            self.fillPopulationEnergyGraph(Alignment.northSouth)
            if pbar is not None:
                pbar.update(1)
            northSouthSplitResult = self.getPopulationEnergySplit(Alignment.northSouth, shouldDrawGraph=shouldDrawGraph)
            northSouthSplitResultType = northSouthSplitResult[0]
            if northSouthSplitResultType is SplitType.NoSplit:
                northSouthSplit = None
            elif northSouthSplitResultType is SplitType.ForceSplitAllBlocks:
                return self.createRedistrictingGroupForEachChild()
            else:
                northSouthSplit = northSouthSplitResult[1]
            if pbar is not None:
                pbar.update(1)
            self.clearPopulationEnergyGraph()

        if alignment is Alignment.all or alignment is Alignment.westEast:
            self.fillPopulationEnergyGraph(Alignment.westEast)
            if pbar is not None:
                pbar.update(1)
            westEastSplitResult = self.getPopulationEnergySplit(Alignment.westEast, shouldDrawGraph=shouldDrawGraph)
            westEastSplitResultType = westEastSplitResult[0]
            if westEastSplitResultType is SplitType.NoSplit:
                westEastSplit = None
            elif westEastSplitResultType is SplitType.ForceSplitAllBlocks:
                return self.createRedistrictingGroupForEachChild()
            else:
                westEastSplit = westEastSplitResult[1]
            if pbar is not None:
                pbar.update(1)
            self.clearPopulationEnergyGraph()

        if pbar is not None:
            pbar.close()

        if northSouthSplit is None and westEastSplit is None:
            return self.createRedistrictingGroupForEachChild()

        if countForProgress is not None:
            tqdm.write('            *** Creating new Redistricting Groups in {0} ***'.format(countForProgress))

        splitGroups = []
        if northSouthSplit and not westEastSplit:
            northSplit = RedistrictingGroup(childrenBlocks=northSouthSplit[0])
            splitGroups.append(northSplit)

            southSplit = RedistrictingGroup(childrenBlocks=northSouthSplit[1])
            splitGroups.append(southSplit)
        elif not northSouthSplit and westEastSplit:
            westSplit = RedistrictingGroup(childrenBlocks=westEastSplit[0])
            splitGroups.append(westSplit)

            eastSplit = RedistrictingGroup(childrenBlocks=westEastSplit[1])
            splitGroups.append(eastSplit)
        else:
            northWestSplitChildren = [group for group in northSouthSplit[0] if group in westEastSplit[0]]
            northEastSplitChildren = [group for group in northSouthSplit[0] if group in westEastSplit[1]]
            southWestSplitChildren = [group for group in northSouthSplit[1] if group in westEastSplit[0]]
            southEastSplitChildren = [group for group in northSouthSplit[1] if group in westEastSplit[1]]

            splitChildrenList = [northWestSplitChildren,
                                 northEastSplitChildren,
                                 southWestSplitChildren,
                                 southEastSplitChildren]

            for splitChildren in splitChildrenList:
                if len(splitChildren) > 0:
                    splitGroup = RedistrictingGroup(childrenBlocks=splitChildren)
                    splitGroups.append(splitGroup)

        if countForProgress is not None:
            tqdm.write(
                '            *** Re-assigning neighboring blocks to new Redistricting Groups in {0} ***'.format(
                    countForProgress))

        # check for orphan blocks and validate existing neighboring blocks and attach to intersecting group
        splitGroups = reorganizeAtomicBlockBetweenRedistrictingGroups(redistrictingGroups=splitGroups)

        for splitGroup in splitGroups:
            if shouldDrawGraph:
                plotBlocksForRedistrictingGroup(splitGroup)
            splitGroup.removeOutdatedNeighborConnections()
            splitGroup.validateBlockNeighbors()

        if shouldDrawGraph:
            plotRedistrictingGroups(redistrictingGroups=splitGroups)

        return splitGroups