surrealml_core/execution/
compute.rs1use std::collections::HashMap;
3
4use ndarray::ArrayD;
5use ort::session::Session;
6use ort::value::ValueType;
7
8use crate::errors::error::{SurrealError, SurrealErrorStatus};
9use crate::execution::session::get_session;
10use crate::safe_eject;
11use crate::storage::surml_file::SurMlFile;
12
13pub struct ModelComputation<'a> {
20 pub surml_file: &'a mut SurMlFile,
21}
22
23impl ModelComputation<'_> {
24 pub fn input_tensor_from_key_bindings(
34 &self,
35 input_values: HashMap<String, f32>,
36 ) -> Result<ArrayD<f32>, SurrealError> {
37 let buffer = self.input_vector_from_key_bindings(input_values)?;
38 Ok(ndarray::arr1::<f32>(&buffer).into_dyn())
39 }
40
41 fn process_input_dims(session_ref: &Session) -> Result<Vec<usize>, SurrealError> {
49 let inputs = session_ref.inputs();
51 if inputs.is_empty() {
52 return Err(SurrealError {
53 message: "No inputs found in session".into(),
54 status: SurrealErrorStatus::Unknown,
55 });
56 }
57
58 let dtype = inputs[0].dtype();
60
61 let unwrapped_dims = match dtype {
63 ValueType::Tensor {
64 ty: _,
65 shape,
66 dimension_symbols: _,
67 } => shape,
68 _ => {
69 return Err(SurrealError {
70 message: "input dims not found".into(),
71 status: SurrealErrorStatus::Unknown,
72 });
73 }
74 };
75
76 let mut dims_cache = Vec::new();
77 for dim in unwrapped_dims.iter() {
78 if dim < &0 {
79 dims_cache.push((dim * -1) as usize);
80 } else {
81 dims_cache.push(*dim as usize);
82 }
83 }
84 Ok(dims_cache)
85 }
86
87 pub fn input_vector_from_key_bindings(
97 &self,
98 mut input_values: HashMap<String, f32>,
99 ) -> Result<Vec<f32>, SurrealError> {
100 let mut buffer = Vec::with_capacity(self.surml_file.header.keys.store.len());
101
102 for key in &self.surml_file.header.keys.store {
103 let value = match input_values.get_mut(key) {
104 Some(value) => value,
105 None => {
106 return Err(SurrealError::new(
107 format!(
108 "src/execution/compute.rs 67: Key {} not found in input values",
109 key
110 ),
111 SurrealErrorStatus::NotFound,
112 ));
113 }
114 };
115 buffer.push(std::mem::take(value));
116 }
117
118 Ok(buffer)
119 }
120
121 pub fn raw_compute(
129 &self,
130 tensor: ArrayD<f32>,
131 _dims: Option<(i32, i32)>,
132 ) -> Result<Vec<f32>, SurrealError> {
133 let mut session = get_session(self.surml_file.model.clone())?;
134 let dims_cache = ModelComputation::process_input_dims(&session)?;
135 let tensor = if dims_cache.is_empty() {
136 tensor
138 } else {
139 match tensor.into_shape_with_order(dims_cache) {
140 Ok(tensor) => tensor,
141 Err(_) => {
142 return Err(SurrealError::new(
143 "Failed to reshape tensor to input dimensions".to_string(),
144 SurrealErrorStatus::Unknown,
145 ));
146 }
147 }
148 };
149 let tensor = match ort::value::Tensor::from_array(tensor) {
150 Ok(tensor) => tensor,
151 Err(_) => {
152 return Err(SurrealError::new(
153 "Failed to convert tensor to ort tensor".to_string(),
154 SurrealErrorStatus::Unknown,
155 ));
156 }
157 };
158 let x = ort::inputs![tensor];
159 let outputs = safe_eject!(session.run(x), SurrealErrorStatus::Unknown);
160
161 let mut buffer: Vec<f32> = Vec::new();
162
163 match outputs[0].try_extract_tensor::<f32>() {
165 Ok((_shape, data)) => {
166 for i in data.iter() {
167 buffer.push(*i);
168 }
169 }
170 Err(_) => {
171 let (_shape, data) = safe_eject!(
172 outputs[0].try_extract_tensor::<i64>(),
173 SurrealErrorStatus::Unknown
174 );
175 for i in data.iter() {
176 buffer.push(*i as f32);
177 }
178 }
179 };
180 Ok(buffer)
181 }
182
183 pub fn buffered_compute(
197 &self,
198 input_values: &mut HashMap<String, f32>,
199 ) -> Result<Vec<f32>, SurrealError> {
200 for (key, value) in &mut *input_values {
202 let value_ref = *value;
203 if let Some(normaliser) = self.surml_file.header.get_normaliser(&key.to_string())? {
204 *value = normaliser.normalise(value_ref);
205 }
206 }
207 let tensor = self.input_tensor_from_key_bindings(input_values.clone())?;
208 let output = self.raw_compute(tensor, None)?;
209
210 if self.surml_file.header.output.normaliser.is_none() {
212 return Ok(output);
213 }
214
215 let output_normaliser = match self.surml_file.header.output.normaliser.as_ref() {
217 Some(normaliser) => normaliser,
218 None => {
219 return Err(SurrealError::new(
220 String::from(
221 "No normaliser present for output which shouldn't happen as passed initial check for",
222 )
223 .to_string(),
224 SurrealErrorStatus::Unknown,
225 ));
226 }
227 };
228 let mut buffer = Vec::with_capacity(output.len());
229
230 for value in output {
231 buffer.push(output_normaliser.inverse_normalise(value));
232 }
233 Ok(buffer)
234 }
235}
236
237#[cfg(test)]
238mod tests {
239
240 #[cfg(any(
241 feature = "sklearn-tests",
242 feature = "onnx-tests",
243 feature = "torch-tests",
244 feature = "tensorflow-tests"
245 ))]
246 use super::*;
247 #[cfg(any(
248 feature = "sklearn-tests",
249 feature = "onnx-tests",
250 feature = "torch-tests",
251 feature = "tensorflow-tests"
252 ))]
253 #[cfg(feature = "sklearn-tests")]
254 #[test]
255 fn test_raw_compute_linear_sklearn() {
256 let mut file = SurMlFile::from_file("./model_stash/sklearn/surml/linear.surml").unwrap();
257 let model_computation = ModelComputation {
258 surml_file: &mut file,
259 };
260
261 let mut input_values = HashMap::new();
262 input_values.insert(String::from("squarefoot"), 1000.0);
263 input_values.insert(String::from("num_floors"), 2.0);
264
265 let raw_input = model_computation.input_tensor_from_key_bindings(input_values).unwrap();
266
267 let output = model_computation.raw_compute(raw_input, Some((1, 2))).unwrap();
268 assert_eq!(output.len(), 1);
269 assert_eq!(output[0], 985.57745);
270 }
271
272 #[cfg(feature = "sklearn-tests")]
273 #[test]
274 fn test_buffered_compute_linear_sklearn() {
275 let mut file = SurMlFile::from_file("./model_stash/sklearn/surml/linear.surml").unwrap();
276 let model_computation = ModelComputation {
277 surml_file: &mut file,
278 };
279
280 let mut input_values = HashMap::new();
281 input_values.insert(String::from("squarefoot"), 1000.0);
282 input_values.insert(String::from("num_floors"), 2.0);
283
284 let output = model_computation.buffered_compute(&mut input_values).unwrap();
285 assert_eq!(output.len(), 1);
286 }
287
288 #[cfg(feature = "onnx-tests")]
289 #[test]
290 fn test_raw_compute_linear_onnx() {
291 let mut file = SurMlFile::from_file("./model_stash/onnx/surml/linear.surml").unwrap();
292 let model_computation = ModelComputation {
293 surml_file: &mut file,
294 };
295
296 let mut input_values = HashMap::new();
297 input_values.insert(String::from("squarefoot"), 1000.0);
298 input_values.insert(String::from("num_floors"), 2.0);
299
300 let raw_input = model_computation.input_tensor_from_key_bindings(input_values).unwrap();
301
302 let output = model_computation.raw_compute(raw_input, Some((1, 2))).unwrap();
303 assert_eq!(output.len(), 1);
304 assert_eq!(output[0], 985.57745);
305 }
306
307 #[cfg(feature = "onnx-tests")]
308 #[test]
309 fn test_buffered_compute_linear_onnx() {
310 let mut file = SurMlFile::from_file("./model_stash/onnx/surml/linear.surml").unwrap();
311 let model_computation = ModelComputation {
312 surml_file: &mut file,
313 };
314
315 let mut input_values = HashMap::new();
316 input_values.insert(String::from("squarefoot"), 1000.0);
317 input_values.insert(String::from("num_floors"), 2.0);
318
319 let output = model_computation.buffered_compute(&mut input_values).unwrap();
320 assert_eq!(output.len(), 1);
321 }
322
323 #[cfg(feature = "torch-tests")]
324 #[test]
325 fn test_raw_compute_linear_torch() {
326 let mut file = SurMlFile::from_file("./model_stash/torch/surml/linear.surml").unwrap();
327 let model_computation = ModelComputation {
328 surml_file: &mut file,
329 };
330
331 let mut input_values = HashMap::new();
332 input_values.insert(String::from("squarefoot"), 1000.0);
333 input_values.insert(String::from("num_floors"), 2.0);
334
335 let raw_input = model_computation.input_tensor_from_key_bindings(input_values).unwrap();
336
337 let output = model_computation.raw_compute(raw_input, None).unwrap();
338 assert_eq!(output.len(), 1);
339 }
340
341 #[cfg(feature = "torch-tests")]
342 #[test]
343 fn test_buffered_compute_linear_torch() {
344 let mut file = SurMlFile::from_file("./model_stash/torch/surml/linear.surml").unwrap();
345 let model_computation = ModelComputation {
346 surml_file: &mut file,
347 };
348
349 let mut input_values = HashMap::new();
350 input_values.insert(String::from("squarefoot"), 1000.0);
351 input_values.insert(String::from("num_floors"), 2.0);
352
353 let output = model_computation.buffered_compute(&mut input_values).unwrap();
354 assert_eq!(output.len(), 1);
355 }
356
357 #[cfg(feature = "tensorflow-tests")]
358 #[test]
359 fn test_raw_compute_linear_tensorflow() {
360 let mut file = SurMlFile::from_file("./model_stash/tensorflow/surml/linear.surml").unwrap();
361 let model_computation = ModelComputation {
362 surml_file: &mut file,
363 };
364
365 let mut input_values = HashMap::new();
366 input_values.insert(String::from("squarefoot"), 1000.0);
367 input_values.insert(String::from("num_floors"), 2.0);
368
369 let raw_input = model_computation.input_tensor_from_key_bindings(input_values).unwrap();
370
371 let output = model_computation.raw_compute(raw_input, None).unwrap();
372 assert_eq!(output.len(), 1);
373 }
374
375 #[cfg(feature = "tensorflow-tests")]
376 #[test]
377 fn test_buffered_compute_linear_tensorflow() {
378 let mut file = SurMlFile::from_file("./model_stash/tensorflow/surml/linear.surml").unwrap();
379 let model_computation = ModelComputation {
380 surml_file: &mut file,
381 };
382
383 let mut input_values = HashMap::new();
384 input_values.insert(String::from("squarefoot"), 1000.0);
385 input_values.insert(String::from("num_floors"), 2.0);
386
387 let output = model_computation.buffered_compute(&mut input_values).unwrap();
388 assert_eq!(output.len(), 1);
389 }
390}