Skip to main content

swarm_engine_core/exploration/
space.rs

1//! Exploration Space V2 - 探索空間の統合レイヤー
2//!
3//! # 設計思想
4//!
5//! ## 責務の分離
6//!
7//! | レイヤー | 責務 |
8//! |----------|------|
9//! | `ExplorationSpaceV2` | 統合レイヤー。Map + Operator を繋ぐだけ |
10//! | `GraphMap` | 純粋なデータ構造(Plain Board) |
11//! | `Operator` | Mutation + Selection の連携、統計管理 |
12//! | `DependencyGraph` | アクション依存関係(Optional) |
13//!
14//! ## Operator パターン
15//!
16//! Mutation と Selection はアルゴリズム的にセットである:
17//! - Discover フェーズで発見した結果を Selection の統計に反映
18//! - その統計が次の Selection に影響
19//!
20//! Operator がこの連携を担保する。Space は Operator を通じて
21//! Mutation と Selection にアクセスする。
22//!
23//! # 使用例
24//!
25//! ```ignore
26//! use exploration::{Operator, RulesBasedMutation, FifoSelection, NodeRules};
27//!
28//! // Operator を構築
29//! let rules: NodeRules = dependency_graph.into();
30//! let operator = Operator::new(RulesBasedMutation::new(), FifoSelection::new(), rules);
31//!
32//! // Space に渡す
33//! let mut space = ExplorationSpaceV2::new(operator);
34//!
35//! // ルート作成と初期展開
36//! let root = space.create_root(ActionNodeData::new("task"));
37//! space.initialize(&["auth", "login"]);
38//!
39//! // 結果を適用(Operator 内で stats 更新 → Mutation 実行)
40//! space.apply(&input);
41//!
42//! // ノード選択(Operator 内で stats 参照 → Selection 実行)
43//! let next_nodes = space.select(worker_count);
44//! ```
45
46use std::fmt::Debug;
47
48use super::map::{ExplorationMap, GraphExplorationMap, GraphMap, MapNodeId, MapState};
49use super::mutation::{MapUpdateResult, MutationInput};
50use super::node_rules::Rules;
51use super::operator::{MutationLogic, Operator};
52use super::selection::SelectionLogic;
53use crate::actions::ActionsConfig;
54use crate::online_stats::SwarmStats;
55
56// Re-export MutationInput for convenience
57pub use super::mutation::MutationInput as SpaceMutationInput;
58
59// ============================================================================
60// ExplorationSpaceV2 - 統合レイヤー
61// ============================================================================
62
63/// 探索空間 V2 - 統合レイヤー
64///
65/// GraphMap と Operator を統合する。
66/// ロジックは持たず、コンポーネントを繋ぐだけ。
67///
68/// # 型パラメータ
69///
70/// - `N`: ノードデータの型
71/// - `E`: エッジデータの型
72/// - `S`: 状態の型
73/// - `R`: 遷移ルールの型(`Rules` を実装)
74/// - `M`: Mutation ロジック
75/// - `Sel`: Selection ロジック
76pub struct ExplorationSpaceV2<N, E, S, R, M, Sel>
77where
78    N: Debug + Clone,
79    E: Debug + Clone,
80    S: MapState,
81    R: Rules,
82    M: MutationLogic<N, E, S, R>,
83    Sel: SelectionLogic<N, E, S>,
84{
85    /// グラフデータ
86    map: GraphMap<N, E, S>,
87
88    /// Operator(Mutation + Selection の連携)
89    operator: Operator<M, Sel, N, E, S, R>,
90
91    /// アクション依存グラフ(Optional)
92    dependency_graph: Option<super::DependencyGraph>,
93
94    /// 完了フラグ
95    completed: bool,
96
97    /// アクション設定(category 判定用)
98    actions_config: ActionsConfig,
99}
100
101impl<N, E, S, R, M, Sel> ExplorationSpaceV2<N, E, S, R, M, Sel>
102where
103    N: Debug + Clone,
104    E: Debug + Clone,
105    S: MapState + Default,
106    R: Rules + 'static,
107    M: MutationLogic<N, E, S, R>,
108    Sel: SelectionLogic<N, E, S>,
109{
110    /// 新しい ExplorationSpaceV2 を作成
111    pub fn new(operator: Operator<M, Sel, N, E, S, R>) -> Self {
112        Self {
113            map: GraphMap::new(),
114            operator,
115            dependency_graph: None,
116            completed: false,
117            actions_config: ActionsConfig::new(),
118        }
119    }
120
121    /// DependencyGraph を設定
122    pub fn with_dependency_graph(mut self, graph: super::DependencyGraph) -> Self {
123        self.dependency_graph = Some(graph);
124        self
125    }
126
127    /// ActionsConfig を設定
128    pub fn with_actions_config(mut self, config: ActionsConfig) -> Self {
129        self.actions_config = config;
130        self
131    }
132
133    // ========================================================================
134    // Core API
135    // ========================================================================
136
137    /// ルートノードを作成
138    pub fn create_root(&mut self, data: N) -> MapNodeId {
139        self.map.create_root(data, S::default())
140    }
141
142    /// 入力を適用(Operator に委譲)
143    ///
144    /// Note: 統計は ActionEventPublisher 経由で別途記録されるため、
145    /// この関数では統計を更新しない。SwarmStats は Mutation に渡される。
146    pub fn apply(&mut self, input: &dyn MutationInput, stats: &SwarmStats) -> Vec<MapUpdateResult> {
147        let updates = self
148            .operator
149            .interpret(input, &self.map, &self.actions_config, stats);
150        self.map.apply_updates(updates, |k| k.to_string())
151    }
152
153    /// 次のノードを選択(Operator に委譲)
154    pub fn select(&self, count: usize, stats: &SwarmStats) -> Vec<MapNodeId> {
155        self.operator.select(&self.map, count, stats)
156    }
157
158    /// 次のノードを1つ選択(Operator に委譲)
159    pub fn next(&self, stats: &SwarmStats) -> Option<MapNodeId> {
160        self.operator.next(&self.map, stats)
161    }
162
163    /// ノードの優先度スコアを取得(Operator に委譲)
164    pub fn score(&self, action: &str, target: Option<&str>, stats: &SwarmStats) -> f64 {
165        self.operator.score(action, target, stats)
166    }
167
168    /// 次のノードを選択し、MapNode ごと返す
169    pub fn select_nodes(
170        &self,
171        count: usize,
172        stats: &SwarmStats,
173    ) -> Vec<&super::map::MapNode<N, S>> {
174        let ids = self.select(count, stats);
175        self.map.get_nodes(&ids)
176    }
177
178    /// 初期ノードを展開(Operator に委譲)
179    pub fn initialize(&mut self, initial_contexts: &[&str]) -> Vec<MapUpdateResult> {
180        let root_id = match self.map.root() {
181            Some(id) => id,
182            None => return vec![],
183        };
184
185        if initial_contexts.is_empty() {
186            return vec![];
187        }
188
189        let updates = self.operator.initialize(root_id, initial_contexts);
190        self.map.apply_updates(updates, |k| k.to_string())
191    }
192
193    // ========================================================================
194    // Query API
195    // ========================================================================
196
197    /// 複数ノードを一括取得
198    pub fn get_nodes(&self, ids: &[MapNodeId]) -> Vec<&super::map::MapNode<N, S>> {
199        self.map.get_nodes(ids)
200    }
201
202    /// 複数ノードのデータを一括取得
203    pub fn get_node_data(&self, ids: &[MapNodeId]) -> Vec<&N> {
204        self.map.get_node_data(ids)
205    }
206
207    /// フロンティア(探索可能ノード)を取得
208    pub fn frontiers(&self) -> Vec<MapNodeId> {
209        self.map.frontiers()
210    }
211
212    /// ルートノード ID
213    pub fn root(&self) -> Option<MapNodeId> {
214        self.map.root()
215    }
216
217    /// ノード数
218    pub fn node_count(&self) -> usize {
219        self.map.node_count()
220    }
221
222    /// 探索が完了したか(Operator に委譲)
223    pub fn is_complete(&self) -> bool {
224        self.completed || self.operator.is_complete(&self.map)
225    }
226
227    /// 成功で完了したか(外部からマーク済みの場合)
228    pub fn has_completed(&self) -> bool {
229        self.completed
230    }
231
232    /// 成功として完了をマーク
233    pub fn mark_completed(&mut self) {
234        self.completed = true;
235    }
236
237    /// フロンティアが枯渇したか
238    pub fn is_exhausted(&self) -> bool {
239        self.map.frontiers().is_empty()
240    }
241
242    /// GraphMap への参照
243    pub fn map(&self) -> &GraphMap<N, E, S> {
244        &self.map
245    }
246
247    /// GraphMap への可変参照
248    pub fn map_mut(&mut self) -> &mut GraphMap<N, E, S> {
249        &mut self.map
250    }
251
252    /// Operator への参照
253    pub fn operator(&self) -> &Operator<M, Sel, N, E, S, R> {
254        &self.operator
255    }
256
257    /// Operator への可変参照
258    pub fn operator_mut(&mut self) -> &mut Operator<M, Sel, N, E, S, R> {
259        &mut self.operator
260    }
261
262    /// DependencyGraph への参照
263    pub fn dependency_graph(&self) -> Option<&super::DependencyGraph> {
264        self.dependency_graph.as_ref()
265    }
266
267    /// Operator 名
268    pub fn operator_name(&self) -> String {
269        self.operator.name()
270    }
271
272    /// Rules への参照
273    pub fn rules(&self) -> &R {
274        self.operator.rules()
275    }
276}
277
278// ============================================================================
279// Type Alias for common usage
280// ============================================================================
281
282use super::map::MapNodeState;
283use super::mutation::ActionNodeData;
284use super::operator::RulesBasedMutation;
285use super::selection::{AnySelection, Fifo as FifoSelection};
286
287/// ActionNodeData + FIFO Operator の Space
288pub type ActionSpace<R> =
289    ExplorationSpaceV2<ActionNodeData, String, MapNodeState, R, RulesBasedMutation, FifoSelection>;
290
291/// ActionNodeData + Configurable Selection の Space
292///
293/// AdaptiveProvider / ConfigBasedProvider で Selection を動的に切り替え可能。
294pub type ConfigurableSpace<R> =
295    ExplorationSpaceV2<ActionNodeData, String, MapNodeState, R, RulesBasedMutation, AnySelection>;
296
297// ============================================================================
298// Tests
299// ============================================================================
300
301#[cfg(test)]
302mod tests {
303    use super::super::mutation::ExplorationResult;
304    use super::super::operator::Operator;
305    use super::super::NodeRules;
306    use super::*;
307
308    // テスト用の MutationInput 実装
309    struct TestInput {
310        node_id: MapNodeId,
311        action_name: String,
312        target: Option<String>,
313        result: ExplorationResult,
314    }
315
316    impl MutationInput for TestInput {
317        fn node_id(&self) -> MapNodeId {
318            self.node_id
319        }
320
321        fn action_name(&self) -> &str {
322            &self.action_name
323        }
324
325        fn target(&self) -> Option<&str> {
326            self.target.as_deref()
327        }
328
329        fn result(&self) -> &ExplorationResult {
330            &self.result
331        }
332    }
333
334    fn make_test_space() -> ActionSpace<NodeRules> {
335        let rules = NodeRules::for_testing();
336        let operator = Operator::new(RulesBasedMutation::new(), FifoSelection::new(), rules);
337        ExplorationSpaceV2::new(operator)
338    }
339
340    #[test]
341    fn test_exploration_space_v2_basic() {
342        let mut space = make_test_space();
343
344        // ルート作成
345        let root = space.create_root(ActionNodeData::new("root"));
346        assert_eq!(space.node_count(), 1);
347        assert_eq!(space.root(), Some(root));
348
349        // フロンティア確認
350        assert_eq!(space.frontiers().len(), 1);
351        assert!(!space.is_exhausted());
352    }
353
354    #[test]
355    fn test_exploration_space_v2_apply() {
356        let mut space = make_test_space();
357        let stats = SwarmStats::new();
358
359        let root = space.create_root(ActionNodeData::new("root"));
360
361        // 成功入力を適用(grep → read, summary への子ノード生成 + root を Close)
362        let input = TestInput {
363            node_id: root,
364            action_name: "grep".to_string(),
365            target: Some("auth.rs".to_string()),
366            result: ExplorationResult::Success,
367        };
368
369        let results = space.apply(&input, &stats);
370        // 3 results: read, summary, Close(root)
371        assert_eq!(results.len(), 3);
372        assert!(matches!(results[0], MapUpdateResult::NodeCreated(_)));
373        assert!(matches!(results[1], MapUpdateResult::NodeCreated(_)));
374        assert!(matches!(results[2], MapUpdateResult::NodeClosed(_)));
375
376        // ノード数が増えた (root + read + summary)
377        assert_eq!(space.node_count(), 3);
378
379        // root は Closed されたのでフロンティアは read, summary のみ
380        assert_eq!(space.frontiers().len(), 2);
381    }
382
383    #[test]
384    fn test_exploration_space_v2_select() {
385        let mut space = make_test_space();
386        let stats = SwarmStats::new();
387
388        let root = space.create_root(ActionNodeData::new("root"));
389
390        // 子ノードを追加(grep → read, summary)
391        let input1 = TestInput {
392            node_id: root,
393            action_name: "grep".to_string(),
394            target: Some("a.rs".to_string()),
395            result: ExplorationResult::Success,
396        };
397        space.apply(&input1, &stats);
398
399        // select で複数ノードを取得
400        let selected = space.select(2, &stats);
401        assert_eq!(selected.len(), 2);
402    }
403
404    #[test]
405    fn test_exploration_space_v2_completion() {
406        let mut space = make_test_space();
407
408        // 初期状態: フロンティアなし → Operator は完了と判断
409        assert!(!space.has_completed()); // 明示的マークはまだ
410        assert!(space.is_complete()); // Operator: frontiers 空 → 完了
411        assert!(space.is_exhausted());
412
413        // ルート追加 → フロンティアあり → 完了でない
414        space.create_root(ActionNodeData::new("task"));
415        assert!(!space.is_complete());
416        assert!(!space.is_exhausted());
417
418        // 明示的に完了マーク
419        space.mark_completed();
420        assert!(space.has_completed());
421        assert!(space.is_complete()); // マーク済みなので完了
422    }
423
424    #[test]
425    fn test_exploration_space_v2_is_complete_operator() {
426        let mut space = make_test_space();
427
428        let root = space.create_root(ActionNodeData::new("task"));
429        assert!(!space.is_complete()); // フロンティアあり
430
431        // ノードを Close → フロンティア枯渇 → Operator が完了と判断
432        space.map_mut().set_state(root, MapNodeState::Closed);
433        assert!(space.is_complete());
434        assert!(space.is_exhausted());
435        assert!(!space.has_completed()); // 明示的マークはしてない
436    }
437
438    #[test]
439    fn test_exploration_space_v2_initialize() {
440        let mut space = make_test_space();
441
442        // ルート作成
443        let _root = space.create_root(ActionNodeData::new("task"));
444        assert_eq!(space.node_count(), 1);
445
446        // 初期展開: ["auth", "login"] × roots["grep", "glob"] = 4 ノード
447        let results = space.initialize(&["auth", "login"]);
448        assert_eq!(results.len(), 4); // 2 contexts × 2 root actions
449
450        // ノード数: root + 4 初期ノード = 5
451        assert_eq!(space.node_count(), 5);
452
453        // フロンティア: root(Open) + 4 初期ノード = 5
454        let frontiers = space.frontiers();
455        assert_eq!(frontiers.len(), 5);
456    }
457
458    #[test]
459    fn test_exploration_space_v2_initialize_empty_rules() {
460        // 空のルールで初期化した場合
461        let rules = NodeRules::new(); // 空
462        let operator = Operator::new(RulesBasedMutation::new(), FifoSelection::new(), rules);
463        let mut space: ActionSpace<NodeRules> = ExplorationSpaceV2::new(operator);
464
465        let _root = space.create_root(ActionNodeData::new("task"));
466
467        // ルートアクションがない場合は初期ノードなし
468        let results = space.initialize(&["auth"]);
469        assert!(results.is_empty());
470        assert_eq!(space.node_count(), 1);
471    }
472
473    #[test]
474    fn test_exploration_space_v2_initialize_no_root() {
475        let mut space = make_test_space();
476
477        // ルートがない場合は何もしない
478        let results = space.initialize(&["auth"]);
479        assert!(results.is_empty());
480    }
481
482    #[test]
483    fn test_exploration_space_v2_get_nodes() {
484        use crate::actions::Action;
485
486        let mut space = make_test_space();
487        let stats = SwarmStats::new();
488
489        // ルートとサブノードを作成
490        let root = space.create_root(ActionNodeData::new("root"));
491        let input = TestInput {
492            node_id: root,
493            action_name: "grep".to_string(),
494            target: Some("auth.rs".to_string()),
495            result: ExplorationResult::Success,
496        };
497        space.apply(&input, &stats);
498
499        // select で取得
500        let selected = space.select(3, &stats);
501        assert!(!selected.is_empty());
502
503        // get_nodes で Node を取得
504        let nodes = space.get_nodes(&selected);
505        assert_eq!(nodes.len(), selected.len());
506
507        // get_node_data でデータのみ取得
508        let data = space.get_node_data(&selected);
509        assert_eq!(data.len(), selected.len());
510
511        // ActionNodeData から Action に変換可能
512        let actions: Vec<Action> = data.iter().map(|d| Action::from(*d)).collect();
513        assert_eq!(actions.len(), selected.len());
514    }
515
516    #[test]
517    fn test_exploration_space_v2_operator_name() {
518        let space = make_test_space();
519        assert_eq!(space.operator_name(), "RulesBased+FIFO");
520    }
521}