packages feed

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

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

-- | Exact labelled common refinement. Source boundaries descend through one
-- exact segment-event plan, glue into canonical atomic constraints, and are
-- admitted only when their binary64 DCEL realization preserves every exact
-- relation.
module Moonlight.Triangulation.Overlay
  ( BoundaryLoopRef (..)
  , OverlayOperand
  , BoundaryRef
  , BoundaryVertexRef
  , BoundaryEdgeRef
  , boundaryRefComponent
  , boundaryRefLoop
  , boundaryRefLocalIndex
  , OverlayVertexOrigin
  , overlayOriginLeftVertices
  , overlayOriginRightVertices
  , overlayOriginLeftEdges
  , overlayOriginRightEdges
  , OverlayEdgeOrigin
  , overlayEdgeLeftSources
  , overlayEdgeRightSources
  , OverlaySupport
  , overlaySupportLabels
  , OverlayCellSupport (..)
  , OverlayCellId (..)
  , OverlayCellGeometry (..)
  , OverlayCell (..)
  , OverlayFace (..)
  , OverlayVertex (..)
  , OverlayEdge (..)
  , OverlayReceipt (..)
  , OverlayArrangementObstruction (..)
  , OverlayCellWitness (..)
  , OverlayError (..)
  , OverlayResult
  , OverlaySelectionKind (..)
  , OverlaySelectionError (..)
  , CoverGap
  , coverGapRegion
  , LayerCoverageError (..)
  , overlayLayers
  , overlayAll
  , overlayEmbeddedTriangulation
  , overlayReceipt
  , overlayCells
  , overlayArrangementVertices
  , overlayArrangementEdges
  , overlayPlanarLayer
  , overlaySelectedRegion
  , overlayMass
  , overlayConfusion
  , layerCovers
  , overlayClosedUnion
  , overlayClosedIntersection
  , overlayRegularizedDifference
  ) where

import Control.DeepSeq (NFData)
import Data.Bifunctor (first)
import Control.Monad (filterM)
import Data.List.NonEmpty (NonEmpty (..))
import qualified Data.Map.Strict as Map
import qualified Data.Vector as V
import GHC.Generics (Generic)
import Moonlight.Triangulation.CellSet
  ( CellSelectionError
  , ExactCellSet
  )
import qualified Moonlight.Triangulation.Dcel as Dcel
import Moonlight.Triangulation.Dcel (vertexData)
import Moonlight.Triangulation.Exact (ExactPoint)
import Moonlight.Triangulation.Internal.HandleDefs
  ( UndirectedEdgeId
  , VertexId
  )
import Moonlight.Triangulation.Handles.Iterators.FixedIterators
  ( innerFaces
  , undirectedEdges
  , vertices
  )
import Moonlight.Triangulation.Internal.CellSet (closeExactCellSetWith)
import Moonlight.Triangulation.Internal.Overlay.Arrangement
  ( certifyArrangement
  )
import Moonlight.Triangulation.Internal.Overlay.Resident
  ( OverlayDiagonalSchedule (CanonicalOverlayDiagonals)
  , edgeSupport
  , faceCarriesExactArea
  , faceLabels
  , regionFaceLabels
  , residentOverlay
  , vertexSupport
  )
import Moonlight.Triangulation.Internal.Overlay.Types
import Moonlight.Triangulation.Internal.Representation (Triangulation)
import Moonlight.Triangulation.Internal.Types
  ( ConstraintMode (Constrained)
  )
import Moonlight.Triangulation.Internal.Tournament
  ( interpretTournament
  , planTournament
  )
import Moonlight.Triangulation.Region
  ( PlanarLayer
  , PlanarRegion
  , PolygonComponent
  , RegionPublicationError (..)
  , emptyPlanarRegion
  , planarLayerOutsideLabel
  , planarLayerRegions
  , planarRegionComponents
  )
import Moonlight.Triangulation.Internal.Region.Publication
  ( labelledPlanarLayerFromExactCoordinates
  , planarLayerFromAdmittedComponents
  )
import Moonlight.Triangulation.Valuation
  ( ExactArea
  , polygonComponentArea
  )

-- | Exact full-dimensional part of a requested window not owned by any
-- bounded label region in the candidate layer.
newtype CoverGap = CoverGap PlanarRegion
  deriving stock (Eq, Ord, Show, Generic)
  deriving anyclass (NFData)

coverGapRegion :: CoverGap -> PlanarRegion
coverGapRegion (CoverGap region) = region

data LayerCoverageError label
  = LayerCoverageOverlayFailed !(OverlayError Bool label)
  | LayerCoverageGapPublicationFailed !RegionPublicationError
  | LayerCoverageGap !CoverGap
  deriving stock (Eq, Show, Generic)
  deriving anyclass (NFData)

-- | Construct the exact common refinement and its one faithful resident DCEL.
overlayLayers
  :: (Ord leftLabel, Ord rightLabel)
  => PlanarLayer leftLabel
  -> PlanarLayer rightLabel
  -> Either
      (OverlayError leftLabel rightLabel)
      (OverlayResult leftLabel rightLabel)
overlayLayers leftLayer rightLayer = do
  certified <- certifyArrangement leftLayer rightLayer
  residentOverlay
    CanonicalOverlayDiagonals
    (planarLayerOutsideLabel leftLayer, planarLayerOutsideLabel rightLayer)
    certified

-- | Construct the common refinement of a nonempty family in input order.
-- Adjacent layers glue in a balanced tree, so an early boundary participates
-- in logarithmically many intermediate arrangements rather than every later
-- left-associated step. Each intermediate layer is projected directly from
-- its certified binary result.
overlayAll
  :: Ord label
  => NonEmpty (PlanarLayer label)
  -> Either
      (OverlayError (NonEmpty label) (NonEmpty label))
      (PlanarLayer (NonEmpty label))
overlayAll =
  interpretTournament overlayLayerPair
    . planTournament
    . fmap singletonLabelLayer

overlayLayerPair
  :: Ord label
  => PlanarLayer (NonEmpty label)
  -> PlanarLayer (NonEmpty label)
  -> Either
      (OverlayError (NonEmpty label) (NonEmpty label))
      (PlanarLayer (NonEmpty label))
overlayLayerPair leftLayer rightLayer =
  flattenNaryOverlay <$> overlayLayers leftLayer rightLayer

singletonLabelLayer :: Ord label => PlanarLayer label -> PlanarLayer (NonEmpty label)
singletonLabelLayer layer =
  planarLayerFromAdmittedComponents
    (planarLayerOutsideLabel layer :| [])
    [ (label :| [], component)
    | (label, region) <- Map.toAscList (planarLayerRegions layer)
    , component <- planarRegionComponents region
    ]

flattenNaryOverlay
  :: Ord label
  => OverlayResult (NonEmpty label) (NonEmpty label)
  -> PlanarLayer (NonEmpty label)
flattenNaryOverlay = projectOverlayLayerWith (uncurry (<>))

projectOverlayLayerWith
  :: (Eq leftLabel, Eq rightLabel, Ord projectedLabel)
  => ((leftLabel, rightLabel) -> projectedLabel)
  -> OverlayResult leftLabel rightLabel
  -> PlanarLayer projectedLabel
projectOverlayLayerWith projectLabel result =
  planarLayerFromAdmittedComponents
    (projectLabel (overlayResultOutsideLabels result))
    [ (projectLabel (overlayCellLabels cell), component)
    | cell <- V.toList (overlayResultCells result)
    , overlayCellLabels cell /= overlayResultOutsideLabels result
    , BoundedOverlayCell component <- [overlayCellGeometry cell]
    ]

overlayCellLabels :: OverlayCell leftLabel rightLabel -> (leftLabel, rightLabel)
overlayCellLabels cell = (overlayCellLeft cell, overlayCellRight cell)
{-# INLINE overlayCellLabels #-}

-- | The binary64 realization used by existing DCEL observations. Exact overlay
-- operations accept 'OverlayResult', never this projection.
overlayEmbeddedTriangulation
  :: OverlayResult leftLabel rightLabel
  -> Triangulation
      'Constrained
      OverlayVertex
      ()
      OverlayEdge
      OverlayFace
overlayEmbeddedTriangulation = overlayResultTriangulation

overlayReceipt :: OverlayResult leftLabel rightLabel -> OverlayReceipt
overlayReceipt = overlayResultReceipt

overlayCells
  :: OverlayResult leftLabel rightLabel
  -> [(OverlayCellId, OverlayCell leftLabel rightLabel)]
overlayCells result =
  V.toList
    (V.imap (\index cell -> (OverlayCellId index, cell)) (overlayResultCells result))

overlayArrangementVertices
  :: OverlayResult leftLabel rightLabel
  -> [(VertexId, OverlayVertex)]
overlayArrangementVertices result =
  let triangulation = overlayResultTriangulation result
   in [(vertex, vertexData triangulation vertex) | vertex <- vertices triangulation]

overlayArrangementEdges
  :: OverlayResult leftLabel rightLabel
  -> [(UndirectedEdgeId, OverlayEdgeOrigin)]
overlayArrangementEdges result =
  let triangulation = overlayResultTriangulation result
   in [ (edge, origin)
      | edge <- undirectedEdges triangulation
      , OverlayBoundary origin <- [Dcel.undirectedEdgeData triangulation edge]
      ]

-- | Publish the already-admitted bounded cell geometry. The resident DCEL is
-- a realization of these exact cells, not a second authoring source.
overlayPlanarLayer
  :: (Ord leftLabel, Ord rightLabel)
  => OverlayResult leftLabel rightLabel
  -> PlanarLayer (leftLabel, rightLabel)
overlayPlanarLayer = projectOverlayLayerWith id

-- | Publish the selected two-dimensional cells. Internal arrangement edges
-- between differently labelled but jointly selected cells dissolve because
-- selection precedes component descent.
overlaySelectedRegion
  :: ((leftLabel, rightLabel) -> Bool)
  -> OverlayResult leftLabel rightLabel
  -> Either RegionPublicationError PlanarRegion
overlaySelectedRegion selected result
  | selected (overlayResultOutsideLabels result) = Left RegionUnboundedSelection
  | otherwise = do
      published <-
        labelledPlanarLayerFromExactCoordinates
          False
          triangulation
          exactPointAt
          labelFace
      pure (Map.findWithDefault emptyPlanarRegion True (planarLayerRegions published))
 where
 triangulation = overlayResultTriangulation result
 exactPointAt :: VertexId -> Either RegionPublicationError ExactPoint
 exactPointAt vertex = Right (overlayExactPoint (vertexData triangulation vertex))
 labelFace face
   | faceCarriesExactArea result face =
       selected <$> regionFaceLabels result face
   | otherwise = Right False

-- | Sum selected exact bounded cells without publishing a polygonal union or
-- computing its perimeter. Selecting the unbounded outside pair is refused by
-- the same obstruction used by 'overlaySelectedRegion'.
overlayMass
  :: ((leftLabel, rightLabel) -> Bool)
  -> OverlayResult leftLabel rightLabel
  -> Either RegionPublicationError ExactArea
overlayMass selected result
  | selected (overlayResultOutsideLabels result) = Left RegionUnboundedSelection
  | otherwise =
      Right (foldBoundedOverlayCells selectedCellArea mempty result)
 where
  selectedCellArea area labels component
    | selected labels = area <> polygonComponentArea component
    | otherwise = area

-- | Exact finite confusion masses, grouped by label pair in one traversal.
-- The outside pair is absent because its complete planar cell is unbounded;
-- bounded cavities carrying that same pair are not misreported as its total.
overlayConfusion
  :: (Ord leftLabel, Ord rightLabel)
  => OverlayResult leftLabel rightLabel
  -> Map.Map (leftLabel, rightLabel) ExactArea
overlayConfusion result =
  foldBoundedOverlayCells accumulateCell Map.empty result
 where
  outsideLabels = overlayResultOutsideLabels result
  accumulateCell masses labels component
    | labels == outsideLabels = masses
    | otherwise = Map.insertWith (<>) labels (polygonComponentArea component) masses

foldBoundedOverlayCells
  :: (accumulator -> (leftLabel, rightLabel) -> PolygonComponent -> accumulator)
  -> accumulator
  -> OverlayResult leftLabel rightLabel
  -> accumulator
foldBoundedOverlayCells accumulate initial result =
  V.foldl' descend initial (overlayResultCells result)
 where
  descend accumulator cell =
    case overlayCellGeometry cell of
      BoundedOverlayCell component ->
        accumulate accumulator (overlayCellLabels cell) component
      UnboundedOverlayCell _ -> accumulator
{-# INLINE foldBoundedOverlayCells #-}

-- | Certify full-dimensional coverage of an admitted polygonal window. The
-- success path inspects certified cells only; an exact gap region is glued
-- solely when the obstruction actually exists.
layerCovers
  :: Ord label
  => PlanarLayer label
  -> PolygonComponent
  -> Either (LayerCoverageError label) ()
layerCovers layer window = do
  let outside = planarLayerOutsideLabel layer
      windowLayer = planarLayerFromAdmittedComponents False [(True, window)]
  result <- first LayerCoverageOverlayFailed (overlayLayers windowLayer layer)
  if V.any (isGapCell outside) (overlayResultCells result)
    then do
      gap <-
        first LayerCoverageGapPublicationFailed
          ( overlaySelectedRegion
              (\(insideWindow, label) ->
                 insideWindow && label == outside)
              result
          )
      Left (LayerCoverageGap (CoverGap gap))
    else Right ()

isGapCell :: Eq label => label -> OverlayCell Bool label -> Bool
isGapCell outside cell =
  overlayCellLeft cell
    && overlayCellRight cell == outside
    && case overlayCellGeometry cell of
      BoundedOverlayCell _ -> True
      UnboundedOverlayCell _ -> False

overlayClosedUnion
  :: (Ord leftLabel, Ord rightLabel)
  => (leftLabel -> Bool)
  -> (rightLabel -> Bool)
  -> OverlayResult leftLabel rightLabel
  -> Either OverlaySelectionError ExactCellSet
overlayClosedUnion selectLeft selectRight =
  selectClosedCells
    ClosedUnionSelection
    (\support -> supportAny selectLeft (overlaySupportLeft support) || supportAny selectRight (overlaySupportRight support))
    (\leftLabel rightLabel -> selectLeft leftLabel || selectRight rightLabel)

overlayClosedIntersection
  :: (Ord leftLabel, Ord rightLabel)
  => (leftLabel -> Bool)
  -> (rightLabel -> Bool)
  -> OverlayResult leftLabel rightLabel
  -> Either OverlaySelectionError ExactCellSet
overlayClosedIntersection selectLeft selectRight =
  selectClosedCells
    ClosedIntersectionSelection
    (\support -> supportAny selectLeft (overlaySupportLeft support) && supportAny selectRight (overlaySupportRight support))
    (\leftLabel rightLabel -> selectLeft leftLabel && selectRight rightLabel)

overlayRegularizedDifference
  :: (leftLabel -> Bool)
  -> (rightLabel -> Bool)
  -> OverlayResult leftLabel rightLabel
  -> Either OverlaySelectionError ExactCellSet
overlayRegularizedDifference selectLeft selectRight result =
  let outsidePair = overlayResultOutsideLabels result
      selectFace leftLabel rightLabel = selectLeft leftLabel && not (selectRight rightLabel)
   in if uncurry selectFace outsidePair
        then Left (OverlaySelectionContainsUnboundedCell RegularizedDifferenceSelection)
        else closeSelectedCells [] [] selectFace result

selectClosedCells
  :: (Ord leftLabel, Ord rightLabel)
  => OverlaySelectionKind
  -> (OverlayCellSupport leftLabel rightLabel -> Bool)
  -> (leftLabel -> rightLabel -> Bool)
  -> OverlayResult leftLabel rightLabel
  -> Either OverlaySelectionError ExactCellSet
selectClosedCells selectionKind selectSupport selectFace result =
  let outsidePair = overlayResultOutsideLabels result
      triangulation = overlayResultTriangulation result
   in if uncurry selectFace outsidePair
        then Left (OverlaySelectionContainsUnboundedCell selectionKind)
        else do
          selectedVertices <-
            filterM
              ( fmap selectSupport
                  . first OverlaySelectionProvenance
                  . vertexSupport result
              )
              (vertices triangulation)
          selectedEdges <-
            filterM
              (\edge ->
                 case Dcel.undirectedEdgeData triangulation edge of
                   OverlayDiagonal -> Right False
                   OverlayBoundary _ ->
                     selectSupport
                       <$> first OverlaySelectionProvenance (edgeSupport result edge))
              (undirectedEdges triangulation)
          closeSelectedCells selectedVertices selectedEdges selectFace result

closeSelectedCells
  :: [VertexId]
  -> [UndirectedEdgeId]
  -> (leftLabel -> rightLabel -> Bool)
  -> OverlayResult leftLabel rightLabel
  -> Either OverlaySelectionError ExactCellSet
closeSelectedCells selectedVertices selectedEdges selectFace result =
  let triangulation = overlayResultTriangulation result
      exactPointAt :: VertexId -> Either CellSelectionError ExactPoint
      exactPointAt vertex = Right (overlayExactPoint (vertexData triangulation vertex))
   in do
        selectedFaces <-
          filterM
            (\face ->
               if faceCarriesExactArea result face
                 then
                   fmap (uncurry selectFace)
                     ( first OverlaySelectionProvenance
                         (faceLabels result face)
                     )
                 else Right False)
            (innerFaces triangulation)
        first OverlaySelectionInvalid
          ( closeExactCellSetWith
              exactPointAt
              triangulation
              selectedVertices
              selectedEdges
              selectedFaces
          )

supportAny :: (label -> Bool) -> OverlaySupport label -> Bool
supportAny predicate = any predicate . overlaySupportLabels