packages feed

moonlight-triangulation-1.0.1.0: src-build/Moonlight/Triangulation/SetAlgebra.hs

{-# LANGUAGE DataKinds #-}

-- | Finite-set operations on unconstrained meshes. Every
-- constructing operation returns the finite-arena obstruction instead of
-- laundering it through a partial class instance.
module Moonlight.Triangulation.SetAlgebra
  ( siteRelation
  , union
  , unions
  , intersection
  , intersectionWith
  , difference
  , symmetricDifference
  ) where

import Data.Foldable (traverse_)
import Data.Maybe (isJust)
import qualified Data.Vector as V
import Moonlight.Triangulation.BulkLoad (empty)
import Moonlight.Triangulation.Dcel (numVertices, vertexData, vertexPoint, vertexPoints)
import Moonlight.Triangulation.Handles.Iterators.FixedIterators (vertices)
import Moonlight.Triangulation.Internal.Join (joinBalanced, joinNormalForm)
import Moonlight.Triangulation.Internal.Join.Rebuild (rebuildCanonicalSiteSet)
import Moonlight.Triangulation.Internal.Join.SiteSet
  ( siteSetDifference
  , siteSetFromTriangulation
  , siteSetIntersectionWith
  , siteSetPoints
  , siteRelationFromTriangulations
  , siteSetSymmetricDifferenceFromTriangulations
  , siteSupportFromTriangulation
  )
import Moonlight.Triangulation.Internal.Representation (Triangulation)
import Moonlight.Triangulation.Internal.Types
  ( BuildError (PointLocationFailed)
  , ConstraintMode (Unconstrained)
  , InsertionDisposition (..)
  , Point
  , SiteRelation (..)
  , unitElementDefaults
  )
import Moonlight.Triangulation.JoinSemilattice (JoinSemilattice)
import Moonlight.Triangulation.Session
  ( Session
  , insertVertexAt
  , refuse
  , removeAtNear
  , removeManyAt
  , withLocalSession
  )

-- | Exact relation between two triangulations' coordinate supports. Vertex
-- annotations and topology-element payloads are observations over the support;
-- none participates in this classification.
siteRelation
  :: Triangulation leftMode leftAnnotation leftDirected leftUndirected leftFace
  -> Triangulation rightMode rightAnnotation rightDirected rightUndirected rightFace
  -> SiteRelation
siteRelation left right =
  siteRelationFromTriangulations left right
{-# INLINE siteRelation #-}

-- | A valid Delaunay representative of both site sets. Use
-- 'Moonlight.Triangulation.Dcel.canonicalize'
-- when construction-independent dense numbering is required.
union
  :: JoinSemilattice annotation
  => Triangulation 'Unconstrained annotation () () ()
  -> Triangulation 'Unconstrained annotation () () ()
  -> Either BuildError (Triangulation 'Unconstrained annotation () () ())
union = joinNormalForm

-- | @union@ over a list, folded as a balanced tournament.
unions
  :: JoinSemilattice annotation
  => [Triangulation 'Unconstrained annotation () () ()]
  -> Either BuildError (Triangulation 'Unconstrained annotation () () ())
unions = joinBalanced

-- | The sites both meshes hold.
intersection
  :: Triangulation 'Unconstrained () () () ()
  -> Triangulation 'Unconstrained () () () ()
  -> Either BuildError (Triangulation 'Unconstrained () () () ())
intersection left right
  | numVertices left == 0 = Right left
  | numVertices right == 0 = Right right
  | otherwise =
      case siteRelationFromTriangulations left right of
        EqualSites -> Right left
        LeftProperSubset -> Right left
        RightProperSubset -> Right right
        DisjointSites -> Right (empty unitElementDefaults)
        PartialOverlap overlap -> intersectPartialOverlap overlap
 where
  leftSites = siteSupportFromTriangulation left
  rightSites = siteSupportFromTriangulation right
  rebuildIntersection = intersectionWith (\_ _ -> ()) left right
  intersectPartialOverlap overlap
    | leftRemoved <= rightRemoved
    , removalDeltaIsEligible leftRemoved overlap =
        removeExpectedFrom left (siteSetPoints (siteSetDifference leftSites rightSites))
    | rightRemoved < leftRemoved
    , removalDeltaIsEligible rightRemoved overlap =
        removeExpectedFrom right (siteSetPoints (siteSetDifference rightSites leftSites))
    | otherwise = rebuildIntersection
   where
    leftRemoved = numVertices left - overlap
    rightRemoved = numVertices right - overlap
{-# INLINE intersection #-}

-- | The sites both meshes hold, with the result annotation computed from the
-- left and right annotations at that exact coordinate. The combiner is called
-- only for shared sites, in left-then-right order; geometry remains the sole
-- authority for membership. @intersectionWith const left mask@ is the
-- annotation-preserving restriction of @left@ to @mask@'s support.
intersectionWith
  :: (leftAnnotation -> rightAnnotation -> annotation)
  -> Triangulation 'Unconstrained leftAnnotation () () ()
  -> Triangulation 'Unconstrained rightAnnotation () () ()
  -> Either BuildError (Triangulation 'Unconstrained annotation () () ())
intersectionWith combine left right =
  rebuildCanonicalSiteSet
    ( siteSetIntersectionWith
        combine
        (siteSetFromTriangulation left)
        (siteSetFromTriangulation right)
    )
{-# INLINE intersectionWith #-}

-- | The left's sites, less the right's.
difference
  :: Triangulation 'Unconstrained leftAnnotation () () ()
  -> Triangulation 'Unconstrained rightAnnotation () () ()
  -> Either BuildError (Triangulation 'Unconstrained leftAnnotation () () ())
difference left right
  | numVertices left == 0 || numVertices right == 0 = Right left
  | numVertices right < numVertices left
  , removalDeltaIsEligible (numVertices right) (numVertices left - numVertices right) =
      removeAvailableFrom left (vertexPoints right)
  | otherwise =
      rebuildCanonicalSiteSet
        ( siteSetDifference
            (siteSetFromTriangulation left)
            (siteSupportFromTriangulation right)
        )
{-# INLINE difference #-}

-- | The sites exactly one mesh holds.
symmetricDifference
  :: Triangulation 'Unconstrained annotation () () ()
  -> Triangulation 'Unconstrained annotation () () ()
  -> Either BuildError (Triangulation 'Unconstrained annotation () () ())
symmetricDifference left right
  | numVertices left == 0 = Right right
  | numVertices right == 0 = Right left
  | numVertices right < numVertices left
  , toggleDeltaIsEligible (numVertices right) (numVertices left - numVertices right) =
      toggleIncoming left right
  | numVertices left < numVertices right
  , toggleDeltaIsEligible (numVertices left) (numVertices right - numVertices left) =
      toggleIncoming right left
  | otherwise =
      siteSetSymmetricDifferenceFromTriangulations left right
        >>= rebuildCanonicalSiteSet
{-# INLINE symmetricDifference #-}

removalDeltaIsEligible :: Int -> Int -> Bool
removalDeltaIsEligible removed survivors = removed <= survivors `quot` 128
{-# INLINE removalDeltaIsEligible #-}

toggleDeltaIsEligible :: Int -> Int -> Bool
toggleDeltaIsEligible incoming residentRemainder = incoming <= residentRemainder `quot` 128
{-# INLINE toggleDeltaIsEligible #-}

removeAvailableFrom
  :: Triangulation 'Unconstrained annotation () () ()
  -> V.Vector (Point)
  -> Either BuildError (Triangulation 'Unconstrained annotation () () ())
removeAvailableFrom triangulation points =
  fmap published
    (withLocalSession triangulation 0 (V.any isJust <$> removeManyAt points))
 where
  published (removed, revised, _) = if removed then revised else triangulation
{-# INLINE removeAvailableFrom #-}

removeExpectedFrom
  :: Triangulation 'Unconstrained annotation () () ()
  -> V.Vector (Point)
  -> Either BuildError (Triangulation 'Unconstrained annotation () () ())
removeExpectedFrom triangulation points =
  fmap (\(_, revised, _) -> revised)
    (withLocalSession triangulation 0 (removeExpectedPoints points))
{-# INLINE removeExpectedFrom #-}

removeExpectedPoints
  :: V.Vector (Point)
  -> Session state annotation () () () ()
removeExpectedPoints points = do
  outcomes <- removeManyAt points
  V.zipWithM_
    (\point outcome -> maybe (refuse (PointLocationFailed point)) (const (pure ())) outcome)
    points
    outcomes
{-# INLINE removeExpectedPoints #-}

toggleIncoming
  :: Triangulation 'Unconstrained annotation () () ()
  -> Triangulation 'Unconstrained annotation () () ()
  -> Either BuildError (Triangulation 'Unconstrained annotation () () ())
toggleIncoming base incoming =
  fmap (\(_, revised, _) -> revised)
    (withLocalSession base (numVertices incoming) (toggleIncomingVertices incoming))
{-# INLINE toggleIncoming #-}

toggleIncomingVertices
  :: Triangulation 'Unconstrained annotation () () ()
  -> Session state annotation () () () ()
toggleIncomingVertices incoming =
  traverse_
    (\vertex -> do
      let point = vertexPoint incoming vertex
          annotation = vertexData incoming vertex
      (fresh, disposition) <- insertVertexAt point annotation
      case disposition of
        Inserted -> pure ()
        AlreadyPresent -> do
          outcome <- removeAtNear fresh point
          maybe (refuse (PointLocationFailed point)) (const (pure ())) outcome
    )
    (vertices incoming)
{-# INLINE toggleIncomingVertices #-}