moonlight-triangulation-1.2.0.0: src-embedding/Moonlight/Triangulation/Internal/Overlay/Embedding.hs
{-# LANGUAGE DeriveAnyClass #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE DerivingStrategies #-}
-- | Bounded certification of local binary64 embedding obligations for a
-- declared exact arrangement draft.
module Moonlight.Triangulation.Internal.Overlay.Embedding
( DraftVertexId (..)
, DraftSegmentId (..)
, DraftSourceId (..)
, DraftIncidence (..)
, DraftNeighborhood (..)
, ExactArrangementDraft (..)
, DraftReference (..)
, OverlayEmbeddingObstruction (..)
, EmbeddingObligation (..)
, EmbeddingResidual
, residualUndischargedObligations
, milestoneOneResidual
, LocalEmbeddingCertificate (..)
, certifyLocalEmbedding
) where
import Control.DeepSeq (NFData)
import Data.List (sort, tails)
import Data.List.NonEmpty (NonEmpty (..))
import qualified Data.List.NonEmpty as NonEmpty
import Data.Map.Strict (Map)
import qualified Data.Map.Strict as Map
import GHC.Generics (Generic)
import Moonlight.Triangulation.Exact
( ExactPoint
, SegmentRelation
, exactOrient2d
, exactPointToEmbeddingCandidate
, exactSegmentRelation
)
import Moonlight.Triangulation.Internal.BoundaryCycle (cyclePairs)
import Moonlight.Triangulation.Internal.ExactRational (ExactRational)
import qualified Moonlight.Triangulation.Math as Math
( orient2d
, segmentRelation
)
import Moonlight.Triangulation.Types
( Point (..)
, PointValidationError
)
-- | Draft-local vertex label.
newtype DraftVertexId = DraftVertexId Int
deriving stock (Eq, Ord, Show, Generic)
deriving anyclass (NFData)
-- | Draft-local atomic-segment label.
newtype DraftSegmentId = DraftSegmentId Int
deriving stock (Eq, Ord, Show, Generic)
deriving anyclass (NFData)
-- | Draft-local source-segment label.
newtype DraftSourceId = DraftSourceId Int
deriving stock (Eq, Ord, Show, Generic)
deriving anyclass (NFData)
-- | A declared relation between two atomic draft segments.
data DraftIncidence = DraftIncidence
{ -- | First declared atomic segment.
draftIncidenceFirstSegment :: !DraftSegmentId
, -- | Second declared atomic segment.
draftIncidenceSecondSegment :: !DraftSegmentId
, -- | Relation declared to hold in both exact and rounded geometry.
draftIncidenceRelation :: !SegmentRelation
}
deriving stock (Eq, Ord, Show, Generic)
deriving anyclass (NFData)
-- | One exact vertex and the cyclic neighbor order declared around it.
data DraftNeighborhood = DraftNeighborhood
{ -- | Center of the declared local rotation.
draftNeighborhoodCenter :: !DraftVertexId
, -- | Neighbors in cyclic rotation order.
draftNeighborhoodNeighbors :: !(NonEmpty DraftVertexId)
}
deriving stock (Eq, Ord, Show, Generic)
deriving anyclass (NFData)
-- | A hand-built exact arrangement draft. It contains only declared local
-- structure; no arrangement or global crossing search is derived here.
data ExactArrangementDraft = ExactArrangementDraft
{ -- | Exact coordinates keyed by draft-local vertex identity.
draftVertices :: !(Map DraftVertexId ExactPoint)
, -- | Atomic segment endpoint identities.
draftSegments :: !(Map DraftSegmentId (DraftVertexId, DraftVertexId))
, -- | Exact split parameters and vertices in source-segment order.
draftSourceMemberships :: !(Map DraftSourceId [(ExactRational, DraftVertexId)])
, -- | Segment relations declared to remain invariant under projection.
draftIncidences :: ![DraftIncidence]
, -- | Cyclic local rotations declared to remain orientation-stable.
draftNeighborhoods :: ![DraftNeighborhood]
}
deriving stock (Eq, Show, Generic)
deriving anyclass (NFData)
-- | The typed identity of a missing draft reference.
data DraftReference
= DraftVertexReference !DraftVertexId
| DraftSegmentReference !DraftSegmentId
deriving stock (Eq, Ord, Show, Generic)
deriving anyclass (NFData)
-- | A witness that prevents local embedding certification.
data OverlayEmbeddingObstruction
= DraftReferenceMissing !DraftReference
| VertexProjectionRefused !DraftVertexId !PointValidationError
| RoundedVerticesCollide !DraftVertexId !DraftVertexId !Point
| SplitOrderNotPreserved !DraftSourceId !DraftVertexId !DraftVertexId
| IncidenceRelationChanged
!DraftSegmentId
!DraftSegmentId
!SegmentRelation
!SegmentRelation
!SegmentRelation
| NeighborhoodOrientationChanged
!DraftVertexId
!DraftVertexId
!DraftVertexId
!Ordering
!Ordering
| GlobalRelationAdded
!DraftSegmentId
!DraftSegmentId
!SegmentRelation
| GlobalRelationRemoved
!DraftSegmentId
!DraftSegmentId
!SegmentRelation
| GlobalRelationChanged
!DraftSegmentId
!DraftSegmentId
!SegmentRelation
!SegmentRelation
| ProjectedSegmentCollapsed
!DraftSegmentId
!DraftVertexId
!DraftVertexId
deriving stock (Eq, Show, Generic)
deriving anyclass (NFData)
-- | An embedding obligation not discharged by the bounded local certifier.
data EmbeddingObligation
= GlobalNoNewCrossing
deriving stock (Bounded, Enum, Eq, Ord, Show, Generic)
deriving anyclass (NFData)
-- | A nonempty collection of obligations deferred to a later owner.
newtype EmbeddingResidual = EmbeddingResidual (NonEmpty EmbeddingObligation)
deriving stock (Eq, Ord, Show, Generic)
deriving anyclass (NFData)
-- | Read the obligations that remain explicitly undischarged.
residualUndischargedObligations
:: EmbeddingResidual
-> NonEmpty EmbeddingObligation
residualUndischargedObligations (EmbeddingResidual obligations) = obligations
-- | The Milestone 1 residual: the arrangement sweep has not yet proved global
-- absence of new crossings.
milestoneOneResidual :: EmbeddingResidual
milestoneOneResidual = EmbeddingResidual (GlobalNoNewCrossing :| [])
-- | A certificate for exactly four local obligations on a declared draft:
-- vertex distinctness, source split order, declared incidences, and local
-- neighborhood orientation. Global absence of new crossings is unproved until
-- the Milestone 2 arrangement sweep supplies the complete obligation set.
data LocalEmbeddingCertificate = LocalEmbeddingCertificate
{ -- | Number of rounded vertex-pair distinctness checks discharged.
certificateRoundedVertexDistinctnessCount :: !Int
, -- | Number of adjacent source split-order checks discharged.
certificateSplitOrderPreservationCount :: !Int
, -- | Number of declared incidence checks discharged.
certificateIncidenceRelationPreservationCount :: !Int
, -- | Number of consecutive neighborhood orientation checks discharged.
certificateNeighborhoodOrientationStabilityCount :: !Int
, -- | The candidate projection certified by the four local obligations.
certificateRoundedVertices :: !(Map DraftVertexId Point)
, -- | The necessarily nonempty global obligation residual.
certificateResidual :: !EmbeddingResidual
}
deriving stock (Eq, Show, Generic)
deriving anyclass (NFData)
-- | Certify the four bounded local embedding obligations, collecting every
-- witness within each obligation and preserving obligation order.
certifyLocalEmbedding
:: ExactArrangementDraft
-> Either (NonEmpty OverlayEmbeddingObstruction) LocalEmbeddingCertificate
certifyLocalEmbedding draft =
case NonEmpty.nonEmpty (structuralObstructions draft) of
Just obstructions -> Left obstructions
Nothing ->
case projectVertices (draftVertices draft) of
Invalid obstructions -> Left obstructions
Valid projectedVertices ->
certifyProjectedDraft draft projectedVertices
data Validation value
= Invalid !(NonEmpty OverlayEmbeddingObstruction)
| Valid value
instance Functor Validation where
fmap _ (Invalid obstructions) = Invalid obstructions
fmap transform (Valid value) = Valid (transform value)
instance Applicative Validation where
pure = Valid
Invalid left <*> Invalid right = Invalid (left <> right)
Invalid obstructions <*> Valid _ = Invalid obstructions
Valid _ <*> Invalid obstructions = Invalid obstructions
Valid transform <*> Valid value = Valid (transform value)
invalid :: OverlayEmbeddingObstruction -> Validation value
invalid obstruction = Invalid (obstruction :| [])
data ProjectedVertex = ProjectedVertex
{ projectedExactPoint :: !ExactPoint
, projectedRoundedPoint :: !Point
}
data ResolvedSegment = ResolvedSegment
{ resolvedSegmentId :: !DraftSegmentId
, resolvedSegmentFrom :: !ResolvedVertex
, resolvedSegmentTo :: !ResolvedVertex
}
data ResolvedVertex = ResolvedVertex
{ resolvedVertexId :: !DraftVertexId
, resolvedProjectedVertex :: !ProjectedVertex
}
data ResolvedMembership = ResolvedMembership
{ resolvedMembershipParameter :: !ExactRational
, resolvedMembershipVertex :: !ResolvedVertex
}
structuralObstructions
:: ExactArrangementDraft
-> [OverlayEmbeddingObstruction]
structuralObstructions draft =
segmentEndpointObstructions
<> sourceMembershipObstructions
<> incidenceObstructions
<> neighborhoodObstructions
where
vertices = draftVertices draft
segments = draftSegments draft
missingVertex vertexId =
[ DraftReferenceMissing (DraftVertexReference vertexId)
| Map.notMember vertexId vertices
]
missingSegment segmentId =
[ DraftReferenceMissing (DraftSegmentReference segmentId)
| Map.notMember segmentId segments
]
segmentEndpointObstructions =
concatMap
(\(_, (from, to)) -> missingVertex from <> missingVertex to)
(Map.toAscList segments)
sourceMembershipObstructions =
concatMap
(concatMap (missingVertex . snd) . snd)
(Map.toAscList (draftSourceMemberships draft))
incidenceObstructions =
concatMap
( \incidence ->
missingSegment (draftIncidenceFirstSegment incidence)
<> missingSegment (draftIncidenceSecondSegment incidence)
)
(draftIncidences draft)
neighborhoodObstructions =
concatMap
( \neighborhood ->
missingVertex (draftNeighborhoodCenter neighborhood)
<> concatMap missingVertex (draftNeighborhoodNeighbors neighborhood)
)
(draftNeighborhoods draft)
projectVertices
:: Map DraftVertexId ExactPoint
-> Validation (Map DraftVertexId ProjectedVertex)
projectVertices =
Map.traverseWithKey
( \vertexId point ->
case exactPointToEmbeddingCandidate point of
Left projectionError ->
invalid (VertexProjectionRefused vertexId projectionError)
Right roundedPoint -> Valid (ProjectedVertex point roundedPoint)
)
certifyProjectedDraft
:: ExactArrangementDraft
-> Map DraftVertexId ProjectedVertex
-> Either (NonEmpty OverlayEmbeddingObstruction) LocalEmbeddingCertificate
certifyProjectedDraft draft projectedVertices =
case resolvedFailures of
Left obstruction -> Left (obstruction :| [])
Right localFailures ->
case NonEmpty.nonEmpty (collisionFailures <> localFailures) of
Just obstructions -> Left obstructions
Nothing ->
Right
LocalEmbeddingCertificate
{ certificateRoundedVertexDistinctnessCount = distinctnessCount
, certificateSplitOrderPreservationCount = splitOrderCount
, certificateIncidenceRelationPreservationCount = incidenceCount
, certificateNeighborhoodOrientationStabilityCount = neighborhoodCount
, certificateRoundedVertices = Map.map projectedRoundedPoint projectedVertices
, certificateResidual = milestoneOneResidual
}
where
collisionFailures = roundedVertexCollisionObstructions projectedVertices
resolvedFailures = do
splitFailures <- traverse (uncurry resolveSource) (Map.toAscList (draftSourceMemberships draft))
incidenceFailures <- traverse resolveIncidence (draftIncidences draft)
neighborhoodFailures <- traverse resolveNeighborhood (draftNeighborhoods draft)
pure (concat splitFailures <> concat incidenceFailures <> concat neighborhoodFailures)
resolveVertex vertexId =
case Map.lookup vertexId projectedVertices of
Nothing -> Left (DraftReferenceMissing (DraftVertexReference vertexId))
Just projectedVertex -> Right (ResolvedVertex vertexId projectedVertex)
resolveSegment segmentId =
case Map.lookup segmentId (draftSegments draft) of
Nothing -> Left (DraftReferenceMissing (DraftSegmentReference segmentId))
Just (from, to) ->
ResolvedSegment segmentId
<$> resolveVertex from
<*> resolveVertex to
resolveSource sourceId memberships =
splitOrderObstructions sourceId
<$> traverse
( \(parameter, vertexId) ->
ResolvedMembership parameter <$> resolveVertex vertexId
)
memberships
resolveIncidence incidence =
incidenceRelationObstructions
(draftIncidenceRelation incidence)
<$> resolveSegment (draftIncidenceFirstSegment incidence)
<*> resolveSegment (draftIncidenceSecondSegment incidence)
resolveNeighborhood neighborhood =
neighborhoodOrientationObstructions
<$> resolveVertex (draftNeighborhoodCenter neighborhood)
<*> traverse resolveVertex (draftNeighborhoodNeighbors neighborhood)
vertexCount = Map.size projectedVertices
distinctnessCount = vertexCount * (vertexCount - 1) `quot` 2
splitOrderCount =
sum
( map
(max 0 . subtract 1 . length)
(Map.elems (draftSourceMemberships draft))
)
incidenceCount = length (draftIncidences draft)
neighborhoodCount =
sum
( map
(length . draftNeighborhoodNeighbors)
(draftNeighborhoods draft)
)
roundedVertexCollisionObstructions
:: Map DraftVertexId ProjectedVertex
-> [OverlayEmbeddingObstruction]
roundedVertexCollisionObstructions projectedVertices =
[ RoundedVerticesCollide leftId rightId roundedPoint
| (roundedPoint, vertexIds) <- Map.toAscList verticesByRoundedPoint
, (leftId : remainingIds) <- tails (sort vertexIds)
, rightId <- remainingIds
]
where
verticesByRoundedPoint =
Map.fromListWith (<>)
[ (projectedRoundedPoint projectedVertex, [vertexId])
| (vertexId, projectedVertex) <- Map.toAscList projectedVertices
]
splitOrderObstructions
:: DraftSourceId
-> [ResolvedMembership]
-> [OverlayEmbeddingObstruction]
splitOrderObstructions sourceId memberships =
case memberships of
firstMembership : secondMembership : remainingMemberships ->
let finalMembership =
foldl' (\_ current -> current) secondMembership remainingMemberships
sourceFrom = roundedMembershipPoint firstMembership
sourceTo = roundedMembershipPoint finalMembership
in [ SplitOrderNotPreserved
sourceId
(resolvedVertexId (resolvedMembershipVertex leftMembership))
(resolvedVertexId (resolvedMembershipVertex rightMembership))
| (leftMembership, rightMembership) <-
zip
memberships
(drop 1 memberships)
, compare
(resolvedMembershipParameter leftMembership)
(resolvedMembershipParameter rightMembership)
/= roundedOrderAlong
sourceFrom
sourceTo
(roundedMembershipPoint leftMembership)
(roundedMembershipPoint rightMembership)
]
_ -> []
roundedMembershipPoint :: ResolvedMembership -> Point
roundedMembershipPoint =
projectedRoundedPoint
. resolvedProjectedVertex
. resolvedMembershipVertex
roundedOrderAlong :: Point -> Point -> Point -> Point -> Ordering
roundedOrderAlong
(Point sourceFromX sourceFromY)
(Point sourceToX sourceToY)
(Point leftX leftY)
(Point rightX rightY) =
let directionX = sourceToX - sourceFromX
directionY = sourceToY - sourceFromY
in if abs directionX >= abs directionY
then
if directionX >= 0
then compare leftX rightX
else compare rightX leftX
else
if directionY >= 0
then compare leftY rightY
else compare rightY leftY
incidenceRelationObstructions
:: SegmentRelation
-> ResolvedSegment
-> ResolvedSegment
-> [OverlayEmbeddingObstruction]
incidenceRelationObstructions declaredRelation firstSegment secondSegment =
[ IncidenceRelationChanged
(resolvedSegmentId firstSegment)
(resolvedSegmentId secondSegment)
declaredRelation
exactRelation
roundedRelation
| exactRelation /= declaredRelation || roundedRelation /= declaredRelation
]
where
exactRelation =
relationFor exactSegmentRelation projectedExactPoint firstSegment secondSegment
roundedRelation =
relationFor Math.segmentRelation projectedRoundedPoint firstSegment secondSegment
relationFor
:: (point -> point -> point -> point -> SegmentRelation)
-> (ProjectedVertex -> point)
-> ResolvedSegment
-> ResolvedSegment
-> SegmentRelation
relationFor relation project firstSegment secondSegment =
relation
(project (resolvedProjectedVertex (resolvedSegmentFrom firstSegment)))
(project (resolvedProjectedVertex (resolvedSegmentTo firstSegment)))
(project (resolvedProjectedVertex (resolvedSegmentFrom secondSegment)))
(project (resolvedProjectedVertex (resolvedSegmentTo secondSegment)))
neighborhoodOrientationObstructions
:: ResolvedVertex
-> NonEmpty ResolvedVertex
-> [OverlayEmbeddingObstruction]
neighborhoodOrientationObstructions center neighbors =
[ NeighborhoodOrientationChanged
(resolvedVertexId center)
(resolvedVertexId leftNeighbor)
(resolvedVertexId rightNeighbor)
exactOrientation
roundedOrientation
| (leftNeighbor, rightNeighbor) <-
cyclePairs neighbors
, let exactOrientation =
exactOrient2d
(exactVertexPoint center)
(exactVertexPoint leftNeighbor)
(exactVertexPoint rightNeighbor)
roundedOrientation =
Math.orient2d
(roundedVertexPoint center)
(roundedVertexPoint leftNeighbor)
(roundedVertexPoint rightNeighbor)
, exactOrientation /= roundedOrientation
]
exactVertexPoint :: ResolvedVertex -> ExactPoint
exactVertexPoint = projectedExactPoint . resolvedProjectedVertex
roundedVertexPoint :: ResolvedVertex -> Point
roundedVertexPoint = projectedRoundedPoint . resolvedProjectedVertex