1crate::ix!();
3
4impl JustifiedGrowerTreeConfiguration {
5
6 pub fn stamp(&self) -> String {
8 self.produce_concrete_growth_instructions()
9 }
10
11 pub fn produce_concrete_growth_instructions(&self) -> String {
21
22 trace!("Generating human-readable tree growth instructions based on configuration fields.");
23 let mut lines = Vec::new();
24
25 lines.push(format!(
29 "Tree Depth: This tree extends to {} level(s), counting from the root (level 0) down to the deepest leaves.",
30 self.depth()
31 ));
32 lines.push(format!(" - With confidence {} we select this parameter because {} ", self.depth_confidence(), self.depth_justification() ));
33 lines.push(format!(
34 " - A higher depth value indicates more hierarchical layers before reaching the leaves, allowing for finer subdivisions or more progressive detail.\n"
35 ));
36
37 lines.push(format!(
41 "Base Breadth: In this tree, each node typically branches into up to {} sub-branches (i.e., siblings).",
42 self.breadth()
43 ));
44 lines.push(format!(" - With confidence {} we select this parameter because {}", self.breadth_confidence(), self.breadth_justification() ));
45 lines.push(
46 " - A larger breadth value creates a wider tree with more parallel branches at each level.\n".to_string(),
47 );
48
49 lines.push(format!(
53 "Leaf Density: Each leaf node by default contains {} variant(s) or sub-items.",
54 self.density()
55 ));
56 lines.push(format!(" - With confidence {} we select this parameter because {}", self.density_confidence(), self.density_justification() ));
57 lines.push(" - A higher density allows more final variants at each leaf, increasing detail or coverage within each leaf section.\n".to_string());
58
59 lines.push(format!(
63 "Leaf Granularity: {:.2}. This value can range from 0.0 to 1.0.",
64 self.leaf_granularity()
65 ));
66 lines.push(format!(" - With confidence {} we select this parameter because {}", self.leaf_granularity_confidence(), self.leaf_granularity_justification() ));
67 lines.push(format!(
68 " - A value near 0.0 produces coarser, less specialized leaves, while a value near 1.0 leads to very specific, fine-grained leaves.\n"
69 ));
70
71 lines.push(format!(
75 "Balance Symmetry: {:.2}. This value can range from 0.0 to 1.0.",
76 self.balance_symmetry()
77 ));
78 lines.push(format!(" - With confidence {} we select this parameter because {}", self.balance_symmetry_confidence(), self.balance_symmetry_justification() ));
79 lines.push(
80 " - A lower value (closer to 0.0) yields more uneven or varied branching across nodes, while a higher value (closer to 1.0) produces more uniform branching where each node follows a similar structure.\n"
81 .to_string(),
82 );
83
84 lines.push(format!(
88 "Overall Complexity: {:?}. This reflects how detailed or intricate the tree can become.",
89 self.complexity()
90 ));
91 lines.push(format!(" - With confidence {} we select this parameter because {}", self.complexity_confidence(), self.complexity_justification() ));
92 lines.push(
93 " - 'Simple' configurations produce minimal detail, 'Balanced' provides moderate depth and detail, and 'Complex' is for maximum detail or nuance.\n"
94 .to_string(),
95 );
96
97 lines.push(format!(
102 "Aggregator Preference: {:.2}. This fraction ranges from 0.0 to 1.0.",
103 self.aggregator_preference()
104 ));
105 lines.push(format!(" - With confidence {} we select this parameter because {}", self.aggregator_preference_confidence(), self.aggregator_preference_justification() ));
106 lines.push(
107 " - A value near 0.0 means mostly dispatch nodes are chosen at intermediate levels, while a value near 1.0 means mostly aggregator nodes are used. Values in between yield a mix of aggregator and dispatch nodes.\n"
108 .to_string(),
109 );
110
111 if *self.allow_early_leaves() {
115 lines.push("Early Leaves: Enabled. This allows some branches to terminate and become leaves at intermediate levels, rather than always extending to the final depth.".to_string());
116 } else {
117 lines.push("Early Leaves: Disabled. All branches will extend to the final depth before forming leaves.".to_string());
118 }
119 lines.push(format!(" - With confidence {} we select this parameter because {}\n", self.allow_early_leaves_confidence(), self.allow_early_leaves_justification() ));
120
121 lines.push(format!(
125 "Partial Sub-branch Probability: {:.2}.",
126 self.partial_subbranch_probability()
127 ));
128 lines.push(format!(" - With confidence {} we select this parameter because {}", self.partial_subbranch_probability_confidence(), self.partial_subbranch_probability_justification() ));
129 lines.push(
130 " - This value indicates how often optional child branches are included. A lower number excludes more optional branches, a higher number includes them more frequently.\n"
131 .to_string(),
132 );
133
134 lines.push("Node Variant Selection Policy:".to_string());
138 match &self.tree_expansion_policy() {
139 JustifiedTreeExpansionPolicy::Simple { .. } => {
140 lines.push(" - Simple policy: Every node above the final level is treated as a dispatch node, and every node at the final level is treated as a leaf node (LeafHolder).".to_string());
141 lines.push(" This creates a straightforward structure: dispatch nodes at each interior level, leaves at the bottom.".to_string());
142 }
143 JustifiedTreeExpansionPolicy::Weighted { field_0: cfg, .. } => {
144 lines.push(format!(
145 " - Weighted policy with aggregator_weight={:.2}, dispatch_weight={:.2}, and leaf_holder_weight={:.2}.",
146 cfg.aggregator_weight(),
147 cfg.dispatch_weight(),
148 cfg.leaf_holder_weight(),
149 ));
150 lines.push(" This means that at each intermediate step, the node kind is chosen randomly according to these weights, allowing aggregator, dispatch, or leaf-holder nodes to appear in proportion to these numbers.".to_string());
151 }
152 JustifiedTreeExpansionPolicy::WeightedWithLimits { field_0: cfg, .. } => {
153 lines.push(format!(
154 " - WeightedWithLimits policy: aggregator_weight={:.2}, dispatch_weight={:.2}, leaf_holder_weight={:.2}.",
155 cfg.aggregator_weight(),
156 cfg.dispatch_weight(),
157 cfg.leaf_holder_weight(),
158 ));
159 if let Some(amd) = cfg.aggregator_max_depth() {
160 lines.push(format!(
161 " Aggregator nodes are disallowed deeper than level {}.",
162 amd
163 ));
164 }
165 if let Some(dmd) = cfg.dispatch_max_depth() {
166 lines.push(format!(
167 " Dispatch nodes are disallowed deeper than level {}.",
168 dmd
169 ));
170 }
171 if let Some(lmd) = cfg.leaf_min_depth() {
172 lines.push(format!(
173 " Leaf-holder nodes are not allowed before level {}.",
174 lmd
175 ));
176 }
177 lines.push(" Within these depth constraints, random selection follows the given weights to determine whether each node becomes an aggregator, a dispatch node, or a leaf-holder node.".to_string());
178 }
179 JustifiedTreeExpansionPolicy::DepthBased { field_0: cfg, .. } => {
180 lines.push(format!(
181 " - DepthBased policy: aggregator_start_level={}, leaf_start_level={}.",
182 cfg.aggregator_start_level(),
183 cfg.leaf_start_level()
184 ));
185 lines.push(" Levels below aggregator_start_level will use dispatch nodes, levels between aggregator_start_level and leaf_start_level will use aggregator nodes, and levels from leaf_start_level onward will use leaf-holder nodes.".to_string());
186 }
187 JustifiedTreeExpansionPolicy::AlwaysAggregate { .. } => {
188 lines.push(" - AlwaysAggregate policy: All non-leaf levels use aggregator nodes, and leaves appear at the final depth (or earlier if early leaves are allowed).".to_string());
189 }
190 JustifiedTreeExpansionPolicy::AlwaysDispatch { .. } => {
191 lines.push(" - AlwaysDispatch policy: All non-leaf levels use dispatch nodes, and leaves appear at the final depth (or earlier if early leaves are allowed).".to_string());
192 }
193 JustifiedTreeExpansionPolicy::AlwaysLeafHolder { .. } => {
194 lines.push(" - AlwaysLeafHolder policy: Every node is treated as a leaf-holder node, producing a flattened tree with no deeper internal structure.".to_string());
195 }
196 JustifiedTreeExpansionPolicy::Phased { field_0: cfg, .. } => {
197 lines.push(" - Phased policy: This approach divides node selection into phases, each starting at a specific depth.".to_string());
198 for (index, phase) in cfg.phases().iter().enumerate() {
199 lines.push(format!(
200 " Phase {} begins at level {}: aggregator={:.2}, dispatch={:.2}, leaf={:.2}.",
201 index + 1,
202 phase.start_level(),
203 phase.aggregator_weight(),
204 phase.dispatch_weight(),
205 phase.leaf_weight(),
206 ));
207 }
208 lines.push(" Once you reach a phase's start level, those weights remain in effect until the next phase starts (or until the final depth is reached).".to_string());
209 }
210 JustifiedTreeExpansionPolicy::Scripted { field_0: cfg, .. } => {
211 lines.push(" - Scripted policy: This approach assigns aggregator/dispatch/leaf probabilities per specific tree level.".to_string());
212 for (level, weights) in cfg.levels().iter() {
213 lines.push(format!(
214 " Level {}: aggregator={:.2}, dispatch={:.2}, leaf={:.2}.",
215 level,
216 weights.aggregator_chance(),
217 weights.dispatch_chance(),
218 weights.leaf_chance()
219 ));
220 }
221 lines.push(" Any level not listed follows the last known probabilities or a default fallback if none are specified.".to_string());
222 }
223 }
224
225 lines.push(format!(" - With confidence {} we select this parameter because {}\n", self.tree_expansion_policy_confidence(), self.tree_expansion_policy_justification() ));
226
227 if let Some(agg_lim) = self.aggregator_depth_limit() {
231 lines.push(format!(
232 "Aggregator Depth Limit: Aggregator nodes are disallowed beyond level {}.",
233 agg_lim
234 ));
235 lines.push(format!(" - With confidence {} we select this parameter because {}", self.aggregator_depth_limit_confidence(), self.aggregator_depth_limit_justification() ));
236 lines.push(
237 " - This imposes a cutoff so that aggregator nodes cannot appear deeper in the tree.\n"
238 .to_string(),
239 );
240 }
241
242 if let Some(disp_lim) = self.dispatch_depth_limit() {
246 lines.push(format!(
247 "Dispatch Depth Limit: Dispatch nodes are disallowed beyond level {}.",
248 disp_lim
249 ));
250 lines.push(format!(" - With confidence {} we select this parameter because {}", self.dispatch_depth_limit_confidence(), self.dispatch_depth_limit_justification() ));
251 lines.push(
252 " - This ensures dispatch nodes only appear above a certain level, forcing other node types deeper in the tree.\n"
253 .to_string(),
254 );
255 }
256
257 if let Some(lmd) = self.leaf_min_depth() {
261 lines.push(format!(
262 "Leaf Minimum Depth: Leaf-holder nodes cannot appear before level {}.",
263 lmd
264 ));
265 lines.push(format!(" - With confidence {} we select this parameter because {}", self.leaf_min_depth_confidence(), self.leaf_min_depth_justification() ));
266 lines.push(
267 " - This requirement pushes leaf creation lower in the tree, ensuring at least some intermediate structure forms first.\n"
268 .to_string(),
269 );
270 }
271
272 if let Some(lvl_spec) = self.level_specific() {
276 lines.push("Level-Specific Overrides:".to_string());
277
278 if !lvl_spec.breadth_per_level().is_empty() {
279 lines.push(
280 " - breadth_per_level indicates how many sub-branches each level specifically uses (if specified)."
281 .to_string(),
282 );
283 for (lvl_idx, breadth_val) in lvl_spec.breadth_per_level().iter().enumerate() {
284 lines.push(format!(
285 " Level {} => breadth = {}",
286 lvl_idx, breadth_val
287 ));
288 }
289 lines.push(format!(" - With confidence {} we select this parameter because {}\n", lvl_spec.breadth_per_level_confidence(), lvl_spec.breadth_per_level_justification() ));
290 }
291
292 if !lvl_spec.density_per_level().is_empty() {
293 lines.push(
294 " - density_per_level indicates how many leaf variants are allocated at each level (if it becomes a leaf)."
295 .to_string(),
296 );
297 for (lvl_idx, density_val) in lvl_spec.density_per_level().iter().enumerate() {
298 lines.push(format!(
299 " Level {} => density = {}",
300 lvl_idx, density_val
301 ));
302 }
303 lines.push(
304 format!(
305 " - With confidence {} we select this parameter because {}\n", lvl_spec.density_per_level_confidence(), lvl_spec.density_per_level_justification()
306 )
307 );
308 }
309 }
310
311 if let Some(wb) = self.weighted_branching() {
314 lines.push(format!(
315 "Weighted Branching: mean={}, variance={}.",
316 wb.mean(),
317 wb.variance()
318 ));
319 lines.push(format!(" - With confidence {:?} we select this parameter because {:?}", self.weighted_branching_confidence(), self.weighted_branching_justification() ));
320 lines.push(" - This randomizes the number of sub-branches per node around the mean, within a range governed by ±variance, promoting organic or varied branching counts.\n".to_string());
321 }
322
323 if let Some(lskip) = self.level_skipping() {
327 lines.push("Level Skipping: Certain levels can terminate early as leaves based on their assigned probabilities.".to_string());
328 for (lvl_idx, prob) in lskip.leaf_probability_per_level().iter().enumerate() {
329 lines.push(format!(
330 " Level {} => leaf probability = {:.2}.",
331 lvl_idx, prob
332 ));
333 }
334 lines.push(format!(" - With confidence {:?} we select this parameter because {:?}", self.level_skipping_confidence(), self.level_skipping_justification() ));
335 lines.push(" - At levels with higher leaf probability, more nodes stop branching further and become leaves, shaping the overall depth distribution.\n".to_string());
336 }
337
338 if let Some(cap) = self.capstone() {
342 match cap.mode() {
343 JustifiedCapstoneMode::Off { .. } => {
344 lines.push("Capstone Nodes: Off. No specialized leaf nodes are designated as final or distinct mastery points.".to_string());
345 }
346 JustifiedCapstoneMode::Single { .. } => {
347 lines.push("Capstone Nodes: Single. Exactly one leaf node in the entire tree is designated as a special 'capstone' node.".to_string());
348 }
349 JustifiedCapstoneMode::Probabilistic { .. } => {
350 lines.push(format!(
351 "Capstone Nodes: Probabilistic. A fraction of leaf nodes become 'capstones' at a rate of {:.2}.",
352 cap.probability()
353 ));
354 lines.push(" - This fraction indicates how many leaves are highlighted as culminating concepts or advanced skills.".to_string());
355 }
356 }
357 lines.push(format!(" - With confidence {:?} we select this parameter because {:?}\n", self.capstone_confidence(), self.capstone_justification() ));
358 }
359
360 if let Some(order) = self.ordering() {
364 lines.push(format!(
365 "Sub-branch Ordering: {:?}.",
366 order
367 ));
368 lines.push(format!(" - With confidence {} we select this parameter because {}", self.ordering_confidence(), self.ordering_justification() ));
369 lines.push(
370 " - This setting controls how siblings (sub-branches from the same node) are arranged or sorted, influencing how the tree is displayed or traversed.\n"
371 .to_string(),
372 );
373 }
374
375 if let Some(ai) = self.ai_confidence() {
379 lines.push(format!(
380 "AI Confidence Branching: base_factor={}, factor_multiplier={:.2}.",
381 ai.base_factor(),
382 ai.factor_multiplier()
383 ));
384 lines.push(format!(" - With confidence {:?} we select this parameter because {:?}", self.ai_confidence_confidence(), self.ai_confidence_justification() ));
385 lines.push(" - A higher factor_multiplier means that if the AI is very confident, the branching factor can be amplified to include more sub-branches, whereas low confidence could reduce them, shaping the final tree structure based on certainty levels.".to_string());
386 }
387
388 debug!("Finished building the instructions text. Returning combined string.");
392 lines.join("\n")
393 }
394}
395
396#[cfg(test)]
397mod test_produce_growth_instructions {
398 use super::*;
399
400 #[traced_test]
407 fn test_varied_configurations() {
408 info!("Beginning test of produce_concrete_growth_instructions with varied configurations.");
409
410 let to_justified = |cfg: &GrowerTreeConfiguration| {
412 let as_json = serde_json::to_value(cfg)
413 .expect("Serialization to JSON must succeed");
414 JustifiedGrowerTreeConfiguration::fuzzy_from_json_value(&as_json)
415 .expect("Fuzzy parse into JustifiedGrowerTreeConfiguration must succeed")
416 };
417
418 {
420 let cfg = GrowerTreeConfiguration::default();
421 let jcfg = to_justified(&cfg);
422 let instructions = jcfg.produce_concrete_growth_instructions();
423 info!("--- Configuration #1 (default) ---\n\n{}\n", instructions);
424 }
425
426 {
428 let cfg = GrowerTreeConfigurationBuilder::default()
429 .depth(5)
430 .breadth(2)
431 .aggregator_preference(0.8)
432 .try_build()
433 .unwrap();
434 let jcfg = to_justified(&cfg);
435 let instructions = jcfg.produce_concrete_growth_instructions();
436 info!("--- Configuration #2 ---\n\n{}\n", instructions);
437 }
438
439 {
441 let lvl_spec = TreeLevelSpecificConfigurationBuilder::default()
442 .breadth_per_level(vec![3, 5, 2])
443 .build()
444 .unwrap();
445 let wb = WeightedBranchingConfigurationBuilder::default()
446 .mean(4)
447 .variance(2)
448 .build()
449 .unwrap();
450
451 let cfg = GrowerTreeConfigurationBuilder::default()
452 .depth(6)
453 .breadth(3)
454 .level_specific(Some(lvl_spec))
455 .weighted_branching(Some(wb))
456 .try_build()
457 .unwrap();
458 let jcfg = to_justified(&cfg);
459 let instructions = jcfg.produce_concrete_growth_instructions();
460 info!("--- Configuration #3 ---\n\n{}\n", instructions);
461 }
462
463 {
465 let depth_based = DepthBasedNodeVariantPolicyBuilder::default()
466 .aggregator_start_level(2)
467 .leaf_start_level(4)
468 .build()
469 .unwrap();
470
471 let cfg = GrowerTreeConfigurationBuilder::default()
472 .depth(5)
473 .breadth(4)
474 .partial_subbranch_probability(0.6)
475 .tree_expansion_policy(TreeExpansionPolicy::DepthBased(depth_based))
476 .try_build()
477 .unwrap();
478 let jcfg = to_justified(&cfg);
479 let instructions = jcfg.produce_concrete_growth_instructions();
480 info!("--- Configuration #4 ---\n\n{}\n", instructions);
481 }
482
483 {
485 let wwl = WeightedNodeVariantPolicyWithLimitsBuilder::default()
486 .aggregator_weight(0.3)
487 .dispatch_weight(0.3)
488 .leaf_holder_weight(0.4)
489 .aggregator_max_depth(Some(2))
490 .dispatch_max_depth(Some(4))
491 .leaf_min_depth(Some(3))
492 .build()
493 .unwrap();
494
495 let cfg = GrowerTreeConfigurationBuilder::default()
496 .depth(6)
497 .tree_expansion_policy(TreeExpansionPolicy::WeightedWithLimits(wwl))
498 .try_build()
499 .unwrap();
500 let jcfg = to_justified(&cfg);
501 let instructions = jcfg.produce_concrete_growth_instructions();
502 info!("--- Configuration #5 ---\n\n{}\n", instructions);
503 }
504
505 {
507 let phase1 = NodeVariantPhaseRangeBuilder::default()
508 .start_level(0)
509 .aggregator_weight(0.1)
510 .dispatch_weight(0.7)
511 .leaf_weight(0.2)
512 .build()
513 .unwrap();
514 let phase2 = NodeVariantPhaseRangeBuilder::default()
515 .start_level(3)
516 .aggregator_weight(0.4)
517 .dispatch_weight(0.2)
518 .leaf_weight(0.4)
519 .build()
520 .unwrap();
521
522 let phased_policy = PhasedNodeVariantPolicyBuilder::default()
523 .phases(vec![phase1, phase2])
524 .build()
525 .unwrap();
526
527 let cfg = GrowerTreeConfigurationBuilder::default()
528 .depth(6)
529 .tree_expansion_policy(TreeExpansionPolicy::Phased(phased_policy))
530 .try_build()
531 .unwrap();
532 let jcfg = to_justified(&cfg);
533 let instructions = jcfg.produce_concrete_growth_instructions();
534 info!("--- Configuration #6 ---\n\n{}\n", instructions);
535 }
536
537 {
539 let weights1 = NodeVariantLevelWeightsBuilder::default()
540 .aggregator_chance(0.2)
541 .dispatch_chance(0.5)
542 .leaf_chance(0.3)
543 .build()
544 .unwrap();
545 let weights2 = NodeVariantLevelWeightsBuilder::default()
546 .aggregator_chance(0.6)
547 .dispatch_chance(0.2)
548 .leaf_chance(0.2)
549 .build()
550 .unwrap();
551
552 let mut level_map = std::collections::HashMap::new();
553 level_map.insert(0_u8, weights1);
554 level_map.insert(3_u8, weights2);
555
556 let scripted = ScriptedNodeVariantPolicyBuilder::default()
557 .levels(level_map)
558 .build()
559 .unwrap();
560
561 let cfg = GrowerTreeConfigurationBuilder::default()
562 .depth(5)
563 .tree_expansion_policy(TreeExpansionPolicy::Scripted(scripted))
564 .try_build()
565 .unwrap();
566 let jcfg = to_justified(&cfg);
567 let instructions = jcfg.produce_concrete_growth_instructions();
568 info!("--- Configuration #7 ---\n\n{}\n", instructions);
569 }
570
571 {
573 let ai_conf = AiTreeBranchingConfidenceConfigurationBuilder::default()
574 .base_factor(3)
575 .factor_multiplier(1.5)
576 .build()
577 .unwrap();
578 let cfg = GrowerTreeConfigurationBuilder::default()
579 .depth(4)
580 .breadth(3)
581 .ai_confidence(Some(ai_conf))
582 .try_build()
583 .unwrap();
584 let jcfg = to_justified(&cfg);
585 let instructions = jcfg.produce_concrete_growth_instructions();
586 info!("--- Configuration #8 ---\n\n{}\n", instructions);
587 }
588
589 {
591 let capstone = CapstoneGenerationConfigurationBuilder::default()
592 .mode(CapstoneMode::Single)
593 .probability(0.0)
594 .build()
595 .unwrap();
596
597 let cfg = GrowerTreeConfigurationBuilder::default()
598 .depth(5)
599 .capstone(Some(capstone))
600 .try_build()
601 .unwrap();
602 let jcfg = to_justified(&cfg);
603 let instructions = jcfg.produce_concrete_growth_instructions();
604 info!("--- Configuration #9 ---\n\n{}\n", instructions);
605 }
606
607 {
609 let skip_cfg = LevelSkippingConfigurationBuilder::default()
610 .leaf_probability_per_level(vec![0.0, 0.2, 0.3, 0.7])
611 .build()
612 .unwrap();
613
614 let wb = WeightedBranchingConfigurationBuilder::default()
615 .mean(5)
616 .variance(2)
617 .build()
618 .unwrap();
619
620 let capstone = CapstoneGenerationConfigurationBuilder::default()
621 .mode(CapstoneMode::Probabilistic)
622 .probability(0.25)
623 .build()
624 .unwrap();
625
626 let cfg = GrowerTreeConfigurationBuilder::default()
627 .depth(6)
628 .breadth(4)
629 .density(3)
630 .leaf_granularity(0.9)
631 .balance_symmetry(0.4)
632 .aggregator_preference(0.2)
633 .level_skipping(Some(skip_cfg))
634 .weighted_branching(Some(wb))
635 .capstone(Some(capstone))
636 .try_build()
637 .unwrap();
638 let jcfg = to_justified(&cfg);
639 let instructions = jcfg.produce_concrete_growth_instructions();
640 info!("--- Configuration #10 ---\n\n{}\n", instructions);
641 }
642
643 info!("All varied configurations have been processed successfully.");
644 }
645}