packages feed

moonlight-triangulation-1.4.0.1: src-build/Moonlight/Triangulation/Refinement.hs

{-# LANGUAGE NamedFieldPuns #-}
{-# LANGUAGE ScopedTypeVariables #-}

-- | Ruppert refinement: Steiner insertion composed after a built mesh, never a
-- second kind of mesh. Parameters are reached through checked verbs rather than
-- a raw record, so an unrealizable quality bar is a refusal.
module Moonlight.Triangulation.Refinement
  ( refine
  , refineWithinDomain
  , validateRefinementParameters
  , withMinimumAngle
  , radiusEdgeRatioForAngle
  ) where

import Control.Monad (void)
import Control.Monad.ST (ST)
import Data.Foldable (traverse_)
import qualified Data.IntSet as IntSet
import Data.List (sort)
import Data.Maybe (fromMaybe)
import qualified Data.Set as Set
import qualified Data.Vector as V
import Moonlight.Triangulation.Dcel
  ( faceDirectedEdges
  , faceVertices
  , incidentFace
  , isConstraintEdge
  , numFaces
  , numUndirectedEdges
  , numVertices
  , undirectedEndpoints
  , vertexPoint
  )
import Moonlight.Triangulation.FloodFillIterator (facesAtEvenBarrierDepth)
import Moonlight.Triangulation.Handles.HandleDefs
  ( FaceId (..)
  , DirectedEdgeId
  , UndirectedEdgeId (..)
  , asUndirected
  , directedPair
  , reverseEdge
  )
import Moonlight.Triangulation.Handles.Iterators.FixedIterators (innerFaces)
import Moonlight.Triangulation.Internal.Mutable
import Moonlight.Triangulation.Internal.Paged
  ( PublicationStats
  , TransactionShape (DenseTransaction, LocalTransaction)
  )
import Moonlight.Triangulation.Internal.Refinement
import Moonlight.Triangulation.Internal.Representation (Triangulation (..))
import Moonlight.Triangulation.Internal.Transaction (runTransactionWithPublication)
import Moonlight.Triangulation.Math (squaredDistanceWide)
import Moonlight.Triangulation.Types
import Moonlight.Triangulation.Validation
  ( validateTopology
  , validateTopologyClosureWithStats
  )

data RefinementExecution mode vertex directed undirected face = RefinementExecution
  { refinementExecutionResult :: !(RefinementResult mode vertex directed undirected face)
  , refinementExecutionVisitedFaces :: ![Int]
  , refinementExecutionPermittedFaces :: ![Int]
  , refinementExecutionInterfaceBoundaryReads :: {-# UNPACK #-} !Int
  , refinementExecutionBoundaryCrossingAttempts :: {-# UNPACK #-} !Int
  , refinementExecutionPublicationStats :: !PublicationStats
  }

-- | The radius-edge ratio admitting a minimum angle, in degrees.
radiusEdgeRatioForAngle :: Double -> Either BuildError (Maybe Double)
radiusEdgeRatioForAngle degrees =
  case classifyNonFinite degrees of
    Just nonFinite ->
      Left (RefinementMinimumAngleNotFinite nonFinite)
    Nothing
      | degrees < 0 || degrees > 60 ->
          Left (RefinementMinimumAngleOutOfRange degrees)
      | degrees == 0 -> Right Nothing
      | otherwise ->
          let ratio = 0.5 / sin (degrees * pi / 180)
           in case classifyNonFinite ratio of
                Just nonFinite ->
                  Left (RefinementMinimumAngleDerivedRatioNotFinite nonFinite)
                Nothing -> Right (Just ratio)

-- | Set the minimum angle, in degrees.
withMinimumAngle :: Double -> RefinementParameters -> Either BuildError (RefinementParameters)
withMinimumAngle degrees parameters = do
  ratio <- radiusEdgeRatioForAngle degrees
  pure parameters{refineMaxRadiusEdgeRatio = ratio}

-- | Refine an unconstrained or constrained triangulation in the same finite
-- DCEL. The constructor supplies application payloads for Steiner vertices;
-- payloads annotate the requested geometric point and cannot reauthor it.
refine
  :: (Point -> vertex)
  -> RefinementParameters
  -> Triangulation mode vertex directed undirected face
  -> Either BuildError (RefinementResult mode vertex directed undirected face)
refine makeVertex parameters =
  fmap refinementExecutionResult
    . refineWithInitialSeed RefineEveryFace Nothing makeVertex parameters

-- | Prove that a set of active faces is separated from every protected inner
-- face by exactly the supplied immutable edge section. The witness is tied to
-- the input arena cardinalities and is revalidated before interpretation.
mkRefinementDomain
  :: Set.Set FaceId
  -> Set.Set UndirectedEdgeId
  -> Triangulation mode vertex directed undirected face
  -> Either BuildError RefinementDomain
mkRefinementDomain permittedFaces interfaceEdges triangulation = do
  permitted <- validateSeedFaces triangulation permittedFaces
  interface <- IntSet.fromList <$> traverse validateInterfaceEdge (Set.toAscList interfaceEdges)
  let expected = expectedInterface permitted
  case IntSet.lookupMin (expected IntSet.\\ interface) of
    Just pair -> Left (RefinementDomainInterfaceMissing (UndirectedEdgeId (fromIntegral pair)))
    Nothing ->
      case IntSet.lookupMin (interface IntSet.\\ expected) of
        Just pair -> Left (RefinementDomainInterfaceExtraneous (UndirectedEdgeId (fromIntegral pair)))
        Nothing ->
          Right
            RefinementDomain
              { refinementDomainPermittedFaces = permitted
              , refinementDomainInterfacePairs = interface
              , refinementDomainInputFaceCount = numFaces triangulation
              }
 where
  totalEdges = numUndirectedEdges triangulation
  validateInterfaceEdge :: UndirectedEdgeId -> Either BuildError Int
  validateInterfaceEdge edge@(UndirectedEdgeId raw)
    | toInteger raw >= toInteger totalEdges =
        Left (RefinementDomainInterfaceEdgeNotActive edge totalEdges)
    | otherwise = Right (fromIntegral raw)

  expectedInterface permitted =
    IntSet.fromList
      [ pair
      | rawFace <- IntSet.toAscList permitted
      , edge <- faceDirectedEdges triangulation (FaceId (fromIntegral rawFace))
      , let pair = fromIntegral (unUndirected (asUndirected edge))
      , let adjacent = incidentFace triangulation (reverseEdge edge)
      , adjacent /= FaceId 0
      , IntSet.notMember (fromIntegral (unFace adjacent)) permitted
      ]

  unUndirected (UndirectedEdgeId raw) = raw
  unFace (FaceId raw) = raw

-- | Refine exactly one checked local section. Interface edges are installed as
-- transaction-local legalization barriers and removed before publication;
-- the receipt and collar proof inspect only the admitted local closure.
refineWithinDomain
  :: forall mode vertex directed undirected face.
     (Point -> vertex)
  -> RefinementParameters
  -> Set.Set FaceId
  -> Set.Set UndirectedEdgeId
  -> Triangulation mode vertex directed undirected face
  -> Either BuildError (RefinementDomainResult mode vertex directed undirected face)
refineWithinDomain makeVertex parameters permittedFaces interfaceEdges triangulation = do
  validateDomainParameters parameters
  domain <- mkRefinementDomain permittedFaces interfaceEdges triangulation
  execution <-
    refineWithInitialSeed
      (RefineSeededFaces (refinementDomainPermittedFaces domain))
      (Just domain)
      makeVertex
      parameters
      triangulation
  let result = refinementExecutionResult execution
  validationStats <- validateDomainClosure
    domain
    triangulation
    (refinedTriangulation result)
    (refinementExecutionPermittedFaces execution)
  let receipt =
        buildRefinementReceipt
          domain
          triangulation
          (refinedTriangulation result)
          (refinementExecutionVisitedFaces execution)
          (refinementExecutionPermittedFaces execution)
          (refinementExecutionInterfaceBoundaryReads execution)
          (refinementExecutionBoundaryCrossingAttempts execution)
          (refinementExecutionPublicationStats execution)
          validationStats
  case V.toList (refinementVisitedProtectedFaces receipt) of
    protected : _ -> Left (RefinementDomainWouldRewriteProtectedFace protected)
    [] ->
       let restoredResult :: RefinementResult mode vertex directed undirected face
           restoredResult =
            result
              { refinedTriangulation =
                  (refinedTriangulation result)
                    -- The checked local interpreter rejects every hull-pair
                    -- split, so the cached outer cycle remains an exact
                    -- handle witness and can be restored without a scan.
                    { triSeamFrontier = triSeamFrontier triangulation
                    }
              }
       in Right
        RefinementDomainResult
          { refinementDomainResult = restoredResult
          , refinementDomainReceipt = receipt
          }

validateDomainParameters :: RefinementParameters -> Either BuildError ()
validateDomainParameters parameters
  | not (refinePreserveConvexHull parameters) =
      Left RefinementDomainRequiresConvexHullPreservation
  | not (refineKeepConstraintEdges parameters) =
      Left RefinementDomainRequiresConstraintPreservation
  | refineExcludeOuterFaces parameters =
      Left RefinementDomainForbidsOuterFaceExclusion
  | refineMaxAdditionalVertices parameters == Nothing =
      Left RefinementDomainRequiresFiniteVertexBudget
  | otherwise = Right ()

refineWithInitialSeed
  :: RefinementInitialSeed
  -> Maybe RefinementDomain
  -> (Point -> vertex)
  -> RefinementParameters
  -> Triangulation mode vertex directed undirected face
  -> Either BuildError (RefinementExecution mode vertex directed undirected face)
refineWithInitialSeed initialSeed domain makeVertex parameters triangulation = do
  validateRefinementParameters parameters
  case domain of
    Just _
      | refineMaxAdditionalVertices parameters == Nothing ->
          Left RefinementDomainRequiresFiniteVertexBudget
    _ -> Right ()
  case domain of
    Just _ -> pure ()
    Nothing ->
      case validateTopology triangulation of
        violation : _ -> Left (RefinementInputTopologyInvalid violation)
        [] -> pure ()
  let originalCount = numVertices triangulation
      budget =
        case domain of
          Just _ -> max 0 (fromMaybe 0 (refineMaxAdditionalVertices parameters))
          Nothing -> max 0 (fromMaybe (10 * max 1 originalCount) (refineMaxAdditionalVertices parameters))
      initialExcludedFaces =
        if domain == Nothing && refineExcludeOuterFaces parameters
          then
            IntSet.fromList
              [ fromIntegral raw
              | FaceId raw <-
                  facesAtEvenBarrierDepth
                    triangulation
                    (isConstraintEdge triangulation)
              ]
          else IntSet.empty
  ( outcome
    , frozen
    , stats
    , publicationStats
    ) <-
    runTransactionWithPublication id (transactionShape domain) triangulation budget $ \mutable operation -> do
      installInterfaceBarriers mutable domain
      refinement <-
        refineMutable
          makeVertex
          mutable
          operation
          parameters
          budget
          initialExcludedFaces
          initialSeed
          domain
      removeInterfaceBarriers mutable triangulation domain
      pure refinement
  let (complete, added, excluded, visited, permittedFaces, interfaceBoundaryReads, boundaryCrossingAttempts) = outcome
      excludedFaceSet = IntSet.fromList excluded
      finalAuditFaces =
        case domain of
          Nothing ->
            [ face
            | face@(FaceId raw) <- innerFaces frozen
            , IntSet.notMember (fromIntegral raw) excludedFaceSet
            ]
          Just _ -> fmap (FaceId . fromIntegral) permittedFaces
  if complete
    then validateMaximumEdgeLength parameters frozen finalAuditFaces
    else Right ()
  pure
    RefinementExecution
      { refinementExecutionResult =
          RefinementResult
            { refinedTriangulation = frozen
            , refinementStats = stats
            , refinementAddedVertices = added
            , refinementComplete = complete
            , refinementExcludedFaces = V.fromList (map (FaceId . fromIntegral) excluded)
            }
      , refinementExecutionVisitedFaces = visited
      , refinementExecutionPermittedFaces = permittedFaces
      , refinementExecutionInterfaceBoundaryReads = interfaceBoundaryReads
      , refinementExecutionBoundaryCrossingAttempts = boundaryCrossingAttempts
      , refinementExecutionPublicationStats = publicationStats
      }

transactionShape :: Maybe RefinementDomain -> TransactionShape
transactionShape = maybe DenseTransaction (const LocalTransaction)

installInterfaceBarriers
  :: MutableDcel s vertex directed undirected face
  -> Maybe RefinementDomain
  -> ST s ()
installInterfaceBarriers mutable =
  traverse_
    (\pair -> void (setConstraint mutable (2 * pair)))
    . maybe [] (IntSet.toAscList . refinementDomainInterfacePairs)

removeInterfaceBarriers
  :: MutableDcel s vertex directed undirected face
  -> Triangulation mode vertex directed undirected face
  -> Maybe RefinementDomain
  -> ST s ()
removeInterfaceBarriers mutable input =
  traverse_
    (\pair ->
       let edge = UndirectedEdgeId (fromIntegral pair)
        in if isConstraintEdge input edge
             then pure ()
             else void (clearConstraint mutable (2 * pair))
    )
    . maybe [] (IntSet.toAscList . refinementDomainInterfacePairs)

buildRefinementReceipt
  :: RefinementDomain
  -> Triangulation mode vertex directed undirected face
  -> Triangulation mode vertex directed undirected face
  -> [Int]
  -> [Int]
  -> Int
  -> Int
  -> PublicationStats
  -> ValidationClosureStats
  -> RefinementReceipt
buildRefinementReceipt domain before after visited permittedFaces interfaceBoundaryReads boundaryCrossingAttempts publicationStats validationStats =
  RefinementReceipt
    { refinementVisitedJoinFaces = V.fromList (fmap toFace visitedJoin)
    , refinementVisitedProtectedFaces = V.fromList (fmap toFace visitedProtected)
    , refinementCreatedFaces = V.fromList (fmap toFace createdFaces)
    , refinementFinalPermittedFaces = V.fromList (fmap toFace permittedFaces)
    , refinementTouchedEdges = V.fromList (fmap toEdge touchedEdges)
    , refinementRemovedEdges = V.fromList (fmap toEdge removedEdges)
    , refinementInterfaceBoundaryReads = interfaceBoundaryReads
    , refinementAttemptedBoundaryCrossings = boundaryCrossingAttempts
    , refinementPublicationStats = publicationStats
    , refinementValidationClosureStats = validationStats
    }
 where
  permitted = refinementDomainPermittedFaces domain
  (visitedProtected, visitedJoin) =
    foldr
      (\face (protected, join) ->
         if IntSet.member face permitted
           then (protected, face : join)
           else if face >= refinementDomainInputFaceCount domain
             then (protected, face : join)
           else (face : protected, join))
      ([], []) visited
  initialFaces = refinementDomainPermittedFaces domain
  finalFaces = IntSet.fromList permittedFaces
  faceClosure = IntSet.toAscList (IntSet.union initialFaces finalFaces)
  createdFaces =
    [ face
    | face <- faceClosure
    , localFaceSignature before face /= localFaceSignature after face
    ]
  initialEdges = localDomainEdges before initialFaces
  finalEdges = localDomainEdges after finalFaces
  edgeClosure = IntSet.toAscList (IntSet.union initialEdges finalEdges)
  touchedEdges =
    [ edge
    | edge <- edgeClosure
    , localEdgeSignature before edge /= localEdgeSignature after edge
    ]
  removedEdges =
    [ edge
    | edge <- IntSet.toAscList initialEdges
    , localEdgeSignature before edge /= localEdgeSignature after edge
    ]
  toFace = FaceId . fromIntegral
  toEdge = UndirectedEdgeId . fromIntegral

localFaceSignature
  :: Triangulation mode vertex directed undirected face
  -> Int
  -> Maybe [Point]
localFaceSignature triangulation raw
  | raw <= 0 || raw >= numFaces triangulation = Nothing
  | otherwise =
      Just
        ( sort
            (fmap (vertexPoint triangulation) (faceVertices triangulation (FaceId (fromIntegral raw))))
        )

localDomainEdges
  :: Triangulation mode vertex directed undirected face
  -> IntSet.IntSet
  -> IntSet.IntSet
localDomainEdges triangulation faces =
  IntSet.fromList
    [ fromIntegral (unUndirected (asUndirected edge))
    | rawFace <- IntSet.toAscList faces
    , edge <- faceDirectedEdges triangulation (FaceId (fromIntegral rawFace))
    ]
 where
  unUndirected (UndirectedEdgeId raw) = raw

localEdgeSignature
  :: Triangulation mode vertex directed undirected face
  -> Int
  -> Maybe (Point, Point, [FaceId])
localEdgeSignature triangulation raw
  | raw < 0 || raw >= numUndirectedEdges triangulation = Nothing
  | otherwise =
      let edge = UndirectedEdgeId (fromIntegral raw)
          (fromVertex, toVertex) = undirectedEndpoints triangulation edge
          fromPoint = vertexPoint triangulation fromVertex
          toPoint = vertexPoint triangulation toVertex
          (forward, backward) = directedPair edge
       in Just
            ( min fromPoint toPoint
            , max fromPoint toPoint
            , sort
                [ incidentFace triangulation forward
                , incidentFace triangulation backward
                ]
            )

validateDomainClosure
  :: RefinementDomain
  -> Triangulation mode vertex directed undirected face
  -> Triangulation mode vertex directed undirected face
  -> [Int]
  -> Either BuildError ValidationClosureStats
validateDomainClosure domain before after finalPermittedFaces = do
  let (closureStats, closureViolations) =
        validateTopologyClosureWithStats
          ( IntSet.union
              (refinementDomainPermittedFaces domain)
              (IntSet.fromList finalPermittedFaces)
          )
          (refinementDomainInterfacePairs domain)
          after
  case closureViolations of
    violation : _ -> Left (RefinementInputTopologyInvalid violation)
    [] -> pure ()
  traverse_ validateProtectedFace (IntSet.toAscList protectedFaces)
  traverse_ validateInterfaceEdge (IntSet.toAscList (refinementDomainInterfacePairs domain))
  pure closureStats
 where
  permitted = refinementDomainPermittedFaces domain
  protectedFaces =
    IntSet.fromList
      [ rawFace
      | rawPair <- IntSet.toAscList (refinementDomainInterfacePairs domain)
      , let edge = UndirectedEdgeId (fromIntegral rawPair)
      , let (forward, backward) = directedPair edge
      , rawFace <-
          [ fromIntegral (unFace (incidentFace before forward))
          , fromIntegral (unFace (incidentFace before backward))
          ]
      , rawFace > 0
      , IntSet.notMember rawFace permitted
      ]

  validateProtectedFace rawFace =
    if localFaceSignature before rawFace == localFaceSignature after rawFace
      then Right ()
      else Left (RefinementDomainProtectedFaceChanged (FaceId (fromIntegral rawFace)))

  validateInterfaceEdge rawPair =
    if interfaceEdgePreserved rawPair
      then Right ()
      else Left RefinementDomainTopologyChanged

  interfaceEdgePreserved rawPair =
    case (localEdgeSignature before rawPair, localEdgeSignature after rawPair) of
      ( Just (beforeFrom, beforeTo, beforeFaces)
        , Just (afterFrom, afterTo, afterFaces)
        ) ->
          beforeFrom == afterFrom
            && beforeTo == afterTo
            && all (`elem` afterFaces) (filter protected beforeFaces)
      _ -> False

  protected face =
    let raw = fromIntegral (unFace face)
     in raw > 0 && IntSet.notMember raw permitted

  unFace (FaceId raw) = raw

-- | A drained worklist is publishable only when its final active face section
-- satisfies the optional edge-length law. Global refinement checks every
-- active inner face; local refinement checks only its exact dynamic permitted
-- witness, never the retired handles from the input cover.
validateMaximumEdgeLength
  :: RefinementParameters
  -> Triangulation mode vertex directed undirected face
  -> [FaceId]
  -> Either BuildError ()
validateMaximumEdgeLength parameters triangulation finalPermittedFaces =
  case refineMaxEdgeLength parameters of
    Nothing -> Right ()
    Just maximumLength ->
      traverse_
        (validateFace maximumLength (maximumLength * maximumLength))
        finalPermittedFaces
 where
  validateFace :: Double -> Double -> FaceId -> Either BuildError ()
  validateFace maximumLength maximumSquaredLength face =
    traverse_ (validateEdge maximumLength maximumSquaredLength face)
      (faceDirectedEdges triangulation face)

  validateEdge :: Double -> Double -> FaceId -> DirectedEdgeId -> Either BuildError ()
  validateEdge maximumLength maximumSquaredLength face edge =
    let undirected = asUndirected edge
        (fromVertex, toVertex) = undirectedEndpoints triangulation undirected
        actualSquaredLength =
          squaredDistanceWide
            (vertexPoint triangulation fromVertex)
            (vertexPoint triangulation toVertex)
     in if actualSquaredLength > maximumSquaredLength
          then
            Left
              ( RefinementOversizedEdge
                  face
                  undirected
                  (sqrt actualSquaredLength)
                  maximumLength
              )
          else Right ()

-- | Refuse outer or absent faces rather than silently treating an invalid
-- topology witness as an empty repair. Face zero is the outer face and has no
-- active refinement equation.
validateSeedFaces
  :: Triangulation mode vertex directed undirected face
  -> Set.Set FaceId
  -> Either BuildError IntSet.IntSet
validateSeedFaces triangulation requested =
  IntSet.fromList <$> traverse validateFace (Set.toAscList requested)
 where
  totalFaces = numFaces triangulation

  validateFace :: FaceId -> Either BuildError Int
  validateFace face@(FaceId raw)
    | raw == 0 || toInteger raw >= toInteger totalFaces =
        Left (RefinementSeedFaceNotActive face totalFaces)
    | otherwise = Right (fromIntegral raw)

-- | Refuse parameters no mesh can satisfy.
validateRefinementParameters :: RefinementParameters -> Either BuildError ()
validateRefinementParameters RefinementParameters{refineMaxAdditionalVertices, refineMinArea, refineMaxArea, refineMaxRadiusEdgeRatio, refineMaxEdgeLength} = do
  case refineMaxAdditionalVertices of
    Just value
      | value < 0 ->
          Left (RefinementMaximumAdditionalVerticesNegative value)
    _ -> Right ()
  validateOptionalRefinementParameter
    RefinementMinimumAreaNotFinite
    RefinementMinimumAreaNegative
    (>= 0)
    refineMinArea
  validateOptionalRefinementParameter
    RefinementMaximumAreaNotFinite
    RefinementMaximumAreaNotPositive
    (> 0)
    refineMaxArea
  validateOptionalRefinementParameter
    RefinementMaximumRadiusEdgeRatioNotFinite
    RefinementMaximumRadiusEdgeRatioNotPositive
    (> 0)
    refineMaxRadiusEdgeRatio
  validateOptionalRefinementParameter
    RefinementMaximumEdgeLengthNotFinite
    RefinementMaximumEdgeLengthNotPositive
    (> 0)
    refineMaxEdgeLength
  case (refineMinArea, refineMaxArea) of
    (Just minimumArea, Just maximumArea)
      | minimumArea > maximumArea ->
          Left
            ( RefinementMinimumAreaExceedsMaximum
                minimumArea
                maximumArea
            )
    _ -> Right ()

validateOptionalRefinementParameter
  :: (NonFiniteValue -> BuildError)
  -> (Double -> BuildError)
  -> (Double -> Bool)
  -> Maybe Double
  -> Either BuildError ()
validateOptionalRefinementParameter nonFinite outsideRange predicate value =
  case value of
    Nothing -> Right ()
    Just number ->
      case classifyNonFinite number of
        Just nonFiniteValue ->
          Left (nonFinite nonFiniteValue)
        Nothing
          | predicate number -> Right ()
          | otherwise ->
              Left (outsideRange number)