use std::sync::Arc;
use smallvec::SmallVec;
use static_assertions::assert_impl_all;
use crate::{Differentiable, Tensorial};
use super::{
Designation, Field, Function, Gradients, Misbinding, Structure, Value, ValueRef, Witness,
};
assert_impl_all!(Run<f64>: Send, Sync);
#[derive(Debug)]
pub(crate) enum Posture {
Complete,
Sliced { computed: Vec<bool> },
Observed { readable: Arc<Vec<bool>> },
Training { readable: Arc<Vec<bool>> },
}
impl Posture {
fn mask(&self) -> Option<&[bool]> {
match self {
Posture::Complete => None,
Posture::Sliced { computed } => Some(computed),
Posture::Observed { readable } | Posture::Training { readable, .. } => Some(readable),
}
}
fn differentiable(&self) -> bool {
!matches!(self, Posture::Observed { .. })
}
}
#[derive(Debug)]
pub struct Run<Data> {
structure: Structure<Data>,
field: Field<Data>,
posture: Posture,
}
impl<Data: Differentiable> Run<Data> {
pub(crate) fn new(
structure: Structure<Data>,
witness: Witness,
values: Vec<Data>,
posture: Posture,
) -> Self {
debug_assert_eq!(structure.len(), values.len());
if let Some(mask) = posture.mask() {
debug_assert_eq!(structure.len(), mask.len());
}
Self {
structure,
field: Field::new(witness, values),
posture,
}
}
fn computed(&self, index: usize) -> bool {
match self.posture.mask() {
Some(mask) => mask[index],
None => true,
}
}
fn locate(&self, value: impl ValueRef<Data>) -> usize {
let designation = value.designation();
let subject = match &designation {
Designation::Bound { .. } => "value",
Designation::Named(_) => "symbol",
};
match self.field.locate(designation) {
Ok(index) => index,
Err(Misbinding::ForeignOrigin) => {
panic!("{subject} belongs to a different network lineage")
}
Err(Misbinding::DivergentBranch) => {
panic!("{subject} belongs to a divergent fork of the network")
}
Err(Misbinding::OutOfCoverage) => {
panic!("{subject} was allocated after this run")
}
}
}
pub fn of(&self, value: impl ValueRef<Data>) -> &Data {
let index = self.locate(value);
assert!(
self.computed(index),
"value was not computed by this target-sliced run; add it to the targets"
);
&self.field.payloads()[index]
}
#[cfg(feature = "evcxr")]
pub(crate) fn field(&self) -> &Field<Data> {
&self.field
}
pub fn recorded_gradients<'value>(
&self,
pairs: impl IntoIterator<Item = (Value<'value, Data>, Value<'value, Data>)>,
) -> Gradients<Data>
where
Data: 'value,
{
let values = self.field.payloads();
let mut gradients: Vec<Data> = values.iter().map(|value| value.zero_like()).collect();
for (parameter, gradient) in pairs {
let index = self.locate(parameter);
assert!(
matches!(
self.structure.functions.get(index),
Some(Function::Parameter(_))
),
"recorded gradients pair each parameter with its gradient; the first \
value of a pair is not a parameter"
);
let payload = self.of(gradient).clone();
assert_eq!(
payload.shape(),
parameter.shape(),
"recorded gradient shape does not match its parameter's"
);
gradients[index] = payload;
}
Field::new(self.field.witness().clone(), gradients)
}
}
impl<Data: Tensorial> Run<Data> {
pub fn backward(&self, output: impl ValueRef<Data>) -> Gradients<Data> {
let output_index = self.locate(output);
let values = self.field.payloads();
assert!(
self.computed(output_index),
"value was not computed by this target-sliced run; add it to the targets"
);
assert!(
self.posture.differentiable(),
"this run came from a forward-only plan, whose liveness pass freed \
the buffers backward reads; compile with `engine_backward` to differentiate"
);
assert_eq!(
values[output_index].shape().rank(),
0,
"backward requires a scalar target; reduce it with `sum` first"
);
assert_eq!(
self.structure
.shapes
.get(output_index)
.expect("shapes cover the run")
.rank(),
0,
"backward requires a scalar target; reduce it with `sum` first"
);
let mut gradients: Vec<Data> = values.iter().map(|value| value.zero_like()).collect();
gradients[output_index] = values[output_index].one_like();
let mut ancestors = vec![false; output_index + 1];
ancestors[output_index] = true;
for index in (0..=output_index).rev() {
if !ancestors[index] {
continue;
}
let function = self
.structure
.functions
.get(index)
.expect("snapshot cannot shrink");
let links = self
.structure
.operands
.get(index)
.expect("snapshot cannot shrink")
.as_slice();
let operands: SmallVec<[&Data; 2]> =
links.iter().map(|link| &values[link.index()]).collect();
let gradient = gradients[index].clone();
let cotangents = function.backward(&operands, &values[index], &gradient);
debug_assert_eq!(cotangents.len(), links.len());
for (&link, cotangent) in links.iter().zip(cotangents) {
if let Some(contribution) = cotangent {
let slot = link.index();
ancestors[slot] = true;
gradients[slot] = gradients[slot].clone() + contribution;
}
}
}
Field::new(self.field.witness().clone(), gradients)
}
}
#[cfg(test)]
#[path = "tests/run_tests.rs"]
mod tests;