#![allow(clippy::needless_range_loop, clippy::ptr_arg)]
use std::collections::HashMap;
use std::f64::consts::PI as PI_F64;
use super::functions::scaled_dot_product_attention;
#[allow(unused_imports)]
use super::functions::*;
#[derive(Debug, Clone)]
pub struct BatchNormLayer {
pub running_mean: Vec<f32>,
pub running_var: Vec<f32>,
pub gamma: Vec<f32>,
pub beta: Vec<f32>,
pub epsilon: f32,
pub n_features: usize,
}
impl BatchNormLayer {
pub fn new(n_features: usize) -> Self {
Self {
running_mean: vec![0.0; n_features],
running_var: vec![1.0; n_features],
gamma: vec![1.0; n_features],
beta: vec![0.0; n_features],
epsilon: 1e-5,
n_features,
}
}
pub fn forward(&self, input: &[f32]) -> Vec<f32> {
assert_eq!(input.len(), self.n_features);
let mut output = Vec::with_capacity(self.n_features);
for i in 0..self.n_features {
let normalized =
(input[i] - self.running_mean[i]) / (self.running_var[i] + self.epsilon).sqrt();
output.push(self.gamma[i] * normalized + self.beta[i]);
}
output
}
pub fn set_stats(&mut self, mean: &[f32], var: &[f32]) {
assert_eq!(mean.len(), self.n_features);
assert_eq!(var.len(), self.n_features);
self.running_mean.copy_from_slice(mean);
self.running_var.copy_from_slice(var);
}
pub fn set_affine(&mut self, gamma: &[f32], beta: &[f32]) {
assert_eq!(gamma.len(), self.n_features);
assert_eq!(beta.len(), self.n_features);
self.gamma.copy_from_slice(gamma);
self.beta.copy_from_slice(beta);
}
}
impl BatchNormLayer {
pub fn update_running_stats(&mut self, batch: &[Vec<f32>], momentum: f32) {
assert!(
!batch.is_empty(),
"update_running_stats: batch must not be empty"
);
let n = batch.len() as f32;
let mut batch_mean = vec![0.0_f32; self.n_features];
for sample in batch {
assert_eq!(sample.len(), self.n_features, "sample length mismatch");
for (k, &v) in sample.iter().enumerate() {
batch_mean[k] += v;
}
}
for m in &mut batch_mean {
*m /= n;
}
let mut batch_var = vec![0.0_f32; self.n_features];
for sample in batch {
for (k, &v) in sample.iter().enumerate() {
let d = v - batch_mean[k];
batch_var[k] += d * d;
}
}
for v in &mut batch_var {
*v /= n;
}
for k in 0..self.n_features {
self.running_mean[k] =
(1.0 - momentum) * self.running_mean[k] + momentum * batch_mean[k];
self.running_var[k] = (1.0 - momentum) * self.running_var[k] + momentum * batch_var[k];
}
}
}
#[allow(dead_code)]
#[derive(Debug, Clone)]
pub struct RnnCell {
pub w_x: Vec<f64>,
pub w_h: Vec<f64>,
pub b: Vec<f64>,
pub input_size: usize,
pub hidden_size: usize,
pub activation: ExtActivation,
}
impl RnnCell {
pub fn new(input_size: usize, hidden_size: usize, activation: ExtActivation) -> Self {
Self {
w_x: vec![0.0_f64; hidden_size * input_size],
w_h: vec![0.0_f64; hidden_size * hidden_size],
b: vec![0.0_f64; hidden_size],
input_size,
hidden_size,
activation,
}
}
pub fn step(&self, x: &[f64], h_prev: &[f64]) -> Vec<f64> {
assert_eq!(x.len(), self.input_size);
assert_eq!(h_prev.len(), self.hidden_size);
let mut h = Vec::with_capacity(self.hidden_size);
for o in 0..self.hidden_size {
let mut acc = self.b[o];
for i in 0..self.input_size {
acc += self.w_x[o * self.input_size + i] * x[i];
}
for i in 0..self.hidden_size {
acc += self.w_h[o * self.hidden_size + i] * h_prev[i];
}
h.push(self.activation.apply(acc));
}
h
}
pub fn forward_sequence(&self, sequence: &[Vec<f64>]) -> Vec<Vec<f64>> {
let mut h = vec![0.0_f64; self.hidden_size];
let mut hidden_states = Vec::with_capacity(sequence.len());
for x in sequence {
h = self.step(x, &h);
hidden_states.push(h.clone());
}
hidden_states
}
}
#[derive(Debug, Clone)]
pub struct InferencePipeline {
pub ops: Vec<InferenceOp>,
}
impl InferencePipeline {
pub fn new() -> Self {
Self { ops: Vec::new() }
}
pub fn add_op(&mut self, op: InferenceOp) {
self.ops.push(op);
}
pub fn forward(&self, input: &[f32]) -> Vec<f32> {
let mut current = input.to_vec();
for op in &self.ops {
current = match op {
InferenceOp::Dense(layer) => layer.forward(¤t),
InferenceOp::BatchNorm(bn) => bn.forward(¤t),
InferenceOp::Activation(act) => current.iter().map(|&x| act.apply(x)).collect(),
};
}
current
}
pub fn total_parameters(&self) -> usize {
self.ops
.iter()
.map(|op| match op {
InferenceOp::Dense(layer) => layer.parameter_count(),
InferenceOp::BatchNorm(bn) => 2 * bn.n_features,
InferenceOp::Activation(_) => 0,
})
.sum()
}
}
pub struct NetworkBuilder;
impl NetworkBuilder {
pub fn simple_aann(
n_descriptors: usize,
hidden_sizes: &[usize],
_element: u8,
) -> FeedForwardNet {
let mut net = FeedForwardNet::new();
let mut prev = n_descriptors;
for &h in hidden_sizes {
net.add_layer(DenseLayer::new(prev, h, ActivationFn::Tanh));
prev = h;
}
net.add_layer(DenseLayer::new(prev, 1, ActivationFn::Linear));
net
}
}
#[allow(dead_code)]
#[derive(Debug, Clone)]
pub struct TransformerFfn {
pub d_model: usize,
pub d_ff: usize,
pub w1: Vec<f64>,
pub b1: Vec<f64>,
pub w2: Vec<f64>,
pub b2: Vec<f64>,
}
impl TransformerFfn {
pub fn new(d_model: usize, d_ff: usize) -> Self {
Self {
d_model,
d_ff,
w1: vec![0.0_f64; d_ff * d_model],
b1: vec![0.0_f64; d_ff],
w2: vec![0.0_f64; d_model * d_ff],
b2: vec![0.0_f64; d_model],
}
}
pub fn forward(&self, x: &[f64], seq_len: usize) -> Vec<f64> {
let dm = self.d_model;
let df = self.d_ff;
let mut out = vec![0.0_f64; seq_len * dm];
for t in 0..seq_len {
let mut hidden = vec![0.0_f64; df];
for j in 0..df {
let mut acc = self.b1[j];
for i in 0..dm {
acc += x[t * dm + i] * self.w1[j * dm + i];
}
hidden[j] = acc.max(0.0);
}
for j in 0..dm {
let mut acc = self.b2[j];
for i in 0..df {
acc += hidden[i] * self.w2[j * df + i];
}
out[t * dm + j] = acc;
}
}
out
}
}
#[allow(dead_code)]
#[derive(Debug, Clone)]
pub struct Conv1DLayer {
pub in_channels: usize,
pub out_channels: usize,
pub kernel_size: usize,
pub weights: Vec<Vec<Vec<f64>>>,
pub biases: Vec<f64>,
pub activation: ExtActivation,
}
impl Conv1DLayer {
pub fn new(
in_channels: usize,
out_channels: usize,
kernel_size: usize,
activation: ExtActivation,
) -> Self {
let weights = vec![vec![vec![0.0_f64; in_channels]; kernel_size]; out_channels];
let biases = vec![0.0_f64; out_channels];
Self {
in_channels,
out_channels,
kernel_size,
weights,
biases,
activation,
}
}
pub fn forward(&self, input: &[Vec<f64>]) -> Vec<Vec<f64>> {
let seq_len = input.len();
let mut output = vec![vec![0.0_f64; self.out_channels]; seq_len];
for t in 0..seq_len {
for o in 0..self.out_channels {
let mut acc = self.biases[o];
for k in 0..self.kernel_size {
let src_t = t as isize - k as isize;
if src_t < 0 {
continue;
}
let src_t = src_t as usize;
for c in 0..self.in_channels {
acc += self.weights[o][k][c] * input[src_t][c];
}
}
output[t][o] = self.activation.apply(acc);
}
}
output
}
pub fn num_params(&self) -> usize {
self.out_channels * self.kernel_size * self.in_channels + self.out_channels
}
}
#[derive(Debug, Clone)]
pub enum InferenceOp {
Dense(DenseLayer),
BatchNorm(BatchNormLayer),
Activation(ActivationFn),
}
#[derive(Debug, Clone)]
pub struct AdamOptimizer {
pub lr: f64,
pub beta1: f64,
pub beta2: f64,
pub epsilon: f64,
pub m: Vec<f64>,
pub v: Vec<f64>,
pub step: u64,
}
impl AdamOptimizer {
pub fn new(n_params: usize, lr: f64, beta1: f64, beta2: f64, epsilon: f64) -> Self {
Self {
lr,
beta1,
beta2,
epsilon,
m: vec![0.0; n_params],
v: vec![0.0; n_params],
step: 0,
}
}
pub fn default_params(n_params: usize) -> Self {
Self::new(n_params, 1e-3, 0.9, 0.999, 1e-8)
}
pub fn step_update(&mut self, params: &mut [f64], grads: &[f64]) {
assert_eq!(
params.len(),
self.m.len(),
"AdamOptimizer::step_update: params/m length mismatch"
);
assert_eq!(
grads.len(),
self.m.len(),
"AdamOptimizer::step_update: grads/m length mismatch"
);
self.step += 1;
let t = self.step as f64;
let bias_corr1 = 1.0 - self.beta1.powf(t);
let bias_corr2 = 1.0 - self.beta2.powf(t);
for i in 0..params.len() {
self.m[i] = self.beta1 * self.m[i] + (1.0 - self.beta1) * grads[i];
self.v[i] = self.beta2 * self.v[i] + (1.0 - self.beta2) * grads[i] * grads[i];
let m_hat = self.m[i] / bias_corr1;
let v_hat = self.v[i] / bias_corr2;
params[i] -= self.lr * m_hat / (v_hat.sqrt() + self.epsilon);
}
}
pub fn reset(&mut self) {
self.m.iter_mut().for_each(|x| *x = 0.0);
self.v.iter_mut().for_each(|x| *x = 0.0);
self.step = 0;
}
}
#[allow(dead_code)]
#[derive(Debug, Clone)]
pub struct GnnLayer {
pub in_dim: usize,
pub out_dim: usize,
pub w_self: Vec<f64>,
pub w_neigh: Vec<f64>,
pub bias: Vec<f64>,
pub activation: ExtActivation,
}
impl GnnLayer {
pub fn new(in_dim: usize, out_dim: usize, activation: ExtActivation) -> Self {
Self {
in_dim,
out_dim,
w_self: vec![0.0_f64; out_dim * in_dim],
w_neigh: vec![0.0_f64; out_dim * in_dim],
bias: vec![0.0_f64; out_dim],
activation,
}
}
pub fn forward(&self, node_feats: &[f64], n_nodes: usize, adj: &[Vec<usize>]) -> Vec<f64> {
assert_eq!(node_feats.len(), n_nodes * self.in_dim);
assert_eq!(adj.len(), n_nodes);
let in_d = self.in_dim;
let out_d = self.out_dim;
let mut out = vec![0.0_f64; n_nodes * out_d];
for i in 0..n_nodes {
let h_self = &node_feats[i * in_d..(i + 1) * in_d];
let mut agg = vec![0.0_f64; in_d];
for &j in &adj[i] {
let h_j = &node_feats[j * in_d..(j + 1) * in_d];
for d in 0..in_d {
agg[d] += h_j[d];
}
}
for o in 0..out_d {
let mut acc = self.bias[o];
for d in 0..in_d {
acc += self.w_self[o * in_d + d] * h_self[d];
acc += self.w_neigh[o * in_d + d] * agg[d];
}
out[i * out_d + o] = self.activation.apply(acc);
}
}
out
}
pub fn num_params(&self) -> usize {
2 * self.out_dim * self.in_dim + self.out_dim
}
}
#[allow(dead_code)]
#[derive(Debug, Clone)]
pub struct MessagePassingNet {
pub layers: Vec<GnnLayer>,
}
impl MessagePassingNet {
pub fn new() -> Self {
Self { layers: Vec::new() }
}
pub fn add_layer(&mut self, layer: GnnLayer) {
self.layers.push(layer);
}
pub fn forward(&self, node_feats: &[f64], n_nodes: usize, adj: &[Vec<usize>]) -> Vec<f64> {
let mut h = node_feats.to_vec();
for layer in &self.layers {
h = layer.forward(&h, n_nodes, adj);
}
h
}
pub fn global_mean_pool(&self, node_feats: &[f64], n_nodes: usize, out_dim: usize) -> Vec<f64> {
if n_nodes == 0 {
return vec![0.0_f64; out_dim];
}
let mut pooled = vec![0.0_f64; out_dim];
for i in 0..n_nodes {
for d in 0..out_dim {
pooled[d] += node_feats[i * out_dim + d];
}
}
let inv_n = 1.0 / n_nodes as f64;
for v in &mut pooled {
*v *= inv_n;
}
pooled
}
}
#[derive(Debug, Clone)]
pub struct GradAccumulator {
pub grad_weights: Vec<f64>,
pub grad_biases: Vec<f64>,
pub count: usize,
}
impl GradAccumulator {
pub fn new(n_weights: usize, n_biases: usize) -> Self {
Self {
grad_weights: vec![0.0; n_weights],
grad_biases: vec![0.0; n_biases],
count: 0,
}
}
pub fn accumulate(&mut self, gw: &[f64], gb: &[f64]) {
assert_eq!(gw.len(), self.grad_weights.len());
assert_eq!(gb.len(), self.grad_biases.len());
for (acc, &g) in self.grad_weights.iter_mut().zip(gw.iter()) {
*acc += g;
}
for (acc, &g) in self.grad_biases.iter_mut().zip(gb.iter()) {
*acc += g;
}
self.count += 1;
}
pub fn mean_grads(&self) -> (Vec<f64>, Vec<f64>) {
let n = self.count.max(1) as f64;
let gw: Vec<f64> = self.grad_weights.iter().map(|&g| g / n).collect();
let gb: Vec<f64> = self.grad_biases.iter().map(|&g| g / n).collect();
(gw, gb)
}
pub fn zero(&mut self) {
self.grad_weights.iter_mut().for_each(|x| *x = 0.0);
self.grad_biases.iter_mut().for_each(|x| *x = 0.0);
self.count = 0;
}
}
#[allow(dead_code)]
#[derive(Debug, Clone)]
pub struct MultiHeadAttention {
pub d_model: usize,
pub n_heads: usize,
pub d_head: usize,
pub w_q: Vec<f64>,
pub w_k: Vec<f64>,
pub w_v: Vec<f64>,
pub w_o: Vec<f64>,
pub b_o: Vec<f64>,
}
impl MultiHeadAttention {
pub fn new(d_model: usize, n_heads: usize) -> Self {
assert_eq!(d_model % n_heads, 0, "d_model must be divisible by n_heads");
let d_head = d_model / n_heads;
let dm2 = d_model * d_model;
Self {
d_model,
n_heads,
d_head,
w_q: vec![0.0_f64; dm2],
w_k: vec![0.0_f64; dm2],
w_v: vec![0.0_f64; dm2],
w_o: vec![0.0_f64; dm2],
b_o: vec![0.0_f64; d_model],
}
}
pub fn init_identity(&mut self) {
let dm = self.d_model;
for row in 0..dm {
self.w_q[row * dm + row] = 1.0;
self.w_k[row * dm + row] = 1.0;
self.w_v[row * dm + row] = 1.0;
self.w_o[row * dm + row] = 1.0;
}
}
fn project(
input: &[f64],
w: &[f64],
seq_len: usize,
in_dim: usize,
out_dim: usize,
) -> Vec<f64> {
let mut out = vec![0.0_f64; seq_len * out_dim];
for t in 0..seq_len {
for o in 0..out_dim {
let mut acc = 0.0_f64;
for i in 0..in_dim {
acc += input[t * in_dim + i] * w[o * in_dim + i];
}
out[t * out_dim + o] = acc;
}
}
out
}
pub fn forward(&self, x: &[f64], seq_len: usize) -> Vec<f64> {
let dm = self.d_model;
let dh = self.d_head;
let nh = self.n_heads;
let q_full = Self::project(x, &self.w_q, seq_len, dm, dm);
let k_full = Self::project(x, &self.w_k, seq_len, dm, dm);
let v_full = Self::project(x, &self.w_v, seq_len, dm, dm);
let mut concat = vec![0.0_f64; seq_len * dm];
for h in 0..nh {
let mut q_h = vec![0.0_f64; seq_len * dh];
let mut k_h = vec![0.0_f64; seq_len * dh];
let mut v_h = vec![0.0_f64; seq_len * dh];
for t in 0..seq_len {
for d in 0..dh {
q_h[t * dh + d] = q_full[t * dm + h * dh + d];
k_h[t * dh + d] = k_full[t * dm + h * dh + d];
v_h[t * dh + d] = v_full[t * dm + h * dh + d];
}
}
let head_out =
scaled_dot_product_attention(&q_h, &k_h, &v_h, seq_len, seq_len, dh, dh, None);
for t in 0..seq_len {
for d in 0..dh {
concat[t * dm + h * dh + d] = head_out[t * dh + d];
}
}
}
let projected = Self::project(&concat, &self.w_o, seq_len, dm, dm);
let mut output = projected;
for t in 0..seq_len {
for d in 0..dm {
output[t * dm + d] += self.b_o[d];
}
}
output
}
pub fn num_params(&self) -> usize {
4 * self.d_model * self.d_model + self.d_model
}
}
#[derive(Debug, Clone)]
pub struct NeuralLayer {
pub weights: Vec<Vec<f64>>,
pub biases: Vec<f64>,
pub activation: ActivationFn64,
}
impl NeuralLayer {
pub fn new_xavier(in_features: usize, out_features: usize, activation: ActivationFn64) -> Self {
let limit = (6.0_f64 / (in_features + out_features) as f64).sqrt();
let mut state: u64 = 0x123456789abcdef0;
let lcg_next = |s: &mut u64| -> f64 {
*s = s
.wrapping_mul(6364136223846793005)
.wrapping_add(1442695040888963407);
let bits = (*s >> 33) as f64;
bits / (u64::MAX as f64) * 2.0 * limit - limit
};
let weights: Vec<Vec<f64>> = (0..out_features)
.map(|_| (0..in_features).map(|_| lcg_next(&mut state)).collect())
.collect();
let biases = vec![0.0_f64; out_features];
Self {
weights,
biases,
activation,
}
}
pub fn forward(&self, input: &[f64]) -> Vec<f64> {
let out_features = self.weights.len();
let mut output = Vec::with_capacity(out_features);
for o in 0..out_features {
let mut acc = self.biases[o];
for (i, &x) in input.iter().enumerate() {
acc += self.weights[o][i] * x;
}
output.push(self.activation.apply(acc));
}
output
}
}
#[allow(dead_code)]
#[derive(Debug, Clone)]
pub struct AttentionReadout {
pub d_feat: usize,
pub w_attn: Vec<f64>,
pub b_attn: f64,
}
impl AttentionReadout {
pub fn new(d_feat: usize) -> Self {
Self {
d_feat,
w_attn: vec![0.0_f64; d_feat],
b_attn: 0.0,
}
}
pub fn forward(&self, node_feats: &[f64], n_nodes: usize) -> Vec<f64> {
let df = self.d_feat;
let mut out = vec![0.0_f64; df];
let mut attn_scores = Vec::with_capacity(n_nodes);
for i in 0..n_nodes {
let h = &node_feats[i * df..(i + 1) * df];
let raw: f64 = h
.iter()
.zip(self.w_attn.iter())
.map(|(&x, &w)| x * w)
.sum::<f64>()
+ self.b_attn;
let score = 1.0 / (1.0 + (-raw).exp());
attn_scores.push(score);
}
for i in 0..n_nodes {
let h = &node_feats[i * df..(i + 1) * df];
for d in 0..df {
out[d] += attn_scores[i] * h[d];
}
}
out
}
}
#[allow(dead_code)]
#[derive(Debug, Clone)]
pub struct TransformerBlock {
pub mha: MultiHeadAttention,
pub ffn: TransformerFfn,
pub ln1: LayerNorm,
pub ln2: LayerNorm,
pub d_model: usize,
}
impl TransformerBlock {
pub fn new(d_model: usize, n_heads: usize, d_ff: usize) -> Self {
Self {
mha: MultiHeadAttention::new(d_model, n_heads),
ffn: TransformerFfn::new(d_model, d_ff),
ln1: LayerNorm::new(d_model),
ln2: LayerNorm::new(d_model),
d_model,
}
}
pub fn forward(&self, x: &[f64], seq_len: usize) -> Vec<f64> {
let dm = self.d_model;
let mut normed1 = vec![0.0_f64; seq_len * dm];
for t in 0..seq_len {
let row = &x[t * dm..(t + 1) * dm];
let n = self.ln1.forward(row);
normed1[t * dm..(t + 1) * dm].copy_from_slice(&n);
}
let attn_out = self.mha.forward(&normed1, seq_len);
let mut x1 = vec![0.0_f64; seq_len * dm];
for i in 0..x1.len() {
x1[i] = x[i] + attn_out[i];
}
let mut normed2 = vec![0.0_f64; seq_len * dm];
for t in 0..seq_len {
let row = &x1[t * dm..(t + 1) * dm];
let n = self.ln2.forward(row);
normed2[t * dm..(t + 1) * dm].copy_from_slice(&n);
}
let ffn_out = self.ffn.forward(&normed2, seq_len);
let mut x2 = vec![0.0_f64; seq_len * dm];
for i in 0..x2.len() {
x2[i] = x1[i] + ffn_out[i];
}
x2
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum ActivationFn {
Tanh,
Relu,
Sigmoid,
Silu,
Gelu,
Linear,
}
impl ActivationFn {
pub fn apply(&self, x: f32) -> f32 {
match self {
ActivationFn::Tanh => x.tanh(),
ActivationFn::Relu => x.max(0.0),
ActivationFn::Sigmoid => 1.0 / (1.0 + (-x).exp()),
ActivationFn::Silu => x / (1.0 + (-x).exp()),
ActivationFn::Gelu => {
let cdf = 0.5
* (1.0
+ (std::f32::consts::FRAC_2_SQRT_PI.sqrt() * (x + 0.044715 * x * x * x))
.tanh());
x * cdf
}
ActivationFn::Linear => x,
}
}
pub fn derivative(&self, x: f32) -> f32 {
match self {
ActivationFn::Tanh => {
let t = x.tanh();
1.0 - t * t
}
ActivationFn::Relu => {
if x > 0.0 {
1.0
} else {
0.0
}
}
ActivationFn::Sigmoid => {
let s = 1.0 / (1.0 + (-x).exp());
s * (1.0 - s)
}
ActivationFn::Silu => {
let s = 1.0 / (1.0 + (-x).exp());
s + x * s * (1.0 - s)
}
ActivationFn::Gelu => {
let eps = 1e-5_f32;
(self.apply(x + eps) - self.apply(x - eps)) / (2.0 * eps)
}
ActivationFn::Linear => 1.0,
}
}
}
#[derive(Debug, Clone)]
pub struct LayerNormLayer {
pub n_features: usize,
pub gamma: Vec<f64>,
pub beta: Vec<f64>,
pub epsilon: f64,
}
impl LayerNormLayer {
pub fn new(n_features: usize) -> Self {
Self {
n_features,
gamma: vec![1.0; n_features],
beta: vec![0.0; n_features],
epsilon: 1e-5,
}
}
pub fn forward(&self, input: &[f64]) -> Vec<f64> {
assert_eq!(
input.len(),
self.n_features,
"LayerNorm: input size mismatch"
);
let n = self.n_features as f64;
let mean: f64 = input.iter().sum::<f64>() / n;
let var: f64 = input.iter().map(|&x| (x - mean) * (x - mean)).sum::<f64>() / n;
let std_inv = 1.0 / (var + self.epsilon).sqrt();
(0..self.n_features)
.map(|i| self.gamma[i] * (input[i] - mean) * std_inv + self.beta[i])
.collect()
}
#[allow(non_snake_case)]
pub fn backward(&self, input: &[f64], d_output: &[f64]) -> (Vec<f64>, Vec<f64>, Vec<f64>) {
assert_eq!(input.len(), self.n_features);
assert_eq!(d_output.len(), self.n_features);
let n = self.n_features as f64;
let mean: f64 = input.iter().sum::<f64>() / n;
let var: f64 = input.iter().map(|&x| (x - mean) * (x - mean)).sum::<f64>() / n;
let std_inv = 1.0 / (var + self.epsilon).sqrt();
let x_hat: Vec<f64> = input.iter().map(|&x| (x - mean) * std_inv).collect();
let d_gamma: Vec<f64> = (0..self.n_features)
.map(|i| d_output[i] * x_hat[i])
.collect();
let d_beta: Vec<f64> = d_output.to_vec();
let d_x_hat: Vec<f64> = (0..self.n_features)
.map(|i| d_output[i] * self.gamma[i])
.collect();
let sum_d_x_hat: f64 = d_x_hat.iter().sum();
let sum_d_x_hat_xhat: f64 = d_x_hat.iter().zip(x_hat.iter()).map(|(&a, &b)| a * b).sum();
let d_input: Vec<f64> = (0..self.n_features)
.map(|i| std_inv * (d_x_hat[i] - (sum_d_x_hat + x_hat[i] * sum_d_x_hat_xhat) / n))
.collect();
(d_input, d_gamma, d_beta)
}
}
#[derive(Debug, Clone)]
pub struct DataNormalizer {
pub mean: Vec<f32>,
pub std_dev: Vec<f32>,
}
impl DataNormalizer {
pub fn fit(data: &[Vec<f32>]) -> Self {
assert!(
!data.is_empty(),
"DataNormalizer::fit: data must be non-empty"
);
let n_features = data[0].len();
let n = data.len() as f32;
let mut mean = vec![0.0_f32; n_features];
for sample in data {
assert_eq!(
sample.len(),
n_features,
"DataNormalizer::fit: inconsistent sample length"
);
for (k, &v) in sample.iter().enumerate() {
mean[k] += v;
}
}
for m in &mut mean {
*m /= n;
}
let mut variance = vec![0.0_f32; n_features];
for sample in data {
for (k, &v) in sample.iter().enumerate() {
let diff = v - mean[k];
variance[k] += diff * diff;
}
}
let std_dev: Vec<f32> = variance
.iter()
.map(|&v| {
let s = (v / n).sqrt();
if s < 1e-8 { 1.0 } else { s }
})
.collect();
DataNormalizer { mean, std_dev }
}
pub fn transform(&self, x: &[f32]) -> Vec<f32> {
x.iter()
.zip(self.mean.iter())
.zip(self.std_dev.iter())
.map(|((&xi, &m), &s)| (xi - m) / s)
.collect()
}
pub fn inverse_transform(&self, x: &[f32]) -> Vec<f32> {
x.iter()
.zip(self.mean.iter())
.zip(self.std_dev.iter())
.map(|((&xi, &m), &s)| xi * s + m)
.collect()
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum ActivationFn64 {
Relu,
Sigmoid,
Tanh,
Linear,
}
impl ActivationFn64 {
pub fn apply(&self, x: f64) -> f64 {
match self {
ActivationFn64::Relu => x.max(0.0),
ActivationFn64::Sigmoid => 1.0 / (1.0 + (-x).exp()),
ActivationFn64::Tanh => x.tanh(),
ActivationFn64::Linear => x,
}
}
pub fn apply_batch(&self, v: &mut Vec<f64>) {
for x in v.iter_mut() {
*x = self.apply(*x);
}
}
}
#[derive(Debug, Clone)]
pub struct NeuralNetwork {
pub layers: Vec<NeuralLayer>,
}
impl NeuralNetwork {
pub fn new(layer_sizes: &[usize], activation: ActivationFn64) -> Self {
assert!(
layer_sizes.len() >= 2,
"need at least input and output size"
);
let mut layers = Vec::new();
for i in 0..layer_sizes.len() - 1 {
let act = if i == layer_sizes.len() - 2 {
ActivationFn64::Linear
} else {
activation.clone()
};
layers.push(NeuralLayer::new_xavier(
layer_sizes[i],
layer_sizes[i + 1],
act,
));
}
Self { layers }
}
pub fn forward(&self, input: &[f64]) -> Vec<f64> {
let mut current: Vec<f64> = input.to_vec();
for layer in &self.layers {
current = layer.forward(¤t);
}
current
}
pub fn input_dim(&self) -> usize {
self.layers
.first()
.map_or(0, |l| l.weights.first().map_or(0, |r| r.len()))
}
pub fn output_dim(&self) -> usize {
self.layers.last().map_or(0, |l| l.biases.len())
}
}
#[derive(Debug, Clone)]
pub struct FeedForwardNet {
pub layers: Vec<DenseLayer>,
}
impl FeedForwardNet {
pub fn new() -> Self {
FeedForwardNet { layers: Vec::new() }
}
pub fn add_layer(&mut self, layer: DenseLayer) {
self.layers.push(layer);
}
pub fn forward(&self, input: &[f32]) -> Vec<f32> {
let mut current: Vec<f32> = input.to_vec();
for layer in &self.layers {
current = layer.forward(¤t);
}
current
}
pub fn input_size(&self) -> Option<usize> {
self.layers.first().map(|l| l.in_features)
}
pub fn output_size(&self) -> Option<usize> {
self.layers.last().map(|l| l.out_features)
}
pub fn total_parameters(&self) -> usize {
self.layers.iter().map(|l| l.parameter_count()).sum()
}
}
impl FeedForwardNet {
pub fn compute_gradient_norm(&self, layer_grads: &[Vec<f32>]) -> f32 {
let sum_sq: f32 = layer_grads
.iter()
.flat_map(|g| g.iter())
.map(|&v| v * v)
.sum();
sum_sq.sqrt()
}
pub fn clip_gradients(&self, layer_grads: &mut Vec<Vec<f32>>, max_norm: f32) -> f32 {
let norm = self.compute_gradient_norm(layer_grads);
if norm > max_norm && norm > 0.0 {
let scale = max_norm / norm;
for g in layer_grads.iter_mut() {
for v in g.iter_mut() {
*v *= scale;
}
}
}
norm
}
}
#[allow(dead_code)]
#[derive(Debug, Clone)]
pub struct LayerNorm {
pub n_features: usize,
pub gamma: Vec<f64>,
pub beta: Vec<f64>,
pub epsilon: f64,
}
impl LayerNorm {
pub fn new(n_features: usize) -> Self {
Self {
n_features,
gamma: vec![1.0_f64; n_features],
beta: vec![0.0_f64; n_features],
epsilon: 1e-5,
}
}
pub fn forward(&self, x: &[f64]) -> Vec<f64> {
assert_eq!(x.len(), self.n_features);
let n = self.n_features as f64;
let mean = x.iter().sum::<f64>() / n;
let var = x.iter().map(|&v| (v - mean) * (v - mean)).sum::<f64>() / n;
let std = (var + self.epsilon).sqrt();
x.iter()
.enumerate()
.map(|(i, &v)| self.gamma[i] * (v - mean) / std + self.beta[i])
.collect()
}
}
#[derive(Debug, Clone)]
pub struct GpuNeuralBuffer {
pub batch_size: usize,
pub input_dim: usize,
pub output_dim: usize,
pub data: Vec<f64>,
}
impl GpuNeuralBuffer {
pub fn pack_positions(positions: &[[f64; 3]]) -> Self {
let batch_size = positions.len();
let input_dim = 3;
let output_dim = 3;
let mut data = Vec::with_capacity(batch_size * input_dim);
for p in positions {
data.push(p[0]);
data.push(p[1]);
data.push(p[2]);
}
Self {
batch_size,
input_dim,
output_dim,
data,
}
}
pub fn unpack_forces(&self) -> Vec<[f64; 3]> {
self.data.chunks(3).map(|c| [c[0], c[1], c[2]]).collect()
}
}
#[allow(dead_code)]
#[derive(Debug, Clone)]
pub struct PositionalEncoding {
pub d_model: usize,
pub max_len: usize,
pub table: Vec<Vec<f64>>,
}
impl PositionalEncoding {
pub fn new(d_model: usize, max_len: usize) -> Self {
let mut table = vec![vec![0.0_f64; d_model]; max_len];
for pos in 0..max_len {
for i in 0..(d_model / 2) {
let angle = (pos as f64) / (10000.0_f64).powf(2.0 * i as f64 / d_model as f64);
table[pos][2 * i] = angle.sin();
if 2 * i + 1 < d_model {
table[pos][2 * i + 1] = angle.cos();
}
}
}
Self {
d_model,
max_len,
table,
}
}
pub fn add_to_sequence(&self, embeddings: &mut Vec<Vec<f64>>) {
for (t, emb) in embeddings.iter_mut().enumerate() {
if t >= self.max_len {
break;
}
for d in 0..emb.len().min(self.d_model) {
emb[d] += self.table[t][d];
}
}
}
pub fn get(&self, pos: usize) -> &[f64] {
&self.table[pos.min(self.max_len - 1)]
}
}
#[derive(Debug, Clone)]
pub struct DenseLayer64 {
pub weights: Vec<f64>,
pub biases: Vec<f64>,
pub in_features: usize,
pub out_features: usize,
pub activation: ExtActivation,
pub last_pre_act: Vec<f64>,
pub last_output: Vec<f64>,
pub last_input: Vec<f64>,
}
impl DenseLayer64 {
pub fn new(in_features: usize, out_features: usize, activation: ExtActivation) -> Self {
Self {
weights: vec![0.0_f64; out_features * in_features],
biases: vec![0.0_f64; out_features],
in_features,
out_features,
activation,
last_pre_act: Vec::new(),
last_output: Vec::new(),
last_input: Vec::new(),
}
}
pub fn forward(&mut self, input: &[f64]) -> Vec<f64> {
assert_eq!(
input.len(),
self.in_features,
"DenseLayer64::forward: input size mismatch"
);
self.last_input = input.to_vec();
let mut pre_act = Vec::with_capacity(self.out_features);
for o in 0..self.out_features {
let row = o * self.in_features;
let mut acc = self.biases[o];
for i in 0..self.in_features {
acc += self.weights[row + i] * input[i];
}
pre_act.push(acc);
}
let output: Vec<f64> = pre_act.iter().map(|&z| self.activation.apply(z)).collect();
self.last_pre_act = pre_act;
self.last_output = output.clone();
output
}
#[allow(clippy::too_many_arguments)]
pub fn backward(&self, delta_out: &[f64]) -> (Vec<f64>, Vec<f64>, Vec<f64>) {
assert_eq!(
delta_out.len(),
self.out_features,
"DenseLayer64::backward: delta_out size mismatch"
);
let delta_pre: Vec<f64> = delta_out
.iter()
.zip(self.last_pre_act.iter())
.map(|(&d, &z)| d * self.activation.derivative(z))
.collect();
let mut grad_weights = vec![0.0_f64; self.out_features * self.in_features];
for o in 0..self.out_features {
let row = o * self.in_features;
for i in 0..self.in_features {
grad_weights[row + i] = delta_pre[o] * self.last_input[i];
}
}
let grad_biases = delta_pre.clone();
let mut delta_in = vec![0.0_f64; self.in_features];
for o in 0..self.out_features {
let row = o * self.in_features;
for i in 0..self.in_features {
delta_in[i] += self.weights[row + i] * delta_pre[o];
}
}
(grad_weights, grad_biases, delta_in)
}
pub fn apply_sgd(&mut self, grad_weights: &[f64], grad_biases: &[f64], lr: f64) {
for (w, &gw) in self.weights.iter_mut().zip(grad_weights.iter()) {
*w -= lr * gw;
}
for (b, &gb) in self.biases.iter_mut().zip(grad_biases.iter()) {
*b -= lr * gb;
}
}
pub fn num_params(&self) -> usize {
self.out_features * self.in_features + self.out_features
}
}
#[derive(Debug)]
pub struct AtomicNeuralNetwork {
pub networks: HashMap<u8, FeedForwardNet>,
pub descriptor: BehlerParrinelloDescriptor,
}
impl AtomicNeuralNetwork {
pub fn new(descriptor: BehlerParrinelloDescriptor) -> Self {
AtomicNeuralNetwork {
networks: HashMap::new(),
descriptor,
}
}
pub fn add_element_network(&mut self, atomic_number: u8, net: FeedForwardNet) {
self.networks.insert(atomic_number, net);
}
pub fn atomic_energy(&self, atomic_number: u8, descriptor: &[f32]) -> Option<f32> {
self.networks
.get(&atomic_number)
.map(|net| net.forward(descriptor)[0])
}
pub fn total_energy(&self, positions: &[[f64; 3]], atomic_numbers: &[u8]) -> f64 {
assert_eq!(
positions.len(),
atomic_numbers.len(),
"total_energy: positions and atomic_numbers must have the same length"
);
let mut e_total = 0.0_f64;
for (i, &z) in atomic_numbers.iter().enumerate() {
let desc_f64 = self.descriptor.descriptor_vector(positions, i);
let desc_f32: Vec<f32> = desc_f64.iter().map(|&v| v as f32).collect();
if let Some(e) = self.atomic_energy(z, &desc_f32) {
e_total += e as f64;
}
}
e_total
}
}
#[derive(Debug, Clone)]
pub struct DenseLayer {
pub weights: Vec<f32>,
pub biases: Vec<f32>,
pub in_features: usize,
pub out_features: usize,
pub activation: ActivationFn,
}
impl DenseLayer {
pub fn new(in_features: usize, out_features: usize, activation: ActivationFn) -> Self {
DenseLayer {
weights: vec![0.0_f32; out_features * in_features],
biases: vec![0.0_f32; out_features],
in_features,
out_features,
activation,
}
}
pub fn forward(&self, input: &[f32]) -> Vec<f32> {
assert_eq!(
input.len(),
self.in_features,
"DenseLayer::forward: input length {} != in_features {}",
input.len(),
self.in_features
);
let mut output = Vec::with_capacity(self.out_features);
for o in 0..self.out_features {
let row_offset = o * self.in_features;
let mut acc = self.biases[o];
for i in 0..self.in_features {
acc += self.weights[row_offset + i] * input[i];
}
output.push(self.activation.apply(acc));
}
output
}
pub fn set_weights(&mut self, w: &[f32]) {
assert_eq!(
w.len(),
self.out_features * self.in_features,
"set_weights: expected {} elements, got {}",
self.out_features * self.in_features,
w.len()
);
self.weights.copy_from_slice(w);
}
pub fn set_biases(&mut self, b: &[f32]) {
assert_eq!(
b.len(),
self.out_features,
"set_biases: expected {} elements, got {}",
self.out_features,
b.len()
);
self.biases.copy_from_slice(b);
}
pub fn parameter_count(&self) -> usize {
self.out_features * self.in_features + self.out_features
}
}
#[derive(Debug, Clone)]
pub struct DropoutLayer {
pub rate: f64,
pub training: bool,
pub last_mask: Vec<f64>,
pub(super) seed: u64,
}
impl DropoutLayer {
pub fn new(rate: f64, training: bool) -> Self {
assert!(
(0.0..=1.0).contains(&rate),
"dropout rate must be in [0, 1]"
);
Self {
rate,
training,
last_mask: Vec::new(),
seed: 0xdeadbeefcafe1234,
}
}
pub fn set_seed(&mut self, seed: u64) {
self.seed = seed;
}
pub fn forward(&mut self, input: &[f64]) -> Vec<f64> {
if !self.training || self.rate == 0.0 {
self.last_mask = vec![1.0; input.len()];
return input.to_vec();
}
if self.rate == 1.0 {
self.last_mask = vec![0.0; input.len()];
return vec![0.0; input.len()];
}
let scale = 1.0 / (1.0 - self.rate);
let mut mask = Vec::with_capacity(input.len());
let mut output = Vec::with_capacity(input.len());
for &x in input {
self.seed = self
.seed
.wrapping_mul(6364136223846793005)
.wrapping_add(1442695040888963407);
let u = (self.seed >> 11) as f64 / (1u64 << 53) as f64;
let m = if u >= self.rate { scale } else { 0.0 };
mask.push(m);
output.push(x * m);
}
self.last_mask = mask;
output
}
pub fn backward(&self, delta_out: &[f64]) -> Vec<f64> {
delta_out
.iter()
.zip(self.last_mask.iter())
.map(|(&d, &m)| d * m)
.collect()
}
}
#[derive(Debug, Clone)]
pub struct BehlerParrinelloDescriptor {
pub eta: Vec<f64>,
pub rs: Vec<f64>,
pub cutoff: f64,
}
impl BehlerParrinelloDescriptor {
pub fn cutoff_fn(r: f64, rc: f64) -> f64 {
if r < rc {
0.5 * ((PI_F64 * r / rc).cos() + 1.0)
} else {
0.0
}
}
pub fn radial_g1(r: f64, rc: f64) -> f64 {
Self::cutoff_fn(r, rc)
}
pub fn radial_g2(r: f64, eta: f64, rs: f64, rc: f64) -> f64 {
let diff = r - rs;
(-eta * diff * diff).exp() * Self::cutoff_fn(r, rc)
}
#[allow(clippy::too_many_arguments)]
pub fn angular_g4(
r_ij: f64,
r_ik: f64,
r_jk: f64,
cos_theta: f64,
eta: f64,
zeta: f64,
lambda: f64,
rc: f64,
) -> f64 {
let angular = (1.0 + lambda * cos_theta).powf(zeta);
let radial = (-eta * (r_ij * r_ij + r_ik * r_ik + r_jk * r_jk)).exp();
let fc = Self::cutoff_fn(r_ij, rc) * Self::cutoff_fn(r_ik, rc) * Self::cutoff_fn(r_jk, rc);
2.0_f64.powf(1.0 - zeta) * angular * radial * fc
}
pub fn compute(r_ij: f64, eta: f64, rs: f64, cutoff: f64) -> f64 {
Self::radial_g2(r_ij, eta, rs, cutoff)
}
pub fn descriptor_vector(&self, positions: &[[f64; 3]], center_idx: usize) -> Vec<f64> {
let n_descriptors = self.eta.len();
let mut desc = vec![0.0_f64; n_descriptors];
let ci = positions[center_idx];
for (j, pos_j) in positions.iter().enumerate() {
if j == center_idx {
continue;
}
let dx = pos_j[0] - ci[0];
let dy = pos_j[1] - ci[1];
let dz = pos_j[2] - ci[2];
let r = (dx * dx + dy * dy + dz * dz).sqrt();
if r >= self.cutoff {
continue;
}
for k in 0..n_descriptors {
desc[k] += Self::radial_g2(r, self.eta[k], self.rs[k], self.cutoff);
}
}
desc
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum ExtActivation {
LeakyRelu(f64),
Swish(f64),
Relu,
Sigmoid,
Tanh,
Linear,
}
impl ExtActivation {
pub fn apply(&self, x: f64) -> f64 {
match self {
ExtActivation::LeakyRelu(alpha) => {
if x >= 0.0 {
x
} else {
alpha * x
}
}
ExtActivation::Swish(beta) => x / (1.0 + (-beta * x).exp()),
ExtActivation::Relu => x.max(0.0),
ExtActivation::Sigmoid => 1.0 / (1.0 + (-x).exp()),
ExtActivation::Tanh => x.tanh(),
ExtActivation::Linear => x,
}
}
pub fn derivative(&self, x: f64) -> f64 {
match self {
ExtActivation::LeakyRelu(alpha) => {
if x >= 0.0 {
1.0
} else {
*alpha
}
}
ExtActivation::Swish(beta) => {
let sig = 1.0 / (1.0 + (-beta * x).exp());
sig + beta * x * sig * (1.0 - sig)
}
ExtActivation::Relu => {
if x > 0.0 {
1.0
} else {
0.0
}
}
ExtActivation::Sigmoid => {
let s = 1.0 / (1.0 + (-x).exp());
s * (1.0 - s)
}
ExtActivation::Tanh => {
let t = x.tanh();
1.0 - t * t
}
ExtActivation::Linear => 1.0,
}
}
pub fn apply_vec(&self, v: &mut [f64]) {
for x in v.iter_mut() {
*x = self.apply(*x);
}
}
}