1use crate::error::{MLError, Result};
8use crate::qnn::QuantumNeuralNetwork;
9use scirs2_core::ndarray::{Array1, Array2};
10use scirs2_core::random::prelude::*;
11use std::collections::HashMap;
12
13pub trait Environment {
15 fn state(&self) -> Array1<f64>;
17
18 fn num_actions(&self) -> usize;
20
21 fn step(&mut self, action: usize) -> Result<(Array1<f64>, f64, bool)>;
23
24 fn reset(&mut self) -> Array1<f64>;
26}
27
28pub trait QuantumAgent {
30 fn get_action(&self, state: &Array1<f64>) -> Result<usize>;
32
33 fn update(
35 &mut self,
36 state: &Array1<f64>,
37 action: usize,
38 reward: f64,
39 next_state: &Array1<f64>,
40 done: bool,
41 ) -> Result<()>;
42
43 fn train(&mut self, env: &mut dyn Environment, episodes: usize) -> Result<f64>;
45
46 fn evaluate(&self, env: &mut dyn Environment, episodes: usize) -> Result<f64>;
48}
49
50#[derive(Debug, Clone, Copy)]
52pub enum ReinforcementLearningType {
53 QLearning,
55
56 SARSA,
58
59 DQN,
61
62 PolicyGradient,
64
65 QAOA,
67}
68
69#[derive(Debug, Clone)]
71pub struct ReinforcementLearning {
72 rl_type: ReinforcementLearningType,
74
75 qnn: QuantumNeuralNetwork,
77
78 learning_rate: f64,
80
81 discount_factor: f64,
83
84 exploration_rate: f64,
86
87 state_dim: usize,
89
90 action_dim: usize,
92}
93
94impl ReinforcementLearning {
95 pub fn new() -> Result<Self> {
100 let layers = vec![
104 crate::qnn::QNNLayerType::EncodingLayer { num_features: 4 },
105 crate::qnn::QNNLayerType::VariationalLayer { num_params: 16 },
106 crate::qnn::QNNLayerType::EntanglementLayer {
107 connectivity: "full".to_string(),
108 },
109 crate::qnn::QNNLayerType::VariationalLayer { num_params: 16 },
110 crate::qnn::QNNLayerType::MeasurementLayer {
111 measurement_basis: "computational".to_string(),
112 },
113 ];
114
115 let qnn = QuantumNeuralNetwork::new(
116 layers, 8, 4, 2, )?;
120
121 Ok(ReinforcementLearning {
122 rl_type: ReinforcementLearningType::QLearning,
123 qnn,
124 learning_rate: 0.01,
125 discount_factor: 0.95,
126 exploration_rate: 0.1,
127 state_dim: 4,
128 action_dim: 2,
129 })
130 }
131
132 pub fn with_algorithm(mut self, rl_type: ReinforcementLearningType) -> Self {
134 self.rl_type = rl_type;
135 self
136 }
137
138 pub fn with_state_dimension(mut self, state_dim: usize) -> Self {
140 self.state_dim = state_dim;
141 self
142 }
143
144 pub fn with_action_dimension(mut self, action_dim: usize) -> Self {
146 self.action_dim = action_dim;
147 self
148 }
149
150 pub fn with_learning_rate(mut self, learning_rate: f64) -> Self {
152 self.learning_rate = learning_rate;
153 self
154 }
155
156 pub fn with_discount_factor(mut self, discount_factor: f64) -> Self {
158 self.discount_factor = discount_factor;
159 self
160 }
161
162 pub fn with_exploration_rate(mut self, exploration_rate: f64) -> Self {
164 self.exploration_rate = exploration_rate;
165 self
166 }
167
168 fn get_q_values(&self, state: &Array1<f64>) -> Result<Array1<f64>> {
174 self.qnn.forward(state)
175 }
176}
177
178impl QuantumAgent for ReinforcementLearning {
179 fn get_action(&self, state: &Array1<f64>) -> Result<usize> {
180 if thread_rng().random::<f64>() < self.exploration_rate {
182 Ok(fastrand::usize(0..self.action_dim))
184 } else {
185 let q_values = self.get_q_values(state)?;
187 if q_values.is_empty() {
188 return Err(MLError::MLOperationError(
189 "QNN produced no Q-values".to_string(),
190 ));
191 }
192 let mut best_action = 0;
193 for i in 1..q_values.len() {
194 if q_values[i] > q_values[best_action] {
195 best_action = i;
196 }
197 }
198
199 Ok(best_action)
200 }
201 }
202
203 fn update(
204 &mut self,
205 state: &Array1<f64>,
206 action: usize,
207 reward: f64,
208 next_state: &Array1<f64>,
209 done: bool,
210 ) -> Result<()> {
211 let q_values = self.get_q_values(state)?;
213 if action >= q_values.len() {
214 return Err(MLError::InvalidParameter(format!(
215 "action {action} out of range for {} Q-values",
216 q_values.len()
217 )));
218 }
219 let q_sa = q_values[action];
220
221 let target = if done {
223 reward
224 } else {
225 let next_q = self.get_q_values(next_state)?;
226 let max_next = next_q.iter().cloned().fold(f64::NEG_INFINITY, f64::max);
227 reward + self.discount_factor * max_next
228 };
229
230 let td_error = q_sa - target;
233 let gradient = self.qnn.output_component_gradient(state, action)?;
234 for j in 0..self.qnn.parameters.len() {
235 self.qnn.parameters[j] -= self.learning_rate * 2.0 * td_error * gradient[j];
236 }
237
238 Ok(())
239 }
240
241 fn train(&mut self, env: &mut dyn Environment, episodes: usize) -> Result<f64> {
242 let mut total_reward = 0.0;
243
244 for _ in 0..episodes {
245 let mut state = env.reset();
246 let mut episode_reward = 0.0;
247 let mut done = false;
248
249 while !done {
250 let action = self.get_action(&state)?;
251 let (next_state, reward, is_done) = env.step(action)?;
252
253 self.update(&state, action, reward, &next_state, is_done)?;
254
255 state = next_state;
256 episode_reward += reward;
257 done = is_done;
258 }
259
260 total_reward += episode_reward;
261 }
262
263 Ok(total_reward / episodes as f64)
264 }
265
266 fn evaluate(&self, env: &mut dyn Environment, episodes: usize) -> Result<f64> {
267 let mut total_reward = 0.0;
268
269 for _ in 0..episodes {
270 let mut state = env.reset();
271 let mut episode_reward = 0.0;
272 let mut done = false;
273
274 while !done {
275 let action = self.get_action(&state)?;
276 let (next_state, reward, is_done) = env.step(action)?;
277
278 state = next_state;
279 episode_reward += reward;
280 done = is_done;
281 }
282
283 total_reward += episode_reward;
284 }
285
286 Ok(total_reward / episodes as f64)
287 }
288}
289
290pub struct GridWorldEnvironment {
292 width: usize,
294
295 height: usize,
297
298 position: (usize, usize),
300
301 goal: (usize, usize),
303
304 obstacles: Vec<(usize, usize)>,
306}
307
308impl GridWorldEnvironment {
309 pub fn new(width: usize, height: usize) -> Self {
311 GridWorldEnvironment {
312 width,
313 height,
314 position: (0, 0),
315 goal: (width - 1, height - 1),
316 obstacles: Vec::new(),
317 }
318 }
319
320 pub fn with_goal(mut self, x: usize, y: usize) -> Self {
322 self.goal = (x.min(self.width - 1), y.min(self.height - 1));
323 self
324 }
325
326 pub fn with_obstacles(mut self, obstacles: Vec<(usize, usize)>) -> Self {
328 self.obstacles = obstacles;
329 self
330 }
331
332 pub fn is_obstacle(&self, x: usize, y: usize) -> bool {
334 self.obstacles.contains(&(x, y))
335 }
336
337 pub fn is_goal(&self, x: usize, y: usize) -> bool {
339 (x, y) == self.goal
340 }
341}
342
343impl Environment for GridWorldEnvironment {
344 fn state(&self) -> Array1<f64> {
345 let mut state = Array1::zeros(4);
346
347 state[0] = self.position.0 as f64 / self.width as f64;
349 state[1] = self.position.1 as f64 / self.height as f64;
350
351 state[2] = self.goal.0 as f64 / self.width as f64;
353 state[3] = self.goal.1 as f64 / self.height as f64;
354
355 state
356 }
357
358 fn num_actions(&self) -> usize {
359 4 }
361
362 fn step(&mut self, action: usize) -> Result<(Array1<f64>, f64, bool)> {
363 let (x, y) = self.position;
365 let (new_x, new_y) = match action {
366 0 => (x, y.saturating_sub(1)), 1 => (x + 1, y), 2 => (x, y + 1), 3 => (x.saturating_sub(1), y), _ => {
371 return Err(MLError::InvalidParameter(format!(
372 "Invalid action: {}",
373 action
374 )))
375 }
376 };
377
378 let new_x = new_x.min(self.width - 1);
380 let new_y = new_y.min(self.height - 1);
381
382 if self.obstacles.contains(&(new_x, new_y)) {
384 let reward = -1.0;
386 let done = false;
387 return Ok((self.state(), reward, done));
388 }
389
390 self.position = (new_x, new_y);
392
393 let reward = if (new_x, new_y) == self.goal {
395 10.0 } else {
397 -0.1 };
399
400 let done = (new_x, new_y) == self.goal;
402
403 Ok((self.state(), reward, done))
404 }
405
406 fn reset(&mut self) -> Array1<f64> {
407 self.position = (0, 0);
408 self.state()
409 }
410}