1use crate::error::{OptimError, Result};
4use scirs2_core::ndarray::{Array1, Array2};
5use scirs2_core::numeric::Float;
6use serde::{Deserialize, Serialize};
7use std::fmt::Debug;
8
9fn inverse_frequency(i: usize, model_dimension: usize, base: f64) -> f64 {
15 let exponent = 2.0 * ((i / 2) as f64) / model_dimension.max(1) as f64;
16 1.0 / base.powf(exponent)
17}
18
19#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
21pub enum PositionalEncodingType {
22 Sinusoidal,
24 Learned,
26 Rotary,
28 None,
30}
31
32pub struct PositionalEncoding<
34 T: Float + Debug + scirs2_core::ndarray::ScalarOperand + Send + Sync + 'static,
35> {
36 encoding_type: PositionalEncodingType,
38
39 max_sequence_length: usize,
41
42 model_dimension: usize,
44
45 encoding_matrix: Array2<T>,
47
48 learned_embeddings: Option<Array2<T>>,
50
51 rope_base: T,
53}
54
55impl<T: Float + Debug + scirs2_core::ndarray::ScalarOperand + Send + Sync + 'static>
56 PositionalEncoding<T>
57{
58 pub fn new(
60 max_sequence_length: usize,
61 model_dimension: usize,
62 encoding_type: PositionalEncodingType,
63 ) -> Result<Self> {
64 let rope_base = scirs2_core::numeric::NumCast::from(10000.0).unwrap_or_else(|| T::zero());
65 let mut encoding = Self {
66 encoding_type,
67 max_sequence_length,
68 model_dimension,
69 encoding_matrix: Array2::zeros((max_sequence_length, model_dimension)),
70 learned_embeddings: None,
71 rope_base,
72 };
73
74 encoding.initialize_encoding()?;
75 Ok(encoding)
76 }
77
78 fn initialize_encoding(&mut self) -> Result<()> {
80 match self.encoding_type {
81 PositionalEncodingType::Sinusoidal => self.initialize_sinusoidal(),
82 PositionalEncodingType::Learned => self.initialize_learned(),
83 PositionalEncodingType::Rotary => self.initialize_rotary(),
84 PositionalEncodingType::None => Ok(()),
85 }
86 }
87
88 fn initialize_sinusoidal(&mut self) -> Result<()> {
90 let base = self.rope_base.to_f64().unwrap_or(10000.0);
91 self.fill_sinusoidal(base);
92 Ok(())
93 }
94
95 fn fill_sinusoidal(&mut self, base: f64) {
97 for pos in 0..self.max_sequence_length {
98 for i in 0..self.model_dimension {
99 let angle = pos as f64 * inverse_frequency(i, self.model_dimension, base);
100 let value = if i % 2 == 0 { angle.sin() } else { angle.cos() };
101 self.encoding_matrix[[pos, i]] =
102 scirs2_core::numeric::NumCast::from(value).unwrap_or_else(|| T::zero());
103 }
104 }
105 }
106
107 fn initialize_learned(&mut self) -> Result<()> {
113 let base = self.rope_base.to_f64().unwrap_or(10000.0);
114 self.fill_sinusoidal(base);
115 self.learned_embeddings = Some(self.encoding_matrix.clone());
116 Ok(())
117 }
118
119 fn initialize_rotary(&mut self) -> Result<()> {
124 let base = self.rope_base.to_f64().unwrap_or(10000.0);
125 self.encoding_matrix.fill(T::zero());
126 for i in (0..self.model_dimension).step_by(2) {
127 let freq: T = scirs2_core::numeric::NumCast::from(inverse_frequency(
128 i,
129 self.model_dimension,
130 base,
131 ))
132 .unwrap_or_else(|| T::zero());
133 self.encoding_matrix[[0, i]] = freq;
134 if i + 1 < self.model_dimension {
135 self.encoding_matrix[[0, i + 1]] = freq;
136 }
137 }
138 Ok(())
139 }
140
141 fn validate_input(&self, input: &Array2<T>) -> Result<(usize, usize)> {
144 let (sequence_length, model_dimension) = input.dim();
145 if model_dimension != self.model_dimension {
146 return Err(OptimError::InvalidConfig(format!(
147 "Input width {model_dimension} does not match the model dimension {}",
148 self.model_dimension
149 )));
150 }
151 if sequence_length > self.max_sequence_length {
152 return Err(OptimError::InvalidConfig(format!(
153 "Sequence length {sequence_length} exceeds the maximum {}",
154 self.max_sequence_length
155 )));
156 }
157 Ok((sequence_length, model_dimension))
158 }
159
160 pub fn encode(&self, input: &Array2<T>) -> Result<Array2<T>> {
162 match self.encoding_type {
163 PositionalEncodingType::None => Ok(input.clone()),
164 PositionalEncodingType::Sinusoidal => self.apply_sinusoidal(input),
165 PositionalEncodingType::Learned => self.apply_learned(input),
166 PositionalEncodingType::Rotary => self.apply_rotary(input),
167 }
168 }
169
170 fn apply_sinusoidal(&self, input: &Array2<T>) -> Result<Array2<T>> {
172 let (sequence_length, model_dimension) = self.validate_input(input)?;
173 let mut output = input.clone();
174
175 for pos in 0..sequence_length {
176 for dim in 0..model_dimension {
177 output[[pos, dim]] = output[[pos, dim]] + self.encoding_matrix[[pos, dim]];
178 }
179 }
180
181 Ok(output)
182 }
183
184 fn apply_learned(&self, input: &Array2<T>) -> Result<Array2<T>> {
186 let Some(ref learned) = self.learned_embeddings else {
187 return Err(OptimError::InvalidState(
188 "Learned embeddings not initialized".to_string(),
189 ));
190 };
191
192 let (sequence_length, model_dimension) = self.validate_input(input)?;
193 let mut output = input.clone();
194
195 for pos in 0..sequence_length {
196 for dim in 0..model_dimension {
197 output[[pos, dim]] = output[[pos, dim]] + learned[[pos, dim]];
198 }
199 }
200
201 Ok(output)
202 }
203
204 fn apply_rotary(&self, input: &Array2<T>) -> Result<Array2<T>> {
211 let (sequence_length, model_dimension) = self.validate_input(input)?;
212 let mut output = input.clone();
213
214 for pos in 0..sequence_length {
215 let position: T =
216 scirs2_core::numeric::NumCast::from(pos as f64).unwrap_or_else(|| T::zero());
217
218 for i in (0..model_dimension.saturating_sub(1)).step_by(2) {
219 let freq = self.encoding_matrix[[0, i]];
220 let angle = position * freq;
221 let cos_val = angle.cos();
222 let sin_val = angle.sin();
223
224 let x = input[[pos, i]];
225 let y = input[[pos, i + 1]];
226
227 output[[pos, i]] = x * cos_val - y * sin_val;
228 output[[pos, i + 1]] = x * sin_val + y * cos_val;
229 }
230 }
231
232 Ok(output)
233 }
234
235 pub fn get_position_encoding(&self, position: usize) -> Result<Array1<T>> {
237 if position >= self.max_sequence_length {
238 return Err(crate::error::OptimError::Other(
239 "Position exceeds maximum sequence length".to_string(),
240 ));
241 }
242
243 match self.encoding_type {
244 PositionalEncodingType::Sinusoidal => Ok(self.encoding_matrix.row(position).to_owned()),
245 PositionalEncodingType::Learned => {
246 if let Some(ref learned) = self.learned_embeddings {
247 Ok(learned.row(position).to_owned())
248 } else {
249 Err(crate::error::OptimError::Other(
250 "Learned embeddings not available".to_string(),
251 ))
252 }
253 }
254 PositionalEncodingType::Rotary => {
255 Ok(self.encoding_matrix.row(0).to_owned())
257 }
258 PositionalEncodingType::None => Ok(Array1::zeros(self.model_dimension)),
259 }
260 }
261
262 pub fn update_learned_embeddings(&mut self, gradients: &Array2<T>) -> Result<()> {
264 let Some(ref mut learned) = self.learned_embeddings else {
265 return Err(OptimError::InvalidState(
266 "No learned embeddings to update".to_string(),
267 ));
268 };
269 if learned.dim() != gradients.dim() {
270 return Err(OptimError::InvalidConfig(format!(
271 "Gradient shape {:?} does not match the embedding shape {:?}",
272 gradients.dim(),
273 learned.dim()
274 )));
275 }
276 *learned = &*learned - gradients;
277 Ok(())
278 }
279
280 pub fn parameter_count(&self) -> usize {
282 match self.encoding_type {
283 PositionalEncodingType::Learned => self.max_sequence_length * self.model_dimension,
284 _ => 0, }
286 }
287
288 pub fn reset(&mut self) -> Result<()> {
290 self.initialize_encoding()
291 }
292
293 pub fn get_encoding_type(&self) -> PositionalEncodingType {
295 self.encoding_type
296 }
297
298 pub fn get_max_sequence_length(&self) -> usize {
300 self.max_sequence_length
301 }
302
303 pub fn get_model_dimension(&self) -> usize {
305 self.model_dimension
306 }
307
308 pub fn sinusoidal_with_base(
310 max_sequence_length: usize,
311 model_dimension: usize,
312 base: T,
313 ) -> Result<Self> {
314 let mut encoding = Self::new(
315 max_sequence_length,
316 model_dimension,
317 PositionalEncodingType::Sinusoidal,
318 )?;
319
320 encoding.rope_base = base;
322 encoding.fill_sinusoidal(base.to_f64().unwrap_or(10000.0));
323
324 Ok(encoding)
325 }
326
327 pub fn rotary_with_base(
329 max_sequence_length: usize,
330 model_dimension: usize,
331 base: T,
332 ) -> Result<Self> {
333 let mut encoding = Self::new(
334 max_sequence_length,
335 model_dimension,
336 PositionalEncodingType::Rotary,
337 )?;
338
339 encoding.rope_base = base;
340 encoding.initialize_rotary()?;
341
342 Ok(encoding)
343 }
344}
345
346pub struct RelativePositionalEncoding<
348 T: Float + Debug + scirs2_core::ndarray::ScalarOperand + Send + Sync + 'static,
349> {
350 max_relative_distance: usize,
352
353 model_dimension: usize,
355
356 relative_encoding: Array2<T>,
358}
359
360impl<T: Float + Debug + scirs2_core::ndarray::ScalarOperand + Send + Sync + 'static>
361 RelativePositionalEncoding<T>
362{
363 pub fn new(max_relative_distance: usize, model_dimension: usize) -> Result<Self> {
365 let table_size = 2 * max_relative_distance + 1;
366 let relative_encoding = Array2::zeros((table_size, model_dimension));
367
368 Ok(Self {
369 max_relative_distance,
370 model_dimension,
371 relative_encoding,
372 })
373 }
374
375 pub fn get_relative_encoding(&self, from_pos: usize, to_pos: usize) -> Array1<T> {
377 let relative_distance = (to_pos as i32 - from_pos as i32)
378 .max(-(self.max_relative_distance as i32))
379 .min(self.max_relative_distance as i32);
380
381 let index = (relative_distance + self.max_relative_distance as i32) as usize;
382 self.relative_encoding.row(index).to_owned()
383 }
384
385 pub fn initialize_sinusoidal(&mut self) -> Result<()> {
387 let table_size = 2 * self.max_relative_distance + 1;
388
389 for i in 0..table_size {
390 let relative_pos = i as i32 - self.max_relative_distance as i32;
391 let position =
392 scirs2_core::numeric::NumCast::from(relative_pos).unwrap_or_else(|| T::zero());
393
394 let _ = position;
395 for j in 0..self.model_dimension {
396 let angle =
397 relative_pos as f64 * inverse_frequency(j, self.model_dimension, 10000.0);
398 let value = if j % 2 == 0 { angle.sin() } else { angle.cos() };
399 self.relative_encoding[[i, j]] =
400 scirs2_core::numeric::NumCast::from(value).unwrap_or_else(|| T::zero());
401 }
402 }
403
404 Ok(())
405 }
406
407 pub fn parameter_count(&self) -> usize {
409 (2 * self.max_relative_distance + 1) * self.model_dimension
410 }
411}
412
413#[cfg(test)]
414mod tests {
415 use super::*;
416
417 #[test]
418 fn sinusoidal_pairs_share_a_frequency() {
419 let pe = PositionalEncoding::<f64>::new(64, 8, PositionalEncodingType::Sinusoidal)
420 .expect("encoding creation");
421 let row = pe.get_position_encoding(3).expect("position encoding");
422
423 let omega = 1.0 / 10000.0_f64.powf(2.0 * 1.0 / 8.0);
425 assert!((row[2] - (3.0 * omega).sin()).abs() < 1e-12);
426 assert!((row[3] - (3.0 * omega).cos()).abs() < 1e-12);
427 assert!((row[2] * row[2] + row[3] * row[3] - 1.0).abs() < 1e-12);
428 }
429
430 #[test]
431 fn position_zero_alternates_zero_and_one() {
432 let pe = PositionalEncoding::<f64>::new(16, 8, PositionalEncodingType::Sinusoidal)
433 .expect("encoding creation");
434 let row = pe.get_position_encoding(0).expect("position encoding");
435 for i in 0..8 {
436 let expected = if i % 2 == 0 { 0.0 } else { 1.0 };
437 assert!((row[i] - expected).abs() < 1e-12, "dim {i} was {}", row[i]);
438 }
439 }
440
441 #[test]
442 fn sinusoidal_encoding_adds_per_cell_not_per_row() {
443 let pe = PositionalEncoding::<f64>::new(16, 8, PositionalEncodingType::Sinusoidal)
444 .expect("encoding creation");
445 let input = Array2::<f64>::zeros((4, 8));
446 let encoded = pe.encode(&input).expect("encode");
447 assert_eq!(encoded.dim(), (4, 8));
448
449 for pos in 0..4 {
450 let reference = pe.get_position_encoding(pos).expect("position encoding");
451 for dim in 0..8 {
452 assert!(
453 (encoded[[pos, dim]] - reference[dim]).abs() < 1e-12,
454 "cell ({pos}, {dim}) was {}",
455 encoded[[pos, dim]]
456 );
457 }
458 }
459 assert!(encoded.row(0) != encoded.row(1));
461 }
462
463 #[test]
464 fn wrong_width_is_rejected() {
465 let pe = PositionalEncoding::<f64>::new(16, 8, PositionalEncodingType::Sinusoidal)
466 .expect("encoding creation");
467 assert!(pe.encode(&Array2::<f64>::zeros((4, 5))).is_err());
468 assert!(pe.encode(&Array2::<f64>::zeros((32, 8))).is_err());
469 }
470
471 #[test]
472 fn rotary_encoding_is_a_rotation() {
473 let pe = PositionalEncoding::<f64>::new(16, 4, PositionalEncodingType::Rotary)
474 .expect("encoding creation");
475 let input = Array2::<f64>::from_shape_vec(
476 (3, 4),
477 vec![
478 1.0, 0.0, 0.0, 1.0, 2.0, 1.0, -1.0, 3.0, 0.5, 0.5, 0.5, 0.5,
481 ],
482 )
483 .expect("valid shape");
484
485 let output = pe.encode(&input).expect("encode");
486 assert_eq!(output.dim(), (3, 4));
487
488 for pos in 0..3 {
489 for pair in 0..2 {
490 let before = input[[pos, 2 * pair]].powi(2) + input[[pos, 2 * pair + 1]].powi(2);
491 let after = output[[pos, 2 * pair]].powi(2) + output[[pos, 2 * pair + 1]].powi(2);
492 assert!(
493 (before - after).abs() < 1e-10,
494 "pair {pair} at position {pos} changed norm: {before} -> {after}"
495 );
496 }
497 }
498
499 for dim in 0..4 {
501 assert!((output[[0, dim]] - input[[0, dim]]).abs() < 1e-12);
502 }
503 assert!((output[[2, 0]] - input[[2, 0]]).abs() > 1e-9);
505 }
506
507 #[test]
508 fn rotary_matches_hand_computed_values() {
509 let pe = PositionalEncoding::<f64>::new(16, 2, PositionalEncodingType::Rotary)
510 .expect("encoding creation");
511 let input =
513 Array2::<f64>::from_shape_vec((2, 2), vec![1.0, 0.0, 1.0, 0.0]).expect("valid shape");
514 let output = pe.encode(&input).expect("encode");
515 assert!((output[[1, 0]] - 1.0_f64.cos()).abs() < 1e-12);
516 assert!((output[[1, 1]] - 1.0_f64.sin()).abs() < 1e-12);
517 }
518
519 #[test]
520 fn learned_encoding_starts_position_aware() {
521 let pe = PositionalEncoding::<f64>::new(32, 8, PositionalEncodingType::Learned)
522 .expect("encoding creation");
523 assert_eq!(pe.parameter_count(), 32 * 8);
524 let a = pe.get_position_encoding(1).expect("position encoding");
525 let b = pe.get_position_encoding(2).expect("position encoding");
526 assert!(a != b, "learned embeddings collapsed onto one vector");
527 }
528
529 #[test]
530 fn no_encoding_is_the_identity() {
531 let pe = PositionalEncoding::<f64>::new(16, 4, PositionalEncodingType::None)
532 .expect("encoding creation");
533 let input = Array2::<f64>::from_elem((3, 4), 0.25);
534 assert_eq!(pe.encode(&input).expect("encode"), input);
535 }
536
537 #[test]
538 fn relative_encoding_pairs_share_a_frequency() {
539 let mut rel = RelativePositionalEncoding::<f64>::new(8, 8).expect("creation");
540 rel.initialize_sinusoidal().expect("initialization");
541 let row = rel.get_relative_encoding(0, 3);
542 let omega = 1.0 / 10000.0_f64.powf(2.0 * 1.0 / 8.0);
543 assert!((row[2] - (3.0 * omega).sin()).abs() < 1e-12);
544 assert!((row[3] - (3.0 * omega).cos()).abs() < 1e-12);
545 assert_eq!(rel.parameter_count(), 17 * 8);
546 }
547
548 #[test]
549 fn all_encoding_types_construct() {
550 for encoding_type in [
551 PositionalEncodingType::Sinusoidal,
552 PositionalEncodingType::Learned,
553 PositionalEncodingType::Rotary,
554 PositionalEncodingType::None,
555 ] {
556 let pe = PositionalEncoding::<f64>::new(50, 32, encoding_type);
557 assert!(pe.is_ok(), "{encoding_type:?} failed to construct");
558 }
559 }
560}