use crate::{types::{Action, QValue}};
use candle_core::{Tensor, Result};
use rand::{Rng, rng};
use candle_nn::ops;
#[deprecated(
since = "0.0.3",
note = "The enum PolicyConfig is deprecated, please directly instantiated the policy."
)]
#[derive(Debug, Default, Clone)]
pub enum PolicyConfig {
EpsilonGreedy {
epsilon_start: f32,
epsilon_min: f32,
epsilon_decay: f32,
},
Boltzmann {
temperature_start: f32,
temperature_min: f32,
temperature_decay: f32,
},
OrnsteinUhlenbeck {
mu: f32,
theta: f32,
sigma: f32,
action_dim: usize,
},
GaussianNoise {
mean: f32,
std_dev: f32,
decay_rate: f32,
},
#[default]
DeterministicPolicy,
}
impl PolicyConfig {
pub const fn dqn_epsilon_greedy() -> Self {
Self::EpsilonGreedy {
epsilon_start: 1.0,
epsilon_min: 0.01,
epsilon_decay: 0.995,
}
}
pub const fn default_boltzmann() -> Self {
Self::Boltzmann {
temperature_start: 1.0,
temperature_min: 0.1,
temperature_decay: 0.99,
}
}
pub const fn ddpg_ornstein_uhlenbeck(action_dim: usize) -> Self {
Self::OrnsteinUhlenbeck {
mu: 0.0,
theta: 0.15,
sigma: 0.2,
action_dim,
}
}
pub const fn default_gaussian_noise() -> Self {
Self::GaussianNoise {
mean: 0.0,
std_dev: 0.2,
decay_rate: 0.99,
}
}
}
impl PolicyConfig {
pub fn create_policy<T>(&self, action_dim: usize) -> Result<Box<dyn Policy<T>>>
where
T: Copy + From<f32> + std::ops::Add<Output = T>
+ rand::distr::uniform::SampleUniform + Default + std::cmp::PartialOrd + std::fmt::Display,
{
match self {
Self::EpsilonGreedy { epsilon_start, epsilon_min, epsilon_decay } => {
Ok(Box::new(EpsilonGreedy::new(*epsilon_start, *epsilon_min, *epsilon_decay)))
}
Self::Boltzmann { temperature_start, temperature_min, temperature_decay } => {
Ok(Box::new(Boltzmann::new(*temperature_start, *temperature_min, *temperature_decay)))
}
Self::OrnsteinUhlenbeck { mu, theta, sigma, action_dim: _ } => {
Ok(Box::new(OrnsteinUhlenbeck::new(*mu, *theta, *sigma, action_dim)))
}
Self::GaussianNoise { mean, std_dev, decay_rate } => {
Ok(Box::new(GaussianNoise::new(*mean, *std_dev, *decay_rate)))
}
Self::DeterministicPolicy => {
Ok(Box::new(DeterministicPolicy))
}
}
}
}
pub trait Policy<T = u16> {
fn select_action(&mut self, q_value: &QValue<T>) -> Result<Action<T>>;
fn update(&mut self);
fn get_params(&self) -> String;
}
pub struct EpsilonGreedy {
pub epsilon: f32,
pub epsilon_min: f32,
pub epsilon_decay: f32,
}
impl EpsilonGreedy {
pub fn new(epsilon_start: f32, epsilon_min: f32, epsilon_decay: f32) -> Self {
Self {
epsilon: epsilon_start,
epsilon_min,
epsilon_decay,
}
}
}
impl<T> Policy<T> for EpsilonGreedy
where
T: Copy + rand::distr::uniform::SampleUniform + Default + std::cmp::PartialOrd,
{
fn select_action(&mut self, q_values: &QValue<T>) -> Result<Action<T>> {
let mut rng = rng();
match q_values {
QValue::Deterministic(action) => {
if rng.random::<f32>() < self.epsilon {
Ok(action.random(&mut rng))
} else {
Ok(action.clone())
}
},
QValue::Stochastic(actions_with_values) => {
let best_action = q_values.best_action().clone();
if rng.random::<f32>() < self.epsilon {
let random_idx = rng.random_range(0..actions_with_values.len());
Ok(actions_with_values[random_idx].0.clone())
} else {
Ok(best_action.clone())
}
}
}
}
fn update(&mut self) {
if self.epsilon > self.epsilon_min {
self.epsilon *= self.epsilon_decay;
}
}
fn get_params(&self) -> String {
format!("ε={:.4}", self.epsilon)
}
}
pub struct Boltzmann {
pub temperature: f32,
pub temperature_min: f32,
pub temperature_decay: f32,
}
impl Boltzmann {
pub fn new(temperature_start: f32, temperature_min: f32, temperature_decay: f32) -> Self {
Self {
temperature: temperature_start,
temperature_min,
temperature_decay,
}
}
}
impl<T> Policy<T> for Boltzmann
where
T: Copy,
{
fn select_action(&mut self, q_values: &QValue<T>) -> Result<Action<T>> {
match q_values {
QValue::Deterministic(action) => {
Ok(action.clone())
},
QValue::Stochastic(actions_with_values) => {
let mut rng = rng();
let values: Vec<f32> = actions_with_values.iter()
.map(|(_, q_val)| *q_val)
.collect();
let values_tensor = Tensor::new(values.as_slice(), &candle_core::Device::Cpu)?;
let temperature_tensor = Tensor::new(self.temperature, &candle_core::Device::Cpu)?;
let scaled_values = values_tensor.div(&temperature_tensor)?;
let probabilities = ops::softmax(&scaled_values, 0)?;
let probabilities_vec = probabilities.to_vec1::<f32>()?;
let sample = rng.random::<f32>();
let mut cumulative = 0.0;
for (i, &prob) in probabilities_vec.iter().enumerate() {
cumulative += prob;
if sample < cumulative {
return Ok(actions_with_values[i].0.clone());
}
}
Ok(actions_with_values.last().unwrap().0.clone())
}
}
}
fn update(&mut self) {
if self.temperature > self.temperature_min {
self.temperature *= self.temperature_decay;
}
}
fn get_params(&self) -> String {
format!("T={:.4}", self.temperature)
}
}
pub struct OrnsteinUhlenbeck {
pub mu: f32,
pub theta: f32,
pub sigma: f32,
pub action_dim: usize,
pub state: Option<Vec<f32>>,
}
impl OrnsteinUhlenbeck {
pub fn new(mu: f32, theta: f32, sigma: f32, action_dim: usize) -> Self {
Self {
mu,
theta,
sigma,
action_dim,
state: None,
}
}
fn sample(&mut self) -> Vec<f32> {
let mut rng = rng();
match &mut self.state {
Some(state) => {
for i in 0..self.action_dim {
let dx = self.theta * (self.mu - state[i]) + self.sigma * rng.random_range(-1.0..1.0);
state[i] += dx;
}
state.clone()
},
None => {
let state = vec![self.mu; self.action_dim];
self.state = Some(state.clone());
state
}
}
}
}
impl<T> Policy<T> for OrnsteinUhlenbeck
where
T: Copy + From<f32> + std::ops::Add<Output = T>,
{
fn select_action(&mut self, q_values: &QValue<T>) -> Result<Action<T>> {
match q_values {
QValue::Deterministic(action) => {
let mut action_data = action.value.clone();
let noise = self.sample();
for i in 0..action_data.len() {
action_data[i] = action_data[i] + T::from(noise[i]);
}
Ok(Action::new(action_data, action.uppers.clone()))
},
QValue::Stochastic(_actions_with_values) => {
let best_action = q_values.best_action();
let mut action_data = best_action.value.clone();
let noise = self.sample();
for i in 0..action_data.len() {
action_data[i] = action_data[i] + T::from(noise[i]);
}
Ok(Action::new(action_data, best_action.uppers.clone()))
}
}
}
fn update(&mut self) {
}
fn get_params(&self) -> String {
format!("μ={:.4}, θ={:.4}, σ={:.4}", self.mu, self.theta, self.sigma)
}
}
pub struct GaussianNoise {
pub mean: f32,
pub std_dev: f32,
pub decay_rate: f32,
}
impl GaussianNoise {
pub fn new(mean: f32, std_dev: f32, decay_rate: f32) -> Self {
Self {
mean,
std_dev,
decay_rate,
}
}
fn sample(&self, size: usize) -> Vec<f32> {
let mut rng = rng();
(0..size).map(|_| rng.random_range(-1.0..1.0) * self.std_dev + self.mean).collect()
}
}
impl<T> Policy<T> for GaussianNoise
where
T: Copy + From<f32> + std::ops::Add<Output = T> + std::fmt::Display,
{
fn select_action(&mut self, q_values: &QValue<T>) -> Result<Action<T>> {
match q_values {
QValue::Deterministic(action) => {
let mut action_data = action.value.clone();
let noise = self.sample(action_data.len());
for i in 0..action_data.len() {
action_data[i] = action_data[i] + T::from(noise[i]);
}
Ok(Action::new(action_data, action.uppers.clone()))
},
QValue::Stochastic(_actions_with_values) => {
let best_action = q_values.best_action();
let mut action_data = best_action.value.clone();
let noise = self.sample(action_data.len());
for i in 0..action_data.len() {
action_data[i] = action_data[i] + T::from(noise[i]);
}
Ok(Action::new(action_data, best_action.uppers.clone()))
}
}
}
fn update(&mut self) {
self.std_dev = self.std_dev * self.decay_rate;
}
fn get_params(&self) -> String {
format!("μ={:.4}, σ={:.4}", self.mean, self.std_dev)
}
}
pub struct DeterministicPolicy;
impl DeterministicPolicy {
pub fn new() -> Self {
Self
}
}
impl<T> Policy<T> for DeterministicPolicy
where
T: Copy,
{
fn select_action(&mut self, q_values: &QValue<T>) -> Result<Action<T>> {
match q_values {
QValue::Deterministic(action) => {
Ok(action.clone())
},
QValue::Stochastic(_actions_with_values) => {
Ok(q_values.best_action().clone())
}
}
}
fn update(&mut self) {
}
fn get_params(&self) -> String {
"Deterministic".to_string()
}
}