packages feed

moonlight-planar-1.1.0.0: test/algebra/Moonlight/Planar/RegularEditSpec.hs

-- | Regular insertion, removal, and reweighting against reconstruction.
module Moonlight.Planar.RegularEditSpec
  ( tests
  ) where

import Data.Foldable ( traverse_ )
import Data.List.NonEmpty ( NonEmpty(..) )
import Moonlight.Planar.Convex ( ConvexPolygon )
import Moonlight.Planar.PowerDiagram ( insertRegularSite, removeRegularSite, reweightRegularSites,
  powerSitePosition, regularEdgeLabels, regularFaceLabels, boundedPowerDiagramFromRegular,
  emptyRegularTriangulation, regularEdges, regularFaces, regularNeighbours, regularSite,
  regularSiteCount, regularSiteDisposition, regularSites, regularTriangulation,
  regularTriangulationReceipt, PowerSite(..), RegularEditError(RegularEditUnknownSites,
  RegularEditSiteConflict), RegularEditResult(regularEditTransitions, regularEditChangedSites,
  regularEditTriangulation), RegularSiteDisposition(RegularSiteVisible,
  RegularSiteCoincidentDominatedBy, RegularSiteCoincidentEquivalentTo, RegularSiteHidden),
  RegularSiteTransition(RegularSiteDisappeared, RegularSiteAppeared, RegularSiteTransitioned),
  RegularTriangulation )
import Moonlight.Planar.PowerFixtures ( admittedSite, admittedWeight, squareDomain )
import Moonlight.Planar.RegularAlpha ( regularAlphaBirths, regularAlphaComplexAtBirth,
  regularAlphaFiltration )
import Moonlight.Planar.Point (Point(..))
import Support ( assertEqual, requireRight )
import qualified Data.Map.Strict as Map
import qualified Data.List.NonEmpty as NonEmpty
import qualified Data.Set as Set
import qualified Data.Vector as Vector


tests :: IO ()
tests =
  sequence_
    [ testHiddenInsertionAndExposure
    , testRegularInsertionAndRemovalTransitions
    , testRegularReweightTransitions
    , testTopologyPreservingEdits
    , testRegularEditDifferentialLaws
    , testRegularEditObstructionsAndIdempotence
    ]

testHiddenInsertionAndExposure :: IO ()
testHiddenInsertionAndExposure = do
  zero <- admittedWeight 0
  hiddenWeight <- admittedWeight (-2)
  first <- admittedSite "first" (Point 0 0) zero
  second <- admittedSite "second" (Point 2 0) zero
  third <- admittedSite "third" (Point 0 2) zero
  hidden <- admittedSite "hidden" (Point 0.5 0.5) hiddenWeight
  (initial, _) <-
    requireRight
      "regular hidden-insertion base"
      (regularTriangulation (first :| [second, third]))
  inserted <-
    requireRight "insert hidden regular site" (insertRegularSite hidden initial)
  assertEqual
    "hidden insertion is successful resident publication"
    (Just hidden)
    (regularSite "hidden" (regularEditTriangulation inserted))
  assertEqual
    "hidden insertion disposition"
    [RegularSiteAppeared "hidden" RegularSiteHidden]
    (Vector.toList (regularEditTransitions inserted))
  removed <-
    requireRight
      "remove face-defining site"
      (removeRegularSite "first" (regularEditTriangulation inserted))
  assertEqual
    "removal re-exposes a hidden resident"
    (Just RegularSiteVisible)
    (regularSiteDisposition "hidden" (regularEditTriangulation removed))
  assertEqual
    "hidden exposure receipt"
    [ RegularSiteDisappeared "first" RegularSiteVisible
    , RegularSiteTransitioned "hidden" RegularSiteHidden RegularSiteVisible
    ]
    (Vector.toList (regularEditTransitions removed))

testRegularInsertionAndRemovalTransitions :: IO ()
testRegularInsertionAndRemovalTransitions = do
  zero <- admittedWeight 0
  dominantWeight <- admittedWeight 1
  resident <- admittedSite "a-resident" (Point 0 0) zero
  dominant <- admittedSite "z-dominant" (Point 0 0) dominantWeight
  (initial, _) <-
    requireRight "regular insertion base" (regularTriangulation (resident :| []))
  inserted <-
    requireRight "dominant regular insertion" (insertRegularSite dominant initial)
  assertEqual
    "insert changed-site support"
    (Set.singleton "z-dominant")
    (regularEditChangedSites inserted)
  assertEqual
    "dominant insertion transitions"
    [ RegularSiteTransitioned
        "a-resident"
        RegularSiteVisible
        (RegularSiteCoincidentDominatedBy "z-dominant")
    , RegularSiteAppeared "z-dominant" RegularSiteVisible
    ]
    (Vector.toList (regularEditTransitions inserted))
  removed <-
    requireRight
      "dominant regular removal"
      (removeRegularSite "z-dominant" (regularEditTriangulation inserted))
  assertEqual
    "remove changed-site support"
    (Set.singleton "z-dominant")
    (regularEditChangedSites removed)
  assertEqual
    "dominant removal re-exposes resident"
    [ RegularSiteTransitioned
        "a-resident"
        (RegularSiteCoincidentDominatedBy "z-dominant")
        RegularSiteVisible
    , RegularSiteDisappeared "z-dominant" RegularSiteVisible
    ]
    (Vector.toList (regularEditTransitions removed))
  assertEqual
    "insert then remove returns the canonical site value"
    initial
    (regularEditTriangulation removed)

testRegularReweightTransitions :: IO ()
testRegularReweightTransitions = do
  dominantWeight <- admittedWeight 1
  subordinateWeight <- admittedWeight 0
  promotedWeight <- admittedWeight 2
  first <- admittedSite "a" (Point 0 0) dominantWeight
  second <- admittedSite "b" (Point 0 0) subordinateWeight
  (initial, _) <-
    requireRight "regular reweight base" (regularTriangulation (first :| [second]))
  reweighted <-
    requireRight
      "regular batch reweight"
      (reweightRegularSites (Map.singleton "b" promotedWeight) initial)
  assertEqual
    "reweight changed-site support"
    (Set.singleton "b")
    (regularEditChangedSites reweighted)
  assertEqual
    "reweight transitions preserve identities"
    [ RegularSiteTransitioned
        "a"
        RegularSiteVisible
        (RegularSiteCoincidentDominatedBy "b")
    , RegularSiteTransitioned
        "b"
        (RegularSiteCoincidentDominatedBy "a")
        RegularSiteVisible
    ]
    (Vector.toList (regularEditTransitions reweighted))
  let revised = regularEditTriangulation reweighted
  assertEqual
    "reweight preserves the site position"
    (Just (Point 0 0))
    (powerSitePosition <$> regularSite "b" revised)
  assertEqual
    "reweight replaces only the requested weight"
    (Just promotedWeight)
    (powerSiteWeight <$> regularSite "b" revised)

testTopologyPreservingEdits :: IO ()
testTopologyPreservingEdits = do
  domain <- squareDomain
  zero <- admittedWeight 0
  one <- admittedWeight 1
  hiddenWeight <- admittedWeight (-2)
  lowerHiddenWeight <- admittedWeight (-3)

  representative <- admittedSite "a" (Point 0 0) one
  subordinate <- admittedSite "b" (Point 0 0) zero
  (coincidentBase, _) <-
    requireRight "coincident edit base" (regularTriangulation (representative :| []))
  inserted <-
    requireRight
      "canonical coincident insertion"
      (insertRegularSite subordinate coincidentBase)
  assertEqual
    "coincident subordinate insertion transition"
    [RegularSiteAppeared "b" (RegularSiteCoincidentDominatedBy "a")]
    (Vector.toList (regularEditTransitions inserted))
  assertRegularReconstructs domain "canonical coincident insertion" (regularEditTriangulation inserted)

  reweighted <-
    requireRight
      "topology-preserving coincident reweight"
      (reweightRegularSites (Map.singleton "b" one) (regularEditTriangulation inserted))
  assertEqual
    "coincident subordinate reweight transition"
    [ RegularSiteTransitioned
        "b"
        (RegularSiteCoincidentDominatedBy "a")
        (RegularSiteCoincidentEquivalentTo "a")
    ]
    (Vector.toList (regularEditTransitions reweighted))
  assertRegularReconstructs domain "coincident reweight" (regularEditTriangulation reweighted)

  removedSubordinate <-
    requireRight
      "topology-preserving coincident removal"
      (removeRegularSite "b" (regularEditTriangulation reweighted))
  assertEqual
    "coincident subordinate removal transition"
    [RegularSiteDisappeared "b" (RegularSiteCoincidentEquivalentTo "a")]
    (Vector.toList (regularEditTransitions removedSubordinate))
  assertRegularReconstructs domain "coincident removal" (regularEditTriangulation removedSubordinate)

  first <- admittedSite "first" (Point 0 0) zero
  second <- admittedSite "second" (Point 2 0) zero
  third <- admittedSite "third" (Point 0 2) zero
  hidden <- admittedSite "hidden" (Point 0.5 0.5) hiddenWeight
  secondHidden <- admittedSite "hidden-second" (Point 0.75 0.5) hiddenWeight
  (hiddenBase, _) <-
    requireRight
      "hidden edit base"
      (regularTriangulation (first :| [second, third, hidden, secondHidden]))
  lowered <-
    requireRight
      "topology-preserving hidden reweight"
      ( reweightRegularSites
          (Map.fromList [("hidden", lowerHiddenWeight), ("hidden-second", lowerHiddenWeight)])
          hiddenBase
      )
  assertEqual
    "hidden batch changed-site support"
    (Set.fromList ["hidden", "hidden-second"])
    (regularEditChangedSites lowered)
  assertEqual "hidden downward reweight has no visibility transition" [] (Vector.toList (regularEditTransitions lowered))
  assertRegularReconstructs domain "hidden downward reweight" (regularEditTriangulation lowered)

  removedHidden <-
    requireRight
      "topology-preserving hidden removal"
      (removeRegularSite "hidden" (regularEditTriangulation lowered))
  assertEqual
    "hidden removal transition"
    [RegularSiteDisappeared "hidden" RegularSiteHidden]
    (Vector.toList (regularEditTransitions removedHidden))
  assertRegularReconstructs domain "hidden removal" (regularEditTriangulation removedHidden)

assertRegularReconstructs
  :: (Ord label, Show label)
  => ConvexPolygon
  -> String
  -> RegularTriangulation label
  -> IO ()
assertRegularReconstructs domain name edited =
  case NonEmpty.nonEmpty (regularSites edited) of
    Nothing -> assertEqual (name <> " remains empty") 0 (regularSiteCount edited)
    Just sites -> do
      (rebuilt, _) <-
        requireRight (name <> " reconstruction") (regularTriangulation sites)
      let labels = fmap powerSiteLabel (NonEmpty.toList sites)
      assertEqual
        (name <> " receipt")
        (regularTriangulationReceipt rebuilt)
        (regularTriangulationReceipt edited)
      assertEqual
        (name <> " face labels")
        (fmap regularFaceLabels (regularFaces rebuilt))
        (fmap regularFaceLabels (regularFaces edited))
      assertEqual (name <> " faces") (regularFaces rebuilt) (regularFaces edited)
      assertEqual
        (name <> " edge labels")
        (fmap regularEdgeLabels (regularEdges rebuilt))
        (fmap regularEdgeLabels (regularEdges edited))
      assertEqual (name <> " edges") (regularEdges rebuilt) (regularEdges edited)
      traverse_
        (\label -> do
           assertEqual
             (name <> " disposition " <> show label)
             (regularSiteDisposition label rebuilt)
             (regularSiteDisposition label edited)
           assertEqual
             (name <> " neighbours " <> show label)
             (regularNeighbours label rebuilt)
             (regularNeighbours label edited))
        labels
      rebuiltDiagram <-
        requireRight (name <> " rebuilt clipping") (boundedPowerDiagramFromRegular domain rebuilt)
      editedDiagram <-
        requireRight (name <> " edited clipping") (boundedPowerDiagramFromRegular domain edited)
      assertEqual (name <> " bounded cells") rebuiltDiagram editedDiagram

testRegularEditDifferentialLaws :: IO ()
testRegularEditDifferentialLaws = do
  domain <- squareDomain
  sites <- traverse prepareDifferentialSite [0 .. 47]
  nonEmptySites <-
    maybe (fail "differential regular fixture is empty") pure (NonEmpty.nonEmpty sites)
  baseSites <-
    maybe (fail "differential insertion fixture is empty") pure
      (NonEmpty.nonEmpty (NonEmpty.init nonEmptySites))
  (base, _) <- requireRight "differential insertion base" (regularTriangulation baseSites)
  inserted <-
    requireRight
      "differential local insertion"
      (insertRegularSite (NonEmpty.last nonEmptySites) base)
  assertRegularAlphaClosed
    "differential weighted alpha"
    (regularEditTriangulation inserted)
  assertRegularReconstructs
    domain
    "differential insertion"
    (regularEditTriangulation inserted)
  removed <-
    requireRight
      "differential local removal"
      (removeRegularSite "s23" (regularEditTriangulation inserted))
  assertRegularReconstructs
    domain
    "differential removal"
    (regularEditTriangulation removed)
  raised <- admittedWeight 2
  reweighted <-
    requireRight
      "differential local reweight"
      (reweightRegularSites (Map.singleton "s17" raised) (regularEditTriangulation removed))
  assertRegularReconstructs
    domain
    "differential reweight"
    (regularEditTriangulation reweighted)

assertRegularAlphaClosed
  :: (Ord label, Show label)
  => String
  -> RegularTriangulation label
  -> IO ()
assertRegularAlphaClosed name regular = do
  filtration <- requireRight name (regularAlphaFiltration regular)
  traverse_
    (requireRight (name <> " sublevel") . (`regularAlphaComplexAtBirth` filtration))
    (Map.elems (regularAlphaBirths filtration))

prepareDifferentialSite :: Int -> IO (PowerSite String)
prepareDifferentialSite label = do
  let coordinateX = fromIntegral ((label * 37 + 11) `mod` 97) / 10
      coordinateY = fromIntegral ((label * 61 + 7) `mod` 89) / 10
      weightValue = fromIntegral ((label * 17) `mod` 13 - 6) / 64
  weight <- admittedWeight weightValue
  admittedSite ("s" <> show label) (Point coordinateX coordinateY) weight

testRegularEditObstructionsAndIdempotence :: IO ()
testRegularEditObstructionsAndIdempotence = do
  zero <- admittedWeight 0
  one <- admittedWeight 1
  site <- admittedSite "site" (Point 0 0) zero
  conflicting <- admittedSite "site" (Point 0 0) one
  inserted <-
    requireRight
      "insert into empty regular topology"
      (insertRegularSite site emptyRegularTriangulation)
  repeated <-
    requireRight
      "repeat identical regular insertion"
      (insertRegularSite site (regularEditTriangulation inserted))
  assertEqual
    "identical insertion changes no site"
    Set.empty
    (regularEditChangedSites repeated)
  assertEqual "identical insertion has no transitions" [] (Vector.toList (regularEditTransitions repeated))
  case insertRegularSite conflicting (regularEditTriangulation inserted) of
    Left (RegularEditSiteConflict "site" resident requested) -> do
      assertEqual "conflict retains resident" site resident
      assertEqual "conflict retains requested site" conflicting requested
    other -> fail ("expected regular edit site conflict, got " <> show other)
  case reweightRegularSites (Map.singleton "missing" zero) (regularEditTriangulation inserted) of
    Left (RegularEditUnknownSites ("missing" :| [])) -> pure ()
    other -> fail ("expected unknown reweight site, got " <> show other)
  absent <-
    requireRight
      "remove absent regular site"
      (removeRegularSite "missing" (regularEditTriangulation inserted))
  assertEqual
    "absent removal changes no site"
    Set.empty
    (regularEditChangedSites absent)
  assertEqual "absent removal has no transitions" [] (Vector.toList (regularEditTransitions absent))
  removed <-
    requireRight
      "remove final regular site"
      (removeRegularSite "site" (regularEditTriangulation inserted))
  assertEqual "final removal returns empty" 0 (regularSiteCount (regularEditTriangulation removed))
  assertEqual
    "final removal transition"
    [RegularSiteDisappeared "site" RegularSiteVisible]
    (Vector.toList (regularEditTransitions removed))