Skip to main content

swarm_engine_core/exploration/
operator.rs

1//! Exploration Operator - Mutation と Selection のペア
2//!
3//! # 設計思想
4//!
5//! Mutation(Map展開ロジック)と Selection(ノード選択ロジック)は
6//! アルゴリズム的にセットである。
7//!
8//! - Discover フェーズで発見した結果を Selection の統計に反映
9//! - その統計が次の Selection に影響
10//!
11//! Operator はこの連携を担保する。
12//!
13//! # 責務
14//!
15//! | コンポーネント | 責務 |
16//! |---------------|------|
17//! | MutationLogic | 「結果をどう Map に反映するか」 |
18//! | SelectionLogic | 「次にどの Node を選ぶか」(selection モジュール) |
19//! | Operator | M と Sel の連携、共有状態(stats)の管理 |
20//! | Space | Map + Operator を保持、API 提供 |
21//!
22//! # 使用例
23//!
24//! ```ignore
25//! use swarm_engine_core::exploration::{
26//!     Operator, RulesBasedMutation,
27//!     selection::{Ucb1, Fifo},
28//! };
29//!
30//! // 静的型付け Operator
31//! let operator = Operator::new(
32//!     RulesBasedMutation::new(),
33//!     Ucb1::new(1.41),
34//!     rules,
35//! );
36//!
37//! // 動的 Selection の Operator
38//! let operator = Operator::new(
39//!     RulesBasedMutation::new(),
40//!     AnySelection::from_kind(SelectionKind::Ucb1, 1.41),
41//!     rules,
42//! );
43//! ```
44
45use 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
57// ============================================================================
58// MutationLogic trait
59// ============================================================================
60
61/// Map 展開ロジック
62///
63/// 入力を MapUpdate に変換する責務のみを持つ。
64/// Selection とは分離されている。
65pub 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    /// 入力を MapUpdate に変換
73    ///
74    /// stats を参照して展開ロジックを調整できる(Optional)。
75    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    /// 初期ノードを展開
85    fn initialize(
86        &self,
87        root_id: MapNodeId,
88        initial_contexts: &[&str],
89        rules: &R,
90    ) -> Vec<MapUpdate<N, E, S>>;
91
92    /// ノードデータを生成
93    fn create_node_data(&self, input: &dyn MutationInput) -> N;
94
95    /// エッジデータを生成
96    fn create_edge_data(&self, input: &dyn MutationInput) -> E;
97
98    /// 初期状態を生成
99    fn initial_state(&self) -> S;
100
101    /// 名前
102    fn name(&self) -> &str;
103}
104
105// ============================================================================
106// Operator - Mutation と Selection のペア
107// ============================================================================
108
109/// Mutation と Selection を組み合わせた Operator
110///
111/// SwarmStats を参照して Selection を行う。
112/// 統計は ActionEventPublisher 経由で記録されるため、Operator 自身は統計を管理しない。
113pub 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
126    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    /// 新しい Operator を作成
140    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    /// Provider を設定
151    pub fn with_provider(mut self, provider: SharedLearnedProvider) -> Self {
152        self.provider = provider;
153        self
154    }
155
156    /// Provider への参照
157    pub fn provider(&self) -> &dyn LearnedProvider {
158        self.provider.as_ref()
159    }
160
161    /// Provider を設定
162    pub fn set_provider(&mut self, provider: SharedLearnedProvider) {
163        self.provider = provider;
164    }
165
166    /// Rules への参照
167    pub fn rules(&self) -> &R {
168        &self.rules
169    }
170
171    /// Selection への参照
172    pub fn selection(&self) -> &Sel {
173        &self.selection
174    }
175
176    /// Selection への可変参照
177    pub fn selection_mut(&mut self) -> &mut Sel {
178        &mut self.selection
179    }
180
181    /// Selection を置き換え
182    pub fn set_selection(&mut self, selection: Sel) {
183        self.selection = selection;
184    }
185
186    /// 入力を MapUpdate に変換
187    ///
188    /// Note: 統計は ActionEventPublisher 経由で別途記録されるため、
189    /// この関数では統計を更新しない。
190    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    /// 初期ノードを展開
202    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    /// 次のノードを1つ選択
212    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    /// 次のノードを複数選択
217    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    /// ノードのスコアを計算
228    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    /// 完了判定
234    ///
235    /// デフォルト: フロンティア枯渇で完了
236    pub fn is_complete(&self, map: &GraphMap<N, E, S>) -> bool {
237        map.frontiers().is_empty()
238    }
239
240    /// ノードデータを生成
241    pub fn create_node_data(&self, input: &dyn MutationInput) -> N {
242        self.mutation.create_node_data(input)
243    }
244
245    /// エッジデータを生成
246    pub fn create_edge_data(&self, input: &dyn MutationInput) -> E {
247        self.mutation.create_edge_data(input)
248    }
249
250    /// 初期状態を生成
251    pub fn initial_state(&self) -> S {
252        self.mutation.initial_state()
253    }
254
255    /// Operator 名
256    pub fn name(&self) -> String {
257        format!("{}+{}", self.mutation.name(), self.selection.name())
258    }
259}
260
261// ============================================================================
262// RulesBasedMutation - Rules ベースの基本 Mutation
263// ============================================================================
264
265/// Rules ベースの基本 Mutation
266///
267/// NodeRules を使用してノード遷移を制御する標準的な MutationLogic 実装。
268#[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
498// ============================================================================
499// Type Aliases for Common Operators
500// ============================================================================
501
502/// FIFO Operator(FIFO選択 + RulesBasedMutation)
503pub type FifoOperator<R> =
504    Operator<RulesBasedMutation, Fifo, ActionNodeData, String, super::map::MapNodeState, R>;
505
506/// UCB1 Operator
507pub type Ucb1Operator<R> =
508    Operator<RulesBasedMutation, Ucb1, ActionNodeData, String, super::map::MapNodeState, R>;
509
510/// 動的に Selection を切り替え可能な Operator
511///
512/// Config や LLM から Selection を決定できる。
513pub type ConfigurableOperator<R> =
514    Operator<RulesBasedMutation, AnySelection, ActionNodeData, String, super::map::MapNodeState, R>;
515
516// ============================================================================
517// Tests
518// ============================================================================
519
520#[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        // Initialize
560        let updates = operator.initialize(root, &["auth", "login"]);
561        assert_eq!(updates.len(), 4); // 2 roots × 2 contexts
562
563        // Apply updates
564        for update in updates {
565            map.apply_update(update, |k| k.to_string());
566        }
567        assert_eq!(map.node_count(), 5); // root + 4
568
569        // Select
570        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        // interpret 実行(統計は ActionEventPublisher 経由で記録される)
592        let updates = operator.interpret(&input, &map, &actions, &stats);
593        // Success なので Close が返る
594        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); // grep:auth, glob:auth
623
624        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}