use std::{
cell::RefCell,
fmt::Debug,
ops::{Add, AddAssign, Mul},
ptr::NonNull,
};
use crate::ad::blocklist::BlockList;
use crate::utils::errors::Result;
use crate::{ad::node::TapeNode, utils::errors::QSError};
pub trait TapeHolder:
Sized + Copy + Default + Add<Output = Self> + Mul<Output = Self> + AddAssign + Debug + Send + Sync
{
fn with_tape<R>(f: impl FnOnce(&mut Tape<Self>) -> R) -> R;
}
pub struct Tape<T = f64> {
pub storage: BlockList<TapeNode<T>>,
pub book: Vec<NonNull<TapeNode<T>>>,
pub mark: usize,
pub active: bool,
}
impl<T: TapeHolder> Tape<T> {
#[must_use]
pub fn new() -> Self {
Self {
storage: BlockList::with_default_cap(),
book: Vec::new(),
mark: 0,
active: false,
}
}
#[inline]
fn push(&mut self, mut n: TapeNode<T>) -> NonNull<TapeNode<T>> {
n.idx = self.book.len();
let ptr = self.storage.alloc(n);
self.book.push(ptr);
ptr
}
#[must_use]
pub const fn mark(&self) -> usize {
self.mark
}
#[inline]
fn index_of(&self, p: NonNull<TapeNode<T>>) -> Option<usize> {
let idx = unsafe { p.as_ref().idx };
(self.book.get(idx) == Some(&p)).then_some(idx)
}
#[inline]
pub fn new_leaf(&mut self) -> Option<NonNull<TapeNode<T>>> {
self.record(TapeNode::default())
}
#[inline]
pub fn record(&mut self, n: TapeNode<T>) -> Option<NonNull<TapeNode<T>>> {
self.active.then(|| self.push(n))
}
#[must_use]
pub fn node(&self, p: NonNull<TapeNode<T>>) -> Option<&TapeNode<T>> {
self.index_of(p).map(|i| unsafe { self.book[i].as_ref() })
}
pub fn mut_node(&mut self, p: NonNull<TapeNode<T>>) -> Option<&mut TapeNode<T>> {
self.index_of(p).map(|i| unsafe { self.book[i].as_mut() })
}
pub fn propagate_from(&mut self, root: NonNull<TapeNode<T>>) -> Result<()> {
let start = self
.index_of(root)
.ok_or(QSError::NodeNotIndexedInTapeErr)?;
for i in (0..=start).rev() {
unsafe { self.book[i].as_ref() }.propagate_into();
}
Ok(())
}
pub fn propagate_mark_to_start(&mut self) -> Result<()> {
let end = self.mark.saturating_sub(1);
for i in (0..=end).rev() {
unsafe { self.book[i].as_ref() }.propagate_into();
}
Ok(())
}
pub fn propagate_to_mark(&mut self) -> Result<()> {
let start = self.mark;
let end = self.book.len().saturating_sub(1);
if start > end {
return Ok(());
}
for i in (start..=end).rev() {
unsafe { self.book[i].as_ref() }.propagate_into();
}
Ok(())
}
pub fn reset_adjoints_inner(&self) {
for &ptr in &self.book {
unsafe { (*ptr.as_ptr()).adj = T::default() };
}
}
pub fn start_inner(&mut self) {
self.storage.reset();
self.book.clear();
self.mark = 0;
self.active = true;
}
pub fn start_recording_for() {
T::with_tape(Self::start_inner);
}
pub fn stop_recording_for() {
T::with_tape(|t| t.active = false);
}
pub fn reset_adjoints_for() {
T::with_tape(|t| t.reset_adjoints_inner());
}
pub fn rewind_to_init_for() {
T::with_tape(|t| {
t.storage.reset();
t.book.clear();
t.mark = 0;
});
}
}
impl<T: TapeHolder> Default for Tape<T> {
fn default() -> Self {
Self::new()
}
}
impl TapeHolder for f64 {
fn with_tape<R>(f: impl FnOnce(&mut Tape<Self>) -> R) -> R {
TAPE.with(|tc| {
let mut t = tc.borrow_mut();
f(&mut t)
})
}
}
thread_local! {
pub static TAPE: RefCell<Tape<f64>> = RefCell::new(Tape {
storage: BlockList::with_default_cap(),
book: Vec::new(),
mark: 0,
active: false,
});
}
impl Tape<f64> {
pub fn start_recording() {
TAPE.with(|tc| tc.borrow_mut().start_inner());
}
pub fn stop_recording() {
TAPE.with(|tc| tc.borrow_mut().active = false);
}
#[inline]
#[must_use]
pub fn is_active() -> bool {
TAPE.with(|tc| tc.borrow().active)
}
pub fn set_mark() {
TAPE.with(|tc| {
let len = tc.borrow().book.len();
tc.borrow_mut().mark = len;
});
}
pub fn rewind_to_mark() {
TAPE.with(|tc| {
let mut t = tc.borrow_mut();
let mark = t.mark;
t.book.truncate(mark);
t.storage.rewind_to(mark);
});
}
pub fn reset_mark() {
TAPE.with(|tc| {
tc.borrow_mut().mark = 0;
});
}
pub fn rewind_to_init() {
TAPE.with(|tc| {
let mut t = tc.borrow_mut();
t.storage.reset();
t.book.clear();
t.mark = 0;
});
}
pub fn reset_adjoints() {
TAPE.with(|tc| tc.borrow().reset_adjoints_inner());
}
}