use rayon::prelude::*;
use tfhe::FheInt32;
use tfhe::prelude::*;
use crate::eval::Evaluator;
use crate::model::WeirwoodTree;
use super::client::{EncryptedInput, EncryptedScore, SCALE};
use super::server::ServerContext;
pub struct FheEvaluator {
thread_pool: rayon::ThreadPool,
}
impl FheEvaluator {
pub fn new(ctx: ServerContext) -> Self {
let sk = ctx.server_key.clone();
let thread_pool = rayon::ThreadPoolBuilder::new()
.start_handler(move |_| tfhe::set_server_key(sk.clone()))
.build()
.expect("failed to build Rayon thread pool for FHE evaluation");
FheEvaluator { thread_pool }
}
}
fn eval_node(tree: &crate::model::Tree, node_idx: usize, features: &EncryptedInput) -> FheInt32 {
let node: &crate::model::Node = &tree.nodes[node_idx];
if node.is_leaf() {
let scaled: i32 = (node.leaf_value * SCALE)
.round()
.clamp(i32::MIN as f32, i32::MAX as f32) as i32;
FheInt32::encrypt_trivial(scaled)
} else {
let threshold: i32 = (node.split_threshold * SCALE).round() as i32;
let go_left: tfhe::FheBool = features[node.split_feature as usize].le(threshold);
let left_score: FheInt32 = eval_node(tree, node.left_child as usize, features);
let right_score: FheInt32 = eval_node(tree, node.right_child as usize, features);
go_left.if_then_else(&left_score, &right_score)
}
}
impl Evaluator for FheEvaluator {
type Input = EncryptedInput;
type Output = EncryptedScore;
fn predict(
&self,
weirwood_tree: &WeirwoodTree,
encrypted_features: &EncryptedInput,
) -> EncryptedScore {
let tree_scores: Vec<FheInt32> = self.thread_pool.install(|| {
weirwood_tree
.trees
.par_iter()
.map(|tree| eval_node(tree, 0, encrypted_features))
.collect()
});
let base_scaled = (weirwood_tree.base_score * SCALE)
.round()
.clamp(i32::MIN as f32, i32::MAX as f32) as i32;
let mut total: FheInt32 = FheInt32::encrypt_trivial(base_scaled);
for score in tree_scores {
total += score;
}
total
}
}