use std::fmt::Debug;
use super::map::{ExplorationMap, GraphExplorationMap, GraphMap, MapNodeId, MapState};
use super::mutation::{MapUpdateResult, MutationInput};
use super::node_rules::Rules;
use super::operator::{MutationLogic, Operator};
use super::selection::SelectionLogic;
use crate::actions::ActionsConfig;
use crate::online_stats::SwarmStats;
pub use super::mutation::MutationInput as SpaceMutationInput;
pub struct ExplorationSpaceV2<N, E, S, R, M, Sel>
where
N: Debug + Clone,
E: Debug + Clone,
S: MapState,
R: Rules,
M: MutationLogic<N, E, S, R>,
Sel: SelectionLogic<N, E, S>,
{
map: GraphMap<N, E, S>,
operator: Operator<M, Sel, N, E, S, R>,
dependency_graph: Option<super::DependencyGraph>,
completed: bool,
actions_config: ActionsConfig,
}
impl<N, E, S, R, M, Sel> ExplorationSpaceV2<N, E, S, R, M, Sel>
where
N: Debug + Clone,
E: Debug + Clone,
S: MapState + Default,
R: Rules + 'static,
M: MutationLogic<N, E, S, R>,
Sel: SelectionLogic<N, E, S>,
{
pub fn new(operator: Operator<M, Sel, N, E, S, R>) -> Self {
Self {
map: GraphMap::new(),
operator,
dependency_graph: None,
completed: false,
actions_config: ActionsConfig::new(),
}
}
pub fn with_dependency_graph(mut self, graph: super::DependencyGraph) -> Self {
self.dependency_graph = Some(graph);
self
}
pub fn with_actions_config(mut self, config: ActionsConfig) -> Self {
self.actions_config = config;
self
}
pub fn create_root(&mut self, data: N) -> MapNodeId {
self.map.create_root(data, S::default())
}
pub fn apply(&mut self, input: &dyn MutationInput, stats: &SwarmStats) -> Vec<MapUpdateResult> {
let updates = self
.operator
.interpret(input, &self.map, &self.actions_config, stats);
self.map.apply_updates(updates, |k| k.to_string())
}
pub fn select(&self, count: usize, stats: &SwarmStats) -> Vec<MapNodeId> {
self.operator.select(&self.map, count, stats)
}
pub fn next(&self, stats: &SwarmStats) -> Option<MapNodeId> {
self.operator.next(&self.map, stats)
}
pub fn score(&self, action: &str, target: Option<&str>, stats: &SwarmStats) -> f64 {
self.operator.score(action, target, stats)
}
pub fn select_nodes(
&self,
count: usize,
stats: &SwarmStats,
) -> Vec<&super::map::MapNode<N, S>> {
let ids = self.select(count, stats);
self.map.get_nodes(&ids)
}
pub fn initialize(&mut self, initial_contexts: &[&str]) -> Vec<MapUpdateResult> {
let root_id = match self.map.root() {
Some(id) => id,
None => return vec![],
};
if initial_contexts.is_empty() {
return vec![];
}
let updates = self.operator.initialize(root_id, initial_contexts);
self.map.apply_updates(updates, |k| k.to_string())
}
pub fn get_nodes(&self, ids: &[MapNodeId]) -> Vec<&super::map::MapNode<N, S>> {
self.map.get_nodes(ids)
}
pub fn get_node_data(&self, ids: &[MapNodeId]) -> Vec<&N> {
self.map.get_node_data(ids)
}
pub fn frontiers(&self) -> Vec<MapNodeId> {
self.map.frontiers()
}
pub fn root(&self) -> Option<MapNodeId> {
self.map.root()
}
pub fn node_count(&self) -> usize {
self.map.node_count()
}
pub fn is_complete(&self) -> bool {
self.completed || self.operator.is_complete(&self.map)
}
pub fn has_completed(&self) -> bool {
self.completed
}
pub fn mark_completed(&mut self) {
self.completed = true;
}
pub fn is_exhausted(&self) -> bool {
self.map.frontiers().is_empty()
}
pub fn map(&self) -> &GraphMap<N, E, S> {
&self.map
}
pub fn map_mut(&mut self) -> &mut GraphMap<N, E, S> {
&mut self.map
}
pub fn operator(&self) -> &Operator<M, Sel, N, E, S, R> {
&self.operator
}
pub fn operator_mut(&mut self) -> &mut Operator<M, Sel, N, E, S, R> {
&mut self.operator
}
pub fn dependency_graph(&self) -> Option<&super::DependencyGraph> {
self.dependency_graph.as_ref()
}
pub fn operator_name(&self) -> String {
self.operator.name()
}
pub fn rules(&self) -> &R {
self.operator.rules()
}
}
use super::map::MapNodeState;
use super::mutation::ActionNodeData;
use super::operator::RulesBasedMutation;
use super::selection::{AnySelection, Fifo as FifoSelection};
pub type ActionSpace<R> =
ExplorationSpaceV2<ActionNodeData, String, MapNodeState, R, RulesBasedMutation, FifoSelection>;
pub type ConfigurableSpace<R> =
ExplorationSpaceV2<ActionNodeData, String, MapNodeState, R, RulesBasedMutation, AnySelection>;
#[cfg(test)]
mod tests {
use super::super::mutation::ExplorationResult;
use super::super::operator::Operator;
use super::super::NodeRules;
use super::*;
struct TestInput {
node_id: MapNodeId,
action_name: String,
target: Option<String>,
result: ExplorationResult,
}
impl MutationInput for TestInput {
fn node_id(&self) -> MapNodeId {
self.node_id
}
fn action_name(&self) -> &str {
&self.action_name
}
fn target(&self) -> Option<&str> {
self.target.as_deref()
}
fn result(&self) -> &ExplorationResult {
&self.result
}
}
fn make_test_space() -> ActionSpace<NodeRules> {
let rules = NodeRules::for_testing();
let operator = Operator::new(RulesBasedMutation::new(), FifoSelection::new(), rules);
ExplorationSpaceV2::new(operator)
}
#[test]
fn test_exploration_space_v2_basic() {
let mut space = make_test_space();
let root = space.create_root(ActionNodeData::new("root"));
assert_eq!(space.node_count(), 1);
assert_eq!(space.root(), Some(root));
assert_eq!(space.frontiers().len(), 1);
assert!(!space.is_exhausted());
}
#[test]
fn test_exploration_space_v2_apply() {
let mut space = make_test_space();
let stats = SwarmStats::new();
let root = space.create_root(ActionNodeData::new("root"));
let input = TestInput {
node_id: root,
action_name: "grep".to_string(),
target: Some("auth.rs".to_string()),
result: ExplorationResult::Success,
};
let results = space.apply(&input, &stats);
assert_eq!(results.len(), 3);
assert!(matches!(results[0], MapUpdateResult::NodeCreated(_)));
assert!(matches!(results[1], MapUpdateResult::NodeCreated(_)));
assert!(matches!(results[2], MapUpdateResult::NodeClosed(_)));
assert_eq!(space.node_count(), 3);
assert_eq!(space.frontiers().len(), 2);
}
#[test]
fn test_exploration_space_v2_select() {
let mut space = make_test_space();
let stats = SwarmStats::new();
let root = space.create_root(ActionNodeData::new("root"));
let input1 = TestInput {
node_id: root,
action_name: "grep".to_string(),
target: Some("a.rs".to_string()),
result: ExplorationResult::Success,
};
space.apply(&input1, &stats);
let selected = space.select(2, &stats);
assert_eq!(selected.len(), 2);
}
#[test]
fn test_exploration_space_v2_completion() {
let mut space = make_test_space();
assert!(!space.has_completed()); assert!(space.is_complete()); assert!(space.is_exhausted());
space.create_root(ActionNodeData::new("task"));
assert!(!space.is_complete());
assert!(!space.is_exhausted());
space.mark_completed();
assert!(space.has_completed());
assert!(space.is_complete()); }
#[test]
fn test_exploration_space_v2_is_complete_operator() {
let mut space = make_test_space();
let root = space.create_root(ActionNodeData::new("task"));
assert!(!space.is_complete());
space.map_mut().set_state(root, MapNodeState::Closed);
assert!(space.is_complete());
assert!(space.is_exhausted());
assert!(!space.has_completed()); }
#[test]
fn test_exploration_space_v2_initialize() {
let mut space = make_test_space();
let _root = space.create_root(ActionNodeData::new("task"));
assert_eq!(space.node_count(), 1);
let results = space.initialize(&["auth", "login"]);
assert_eq!(results.len(), 4);
assert_eq!(space.node_count(), 5);
let frontiers = space.frontiers();
assert_eq!(frontiers.len(), 5);
}
#[test]
fn test_exploration_space_v2_initialize_empty_rules() {
let rules = NodeRules::new(); let operator = Operator::new(RulesBasedMutation::new(), FifoSelection::new(), rules);
let mut space: ActionSpace<NodeRules> = ExplorationSpaceV2::new(operator);
let _root = space.create_root(ActionNodeData::new("task"));
let results = space.initialize(&["auth"]);
assert!(results.is_empty());
assert_eq!(space.node_count(), 1);
}
#[test]
fn test_exploration_space_v2_initialize_no_root() {
let mut space = make_test_space();
let results = space.initialize(&["auth"]);
assert!(results.is_empty());
}
#[test]
fn test_exploration_space_v2_get_nodes() {
use crate::actions::Action;
let mut space = make_test_space();
let stats = SwarmStats::new();
let root = space.create_root(ActionNodeData::new("root"));
let input = TestInput {
node_id: root,
action_name: "grep".to_string(),
target: Some("auth.rs".to_string()),
result: ExplorationResult::Success,
};
space.apply(&input, &stats);
let selected = space.select(3, &stats);
assert!(!selected.is_empty());
let nodes = space.get_nodes(&selected);
assert_eq!(nodes.len(), selected.len());
let data = space.get_node_data(&selected);
assert_eq!(data.len(), selected.len());
let actions: Vec<Action> = data.iter().map(|d| Action::from(*d)).collect();
assert_eq!(actions.len(), selected.len());
}
#[test]
fn test_exploration_space_v2_operator_name() {
let space = make_test_space();
assert_eq!(space.operator_name(), "RulesBased+FIFO");
}
}