packages feed

moonlight-triangulation-1.4.0.4: src-public/Moonlight/Triangulation/PowerDiagram.hs

{-# LANGUAGE BangPatterns #-}
{-# LANGUAGE DeriveAnyClass #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE DerivingStrategies #-}

-- | Exact regular topology and power cells. Labelled cell dispositions are
-- authoritative; regular topology and planar layers are derived views.
module Moonlight.Triangulation.PowerDiagram
  ( PowerWeight
  , PowerWeightError (..)
  , powerWeight
  , powerWeightExact
  , PowerSite
  , powerSite
  , powerSiteLabel
  , powerSitePosition
  , powerSiteWeight
  , PowerCellDisposition (..)
  , RegularSiteDisposition (..)
  , RegularFace
  , regularFaceLabels
  , regularFaceDualPoint
  , PowerDualEdge (..)
  , RegularEdge
  , regularEdgeLabels
  , regularEdgeDual
  , RegularTriangulation
  , regularTriangulation
  , regularSiteDisposition
  , regularFaces
  , regularEdges
  , regularNeighbours
  , RegularTriangulationReceipt (..)
  , BoundedPowerDiagram
  , boundedPowerDiagram
  , powerCellDisposition
  , powerCellDispositions
  , powerDiagramPlanarLayer
  , PowerDiagramError (..)
  , RegularTopologyError (..)
  , PowerDiagramReceipt (..)
  , powerDiagramInputSites
  , powerDiagramPeakIntermediateBitGrowth
  , powerDiagramFinalCoordinateBitGrowth
  , AffineForm (..)
  , UpperEnvelopeError (..)
  , upperEnvelope
  ) where

import Control.DeepSeq (NFData)
import Data.Bifunctor (first)
import qualified Data.Foldable as Foldable
import qualified Data.List as List
import Data.List.NonEmpty (NonEmpty (..))
import qualified Data.List.NonEmpty as NonEmpty
import qualified Data.Map.Strict as Map
import Data.Map.Strict (Map)
import Data.Ord (comparing)
import Data.Set (Set)
import qualified Data.Set as Set
import qualified Data.Vector as Vector
import GHC.Generics (Generic)
import Moonlight.Triangulation.Exact
  ( ExactClipDisposition (..)
  , ExactClipError
  , ExactClipReceipt (..)
  , ExactAffineLine
  , ExactClosedHalfPlane
  , ExactHalfPlaneError
  , ExactPoint
  , ExactRay
  , ExactRetainedPolygon
  , ExactSegment
  , exactClipRetainedPolygon
  , exactClosedHalfPlane
  , exactPointCoordinates
  , exactPointBitWidth
  , exactPointFromQueryPoint
  , exactRetainedPolygon
  , oppositeExactAffineLine
  )
import Moonlight.Triangulation.Internal.ExactRational
  ( ExactRational
  , exactRationalBitWidth
  , exactRationalFromFiniteDouble
  )
import Moonlight.Triangulation.Internal.BoundaryCycle
  ( consecutivePairs )
import Moonlight.Triangulation.Internal.RegularTriangulation
  ( DistinctSlopeGenerators (..)
  , ExactPowerGenerator (..)
  , GeneratorDualGeometry (..)
  , GeneratorRegularEdge (..)
  , GeneratorRegularFace (..)
  , GeneratorRegularReceipt (..)
  , GeneratorRegularTopology (..)
  , RegularGeneratorDisposition (..)
  , RegularTopologyError (..)
  , exactGeneratorAxis
  , regularGeneratorTopology
  )
import Moonlight.Triangulation.Internal.Minkowski.Convex
  ( convexHullPolygon
  , convexPolygonComponent
  , convexPolygonFromRetained
  )
import Moonlight.Triangulation.Internal.Overlay.Types
  ( OverlayCell (..)
  , OverlayCellGeometry (..)
  , OverlayError
  , OverlayResult (..)
  )
import Moonlight.Triangulation.Internal.Region.Publication
  ( planarLayerFromAdmittedComponents
  )
import Moonlight.Triangulation.Math (mkQueryPoint)
import Moonlight.Triangulation.Minkowski
  ( ConvexPolygon
  , MinkowskiError
  , convexPolygonPoints
  )
import Moonlight.Triangulation.Overlay
  ( overlayLayers )
import Moonlight.Triangulation.Region
  ( PlanarLayer
  , PolygonComponent
  , exactLoopPoints
  , polygonOuterLoop
  )
import Moonlight.Triangulation.Types
  ( NonFiniteValue
  , Point
  , PointValidationError
  , QueryPoint
  , classifyNonFinite
  , queryPointValue
  )

-- | An admitted signed additive power offset.  Power distance is
-- @||x-p||^2-w@, so negative values are lawful and this is deliberately not a
-- squared-radius refinement.
newtype PowerWeight = PowerWeight ExactRational
  deriving stock (Eq, Ord, Show, Generic)
  deriving anyclass (NFData)

-- | The sole obstruction to admitting a signed binary64 power offset.
data PowerWeightError
  = PowerWeightNonFinite !NonFiniteValue
  deriving stock (Eq, Ord, Show, Generic)
  deriving anyclass (NFData)

-- | Admit a finite binary64 power offset exactly.
powerWeight :: Double -> Either PowerWeightError PowerWeight
powerWeight value =
  case classifyNonFinite value of
    Just obstruction -> Left (PowerWeightNonFinite obstruction)
    Nothing -> Right (PowerWeight (exactRationalFromFiniteDouble value))

-- | Exact rational value of an admitted power offset.
powerWeightExact :: PowerWeight -> ExactRational
powerWeightExact (PowerWeight value) = value

-- | One labelled, admitted weighted site.  Construction validates and
-- canonicalizes the binary64 position once.
data PowerSite label = PowerSite
  { powerSiteLabel :: !label
  , powerSiteQueryPoint :: !QueryPoint
  , powerSiteWeight :: !PowerWeight
  }
  deriving stock (Eq, Ord, Show, Generic)
  deriving anyclass (NFData)

powerSite
  :: label
  -> Point
  -> PowerWeight
  -> Either (PowerDiagramError label) (PowerSite label)
powerSite label point weight =
  PowerSite label <$> first (PowerSitePositionInvalid label) (mkQueryPoint point) <*> pure weight

powerSitePosition :: PowerSite label -> Point
powerSitePosition = queryPointValue . powerSiteQueryPoint

-- | Exactly one authoritative result for each submitted label.
data PowerCellDisposition label
  = PublishedPowerCell !ConvexPolygon
  | LowerDimensionalPowerCell !(NonEmpty ExactPoint)
  | EmptyPowerCell
  | CoincidentEquivalentTo !label
  | CoincidentDominatedBy !label
  deriving stock (Eq, Ord, Show, Generic)
  deriving anyclass (NFData)

-- | Visibility of one submitted label in the exact regular subdivision.
data RegularSiteDisposition label
  = RegularSiteVisible
  | RegularSiteLowerDimensional
  | RegularSiteHidden
  | RegularSiteCoincidentEquivalentTo !label
  | RegularSiteCoincidentDominatedBy !label
  deriving stock (Eq, Ord, Show, Generic)
  deriving anyclass (NFData)

-- | One oriented regular face and its exact weighted-dual vertex.
data RegularFace label = RegularFace !label !label !label !ExactPoint
  deriving stock (Eq, Ord, Show, Generic)
  deriving anyclass (NFData)

regularFaceLabels :: RegularFace label -> (label, label, label)
regularFaceLabels (RegularFace firstLabel secondLabel thirdLabel _) =
  (firstLabel, secondLabel, thirdLabel)

regularFaceDualPoint :: RegularFace label -> ExactPoint
regularFaceDualPoint (RegularFace _ _ _ dualPoint) = dualPoint

-- | Exact weighted Voronoi geometry dual to one regular edge.
data PowerDualEdge
  = BoundedPowerDual !ExactSegment
  | UnboundedPowerDual !ExactRay
  | FullLinePowerDual !ExactAffineLine
  | CollapsedPowerDual !ExactPoint
  deriving stock (Eq, Ord, Show, Generic)
  deriving anyclass (NFData)

-- | One unordered regular edge and its exact dual geometry.
data RegularEdge label = RegularEdge !label !label !PowerDualEdge
  deriving stock (Eq, Ord, Show, Generic)
  deriving anyclass (NFData)

regularEdgeLabels :: RegularEdge label -> (label, label)
regularEdgeLabels (RegularEdge firstLabel secondLabel _) =
  (firstLabel, secondLabel)

regularEdgeDual :: RegularEdge label -> PowerDualEdge
regularEdgeDual (RegularEdge _ _ dual) = dual

-- | Exact regular subdivision, opaque so incidence and visibility cannot
-- disagree.
data RegularTriangulation label = RegularTriangulation
  { storedRegularDispositions :: !(Map label (RegularSiteDisposition label))
  , storedRegularFaces :: ![RegularFace label]
  , storedRegularEdges :: ![RegularEdge label]
  , storedRegularNeighbours :: !(Map label (Set label))
  }
  deriving stock (Eq, Show, Generic)
  deriving anyclass (NFData)

data RegularTriangulationReceipt = RegularTriangulationReceipt
  { regularTriangulationInputSites :: !Int
  , regularTriangulationRepresentativeSites :: !Int
  , regularTriangulationVisibleSites :: !Int
  , regularTriangulationLowerDimensionalSites :: !Int
  , regularTriangulationHiddenSites :: !Int
  , regularTriangulationCoincidentSites :: !Int
  , regularTriangulationFaces :: !Int
  , regularTriangulationEdges :: !Int
  , regularTriangulationPeakHullFaces :: !Int
  }
  deriving stock (Eq, Ord, Show, Generic)
  deriving anyclass (NFData)

-- | Total labelled result, opaque so callers cannot omit a submitted label.
newtype BoundedPowerDiagram label =
  BoundedPowerDiagram (Map label (PowerCellDisposition label))
  deriving stock (Eq, Show, Generic)
  deriving anyclass (NFData)

data PowerDiagramError label
  = PowerSitePositionInvalid !label !PointValidationError
  | DuplicatePowerSiteLabel !label
  | PowerDomainInvalid !ExactHalfPlaneError
  | PowerBisectorInvalid !label !label !ExactHalfPlaneError
  | PowerRegularTopologyFailed !(RegularTopologyError label)
  | PowerCellClipFailed !label !ExactClipError
  | PowerDiagramOutsideLabelCollides !label
  deriving stock (Eq, Show, Generic)
  deriving anyclass (NFData)

data PowerDiagramReceipt = PowerDiagramReceipt
  { powerDiagramDomainVertices :: !Int
  , powerDiagramSubmittedSiteConstraints :: !Int
  , powerDiagramActiveBoundaries :: !Int
  , powerDiagramBoundaryCompatibilityChecks :: !Int
  , powerDiagramExactIntersections :: !Int
  , powerDiagramPublishedCells :: !Int
  , powerDiagramLowerDimensionalCells :: !Int
  , powerDiagramEmptyCells :: !Int
  , powerDiagramCoincidentEquivalentCells :: !Int
  , powerDiagramCoincidentDominatedCells :: !Int
  , powerDiagramRegularFaces :: !Int
  , powerDiagramRegularEdges :: !Int
  , powerDiagramOracleCells :: !Int
  , powerDiagramMaximumCellConstraints :: !Int
  , powerDiagramMaximumInputBits :: !Int
  , powerDiagramMaximumAffineCoefficientBits :: !Int
  , powerDiagramPeakIntermediateCoordinateBits :: !Int
  , powerDiagramFinalCoordinateBits :: !Int
  , powerDiagramFinalDenominatorBits :: !Int
  }
  deriving stock (Eq, Ord, Show, Generic)
  deriving anyclass (NFData)

-- | Submitted sites, derived from the exhaustive disposition partition.
powerDiagramInputSites :: PowerDiagramReceipt -> Int
powerDiagramInputSites receipt =
  powerDiagramPublishedCells receipt
    + powerDiagramLowerDimensionalCells receipt
    + powerDiagramEmptyCells receipt
    + powerDiagramCoincidentEquivalentCells receipt
    + powerDiagramCoincidentDominatedCells receipt
{-# INLINE powerDiagramInputSites #-}

-- | Peak exact-coordinate width beyond the widest admitted input.
powerDiagramPeakIntermediateBitGrowth :: PowerDiagramReceipt -> Int
powerDiagramPeakIntermediateBitGrowth receipt =
  max
    0
    ( powerDiagramPeakIntermediateCoordinateBits receipt
        - powerDiagramMaximumInputBits receipt
    )
{-# INLINE powerDiagramPeakIntermediateBitGrowth #-}

-- | Published coordinate width beyond the widest admitted input.
powerDiagramFinalCoordinateBitGrowth :: PowerDiagramReceipt -> Int
powerDiagramFinalCoordinateBitGrowth receipt =
  max
    0
    ( powerDiagramFinalCoordinateBits receipt
        - powerDiagramMaximumInputBits receipt
    )
{-# INLINE powerDiagramFinalCoordinateBitGrowth #-}

-- | One exact affine form @c0 + cx*x + cy*y@.
data AffineForm = AffineForm
  { affineFormConstant :: !ExactRational
  , affineFormXCoefficient :: !ExactRational
  , affineFormYCoefficient :: !ExactRational
  }
  deriving stock (Eq, Ord, Show, Generic)
  deriving anyclass (NFData)

-- | Typed obstructions from exact affine argmax decomposition.  Power-cell
-- construction remains the canonical geometric owner; window restriction is
-- the only additional boundary.
data UpperEnvelopeError label
  = UpperEnvelopeEmptyForms
  | UpperEnvelopeWindowHullFailed !MinkowskiError
  | UpperEnvelopePowerConstructionFailed !(PowerDiagramError label)
  | UpperEnvelopeWindowOverlayFailed !(OverlayError (Maybe label) Bool)
  deriving stock (Eq, Show, Generic)
  deriving anyclass (NFData)

-- | Construct the exact regular subdivision before any bounded clipping.
regularTriangulation
  :: Ord label
  => NonEmpty (PowerSite label)
  -> Either
      (PowerDiagramError label)
      (RegularTriangulation label, RegularTriangulationReceipt)
regularTriangulation submitted = do
  sortedSites <- validateAndSortSites submitted
  let generators = fmap (fst . prepareExactPowerGenerator) sortedSites
  section <- resolvedGeneratorSection generators
  pure (publishRegularSection section)

regularSiteDisposition
  :: Ord label
  => label
  -> RegularTriangulation label
  -> Maybe (RegularSiteDisposition label)
regularSiteDisposition label = Map.lookup label . storedRegularDispositions

regularFaces :: RegularTriangulation label -> [RegularFace label]
regularFaces = storedRegularFaces

regularEdges :: RegularTriangulation label -> [RegularEdge label]
regularEdges = storedRegularEdges

regularNeighbours
  :: Ord label
  => label
  -> RegularTriangulation label
  -> Set label
regularNeighbours label =
  Map.findWithDefault Set.empty label . storedRegularNeighbours

boundedPowerDiagram
  :: Ord label
  => ConvexPolygon
  -> NonEmpty (PowerSite label)
  -> Either (PowerDiagramError label) (BoundedPowerDiagram label, PowerDiagramReceipt)
boundedPowerDiagram domain submitted = do
  sortedSites <- validateAndSortSites submitted
  let preparedGenerators = fmap prepareExactPowerGenerator sortedSites
      generators = fmap fst preparedGenerators
      siteInputBits =
        Foldable.foldl' (\bits preparation -> max bits (snd preparation)) 0 preparedGenerators
  (dispositions, clipReceipt, regularReceipt, maximumCellConstraints) <-
    exactGeneratorDispositionsWith (<>) mempty domain generators
  let receipt =
        aggregateReceipt
          domain
          dispositions
          siteInputBits
          clipReceipt
          regularReceipt
          maximumCellConstraints
  pure (BoundedPowerDiagram dispositions, receipt)

-- | Full-dimensional labelled argmax regions for affine forms
-- @c0 + cx*x + cy*y@ inside an admitted polygonal window. The result is a
-- planar projection: lower-dimensional and empty winners intentionally have
-- no region. Use 'regularTriangulation' on corresponding weighted sites when
-- those dispositions or exact unbounded duals are required. Identical forms
-- choose the least label, independent of map construction order.
upperEnvelope
  :: Ord label
  => PolygonComponent
  -> Map label AffineForm
  -> Either (UpperEnvelopeError label) (PlanarLayer (Maybe label))
upperEnvelope window forms = do
  generators <- affineFormGenerators forms
  domain <-
    first UpperEnvelopeWindowHullFailed
      (convexHullPolygon (exactLoopPoints (polygonOuterLoop window)))
  (dispositions, _, _, _) <-
    first UpperEnvelopePowerConstructionFailed
      (exactGeneratorDispositionsWith discardClipReceipt () domain generators)
  let envelopeLayer = affineDispositionLayer dispositions
  if convexPolygonComponent domain == window
    then Right envelopeLayer
    else restrictEnvelopeToWindow window envelopeLayer

powerCellDisposition
  :: Ord label
  => label
  -> BoundedPowerDiagram label
  -> Maybe (PowerCellDisposition label)
powerCellDisposition label (BoundedPowerDiagram dispositions) =
  Map.lookup label dispositions

powerCellDispositions
  :: BoundedPowerDiagram label
  -> [(label, PowerCellDisposition label)]
powerCellDispositions (BoundedPowerDiagram dispositions) = Map.toAscList dispositions

-- | Publish only full-dimensional cells.  The outside label is a caller-owned
-- view choice and may not collide with any submitted site label.
powerDiagramPlanarLayer
  :: Ord label
  => label
  -> BoundedPowerDiagram label
  -> Either (PowerDiagramError label) (PlanarLayer label)
powerDiagramPlanarLayer outside (BoundedPowerDiagram dispositions)
  | Map.member outside dispositions = Left (PowerDiagramOutsideLabelCollides outside)
  | otherwise =
      Right (publishedPowerLayer outside id dispositions)

affineDispositionLayer
  :: Ord label
  => Map label (PowerCellDisposition label)
  -> PlanarLayer (Maybe label)
affineDispositionLayer = publishedPowerLayer Nothing Just

publishedPowerLayer
  :: Ord publishedLabel
  => publishedLabel
  -> (label -> publishedLabel)
  -> Map label (PowerCellDisposition label)
  -> PlanarLayer publishedLabel
publishedPowerLayer outside publishLabel dispositions =
  planarLayerFromAdmittedComponents
    outside
    [ (publishLabel label, convexPolygonComponent polygon)
    | (label, PublishedPowerCell polygon) <- Map.toAscList dispositions
    ]

restrictEnvelopeToWindow
  :: Ord label
  => PolygonComponent
  -> PlanarLayer (Maybe label)
  -> Either (UpperEnvelopeError label) (PlanarLayer (Maybe label))
restrictEnvelopeToWindow window envelopeLayer = do
  let windowLayer = planarLayerFromAdmittedComponents False [(True, window)]
  clipped <-
    first UpperEnvelopeWindowOverlayFailed
      (overlayLayers envelopeLayer windowLayer)
  pure
    ( planarLayerFromAdmittedComponents
        Nothing
        [ (Just label, component)
        | cell <- Vector.toList (overlayResultCells clipped)
        , overlayCellRight cell
        , Just label <- [overlayCellLeft cell]
        , BoundedOverlayCell component <- [overlayCellGeometry cell]
        ]
    )

validateAndSortSites
  :: Ord label
  => NonEmpty (PowerSite label)
  -> Either (PowerDiagramError label) (NonEmpty (PowerSite label))
validateAndSortSites submitted =
  let sorted = NonEmpty.sortBy (comparing powerSiteLabel) submitted
   in case List.find (uncurry sameLabel) (consecutivePairs (NonEmpty.toList sorted)) of
        Just (duplicate, _) -> Left (DuplicatePowerSiteLabel (powerSiteLabel duplicate))
        Nothing -> Right sorted
 where
  sameLabel :: Eq label => PowerSite label -> PowerSite label -> Bool
  sameLabel left right = powerSiteLabel left == powerSiteLabel right

prepareExactPowerGenerator :: PowerSite label -> (ExactPowerGenerator label, Int)
prepareExactPowerGenerator site =
  let point = exactPointFromQueryPoint (powerSiteQueryPoint site)
      (coordinateX, coordinateY) = exactPointCoordinates point
      weight = powerWeightExact (powerSiteWeight site)
   in ( ExactPowerGenerator
          { exactPowerGeneratorLabel = powerSiteLabel site
          , exactPowerGeneratorXCoefficient = 2 * coordinateX
          , exactPowerGeneratorYCoefficient = 2 * coordinateY
          , exactPowerGeneratorConstant =
              weight - coordinateX * coordinateX - coordinateY * coordinateY
          }
      , max (exactPointBitWidth point) (exactRationalBitWidth weight)
      )

affineFormGenerators
  :: Map label AffineForm
  -> Either (UpperEnvelopeError label) (NonEmpty (ExactPowerGenerator label))
affineFormGenerators forms =
  case Map.minViewWithKey forms of
    Nothing -> Left UpperEnvelopeEmptyForms
    Just ((firstLabel, firstForm), remaining) ->
      Right
        ( affineFormGenerator firstLabel firstForm
            :| fmap (uncurry affineFormGenerator) (Map.toAscList remaining)
        )

affineFormGenerator
  :: label
  -> AffineForm
  -> ExactPowerGenerator label
affineFormGenerator label form =
  ExactPowerGenerator
    { exactPowerGeneratorLabel = label
    , exactPowerGeneratorXCoefficient = affineFormXCoefficient form
    , exactPowerGeneratorYCoefficient = affineFormYCoefficient form
    , exactPowerGeneratorConstant = affineFormConstant form
    }

exactGeneratorDispositionsWith
  :: Ord label
  => (summary -> ExactClipReceipt -> summary)
  -> summary
  -> ConvexPolygon
  -> NonEmpty (ExactPowerGenerator label)
  -> Either
      (PowerDiagramError label)
      ( Map label (PowerCellDisposition label)
      , summary
      , GeneratorRegularReceipt
      , Int
      )
exactGeneratorDispositionsWith summarizeReceipt initialSummary domain generators = do
  retainedDomain <-
    first PowerDomainInvalid (exactRetainedPolygon (convexPolygonPoints domain))
  section <- resolvedGeneratorSection generators
  let topology = resolvedRegularTopology section
  constraints <- prepareRegularConstraintSection topology
  clipped <-
    traverse
      (clipPowerCell retainedDomain section constraints)
      (generatorRegularDispositions topology)
  let (publishedAssociations, summary, maximumCellConstraints) =
        Foldable.foldl'
          (\(associations, accumulatedSummary, peak) (label, disposition, cellReceipt, cellAxes) ->
             let !combinedSummary = summarizeReceipt accumulatedSummary cellReceipt
              in ( (label, disposition) : associations
                 , combinedSummary
                 , max peak cellAxes
                 ))
          ([], initialSummary, 0)
          clipped
      publishedDispositions = Map.fromList publishedAssociations
      dispositions =
        fmap coincidentPowerDisposition (resolvedCoincidentDispositions section)
          <> publishedDispositions
  pure
    ( dispositions
    , summary
    , generatorRegularReceipt topology
    , maximumCellConstraints
    )

discardClipReceipt :: () -> ExactClipReceipt -> ()
discardClipReceipt _ _ = ()

groupGeneratorsBySlope
  :: NonEmpty (ExactPowerGenerator label)
  -> NonEmpty (NonEmpty (ExactPowerGenerator label))
groupGeneratorsBySlope (initial :| remaining) =
  let initialSlope = exactGeneratorSlope initial
      (sameInitialSlope, otherGenerators) =
        List.partition ((== initialSlope) . exactGeneratorSlope) remaining
      otherGroups =
        Map.fromListWith
          (<>)
          [ (exactGeneratorSlope generator, generator :| [])
          | generator <- otherGenerators
          ]
   in (initial :| sameInitialSlope) :| Map.elems otherGroups

exactGeneratorSlope
  :: ExactPowerGenerator label
  -> (ExactRational, ExactRational)
exactGeneratorSlope generator =
  ( exactPowerGeneratorXCoefficient generator
  , exactPowerGeneratorYCoefficient generator
  )

resolveCoincidentGeneratorGroup
  :: Ord label
  => NonEmpty (ExactPowerGenerator label)
  -> (ExactPowerGenerator label, [(label, CoincidentGeneratorDisposition label)])
resolveCoincidentGeneratorGroup generators@(initial :| remaining) =
  let representative = List.foldl' chooseRepresentative initial remaining
      representativeLabel = exactPowerGeneratorLabel representative
   in ( representative
      , [ ( exactPowerGeneratorLabel generator
          , classifyCoincidentGenerator representative generator
          )
        | generator <- NonEmpty.toList generators
        , exactPowerGeneratorLabel generator /= representativeLabel
        ]
      )
 where
  chooseRepresentative
    :: Ord label
    => ExactPowerGenerator label
    -> ExactPowerGenerator label
    -> ExactPowerGenerator label
  chooseRepresentative selected candidate =
    case compare
      (exactPowerGeneratorConstant candidate)
      (exactPowerGeneratorConstant selected) of
      GT -> candidate
      LT -> selected
      EQ ->
        if exactPowerGeneratorLabel candidate < exactPowerGeneratorLabel selected
          then candidate
          else selected

classifyCoincidentGenerator
  :: ExactPowerGenerator label
  -> ExactPowerGenerator label
  -> CoincidentGeneratorDisposition label
classifyCoincidentGenerator representative candidate
  | exactPowerGeneratorConstant candidate == exactPowerGeneratorConstant representative =
      CoincidentGeneratorEquivalentTo (exactPowerGeneratorLabel representative)
  | otherwise = CoincidentGeneratorDominatedBy (exactPowerGeneratorLabel representative)

data CoincidentGeneratorDisposition label
  = CoincidentGeneratorEquivalentTo !label
  | CoincidentGeneratorDominatedBy !label

coincidentPowerDisposition
  :: CoincidentGeneratorDisposition label
  -> PowerCellDisposition label
coincidentPowerDisposition disposition =
  case disposition of
    CoincidentGeneratorEquivalentTo label -> CoincidentEquivalentTo label
    CoincidentGeneratorDominatedBy label -> CoincidentDominatedBy label

coincidentRegularDisposition
  :: CoincidentGeneratorDisposition label
  -> RegularSiteDisposition label
coincidentRegularDisposition disposition =
  case disposition of
    CoincidentGeneratorEquivalentTo label -> RegularSiteCoincidentEquivalentTo label
    CoincidentGeneratorDominatedBy label -> RegularSiteCoincidentDominatedBy label

data ResolvedGeneratorSection label = ResolvedGeneratorSection
  { resolvedCoincidentDispositions :: !(Map label (CoincidentGeneratorDisposition label))
  , resolvedRegularTopology :: !(GeneratorRegularTopology label)
  }

resolvedGeneratorSection
  :: Ord label
  => NonEmpty (ExactPowerGenerator label)
  -> Either (PowerDiagramError label) (ResolvedGeneratorSection label)
resolvedGeneratorSection generators = do
  let resolvedGroups = fmap resolveCoincidentGeneratorGroup (groupGeneratorsBySlope generators)
      representatives = fmap fst resolvedGroups
      coincidentDispositions =
        Map.fromList (concatMap snd (NonEmpty.toList resolvedGroups))
  topology <-
    first PowerRegularTopologyFailed
      (regularGeneratorTopology (DistinctSlopeGenerators representatives))
  pure
    ResolvedGeneratorSection
      { resolvedCoincidentDispositions = coincidentDispositions
      , resolvedRegularTopology = topology
      }

publishRegularSection
  :: Ord label
  => ResolvedGeneratorSection label
  -> (RegularTriangulation label, RegularTriangulationReceipt)
publishRegularSection section =
  let topology = resolvedRegularTopology section
      coincident = resolvedCoincidentDispositions section
      dispositions =
        Map.fromList
          [ ( exactPowerGeneratorLabel generator
            , publishGeneratorDisposition disposition
            )
          | (generator, disposition) <-
              NonEmpty.toList (generatorRegularDispositions topology)
          ]
          <> fmap coincidentRegularDisposition coincident
      faces = fmap publishGeneratorFace (generatorRegularFaces topology)
      edges = fmap publishGeneratorEdge (generatorRegularEdges topology)
      generatorReceipt = generatorRegularReceipt topology
      coincidentCount = Map.size coincident
   in ( RegularTriangulation
          { storedRegularDispositions = dispositions
          , storedRegularFaces = faces
          , storedRegularEdges = edges
          , storedRegularNeighbours = regularNeighbourSection topology
          }
      , RegularTriangulationReceipt
          { regularTriangulationInputSites =
              generatorRegularInputSites generatorReceipt + coincidentCount
          , regularTriangulationRepresentativeSites =
              generatorRegularInputSites generatorReceipt
          , regularTriangulationVisibleSites =
              generatorRegularVisibleSites generatorReceipt
          , regularTriangulationLowerDimensionalSites =
              generatorRegularLowerDimensionalSites generatorReceipt
          , regularTriangulationHiddenSites =
              generatorRegularHiddenSites generatorReceipt
          , regularTriangulationCoincidentSites = coincidentCount
          , regularTriangulationFaces = generatorRegularFaceCount generatorReceipt
          , regularTriangulationEdges = generatorRegularEdgeCount generatorReceipt
          , regularTriangulationPeakHullFaces =
              generatorRegularPeakHullFaces generatorReceipt
          }
      )

publishGeneratorDisposition
  :: RegularGeneratorDisposition
  -> RegularSiteDisposition label
publishGeneratorDisposition disposition =
  case disposition of
    RegularGeneratorVisible -> RegularSiteVisible
    RegularGeneratorLowerDimensional -> RegularSiteLowerDimensional
    RegularGeneratorHidden -> RegularSiteHidden

publishGeneratorFace :: GeneratorRegularFace label -> RegularFace label
publishGeneratorFace face =
  RegularFace
    (generatorRegularFaceFirst face)
    (generatorRegularFaceSecond face)
    (generatorRegularFaceThird face)
    (generatorRegularFaceDualPoint face)

publishGeneratorEdge :: GeneratorRegularEdge label -> RegularEdge label
publishGeneratorEdge edge =
  RegularEdge
    (exactPowerGeneratorLabel (generatorRegularEdgeFirst edge))
    (exactPowerGeneratorLabel (generatorRegularEdgeSecond edge))
    (case generatorRegularEdgeDual edge of
       GeneratorDualSegment segment -> BoundedPowerDual segment
       GeneratorDualRay ray -> UnboundedPowerDual ray
       GeneratorDualLine line -> FullLinePowerDual line
       GeneratorDualCollapsed point -> CollapsedPowerDual point)

regularNeighbourSection
  :: Ord label
  => GeneratorRegularTopology label
  -> Map label (Set label)
regularNeighbourSection =
  List.foldl' insertRegularNeighbourEdge Map.empty . generatorRegularEdges

insertRegularNeighbourEdge
  :: Ord label
  => Map label (Set label)
  -> GeneratorRegularEdge label
  -> Map label (Set label)
insertRegularNeighbourEdge neighbours edge =
  let firstLabel = exactPowerGeneratorLabel (generatorRegularEdgeFirst edge)
      secondLabel = exactPowerGeneratorLabel (generatorRegularEdgeSecond edge)
   in Map.insertWith Set.union firstLabel (Set.singleton secondLabel)
        (Map.insertWith Set.union secondLabel (Set.singleton firstLabel) neighbours)

prepareRadicalAxis
  :: ExactPowerGenerator label
  -> ExactPowerGenerator label
  -> Either (PowerDiagramError label) ExactAffineLine
prepareRadicalAxis firstGenerator secondGenerator =
  first
    ( PowerBisectorInvalid
        (exactPowerGeneratorLabel firstGenerator)
        (exactPowerGeneratorLabel secondGenerator)
    )
    (exactGeneratorAxis firstGenerator secondGenerator)

prepareRegularConstraintSection
  :: Ord label
  => GeneratorRegularTopology label
  -> Either (PowerDiagramError label) (Map label [ExactClosedHalfPlane])
prepareRegularConstraintSection =
  Foldable.foldlM prepareRegularConstraintEdge Map.empty . generatorRegularEdges

prepareRegularConstraintEdge
  :: Ord label
  => Map label [ExactClosedHalfPlane]
  -> GeneratorRegularEdge label
  -> Either (PowerDiagramError label) (Map label [ExactClosedHalfPlane])
prepareRegularConstraintEdge constraints edge = do
  let firstGenerator = generatorRegularEdgeFirst edge
      secondGenerator = generatorRegularEdgeSecond edge
      firstLabel = exactPowerGeneratorLabel firstGenerator
      secondLabel = exactPowerGeneratorLabel secondGenerator
  axis <- prepareRadicalAxis firstGenerator secondGenerator
  pure
    ( Map.insertWith (<>) secondLabel [exactClosedHalfPlane (oppositeExactAffineLine axis)]
        (Map.insertWith (<>) firstLabel [exactClosedHalfPlane axis] constraints)
    )

clipPowerCell
  :: Ord label
  => ExactRetainedPolygon
  -> ResolvedGeneratorSection label
  -> Map label [ExactClosedHalfPlane]
  -> (ExactPowerGenerator label, RegularGeneratorDisposition)
  -> Either
      (PowerDiagramError label)
      (label, PowerCellDisposition label, ExactClipReceipt, Int)
clipPowerCell retainedDomain section constraints (ownerGenerator, disposition) =
  let ownerLabel = exactPowerGeneratorLabel ownerGenerator
      topology = resolvedRegularTopology section
   in case disposition of
    RegularGeneratorHidden ->
      pure (ownerLabel, EmptyPowerCell, mempty, 0)
    RegularGeneratorVisible ->
      finishPowerCell
        retainedDomain
        ownerGenerator
        (Map.findWithDefault [] ownerLabel constraints)
    RegularGeneratorLowerDimensional -> do
      let competitors =
            [ generator
            | (generator, _) <-
                NonEmpty.toList (generatorRegularDispositions topology)
            , exactPowerGeneratorLabel generator /= ownerLabel
            ]
      halfPlanes <- traverse (preparedDirectHalfPlane ownerGenerator) competitors
      finishPowerCell retainedDomain ownerGenerator halfPlanes

preparedDirectHalfPlane
  :: ExactPowerGenerator label
  -> ExactPowerGenerator label
  -> Either (PowerDiagramError label) ExactClosedHalfPlane
preparedDirectHalfPlane owner competitor =
  exactClosedHalfPlane <$> prepareRadicalAxis owner competitor

finishPowerCell
  :: ExactRetainedPolygon
  -> ExactPowerGenerator label
  -> [ExactClosedHalfPlane]
  -> Either
      (PowerDiagramError label)
      (label, PowerCellDisposition label, ExactClipReceipt, Int)
finishPowerCell retainedDomain ownerGenerator halfPlanes = do
  (exactDisposition, receipt) <-
    first (PowerCellClipFailed (exactPowerGeneratorLabel ownerGenerator))
      (exactClipRetainedPolygon retainedDomain halfPlanes)
  pure
    ( exactPowerGeneratorLabel ownerGenerator
    , case exactDisposition of
        ExactClipFullDimensional retained ->
          PublishedPowerCell (convexPolygonFromRetained retained)
        ExactClipLowerDimensional points -> LowerDimensionalPowerCell points
        ExactClipEmpty -> EmptyPowerCell
    , receipt
    , length halfPlanes
    )

aggregateReceipt
  :: ConvexPolygon
  -> Map label (PowerCellDisposition label)
  -> Int
  -> ExactClipReceipt
  -> GeneratorRegularReceipt
  -> Int
  -> PowerDiagramReceipt
aggregateReceipt domain dispositions siteInputBits clipReceipt regularReceipt maximumCellConstraints =
  let inputBits = max siteInputBits (exactClipInputCoordinateBits clipReceipt)
      peakBits = exactClipPeakIntermediateCoordinateBits clipReceipt
      finalBits = exactClipFinalCoordinateBits clipReceipt
      dispositionCounts = countPowerDispositions dispositions
   in PowerDiagramReceipt
        { powerDiagramDomainVertices = NonEmpty.length (convexPolygonPoints domain)
        , powerDiagramSubmittedSiteConstraints = exactClipSubmittedHalfPlanes clipReceipt
        , powerDiagramActiveBoundaries = exactClipActiveBoundaries clipReceipt
        , powerDiagramBoundaryCompatibilityChecks = exactClipBoundaryCompatibilityChecks clipReceipt
        , powerDiagramExactIntersections = exactClipExactIntersections clipReceipt
        , powerDiagramPublishedCells = countedPublishedCells dispositionCounts
        , powerDiagramLowerDimensionalCells = countedLowerDimensionalCells dispositionCounts
        , powerDiagramEmptyCells = countedEmptyCells dispositionCounts
        , powerDiagramCoincidentEquivalentCells = countedCoincidentEquivalentCells dispositionCounts
        , powerDiagramCoincidentDominatedCells = countedCoincidentDominatedCells dispositionCounts
        , powerDiagramRegularFaces = generatorRegularFaceCount regularReceipt
        , powerDiagramRegularEdges = generatorRegularEdgeCount regularReceipt
        , powerDiagramOracleCells = generatorRegularLowerDimensionalSites regularReceipt
        , powerDiagramMaximumCellConstraints = maximumCellConstraints
        , powerDiagramMaximumInputBits = inputBits
        , powerDiagramMaximumAffineCoefficientBits = exactClipMaximumAffineCoefficientBits clipReceipt
        , powerDiagramPeakIntermediateCoordinateBits = peakBits
        , powerDiagramFinalCoordinateBits = finalBits
        , powerDiagramFinalDenominatorBits = exactClipFinalDenominatorBits clipReceipt
        }

data PowerDispositionCounts = PowerDispositionCounts
  { countedPublishedCells :: !Int
  , countedLowerDimensionalCells :: !Int
  , countedEmptyCells :: !Int
  , countedCoincidentEquivalentCells :: !Int
  , countedCoincidentDominatedCells :: !Int
  }

countPowerDispositions
  :: Map label (PowerCellDisposition label)
  -> PowerDispositionCounts
countPowerDispositions =
  Map.foldl'
    (\counts disposition -> case disposition of
        PublishedPowerCell _ ->
          counts {countedPublishedCells = countedPublishedCells counts + 1}
        LowerDimensionalPowerCell _ ->
          counts {countedLowerDimensionalCells = countedLowerDimensionalCells counts + 1}
        EmptyPowerCell ->
          counts {countedEmptyCells = countedEmptyCells counts + 1}
        CoincidentEquivalentTo _ ->
          counts {countedCoincidentEquivalentCells = countedCoincidentEquivalentCells counts + 1}
        CoincidentDominatedBy _ ->
          counts {countedCoincidentDominatedCells = countedCoincidentDominatedCells counts + 1})
    PowerDispositionCounts
      { countedPublishedCells = 0
      , countedLowerDimensionalCells = 0
      , countedEmptyCells = 0
      , countedCoincidentEquivalentCells = 0
      , countedCoincidentDominatedCells = 0
      }