tract_proxy/
lib.rs

1use std::ffi::{CStr, CString};
2use std::path::Path;
3use std::ptr::{null, null_mut};
4
5use tract_api::*;
6use tract_proxy_sys as sys;
7
8use anyhow::{Context, Result};
9use ndarray::*;
10
11macro_rules! check {
12    ($expr:expr) => {
13        unsafe {
14            if $expr == sys::TRACT_RESULT_TRACT_RESULT_KO {
15                let buf = CStr::from_ptr(sys::tract_get_last_error());
16                Err(anyhow::anyhow!(buf.to_string_lossy().to_string()))
17            } else {
18                Ok(())
19            }
20        }
21    };
22}
23
24macro_rules! wrapper {
25    ($new_type:ident, $c_type:ident, $dest:ident $(, $typ:ty )*) => {
26        #[derive(Debug, Clone)]
27        pub struct $new_type(*mut sys::$c_type $(, $typ)*);
28
29        impl Drop for $new_type {
30            fn drop(&mut self) {
31                unsafe {
32                    sys::$dest(&mut self.0);
33                }
34            }
35        }
36    };
37}
38
39pub fn nnef() -> Result<Nnef> {
40    let mut nnef = null_mut();
41    check!(sys::tract_nnef_create(&mut nnef))?;
42    Ok(Nnef(nnef))
43}
44
45pub fn onnx() -> Result<Onnx> {
46    let mut onnx = null_mut();
47    check!(sys::tract_onnx_create(&mut onnx))?;
48    Ok(Onnx(onnx))
49}
50
51pub fn version() -> &'static str {
52    unsafe { CStr::from_ptr(sys::tract_version()).to_str().unwrap() }
53}
54
55wrapper!(Nnef, TractNnef, tract_nnef_destroy);
56impl NnefInterface for Nnef {
57    type Model = Model;
58    fn model_for_path(&self, path: impl AsRef<Path>) -> Result<Model> {
59        let path = path.as_ref();
60        let path = CString::new(
61            path.to_str().with_context(|| format!("Failed to re-encode {path:?} to uff-8"))?,
62        )?;
63        let mut model = null_mut();
64        check!(sys::tract_nnef_model_for_path(self.0, path.as_ptr(), &mut model))?;
65        Ok(Model(model))
66    }
67
68    fn transform_model(&self, model: &mut Self::Model, transform_spec: &str) -> Result<()> {
69        let t = CString::new(transform_spec)?;
70        check!(sys::tract_nnef_transform_model(self.0, model.0, t.as_ptr()))
71    }
72
73    fn enable_tract_core(&mut self) -> Result<()> {
74        check!(sys::tract_nnef_enable_tract_core(self.0))
75    }
76
77    fn enable_tract_extra(&mut self) -> Result<()> {
78        check!(sys::tract_nnef_enable_tract_extra(self.0))
79    }
80
81    fn enable_tract_transformers(&mut self) -> Result<()> {
82        check!(sys::tract_nnef_enable_tract_transformers(self.0))
83    }
84
85    fn enable_onnx(&mut self) -> Result<()> {
86        check!(sys::tract_nnef_enable_onnx(self.0))
87    }
88
89    fn enable_pulse(&mut self) -> Result<()> {
90        check!(sys::tract_nnef_enable_pulse(self.0))
91    }
92
93    fn enable_extended_identifier_syntax(&mut self) -> Result<()> {
94        check!(sys::tract_nnef_enable_extended_identifier_syntax(self.0))
95    }
96
97    fn write_model_to_dir(&self, path: impl AsRef<Path>, model: &Model) -> Result<()> {
98        let path = path.as_ref();
99        let path = CString::new(
100            path.to_str().with_context(|| format!("Failed to re-encode {path:?} to uff-8"))?,
101        )?;
102        check!(sys::tract_nnef_write_model_to_dir(self.0, path.as_ptr(), model.0))?;
103        Ok(())
104    }
105
106    fn write_model_to_tar(&self, path: impl AsRef<Path>, model: &Model) -> Result<()> {
107        let path = path.as_ref();
108        let path = CString::new(
109            path.to_str().with_context(|| format!("Failed to re-encode {path:?} to uff-8"))?,
110        )?;
111        check!(sys::tract_nnef_write_model_to_tar(self.0, path.as_ptr(), model.0))?;
112        Ok(())
113    }
114
115    fn write_model_to_tar_gz(&self, path: impl AsRef<Path>, model: &Model) -> Result<()> {
116        let path = path.as_ref();
117        let path = CString::new(
118            path.to_str().with_context(|| format!("Failed to re-encode {path:?} to uff-8"))?,
119        )?;
120        check!(sys::tract_nnef_write_model_to_tar_gz(self.0, path.as_ptr(), model.0))?;
121        Ok(())
122    }
123}
124
125// ONNX
126wrapper!(Onnx, TractOnnx, tract_onnx_destroy);
127
128impl OnnxInterface for Onnx {
129    type InferenceModel = InferenceModel;
130    fn model_for_path(&self, path: impl AsRef<Path>) -> Result<InferenceModel> {
131        let path = path.as_ref();
132        let path = CString::new(
133            path.to_str().with_context(|| format!("Failed to re-encode {path:?} to uff-8"))?,
134        )?;
135        let mut model = null_mut();
136        check!(sys::tract_onnx_model_for_path(self.0, path.as_ptr(), &mut model))?;
137        Ok(InferenceModel(model))
138    }
139}
140
141// INFERENCE MODEL
142wrapper!(InferenceModel, TractInferenceModel, tract_inference_model_destroy);
143impl InferenceModelInterface for InferenceModel {
144    type Model = Model;
145    type InferenceFact = InferenceFact;
146    fn set_output_names(
147        &mut self,
148        outputs: impl IntoIterator<Item = impl AsRef<str>>,
149    ) -> Result<()> {
150        let c_strings: Vec<CString> =
151            outputs.into_iter().map(|a| Ok(CString::new(a.as_ref())?)).collect::<Result<_>>()?;
152        let ptrs: Vec<_> = c_strings.iter().map(|cs| cs.as_ptr()).collect();
153        check!(sys::tract_inference_model_set_output_names(
154            self.0,
155            c_strings.len(),
156            ptrs.as_ptr()
157        ))?;
158        Ok(())
159    }
160
161    fn input_count(&self) -> Result<usize> {
162        let mut count = 0;
163        check!(sys::tract_inference_model_input_count(self.0, &mut count))?;
164        Ok(count)
165    }
166
167    fn output_count(&self) -> Result<usize> {
168        let mut count = 0;
169        check!(sys::tract_inference_model_output_count(self.0, &mut count))?;
170        Ok(count)
171    }
172
173    fn input_name(&self, id: usize) -> Result<String> {
174        let mut ptr = null_mut();
175        check!(sys::tract_inference_model_input_name(self.0, id, &mut ptr))?;
176        unsafe {
177            let ret = CStr::from_ptr(ptr).to_str()?.to_owned();
178            sys::tract_free_cstring(ptr);
179            Ok(ret)
180        }
181    }
182
183    fn output_name(&self, id: usize) -> Result<String> {
184        let mut ptr = null_mut();
185        check!(sys::tract_inference_model_output_name(self.0, id, &mut ptr))?;
186        unsafe {
187            let ret = CStr::from_ptr(ptr).to_str()?.to_owned();
188            sys::tract_free_cstring(ptr);
189            Ok(ret)
190        }
191    }
192
193    fn input_fact(&self, id: usize) -> Result<InferenceFact> {
194        let mut ptr = null_mut();
195        check!(sys::tract_inference_model_input_fact(self.0, id, &mut ptr))?;
196        Ok(InferenceFact(ptr))
197    }
198
199    fn set_input_fact(
200        &mut self,
201        id: usize,
202        fact: impl AsFact<Self, Self::InferenceFact>,
203    ) -> Result<()> {
204        let fact = fact.as_fact(self)?;
205        check!(sys::tract_inference_model_set_input_fact(self.0, id, fact.0))?;
206        Ok(())
207    }
208
209    fn output_fact(&self, id: usize) -> Result<InferenceFact> {
210        let mut ptr = null_mut();
211        check!(sys::tract_inference_model_output_fact(self.0, id, &mut ptr))?;
212        Ok(InferenceFact(ptr))
213    }
214
215    fn set_output_fact(
216        &mut self,
217        id: usize,
218        fact: impl AsFact<InferenceModel, InferenceFact>,
219    ) -> Result<()> {
220        let fact = fact.as_fact(self)?;
221        check!(sys::tract_inference_model_set_output_fact(self.0, id, fact.0))?;
222        Ok(())
223    }
224
225    fn analyse(&mut self) -> Result<()> {
226        check!(sys::tract_inference_model_analyse(self.0))?;
227        Ok(())
228    }
229
230    fn into_typed(mut self) -> Result<Self::Model> {
231        let mut ptr = null_mut();
232        check!(sys::tract_inference_model_into_typed(&mut self.0, &mut ptr))?;
233        Ok(Model(ptr))
234    }
235
236    fn into_optimized(mut self) -> Result<Self::Model> {
237        let mut ptr = null_mut();
238        check!(sys::tract_inference_model_into_optimized(&mut self.0, &mut ptr))?;
239        Ok(Model(ptr))
240    }
241}
242
243// MODEL
244wrapper!(Model, TractModel, tract_model_destroy);
245
246impl ModelInterface for Model {
247    type Fact = Fact;
248    type Value = Value;
249    type Runnable = Runnable;
250    fn input_count(&self) -> Result<usize> {
251        let mut count = 0;
252        check!(sys::tract_model_input_count(self.0, &mut count))?;
253        Ok(count)
254    }
255
256    fn output_count(&self) -> Result<usize> {
257        let mut count = 0;
258        check!(sys::tract_model_output_count(self.0, &mut count))?;
259        Ok(count)
260    }
261
262    fn input_name(&self, id: usize) -> Result<String> {
263        let mut ptr = null_mut();
264        check!(sys::tract_model_input_name(self.0, id, &mut ptr))?;
265        unsafe {
266            let ret = CStr::from_ptr(ptr).to_str()?.to_owned();
267            sys::tract_free_cstring(ptr);
268            Ok(ret)
269        }
270    }
271
272    fn output_name(&self, id: usize) -> Result<String> {
273        let mut ptr = null_mut();
274        check!(sys::tract_model_output_name(self.0, id, &mut ptr))?;
275        unsafe {
276            let ret = CStr::from_ptr(ptr).to_str()?.to_owned();
277            sys::tract_free_cstring(ptr);
278            Ok(ret)
279        }
280    }
281
282    fn set_output_names(
283        &mut self,
284        outputs: impl IntoIterator<Item = impl AsRef<str>>,
285    ) -> Result<()> {
286        let c_strings: Vec<CString> =
287            outputs.into_iter().map(|a| Ok(CString::new(a.as_ref())?)).collect::<Result<_>>()?;
288        let ptrs: Vec<_> = c_strings.iter().map(|cs| cs.as_ptr()).collect();
289        check!(sys::tract_model_set_output_names(self.0, c_strings.len(), ptrs.as_ptr()))?;
290        Ok(())
291    }
292
293    fn input_fact(&self, id: usize) -> Result<Fact> {
294        let mut ptr = null_mut();
295        check!(sys::tract_model_input_fact(self.0, id, &mut ptr))?;
296        Ok(Fact(ptr))
297    }
298
299    fn output_fact(&self, id: usize) -> Result<Fact> {
300        let mut ptr = null_mut();
301        check!(sys::tract_model_output_fact(self.0, id, &mut ptr))?;
302        Ok(Fact(ptr))
303    }
304
305    fn declutter(&mut self) -> Result<()> {
306        check!(sys::tract_model_declutter(self.0))?;
307        Ok(())
308    }
309
310    fn optimize(&mut self) -> Result<()> {
311        check!(sys::tract_model_optimize(self.0))?;
312        Ok(())
313    }
314
315    fn into_decluttered(self) -> Result<Model> {
316        check!(sys::tract_model_declutter(self.0))?;
317        Ok(self)
318    }
319
320    fn into_optimized(self) -> Result<Model> {
321        check!(sys::tract_model_optimize(self.0))?;
322        Ok(self)
323    }
324
325    fn into_runnable(self) -> Result<Runnable> {
326        let mut model = self;
327        let mut runnable = null_mut();
328        check!(sys::tract_model_into_runnable(&mut model.0, &mut runnable))?;
329        Ok(Runnable(runnable))
330    }
331
332    fn concretize_symbols(
333        &mut self,
334        values: impl IntoIterator<Item = (impl AsRef<str>, i64)>,
335    ) -> Result<()> {
336        let (names, values): (Vec<_>, Vec<_>) = values.into_iter().unzip();
337        let c_strings: Vec<CString> =
338            names.into_iter().map(|a| Ok(CString::new(a.as_ref())?)).collect::<Result<_>>()?;
339        let ptrs: Vec<_> = c_strings.iter().map(|cs| cs.as_ptr()).collect();
340        check!(sys::tract_model_concretize_symbols(
341            self.0,
342            ptrs.len(),
343            ptrs.as_ptr(),
344            values.as_ptr()
345        ))?;
346        Ok(())
347    }
348
349    fn transform(&mut self, transform: &str) -> Result<()> {
350        let t = CString::new(transform)?;
351        check!(sys::tract_model_transform(self.0, t.as_ptr()))?;
352        Ok(())
353    }
354
355    fn pulse(&mut self, name: impl AsRef<str>, value: impl AsRef<str>) -> Result<()> {
356        let name = CString::new(name.as_ref())?;
357        let value = CString::new(value.as_ref())?;
358        check!(sys::tract_model_pulse_simple(&mut self.0, name.as_ptr(), value.as_ptr()))?;
359        Ok(())
360    }
361
362    fn cost_json(&self) -> Result<String> {
363        let input: Option<Vec<Value>> = None;
364        let states: Option<Vec<Value>> = None;
365        self.profile_json(input, states)
366    }
367
368    fn profile_json<I, IV, IE, S, SV, SE>(
369        &self,
370        inputs: Option<I>,
371        state_initializers: Option<S>,
372    ) -> Result<String>
373    where
374        I: IntoIterator<Item = IV>,
375        IV: TryInto<Self::Value, Error = IE>,
376        IE: Into<anyhow::Error>,
377        S: IntoIterator<Item = SV>,
378        SV: TryInto<Self::Value, Error = SE>,
379        SE: Into<anyhow::Error>,
380    {
381        let inputs = if let Some(inputs) = inputs {
382            let inputs = inputs
383                .into_iter()
384                .map(|i| i.try_into().map_err(|e| e.into()))
385                .collect::<Result<Vec<Value>>>()?;
386            anyhow::ensure!(self.input_count()? == inputs.len());
387            Some(inputs)
388        } else {
389            None
390        };
391        let mut iptrs: Option<Vec<*mut sys::TractValue>> =
392            inputs.as_ref().map(|is| is.iter().map(|v| v.0).collect());
393        let mut json: *mut i8 = null_mut();
394        let values = iptrs.as_mut().map(|it| it.as_mut_ptr()).unwrap_or(null_mut());
395
396        let (state_inits, n_states) = if let Some(state_vec) = state_initializers {
397            let mut states: Vec<*const _> = vec![];
398
399            for v in state_vec {
400                let val: Value = v.try_into().map_err(|e| e.into())?;
401                states.push(val.0);
402            }
403            let len = states.len();
404            (Some(states), len)
405        } else {
406            (None, 0)
407        };
408
409        let states = state_inits.map(|is| is.as_ptr()).unwrap_or(null());
410        check!(sys::tract_model_profile_json(self.0, values, states, n_states, &mut json))?;
411        anyhow::ensure!(!json.is_null());
412        unsafe {
413            let s = CStr::from_ptr(json).to_owned();
414            sys::tract_free_cstring(json);
415            Ok(s.to_str()?.to_owned())
416        }
417    }
418
419    fn property_keys(&self) -> Result<Vec<String>> {
420        let mut len = 0;
421        check!(sys::tract_model_property_count(self.0, &mut len))?;
422        let mut keys = vec![null_mut(); len];
423        check!(sys::tract_model_property_names(self.0, keys.as_mut_ptr()))?;
424        unsafe {
425            keys.into_iter()
426                .map(|pc| {
427                    let s = CStr::from_ptr(pc).to_str()?.to_owned();
428                    sys::tract_free_cstring(pc);
429                    Ok(s)
430                })
431                .collect()
432        }
433    }
434
435    fn property(&self, name: impl AsRef<str>) -> Result<Value> {
436        let mut v = null_mut();
437        let name = CString::new(name.as_ref())?;
438        check!(sys::tract_model_property(self.0, name.as_ptr(), &mut v))?;
439        Ok(Value(v))
440    }
441}
442
443// RUNNABLE
444wrapper!(Runnable, TractRunnable, tract_runnable_release);
445
446impl RunnableInterface for Runnable {
447    type Value = Value;
448    type State = State;
449
450    fn run<I, V, E>(&self, inputs: I) -> Result<Vec<Value>>
451    where
452        I: IntoIterator<Item = V>,
453        V: TryInto<Value, Error = E>,
454        E: Into<anyhow::Error>,
455    {
456        self.spawn_state()?.run(inputs)
457    }
458
459    fn spawn_state(&self) -> Result<State> {
460        let mut state = null_mut();
461        check!(sys::tract_runnable_spawn_state(self.0, &mut state))?;
462        Ok(State(state))
463    }
464
465    fn input_count(&self) -> Result<usize> {
466        let mut count = 0;
467        check!(sys::tract_runnable_input_count(self.0, &mut count))?;
468        Ok(count)
469    }
470
471    fn output_count(&self) -> Result<usize> {
472        let mut count = 0;
473        check!(sys::tract_runnable_output_count(self.0, &mut count))?;
474        Ok(count)
475    }
476}
477
478// STATE
479wrapper!(State, TractState, tract_state_destroy);
480
481impl StateInterface for State {
482    type Value = Value;
483    type Fact = Fact;
484
485    fn run<I, V, E>(&mut self, inputs: I) -> Result<Vec<Value>>
486    where
487        I: IntoIterator<Item = V>,
488        V: TryInto<Value, Error = E>,
489        E: Into<anyhow::Error>,
490    {
491        let inputs = inputs
492            .into_iter()
493            .map(|i| i.try_into().map_err(|e| e.into()))
494            .collect::<Result<Vec<Value>>>()?;
495        let mut outputs = vec![null_mut(); self.output_count()?];
496        let mut inputs: Vec<_> = inputs.iter().map(|v| v.0).collect();
497        check!(sys::tract_state_run(self.0, inputs.as_mut_ptr(), outputs.as_mut_ptr()))?;
498        let outputs = outputs.into_iter().map(Value).collect();
499        Ok(outputs)
500    }
501
502    fn input_count(&self) -> Result<usize> {
503        let mut count = 0;
504        check!(sys::tract_state_input_count(self.0, &mut count))?;
505        Ok(count)
506    }
507
508    fn output_count(&self) -> Result<usize> {
509        let mut count = 0;
510        check!(sys::tract_state_output_count(self.0, &mut count))?;
511        Ok(count)
512    }
513
514    fn initializable_states_count(&self) -> Result<usize> {
515        let mut n_states = 0;
516        check!(sys::tract_state_initializable_states_count(self.0, &mut n_states))?;
517        Ok(n_states)
518    }
519
520    fn get_states_facts(&self) -> Result<Vec<Fact>> {
521        let n_states = self.initializable_states_count()?;
522        let mut fptrs = vec![null_mut(); n_states];
523
524        check!(sys::tract_state_get_states_facts(self.0, fptrs.as_mut_ptr()))?;
525
526        let res = fptrs.into_iter().map(|value| Ok(Fact(value))).collect::<Result<Vec<Fact>>>();
527
528        res
529    }
530
531    fn set_states<I, V, E>(&mut self, state_initializers: I) -> Result<()>
532    where
533        I: IntoIterator<Item = V>,
534        V: TryInto<Self::Value, Error = E>,
535        E: Into<anyhow::Error>,
536    {
537        let sptrs = {
538            let mut states: Vec<*const _> = vec![];
539
540            for s in state_initializers {
541                let val: Value = s.try_into().map_err(|e| e.into())?;
542                states.push(val.0);
543            }
544
545            let len = states.len();
546            anyhow::ensure!(
547                len == self.initializable_states_count()?,
548                "Expected {} states, got {len}",
549                self.initializable_states_count()?
550            );
551            Some(states)
552        };
553
554        let sptrs = sptrs.map(|it| it.as_ptr()).unwrap_or(null());
555        check!(sys::tract_state_set_states(self.0, sptrs))?;
556
557        Ok(())
558    }
559
560    fn get_states(&self) -> Result<Vec<Self::Value>> {
561        let n_states = self.initializable_states_count()?;
562
563        let mut sptrs = vec![null_mut(); n_states];
564        check!(sys::tract_state_get_states(self.0, sptrs.as_mut_ptr()))?;
565
566        let res = sptrs.into_iter().map(|value| Ok(Value(value))).collect::<Result<Vec<Value>>>();
567
568        res
569    }
570}
571
572// VALUE
573wrapper!(Value, TractValue, tract_value_destroy);
574
575impl ValueInterface for Value {
576    fn from_bytes(dt: DatumType, shape: &[usize], data: &[u8]) -> Result<Self> {
577        anyhow::ensure!(data.len() == shape.iter().product::<usize>() * dt.size_of());
578        let mut value = null_mut();
579        check!(sys::tract_value_from_bytes(
580            dt as _,
581            shape.len(),
582            shape.as_ptr(),
583            data.as_ptr() as _,
584            &mut value
585        ))?;
586        Ok(Value(value))
587    }
588
589    fn as_bytes(&self) -> Result<(DatumType, &[usize], &[u8])> {
590        let mut rank = 0;
591        let mut dt = sys::DatumType_TRACT_DATUM_TYPE_BOOL as _;
592        let mut shape = null();
593        let mut data = null();
594        check!(sys::tract_value_as_bytes(self.0, &mut dt, &mut rank, &mut shape, &mut data))?;
595        unsafe {
596            let dt: DatumType = std::mem::transmute(dt);
597            let shape = std::slice::from_raw_parts(shape, rank);
598            let len: usize = shape.iter().product();
599            let data = std::slice::from_raw_parts(data as *const u8, len * dt.size_of());
600            Ok((dt, shape, data))
601        }
602    }
603}
604
605value_from_to_ndarray!();
606
607// FACT
608wrapper!(Fact, TractFact, tract_fact_destroy);
609
610impl Fact {
611    fn new(model: &mut Model, spec: impl ToString) -> Result<Fact> {
612        let cstr = CString::new(spec.to_string())?;
613        let mut fact = null_mut();
614        check!(sys::tract_fact_parse(model.0, cstr.as_ptr(), &mut fact))?;
615        Ok(Fact(fact))
616    }
617
618    fn dump(&self) -> Result<String> {
619        let mut ptr = null_mut();
620        check!(sys::tract_fact_dump(self.0, &mut ptr))?;
621        unsafe {
622            let s = CStr::from_ptr(ptr).to_owned();
623            sys::tract_free_cstring(ptr);
624            Ok(s.to_str()?.to_owned())
625        }
626    }
627}
628
629impl FactInterface for Fact {}
630
631impl std::fmt::Display for Fact {
632    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
633        match self.dump() {
634            Ok(s) => f.write_str(&s),
635            Err(_) => Err(std::fmt::Error),
636        }
637    }
638}
639
640// INFERENCE FACT
641wrapper!(InferenceFact, TractInferenceFact, tract_inference_fact_destroy);
642
643impl InferenceFact {
644    fn new(model: &mut InferenceModel, spec: impl ToString) -> Result<InferenceFact> {
645        let cstr = CString::new(spec.to_string())?;
646        let mut fact = null_mut();
647        check!(sys::tract_inference_fact_parse(model.0, cstr.as_ptr(), &mut fact))?;
648        Ok(InferenceFact(fact))
649    }
650
651    fn dump(&self) -> Result<String> {
652        let mut ptr = null_mut();
653        check!(sys::tract_inference_fact_dump(self.0, &mut ptr))?;
654        unsafe {
655            let s = CStr::from_ptr(ptr).to_owned();
656            sys::tract_free_cstring(ptr);
657            Ok(s.to_str()?.to_owned())
658        }
659    }
660}
661
662impl InferenceFactInterface for InferenceFact {
663    fn empty() -> Result<InferenceFact> {
664        let mut fact = null_mut();
665        check!(sys::tract_inference_fact_empty(&mut fact))?;
666        Ok(InferenceFact(fact))
667    }
668}
669
670impl Default for InferenceFact {
671    fn default() -> Self {
672        Self::empty().unwrap()
673    }
674}
675
676impl std::fmt::Display for InferenceFact {
677    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
678        match self.dump() {
679            Ok(s) => f.write_str(&s),
680            Err(_) => Err(std::fmt::Error),
681        }
682    }
683}
684
685as_inference_fact_impl!(InferenceModel, InferenceFact);
686as_fact_impl!(Model, Fact);