moonlight-planar-1.1.0.0: src-public/Moonlight/Planar/Internal/PowerDiagram/Projection.hs
{-# LANGUAGE BangPatterns #-}
-- | Bounded clipping and planar publication of exact regular sections.
module Moonlight.Planar.Internal.PowerDiagram.Projection
( boundedPowerDiagram
, boundedPowerDiagramFromRegular
, powerCellDisposition
, powerCellDispositions
, powerDiagramPlanarLayer
, upperEnvelope
) where
import Data.Bifunctor (first)
import qualified Data.Foldable as Foldable
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 Moonlight.Planar.Exact
( ExactClipDisposition (..)
, ExactClipReceipt (..)
, emptyExactClipReceipt
, ExactAffineLine
, ExactClosedHalfPlane
, ExactRetainedPolygon
, exactClipRetainedPolygon
, exactClosedHalfPlane
, oppositeExactAffineLine
)
import Moonlight.Planar.Internal.PowerDiagram.Generator
( ExactPowerGenerator (..)
, GeneratorRegularEdge (..)
, GeneratorRegularReceipt (..)
, GeneratorRegularTopology (..)
, RegularGeneratorDisposition (..)
, exactGeneratorAxis
)
import Moonlight.Planar.Internal.PowerDiagram.Model
import Moonlight.Planar.Internal.PowerDiagram.Section
import Moonlight.Planar.Convex
( ConvexPolygon
, convexPolygonPoints
, convexHullPolygon
, convexPolygonComponent
, convexPolygonFromRetained
, retainConvexPolygon
)
import Moonlight.Planar.Internal.Region.Publication
( planarLayerFromAdmittedComponents
)
import Moonlight.Planar.Overlay
( overlayLayers
, overlayPlanarLayer
)
import Moonlight.Planar.Region
( PlanarLayer
, PolygonComponent
, exactLoopPoints
, planarLayerRegions
, planarRegionComponents
, polygonOuterLoop
)
-- | Construct and clip a normalized regular subdivision to one convex domain.
boundedPowerDiagram
:: Ord label
=> ConvexPolygon
-> NonEmpty (PowerSite label)
-> Either (PowerDiagramError label) (BoundedPowerDiagram label, PowerDiagramReceipt)
boundedPowerDiagram domain submitted = do
sortedSites <- validateAndSortSites submitted
let generators = fmap prepareExactPowerGenerator sortedSites
siteInputBits = maximumPowerSiteInputBits sortedSites
(dispositions, clipReceipt, regularReceipt, maximumCellConstraints) <-
exactGeneratorDispositionsWith (<>) emptyExactClipReceipt domain generators
let receipt =
aggregateReceipt
domain
dispositions
siteInputBits
clipReceipt
regularReceipt
maximumCellConstraints
pure (BoundedPowerDiagram dispositions, receipt)
-- | Clip an already normalized regular value without rebuilding its lifted
-- hull. The empty regular value yields an empty labelled diagram.
boundedPowerDiagramFromRegular
:: Ord label
=> ConvexPolygon
-> RegularTriangulation label
-> Either (PowerDiagramError label) (BoundedPowerDiagram label, PowerDiagramReceipt)
boundedPowerDiagramFromRegular domain triangulation =
case storedRegularSection triangulation of
Nothing ->
Right
( BoundedPowerDiagram Map.empty
, aggregateReceipt
domain
Map.empty
0
emptyExactClipReceipt
emptyGeneratorRegularReceipt
0
)
Just section -> do
(dispositions, clipReceipt, generatorReceipt, maximumCellConstraints) <-
exactRegularSectionDispositionsWith (<>) emptyExactClipReceipt domain section
let receipt =
aggregateReceipt
domain
dispositions
(maximumPowerSiteInputBits (storedRegularSites triangulation))
clipReceipt
generatorReceipt
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
-- | Look up the exhaustive bounded-cell disposition for one submitted label.
powerCellDisposition
:: Ord label
=> label
-> BoundedPowerDiagram label
-> Maybe (PowerCellDisposition label)
powerCellDisposition label (BoundedPowerDiagram dispositions) =
Map.lookup label dispositions
-- | Enumerate every submitted label and bounded-cell disposition in order.
powerCellDispositions
:: BoundedPowerDiagram label
-> [(label, PowerCellDisposition label)]
powerCellDispositions (BoundedPowerDiagram dispositions) = Map.toAscList dispositions
-- | Publish the full-dimensional cell layer with a canonical exterior.
powerDiagramPlanarLayer
:: Ord label
=> BoundedPowerDiagram label
-> PlanarLayer (Maybe label)
powerDiagramPlanarLayer (BoundedPowerDiagram dispositions) =
affineDispositionLayer 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)
published <- first UpperEnvelopeWindowPublicationFailed (overlayPlanarLayer clipped)
pure
( planarLayerFromAdmittedComponents
Nothing
[ (Just label, component)
| ((Just label, True), region) <- Map.toAscList (planarLayerRegions published)
, component <- planarRegionComponents region
]
)
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
section <- first PowerRegularTopologyFailed (resolvedGeneratorSection generators)
exactResolvedGeneratorDispositionsWith
summarizeReceipt
initialSummary
domain
section
exactResolvedGeneratorDispositionsWith
:: Ord label
=> (summary -> ExactClipReceipt -> summary)
-> summary
-> ConvexPolygon
-> ResolvedGeneratorSection label
-> Either
(PowerDiagramError label)
( Map label (PowerCellDisposition label)
, summary
, GeneratorRegularReceipt
, Int
)
exactResolvedGeneratorDispositionsWith summarizeReceipt initialSummary domain section = do
let retainedDomain = retainConvexPolygon domain
topology = resolvedRegularTopology section
constraints <- prepareRegularConstraintSection topology
clipped <-
traverse
(clipPowerCell retainedDomain section constraints)
(generatorRegularDispositions topology)
let (dispositions, summary, maximumCellConstraints) =
summarizeClippedPowerCells
summarizeReceipt
initialSummary
(resolvedCoincidentDispositions section)
(NonEmpty.toList clipped)
pure
( dispositions
, summary
, generatorRegularReceipt topology
, maximumCellConstraints
)
exactRegularSectionDispositionsWith
:: Ord label
=> (summary -> ExactClipReceipt -> summary)
-> summary
-> ConvexPolygon
-> RegularSection label
-> Either
(PowerDiagramError label)
( Map label (PowerCellDisposition label)
, summary
, GeneratorRegularReceipt
, Int
)
exactRegularSectionDispositionsWith summarizeReceipt initialSummary domain section = do
let retainedDomain = retainConvexPolygon domain
constraints <- preparePublishedRegularConstraints section
representativeDispositions <-
traverse
(\(label, generator) -> do
disposition <-
maybe
(Left (PowerRegularDispositionMissing label))
Right
(Map.lookup label (sectionRegularDispositions section))
clipPublishedPowerCell
retainedDomain
section
constraints
generator
disposition)
(Map.toAscList (sectionGenerators section))
let (dispositions, summary, maximumCellConstraints) =
summarizeClippedPowerCells
summarizeReceipt
initialSummary
(sectionCoincidentDispositions section)
representativeDispositions
pure
( dispositions
, summary
, sectionRegularReceipt section
, maximumCellConstraints
)
summarizeClippedPowerCells
:: Ord label
=> (summary -> ExactClipReceipt -> summary)
-> summary
-> Map label (CoincidentGeneratorDisposition label)
-> [(label, PowerCellDisposition label, ExactClipReceipt, Int)]
-> (Map label (PowerCellDisposition label), summary, Int)
summarizeClippedPowerCells summarizeReceipt initialSummary coincident clipped =
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
in ( fmap coincidentPowerDisposition coincident
<> Map.fromList publishedAssociations
, summary
, maximumCellConstraints
)
preparePublishedRegularConstraints
:: Ord label
=> RegularSection label
-> Either (PowerDiagramError label) (Map label [ExactClosedHalfPlane])
preparePublishedRegularConstraints section =
Foldable.foldlM prepare Map.empty (Map.elems (sectionRegularEdges section))
where
prepare constraints edgeSection = do
let edge = sectionRegularEdge edgeSection
(firstLabel, secondLabel) = regularEdgeLabels edge
firstGenerator <- requireSectionGenerator section firstLabel
secondGenerator <- requireSectionGenerator section secondLabel
insertRegularConstraintEdge constraints firstGenerator secondGenerator
clipPublishedPowerCell
:: Ord label
=> ExactRetainedPolygon
-> RegularSection label
-> Map label [ExactClosedHalfPlane]
-> ExactPowerGenerator label
-> RegularSiteDisposition label
-> Either
(PowerDiagramError label)
(label, PowerCellDisposition label, ExactClipReceipt, Int)
clipPublishedPowerCell retainedDomain section constraints ownerGenerator disposition =
let ownerLabel = exactPowerGeneratorLabel ownerGenerator
competitors =
filter
((/= ownerLabel) . exactPowerGeneratorLabel)
(Map.elems (sectionGenerators section))
clipAs =
clipRegularGeneratorCell
retainedDomain
ownerGenerator
(Map.findWithDefault [] ownerLabel constraints)
competitors
in case disposition of
RegularSiteHidden -> clipAs RegularGeneratorHidden
RegularSiteVisible -> clipAs RegularGeneratorVisible
RegularSiteLowerDimensional -> clipAs RegularGeneratorLowerDimensional
RegularSiteCoincidentEquivalentTo _ ->
Left (PowerRegularDispositionMissing ownerLabel)
RegularSiteCoincidentDominatedBy _ ->
Left (PowerRegularDispositionMissing ownerLabel)
requireSectionGenerator
:: Ord label
=> RegularSection label
-> label
-> Either (PowerDiagramError label) (ExactPowerGenerator label)
requireSectionGenerator section label =
maybe
(Left (PowerRegularGeneratorMissing label))
Right
(Map.lookup label (sectionGenerators section))
discardClipReceipt :: () -> ExactClipReceipt -> ()
discardClipReceipt _ _ = ()
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
insertRegularConstraintEdge constraints firstGenerator secondGenerator
insertRegularConstraintEdge
:: Ord label
=> Map label [ExactClosedHalfPlane]
-> ExactPowerGenerator label
-> ExactPowerGenerator label
-> Either (PowerDiagramError label) (Map label [ExactClosedHalfPlane])
insertRegularConstraintEdge constraints firstGenerator secondGenerator = do
let 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
competitors =
[ generator
| (generator, _) <-
NonEmpty.toList (generatorRegularDispositions topology)
, exactPowerGeneratorLabel generator /= ownerLabel
]
in clipRegularGeneratorCell
retainedDomain
ownerGenerator
(Map.findWithDefault [] ownerLabel constraints)
competitors
disposition
clipRegularGeneratorCell
:: ExactRetainedPolygon
-> ExactPowerGenerator label
-> [ExactClosedHalfPlane]
-> [ExactPowerGenerator label]
-> RegularGeneratorDisposition
-> Either
(PowerDiagramError label)
(label, PowerCellDisposition label, ExactClipReceipt, Int)
clipRegularGeneratorCell retainedDomain ownerGenerator activeConstraints competitors disposition =
case disposition of
RegularGeneratorHidden ->
pure (exactPowerGeneratorLabel ownerGenerator, EmptyPowerCell, emptyExactClipReceipt, 0)
RegularGeneratorVisible ->
finishPowerCell retainedDomain ownerGenerator activeConstraints
RegularGeneratorLowerDimensional -> do
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
}