1use std::fmt::Debug;
46use std::marker::PhantomData;
47use std::sync::Arc;
48
49use super::map::{ExplorationMap, GraphMap, MapNodeId, MapState};
50use super::mutation::{ActionNodeData, ExplorationResult, MapUpdate, MutationInput};
51use super::node_rules::Rules;
52use super::selection::{AnySelection, Fifo, SelectionLogic, Ucb1};
53use crate::actions::ActionsConfig;
54use crate::learn::{LearnedProvider, NullProvider, SharedLearnedProvider};
55use crate::online_stats::SwarmStats;
56
57pub trait MutationLogic<N, E, S, R>: Send + Sync
66where
67 N: Debug + Clone,
68 E: Debug + Clone,
69 S: MapState,
70 R: Rules,
71{
72 fn interpret(
76 &self,
77 input: &dyn MutationInput,
78 map: &GraphMap<N, E, S>,
79 actions: &ActionsConfig,
80 rules: &R,
81 stats: &SwarmStats,
82 ) -> Vec<MapUpdate<N, E, S>>;
83
84 fn initialize(
86 &self,
87 root_id: MapNodeId,
88 initial_contexts: &[&str],
89 rules: &R,
90 ) -> Vec<MapUpdate<N, E, S>>;
91
92 fn create_node_data(&self, input: &dyn MutationInput) -> N;
94
95 fn create_edge_data(&self, input: &dyn MutationInput) -> E;
97
98 fn initial_state(&self) -> S;
100
101 fn name(&self) -> &str;
103}
104
105pub struct Operator<M, Sel, N, E, S, R>
114where
115 N: Debug + Clone,
116 E: Debug + Clone,
117 S: MapState,
118 R: Rules,
119 M: MutationLogic<N, E, S, R>,
120 Sel: SelectionLogic<N, E, S>,
121{
122 mutation: M,
123 pub selection: Sel,
124 rules: R,
125 provider: SharedLearnedProvider,
127 _phantom: PhantomData<(N, E, S)>,
128}
129
130impl<M, Sel, N, E, S, R> Operator<M, Sel, N, E, S, R>
131where
132 N: Debug + Clone,
133 E: Debug + Clone,
134 S: MapState,
135 R: Rules,
136 M: MutationLogic<N, E, S, R>,
137 Sel: SelectionLogic<N, E, S>,
138{
139 pub fn new(mutation: M, selection: Sel, rules: R) -> Self {
141 Self {
142 mutation,
143 selection,
144 rules,
145 provider: Arc::new(NullProvider),
146 _phantom: PhantomData,
147 }
148 }
149
150 pub fn with_provider(mut self, provider: SharedLearnedProvider) -> Self {
152 self.provider = provider;
153 self
154 }
155
156 pub fn provider(&self) -> &dyn LearnedProvider {
158 self.provider.as_ref()
159 }
160
161 pub fn set_provider(&mut self, provider: SharedLearnedProvider) {
163 self.provider = provider;
164 }
165
166 pub fn rules(&self) -> &R {
168 &self.rules
169 }
170
171 pub fn selection(&self) -> &Sel {
173 &self.selection
174 }
175
176 pub fn selection_mut(&mut self) -> &mut Sel {
178 &mut self.selection
179 }
180
181 pub fn set_selection(&mut self, selection: Sel) {
183 self.selection = selection;
184 }
185
186 pub fn interpret(
191 &self,
192 input: &dyn MutationInput,
193 map: &GraphMap<N, E, S>,
194 actions: &ActionsConfig,
195 stats: &SwarmStats,
196 ) -> Vec<MapUpdate<N, E, S>> {
197 self.mutation
198 .interpret(input, map, actions, &self.rules, stats)
199 }
200
201 pub fn initialize(
203 &self,
204 root_id: MapNodeId,
205 initial_contexts: &[&str],
206 ) -> Vec<MapUpdate<N, E, S>> {
207 self.mutation
208 .initialize(root_id, initial_contexts, &self.rules)
209 }
210
211 pub fn next(&self, map: &GraphMap<N, E, S>, stats: &SwarmStats) -> Option<MapNodeId> {
213 self.selection.next(map, stats, self.provider.as_ref())
214 }
215
216 pub fn select(
218 &self,
219 map: &GraphMap<N, E, S>,
220 count: usize,
221 stats: &SwarmStats,
222 ) -> Vec<MapNodeId> {
223 self.selection
224 .select(map, count, stats, self.provider.as_ref())
225 }
226
227 pub fn score(&self, action: &str, target: Option<&str>, stats: &SwarmStats) -> f64 {
229 self.selection
230 .score(action, target, stats, self.provider.as_ref())
231 }
232
233 pub fn is_complete(&self, map: &GraphMap<N, E, S>) -> bool {
237 map.frontiers().is_empty()
238 }
239
240 pub fn create_node_data(&self, input: &dyn MutationInput) -> N {
242 self.mutation.create_node_data(input)
243 }
244
245 pub fn create_edge_data(&self, input: &dyn MutationInput) -> E {
247 self.mutation.create_edge_data(input)
248 }
249
250 pub fn initial_state(&self) -> S {
252 self.mutation.initial_state()
253 }
254
255 pub fn name(&self) -> String {
257 format!("{}+{}", self.mutation.name(), self.selection.name())
258 }
259}
260
261#[derive(Debug, Clone, Default)]
269pub struct RulesBasedMutation;
270
271impl RulesBasedMutation {
272 pub fn new() -> Self {
273 Self
274 }
275}
276
277impl<E, S, R> MutationLogic<ActionNodeData, E, S, R> for RulesBasedMutation
278where
279 E: Debug + Clone + Default,
280 S: MapState + Default,
281 R: Rules,
282{
283 fn interpret(
284 &self,
285 input: &dyn MutationInput,
286 _map: &GraphMap<ActionNodeData, E, S>,
287 _actions: &ActionsConfig,
288 rules: &R,
289 _stats: &SwarmStats,
290 ) -> Vec<MapUpdate<ActionNodeData, E, S>> {
291 let node_id = input.node_id();
292 let action_name = input.action_name();
293
294 match input.result() {
295 ExplorationResult::Discover(children) => {
296 let successors = rules.successors(action_name);
297 tracing::debug!(
298 node_id = node_id.0,
299 action = %action_name,
300 target = ?input.target(),
301 children_count = children.len(),
302 successors = ?successors,
303 "ExpMap: Discover result"
304 );
305 if successors.is_empty() || children.is_empty() {
306 tracing::debug!(
307 node_id = node_id.0,
308 "ExpMap: Close (no successors or children)"
309 );
310 return vec![MapUpdate::Close(node_id)];
311 }
312
313 let mut updates: Vec<MapUpdate<ActionNodeData, E, S>> = Vec::new();
314
315 for child in children {
316 for next_action in &successors {
317 let dedup_key = format!("{}:{}", next_action, child);
318 let node_data = ActionNodeData::new(*next_action).with_target(child);
319 tracing::debug!(
320 parent = node_id.0,
321 next_action = %next_action,
322 child = %child,
323 dedup_key = %dedup_key,
324 "ExpMap: AddChild (from Discover)"
325 );
326
327 updates.push(MapUpdate::AddChild {
328 parent: node_id,
329 edge_data: E::default(),
330 node_data,
331 node_state: S::default(),
332 dedup_key,
333 });
334 }
335 }
336
337 updates.push(MapUpdate::Close(node_id));
338 updates
339 }
340
341 ExplorationResult::Success => {
342 tracing::debug!(
343 node_id = node_id.0,
344 action = %action_name,
345 target = ?input.target(),
346 is_terminal = rules.is_terminal(action_name),
347 "ExpMap: Success result"
348 );
349 if rules.is_terminal(action_name) {
350 tracing::debug!(node_id = node_id.0, "ExpMap: Close (terminal action)");
351 return vec![MapUpdate::Close(node_id)];
352 }
353
354 let successors = rules.successors(action_name);
355 if successors.is_empty() {
356 tracing::debug!(node_id = node_id.0, "ExpMap: Close (no successors)");
357 return vec![MapUpdate::Close(node_id)];
358 }
359
360 let target = input.target();
361 let mut updates: Vec<MapUpdate<ActionNodeData, E, S>> = Vec::new();
362
363 tracing::debug!(
364 node_id = node_id.0,
365 successors = ?successors,
366 target = ?target,
367 "ExpMap: expanding successors"
368 );
369 for next_action in successors {
370 if let Some((param_key, param_values)) = rules.param_variants(next_action) {
371 for param_value in param_values {
372 let dedup_key = match target {
373 Some(t) => {
374 format!("{}:{}:{}:{}", next_action, t, param_key, param_value)
375 }
376 None => format!("{}:_:{}:{}", next_action, param_key, param_value),
377 };
378 tracing::debug!(
379 parent = node_id.0,
380 next_action = %next_action,
381 param = %format!("{}={}", param_key, param_value),
382 dedup_key = %dedup_key,
383 "ExpMap: AddChild (with param variant)"
384 );
385
386 let mut node_data = ActionNodeData::new(next_action);
387 if let Some(t) = target {
388 node_data = node_data.with_target(t);
389 }
390 node_data = node_data.with_arg(param_key, param_value);
391
392 updates.push(MapUpdate::AddChild {
393 parent: node_id,
394 edge_data: E::default(),
395 node_data,
396 node_state: S::default(),
397 dedup_key,
398 });
399 }
400 } else {
401 let dedup_key = match target {
402 Some(t) => format!("{}:{}", next_action, t),
403 None => format!("{}:_", next_action),
404 };
405 tracing::debug!(
406 parent = node_id.0,
407 next_action = %next_action,
408 dedup_key = %dedup_key,
409 "ExpMap: AddChild (from Success)"
410 );
411 let mut node_data = ActionNodeData::new(next_action);
412 if let Some(t) = target {
413 node_data = node_data.with_target(t);
414 }
415 updates.push(MapUpdate::AddChild {
416 parent: node_id,
417 edge_data: E::default(),
418 node_data,
419 node_state: S::default(),
420 dedup_key,
421 });
422 }
423 }
424
425 updates.push(MapUpdate::Close(node_id));
426 updates
427 }
428
429 ExplorationResult::Fail(ref err) => {
430 tracing::debug!(
431 node_id = node_id.0,
432 action = %action_name,
433 target = ?input.target(),
434 error = ?err,
435 "ExpMap: Fail result, closing node"
436 );
437 vec![MapUpdate::Close(node_id)]
438 }
439 }
440 }
441
442 fn initialize(
443 &self,
444 root_id: MapNodeId,
445 initial_contexts: &[&str],
446 rules: &R,
447 ) -> Vec<MapUpdate<ActionNodeData, E, S>> {
448 let root_actions = rules.roots();
449 tracing::debug!(
450 root_id = root_id.0,
451 root_actions = ?root_actions,
452 initial_contexts = ?initial_contexts,
453 "ExpMap: initialize"
454 );
455 let mut updates = Vec::new();
456
457 for action in root_actions {
458 for ctx in initial_contexts {
459 let dedup_key = format!("{}:{}", action, ctx);
460 let node_data = ActionNodeData::new(action).with_target(*ctx);
461 tracing::debug!(
462 parent = root_id.0,
463 action = %action,
464 context = %ctx,
465 dedup_key = %dedup_key,
466 "ExpMap: AddChild (initial)"
467 );
468 updates.push(MapUpdate::AddChild {
469 parent: root_id,
470 edge_data: E::default(),
471 node_data,
472 node_state: S::default(),
473 dedup_key,
474 });
475 }
476 }
477
478 updates
479 }
480
481 fn create_node_data(&self, input: &dyn MutationInput) -> ActionNodeData {
482 ActionNodeData::from_input(input)
483 }
484
485 fn create_edge_data(&self, _input: &dyn MutationInput) -> E {
486 E::default()
487 }
488
489 fn initial_state(&self) -> S {
490 S::default()
491 }
492
493 fn name(&self) -> &str {
494 "RulesBased"
495 }
496}
497
498pub type FifoOperator<R> =
504 Operator<RulesBasedMutation, Fifo, ActionNodeData, String, super::map::MapNodeState, R>;
505
506pub type Ucb1Operator<R> =
508 Operator<RulesBasedMutation, Ucb1, ActionNodeData, String, super::map::MapNodeState, R>;
509
510pub type ConfigurableOperator<R> =
514 Operator<RulesBasedMutation, AnySelection, ActionNodeData, String, super::map::MapNodeState, R>;
515
516#[cfg(test)]
521mod tests {
522 use super::super::map::MapNodeState;
523 use super::super::selection::FifoSelection;
524 use super::super::NodeRules;
525 use super::*;
526
527 struct TestInput {
528 node_id: MapNodeId,
529 action_name: String,
530 target: Option<String>,
531 result: ExplorationResult,
532 }
533
534 impl MutationInput for TestInput {
535 fn node_id(&self) -> MapNodeId {
536 self.node_id
537 }
538 fn action_name(&self) -> &str {
539 &self.action_name
540 }
541 fn target(&self) -> Option<&str> {
542 self.target.as_deref()
543 }
544 fn result(&self) -> &ExplorationResult {
545 &self.result
546 }
547 }
548
549 #[test]
550 fn test_operator_basic() {
551 let rules = NodeRules::for_testing();
552 let operator: FifoOperator<NodeRules> =
553 Operator::new(RulesBasedMutation::new(), FifoSelection::new(), rules);
554 let stats = SwarmStats::new();
555
556 let mut map: GraphMap<ActionNodeData, String, MapNodeState> = GraphMap::new();
557 let root = map.create_root(ActionNodeData::new("root"), MapNodeState::Open);
558
559 let updates = operator.initialize(root, &["auth", "login"]);
561 assert_eq!(updates.len(), 4); for update in updates {
565 map.apply_update(update, |k| k.to_string());
566 }
567 assert_eq!(map.node_count(), 5); let selected = operator.select(&map, 2, &stats);
571 assert_eq!(selected.len(), 2);
572 }
573
574 #[test]
575 fn test_operator_interpret() {
576 let rules = NodeRules::for_testing();
577 let operator: FifoOperator<NodeRules> =
578 Operator::new(RulesBasedMutation::new(), FifoSelection::new(), rules);
579 let stats = SwarmStats::new();
580
581 let map: GraphMap<ActionNodeData, String, MapNodeState> = GraphMap::new();
582 let actions = ActionsConfig::new();
583
584 let input = TestInput {
585 node_id: MapNodeId::new(0),
586 action_name: "grep".to_string(),
587 target: Some("auth.rs".to_string()),
588 result: ExplorationResult::Success,
589 };
590
591 let updates = operator.interpret(&input, &map, &actions, &stats);
593 assert!(!updates.is_empty());
595 }
596
597 #[test]
598 fn test_operator_name() {
599 let rules = NodeRules::for_testing();
600 let operator: FifoOperator<NodeRules> =
601 Operator::new(RulesBasedMutation::new(), FifoSelection::new(), rules);
602
603 assert_eq!(operator.name(), "RulesBased+FIFO");
604 }
605
606 #[test]
607 fn test_configurable_operator_works_like_fifo() {
608 use super::super::selection::SelectionKind;
609
610 let rules = NodeRules::for_testing();
611 let operator: ConfigurableOperator<NodeRules> = Operator::new(
612 RulesBasedMutation::new(),
613 AnySelection::from_kind(SelectionKind::Fifo, 1.0),
614 rules,
615 );
616 let stats = SwarmStats::new();
617
618 let mut map: GraphMap<ActionNodeData, String, MapNodeState> = GraphMap::new();
619 let root = map.create_root(ActionNodeData::new("root"), MapNodeState::Open);
620
621 let updates = operator.initialize(root, &["auth"]);
622 assert_eq!(updates.len(), 2); for update in updates {
625 map.apply_update(update, |k| k.to_string());
626 }
627
628 let selected = operator.select(&map, 1, &stats);
629 assert_eq!(selected.len(), 1);
630 }
631}