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