moonlight-planar-1.1.0.0: test/algebra/Moonlight/Planar/RegularTopologySpec.hs
-- | Regular topology, dual rays, and visible/hidden site classification laws.
module Moonlight.Planar.RegularTopologySpec
( tests
) where
import Control.Monad ( unless )
import Data.Foldable ( traverse_ )
import Data.List.NonEmpty ( NonEmpty(..) )
import Moonlight.Planar.Exact ( ExactPoint, ExactVector(..), exactRayDirection, exactRayOrigin,
exactSegmentEndpoints, translateExactPoint )
import Moonlight.Planar.PowerDiagram ( regularEdgeDual, regularEdgeLabels, regularFaceDualPoint,
regularFaceLabels, boundedPowerDiagram, powerCellDisposition, emptyRegularTriangulation,
regularEdges, regularFaces, regularNeighbours, regularSite, regularSiteCount,
regularSiteDisposition, regularSites, regularTriangulation, PowerDualEdge(UnboundedPowerDual,
BoundedPowerDual, CollapsedPowerDual), PowerCellDisposition(EmptyPowerCell,
LowerDimensionalPowerCell), PowerDiagramReceipt(powerDiagramRegularEdges, powerDiagramOracleCells,
powerDiagramMaximumCellConstraints), PowerSite(powerSiteLabel), RegularEdge,
RegularSiteDisposition(RegularSiteHidden, RegularSiteLowerDimensional, RegularSiteVisible),
RegularTriangulationReceipt(regularTriangulationInputSites, regularTriangulationEdges,
regularTriangulationVisibleSites, regularTriangulationFaces) )
import Moonlight.Planar.PowerFixtures ( admittedSite, admittedWeight, admittedWeightedSite,
squareDomain, dispositionTag, powerDistance )
import Moonlight.Planar.Point (Point(..))
import Support ( assertEqual, requireRight )
import qualified Data.List as List
import qualified Data.List.NonEmpty as NonEmpty
import qualified Data.Set as Set
tests :: IO ()
tests =
sequence_
[ testRegularTriangleDualRays
, testRegularBoundedDualSegment
, testRegularCoplanarUpperFacet
, testRegularCollinearClassification
, testRegularLowerDimensionalAndHiddenSites
, testRegularSiteOwnership
]
testRegularTriangleDualRays :: IO ()
testRegularTriangleDualRays = do
zero <- admittedWeight 0
firstSite <- admittedSite "first" (Point 0 0) zero
secondSite <- admittedSite "second" (Point 2 0) zero
thirdSite <- admittedSite "third" (Point 0 2) zero
let sites = firstSite :| [secondSite, thirdSite]
(regular, receipt) <-
requireRight "three-site regular topology" (regularTriangulation sites)
assertEqual "three visible regular sites" 3 (regularTriangulationVisibleSites receipt)
assertEqual "one regular face" 1 (length (regularFaces regular))
assertEqual "three regular boundary edges" 3 (length (regularEdges regular))
dualPoint <-
case regularFaces regular of
[face] -> pure (regularFaceDualPoint face)
faces -> fail ("three-site topology expected one face, got " <> show (length faces))
traverse_ (assertBoundaryDualRay sites dualPoint) (regularEdges regular)
traverse_
(\site ->
let label = powerSiteLabel site
in do
assertEqual
("visible regular disposition for " <> label)
(Just RegularSiteVisible)
(regularSiteDisposition label regular)
assertEqual
("two regular neighbours for " <> label)
2
(Set.size (regularNeighbours label regular)))
sites
testRegularBoundedDualSegment :: IO ()
testRegularBoundedDualSegment = do
zero <- admittedWeight 0
sites <-
traverse
(\(label, point) -> admittedSite label point zero)
( ("south-west", Point 0 0)
:| [ ("south-east", Point 4 0)
, ("north-east", Point 3 3)
, ("north-west", Point 0 4)
]
)
(regular, receipt) <-
requireRight "four-site regular topology" (regularTriangulation sites)
assertEqual
"regular planar Euler equation"
1
( regularTriangulationVisibleSites receipt
- regularTriangulationEdges receipt
+ regularTriangulationFaces receipt
)
let faceDuals = Set.fromList (fmap regularFaceDualPoint (regularFaces regular))
bounded =
[ segment
| edge <- regularEdges regular
, BoundedPowerDual segment <- [regularEdgeDual edge]
]
case bounded of
[segment] -> do
let (firstEndpoint, secondEndpoint) = exactSegmentEndpoints segment
unless
(Set.member firstEndpoint faceDuals && Set.member secondEndpoint faceDuals)
(fail "bounded regular dual does not join its two incident face duals")
assertEqual "bounded regular dual has canonical ascending endpoints" LT
(compare firstEndpoint secondEndpoint)
segments ->
fail ("four-site topology expected one bounded dual, got " <> show (length segments))
testRegularCoplanarUpperFacet :: IO ()
testRegularCoplanarUpperFacet = do
sites <-
traverse
admittedWeightedSite
( ("a-bottom", Point 0.25 0.25, -0.875)
:| [ ("b-south-west", Point 0 0, 0)
, ("c-south-east", Point 1 0, 1)
, ("d-center", Point 0.5 0.5, 0.5)
, ("e-north-east", Point 1 1, 2)
, ("f-north-west", Point 0 1, 1)
]
)
(regular, receipt) <-
requireRight "coplanar upper regular facet" (regularTriangulation sites)
traverse_
(\label ->
assertEqual
("upper-facet vertex remains visible: " <> label)
(Just RegularSiteVisible)
(regularSiteDisposition label regular))
["b-south-west", "c-south-east", "e-north-east", "f-north-west"]
assertEqual
"upper-facet interior generator remains lower-dimensional"
(Just RegularSiteLowerDimensional)
(regularSiteDisposition "d-center" regular)
assertEqual
"strictly lower lifted generator remains hidden"
(Just RegularSiteHidden)
(regularSiteDisposition "a-bottom" regular)
assertEqual "coplanar upper facet has four visible vertices" 4 (regularTriangulationVisibleSites receipt)
assertEqual "coplanar upper facet receives one deterministic diagonal" 2 (regularTriangulationFaces receipt)
let collapsed =
[ point
| edge <- regularEdges regular
, CollapsedPowerDual point <- [regularEdgeDual edge]
]
faceDuals = Set.fromList (fmap regularFaceDualPoint (regularFaces regular))
assertEqual "coplanar diagonal is published as one collapsed dual" 1 (length collapsed)
assertEqual "collapsed dual coincides with every incident face dual" faceDuals (Set.fromList collapsed)
traverse_
(\face ->
let (firstLabel, secondLabel, thirdLabel) = regularFaceLabels face
in assertEqual "regular face rotation begins at least label" firstLabel
(min firstLabel (min secondLabel thirdLabel)))
(regularFaces regular)
testRegularLowerDimensionalAndHiddenSites :: IO ()
testRegularLowerDimensionalAndHiddenSites = do
domain <- squareDomain
zero <- admittedWeight 0
lowerWeight <- admittedWeight (-1.5)
hiddenWeight <- admittedWeight (-2)
firstSite <- admittedSite "first" (Point 0 0) zero
secondSite <- admittedSite "second" (Point 2 0) zero
thirdSite <- admittedSite "third" (Point 0 2) zero
lowerSite <- admittedSite "center" (Point 0.5 0.5) lowerWeight
hiddenSite <- admittedSite "center" (Point 0.5 0.5) hiddenWeight
let lowerSites = firstSite :| [secondSite, thirdSite, lowerSite]
hiddenSites = firstSite :| [secondSite, thirdSite, hiddenSite]
(lowerRegular, _) <-
requireRight "coplanar regular topology" (regularTriangulation lowerSites)
assertEqual
"coplanar interior generator remains lower-dimensional"
(Just RegularSiteLowerDimensional)
(regularSiteDisposition "center" lowerRegular)
(lowerDiagram, lowerReceipt) <-
requireRight "bounded lower-dimensional power cell" (boundedPowerDiagram domain lowerSites)
case powerCellDisposition "center" lowerDiagram of
Just (LowerDimensionalPowerCell _) -> pure ()
other -> fail ("expected lower-dimensional center cell, got " <> dispositionTag other)
assertEqual "one lower-dimensional oracle cell" 1 (powerDiagramOracleCells lowerReceipt)
(hiddenRegular, _) <-
requireRight "hidden regular topology" (regularTriangulation hiddenSites)
assertEqual
"strictly interior lifted generator is hidden"
(Just RegularSiteHidden)
(regularSiteDisposition "center" hiddenRegular)
(hiddenDiagram, hiddenReceipt) <-
requireRight "bounded hidden power cell" (boundedPowerDiagram domain hiddenSites)
assertEqual "hidden generator has empty cell" (Just EmptyPowerCell) (powerCellDisposition "center" hiddenDiagram)
assertEqual "hidden generator needs no HPI oracle" 0 (powerDiagramOracleCells hiddenReceipt)
unless
( powerDiagramMaximumCellConstraints hiddenReceipt <= powerDiagramRegularEdges hiddenReceipt
)
(fail "power construction retained more axes than the regular graph")
testRegularSiteOwnership :: IO ()
testRegularSiteOwnership = do
zero <- admittedWeight 0
sites <-
traverse
(\(label, point) -> admittedSite label point zero)
( ("c", Point 0 2)
:| [("a", Point 0 0), ("b", Point 2 0)]
)
(regular, receipt) <-
requireRight "site-owning regular topology" (regularTriangulation sites)
assertEqual "regular site count" 3 (regularSiteCount regular)
assertEqual
"regular sites are the canonical ascending section"
["a", "b", "c"]
(fmap powerSiteLabel (regularSites regular))
traverse_
(\site ->
assertEqual
("regular site lookup for " <> powerSiteLabel site)
(Just site)
(regularSite (powerSiteLabel site) regular))
sites
case NonEmpty.nonEmpty (regularSites regular) of
Nothing -> fail "nonempty regular topology lost its canonical site section"
Just retainedSites -> do
(reconstructed, _) <-
requireRight "regular reconstruction from owned sites" (regularTriangulation retainedSites)
assertEqual "owned sites reconstruct the same semantic value" regular reconstructed
assertEqual "receipt counts every owned site" 3 (regularTriangulationInputSites receipt)
assertEqual "empty regular site count" 0 (regularSiteCount emptyRegularTriangulation)
assertEqual
"empty regular site section"
([] :: [PowerSite String])
(regularSites emptyRegularTriangulation)
testRegularCollinearClassification :: IO ()
testRegularCollinearClassification = do
sites <- traverse prepareCollinearSite (0 :| [1 .. 8])
(regular, _) <-
requireRight "collinear regular topology" (regularTriangulation sites)
traverse_
(\index ->
assertEqual
("collinear disposition for " <> show index)
(Just (if even index then RegularSiteVisible else RegularSiteHidden))
(regularSiteDisposition (show index) regular))
([0 .. 8] :: [Int])
where
prepareCollinearSite :: Int -> IO (PowerSite String)
prepareCollinearSite index = do
weight <- admittedWeight (if odd index then -(2 / 256) else 0)
admittedSite (show index) (Point (fromIntegral index / 16) 0) weight
assertBoundaryDualRay
:: NonEmpty (PowerSite String)
-> ExactPoint
-> RegularEdge String
-> IO ()
assertBoundaryDualRay sites expectedOrigin edge =
case regularEdgeDual edge of
UnboundedPowerDual ray -> do
assertEqual "regular ray starts at incident face dual" expectedOrigin (exactRayOrigin ray)
let ExactVector directionX directionY = exactRayDirection ray
unless (directionX /= 0 || directionY /= 0) $
fail ("regular edge has a zero dual-ray direction: " <> show (regularEdgeLabels edge))
let sample = translateExactPoint expectedOrigin (exactRayDirection ray)
(firstLabel, secondLabel) = regularEdgeLabels edge
firstSite <- requireSite firstLabel sites
secondSite <- requireSite secondLabel sites
firstDistance <- powerDistance sample firstSite
secondDistance <- powerDistance sample secondSite
assertEqual "regular ray remains on its radical axis" firstDistance secondDistance
traverse_
(\competitor -> do
competitorDistance <- powerDistance sample competitor
unless (firstDistance <= competitorDistance) $
fail ("regular ray points outside the common winning cone: " <> show (firstLabel, secondLabel)))
sites
other -> fail ("regular triangle boundary expected a ray, got " <> show other)
requireSite :: Eq label => label -> NonEmpty (PowerSite label) -> IO (PowerSite label)
requireSite label sites =
case List.find ((== label) . powerSiteLabel) (NonEmpty.toList sites) of
Nothing -> fail "regular topology references a missing source site"
Just site -> pure site