File: Mathlib/Data/MLList/BestFirst.lean

full_name: BestFirstNode.estimator
code: /-- A node in a `BestFirstQueue`. -/
structure BestFirstNode {α : Sort*} {ω : Type*} (prio : α → Thunk ω) (ε : α → Type) where
  /-- The data to store at a node, from which we can calculate a priority using `prio`. -/
  key : α
  /-- An estimator for the priority of the key.
  (We will assume we have `[∀ a : α, Estimator (prio a) (ε a)]`.) -/
  estimator : ε key
goal: ε self.key

full_name: BestFirstNode
code: /-- A node in a `BestFirstQueue`. -/
structure BestFirstNode {α : Sort*} {ω : Type*} (prio : α → Thunk ω) (ε : α → Type) where
  /-- The data to store at a node, from which we can calculate a priority using `prio`. -/
  key : α
  /-- An estimator for the priority of the key.
  (We will assume we have `[∀ a : α, Estimator (prio a) (ε a)]`.) -/
  estimator : ε key
goal: Sort (max 1 u_1)

full_name: BestFirstNode.key
code: /-- A node in a `BestFirstQueue`. -/
structure BestFirstNode {α : Sort*} {ω : Type*} (prio : α → Thunk ω) (ε : α → Type) where
  /-- The data to store at a node, from which we can calculate a priority using `prio`. -/
  key : α
  /-- An estimator for the priority of the key.
  (We will assume we have `[∀ a : α, Estimator (prio a) (ε a)]`.) -/
  estimator : ε key
goal: α

full_name: BestFirstNode.estimate
code: /-- Calculate the current best lower bound for the priority of a node. -/
def BestFirstNode.estimate (n : BestFirstNode prio ε) : ω 
goal: ω

full_name: instOrdBestFirstNode
code: instance [Ord ω] [Ord α] : Ord (BestFirstNode prio ε) 
goal: Ord (BestFirstNode prio ε)

full_name: BestFirstQueue
code: /-- A queue of `MLList m β`s, lazily prioritized by lower bounds. -/
@[nolint unusedArguments]
def BestFirstQueue (maxSize : Option Nat) 
goal: Type

full_name: BestFirstQueue.insertAndEject
code: /--
Add a new `MLList m β` to the `BestFirstQueue`, and if this takes the size above `maxSize`,
eject a `MLList` from the tail of the queue.
-/
-- Note this ejects the element with the greatest estimated priority,
-- not necessarily the greatest priority!
def insertAndEject
    (q : BestFirstQueue prio ε m β maxSize) (n : BestFirstNode prio ε) (l : MLList m β) :
    BestFirstQueue prio ε m β maxSize 
goal: BestFirstQueue prio ε m β maxSize

full_name: BestFirstQueue.ensureFirstIsBest
code: /--
By improving priority estimates as needed, and permuting elements,
ensure that the first element of the queue has the greatest priority.
-/
partial def ensureFirstIsBest (q : BestFirstQueue prio ε m β maxSize) :
    m (BestFirstQueue prio ε m β maxSize) 
goal: m (BestFirstQueue prio ε m β maxSize)

full_name: BestFirstQueue.popWithBound
code: /--
Pop a `β` off the `MLList m β` with lowest priority,
also returning the index in `α` and the current best lower bound for its priority.
This may require improving estimates of priorities and shuffling the queue.
-/
partial def popWithBound (q : BestFirstQueue prio ε m β maxSize) :
    m (Option (((a : α) × (ε a) × β) × BestFirstQueue prio ε m β maxSize)) 
goal: m (Option (((a : α) × ε a × β) × BestFirstQueue prio ε m β maxSize))

full_name: BestFirstQueue.popWithPriority
code: /--
Pop a `β` off the `MLList m β` with lowest priority,
also returning the index in `α` and the value of the current best lower bound for its priority.
-/
def popWithPriority (q : BestFirstQueue prio ε m β maxSize) :
    m (Option (((α × ω) × β) × BestFirstQueue prio ε m β maxSize)) 
goal: m (Option (((α × ω) × β) × BestFirstQueue prio ε m β maxSize))

full_name: BestFirstQueue.pop
code: /--
Pop a `β` off the `MLList m β` with lowest priority.
-/
def pop (q : BestFirstQueue prio ε m β maxSize) :
    m (Option ((α × β) × BestFirstQueue prio ε m β maxSize)) 
goal: m (Option ((α × β) × BestFirstQueue prio ε m β maxSize))

full_name: BestFirstQueue.toMLListWithPriority
code: /--
Convert a `BestFirstQueue` to a `MLList ((α × ω) × β)`, by popping off all elements,
recording also the values in `ω` of the best current lower bounds.
-/
-- This is not used in the algorithms below, but can be useful for debugging.
partial def toMLListWithPriority (q : BestFirstQueue prio ε m β maxSize) : MLList m ((α × ω) × β) 
goal: MLList m ((α × ω) × β)

full_name: BestFirstQueue.toMLList
code: /--
Convert a `BestFirstQueue` to a `MLList (α × β)`, by popping off all elements.
-/
def toMLList (q : BestFirstQueue prio ε m β maxSize) : MLList m (α × β) 
goal: MLList m (α × β)

full_name: impl.go
code: /--
Core implementation of `bestFirstSearch`, that works by iteratively updating an internal state,
consisting of a priority queue of `MLList m α`.

At each step we pop an element off the queue,
compute its children (lazily) and put these back on the queue.
-/
def impl (maxSize : Option Nat) (f : α → MLList m α) (a : α) : MLList m α 
goal: BestFirstQueue prio ε m α maxSize → m (α × BestFirstQueue prio ε m α maxSize)

full_name: impl
code: /--
Core implementation of `bestFirstSearch`, that works by iteratively updating an internal state,
consisting of a priority queue of `MLList m α`.

At each step we pop an element off the queue,
compute its children (lazily) and put these back on the queue.
-/
def impl (maxSize : Option Nat) (f : α → MLList m α) (a : α) : MLList m α 
goal: MLList m α

full_name: implMaxDepth
code: /--
Wrapper for `impl` implementing the `maxDepth` option.
-/
def implMaxDepth (maxSize : Option Nat) (maxDepth : Option Nat) (f : α → MLList m α) (a : α) :
    MLList m α 
goal: MLList m α

full_name: bestFirstSearchCore
code: /--
A lazy list recording the best first search of a graph generated by a function
`f : α → MLList m α`.

We maintain a priority queue of visited-but-not-exhausted nodes,
and at each step take the next child of the highest priority node in the queue.

The option `maxDepth` limits the search depth.

The option `maxQueued` bounds the size of the priority queue,
discarding the lowest priority nodes as needed.
This implements a "beam" search, which may be incomplete but uses bounded memory.

The option `removeDuplicates` keeps an `RBSet` of previously visited nodes.
Otherwise, if the graph is not a tree then nodes will be visited multiple times.

This version allows specifying a custom priority function `prio : α → Thunk ω`
along with estimators `ε : α → Type` equipped with `[∀ a, Estimator (prio a) (ε a)]`
that control the behaviour of the priority queue.
This function returns values `a : α` that have
the lowest possible `prio a` amongst unvisited neighbours of visited nodes,
but lazily estimates these priorities to avoid unnecessary computations.
-/
def bestFirstSearchCore (f : α → MLList m α) (a : α)
    (β : Type _) [Ord β] (removeDuplicatesBy? : Option (α → β) := none)
    (maxQueued : Option Nat := none) (maxDepth : Option Nat := none)  :
    MLList m α 
goal: MLList m α

full_name: instOrderBotEq
code: /-- A local instance that enables using "the actual value" as a priority estimator,
for simple use cases. -/
local instance instOrderBotEq {a : α} : OrderBot { x : α // x = a } 
goal: OrderBot { x // x = a }

full_name: bestFirstSearch
code: /--
A lazy list recording the best first search of a graph generated by a function
`f : α → MLList m α`.

We maintain a priority queue of visited-but-not-exhausted nodes,
and at each step take the next child of the highest priority node in the queue.

The option `maxDepth` limits the search depth.

The option `maxQueued` bounds the size of the priority queue,
discarding the lowest priority nodes as needed.
This implements a "beam" search, which may be incomplete but uses bounded memory.

The option `removeDuplicates` keeps an `RBSet` of previously visited nodes.
Otherwise, if the graph is not a tree then nodes will be visited multiple times.

This function returns values `a : α` that are least in the `[LinearOrder α]`
amongst unvisited neighbours of visited nodes.
-/
-- Although the core implementation lazily computes estimates of priorities,
-- this version does not take advantage of those features.
def bestFirstSearch (f : α → MLList m α) (a : α)
    (maxQueued : Option Nat := none) (maxDepth : Option Nat := none) (removeDuplicates := true) :
    MLList m α 
goal: MLList m α