Custom Policies¶
Implement your own decision-making policies.
Overview¶
Policies define how decisions are made in the simulation. You can create custom policies for:
- Task allocation
- Station assignment
- Routing
- Charging
Policy Traits¶
TaskAllocationPolicy¶
Decides which robot handles a task.
pub trait TaskAllocationPolicy: Send + Sync {
/// Allocate a task to a robot
fn allocate(
&self,
task: &Task,
robots: &[Robot],
map: &WarehouseMap,
) -> Option<RobotId>;
/// Name for logging/metrics
fn name(&self) -> &str;
}
StationAssignmentPolicy¶
Decides which station serves a task.
pub trait StationAssignmentPolicy: Send + Sync {
/// Assign a station for a task
fn assign(
&self,
task: &Task,
stations: &[Station],
robot_position: NodeId,
map: &WarehouseMap,
) -> Option<StationId>;
fn name(&self) -> &str;
}
RoutingPolicy¶
Decides which path a robot takes.
pub trait RoutingPolicy: Send + Sync {
/// Find path from source to destination
fn find_path(
&self,
from: NodeId,
to: NodeId,
map: &WarehouseMap,
traffic: &TrafficState,
) -> Option<Path>;
fn name(&self) -> &str;
}
Example: Custom Task Allocation¶
Step 1: Define the Policy¶
// crates/waremax-policies/src/custom.rs
use waremax_entities::{Robot, Task, RobotId};
use waremax_map::WarehouseMap;
use crate::TaskAllocationPolicy;
/// Allocate to robot with highest battery
pub struct HighestBatteryPolicy;
impl TaskAllocationPolicy for HighestBatteryPolicy {
fn allocate(
&self,
task: &Task,
robots: &[Robot],
_map: &WarehouseMap,
) -> Option<RobotId> {
robots
.iter()
.filter(|r| r.is_idle())
.max_by(|a, b| {
a.battery_pct()
.partial_cmp(&b.battery_pct())
.unwrap()
})
.map(|r| r.id())
}
fn name(&self) -> &str {
"highest_battery"
}
}
Step 2: Register the Policy¶
// crates/waremax-policies/src/registry.rs
pub fn create_task_allocation_policy(
name: &str,
config: &PolicyConfig,
) -> Box<dyn TaskAllocationPolicy> {
match name {
"nearest_idle" => Box::new(NearestIdlePolicy::new(config)),
"least_busy" => Box::new(LeastBusyPolicy::new(config)),
"highest_battery" => Box::new(HighestBatteryPolicy),
_ => panic!("Unknown policy: {}", name),
}
}
Step 3: Add Configuration Support¶
// crates/waremax-config/src/policies.rs
#[derive(Deserialize)]
#[serde(tag = "type")]
pub enum TaskAllocationConfig {
NearestIdle,
LeastBusy,
RoundRobin,
HighestBattery, // Add new variant
}
Example: Custom Routing¶
Step 1: Define the Policy¶
// Custom routing that avoids certain node types
pub struct AvoidChargingRoutingPolicy {
fallback: ShortestPathPolicy,
}
impl RoutingPolicy for AvoidChargingRoutingPolicy {
fn find_path(
&self,
from: NodeId,
to: NodeId,
map: &WarehouseMap,
traffic: &TrafficState,
) -> Option<Path> {
// Try to find path avoiding charging nodes
if let Some(path) = self.find_avoiding_path(from, to, map) {
return Some(path);
}
// Fallback to standard routing
self.fallback.find_path(from, to, map, traffic)
}
fn name(&self) -> &str {
"avoid_charging"
}
}
impl AvoidChargingRoutingPolicy {
fn find_avoiding_path(
&self,
from: NodeId,
to: NodeId,
map: &WarehouseMap,
) -> Option<Path> {
// Custom pathfinding logic
let graph = map.graph();
// Add high cost to charging nodes
let edge_cost = |edge: EdgeId| {
let target = graph.edge_target(edge);
if map.node(target).is_charging() {
1000.0 // High cost to avoid
} else {
map.edge_length(edge)
}
};
dijkstra_with_cost(graph, from, to, edge_cost)
}
}
Testing Policies¶
Unit Tests¶
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_highest_battery_allocation() {
let policy = HighestBatteryPolicy;
let robots = vec![
Robot::new(RobotId(1), battery_pct: 50.0),
Robot::new(RobotId(2), battery_pct: 80.0),
Robot::new(RobotId(3), battery_pct: 30.0),
];
let task = Task::new(TaskId(1), /* ... */);
let map = WarehouseMap::simple_grid(3, 3);
let result = policy.allocate(&task, &robots, &map);
assert_eq!(result, Some(RobotId(2))); // Highest battery
}
}
Integration Tests¶
#[test]
fn test_custom_policy_in_simulation() {
let scenario = ScenarioBuilder::new()
.robots(5)
.policy(TaskAllocationConfig::HighestBattery)
.build();
let result = run_simulation(scenario);
assert!(result.tasks_completed > 0);
}
Policy Configuration¶
Adding Parameters¶
pub struct WeightedAllocationPolicy {
distance_weight: f64,
battery_weight: f64,
queue_weight: f64,
}
impl WeightedAllocationPolicy {
pub fn from_config(config: &WeightedConfig) -> Self {
Self {
distance_weight: config.distance_weight.unwrap_or(1.0),
battery_weight: config.battery_weight.unwrap_or(0.5),
queue_weight: config.queue_weight.unwrap_or(0.3),
}
}
}
YAML Configuration¶
policies:
task_allocation:
type: weighted
distance_weight: 1.0
battery_weight: 0.8
queue_weight: 0.5
Best Practices¶
Performance¶
- Keep
allocate()andfind_path()fast - Avoid expensive computations per call
- Cache when appropriate
Determinism¶
- Same inputs must produce same outputs
- Use provided RNG if randomness needed
- No external state dependencies
Error Handling¶
- Return
Nonerather than panic - Log warnings for unusual situations
- Handle edge cases gracefully