1use std::collections::{HashMap, HashSet};
2use std::sync::Arc;
3
4use crate::{ADKey, ADRuleError, ADRuleKind, ADRuleResult, DiffPassId, Primitive};
5use computegraph::resolve::{ResolvedView, ValueDef};
6use computegraph::{GraphOperation, LocalValueId, OperationKey, ValueKey};
7
8use crate::split_builder::SplitGraphBuilder;
9use crate::LinearizedGraph;
10
11pub fn linearize<Op: Primitive>(
31 view: &ResolvedView<Op>,
32 outputs: &[ValueKey<Op>],
33 wrt: &[Op::InputKey],
34 pass: DiffPassId,
35 ctx: &mut Op::ADContext,
36 aliases: &HashMap<Op::InputKey, ValueKey<Op>>,
37) -> ADRuleResult<LinearizedGraph<Op>>
38where
39 Op::InputKey: ADKey,
40{
41 let mut builder = SplitGraphBuilder::<Op>::new();
42 let topo_keys = topological_order(view, outputs, aliases);
43 let mut tangent_env: HashMap<ValueKey<Op>, Option<LocalValueId>> = HashMap::new();
44 let mut processed_ops = HashSet::new();
45
46 let mut tangent_inputs = Vec::with_capacity(wrt.len());
47 for wrt_key in wrt {
48 let tangent_key = wrt_key.tangent_of(pass);
49 let (unified_id, linear_id) =
50 builder.add_linear_input(tangent_key, Some(ValueKey::Input(wrt_key.clone())));
51 tangent_env.insert(ValueKey::Input(wrt_key.clone()), Some(unified_id));
52 tangent_inputs.push((wrt_key.clone(), linear_id));
53 }
54
55 for key in topo_keys {
56 if tangent_env.contains_key(&key) {
57 continue;
58 }
59
60 let val_def = match view.resolve_value(&key) {
61 Some(val_def) => val_def,
62 None => continue,
63 };
64
65 match val_def {
66 ValueDef::Input { key: ref input_key } => {
67 if let Some(aliased_key) = aliases.get(input_key) {
68 let aliased_tangent = tangent_env.get(aliased_key).copied().flatten();
69 tangent_env.insert(key, aliased_tangent);
70 } else {
71 tangent_env.insert(key, None);
72 }
73 }
74 ValueDef::Produced {
75 operation,
76 input_keys,
77 role,
78 ..
79 } => {
80 let global_op_key =
81 OperationKey::new(operation.clone(), input_keys.clone(), role.clone());
82 if !processed_ops.insert(global_op_key.clone()) {
83 continue;
84 }
85
86 let tangent_in: Vec<Option<LocalValueId>> = input_keys
87 .iter()
88 .map(|input_key| tangent_env.get(input_key).copied().flatten())
89 .collect();
90 let output_keys = output_keys(&global_op_key, operation.output_count());
91
92 if tangent_in.iter().all(Option::is_none) {
93 for output_key in output_keys {
94 tangent_env.insert(output_key, None);
95 }
96 continue;
97 }
98
99 let tangent_out = operation.jvp_rule(
100 &mut builder,
101 &input_keys,
102 &output_keys,
103 &tangent_in,
104 ctx,
105 )?;
106 if let Some(err) = builder.take_error() {
107 return Err(err);
108 }
109 if tangent_out.len() != output_keys.len() {
110 return Err(ADRuleError::invalid_input(
111 format!("{:?}", operation),
112 ADRuleKind::Jvp,
113 format!(
114 "rule returned {} tangents for {} outputs",
115 tangent_out.len(),
116 output_keys.len()
117 ),
118 ));
119 }
120
121 for (output_key, tangent_output) in output_keys.iter().zip(tangent_out) {
122 if let Some(tangent_id) = tangent_output {
123 builder.set_linear_primal(tangent_id, output_key.clone())?;
124 }
125 tangent_env.insert(output_key.clone(), tangent_output);
126 }
127 }
128 }
129 }
130
131 let tangent_outputs: Vec<Option<LocalValueId>> = outputs
132 .iter()
133 .map(|key| match tangent_env.get(key).copied().flatten() {
134 Some(unified_id) => builder
135 .linear_local_id(unified_id)
136 .ok_or_else(|| {
137 ADRuleError::invalid_input(
138 "tidu::linearize",
139 ADRuleKind::Jvp,
140 "JVP rule returned a residual value as an active requested tangent output",
141 )
142 })
143 .map(Some),
144 None => Ok(None),
145 })
146 .collect::<ADRuleResult<Vec<_>>>()?;
147 let active_outputs: Vec<LocalValueId> = tangent_outputs.iter().filter_map(|id| *id).collect();
148 let (linear, residual, linear_primals) = builder.finish(active_outputs);
149
150 Ok(LinearizedGraph::from_parts(
151 linear,
152 residual,
153 tangent_inputs,
154 tangent_outputs,
155 linear_primals,
156 ))
157}
158
159fn output_keys<Op: GraphOperation>(
160 op_key: &OperationKey<Op>,
161 output_count: usize,
162) -> Vec<ValueKey<Op>> {
163 let op_key = Arc::new(op_key.clone());
164 (0..output_count)
165 .map(|output_slot| ValueKey::Derived {
166 operation: Arc::clone(&op_key),
167 output_slot: output_slot as u8,
168 })
169 .collect()
170}
171
172fn topological_order<Op: GraphOperation>(
173 view: &ResolvedView<Op>,
174 outputs: &[ValueKey<Op>],
175 aliases: &HashMap<Op::InputKey, ValueKey<Op>>,
176) -> Vec<ValueKey<Op>> {
177 fn visit<Op: GraphOperation>(
178 key: &ValueKey<Op>,
179 view: &ResolvedView<Op>,
180 aliases: &HashMap<Op::InputKey, ValueKey<Op>>,
181 visited: &mut HashSet<ValueKey<Op>>,
182 order: &mut Vec<ValueKey<Op>>,
183 ) {
184 if !visited.insert(key.clone()) {
185 return;
186 }
187
188 match view.resolve_value(key) {
189 Some(ValueDef::Produced { input_keys, .. }) => {
190 for input_key in input_keys {
191 visit(&input_key, view, aliases, visited, order);
192 }
193 }
194 Some(ValueDef::Input { key: input_key }) => {
195 if let Some(aliased_key) = aliases.get(&input_key) {
196 visit(aliased_key, view, aliases, visited, order);
197 }
198 }
199 None => {}
200 }
201
202 order.push(key.clone());
203 }
204
205 let mut visited = HashSet::new();
206 let mut order = Vec::new();
207 for output_key in outputs {
208 visit(output_key, view, aliases, &mut visited, &mut order);
209 }
210 order
211}