use crate::magnon::SpinChain;
use crate::vector3::Vector3;
#[derive(Debug, Clone)]
pub struct MagnonReservoir {
pub system: SpinChain,
pub input_nodes: Vec<usize>,
pub readout_nodes: Vec<usize>,
pub readout_weights: Vec<f64>,
pub bias: f64,
pub input_scale: f64,
}
impl MagnonReservoir {
pub fn new(system: SpinChain, input_nodes: Vec<usize>, readout_nodes: Vec<usize>) -> Self {
let n_readout = readout_nodes.len() * 3;
Self {
system,
input_nodes,
readout_nodes,
readout_weights: vec![0.0; n_readout],
bias: 0.0,
input_scale: 1.0e4, }
}
pub fn uniform_distribution(
n_spins: usize,
n_input: usize,
n_readout: usize,
exchange: f64,
alpha: f64,
) -> Self {
use crate::magnon::chain::ChainParameters;
let params = ChainParameters {
a_ex: exchange,
alpha,
..ChainParameters::default()
};
let system = SpinChain::new(n_spins, params);
let input_nodes: Vec<usize> = (0..n_input).map(|i| i * n_spins / (n_input + 1)).collect();
let readout_nodes: Vec<usize> = (0..n_readout)
.map(|i| i * n_spins / (n_readout + 1))
.collect();
Self::new(system, input_nodes, readout_nodes)
}
fn inject_input(&self, input: &[f64]) -> Vector3<f64> {
if input.is_empty() {
return Vector3::new(0.0, 0.0, 0.0);
}
let total_input: f64 = input.iter().sum();
Vector3::new(0.0, 0.0, total_input * self.input_scale)
}
pub fn extract_state(&self) -> Vec<f64> {
let mut state = Vec::with_capacity(self.readout_nodes.len() * 3);
for &idx in &self.readout_nodes {
let m = self.system.spins[idx];
state.push(m.x);
state.push(m.y);
state.push(m.z);
}
state
}
fn apply_readout(&self, state: &[f64]) -> f64 {
let mut output = self.bias;
for (i, &s) in state.iter().enumerate() {
if i < self.readout_weights.len() {
output += self.readout_weights[i] * s;
}
}
output
}
pub fn process(
&mut self,
input_signal: &[f64],
h_ext: Vector3<f64>,
steps: usize,
dt: f64,
) -> f64 {
let input_field = self.inject_input(input_signal);
let total_field = h_ext + input_field;
for _ in 0..steps {
self.system.evolve_heun(total_field, dt);
}
let state = self.extract_state();
self.apply_readout(&state)
}
#[allow(clippy::needless_range_loop)]
pub fn train(
&mut self,
inputs: &[Vec<f64>],
targets: &[f64],
h_ext: Vector3<f64>,
steps: usize,
dt: f64,
) -> Result<f64, &'static str> {
if inputs.len() != targets.len() {
return Err("Input and target lengths must match");
}
if inputs.is_empty() {
return Err("No training data provided");
}
let n_samples = inputs.len();
let state_dim = self.readout_nodes.len() * 3;
let mut state_matrix = vec![vec![0.0; state_dim + 1]; n_samples];
for (i, input) in inputs.iter().enumerate() {
self.system.reset_to_z();
let input_field = self.inject_input(input);
let total_field = h_ext + input_field;
for _ in 0..steps {
self.system.evolve_heun(total_field, dt);
}
let state = self.extract_state();
for (j, &s) in state.iter().enumerate() {
state_matrix[i][j] = s;
}
state_matrix[i][state_dim] = 1.0; }
let learning_rate = 0.01;
let epochs = 1000;
self.readout_weights = vec![0.0; state_dim];
self.bias = 0.0;
for _ in 0..epochs {
let mut weight_grads = vec![0.0; state_dim];
let mut bias_grad = 0.0;
for (i, state) in state_matrix.iter().enumerate() {
let mut prediction = self.bias;
for j in 0..state_dim {
prediction += self.readout_weights[j] * state[j];
}
let error = prediction - targets[i];
for j in 0..state_dim {
weight_grads[j] += error * state[j];
}
bias_grad += error;
}
for j in 0..state_dim {
self.readout_weights[j] -= learning_rate * weight_grads[j] / n_samples as f64;
}
self.bias -= learning_rate * bias_grad / n_samples as f64;
}
let mut mse = 0.0;
for (i, state) in state_matrix.iter().enumerate() {
let mut prediction = self.bias;
for j in 0..state_dim {
prediction += self.readout_weights[j] * state[j];
}
let error = prediction - targets[i];
mse += error * error;
}
mse /= n_samples as f64;
Ok(mse)
}
pub fn reset(&mut self) {
self.system.reset_to_z();
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_reservoir_creation() {
let reservoir = MagnonReservoir::uniform_distribution(
20, 3, 5, 1.0e-20, 0.01,
);
assert_eq!(reservoir.input_nodes.len(), 3);
assert_eq!(reservoir.readout_nodes.len(), 5);
assert_eq!(reservoir.readout_weights.len(), 15); }
#[test]
fn test_state_extraction() {
let reservoir = MagnonReservoir::uniform_distribution(10, 2, 3, 1.0e-20, 0.01);
let state = reservoir.extract_state();
assert_eq!(state.len(), 9); }
#[test]
fn test_input_processing() {
let mut reservoir = MagnonReservoir::uniform_distribution(10, 2, 3, 1.0e-20, 0.01);
let input = vec![0.1, 0.2];
let h_ext = Vector3::new(0.0, 0.0, 1.0e5);
let output = reservoir.process(&input, h_ext, 10, 1.0e-13);
assert!(output.is_finite());
}
#[test]
fn test_training_simple_constant() {
let mut reservoir = MagnonReservoir::uniform_distribution(15, 2, 4, 1.0e-20, 0.01);
let inputs = vec![
vec![0.1, 0.2],
vec![0.3, 0.1],
vec![0.0, 0.5],
vec![0.2, 0.2],
];
let targets = vec![1.0, 1.0, 1.0, 1.0];
let h_ext = Vector3::new(0.0, 0.0, 1.0e5);
let result = reservoir.train(&inputs, &targets, h_ext, 5, 1.0e-13);
assert!(result.is_ok());
let mse = result.expect("reservoir training should succeed");
assert!(mse >= 0.0);
}
#[test]
fn test_reset_functionality() {
let mut reservoir = MagnonReservoir::uniform_distribution(10, 2, 3, 1.0e-20, 0.01);
let h_ext = Vector3::new(1.0e5, 0.0, 0.0);
for _ in 0..50 {
reservoir.system.evolve_heun(h_ext, 1.0e-13);
}
reservoir.reset();
for spin in &reservoir.system.spins {
assert!((spin.x).abs() < 1e-10);
assert!((spin.y).abs() < 1e-10);
assert!((spin.z - 1.0).abs() < 1e-10);
}
}
}