1crate::ix!();
3
4#[derive(
33 SaveLoad,
34 Debug,
35 Clone,
36 PartialEq,
37 Getters,
38 Builder,
39 Serialize,
40 Deserialize,
41 AiJsonTemplate,
42 AiJsonTemplateWithJustification,
43)]
44#[builder(pattern = "owned", setter(into), build_fn(name = "try_build"))]
45#[getset(get = "pub")]
46pub struct GrowerTreeConfiguration {
47
48 #[builder(default = "\"target\".to_string()")]
50 #[serde(default)]
51 target_name: String,
52
53 #[builder(default = "4")]
57 #[serde(deserialize_with = "fuzzy_u8")]
58 depth: u8,
59
60 #[builder(default = "2")]
64 #[serde(deserialize_with = "fuzzy_u8")]
65 breadth: u8,
66
67 #[builder(default = "2")]
71 #[serde(deserialize_with = "fuzzy_u8")]
72 density: u8,
73
74 #[builder(default = "0.5")]
78 leaf_granularity: f32,
79
80 #[builder(default = "0.5")]
84 balance_symmetry: f32,
85
86 #[builder(default)]
88 complexity: ConfigurationComplexity,
89
90 #[builder(default)]
92 level_specific: Option<TreeLevelSpecificConfiguration>,
93
94 #[builder(default)]
96 weighted_branching: Option<WeightedBranchingConfiguration>,
97
98 #[builder(default)]
100 level_skipping: Option<LevelSkippingConfiguration>,
101
102 #[builder(default)]
105 capstone: Option<CapstoneGenerationConfiguration>,
106
107 #[builder(default)]
109 ordering: Option<SubBranchOrdering>,
110
111 #[builder(default)]
113 ai_confidence: Option<AiTreeBranchingConfidenceConfiguration>,
114
115 #[builder(default = "0.5")]
124 aggregator_preference: f32,
125
126 #[builder(default = "false")]
130 allow_early_leaves: bool,
131
132 #[builder(default = "1.0")]
139 partial_subbranch_probability: f32,
140
141 #[builder(default)]
148 tree_expansion_policy: TreeExpansionPolicy,
149
150 #[builder(default)]
154 #[serde(deserialize_with = "fuzzy_option_u8")]
155 #[serde(default)]
156 aggregator_depth_limit: Option<u8>,
157
158 #[builder(default)]
162 #[serde(deserialize_with = "fuzzy_option_u8")]
163 #[serde(default)]
164 dispatch_depth_limit: Option<u8>,
165
166 #[builder(default)]
170 #[serde(deserialize_with = "fuzzy_option_u8")]
171 #[serde(default)]
172 leaf_min_depth: Option<u8>,
173}
174
175impl Default for GrowerTreeConfiguration {
176 fn default() -> Self {
177 GrowerTreeConfigurationBuilder::default()
178 .build()
179 .unwrap()
180 }
181}
182
183impl GrowerTreeConfigurationBuilder {
184 pub fn build(
186 self,
187 ) -> Result<GrowerTreeConfiguration, GrowerTreeConfigurationError> {
188
189 let config = self.try_build()?;
193 config.validate()?;
195 Ok(config)
196 }
197}
198
199impl GrowerTreeConfiguration {
200
201 #[instrument(level = "trace", skip(self))]
204 pub fn validate(&self) -> Result<(), GrowerTreeConfigurationError> {
205 if self.depth == 0 {
206 return Err(GrowerTreeConfigurationError::DepthMustBeAtLeastOne);
207 }
208 if self.breadth == 0 {
209 return Err(GrowerTreeConfigurationError::BreadthMustBeAtLeastOne);
210 }
211 if self.density == 0 {
212 return Err(GrowerTreeConfigurationError::DensityMustBeAtLeastOne);
213 }
214
215 if !(0.0..=1.0).contains(&self.leaf_granularity) {
216 return Err(GrowerTreeConfigurationError::LeafGranularityMustBeWithinZeroOne);
217 }
218 if !(0.0..=1.0).contains(&self.balance_symmetry) {
219 return Err(GrowerTreeConfigurationError::BalanceSymmetryMustBeWithinZeroOne);
220 }
221
222 if let Some(ref ls) = self.level_specific {
223 ls.validate(self.depth)?;
224 }
225 if let Some(ref wb) = self.weighted_branching {
226 wb.validate()?;
227 }
228 if let Some(ref skip) = self.level_skipping {
229 skip.validate(self.depth)?;
230 }
231 if let Some(ref cap) = self.capstone {
232 cap.validate()?;
233 }
234 if let Some(ref ai_conf) = self.ai_confidence {
235 ai_conf.validate()?;
236 }
237
238 Ok(())
239 }
240
241 #[instrument(level = "trace")]
243 pub fn from_toml_str(s: &str) -> Result<Self, GrowerTreeConfigurationError> {
244 let cfg: GrowerTreeConfiguration = toml::from_str(s)
245 .map_err(GrowerTreeConfigurationError::TomlDeError)?;
246 Ok(cfg)
247 }
248
249 #[instrument(level = "trace", skip(self))]
251 pub fn to_toml_string(&self) -> Result<String, GrowerTreeConfigurationError> {
252 let out = toml::to_string_pretty(self)
253 .map_err(GrowerTreeConfigurationError::TomlSerError)?;
254 Ok(out)
255 }
256
257 #[instrument(level = "trace", skip(path))]
259 pub fn from_toml_file<P: AsRef<Path>>(path: P) -> Result<Self, GrowerTreeConfigurationError> {
260 let contents = std::fs::read_to_string(path)
261 .map_err(GrowerTreeConfigurationError::IoError)?;
262 Self::from_toml_str(&contents)
263 }
264
265 #[instrument(level = "trace", skip(self, path))]
267 pub fn to_toml_file<P: AsRef<Path>>(&self, path: P) -> Result<(), GrowerTreeConfigurationError> {
268 let data = self.to_toml_string()?;
269 std::fs::write(path, data).map_err(GrowerTreeConfigurationError::IoError)?;
270 Ok(())
271 }
272}
273
274impl FuzzyFromJsonValue for JustifiedGrowerTreeConfiguration {
275 fn fuzzy_from_json_value(value: &serde_json::Value) -> Result<Self, FuzzyFromJsonValueError> {
276 trace!("(JustifiedGrowerTreeConfiguration) Entering fuzzy_from_json_value");
277
278 let mut obj = match value.as_object() {
280 Some(m) => {
281 trace!("(JustifiedGrowerTreeConfiguration) Received JSON object => BFS-flatten all 'fields'.");
282 let mut cloned = m.clone();
283 flatten_all_fields(&mut cloned);
284 cloned
285 }
286 None => {
287 error!("(JustifiedGrowerTreeConfiguration) Received non-object => cannot parse!");
288 return Err(FuzzyFromJsonValueError::NotAnObject {
289 target_type: "JustifiedGrowerTreeConfiguration",
290 actual: value.clone(),
291 });
292 }
293 };
294
295 let target_name_val = if let Some(raw) = obj.get("target_name") {
296 raw.to_string().to_kebab_case()
297 } else {
298 warn!("(JustifiedGrowerTreeConfiguration) 'target-name' missing => will default to target-name-unset");
299 "target-name-unset".to_string()
300 };
301
302 let target_name_conf = get_f64_field(&obj, "target_name_confidence", "JustifiedGrowerTreeConfiguration").unwrap_or(0.0);
303 let target_name_just = get_string_field(&obj, "target_name_justification", "JustifiedGrowerTreeConfiguration").unwrap_or_default();
304
305 let depth_val = if let Some(raw) = obj.get("depth") {
307 match fuzzy_u8_from_value(raw) {
308 Ok(u) => u,
309 Err(e) => {
310 error!("(JustifiedGrowerTreeConfiguration) parse 'depth' => {}", e);
311 return Err(FuzzyFromJsonValueError::Other {
312 target_type: "JustifiedGrowerTreeConfiguration",
313 detail: format!("Cannot parse depth: {}", e),
314 });
315 }
316 }
317 } else {
318 debug!("(JustifiedGrowerTreeConfiguration) 'depth' missing => default 0");
319 0
320 };
321 let depth_conf = get_f64_field(&obj, "depth_confidence", "JustifiedGrowerTreeConfiguration").unwrap_or(0.0);
322 let depth_just = get_string_field(&obj, "depth_justification", "JustifiedGrowerTreeConfiguration").unwrap_or_default();
323
324 let breadth_val = if let Some(raw) = obj.get("breadth") {
326 match fuzzy_u8_from_value(raw) {
327 Ok(u) => u,
328 Err(e) => {
329 error!("(JustifiedGrowerTreeConfiguration) parse 'breadth' => {}", e);
330 return Err(FuzzyFromJsonValueError::Other {
331 target_type: "JustifiedGrowerTreeConfiguration",
332 detail: format!("Cannot parse breadth: {}", e),
333 });
334 }
335 }
336 } else {
337 debug!("(JustifiedGrowerTreeConfiguration) 'breadth' missing => default 0");
338 0
339 };
340 let breadth_conf = get_f64_field(&obj, "breadth_confidence", "JustifiedGrowerTreeConfiguration").unwrap_or(0.0);
341 let breadth_just = get_string_field(&obj, "breadth_justification", "JustifiedGrowerTreeConfiguration").unwrap_or_default();
342
343 let density_val = if let Some(raw) = obj.get("density") {
345 match fuzzy_u8_from_value(raw) {
346 Ok(u) => u,
347 Err(e) => {
348 error!("(JustifiedGrowerTreeConfiguration) parse 'density' => {}", e);
349 return Err(FuzzyFromJsonValueError::Other {
350 target_type: "JustifiedGrowerTreeConfiguration",
351 detail: format!("Cannot parse density: {}", e),
352 });
353 }
354 }
355 } else {
356 debug!("(JustifiedGrowerTreeConfiguration) 'density' missing => default 0");
357 0
358 };
359 let density_conf = get_f64_field(&obj, "density_confidence", "JustifiedGrowerTreeConfiguration").unwrap_or(0.0);
360 let density_just = get_string_field(&obj, "density_justification", "JustifiedGrowerTreeConfiguration").unwrap_or_default();
361
362 let lg_f64 = get_f64_field(&obj, "leaf_granularity", "JustifiedGrowerTreeConfiguration").unwrap_or(0.0);
364 let leaf_granularity = lg_f64 as f32;
365 let lg_conf = get_f64_field(&obj, "leaf_granularity_confidence", "JustifiedGrowerTreeConfiguration").unwrap_or(0.0);
366 let lg_just = get_string_field(&obj, "leaf_granularity_justification", "JustifiedGrowerTreeConfiguration").unwrap_or_default();
367
368 let bs_f64 = get_f64_field(&obj, "balance_symmetry", "JustifiedGrowerTreeConfiguration").unwrap_or(0.0);
370 let balance_symmetry = bs_f64 as f32;
371 let bs_conf = get_f64_field(&obj, "balance_symmetry_confidence", "JustifiedGrowerTreeConfiguration").unwrap_or(0.0);
372 let bs_just = get_string_field(&obj, "balance_symmetry_justification", "JustifiedGrowerTreeConfiguration").unwrap_or_default();
373
374 let ap_f64 = get_f64_field(&obj, "aggregator_preference", "JustifiedGrowerTreeConfiguration").unwrap_or(0.0);
376 let aggregator_preference = ap_f64 as f32;
377 let ap_conf = get_f64_field(&obj, "aggregator_preference_confidence", "JustifiedGrowerTreeConfiguration").unwrap_or(0.0);
378 let ap_just = get_string_field(&obj, "aggregator_preference_justification", "JustifiedGrowerTreeConfiguration").unwrap_or_default();
379
380 let allow_early_leaves = match obj.get("allow_early_leaves") {
382 Some(serde_json::Value::Bool(b)) => *b,
383 _ => {
384 debug!("(JustifiedGrowerTreeConfiguration) no 'allow_early_leaves' => false");
385 false
386 }
387 };
388 let ael_conf = get_f64_field(&obj, "allow_early_leaves_confidence", "JustifiedGrowerTreeConfiguration").unwrap_or(0.0);
389 let ael_just = get_string_field(&obj, "allow_early_leaves_justification", "JustifiedGrowerTreeConfiguration").unwrap_or_default();
390
391 let psp_f64 = get_f64_field(&obj, "partial_subbranch_probability", "JustifiedGrowerTreeConfiguration").unwrap_or(1.0);
393 let partial_subbranch_probability = psp_f64 as f32;
394 let psp_conf = get_f64_field(&obj, "partial_subbranch_probability_confidence", "JustifiedGrowerTreeConfiguration").unwrap_or(0.0);
395 let psp_just = get_string_field(&obj, "partial_subbranch_probability_justification", "JustifiedGrowerTreeConfiguration").unwrap_or_default();
396
397 let adl_val = obj.get("aggregator_depth_limit");
399 let aggregator_depth_limit = match adl_val {
400 Some(val) => match fuzzy_option_u8_from_value(val) {
401 Ok(opt) => opt,
402 Err(e) => {
403 error!("(JustifiedGrowerTreeConfiguration) aggregator_depth_limit parse => {}", e);
404 return Err(FuzzyFromJsonValueError::Other {
405 target_type: "JustifiedGrowerTreeConfiguration",
406 detail: format!("Error aggregator_depth_limit: {:?}", e),
407 });
408 }
409 },
410 None => None,
411 };
412 let adl_conf = get_f64_field(&obj, "aggregator_depth_limit_confidence", "JustifiedGrowerTreeConfiguration").unwrap_or(0.0);
413 let adl_just = get_string_field(&obj, "aggregator_depth_limit_justification", "JustifiedGrowerTreeConfiguration").unwrap_or_default();
414
415 let ddl_val = obj.get("dispatch_depth_limit");
417 let dispatch_depth_limit = match ddl_val {
418 Some(val) => match fuzzy_option_u8_from_value(val) {
419 Ok(opt) => opt,
420 Err(e) => {
421 error!("(JustifiedGrowerTreeConfiguration) dispatch_depth_limit parse => {}", e);
422 return Err(FuzzyFromJsonValueError::Other {
423 target_type: "JustifiedGrowerTreeConfiguration",
424 detail: format!("Error dispatch_depth_limit: {:?}", e),
425 });
426 }
427 },
428 None => None,
429 };
430 let ddl_conf = get_f64_field(&obj, "dispatch_depth_limit_confidence", "JustifiedGrowerTreeConfiguration").unwrap_or(0.0);
431 let ddl_just = get_string_field(&obj, "dispatch_depth_limit_justification", "JustifiedGrowerTreeConfiguration").unwrap_or_default();
432
433 let lmd_val = obj.get("leaf_min_depth");
435 let leaf_min_depth = match lmd_val {
436 Some(val) => match fuzzy_option_u8_from_value(val) {
437 Ok(opt) => opt,
438 Err(e) => {
439 error!("(JustifiedGrowerTreeConfiguration) leaf_min_depth parse => {}", e);
440 return Err(FuzzyFromJsonValueError::Other {
441 target_type: "JustifiedGrowerTreeConfiguration",
442 detail: format!("Error leaf_min_depth: {:?}", e),
443 });
444 }
445 },
446 None => None,
447 };
448 let lmd_conf = get_f64_field(&obj, "leaf_min_depth_confidence", "JustifiedGrowerTreeConfiguration").unwrap_or(0.0);
449 let lmd_just = get_string_field(&obj, "leaf_min_depth_justification", "JustifiedGrowerTreeConfiguration").unwrap_or_default();
450
451 let complexity_val = obj.get("complexity");
453 let (complexity, complexity_conf, complexity_just) = match complexity_val {
454 Some(cv) => {
455 let cparsed = JustifiedConfigurationComplexity::fuzzy_from_json_value(cv)?;
456 let cc = get_f64_field(&obj, "complexity_confidence", "JustifiedGrowerTreeConfiguration").unwrap_or(0.0);
457 let cj = get_string_field(&obj, "complexity_justification", "JustifiedGrowerTreeConfiguration").unwrap_or_default();
458 (cparsed, cc, cj)
459 }
460 None => {
461 debug!("(JustifiedGrowerTreeConfiguration) no 'complexity' => default Balanced");
462 let fallback = JustifiedConfigurationComplexity::Balanced {
463 variant_confidence: 0.0,
464 variant_justification: "".to_string(),
465 };
466 (fallback, 0.0, "".to_string())
467 }
468 };
469
470 let level_spec_parsed = match obj.get("level_specific") {
472 Some(x) => Some(JustifiedTreeLevelSpecificConfiguration::fuzzy_from_json_value(x)?),
473 None => None,
474 };
475 let ls_conf = get_f64_field(&obj, "level_specific_confidence", "JustifiedGrowerTreeConfiguration").unwrap_or(0.0);
476 let ls_just = get_string_field(&obj, "level_specific_justification", "JustifiedGrowerTreeConfiguration").unwrap_or_default();
477
478 let wb_parsed = match obj.get("weighted_branching") {
480 Some(x) => Some(JustifiedWeightedBranchingConfiguration::fuzzy_from_json_value(x)?),
481 None => None,
482 };
483 let wb_conf = get_f64_field(&obj, "weighted_branching_confidence", "JustifiedGrowerTreeConfiguration").unwrap_or(0.0);
484 let wb_just = get_string_field(&obj, "weighted_branching_justification", "JustifiedGrowerTreeConfiguration").unwrap_or_default();
485
486 let lskip_parsed = match obj.get("level_skipping") {
488 Some(x) => Some(JustifiedLevelSkippingConfiguration::fuzzy_from_json_value(x)?),
489 None => None,
490 };
491 let lskip_conf = get_f64_field(&obj, "level_skipping_confidence", "JustifiedGrowerTreeConfiguration").unwrap_or(0.0);
492 let lskip_just = get_string_field(&obj, "level_skipping_justification", "JustifiedGrowerTreeConfiguration").unwrap_or_default();
493
494 let cap_parsed = match obj.get("capstone") {
496 Some(x) => Some(JustifiedCapstoneGenerationConfiguration::fuzzy_from_json_value(x)?),
497 None => None,
498 };
499 let cap_conf = get_f64_field(&obj, "capstone_confidence", "JustifiedGrowerTreeConfiguration").unwrap_or(0.0);
500 let cap_just = get_string_field(&obj, "capstone_justification", "JustifiedGrowerTreeConfiguration").unwrap_or_default();
501
502 let ordering_parsed = match obj.get("ordering") {
504 Some(x) => Some(JustifiedSubBranchOrdering::fuzzy_from_json_value(x)?),
505 None => None,
506 };
507 let ordering_conf = get_f64_field(&obj, "ordering_confidence", "JustifiedGrowerTreeConfiguration").unwrap_or(0.0);
508 let ordering_just = get_string_field(&obj, "ordering_justification", "JustifiedGrowerTreeConfiguration").unwrap_or_default();
509
510 let ai_conf_parsed = match obj.get("ai_confidence") {
512 Some(x) => Some(JustifiedAiTreeBranchingConfidenceConfiguration::fuzzy_from_json_value(x)?),
513 None => None,
514 };
515 let aic_conf = get_f64_field(&obj, "ai_confidence_confidence", "JustifiedGrowerTreeConfiguration").unwrap_or(0.0);
516 let aic_just = get_string_field(&obj, "ai_confidence_justification", "JustifiedGrowerTreeConfiguration").unwrap_or_default();
517
518 let tep_val = obj.get("tree_expansion_policy");
520 let (tree_expansion_policy, tep_conf, tep_just) = match tep_val {
521 Some(tv) => {
522 let parsed = JustifiedTreeExpansionPolicy::fuzzy_from_json_value(tv)?;
523 let cf = get_f64_field(&obj, "tree_expansion_policy_confidence", "JustifiedGrowerTreeConfiguration").unwrap_or(0.0);
524 let cj = get_string_field(&obj, "tree_expansion_policy_justification", "JustifiedGrowerTreeConfiguration").unwrap_or_default();
525 (parsed, cf, cj)
526 }
527 None => {
528 debug!("(JustifiedGrowerTreeConfiguration) no 'tree_expansion_policy' => default=>Simple");
529 let fallback = JustifiedTreeExpansionPolicy::Simple {
530 variant_confidence: 0.0,
531 variant_justification: "".to_string(),
532 };
533 (fallback, 0.0, "".to_string())
534 }
535 };
536
537 let jcfg = JustifiedGrowerTreeConfiguration {
538 target_name: target_name_val,
539 target_name_confidence: target_name_conf,
540 target_name_justification: target_name_just,
541 depth: depth_val,
542 depth_confidence: depth_conf,
543 depth_justification: depth_just,
544
545 breadth: breadth_val,
546 breadth_confidence: breadth_conf,
547 breadth_justification: breadth_just,
548
549 density: density_val,
550 density_confidence: density_conf,
551 density_justification: density_just,
552
553 leaf_granularity,
554 leaf_granularity_confidence: lg_conf,
555 leaf_granularity_justification: lg_just,
556
557 balance_symmetry,
558 balance_symmetry_confidence: bs_conf,
559 balance_symmetry_justification: bs_just,
560
561 complexity,
562 complexity_confidence: complexity_conf,
563 complexity_justification: complexity_just,
564
565 level_specific: level_spec_parsed,
566 level_specific_confidence: ls_conf,
567 level_specific_justification: ls_just,
568
569 weighted_branching: wb_parsed,
570 weighted_branching_confidence: wb_conf,
571 weighted_branching_justification: wb_just,
572
573 level_skipping: lskip_parsed,
574 level_skipping_confidence: lskip_conf,
575 level_skipping_justification: lskip_just,
576
577 capstone: cap_parsed,
578 capstone_confidence: cap_conf,
579 capstone_justification: cap_just,
580
581 ordering: ordering_parsed,
582 ordering_confidence: ordering_conf,
583 ordering_justification: ordering_just,
584
585 ai_confidence: ai_conf_parsed,
586 ai_confidence_confidence: aic_conf,
587 ai_confidence_justification: aic_just,
588
589 aggregator_preference,
590 aggregator_preference_confidence: ap_conf,
591 aggregator_preference_justification: ap_just,
592
593 allow_early_leaves,
594 allow_early_leaves_confidence: ael_conf,
595 allow_early_leaves_justification: ael_just,
596
597 partial_subbranch_probability,
598 partial_subbranch_probability_confidence: psp_conf,
599 partial_subbranch_probability_justification: psp_just,
600
601 aggregator_depth_limit,
602 aggregator_depth_limit_confidence: adl_conf,
603 aggregator_depth_limit_justification: adl_just,
604
605 dispatch_depth_limit,
606 dispatch_depth_limit_confidence: ddl_conf,
607 dispatch_depth_limit_justification: ddl_just,
608
609 leaf_min_depth,
610 leaf_min_depth_confidence: lmd_conf,
611 leaf_min_depth_justification: lmd_just,
612
613 tree_expansion_policy,
614 tree_expansion_policy_confidence: tep_conf,
615 tree_expansion_policy_justification: tep_just,
616 };
617
618 trace!("(JustifiedGrowerTreeConfiguration) Successfully constructed => returning Ok.");
619 Ok(jcfg)
620 }
621}
622
623#[cfg(test)]
624mod tests {
625 use super::*;
626
627 mod test_weighted_branching {
632 use super::*;
633
634 #[traced_test]
635 fn test_valid() {
636 let wb = WeightedBranchingConfigurationBuilder::default()
637 .mean(4)
638 .variance(3)
639 .build()
640 .unwrap();
641 assert!(wb.validate().is_ok());
642 }
643
644 #[traced_test]
645 fn test_variance_exceeds_mean() {
646 let wb = WeightedBranchingConfigurationBuilder::default()
647 .mean(2)
648 .variance(4)
649 .build()
650 .unwrap();
651 let res = wb.validate();
652 assert!(res.is_err());
653 assert!(matches!(res.err().unwrap(), GrowerTreeConfigurationError::WeightedBranchMeanLessThanVariance));
654 }
655
656 #[traced_test]
657 fn test_mean_zero() {
658 let wb = WeightedBranchingConfigurationBuilder::default()
659 .mean(0)
660 .variance(0)
661 .build()
662 .unwrap();
663 let res = wb.validate();
664 assert!(res.is_err());
665 assert!(matches!(res.err().unwrap(), GrowerTreeConfigurationError::WeightedBranchMeanCannotBeZero));
666 }
667 }
668
669 mod test_level_specific {
670 use super::*;
671
672 #[traced_test]
673 fn test_valid() {
674 let ls = TreeLevelSpecificConfigurationBuilder::default()
675 .breadth_per_level(vec![3,4])
676 .density_per_level(vec![2,5])
677 .build()
678 .unwrap();
679 assert!(ls.validate(3).is_ok());
680 }
681
682 #[traced_test]
683 fn test_breadth_len_exceeds_depth() {
684 let ls = TreeLevelSpecificConfigurationBuilder::default()
685 .breadth_per_level(vec![3,4,5,6])
686 .density_per_level(vec![2])
687 .build()
688 .unwrap();
689 let res = ls.validate(3);
690 assert!(res.is_err());
691 assert!(matches!(res.err().unwrap(), GrowerTreeConfigurationError::LevelSpecificBreadthLenExceedsDepth));
692 }
693 }
694
695 mod test_level_skipping {
696 use super::*;
697
698 #[traced_test]
699 fn test_valid() {
700 let skip = LevelSkippingConfigurationBuilder::default()
701 .leaf_probability_per_level(vec![0.0,0.2,0.4])
702 .build()
703 .unwrap();
704 assert!(skip.validate(3).is_ok());
705 }
706
707 #[traced_test]
708 fn test_probability_exceeds_depth() {
709 let skip = LevelSkippingConfigurationBuilder::default()
710 .leaf_probability_per_level(vec![0.0,0.2,0.4,0.6])
711 .build()
712 .unwrap();
713 let res = skip.validate(3);
714 assert!(res.is_err());
715 assert!(matches!(res.err().unwrap(), GrowerTreeConfigurationError::LeafProbabilityLenExceedsDepth));
716 }
717
718 #[traced_test]
719 fn test_probability_out_of_range() {
720 let skip = LevelSkippingConfigurationBuilder::default()
721 .leaf_probability_per_level(vec![-0.1,1.1])
722 .build()
723 .unwrap();
724 let res = skip.validate(3);
725 assert!(res.is_err());
726 }
728 }
729
730 mod test_capstone {
731 use super::*;
732
733 #[traced_test]
734 fn test_off_ok() {
735 let cap = CapstoneGenerationConfigurationBuilder::default()
736 .mode(CapstoneMode::Off)
737 .probability(0.0)
738 .build()
739 .unwrap();
740 assert!(cap.validate().is_ok());
741 }
742
743 #[traced_test]
744 fn test_single_ok() {
745 let cap = CapstoneGenerationConfigurationBuilder::default()
746 .mode(CapstoneMode::Single)
747 .probability(0.0)
748 .build()
749 .unwrap();
750 assert!(cap.validate().is_ok());
751 }
752
753 #[traced_test]
754 fn test_probabilistic_valid() {
755 let cap = CapstoneGenerationConfigurationBuilder::default()
756 .mode(CapstoneMode::Probabilistic)
757 .probability(0.5)
758 .build()
759 .unwrap();
760 assert!(cap.validate().is_ok());
761 }
762
763 #[traced_test]
764 fn test_probabilistic_out_of_range() {
765 let cap = CapstoneGenerationConfigurationBuilder::default()
766 .mode(CapstoneMode::Probabilistic)
767 .probability(1.2)
768 .build()
769 .unwrap();
770 let res = cap.validate();
771 assert!(res.is_err());
772 assert!(matches!(res.err().unwrap(), GrowerTreeConfigurationError::CapstoneProbabilityOutOfRange));
773 }
774 }
775
776 mod test_ai_confidence {
777 use super::*;
778
779 #[traced_test]
780 fn test_valid() {
781 let aic = AiTreeBranchingConfidenceConfigurationBuilder::default()
782 .base_factor(4)
783 .factor_multiplier(0.5)
784 .build()
785 .unwrap();
786 assert!(aic.validate().is_ok());
787 }
788
789 #[traced_test]
790 fn test_negative_multiplier() {
791 let aic = AiTreeBranchingConfidenceConfigurationBuilder::default()
792 .base_factor(2)
793 .factor_multiplier(-0.1)
794 .build()
795 .unwrap();
796 let res = aic.validate();
797 assert!(res.is_err());
798 assert!(matches!(res.err().unwrap(), GrowerTreeConfigurationError::AIConfidenceFactorNegative));
799 }
800 }
801
802 #[traced_test]
807 fn test_main_config_ok() {
808 let cfg = GrowerTreeConfigurationBuilder::default()
809 .depth(3)
810 .breadth(2)
811 .density(2)
812 .leaf_granularity(0.5)
813 .balance_symmetry(0.5)
814 .complexity(ConfigurationComplexity::Balanced)
815 .weighted_branching(Some(
817 WeightedBranchingConfigurationBuilder::default()
818 .mean(4)
819 .variance(1)
820 .build()
821 .unwrap()
822 ))
823 .build()
824 .unwrap();
825 cfg.validate().unwrap();
826 }
827
828 #[traced_test]
829 fn test_main_config_toml_round_trip() {
830 let cfg = GrowerTreeConfigurationBuilder::default()
831 .depth(2)
832 .breadth(2)
833 .density(2)
834 .leaf_granularity(0.6)
835 .balance_symmetry(0.6)
836 .complexity(ConfigurationComplexity::Complex)
837 .build()
838 .unwrap();
839
840 let toml_str = cfg.to_toml_string().unwrap();
841 let parsed = GrowerTreeConfiguration::from_toml_str(&toml_str).unwrap();
842 parsed.validate().unwrap();
843 assert_eq!(parsed, cfg);
844 }
845
846 #[traced_test]
847 fn test_main_config_file_io() {
848 let dir = tempdir().unwrap();
849 let file_path = dir.path().join("cfg.toml");
850
851 let cfg = GrowerTreeConfigurationBuilder::default()
852 .depth(5)
853 .breadth(3)
854 .density(4)
855 .leaf_granularity(0.8)
856 .balance_symmetry(0.4)
857 .complexity(ConfigurationComplexity::Simple)
858 .build()
859 .unwrap();
860
861 cfg.validate().unwrap();
862 cfg.to_toml_file(&file_path).unwrap();
863
864 let loaded = GrowerTreeConfiguration::from_toml_file(&file_path).unwrap();
865 loaded.validate().unwrap();
866 assert_eq!(loaded, cfg);
867 }
868
869 #[traced_test]
870 #[disable]
871 fn test_ai_json_template_schema_generation() {
872 let config = GrowerTreeConfigurationBuilder::default()
874 .depth(3)
875 .breadth(2)
876 .density(2)
877 .leaf_granularity(0.7)
878 .balance_symmetry(0.5)
879 .complexity(ConfigurationComplexity::Complex)
880 .weighted_branching(Some(
882 WeightedBranchingConfigurationBuilder::default()
883 .mean(4)
884 .variance(1)
885 .build()
886 .unwrap()
887 ))
888 .build()
889 .expect("Failed building GrowerTreeConfiguration");
890
891 let schema_json = GrowerTreeConfiguration::to_template();
893
894 info!("AiJsonTemplate => {:#?}", schema_json);
896
897 assert!(schema_json.is_object(), "Top-level schema should be an object.");
900
901 let root_obj = schema_json.as_object().unwrap();
902 assert!(root_obj.contains_key("struct_name"), "Schema must contain 'struct_name'");
903 assert_eq!(root_obj["struct_name"], "GrowerTreeConfiguration");
904
905 let fields_val = root_obj.get("fields").expect("Schema must contain 'fields'");
907 assert!(fields_val.is_object(), "'fields' should be an object.");
908
909 let fields_obj = fields_val.as_object().unwrap();
911 let leaf_granularity_schema = fields_obj.get("leaf_granularity").expect("Expected 'leaf_granularity' in schema");
912
913 let schema_map = leaf_granularity_schema.as_object().expect("Should be an object schema");
915 assert_eq!(schema_map["type"], "number", "leaf_granularity must be numeric in AI schema");
916 assert_eq!(schema_map["required"], true, "leaf_granularity is not optional.");
917
918 }
924}