packages feed

moonlight-core-0.1.0.0: src-solver/Moonlight/Core/Fixpoint/Internal/Solver/Engine.hs

-- | The monotone solving engine: arena-based equation evaluation, acyclic and
-- cyclic component solving, widening/narrowing, and the public @solve*@ entry
-- points — the only @runST@ seals over the private arena/queue/bitset.
module Moonlight.Core.Fixpoint.Internal.Solver.Engine
  ( solveMonotone,
    solveDenseMonotone,
    solveIncremental,
  )
where

import Control.Monad (void, when)
import Control.Monad.ST (ST, runST)
import Data.Foldable (traverse_)
import Data.IntMap.Strict (IntMap)
import Data.IntMap.Strict qualified as IntMap
import Data.IntSet (IntSet)
import Data.IntSet qualified as IntSet
import Data.Vector (Vector)
import Data.Vector qualified as Vector
import Data.Vector.Mutable qualified as MVector
import Moonlight.Core.Fixpoint.Internal.Solver.Arena qualified as Arena
import Moonlight.Core.Fixpoint.Internal.Solver.Plan
  ( dense,
    equationsForOutput,
    equationsUsingInput,
    validateDeltas,
    validateSnapshot,
  )
import Moonlight.Core.Fixpoint.Internal.Solver.Types
  ( ConvergencePlan (..),
    DeltaDomain (..),
    Equation (..),
    EquationId (..),
    Evaluation,
    OutputUpdate,
    Component (..),
    Obstruction,
    Plan (..),
    Result,
    Snapshot (..),
    WideningPolicy (..),
    equationIdKey,
  )
import Moonlight.Core.Fixpoint.Internal.Solver.WorkQueue qualified as WorkQueue
import Prelude

solveMonotone :: DeltaDomain value delta -> Plan value delta -> Vector value -> Either Obstruction (Result value delta)
solveMonotone domain plan values =
  validateSnapshot plan snapshot
    *> pure (solveFullSnapshot domain plan snapshot)
  where
    snapshot =
      Snapshot values

solveDenseMonotone ::
  DeltaDomain value delta ->
  Int ->
  (Int -> Evaluation value value) ->
  (Int -> value) ->
  Either Obstruction (Result value delta)
solveDenseMonotone domain valueCount evaluate initialValue =
  dense count evaluate
    >>= \plan ->
      pure
        ( runST $ do
            arena <- Arena.new domain snapshot
            traverse_ (solveComponentM domain plan arena) (components plan)
            Arena.toResult arena
        )
  where
    count =
      max 0 valueCount
    snapshot =
      Snapshot (Vector.generate count initialValue)

solveIncremental ::
  DeltaDomain value delta ->
  Plan value delta ->
  Snapshot value delta ->
  IntMap delta ->
  Either Obstruction (Result value delta)
solveIncremental domain plan snapshot deltas =
  validateSnapshot plan snapshot
    *> validateDeltas domain plan deltas
    *> pure
      ( runST $ do
          arena <- Arena.new domain snapshot
          case convergencePlan plan of
            FiniteHeightScc ->
              solveFiniteIncrementalM domain plan arena deltas
            Widening {} ->
              Arena.seed domain arena deltas
                *> traverse_ (solveComponentM domain plan arena) (components plan)
          Arena.toResult arena
      )

solveFullSnapshot :: DeltaDomain value delta -> Plan value delta -> Snapshot value delta -> Result value delta
solveFullSnapshot domain plan snapshot =
  runST $ do
    arena <- Arena.new domain snapshot
    traverse_ (solveComponentM domain plan arena) (components plan)
    Arena.toResult arena
{-# INLINE solveFullSnapshot #-}

solveFiniteIncrementalM ::
  DeltaDomain value delta ->
  Plan value delta ->
  Arena.Arena state value delta ->
  IntMap delta ->
  ST state ()
solveFiniteIncrementalM domain plan arena deltas = do
  queue <- WorkQueue.new (MVector.length (Arena.values arena))
  seedQueued domain arena queue deltas
  WorkQueue.drain queue (evaluatePendingInputM finiteOutputUpdate domain plan arena queue)
{-# INLINE solveFiniteIncrementalM #-}

seedQueued ::
  DeltaDomain value delta ->
  Arena.Arena state value delta ->
  WorkQueue.WorkQueue state ->
  IntMap delta ->
  ST state ()
seedQueued domain arena queue =
  traverse_ (uncurry seedQueuedDelta) . IntMap.toAscList
  where
    seedQueuedDelta key deltaValue
      | deltaNull domain deltaValue = pure ()
      | otherwise = do
          changed <- Arena.seedDeltaM domain arena key deltaValue
          when changed (WorkQueue.enqueue queue key)
{-# INLINE seedQueued #-}

solveComponentM :: DeltaDomain value delta -> Plan value delta -> Arena.Arena state value delta -> Component -> ST state ()
solveComponentM domain plan arena component =
  case component of
    AcyclicOutput output ->
      traverse_ (evaluateFullEquationM domain arena) (equationsForOutput output plan)
    CyclicOutputs outputs ->
      solveCyclicComponentM domain plan arena outputs

solveCyclicComponentM :: DeltaDomain value delta -> Plan value delta -> Arena.Arena state value delta -> IntSet -> ST state ()
solveCyclicComponentM domain plan arena outputs = do
  queue <- WorkQueue.new (MVector.length (Arena.values arena))
  seedCyclicComponentM widenOutput domain plan arena outputs queue
  WorkQueue.drain queue (evaluateCyclicInputM widenOutput domain plan arena outputs queue)
  narrowCyclicComponentM (convergencePlan plan) domain plan arena outputs
  where
    widenOutput =
      convergenceWidenOutput (convergencePlan plan) outputs

finiteOutputUpdate :: OutputUpdate value
finiteOutputUpdate _ _ newValue =
  newValue

convergenceWidenOutput :: ConvergencePlan value -> IntSet -> OutputUpdate value
convergenceWidenOutput convergence outputs =
  case convergence of
    FiniteHeightScc ->
      finiteOutputUpdate
    Widening policy ->
      headedOutputUpdate (IntSet.intersection (wideningHeads policy) outputs) (widenAt policy)

narrowCyclicComponentM ::
  ConvergencePlan value ->
  DeltaDomain value delta ->
  Plan value delta ->
  Arena.Arena state value delta ->
  IntSet ->
  ST state ()
narrowCyclicComponentM convergence domain plan arena outputs =
  case convergence of
    FiniteHeightScc ->
      pure ()
    Widening policy
      | IntSet.null componentHeads ->
          pure ()
      | otherwise -> do
          queue <- WorkQueue.new (MVector.length (Arena.values arena))
          seedCyclicComponentM narrowOutput domain plan arena outputs queue
          WorkQueue.drain queue (evaluateCyclicInputM narrowOutput domain plan arena outputs queue)
      where
        componentHeads =
          IntSet.intersection (wideningHeads policy) outputs
        narrowOutput =
          headedOutputUpdate componentHeads (narrowAt policy)

headedOutputUpdate :: IntSet -> (Int -> value -> value -> value) -> OutputUpdate value
headedOutputUpdate heads update key oldValue newValue
  | IntSet.member key heads =
      update key oldValue newValue
  | otherwise =
      newValue

seedCyclicComponentM ::
  OutputUpdate value ->
  DeltaDomain value delta ->
  Plan value delta ->
  Arena.Arena state value delta ->
  IntSet ->
  WorkQueue.WorkQueue state ->
  ST state ()
seedCyclicComponentM updateOutput domain plan arena outputs queue =
  traverse_ seedOutput (IntSet.toAscList outputs)
  where
    seedOutput outputKey =
      traverse_ seedEquation (equationsForOutput (EquationId outputKey) plan)
    seedEquation equation = do
      changed <- evaluateFullEquationChangedWithM updateOutput domain arena equation
      if changed
        then WorkQueue.enqueue queue (equationIdKey (equationOutput equation))
        else pure ()

evaluateCyclicInputM ::
  OutputUpdate value ->
  DeltaDomain value delta ->
  Plan value delta ->
  Arena.Arena state value delta ->
  IntSet ->
  WorkQueue.WorkQueue state ->
  Int ->
  ST state ()
evaluateCyclicInputM updateOutput domain plan arena componentOutputs queue inputKey = do
  inputDelta <- Arena.takePendingDelta domain arena inputKey
  traverse_ (step inputDelta) relevantEquations
  where
    input = EquationId inputKey
    relevantEquations =
      filter ((`IntSet.member` componentOutputs) . unEquationId . equationOutput) (equationsUsingInput input plan)
    step inputDelta equation = do
      changed <- evaluateEquationForInputM updateOutput domain arena input inputDelta equation
      if changed
        then WorkQueue.enqueue queue (equationIdKey (equationOutput equation))
        else pure ()

evaluatePendingInputM ::
  OutputUpdate value ->
  DeltaDomain value delta ->
  Plan value delta ->
  Arena.Arena state value delta ->
  WorkQueue.WorkQueue state ->
  Int ->
  ST state ()
evaluatePendingInputM updateOutput domain plan arena queue inputKey = do
  inputDelta <- Arena.takePendingDelta domain arena inputKey
  traverse_ (step inputDelta) (equationsUsingInput input plan)
  where
    input =
      EquationId inputKey
    step inputDelta equation = do
      changed <- evaluateEquationForInputM updateOutput domain arena input inputDelta equation
      if changed
        then WorkQueue.enqueue queue (equationIdKey (equationOutput equation))
        else pure ()
{-# INLINE evaluatePendingInputM #-}

evaluateEquationForInputM ::
  OutputUpdate value ->
  DeltaDomain value delta ->
  Arena.Arena state value delta ->
  EquationId ->
  delta ->
  Equation value delta ->
  ST state Bool
evaluateEquationForInputM updateOutput domain arena input inputDelta equation =
  case evaluateDelta equation of
    Just derivative
      | not (deltaNull domain inputDelta) ->
          applyDeltaWithM updateOutput domain arena (equationOutput equation) (derivative input inputDelta)
    _ ->
      evaluateFullEquationChangedWithM updateOutput domain arena equation

evaluateFullEquationM :: DeltaDomain value delta -> Arena.Arena state value delta -> Equation value delta -> ST state ()
evaluateFullEquationM domain arena equation =
  void (evaluateFullEquationChangedM domain arena equation)

evaluateFullEquationChangedM :: DeltaDomain value delta -> Arena.Arena state value delta -> Equation value delta -> ST state Bool
evaluateFullEquationChangedM =
  evaluateFullEquationChangedWithM finiteOutputUpdate

evaluateFullEquationChangedWithM :: OutputUpdate value -> DeltaDomain value delta -> Arena.Arena state value delta -> Equation value delta -> ST state Bool
evaluateFullEquationChangedWithM updateOutput domain arena equation = do
  let outputKey = equationIdKey (equationOutput equation)
  oldValue <- MVector.read (Arena.values arena) outputKey
  newValue <- Arena.evaluate arena (evaluateFull equation)
  applyDeltaWithM updateOutput domain arena (equationOutput equation) (deltaBetween domain oldValue newValue)

applyDeltaWithM :: OutputUpdate value -> DeltaDomain value delta -> Arena.Arena state value delta -> EquationId -> delta -> ST state Bool
applyDeltaWithM updateOutput domain arena (EquationId key) deltaValue
  | deltaNull domain deltaValue = pure False
  | otherwise = do
      oldValue <- MVector.read (Arena.values arena) key
      let candidateValue =
            deltaApply domain deltaValue oldValue
          newValue =
            updateOutput key oldValue candidateValue
          effectiveDelta =
            deltaBetween domain oldValue newValue
      if deltaNull domain effectiveDelta
        then pure False
        else do
          MVector.write (Arena.values arena) key newValue
          Arena.mergePendingDelta domain arena key effectiveDelta
          pure True