Skip to main content

laddu_kernel/ir/
wrappers.rs

1use super::*;
2
3fn validate_root_bounds(values: &[KernelValue], root: KernelValueId) -> Result<(), KernelIrError> {
4    if root.index() >= values.len() {
5        return Err(KernelIrError::RootOutOfBounds {
6            root: root.index(),
7            len: values.len(),
8        });
9    }
10    Ok(())
11}
12
13fn validate_scalar_root(
14    values: &[KernelValue],
15    root: KernelValueId,
16    operation: &'static str,
17    message: &'static str,
18) -> Result<(), KernelIrError> {
19    if !values[root.index()].kind.is_scalar() {
20        return Err(KernelInstruction::shape_error(
21            root.index(),
22            operation,
23            message,
24        ));
25    }
26    Ok(())
27}
28
29fn validate_cache_outputs(
30    values: &[KernelValue],
31    outputs: &[KernelValueId],
32) -> Result<(), KernelIrError> {
33    for output in outputs {
34        if output.index() >= values.len() {
35            return Err(KernelIrError::CacheOutputOutOfBounds {
36                output: output.index(),
37                len: values.len(),
38            });
39        }
40    }
41    Ok(())
42}
43
44fn validate_gradient_outputs(
45    values: &[KernelValue],
46    outputs: &[KernelValueId],
47) -> Result<(), KernelIrError> {
48    for output in outputs {
49        let Some(value) = values.get(output.index()) else {
50            return Err(KernelIrError::GradientOutOfBounds {
51                output: output.index(),
52                len: values.len(),
53            });
54        };
55        if value.kind != KernelValueKind::Real {
56            return Err(KernelIrError::GradientKindMismatch {
57                output: output.index(),
58                actual: value.kind,
59            });
60        }
61    }
62    Ok(())
63}
64
65impl ScalarKernelIr {
66    /// Validates values and constructs a scalar kernel rooted at `root`.
67    ///
68    /// # Errors
69    ///
70    /// Returns [`KernelIrError`] when the value list is empty, `root` or an
71    /// operand is out of bounds, values are not topologically ordered, a
72    /// value's kind or class is inconsistent with its instruction, or the
73    /// root is not scalar.
74    pub fn new(values: Vec<KernelValue>, root: KernelValueId) -> Result<Self, KernelIrError> {
75        let ir = Self { values, root };
76        ir.validate()?;
77        Ok(ir)
78    }
79
80    /// Revalidates ordering, types, classes, and the scalar root.
81    ///
82    /// # Errors
83    ///
84    /// Returns [`KernelIrError`] when the IR is empty, its root or an operand
85    /// is out of bounds, its values are not topologically ordered, or a
86    /// value's kind, class, or shape is inconsistent with its instruction.
87    pub fn validate(&self) -> Result<(), KernelIrError> {
88        if self.values.is_empty() {
89            return validate_graph(&self.values);
90        }
91        validate_root_bounds(&self.values, self.root)?;
92        validate_graph(&self.values)?;
93        validate_scalar_root(
94            &self.values,
95            self.root,
96            "kernel root",
97            "root must be scalar",
98        )
99    }
100
101    /// Returns all IR values in topological order.
102    pub fn values(&self) -> &[KernelValue] {
103        &self.values
104    }
105
106    /// Returns the scalar output identifier.
107    pub fn root(&self) -> KernelValueId {
108        self.root
109    }
110}
111
112impl CacheKernelIr {
113    /// Validates values and constructs a cache kernel with the given outputs.
114    ///
115    /// # Errors
116    ///
117    /// Returns [`KernelIrError`] when `outputs` is empty, an output or operand
118    /// is out of bounds, values are not topologically ordered, or a value's
119    /// kind, class, or shape is inconsistent with its instruction.
120    pub fn new(
121        values: Vec<KernelValue>,
122        outputs: Vec<KernelValueId>,
123    ) -> Result<Self, KernelIrError> {
124        if outputs.is_empty() {
125            return Err(KernelIrError::EmptyCacheOutputs);
126        }
127        validate_graph(&values)?;
128        validate_cache_outputs(&values, &outputs)?;
129        Ok(Self { values, outputs })
130    }
131
132    /// Returns all IR values in topological order.
133    pub fn values(&self) -> &[KernelValue] {
134        &self.values
135    }
136
137    /// Returns cache output identifiers in storage order.
138    pub fn outputs(&self) -> &[KernelValueId] {
139        &self.outputs
140    }
141}
142
143impl GradientKernelIr {
144    /// Validates and constructs a gradient kernel.
145    ///
146    /// # Errors
147    ///
148    /// Returns [`KernelIrError`] when the primal IR is invalid, the primal
149    /// root is not scalar, a gradient output is out of bounds, or a gradient
150    /// output is not real-valued.
151    pub fn new(
152        values: Vec<KernelValue>,
153        primal_root: KernelValueId,
154        outputs: Vec<KernelValueId>,
155        component: OutputComponent,
156    ) -> Result<Self, KernelIrError> {
157        let ir = Self {
158            values,
159            primal_root,
160            outputs,
161            component,
162        };
163        ir.validate()?;
164        Ok(ir)
165    }
166
167    /// Revalidates the primal root and real gradient outputs.
168    ///
169    /// # Errors
170    ///
171    /// Returns [`KernelIrError`] when the primal IR is invalid, the primal
172    /// root is not scalar, a gradient output is out of bounds, or a gradient
173    /// output is not real-valued.
174    pub fn validate(&self) -> Result<(), KernelIrError> {
175        if self.values.is_empty() {
176            return validate_graph(&self.values);
177        }
178        validate_root_bounds(&self.values, self.primal_root)?;
179        validate_graph(&self.values)?;
180        validate_scalar_root(
181            &self.values,
182            self.primal_root,
183            "gradient primal root",
184            "primal root must be scalar",
185        )?;
186        validate_gradient_outputs(&self.values, &self.outputs)
187    }
188
189    /// Returns all primal and derivative IR values in topological order.
190    pub fn values(&self) -> &[KernelValue] {
191        &self.values
192    }
193
194    /// Returns the primal scalar output identifier.
195    pub fn primal_root(&self) -> KernelValueId {
196        self.primal_root
197    }
198
199    /// Returns derivative output identifiers.
200    pub fn outputs(&self) -> &[KernelValueId] {
201        &self.outputs
202    }
203
204    /// Returns the differentiated component of the complex primal.
205    pub fn component(&self) -> OutputComponent {
206        self.component
207    }
208}