use crate::{
shapes::{Dtype, Shape, Unit},
tensor::{Gradients, Storage, Tensor, UniqueId},
};
pub trait Optimizer<M, D: Storage<E>, E: Dtype> {
fn update(
&mut self,
module: &mut M,
gradients: &Gradients<E, D>,
) -> Result<(), OptimizerUpdateError<D::Err>>;
}
#[derive(Debug, Default)]
pub struct UnusedTensors {
pub ids: std::vec::Vec<UniqueId>,
}
impl UnusedTensors {
pub fn add<S: Shape, E: Unit, D: Storage<E>, T>(&mut self, t: &Tensor<S, E, D, T>) {
self.ids.push(t.id);
}
pub fn is_empty(&self) -> bool {
self.ids.is_empty()
}
pub fn clear(&mut self) {
self.ids.clear();
}
}
#[derive(Debug)]
pub enum OptimizerUpdateError<Err> {
UnusedParams(UnusedTensors),
DeviceError(Err),
}
impl<Err: std::fmt::Display> std::fmt::Display for OptimizerUpdateError<Err> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::UnusedParams(unused) => write!(f, "Unused tensors: {unused:?}"),
Self::DeviceError(err) => write!(f, "{err}"),
}
}
}
#[cfg(feature = "std")]
impl<Err: std::fmt::Debug + std::fmt::Display> std::error::Error for OptimizerUpdateError<Err> {}
#[allow(clippy::from_over_into)]
impl<Err> Into<Result<(), OptimizerUpdateError<Err>>> for UnusedTensors {
fn into(self) -> Result<(), OptimizerUpdateError<Err>> {
if self.is_empty() {
Ok(())
} else {
Err(OptimizerUpdateError::UnusedParams(self))
}
}
}