///|
/// Exact residual verification for a deterministic candidate tree.
/// Each sibling is a point-mass proposal: accept with its current residual
/// probability; after rejection remove that token and renormalize. Raw draft
/// probabilities guide candidate construction, not this acceptance law.
pub enum TreeEvaluateError {
TreeInvalid
TargetLengthMismatch
RandomLengthMismatch
InvalidTarget(Int)
InvalidRandom(Int)
InconsistentSiblings(Int)
} derive(Eq, Debug)
///|
pub struct TreeEvaluation {
accepted_nodes : Array[Int]
rejected_nodes : Array[Int]
skipped_nodes : Array[Int]
committed_path : Array[Int]
emitted : Array[Int]
}
///|
pub fn TreeEvaluation::evaluated_count(self : TreeEvaluation) -> Int {
self.accepted_nodes.length() + self.rejected_nodes.length()
}
///|
fn contains_id(values : Array[Int], target : Int) -> Bool {
for value in values {
if value == target {
return true
}
}
false
}
///|
/// Targets in node order score the prefix BEFORE each node. Siblings must
/// share one target row. Acceptance draws are indexed by node; fallback draws
/// by depth minus one. Draws must be independent of candidate construction and
/// one another. At a leaf this round ends; the next round produces more tokens.
pub fn evaluate_tree(
tree : DraftTree,
target_distributions : Array[Array[Double]],
uniforms : Array[Double],
fallback_uniforms : Array[Double],
) -> Result[TreeEvaluation, TreeEvaluateError] {
match validate_tree(tree) {
Err(_) => return Err(TreeInvalid)
Ok(_) => ()
}
if target_distributions.length() != tree.nodes.length() {
return Err(TargetLengthMismatch)
}
if uniforms.length() != tree.nodes.length() ||
fallback_uniforms.length() != tree.max_depth() {
return Err(RandomLengthMismatch)
}
for i in 0.. value
Err(_) => return Err(InvalidTarget(node.id))
}
}
match selected {
Some(id) => parent = Some(id)
None => {
let token = match
sample_categorical(residual, fallback_uniforms[depth]) {
Ok(value) => value
Err(_) => return Err(InvalidTarget(siblings[0]))
}
emitted.push(token)
break
}
}
depth = depth + 1
}
let skipped : Array[Int] = []
for node in tree.nodes {
if !contains_id(accepted, node.id) && !contains_id(rejected, node.id) {
skipped.push(node.id)
}
}
Ok({
accepted_nodes: accepted,
rejected_nodes: rejected,
skipped_nodes: skipped,
committed_path: path,
emitted,
})
}
///|
pub fn render_tree_evaluation(value : TreeEvaluation) -> String {
"accepted_nodes=" +
value.accepted_nodes.length().to_string() +
"\nrejected_nodes=" +
value.rejected_nodes.length().to_string() +
"\nskipped_nodes=" +
value.skipped_nodes.length().to_string() +
"\ncommitted_depth=" +
value.committed_path.length().to_string() +
"\nemitted_tokens=" +
value.emitted.length().to_string() +
"\n"
}