OnnxEvalSearch

dicechess.engine.search.OnnxEvalSearch
See theOnnxEvalSearch companion object
class OnnxEvalSearch(modelPath: String, extractFeatures: (GameState, Color) => Array[Float] = ..., clock: () => Long = ...) extends TimeBudgetedSearch, AutoCloseable

One-ply search scored by an externally-trained model (LightGBM, exported to ONNX) instead of a hand-tuned heuristic. Structurally identical to GreedySearch/AggressiveSearch — the only difference is the evalFn handed to SearchScoring.scorePath.

The ONNX session is created once per instance (session creation, not inference, is the expensive part) and is not thread-safe to call concurrently from multiple threads without external synchronization — matches every other bot in this codebase, none of which claim thread safety either.

As a TimeBudgetedSearch it honours a wall-clock deadline by scoring candidates through the model in OnnxEvalSearch.BatchSize-sized chunks, checking the clock between chunks rather than only across the whole candidate set — see findBestMove(state:dicechess\.engine\.domain\.GameState,deadlineNanos:Long,random:scala\.util\.Random)*.

Value parameters

clock

nanosecond time source (defaults to java.lang.System.nanoTime); injectable for deterministic deadline testing

extractFeatures

feature extraction function converting a position and mover perspective to an input row for the model

modelPath

filesystem path to the ONNX model file

Attributes

Companion
object
Source
OnnxEvalSearch.scala
Graph
Supertypes
trait AutoCloseable
class Object
trait Matchable
class Any
Show all

Members list

Value members

Concrete methods

override def close(): Unit

Attributes

Definition Classes
AutoCloseable
Source
OnnxEvalSearch.scala
override def findBestMove(state: GameState): Option[ScoredSequence]

Finds and returns the best full-turn path according to this strategy.

Finds and returns the best full-turn path according to this strategy.

Value parameters

state

the current dicechess.engine.domain.GameState; state.activeColor identifies the side to move

Attributes

Returns

Some([[ScoredSequence]]) when at least one legal path exists; None when the player must pass

Definition Classes
Source
OnnxEvalSearch.scala
override def findBestMove(state: GameState, rand: Random): Option[ScoredSequence]

Finds the best full-turn path, drawing any randomness (e.g. tie-breaking among equally-valued turns) from the supplied random. Lets a caller make a whole game reproducible from one seed — most importantly the bot arena, which otherwise gets a fresh unseeded Random per move and so is non-deterministic.

Finds the best full-turn path, drawing any randomness (e.g. tie-breaking among equally-valued turns) from the supplied random. Lets a caller make a whole game reproducible from one seed — most importantly the bot arena, which otherwise gets a fresh unseeded Random per move and so is non-deterministic.

The default ignores random and delegates to the no-arg findBestMove; randomised strategies override it to route their tie-breaking through the given source.

Attributes

Definition Classes
Source
OnnxEvalSearch.scala
override def findBestMove(state: GameState, deadlineNanos: Long, random: Random): Option[ScoredSequence]

Finds the best turn under a wall-clock deadline, scoring the model in OnnxEvalSearch.BatchSize-row chunks so the deadline can be honoured between chunks rather than only across the whole candidate set (#497) — mind the granularity point in TimeBudgetedSearch's Scaladoc.

Finds the best turn under a wall-clock deadline, scoring the model in OnnxEvalSearch.BatchSize-row chunks so the deadline can be honoured between chunks rather than only across the whole candidate set (#497) — mind the granularity point in TimeBudgetedSearch's Scaladoc.

Strategy, mirroring MonteCarloSearch's deadline path: a cheap material pre-score (Evaluator.evaluateMaterial via SearchScoring.scorePath) takes any immediate king capture for free — scorePath assigns SearchScoring.TerminalWinScore regardless of the evaluator — and otherwise doubles as the anytime fallback if the deadline is already past before the first chunk runs. Every remaining candidate's resulting position is then scored through the model, one chunk at a time, keeping the best (ties broken via random, as in the untimed path) of whatever chunks complete before the deadline.

Attributes

Definition Classes
Source
OnnxEvalSearch.scala
def onnxEval(state: GameState, color: Color): Int

Runs the ONNX session on state's features (via extractFeatures) from color's perspective, scaled onto scorePath's Int axis.

Runs the ONNX session on state's features (via extractFeatures) from color's perspective, scaled onto scorePath's Int axis.

Attributes

Source
OnnxEvalSearch.scala
def onnxEvalBatch(states: Array[GameState], color: Color): Array[Int]

Batched onnxEval: evaluates many positions from a single color's perspective in one session run.

Batched onnxEval: evaluates many positions from a single color's perspective in one session run.

Session-run cost is dominated by per-call overhead (JNI boundary, graph setup), not by the number of rows, so folding N positions into one [N × F] tensor is far cheaper than N separate [1 × F] runs. This is the hot path for a multi-leaf search — e.g. an expectimax chance node integrating over the 56 dice outcomes (see DiceRolls), whose leaves are all scored from the root mover's perspective. Scores come back on the same scaled axis as onnxEval, one per input in order; an empty input yields an empty result.

Attributes

Source
OnnxEvalSearch.scala
def onnxEvalBatchChunked(states: Array[GameState], color: Color, chunkSize: Int): Array[Int]

onnxEvalBatch in bounded chunks: the same scores, in the same order, from several session runs instead of one.

onnxEvalBatch in bounded chunks: the same scores, in the same order, from several session runs instead of one.

One tensor per call is right where the batch is bounded by the problem — a chance node's leaves, a root's candidate limit. It is wrong where the batch is bounded by the position: pre-ranking sees every legal turn, and Dice Chess routinely offers hundreds and can offer thousands, so a single run would allocate one [thousands × F] float tensor and hand the native runtime one uninterruptible call whose size no configuration bounds. Chunking bounds the tensor without changing a single score — inference here is row-independent, so the split is invisible to the model.

Value parameters

chunkSize

maximum rows per session run; must be positive

Attributes

Source
OnnxEvalSearch.scala
def validateContract(manifest: ModelManifest): Either[String, Unit]

Checks this instance's own loaded graph against manifest — the tensor names, dtypes and shapes the manifest promises (dicechess.engine.model.OnnxModelContract).

Checks this instance's own loaded graph against manifest — the tensor names, dtypes and shapes the manifest promises (dicechess.engine.model.OnnxModelContract).

The session is private and stays private: a caller cannot validate "the session this bot uses" from outside, and validating a second, identical session would prove something about that one instead. This is the only way to check the graph that will actually serve the positions.

Attributes

Source
OnnxEvalSearch.scala

Inherited methods

protected def estimateWinProbability(state: GameState): Double

Estimates the winning probability in [0.0, 1.0] for the side to move. Convenience over winProbability for offer decisions, where the deciding side is the active colour; do not override it, override winProbability.

Estimates the winning probability in [0.0, 1.0] for the side to move. Convenience over winProbability for offer decisions, where the deciding side is the active colour; do not override it, override winProbability.

Attributes

Inherited from:
SearchAlgorithm
Source
SearchAlgorithm.scala
def shouldAcceptDouble(state: GameState, currentStake: Int): Boolean

Determines whether the bot should accept (Take) or decline (Drop) a double from the opponent, taking the side to move as the responder. Kept for source and binary compatibility; it delegates to the three-argument overload.

Determines whether the bot should accept (Take) or decline (Drop) a double from the opponent, taking the side to move as the responder. Kept for source and binary compatibility; it delegates to the three-argument overload.

Value parameters

currentStake

the proposed double stake (e.g. 2, 4...)

state

current game state (dice pool is empty)

Attributes

Returns

true to accept the double (Take), false to resign the current stake (Drop)

Note

Perspective hazard: this overload evaluates the win probability of state.activeColor. In a double decision state, state.activeColor is the player who offered the double, NOT the responder, so a caller that feeds the delivered position here asks whether the offerer should take. Callers must prefer shouldAcceptDouble(state, currentStake, responder); strategies must override that overload, not this one.

Inherited from:
SearchAlgorithm
Source
SearchAlgorithm.scala
def shouldAcceptDouble(state: GameState, currentStake: Int, responder: Color): Boolean

Determines whether the bot should accept (Take) or decline (Drop) a double from the opponent, evaluated from the perspective of responder.

Determines whether the bot should accept (Take) or decline (Drop) a double from the opponent, evaluated from the perspective of responder.

This is the single policy point for the take/drop decision: strategies that want their own threshold or estimator override this overload. The two-argument form below only delegates here, so a policy written against it is bypassed whenever a caller names the responder explicitly (which every caller holding a doubleDecision state must, see the hazard note there).

Value parameters

currentStake

the proposed double stake (e.g. 2, 4...)

responder

the colour of the player responding to the double offer

state

current game state (dice pool is empty); its active colour is the side to move, which in a double decision is the offerer, not the responder

Attributes

Returns

true to accept the double (Take), false to resign the current stake (Drop)

Inherited from:
SearchAlgorithm
Source
SearchAlgorithm.scala
def shouldAcceptDraw(state: GameState): Boolean

Determines whether the bot should accept a draw offered by the opponent.

Determines whether the bot should accept a draw offered by the opponent.

Value parameters

state

current game state

Attributes

Returns

true to accept the draw

Inherited from:
SearchAlgorithm
Source
SearchAlgorithm.scala
def shouldOfferDouble(state: GameState, currentStake: Int): Boolean

Determines whether the bot should offer a double before its dice roll.

Determines whether the bot should offer a double before its dice roll.

Value parameters

currentStake

the current stake of the game

state

current game state (dice pool is empty)

Attributes

Returns

true to offer a double, false otherwise

Inherited from:
SearchAlgorithm
Source
SearchAlgorithm.scala
def shouldOfferDraw(state: GameState): Boolean

Determines whether the bot should offer a draw in the current position.

Determines whether the bot should offer a draw in the current position.

Value parameters

state

current game state

Attributes

Returns

true to offer a draw

Inherited from:
SearchAlgorithm
Source
SearchAlgorithm.scala
protected def winProbability(state: GameState, color: Color): Double

Estimates the winning probability in [0.0, 1.0] of color, whichever side is to move.

Estimates the winning probability in [0.0, 1.0] of color, whichever side is to move.

This is the estimator hook: a strategy with its own equity model (for example a Monte-Carlo rollout) overrides this method, so that both the offer decision (active colour) and the take/drop decision (responder colour) see the same estimate. The default maps the centipawn evaluation to a probability with a logistic sigmoid.

Attributes

Inherited from:
SearchAlgorithm
Source
SearchAlgorithm.scala