packages feed

moonlight-triangulation-1.4.0.2: bench/spade-compare/hs/Moonlight/Triangulation/Bench/SpadeCompare/Hierarchy.hs

{-# LANGUAGE DataKinds #-}

-- | Hierarchy-hint lanes, their gates, and the maintained-hint step.
module Moonlight.Triangulation.Bench.SpadeCompare.Hierarchy where

import System.FilePath ((</>))
import qualified Data.Vector as V
import Moonlight.Triangulation
import Moonlight.Triangulation.BulkLoad
import Moonlight.Triangulation.HintGenerator
import Moonlight.Triangulation.Session
import Moonlight.Triangulation.Bench.SpadeCompare.Support
import Moonlight.Triangulation.Bench.SpadeCompare.Input (randomPoints)

-- | The step both insertion lanes run: one insert, one hierarchy update, and
-- nothing between them. It is what spade's @insert@ does internally through
-- @notify_vertex_inserted@, and both sides carry branch factor 16.
--
-- The point handed to 'updateHierarchyAfterInsertion' is the same binding
-- handed to 'insertVertexAt', never one read back out of the harness's input
-- vector. The mesh stores 'canonicalPoint' of what it was given, so a
-- harness-side copy would part company from it on a signed zero, seed a
-- different nearest neighbour, and move the gate against spade. Same function,
-- same argument, same bits.
hierarchyStep
  :: HierarchyHint
  -> Point
  -> Session s Point () () () HierarchyHint
hierarchyStep hierarchy point = do
  (vertex, disposition) <- insertVertexAt point point
  either refuse pure (updateHierarchyAfterInsertion hierarchy point vertex disposition)

-- | Arrival-order insertion with the hierarchy maintained alongside it.
--
-- One session spans the whole run. The hierarchy no longer asks for a
-- triangulation, so the per-point publication that existed only to hand it one
-- is gone: the mesh is thawed once and published once for the program, where
-- before it was once per point. The measured operation is unchanged — still an
-- insert per point with the hint maintained against it, which is spade's loop.
insertAllWithHierarchy
  :: V.Vector Point
  -> IO (DelaunayTriangulation Point, HierarchyHint)
insertAllWithHierarchy points = do
  let base = empty unitElementDefaults
  initial <- require (buildHierarchyHint defaultHierarchyBranchFactor base)
  (hierarchy, mesh, _) <-
    require (withSession base (V.length points) (V.foldM' hierarchyStep initial points))
  pure (mesh, hierarchy)

-- | The same step over a mesh that already holds every point, which is the
-- duplicate lane. Each insert locates a site that is already there and answers
-- 'AlreadyPresent', so the hierarchy comes back unexamined and the level walk
-- is never entered. What remains on the clock is the locate and the
-- disposition test, with no rebuild standing behind either.
reinsertAllWithHierarchy
  :: (DelaunayTriangulation Point, HierarchyHint)
  -> V.Vector Point
  -> IO (DelaunayTriangulation Point, HierarchyHint)
reinsertAllWithHierarchy (triangulation, hierarchy) points =
  case withSession triangulation (V.length points) (V.foldM' hierarchyStep hierarchy points) of
    Left refusal -> fail (show refusal)
    Right (updated, mesh, _) -> pure (mesh, updated)

-- | Remove a program of coordinates through the hierarchy: every locate
-- starts at the hierarchy's nearest sample, one base session publishes once,
-- and the hierarchy of the resulting value is published once.
--
-- No query observes an intermediate hierarchy in this lane. A triangulation
-- and its hierarchy denote their current values rather than the edit history,
-- so the lawful physical schedule is one base session followed by one
-- hierarchy publication — the shape of the strict side's
-- @HierarchyTriangulation@, whose removals also locate through the hierarchy.
removeAllWithHierarchy
  :: (DelaunayTriangulation Point, HierarchyHint)
  -> V.Vector Point
  -> IO (DelaunayTriangulation Point, HierarchyHint)
removeAllWithHierarchy (triangulation, hierarchy) points =
  case removeManyWithHierarchy hierarchy triangulation points of
    Left refusal -> fail (show refusal)
    Right (outcomes, surviving, repaired) -> do
      V.imapM_ checkRemoved outcomes
      pure (surviving, repaired)
 where
  checkRemoved index outcome = case outcome of
    Nothing ->
      error
        ( "removal target is not a vertex: index="
            <> show index
            <> " point="
            <> ( case points V.! index of
                  Point x y -> hex64 x <> "," <> hex64 y
               )
        )
    Just _ -> pure ()

-- | The hierarchy lanes' gate, in two parts. The base mesh alone would not see
-- a hierarchy whose nesting law has drifted, because a search started from a
-- bad hint still walks to the right answer; a hint naming a vertex that does
-- not exist, or one the search cannot walk out of, moves the answers. Both are
-- pinned.
hierarchyGateQueries :: Int
hierarchyGateQueries = 1000

writeHierarchyGate
  :: FilePath
  -> String
  -> (DelaunayTriangulation Point, HierarchyHint)
  -> IO ()
writeHierarchyGate directory label (triangulation, hierarchy) = do
  queries <- requireQueryPoints (randomPoints 0x3141592653589793 hierarchyGateQueries)
  writeFile
    (directory </> (label <> "-edges.txt"))
    (unlines (canonicalEdges triangulation))
  writeFile
    (directory </> (label <> "-nearest.txt"))
    ( unlines
        ( map
            (nearestAnswer triangulation hierarchy)
            queries
        )
    )

writeHierarchyIncrementalGate :: FilePath -> Int -> IO ()
writeHierarchyIncrementalGate directory pointCount = do
  built <- insertAllWithHierarchy (V.fromList (randomPoints 0x9e3779b97f4a7c15 pointCount))
  writeHierarchyGate directory ("hierarchy-incremental-" <> show pointCount) built

writeHierarchyDuplicateGate :: FilePath -> Int -> Int -> IO ()
writeHierarchyDuplicateGate directory pointCount duplicateCount = do
  base <- hierarchyOf pointCount
  built <-
    reinsertAllWithHierarchy
      base
      (V.fromList (randomPoints 0x9e3779b97f4a7c15 duplicateCount))
  writeHierarchyGate
    directory
    ("hierarchy-duplicate-" <> show pointCount <> "-" <> show duplicateCount)
    built

writeHierarchyRemovalGate :: FilePath -> Int -> Int -> IO ()
writeHierarchyRemovalGate directory pointCount removalCount = do
  base <- hierarchyOf pointCount
  built <-
    removeAllWithHierarchy
      base
      (V.fromList (take removalCount (randomPoints 0x9e3779b97f4a7c15 pointCount)))
  writeHierarchyGate
    directory
    ("hierarchy-removal-" <> show pointCount <> "-" <> show removalCount)
    built

-- | A bulk-loaded mesh and the hierarchy over it, which is where the duplicate
-- and removal lanes start. The strict side reaches the same pair through
-- @HierarchyTriangulation::bulk_load@.
hierarchyOf
  :: Int
  -> IO (DelaunayTriangulation Point, HierarchyHint)
hierarchyOf count = do
  triangulation <- delaunayOf count
  hierarchy <- require (buildHierarchyHint defaultHierarchyBranchFactor triangulation)
  pure (triangulation, hierarchy)