packages feed

moonlight-triangulation-0.1.0.0: src-build/Moonlight/Triangulation/Internal/Join/SiteSet.hs

{-# LANGUAGE BangPatterns #-}
{-# LANGUAGE ScopedTypeVariables #-}

-- | Exact transient site sections derived from the authoritative coordinate
-- planes. This is the sole owner of coordinate-set classification for joins,
-- set algebra, and constrained union. Payloads travel as annotations; they do
-- not participate in site identity.
module Moonlight.Triangulation.Internal.Join.SiteSet
  ( SiteSet
  , siteSetFromTriangulation
  , siteSupportFromTriangulation
  , siteSetSize
  , siteSetRelation
  , siteRelationFromTriangulations
  , siteSetUnionWith
  , siteSetIntersectionWith
  , siteSetDifference
  , siteSetSymmetricDifferenceFromTriangulations
  , siteSetAssocs
  , siteSetPoints
  ) where

import Control.Monad.ST (ST, runST)
import qualified Data.Map.Strict as Map
import qualified Data.Map.Merge.Strict as MapMerge
import Data.Functor.Const (Const (..))
import Data.Maybe (isJust)
import qualified Data.Vector as V
import qualified Data.Vector.Unboxed as U
import qualified Data.Vector.Unboxed.Mutable as MUV
import Moonlight.Triangulation.Dcel (numVertices, vertexData, vertexPoint)
import Moonlight.Triangulation.Handles.HandleDefs (VertexId (..))
import Moonlight.Triangulation.Handles.Iterators.FixedIterators
  ( foldVertices'
  )
import Moonlight.Triangulation.Internal.Paged
  ( pagedUnsafeIndex
  , toVector
  )
import Moonlight.Triangulation.Internal.PointIndex
  ( MutablePointIndex
  , lookupMutablePoint
  , lookupPointIndex
  , newMutablePointIndex
  , seedMutablePointIndex
  )
import Moonlight.Triangulation.Internal.Representation (Triangulation (..))
import Moonlight.Triangulation.Internal.Types
  ( BuildError
  , Point (..)
  , SiteRelation (..)
  )

newtype SiteSet annotation = SiteSet (Map.Map (Point) annotation)

-- Strict sufficient statistics for the one-pass ordered-map descent used by
-- 'siteSetRelation'. Keeping the census strict prevents a relation query from
-- replacing an intermediate map allocation with a chain of monoidal thunks.
data SiteRelationCensus = SiteRelationCensus !Int !Int !Int

instance Semigroup SiteRelationCensus where
  SiteRelationCensus leftA rightA overlapA <> SiteRelationCensus leftB rightB overlapB =
    SiteRelationCensus
      (leftA + leftB)
      (rightA + rightB)
      (overlapA + overlapB)

instance Monoid SiteRelationCensus where
  mempty = SiteRelationCensus 0 0 0

siteSetFromTriangulation
  :: Triangulation mode annotation directed undirected face
  -> SiteSet annotation
siteSetFromTriangulation triangulation =
  SiteSet
    ( Map.fromList
        [ ( Point
              (triPointX triangulation `pagedUnsafeIndex` vertex)
              (triPointY triangulation `pagedUnsafeIndex` vertex)
          , vertexData triangulation (VertexId (fromIntegral vertex))
          )
        | vertex <- [0 .. numVertices triangulation - 1]
        ]
    )

-- | Coordinate support without touching the boxed annotation plane. Order and
-- set identity are geometric observations; callers that discard annotations
-- should not pay a boxed-page read per vertex merely to manufacture ignored
-- map values.
siteSupportFromTriangulation
  :: Triangulation mode vertex directed undirected face
  -> SiteSet ()
siteSupportFromTriangulation triangulation =
  SiteSet
    ( Map.fromList
        [ ( Point
              (triPointX triangulation `pagedUnsafeIndex` vertex)
              (triPointY triangulation `pagedUnsafeIndex` vertex)
          , ()
          )
        | vertex <- [0 .. numVertices triangulation - 1]
        ]
    )
{-# INLINE siteSupportFromTriangulation #-}

siteSetSize :: SiteSet annotation -> Int
siteSetSize (SiteSet sites) = Map.size sites
{-# INLINE siteSetSize #-}

siteSetRelation
  :: SiteSet leftAnnotation
  -> SiteSet rightAnnotation
  -> SiteRelation
siteSetRelation (SiteSet left) (SiteSet right) =
  siteRelationFromCardinalities
    (leftOnly + overlap)
    (rightOnly + overlap)
    overlap
 where
  SiteRelationCensus leftOnly rightOnly overlap =
    getConst
      ( MapMerge.mergeA
          (MapMerge.traverseMissing (\_ _ -> Const (SiteRelationCensus 1 0 0)))
          (MapMerge.traverseMissing (\_ _ -> Const (SiteRelationCensus 0 1 0)))
          (MapMerge.zipWithAMatched (\_ _ _ -> Const (SiteRelationCensus 0 0 1)))
          left
          right
      )
{-# INLINE siteSetRelation #-}

siteRelationFromTriangulations
  :: Triangulation leftMode leftAnnotation leftDirected leftUndirected leftFace
  -> Triangulation rightMode rightAnnotation rightDirected rightUndirected rightFace
  -> SiteRelation
siteRelationFromTriangulations left right =
  siteRelationFromCardinalities leftCount rightCount overlap
 where
  !leftCount = numVertices left
  !rightCount = numVertices right
  !overlap
    | leftCount <= rightCount = exactOverlapCount right left
    | otherwise = exactOverlapCount left right
{-# INLINE siteRelationFromTriangulations #-}

siteRelationFromCardinalities :: Int -> Int -> Int -> SiteRelation
siteRelationFromCardinalities leftCount rightCount overlap
  | leftCount == rightCount && overlap == leftCount = EqualSites
  | overlap == leftCount = LeftProperSubset
  | overlap == rightCount = RightProperSubset
  | overlap == 0 = DisjointSites
  | otherwise = PartialOverlap overlap
{-# INLINE siteRelationFromCardinalities #-}

exactOverlapCount
  :: Triangulation sourceMode sourceAnnotation sourceDirected sourceUndirected sourceFace
  -> Triangulation indexedMode indexedAnnotation indexedDirected indexedUndirected indexedFace
  -> Int
exactOverlapCount source indexed =
  either
    (const (persistentExactOverlapCount source indexed))
    id
    (transientExactOverlapCount source indexed)
{-# INLINE exactOverlapCount #-}

transientExactOverlapCount
  :: Triangulation sourceMode sourceAnnotation sourceDirected sourceUndirected sourceFace
  -> Triangulation indexedMode indexedAnnotation indexedDirected indexedUndirected indexedFace
  -> Either BuildError Int
transientExactOverlapCount source indexed = runST $ do
  pointIndex <- newMutablePointIndex (numVertices indexed)
  seeded <-
    seedMutablePointIndex
      pointIndex
      (numVertices indexed)
      (readCoordinateX indexed)
      (readCoordinateY indexed)
  case seeded of
    Left failure -> pure (Left failure)
    Right () ->
      fmap Right
        ( U.ifoldM'
            (\count vertex x -> do
               let y = triPointY source `pagedUnsafeIndex` vertex
               match <-
                 lookupMutablePoint
                   pointIndex
                   (readCoordinateX indexed)
                   (readCoordinateY indexed)
                   x
                   y
               pure (if isJust match then count + 1 else count)
            )
            0
            (toVector (triPointX source))
        )
{-# INLINE transientExactOverlapCount #-}

persistentExactOverlapCount
  :: Triangulation sourceMode sourceAnnotation sourceDirected sourceUndirected sourceFace
  -> Triangulation indexedMode indexedAnnotation indexedDirected indexedUndirected indexedFace
  -> Int
persistentExactOverlapCount source indexed =
  foldVertices'
    source
    (\count vertex ->
       if pointOccursIn indexed (vertexPoint source vertex)
         then count + 1
         else count
    )
    0
{-# INLINE persistentExactOverlapCount #-}

siteSetUnionWith
  :: (annotation -> annotation -> annotation)
  -> SiteSet annotation
  -> SiteSet annotation
  -> SiteSet annotation
siteSetUnionWith combine (SiteSet left) (SiteSet right) =
  SiteSet (Map.unionWith combine left right)
{-# INLINE siteSetUnionWith #-}

siteSetIntersectionWith
  :: (leftAnnotation -> rightAnnotation -> annotation)
  -> SiteSet leftAnnotation
  -> SiteSet rightAnnotation
  -> SiteSet annotation
siteSetIntersectionWith combine (SiteSet left) (SiteSet right) =
  SiteSet (Map.intersectionWith combine left right)
{-# INLINE siteSetIntersectionWith #-}

siteSetDifference
  :: SiteSet annotation
  -> SiteSet other
  -> SiteSet annotation
siteSetDifference (SiteSet left) (SiteSet right) = SiteSet (Map.difference left right)
{-# INLINE siteSetDifference #-}

siteSetSymmetricDifferenceFromTriangulations
  :: Triangulation leftMode annotation leftDirected leftUndirected leftFace
  -> Triangulation rightMode annotation rightDirected rightUndirected rightFace
  -> Either BuildError (SiteSet annotation)
siteSetSymmetricDifferenceFromTriangulations left right
  | numVertices left >= numVertices right = indexedSymmetricDifference left right
  | otherwise = indexedSymmetricDifference right left
{-# INLINE siteSetSymmetricDifferenceFromTriangulations #-}

indexedSymmetricDifference
  :: forall sourceMode annotation sourceDirected sourceUndirected sourceFace
            indexedMode indexedDirected indexedUndirected indexedFace
  . Triangulation sourceMode annotation sourceDirected sourceUndirected sourceFace
  -> Triangulation indexedMode annotation indexedDirected indexedUndirected indexedFace
  -> Either BuildError (SiteSet annotation)
indexedSymmetricDifference source indexed =
  fmap (SiteSet . Map.fromList) (runST collectExclusiveAssociations)
 where
  collectExclusiveAssociations
    :: forall state. ST state (Either BuildError [(Point, annotation)])
  collectExclusiveAssociations = do
    pointIndex <- newMutablePointIndex (numVertices indexed)
    seeded <-
      seedMutablePointIndex
        pointIndex
        (numVertices indexed)
        (readCoordinateX indexed)
        (readCoordinateY indexed)
    case seeded of
      Left failure -> pure (Left failure)
      Right () -> do
        matchedIndexedVertices <- MUV.replicate (numVertices indexed) False
        sourceExclusive <-
          U.ifoldM'
            (collectSourceExclusive pointIndex matchedIndexedVertices)
            []
            (toVector (triPointX source))
        indexedExclusive <-
          U.ifoldM'
            (collectIndexedExclusive matchedIndexedVertices)
            []
            (toVector (triPointX indexed))
        pure (Right (sourceExclusive <> indexedExclusive))
   where
    collectSourceExclusive
      :: MutablePointIndex state
      -> MUV.MVector state Bool
      -> [(Point, annotation)]
      -> Int
      -> Double
      -> ST state [(Point, annotation)]
    collectSourceExclusive pointIndex matchedIndexedVertices associations rawVertex x = do
      let vertex = VertexId (fromIntegral rawVertex)
          y = triPointY source `pagedUnsafeIndex` rawVertex
          point = Point x y
      match <-
        lookupMutablePoint
          pointIndex
          (readCoordinateX indexed)
          (readCoordinateY indexed)
          x
          y
      case match of
        Nothing -> pure ((point, vertexData source vertex) : associations)
        Just indexedVertex -> do
          MUV.unsafeWrite matchedIndexedVertices indexedVertex True
          pure associations

    collectIndexedExclusive
      :: MUV.MVector state Bool
      -> [(Point, annotation)]
      -> Int
      -> Double
      -> ST state [(Point, annotation)]
    collectIndexedExclusive matchedIndexedVertices associations rawVertex x = do
      let vertex = VertexId (fromIntegral rawVertex)
          point = Point x (triPointY indexed `pagedUnsafeIndex` rawVertex)
      matched <- MUV.unsafeRead matchedIndexedVertices rawVertex
      pure
        ( if matched
            then associations
            else (point, vertexData indexed vertex) : associations
        )
{-# INLINE indexedSymmetricDifference #-}

readCoordinateX
  :: Triangulation mode annotation directed undirected face
  -> Int
  -> ST state Double
readCoordinateX triangulation vertex =
  pure (triPointX triangulation `pagedUnsafeIndex` vertex)
{-# INLINE readCoordinateX #-}

readCoordinateY
  :: Triangulation mode annotation directed undirected face
  -> Int
  -> ST state Double
readCoordinateY triangulation vertex =
  pure (triPointY triangulation `pagedUnsafeIndex` vertex)
{-# INLINE readCoordinateY #-}

pointOccursIn
  :: Triangulation mode annotation directed undirected face
  -> Point
  -> Bool
pointOccursIn triangulation = isJust . lookupPointIn triangulation
{-# INLINE pointOccursIn #-}

lookupPointIn
  :: Triangulation mode annotation directed undirected face
  -> Point
  -> Maybe Int
lookupPointIn triangulation =
  lookupPointIndex
    (triPointX triangulation)
    (triPointY triangulation)
    (triPointIndex triangulation)
{-# INLINE lookupPointIn #-}

siteSetAssocs :: SiteSet annotation -> [(Point, annotation)]
siteSetAssocs (SiteSet sites) = Map.toAscList sites
{-# INLINE siteSetAssocs #-}

siteSetPoints :: SiteSet annotation -> V.Vector (Point)
siteSetPoints (SiteSet sites) = V.fromList (Map.keys sites)
{-# INLINE siteSetPoints #-}