use crate::base::{EdgeWeight, Graph, Node};
use crate::error::{GraphError, Result};
use scirs2_core::random::{Rng, RngExt};
use std::collections::HashMap;
use std::time::{Duration, Instant};
#[derive(Debug, Clone, Default)]
pub struct SimplePerformanceMonitor {
active: HashMap<String, Instant>,
completed: HashMap<String, (usize, Duration)>,
}
impl SimplePerformanceMonitor {
pub fn new() -> Self {
Self::default()
}
pub fn start_operation(&mut self, name: &str) {
self.active.insert(name.to_string(), Instant::now());
}
pub fn stop_operation(&mut self, name: &str) {
if let Some(start) = self.active.remove(name) {
let elapsed = start.elapsed();
let entry = self
.completed
.entry(name.to_string())
.or_insert((0, Duration::ZERO));
entry.0 += 1;
entry.1 += elapsed;
}
}
pub fn get_report(&self) -> SimplePerformanceReport {
let total_operations: usize = self.completed.values().map(|(count, _)| *count).sum();
let total_time: Duration = self.completed.values().map(|(_, dur)| *dur).sum();
SimplePerformanceReport {
total_operations,
total_time_ms: total_time.as_secs_f64() * 1000.0,
}
}
}
#[derive(Debug, Clone, Default)]
pub struct SimplePerformanceReport {
pub total_operations: usize,
pub total_time_ms: f64,
}
#[derive(Debug, Clone)]
pub struct AdvancedConfig {
pub enable_neural_rl: bool,
pub enable_gpu_acceleration: bool,
pub enable_neuromorphic: bool,
pub enable_realtime_adaptation: bool,
pub enable_memory_optimization: bool,
pub learning_rate: f64,
pub memory_threshold_mb: usize,
pub gpu_memory_pool_mb: usize,
pub neural_hidden_size: usize,
}
impl Default for AdvancedConfig {
fn default() -> Self {
AdvancedConfig {
enable_neural_rl: true,
enable_gpu_acceleration: true,
enable_neuromorphic: true,
enable_realtime_adaptation: true,
enable_memory_optimization: true,
learning_rate: 0.001,
memory_threshold_mb: 1024,
gpu_memory_pool_mb: 2048,
neural_hidden_size: 128,
}
}
}
#[derive(Debug, Clone)]
pub enum ExplorationStrategy {
EpsilonGreedy {
epsilon: f64,
},
UCB {
c: f64,
},
ThompsonSampling {
alpha: f64,
beta: f64,
},
AdaptiveUncertainty {
uncertainty_threshold: f64,
},
}
impl Default for ExplorationStrategy {
fn default() -> Self {
ExplorationStrategy::EpsilonGreedy { epsilon: 0.1 }
}
}
pub struct AdvancedProcessor {
config: AdvancedConfig,
performance_monitor: SimplePerformanceMonitor,
stats: AdvancedStats,
rl_agent: NeuralRLAgent,
gpu_context: GPUAccelerationContext,
}
pub type CandidateOp<N, E, Ix, T> = fn(&Graph<N, E, Ix>) -> Result<T>;
impl AdvancedProcessor {
pub fn new(config: AdvancedConfig) -> Self {
let gpu_context = if config.enable_gpu_acceleration {
GPUAccelerationContext::detect()
} else {
GPUAccelerationContext::default()
};
let rl_agent = NeuralRLAgent::new(config.clone(), ExplorationStrategy::default());
AdvancedProcessor {
config,
performance_monitor: SimplePerformanceMonitor::new(),
stats: AdvancedStats::default(),
rl_agent,
gpu_context,
}
}
pub fn execute<N, E, Ix, T, F>(&mut self, graph: &Graph<N, E, Ix>, operation: F) -> Result<T>
where
N: Node + std::fmt::Debug,
E: EdgeWeight,
Ix: petgraph::graph::IndexType,
F: FnOnce(&Graph<N, E, Ix>) -> Result<T>,
{
self.performance_monitor
.start_operation("advanced_execution");
let result = operation(graph);
self.performance_monitor
.stop_operation("advanced_execution");
let graph_bytes = structural_graph_memory_estimate(graph);
self.update_stats(graph_bytes);
result
}
pub fn execute_profiled<N, E, Ix, T, F>(
&mut self,
graph: &Graph<N, E, Ix>,
operation: F,
) -> Result<T>
where
N: Node + std::fmt::Debug,
E: EdgeWeight,
Ix: petgraph::graph::IndexType,
F: FnOnce(&Graph<N, E, Ix>) -> Result<T>,
{
self.performance_monitor
.start_operation("advanced_execution");
let sample_interval = Duration::from_micros(200);
let (result, memory_metrics) =
crate::memory::AdvancedMemoryAnalyzer::analyze_operation_memory(
"advanced_execution",
|| operation(graph),
sample_interval,
);
self.performance_monitor
.stop_operation("advanced_execution");
let memory_bytes = if memory_metrics.peak_memory > 0 {
memory_metrics.peak_memory as usize
} else {
structural_graph_memory_estimate(graph)
};
self.update_stats(memory_bytes);
result
}
pub fn execute_adaptive<N, E, Ix, T>(
&mut self,
graph: &Graph<N, E, Ix>,
candidates: &[CandidateOp<N, E, Ix, T>],
) -> Result<T>
where
N: Node + std::fmt::Debug,
E: EdgeWeight,
Ix: petgraph::graph::IndexType,
{
if candidates.is_empty() {
return Err(GraphError::InvalidGraph(
"execute_adaptive: at least one candidate operation is required".to_string(),
));
}
self.performance_monitor
.start_operation("advanced_execution");
let arm = self.rl_agent.select_arm(candidates.len());
let start = Instant::now();
let result = (candidates[arm])(graph);
let elapsed_secs = start.elapsed().as_secs_f64();
self.performance_monitor
.stop_operation("advanced_execution");
let reward = 1.0 / (1.0 + elapsed_secs);
self.rl_agent.record_reward(arm, reward);
let graph_bytes = structural_graph_memory_estimate(graph);
self.update_stats(graph_bytes);
result
}
fn update_stats(&mut self, latest_memory_estimate_bytes: usize) {
let report = self.performance_monitor.get_report();
self.stats.total_operations = report.total_operations;
self.stats.avg_execution_time_ms = if report.total_operations > 0 {
report.total_time_ms / report.total_operations as f64
} else {
0.0
};
self.stats.memory_usage_bytes = latest_memory_estimate_bytes;
const ASSUMED_FIXED_OVERHEAD_BYTES: f64 = 1024.0;
self.stats.memory_efficiency = if latest_memory_estimate_bytes == 0 {
1.0
} else {
let bytes = latest_memory_estimate_bytes as f64;
bytes / (bytes + ASSUMED_FIXED_OVERHEAD_BYTES)
};
self.stats.gpu_utilization_percent = 0.0;
}
pub fn get_performance_report(&self) -> SimplePerformanceReport {
self.performance_monitor.get_report()
}
pub fn get_optimization_stats(&self) -> AdvancedStats {
self.stats.clone()
}
pub fn gpu_context(&self) -> &GPUAccelerationContext {
&self.gpu_context
}
pub fn rl_agent(&self) -> &NeuralRLAgent {
&self.rl_agent
}
pub fn rl_agent_mut(&mut self) -> &mut NeuralRLAgent {
&mut self.rl_agent
}
}
fn structural_graph_memory_estimate<N, E, Ix>(graph: &Graph<N, E, Ix>) -> usize
where
N: Node + std::fmt::Debug,
E: EdgeWeight,
Ix: petgraph::graph::IndexType,
{
const BASE_OVERHEAD_BYTES: usize = 1024;
let node_size = std::mem::size_of::<N>() + std::mem::size_of::<Ix>();
let edge_size = std::mem::size_of::<E>() + 2 * std::mem::size_of::<Ix>();
BASE_OVERHEAD_BYTES + graph.node_count() * node_size + graph.edge_count() * edge_size
}
#[derive(Debug, Clone)]
pub struct AdvancedStats {
pub total_operations: usize,
pub avg_execution_time_ms: f64,
pub memory_usage_bytes: usize,
pub gpu_utilization_percent: f64,
pub memory_efficiency: f64,
}
impl Default for AdvancedStats {
fn default() -> Self {
AdvancedStats {
total_operations: 0,
avg_execution_time_ms: 0.0,
memory_usage_bytes: 0,
gpu_utilization_percent: 0.0,
memory_efficiency: 1.0,
}
}
}
pub fn create_advanced_processor() -> AdvancedProcessor {
AdvancedProcessor::new(AdvancedConfig::default())
}
pub fn create_enhanced_advanced_processor() -> AdvancedProcessor {
let mut config = AdvancedConfig::default();
config.neural_hidden_size = 256;
config.gpu_memory_pool_mb = 4096;
AdvancedProcessor::new(config)
}
pub fn execute_with_advanced<N, E, Ix, T>(
graph: &Graph<N, E, Ix>,
operation: impl FnOnce(&Graph<N, E, Ix>) -> Result<T>,
) -> Result<T>
where
N: Node + std::fmt::Debug,
E: EdgeWeight,
Ix: petgraph::graph::IndexType,
{
let mut processor = create_advanced_processor();
processor.execute(graph, operation)
}
pub fn execute_with_enhanced_advanced<N, E, Ix, T>(
graph: &Graph<N, E, Ix>,
operation: impl FnOnce(&Graph<N, E, Ix>) -> Result<T>,
) -> Result<T>
where
N: Node + std::fmt::Debug,
E: EdgeWeight,
Ix: petgraph::graph::IndexType,
{
let mut processor = create_enhanced_advanced_processor();
processor.execute(graph, operation)
}
pub fn create_large_graph_advanced_processor() -> AdvancedProcessor {
let mut config = AdvancedConfig::default();
config.memory_threshold_mb = 8192;
config.gpu_memory_pool_mb = 8192;
config.enable_memory_optimization = true;
AdvancedProcessor::new(config)
}
pub fn create_realtime_advanced_processor() -> AdvancedProcessor {
let mut config = AdvancedConfig::default();
config.enable_realtime_adaptation = true;
config.learning_rate = 0.01;
AdvancedProcessor::new(config)
}
pub fn create_performance_advanced_processor() -> AdvancedProcessor {
let mut config = AdvancedConfig::default();
config.enable_gpu_acceleration = true;
config.enable_neuromorphic = true;
config.gpu_memory_pool_mb = 16384;
AdvancedProcessor::new(config)
}
pub fn create_memory_efficient_advanced_processor() -> AdvancedProcessor {
let mut config = AdvancedConfig::default();
config.enable_memory_optimization = true;
config.memory_threshold_mb = 512;
config.gpu_memory_pool_mb = 1024;
AdvancedProcessor::new(config)
}
pub fn create_adaptive_advanced_processor() -> AdvancedProcessor {
let mut config = AdvancedConfig::default();
config.enable_realtime_adaptation = true;
config.enable_neural_rl = true;
config.learning_rate = 0.005;
AdvancedProcessor::new(config)
}
#[derive(Debug, Clone)]
pub struct AlgorithmMetrics {
pub algorithm_name: String,
pub execution_time_ms: f64,
pub memory_usage_bytes: usize,
}
impl Default for AlgorithmMetrics {
fn default() -> Self {
AlgorithmMetrics {
algorithm_name: String::new(),
execution_time_ms: 0.0,
memory_usage_bytes: 0,
}
}
}
#[derive(Debug, Default, Clone, Copy, PartialEq)]
pub struct GPUAccelerationContext {
pub gpu_available: bool,
pub memory_pool_size: usize,
}
impl GPUAccelerationContext {
pub fn detect() -> Self {
#[cfg(feature = "cuda")]
{
if crate::gpu_cuda::cuda_is_available() {
return GPUAccelerationContext {
gpu_available: true,
memory_pool_size: 0,
};
}
}
GPUAccelerationContext {
gpu_available: false,
memory_pool_size: 0,
}
}
}
#[derive(Debug, Clone)]
pub struct NeuralRLAgent {
pub config: AdvancedConfig,
pub learning_rate: f64,
pub strategy: ExplorationStrategy,
arm_stats: Vec<(u64, f64)>,
}
impl Default for NeuralRLAgent {
fn default() -> Self {
NeuralRLAgent {
config: AdvancedConfig::default(),
learning_rate: 0.001,
strategy: ExplorationStrategy::default(),
arm_stats: Vec::new(),
}
}
}
impl NeuralRLAgent {
pub fn new(config: AdvancedConfig, strategy: ExplorationStrategy) -> Self {
let learning_rate = config.learning_rate;
NeuralRLAgent {
config,
learning_rate,
strategy,
arm_stats: Vec::new(),
}
}
pub fn arm_count(&self) -> usize {
self.arm_stats.len()
}
pub fn arm_stats(&self, arm: usize) -> Option<(u64, f64)> {
self.arm_stats.get(arm).copied()
}
pub fn select_arm(&mut self, n_arms: usize) -> usize {
if n_arms == 0 {
return 0;
}
if self.arm_stats.len() < n_arms {
self.arm_stats.resize(n_arms, (0, 0.0));
}
if let Some(idx) = self.arm_stats[..n_arms]
.iter()
.position(|&(count, _)| count == 0)
{
return idx;
}
match &self.strategy {
ExplorationStrategy::EpsilonGreedy { epsilon } => {
let mut rng = scirs2_core::random::rng();
if rng.random::<f64>() < *epsilon {
rng.random_range(0..n_arms)
} else {
self.best_arm(n_arms)
}
}
ExplorationStrategy::UCB { c } => {
let total_pulls: u64 = self.arm_stats[..n_arms].iter().map(|&(cnt, _)| cnt).sum();
let ln_total = (total_pulls.max(1) as f64).ln();
(0..n_arms)
.max_by(|&a, &b| {
let score = |i: usize| {
let (count, mean) = self.arm_stats[i];
mean + c * (ln_total / (count.max(1) as f64)).sqrt()
};
score(a)
.partial_cmp(&score(b))
.unwrap_or(std::cmp::Ordering::Equal)
})
.unwrap_or(0)
}
ExplorationStrategy::ThompsonSampling { alpha, beta } => {
let mut rng = scirs2_core::random::rng();
let mut best = 0usize;
let mut best_score = f64::NEG_INFINITY;
for i in 0..n_arms {
let (count, mean) = self.arm_stats[i];
let mean = mean.clamp(0.0, 1.0);
let successes = alpha + mean * count as f64;
let failures = beta + (1.0 - mean) * count as f64;
let posterior_mean = successes / (successes + failures).max(1e-9);
let uncertainty = 1.0 / ((count as f64) + 1.0).sqrt();
let noise =
box_muller_standard_normal(rng.random::<f64>(), rng.random::<f64>())
* uncertainty
* 0.25;
let score = posterior_mean + noise;
if score > best_score {
best_score = score;
best = i;
}
}
best
}
ExplorationStrategy::AdaptiveUncertainty {
uncertainty_threshold,
} => {
let least_tried = (0..n_arms)
.min_by_key(|&i| self.arm_stats[i].0)
.unwrap_or(0);
let uncertainty = 1.0 / ((self.arm_stats[least_tried].0 as f64) + 1.0).sqrt();
if uncertainty > *uncertainty_threshold {
least_tried
} else {
self.best_arm(n_arms)
}
}
}
}
pub fn record_reward(&mut self, arm: usize, reward: f64) {
if arm >= self.arm_stats.len() {
self.arm_stats.resize(arm + 1, (0, 0.0));
}
let (count, mean) = &mut self.arm_stats[arm];
*count += 1;
if self.learning_rate > 0.0 {
*mean += self.learning_rate * (reward - *mean);
} else {
*mean += (reward - *mean) / (*count as f64);
}
}
fn best_arm(&self, n_arms: usize) -> usize {
(0..n_arms)
.max_by(|&a, &b| {
self.arm_stats[a]
.1
.partial_cmp(&self.arm_stats[b].1)
.unwrap_or(std::cmp::Ordering::Equal)
})
.unwrap_or(0)
}
}
fn box_muller_standard_normal(u1: f64, u2: f64) -> f64 {
let u1 = u1.max(1e-12); (-2.0 * u1.ln()).sqrt() * (2.0 * std::f64::consts::PI * u2).cos()
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct NeuromorphicProcessor {
pub num_neurons: usize,
pub num_synapses: usize,
}
impl Default for NeuromorphicProcessor {
fn default() -> Self {
NeuromorphicProcessor {
num_neurons: 1000,
num_synapses: 10000,
}
}
}
impl NeuromorphicProcessor {
pub fn accelerate<T>(&self, operation_name: &str) -> Result<T> {
Err(GraphError::Unsupported(format!(
"NeuromorphicProcessor::accelerate({operation_name}): neuromorphic acceleration is \
not implemented (num_neurons={}, num_synapses={} are configuration only; no \
neuromorphic simulator or hardware backend exists in this crate)",
self.num_neurons, self.num_synapses
)))
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::base::Graph;
fn small_graph() -> Graph<i32, f64> {
let mut graph: Graph<i32, f64> = Graph::new();
graph.add_edge(0, 1, 1.0).expect("add_edge failed");
graph.add_edge(1, 2, 1.0).expect("add_edge failed");
graph
}
fn bigger_graph() -> Graph<i32, f64> {
let mut graph: Graph<i32, f64> = Graph::new();
for i in 0..50i32 {
graph.add_edge(i, i + 1, 1.0).expect("add_edge failed");
}
graph
}
#[test]
fn test_performance_monitor_tracks_real_timing() {
let mut monitor = SimplePerformanceMonitor::new();
monitor.start_operation("op_a");
std::thread::sleep(Duration::from_millis(5));
monitor.stop_operation("op_a");
monitor.start_operation("op_a");
std::thread::sleep(Duration::from_millis(5));
monitor.stop_operation("op_a");
let report = monitor.get_report();
assert_eq!(report.total_operations, 2);
assert!(
report.total_time_ms >= 8.0,
"expected at least ~10ms of real elapsed time, got {}",
report.total_time_ms
);
}
#[test]
fn test_performance_monitor_stop_without_start_is_noop() {
let mut monitor = SimplePerformanceMonitor::new();
monitor.stop_operation("never_started");
let report = monitor.get_report();
assert_eq!(report.total_operations, 0);
assert_eq!(report.total_time_ms, 0.0);
}
#[test]
fn test_advanced_processor_execute_produces_real_stats() {
let mut processor = create_advanced_processor();
let before = processor.get_optimization_stats();
assert_eq!(before.total_operations, 0);
let graph = small_graph();
let result: Result<usize> = processor.execute(&graph, |g| Ok(g.node_count()));
assert_eq!(result.expect("execute failed"), 3);
let after = processor.get_optimization_stats();
assert_eq!(
after.total_operations, 1,
"total_operations must reflect the real call count, not stay at the old default 0"
);
assert!(
after.memory_usage_bytes > 0,
"memory_usage_bytes must be a real (nonzero) structural estimate"
);
assert_eq!(after.gpu_utilization_percent, 0.0);
}
#[test]
fn test_advanced_processor_memory_estimate_scales_with_graph_size() {
let mut small_processor = create_advanced_processor();
let mut big_processor = create_advanced_processor();
let small = small_graph();
let big = bigger_graph();
small_processor
.execute(&small, |g| Ok(g.node_count()))
.expect("execute failed");
big_processor
.execute(&big, |g| Ok(g.node_count()))
.expect("execute failed");
let small_stats = small_processor.get_optimization_stats();
let big_stats = big_processor.get_optimization_stats();
assert!(
big_stats.memory_usage_bytes > small_stats.memory_usage_bytes,
"a 51-node graph should report a larger memory estimate than a 3-node graph \
({} vs {})",
big_stats.memory_usage_bytes,
small_stats.memory_usage_bytes
);
}
#[test]
fn test_advanced_processor_execute_profiled_runs_real_operation() {
let mut processor = create_large_graph_advanced_processor();
let graph = bigger_graph();
let result: Result<usize> = processor.execute_profiled(&graph, |g| Ok(g.edge_count()));
assert_eq!(result.expect("execute_profiled failed"), 50);
let stats = processor.get_optimization_stats();
assert_eq!(stats.total_operations, 1);
assert!(stats.memory_usage_bytes > 0);
}
#[test]
fn test_gpu_acceleration_context_detect_is_honest() {
let ctx = GPUAccelerationContext::detect();
assert_eq!(ctx.memory_pool_size, 0);
#[cfg(feature = "cuda")]
{
assert_eq!(ctx.gpu_available, crate::gpu_cuda::cuda_is_available());
}
#[cfg(not(feature = "cuda"))]
{
assert!(!ctx.gpu_available);
}
}
#[test]
fn test_neuromorphic_processor_is_honestly_unsupported() {
let processor = NeuromorphicProcessor::default();
let result: Result<()> = processor.accelerate("pagerank");
match result {
Err(GraphError::Unsupported(msg)) => {
assert!(msg.contains("neuromorphic"));
}
other => panic!("expected GraphError::Unsupported, got {other:?}"),
}
}
#[test]
fn test_neural_rl_agent_select_arm_tries_every_arm_before_repeating() {
let mut agent = NeuralRLAgent::new(
AdvancedConfig::default(),
ExplorationStrategy::EpsilonGreedy { epsilon: 0.0 },
);
let mut seen = std::collections::HashSet::new();
for _ in 0..4 {
let arm = agent.select_arm(4);
seen.insert(arm);
agent.record_reward(arm, 0.5);
}
assert_eq!(
seen.len(),
4,
"every arm must be tried at least once during warm-up"
);
}
#[test]
fn test_neural_rl_agent_epsilon_greedy_converges_to_best_arm() {
let mut agent = NeuralRLAgent::new(
AdvancedConfig::default(),
ExplorationStrategy::EpsilonGreedy { epsilon: 0.0 },
);
const N_ARMS: usize = 5;
for arm in 0..N_ARMS {
agent.record_reward(arm, if arm == 0 { 1.0 } else { 0.0 });
}
for _ in 0..20 {
let arm = agent.select_arm(N_ARMS);
agent.record_reward(arm, if arm == 0 { 1.0 } else { 0.0 });
}
let chosen = agent.select_arm(N_ARMS);
assert_eq!(
chosen, 0,
"epsilon=0 agent must exploit the arm with the only nonzero reward"
);
}
#[test]
fn test_neural_rl_agent_ucb_prefers_less_explored_arms_when_tied() {
let mut agent = NeuralRLAgent::new(
AdvancedConfig {
learning_rate: 0.0,
..AdvancedConfig::default()
},
ExplorationStrategy::UCB { c: 2.0 },
);
agent.record_reward(0, 0.5);
agent.record_reward(1, 0.5);
for _ in 0..30 {
agent.record_reward(0, 0.5);
}
let (count0, mean0) = agent.arm_stats(0).expect("arm 0 stats");
let (count1, mean1) = agent.arm_stats(1).expect("arm 1 stats");
assert_eq!(count0, 31);
assert_eq!(count1, 1);
assert!((mean0 - mean1).abs() < 1e-9, "means must be tied by design");
let chosen = agent.select_arm(2);
assert_eq!(
chosen, 1,
"with tied mean rewards, UCB must prefer the far-less-explored arm"
);
}
#[test]
fn test_neural_rl_agent_record_reward_updates_mean() {
let mut agent = NeuralRLAgent::new(
AdvancedConfig {
learning_rate: 0.0, ..AdvancedConfig::default()
},
ExplorationStrategy::default(),
);
agent.record_reward(0, 0.0);
agent.record_reward(0, 1.0);
let (count, mean) = agent.arm_stats(0).expect("arm 0 should have stats");
assert_eq!(count, 2);
assert!(
(mean - 0.5).abs() < 1e-9,
"running mean of [0.0, 1.0] should be 0.5, got {mean}"
);
}
#[test]
fn test_advanced_processor_execute_adaptive_learns_the_faster_candidate() {
let mut processor = create_adaptive_advanced_processor();
let graph = small_graph();
fn fast_op(g: &Graph<i32, f64>) -> Result<usize> {
Ok(g.node_count())
}
fn slow_op(g: &Graph<i32, f64>) -> Result<usize> {
std::thread::sleep(Duration::from_millis(2));
Ok(g.node_count())
}
let candidates: [CandidateOp<i32, f64, u32, usize>; 2] = [slow_op, fast_op];
processor.rl_agent_mut().strategy = ExplorationStrategy::EpsilonGreedy { epsilon: 0.0 };
for _ in 0..8 {
processor
.execute_adaptive(&graph, &candidates)
.expect("execute_adaptive failed");
}
let (_, slow_mean) = processor
.rl_agent()
.arm_stats(0)
.expect("slow arm should have stats");
let (_, fast_mean) = processor
.rl_agent()
.arm_stats(1)
.expect("fast arm should have stats");
assert!(
fast_mean > slow_mean,
"the genuinely faster candidate should have accumulated a higher reward \
(fast={fast_mean}, slow={slow_mean})"
);
}
#[test]
fn test_execute_adaptive_rejects_empty_candidates() {
let mut processor = create_advanced_processor();
let graph = small_graph();
let candidates: [CandidateOp<i32, f64, u32, usize>; 0] = [];
assert!(processor.execute_adaptive(&graph, &candidates).is_err());
}
}