1use crate::{Graph, NodeId, Op, Shape};
8
9impl Graph {
10 pub fn input(&mut self, name: impl Into<String>, shape: Shape) -> NodeId {
12 let name: String = name.into();
13 self.push(Op::Input { name: name.clone() }, vec![], shape, Some(name))
14 }
15
16 pub fn param(&mut self, name: impl Into<String>, shape: Shape) -> NodeId {
18 let name: String = name.into();
19 self.push(Op::Param { name: name.clone() }, vec![], shape, Some(name))
20 }
21
22 pub fn add_node(&mut self, op: Op, inputs: Vec<NodeId>, shape: Shape) -> NodeId {
24 self.push(op, inputs, shape, None)
25 }
26
27 pub(crate) fn const_f32_tensor(&mut self, data: Vec<f32>, dims: &[usize]) -> NodeId {
32 debug_assert_eq!(
33 data.len(),
34 dims.iter().product::<usize>(),
35 "const_f32_tensor: data length does not match dims"
36 );
37 let mut bytes = Vec::with_capacity(data.len() * 4);
38 for v in &data {
39 bytes.extend_from_slice(&v.to_le_bytes());
40 }
41 self.add_node(
42 Op::Constant { data: bytes },
43 vec![],
44 Shape::new(dims, crate::DType::F32),
45 )
46 }
47
48 pub fn custom_op(
59 &mut self,
60 name: impl Into<String>,
61 attrs: Vec<u8>,
62 inputs: Vec<NodeId>,
63 ) -> NodeId {
64 let name: String = name.into();
65 let ext = crate::lookup_op(&name)
66 .unwrap_or_else(|| panic!("custom_op: '{name}' is not registered in the op registry"));
67 assert_eq!(
68 ext.num_inputs(),
69 inputs.len(),
70 "custom_op '{name}': registered op expects {} inputs, got {}",
71 ext.num_inputs(),
72 inputs.len(),
73 );
74 let in_shapes: Vec<&Shape> = inputs.iter().map(|id| self.shape(*id)).collect();
75 let out_shape = ext.infer_shape(&in_shapes, &attrs);
76 let num_inputs = ext.num_inputs() as u32;
77 self.push(
78 Op::Custom {
79 name,
80 num_inputs,
81 attrs,
82 },
83 inputs,
84 out_shape,
85 None,
86 )
87 }
88
89 pub fn custom_op_packed(
113 &mut self,
114 name: impl Into<String>,
115 attrs: Vec<u8>,
116 inputs: Vec<NodeId>,
117 out_shape: Shape,
118 ) -> NodeId {
119 let name: String = name.into();
120 let ext = crate::lookup_op(&name).unwrap_or_else(|| {
121 panic!("custom_op_packed: '{name}' is not registered in the op registry")
122 });
123 assert_eq!(
124 ext.num_inputs(),
125 inputs.len(),
126 "custom_op_packed '{name}': registered op expects {} inputs, got {}",
127 ext.num_inputs(),
128 inputs.len(),
129 );
130 let num_inputs = ext.num_inputs() as u32;
131 self.push(
132 Op::Custom {
133 name,
134 num_inputs,
135 attrs,
136 },
137 inputs,
138 out_shape,
139 None,
140 )
141 }
142
143 pub fn try_custom_op(
148 &mut self,
149 name: impl Into<String>,
150 attrs: Vec<u8>,
151 inputs: Vec<NodeId>,
152 ) -> Result<NodeId, crate::CustomOpError> {
153 let name: String = name.into();
154 let ext = crate::lookup_op(&name)
155 .ok_or_else(|| crate::CustomOpError::NotRegistered(name.clone()))?;
156 if ext.num_inputs() != inputs.len() {
157 return Err(crate::CustomOpError::ArityMismatch {
158 name,
159 expected: ext.num_inputs(),
160 got: inputs.len(),
161 });
162 }
163 let in_shapes: Vec<&Shape> = inputs.iter().map(|id| self.shape(*id)).collect();
164 let out_shape = ext.infer_shape(&in_shapes, &attrs);
165 let num_inputs = ext.num_inputs() as u32;
166 Ok(self.push(
167 Op::Custom {
168 name,
169 num_inputs,
170 attrs,
171 },
172 inputs,
173 out_shape,
174 None,
175 ))
176 }
177
178 pub fn fft(&mut self, x: NodeId, inverse: bool) -> NodeId {
187 self.fft_norm(x, inverse, crate::fft::FftNorm::Backward)
188 }
189
190 pub fn fft_norm(&mut self, x: NodeId, inverse: bool, norm: crate::fft::FftNorm) -> NodeId {
192 let s = self.shape(x).clone();
193 crate::fft::fft_meta(&s);
194 self.push(Op::Fft { inverse, norm }, vec![x], s, None)
195 }
196
197 pub fn fft_butterfly_stage(
199 &mut self,
200 state: NodeId,
201 gate: NodeId,
202 rev: NodeId,
203 tw_re: NodeId,
204 tw_im: NodeId,
205 stage: u32,
206 n_fft: u32,
207 ) -> NodeId {
208 let s = self.shape(state).clone();
209 self.push(
210 Op::FftButterflyStage { stage, n_fft },
211 vec![state, gate, rev, tw_re, tw_im],
212 s,
213 None,
214 )
215 }
216
217 pub fn fft_axis(&mut self, x: NodeId, axis: usize, inverse: bool) -> NodeId {
222 use crate::infer::GraphExt as _;
223 let rank = self.shape(x).rank();
224 assert!(
225 axis < rank,
226 "fft_axis: axis {axis} out of range for rank-{rank} tensor"
227 );
228 let last = rank - 1;
229 if axis == last {
230 return self.fft(x, inverse);
231 }
232 let mut perm: Vec<usize> = (0..rank).collect();
233 perm.swap(axis, last);
234
235 let x_t = self.transpose_(x, perm.clone());
236 let y_t = self.fft(x_t, inverse);
237 self.transpose_(y_t, perm)
238 }
239
240 pub fn fftn(&mut self, x: NodeId, axes: &[usize], inverse: bool) -> NodeId {
247 let rank = self.shape(x).rank();
248 let axes = crate::fft::normalize_fftn_axes(rank, axes);
249 if axes.is_empty() {
250 return x;
251 }
252 if axes.len() > 1 && !self.shape(x).dtype().is_complex() {
253 panic!(
254 "fftn: multi-axis FFT on {:?} requires DType::C64; \
255 the F32/F64 2N real-block layout supports only one complex axis — \
256 call fft_axis for a single transform",
257 self.shape(x).dtype()
258 );
259 }
260 let mut y = x;
261 for axis in axes {
262 y = self.fft_axis(y, axis, inverse);
263 }
264 y
265 }
266
267 pub fn ifftn(&mut self, x: NodeId, axes: &[usize]) -> NodeId {
269 self.fftn(x, axes, true)
270 }
271}