use crate::{ActivationFunction, Connection};
use num_traits::Float;
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct Neuron<T: Float> {
pub sum: T,
pub value: T,
pub activation_steepness: T,
pub activation_function: ActivationFunction,
pub connections: Vec<Connection<T>>,
pub is_bias: bool,
}
impl<T: Float> Neuron<T> {
pub fn new(activation_function: ActivationFunction, activation_steepness: T) -> Self {
Neuron {
sum: T::zero(),
value: T::zero(),
activation_steepness,
activation_function,
connections: Vec::new(),
is_bias: false,
}
}
pub fn new_bias() -> Self {
let one = T::one();
Neuron {
sum: one,
value: one,
activation_steepness: one,
activation_function: ActivationFunction::Linear,
connections: Vec::new(),
is_bias: true,
}
}
pub fn add_connection(&mut self, from_neuron: usize, weight: T) {
let neuron_index = self.connections.len();
self.connections
.push(Connection::new(from_neuron, neuron_index, weight));
}
pub fn clear_connections(&mut self) {
self.connections.clear();
}
pub fn reset(&mut self) {
if self.is_bias {
self.sum = T::one();
self.value = T::one();
} else {
self.sum = T::zero();
self.value = T::zero();
}
}
pub fn calculate(&mut self, inputs: &[T]) {
if self.is_bias {
return;
}
self.sum = T::zero();
for connection in &self.connections {
if connection.from_neuron < inputs.len() {
self.sum = self.sum + inputs[connection.from_neuron] * connection.weight;
}
}
self.value = self.apply_activation_function(self.sum);
}
fn apply_activation_function(&self, x: T) -> T {
match self.activation_function {
ActivationFunction::Linear => x * self.activation_steepness,
ActivationFunction::Sigmoid => {
let exp_val = (-self.activation_steepness * x).exp();
T::one() / (T::one() + exp_val)
}
ActivationFunction::ReLU => {
if x > T::zero() {
x
} else {
T::zero()
}
}
ActivationFunction::ReLULeaky => {
let alpha = T::from(0.01).unwrap_or(T::zero());
if x > T::zero() {
x
} else {
alpha * x
}
}
ActivationFunction::Tanh => (self.activation_steepness * x).tanh(),
ActivationFunction::SigmoidSymmetric => (self.activation_steepness * x).tanh(),
ActivationFunction::Gaussian => {
let x_scaled = x * self.activation_steepness;
(-x_scaled * x_scaled).exp()
}
_ => x, }
}
pub fn activation_derivative(&self) -> T {
match self.activation_function {
ActivationFunction::Linear => self.activation_steepness,
ActivationFunction::Sigmoid => {
self.value * (T::one() - self.value) * self.activation_steepness
}
ActivationFunction::ReLU => {
if self.sum > T::zero() {
T::one()
} else {
T::zero()
}
}
ActivationFunction::ReLULeaky => {
let alpha = T::from(0.01).unwrap_or(T::zero());
if self.sum > T::zero() {
T::one()
} else {
alpha
}
}
ActivationFunction::Tanh | ActivationFunction::SigmoidSymmetric => {
(T::one() - self.value * self.value) * self.activation_steepness
}
ActivationFunction::Gaussian => {
let x_scaled = self.sum * self.activation_steepness;
let neg_two = T::from(-2.0).unwrap_or(T::zero());
neg_two * self.activation_steepness * x_scaled * self.value
}
_ => T::one(), }
}
pub fn set_value(&mut self, value: T) {
if !self.is_bias {
self.value = value;
self.sum = value;
}
}
pub fn get_connection_weight(&self, index: usize) -> Option<T> {
self.connections.get(index).map(|c| c.weight)
}
pub fn set_connection_weight(&mut self, index: usize, weight: T) -> Result<(), &'static str> {
if let Some(connection) = self.connections.get_mut(index) {
connection.set_weight(weight);
Ok(())
} else {
Err("Connection index out of bounds")
}
}
}
impl<T: Float> PartialEq for Neuron<T> {
fn eq(&self, other: &Self) -> bool {
self.activation_function == other.activation_function
&& self.activation_steepness == other.activation_steepness
&& self.is_bias == other.is_bias
&& self.connections == other.connections
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_neuron_creation() {
let neuron = Neuron::<f32>::new(ActivationFunction::Sigmoid, 1.0);
assert_eq!(neuron.activation_function, ActivationFunction::Sigmoid);
assert_eq!(neuron.activation_steepness, 1.0);
assert_eq!(neuron.sum, 0.0);
assert_eq!(neuron.value, 0.0);
assert!(!neuron.is_bias);
assert!(neuron.connections.is_empty());
}
#[test]
fn test_bias_neuron() {
let bias = Neuron::<f32>::new_bias();
assert!(bias.is_bias);
assert_eq!(bias.value, 1.0);
assert_eq!(bias.sum, 1.0);
}
#[test]
fn test_add_connection() {
let mut neuron = Neuron::<f32>::new(ActivationFunction::ReLU, 1.0);
neuron.add_connection(0, 0.5);
neuron.add_connection(1, -0.3);
assert_eq!(neuron.connections.len(), 2);
assert_eq!(neuron.connections[0].from_neuron, 0);
assert_eq!(neuron.connections[0].weight, 0.5);
assert_eq!(neuron.connections[1].from_neuron, 1);
assert_eq!(neuron.connections[1].weight, -0.3);
}
#[test]
fn test_reset_neuron() {
let mut neuron = Neuron::<f32>::new(ActivationFunction::Sigmoid, 1.0);
neuron.sum = 5.0;
neuron.value = 2.5;
neuron.reset();
assert_eq!(neuron.sum, 0.0);
assert_eq!(neuron.value, 0.0);
}
#[test]
fn test_reset_bias_neuron() {
let mut bias = Neuron::<f32>::new_bias();
bias.sum = 5.0;
bias.value = 2.5;
bias.reset();
assert_eq!(bias.sum, 1.0);
assert_eq!(bias.value, 1.0);
}
#[test]
fn test_set_value() {
let mut neuron = Neuron::<f32>::new(ActivationFunction::Linear, 1.0);
neuron.set_value(std::f32::consts::PI);
assert_eq!(neuron.value, std::f32::consts::PI);
assert_eq!(neuron.sum, std::f32::consts::PI);
}
#[test]
fn test_calculate() {
let mut neuron = Neuron::<f32>::new(ActivationFunction::Linear, 1.0);
neuron.add_connection(0, 0.5);
neuron.add_connection(1, -0.3);
neuron.add_connection(2, 0.2);
let inputs = vec![1.0, 2.0, -1.0];
neuron.calculate(&inputs);
assert_eq!(neuron.sum, -0.3);
}
}