use std::fmt::Debug;
use std::marker::PhantomData;
use std::sync::Arc;
use super::map::{ExplorationMap, GraphMap, MapNodeId, MapState};
use super::mutation::{ActionNodeData, ExplorationResult, MapUpdate, MutationInput};
use super::node_rules::Rules;
use super::selection::{AnySelection, Fifo, SelectionLogic, Ucb1};
use crate::actions::ActionsConfig;
use crate::learn::{LearnedProvider, NullProvider, SharedLearnedProvider};
use crate::online_stats::SwarmStats;
pub trait MutationLogic<N, E, S, R>: Send + Sync
where
N: Debug + Clone,
E: Debug + Clone,
S: MapState,
R: Rules,
{
fn interpret(
&self,
input: &dyn MutationInput,
map: &GraphMap<N, E, S>,
actions: &ActionsConfig,
rules: &R,
stats: &SwarmStats,
) -> Vec<MapUpdate<N, E, S>>;
fn initialize(
&self,
root_id: MapNodeId,
initial_contexts: &[&str],
rules: &R,
) -> Vec<MapUpdate<N, E, S>>;
fn create_node_data(&self, input: &dyn MutationInput) -> N;
fn create_edge_data(&self, input: &dyn MutationInput) -> E;
fn initial_state(&self) -> S;
fn name(&self) -> &str;
}
pub struct Operator<M, Sel, N, E, S, R>
where
N: Debug + Clone,
E: Debug + Clone,
S: MapState,
R: Rules,
M: MutationLogic<N, E, S, R>,
Sel: SelectionLogic<N, E, S>,
{
mutation: M,
pub selection: Sel,
rules: R,
provider: SharedLearnedProvider,
_phantom: PhantomData<(N, E, S)>,
}
impl<M, Sel, N, E, S, R> Operator<M, Sel, N, E, S, R>
where
N: Debug + Clone,
E: Debug + Clone,
S: MapState,
R: Rules,
M: MutationLogic<N, E, S, R>,
Sel: SelectionLogic<N, E, S>,
{
pub fn new(mutation: M, selection: Sel, rules: R) -> Self {
Self {
mutation,
selection,
rules,
provider: Arc::new(NullProvider),
_phantom: PhantomData,
}
}
pub fn with_provider(mut self, provider: SharedLearnedProvider) -> Self {
self.provider = provider;
self
}
pub fn provider(&self) -> &dyn LearnedProvider {
self.provider.as_ref()
}
pub fn set_provider(&mut self, provider: SharedLearnedProvider) {
self.provider = provider;
}
pub fn rules(&self) -> &R {
&self.rules
}
pub fn selection(&self) -> &Sel {
&self.selection
}
pub fn selection_mut(&mut self) -> &mut Sel {
&mut self.selection
}
pub fn set_selection(&mut self, selection: Sel) {
self.selection = selection;
}
pub fn interpret(
&self,
input: &dyn MutationInput,
map: &GraphMap<N, E, S>,
actions: &ActionsConfig,
stats: &SwarmStats,
) -> Vec<MapUpdate<N, E, S>> {
self.mutation
.interpret(input, map, actions, &self.rules, stats)
}
pub fn initialize(
&self,
root_id: MapNodeId,
initial_contexts: &[&str],
) -> Vec<MapUpdate<N, E, S>> {
self.mutation
.initialize(root_id, initial_contexts, &self.rules)
}
pub fn next(&self, map: &GraphMap<N, E, S>, stats: &SwarmStats) -> Option<MapNodeId> {
self.selection.next(map, stats, self.provider.as_ref())
}
pub fn select(
&self,
map: &GraphMap<N, E, S>,
count: usize,
stats: &SwarmStats,
) -> Vec<MapNodeId> {
self.selection
.select(map, count, stats, self.provider.as_ref())
}
pub fn score(&self, action: &str, target: Option<&str>, stats: &SwarmStats) -> f64 {
self.selection
.score(action, target, stats, self.provider.as_ref())
}
pub fn is_complete(&self, map: &GraphMap<N, E, S>) -> bool {
map.frontiers().is_empty()
}
pub fn create_node_data(&self, input: &dyn MutationInput) -> N {
self.mutation.create_node_data(input)
}
pub fn create_edge_data(&self, input: &dyn MutationInput) -> E {
self.mutation.create_edge_data(input)
}
pub fn initial_state(&self) -> S {
self.mutation.initial_state()
}
pub fn name(&self) -> String {
format!("{}+{}", self.mutation.name(), self.selection.name())
}
}
#[derive(Debug, Clone, Default)]
pub struct RulesBasedMutation;
impl RulesBasedMutation {
pub fn new() -> Self {
Self
}
}
impl<E, S, R> MutationLogic<ActionNodeData, E, S, R> for RulesBasedMutation
where
E: Debug + Clone + Default,
S: MapState + Default,
R: Rules,
{
fn interpret(
&self,
input: &dyn MutationInput,
_map: &GraphMap<ActionNodeData, E, S>,
_actions: &ActionsConfig,
rules: &R,
_stats: &SwarmStats,
) -> Vec<MapUpdate<ActionNodeData, E, S>> {
let node_id = input.node_id();
let action_name = input.action_name();
match input.result() {
ExplorationResult::Discover(children) => {
let successors = rules.successors(action_name);
tracing::debug!(
node_id = node_id.0,
action = %action_name,
target = ?input.target(),
children_count = children.len(),
successors = ?successors,
"ExpMap: Discover result"
);
if successors.is_empty() || children.is_empty() {
tracing::debug!(
node_id = node_id.0,
"ExpMap: Close (no successors or children)"
);
return vec![MapUpdate::Close(node_id)];
}
let mut updates: Vec<MapUpdate<ActionNodeData, E, S>> = Vec::new();
for child in children {
for next_action in &successors {
let dedup_key = format!("{}:{}", next_action, child);
let node_data = ActionNodeData::new(*next_action).with_target(child);
tracing::debug!(
parent = node_id.0,
next_action = %next_action,
child = %child,
dedup_key = %dedup_key,
"ExpMap: AddChild (from Discover)"
);
updates.push(MapUpdate::AddChild {
parent: node_id,
edge_data: E::default(),
node_data,
node_state: S::default(),
dedup_key,
});
}
}
updates.push(MapUpdate::Close(node_id));
updates
}
ExplorationResult::Success => {
tracing::debug!(
node_id = node_id.0,
action = %action_name,
target = ?input.target(),
is_terminal = rules.is_terminal(action_name),
"ExpMap: Success result"
);
if rules.is_terminal(action_name) {
tracing::debug!(node_id = node_id.0, "ExpMap: Close (terminal action)");
return vec![MapUpdate::Close(node_id)];
}
let successors = rules.successors(action_name);
if successors.is_empty() {
tracing::debug!(node_id = node_id.0, "ExpMap: Close (no successors)");
return vec![MapUpdate::Close(node_id)];
}
let target = input.target();
let mut updates: Vec<MapUpdate<ActionNodeData, E, S>> = Vec::new();
tracing::debug!(
node_id = node_id.0,
successors = ?successors,
target = ?target,
"ExpMap: expanding successors"
);
for next_action in successors {
if let Some((param_key, param_values)) = rules.param_variants(next_action) {
for param_value in param_values {
let dedup_key = match target {
Some(t) => {
format!("{}:{}:{}:{}", next_action, t, param_key, param_value)
}
None => format!("{}:_:{}:{}", next_action, param_key, param_value),
};
tracing::debug!(
parent = node_id.0,
next_action = %next_action,
param = %format!("{}={}", param_key, param_value),
dedup_key = %dedup_key,
"ExpMap: AddChild (with param variant)"
);
let mut node_data = ActionNodeData::new(next_action);
if let Some(t) = target {
node_data = node_data.with_target(t);
}
node_data = node_data.with_arg(param_key, param_value);
updates.push(MapUpdate::AddChild {
parent: node_id,
edge_data: E::default(),
node_data,
node_state: S::default(),
dedup_key,
});
}
} else {
let dedup_key = match target {
Some(t) => format!("{}:{}", next_action, t),
None => format!("{}:_", next_action),
};
tracing::debug!(
parent = node_id.0,
next_action = %next_action,
dedup_key = %dedup_key,
"ExpMap: AddChild (from Success)"
);
let mut node_data = ActionNodeData::new(next_action);
if let Some(t) = target {
node_data = node_data.with_target(t);
}
updates.push(MapUpdate::AddChild {
parent: node_id,
edge_data: E::default(),
node_data,
node_state: S::default(),
dedup_key,
});
}
}
updates.push(MapUpdate::Close(node_id));
updates
}
ExplorationResult::Fail(ref err) => {
tracing::debug!(
node_id = node_id.0,
action = %action_name,
target = ?input.target(),
error = ?err,
"ExpMap: Fail result, closing node"
);
vec![MapUpdate::Close(node_id)]
}
}
}
fn initialize(
&self,
root_id: MapNodeId,
initial_contexts: &[&str],
rules: &R,
) -> Vec<MapUpdate<ActionNodeData, E, S>> {
let root_actions = rules.roots();
tracing::debug!(
root_id = root_id.0,
root_actions = ?root_actions,
initial_contexts = ?initial_contexts,
"ExpMap: initialize"
);
let mut updates = Vec::new();
for action in root_actions {
for ctx in initial_contexts {
let dedup_key = format!("{}:{}", action, ctx);
let node_data = ActionNodeData::new(action).with_target(*ctx);
tracing::debug!(
parent = root_id.0,
action = %action,
context = %ctx,
dedup_key = %dedup_key,
"ExpMap: AddChild (initial)"
);
updates.push(MapUpdate::AddChild {
parent: root_id,
edge_data: E::default(),
node_data,
node_state: S::default(),
dedup_key,
});
}
}
updates
}
fn create_node_data(&self, input: &dyn MutationInput) -> ActionNodeData {
ActionNodeData::from_input(input)
}
fn create_edge_data(&self, _input: &dyn MutationInput) -> E {
E::default()
}
fn initial_state(&self) -> S {
S::default()
}
fn name(&self) -> &str {
"RulesBased"
}
}
pub type FifoOperator<R> =
Operator<RulesBasedMutation, Fifo, ActionNodeData, String, super::map::MapNodeState, R>;
pub type Ucb1Operator<R> =
Operator<RulesBasedMutation, Ucb1, ActionNodeData, String, super::map::MapNodeState, R>;
pub type ConfigurableOperator<R> =
Operator<RulesBasedMutation, AnySelection, ActionNodeData, String, super::map::MapNodeState, R>;
#[cfg(test)]
mod tests {
use super::super::map::MapNodeState;
use super::super::selection::FifoSelection;
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
}
}
#[test]
fn test_operator_basic() {
let rules = NodeRules::for_testing();
let operator: FifoOperator<NodeRules> =
Operator::new(RulesBasedMutation::new(), FifoSelection::new(), rules);
let stats = SwarmStats::new();
let mut map: GraphMap<ActionNodeData, String, MapNodeState> = GraphMap::new();
let root = map.create_root(ActionNodeData::new("root"), MapNodeState::Open);
let updates = operator.initialize(root, &["auth", "login"]);
assert_eq!(updates.len(), 4);
for update in updates {
map.apply_update(update, |k| k.to_string());
}
assert_eq!(map.node_count(), 5);
let selected = operator.select(&map, 2, &stats);
assert_eq!(selected.len(), 2);
}
#[test]
fn test_operator_interpret() {
let rules = NodeRules::for_testing();
let operator: FifoOperator<NodeRules> =
Operator::new(RulesBasedMutation::new(), FifoSelection::new(), rules);
let stats = SwarmStats::new();
let map: GraphMap<ActionNodeData, String, MapNodeState> = GraphMap::new();
let actions = ActionsConfig::new();
let input = TestInput {
node_id: MapNodeId::new(0),
action_name: "grep".to_string(),
target: Some("auth.rs".to_string()),
result: ExplorationResult::Success,
};
let updates = operator.interpret(&input, &map, &actions, &stats);
assert!(!updates.is_empty());
}
#[test]
fn test_operator_name() {
let rules = NodeRules::for_testing();
let operator: FifoOperator<NodeRules> =
Operator::new(RulesBasedMutation::new(), FifoSelection::new(), rules);
assert_eq!(operator.name(), "RulesBased+FIFO");
}
#[test]
fn test_configurable_operator_works_like_fifo() {
use super::super::selection::SelectionKind;
let rules = NodeRules::for_testing();
let operator: ConfigurableOperator<NodeRules> = Operator::new(
RulesBasedMutation::new(),
AnySelection::from_kind(SelectionKind::Fifo, 1.0),
rules,
);
let stats = SwarmStats::new();
let mut map: GraphMap<ActionNodeData, String, MapNodeState> = GraphMap::new();
let root = map.create_root(ActionNodeData::new("root"), MapNodeState::Open);
let updates = operator.initialize(root, &["auth"]);
assert_eq!(updates.len(), 2);
for update in updates {
map.apply_update(update, |k| k.to_string());
}
let selected = operator.select(&map, 1, &stats);
assert_eq!(selected.len(), 1);
}
}