khive_pack_brain/
tunable.rs1use khive_runtime::pack::PackRuntime;
2use khive_runtime::RuntimeError;
3use serde::{Deserialize, Serialize};
4use serde_json::Value;
5
6use crate::state::{BalancedRecallState, BetaPosterior};
7
8pub trait PackTunable: PackRuntime {
14 fn parameter_space(&self) -> ParameterSpace;
15 fn project_config(&self, state: &BalancedRecallState) -> Value;
16 fn apply_config(&self, config: Value) -> Result<(), RuntimeError>;
17}
18
19#[derive(Debug, Clone, Serialize, Deserialize)]
20pub struct ParameterSpace {
21 pub parameters: Vec<ParameterDef>,
22}
23
24#[derive(Debug, Clone, Serialize, Deserialize)]
25pub struct ParameterDef {
26 pub name: String,
27 pub prior_alpha: f64,
28 pub prior_beta: f64,
29 pub bounds: (f64, f64),
30}
31
32impl ParameterDef {
33 pub fn prior(&self) -> BetaPosterior {
34 BetaPosterior::new(self.prior_alpha, self.prior_beta)
35 }
36}
37
38#[cfg(test)]
39mod tests {
40 use super::*;
41
42 #[test]
43 fn parameter_def_prior_returns_matching_beta_posterior() {
44 let def = ParameterDef {
45 name: "recall::relevance_weight".into(),
46 prior_alpha: 2.0,
47 prior_beta: 8.0,
48 bounds: (0.0, 1.0),
49 };
50 let prior = def.prior();
51 assert!((prior.alpha - 2.0).abs() < 1e-12);
52 assert!((prior.beta - 8.0).abs() < 1e-12);
53 assert!((prior.mean() - 0.2).abs() < 1e-12);
54 }
55
56 #[test]
57 fn parameter_space_serializes() {
58 let space = ParameterSpace {
59 parameters: vec![ParameterDef {
60 name: "p".into(),
61 prior_alpha: 1.0,
62 prior_beta: 1.0,
63 bounds: (0.0, 1.0),
64 }],
65 };
66 let json = serde_json::to_string(&space).unwrap();
67 let back: ParameterSpace = serde_json::from_str(&json).unwrap();
68 assert_eq!(back.parameters.len(), 1);
69 assert_eq!(back.parameters[0].name, "p");
70 }
71}