1use crate::{
32 context::{BadNode, Context, Node, Tree},
33 eval::{
34 BulkEvalError, BulkEvaluator, Function, MathFunction, Tape,
35 TracingEvalError, TracingEvaluator,
36 },
37 types::{Grad, Interval},
38 var::{BulkArgError, TracingArgError, Var, VarIndex, VarMap},
39 vm::BadTrace,
40};
41use nalgebra::{Matrix4, Point3};
42use std::collections::HashMap;
43
44pub struct Shape<F> {
52 f: F,
54}
55
56impl<F: Clone> Clone for Shape<F> {
57 fn clone(&self) -> Self {
58 Self { f: self.f.clone() }
59 }
60}
61
62impl<F: Function + Clone> Shape<F> {
63 pub fn new_point_eval() -> ShapeTracingEval<F::PointEval> {
65 ShapeTracingEval {
66 eval: F::PointEval::default(),
67 scratch: vec![],
68 }
69 }
70
71 pub fn new_interval_eval() -> ShapeTracingEval<F::IntervalEval> {
73 ShapeTracingEval {
74 eval: F::IntervalEval::default(),
75 scratch: vec![],
76 }
77 }
78
79 pub fn new_float_slice_eval() -> ShapeBulkEval<F::FloatSliceEval> {
81 ShapeBulkEval {
82 eval: F::FloatSliceEval::default(),
83 scratch: vec![],
84 }
85 }
86
87 pub fn new_grad_slice_eval() -> ShapeBulkEval<F::GradSliceEval> {
89 ShapeBulkEval {
90 eval: F::GradSliceEval::default(),
91 scratch: vec![],
92 }
93 }
94
95 #[inline]
97 pub fn point_tape(
98 &self,
99 storage: F::TapeStorage,
100 ) -> ShapeTape<<F::PointEval as TracingEvaluator>::Tape> {
101 let tape = self.f.point_tape(storage);
102 ShapeTape { tape }
103 }
104
105 #[inline]
107 pub fn interval_tape(
108 &self,
109 storage: F::TapeStorage,
110 ) -> ShapeTape<<F::IntervalEval as TracingEvaluator>::Tape> {
111 let tape = self.f.interval_tape(storage);
112 ShapeTape { tape }
113 }
114
115 #[inline]
117 pub fn float_slice_tape(
118 &self,
119 storage: F::TapeStorage,
120 ) -> ShapeTape<<F::FloatSliceEval as BulkEvaluator>::Tape> {
121 let tape = self.f.float_slice_tape(storage);
122 ShapeTape { tape }
123 }
124
125 #[inline]
127 pub fn grad_slice_tape(
128 &self,
129 storage: F::TapeStorage,
130 ) -> ShapeTape<<F::GradSliceEval as BulkEvaluator>::Tape> {
131 let tape = self.f.grad_slice_tape(storage);
132 ShapeTape { tape }
133 }
134
135 #[inline]
137 pub fn simplify(
138 &self,
139 trace: &F::Trace,
140 storage: F::Storage,
141 workspace: &mut F::Workspace,
142 ) -> Result<Self, BadTrace>
143 where
144 Self: Sized,
145 {
146 let f = self.f.simplify(trace, storage, workspace)?;
147 Ok(Self { f })
148 }
149
150 #[inline]
155 pub fn recycle(self) -> Option<F::Storage> {
156 self.f.recycle()
157 }
158
159 #[inline]
164 pub fn size(&self) -> usize {
165 self.f.size()
166 }
167
168 #[inline]
170 pub fn bind<'a, D>(
171 &self,
172 vars: &'a ShapeVars<D>,
173 ) -> Result<BoundShape<'a, F, D>, MissingVar> {
174 BoundShape::new(self.clone(), vars)
175 }
176}
177
178impl<F> Shape<F> {
179 pub fn inner(&self) -> &F {
181 &self.f
182 }
183}
184
185pub struct ShapeVars<F>(HashMap<VarIndex, F>);
191
192impl<F> Default for ShapeVars<F> {
193 fn default() -> Self {
194 Self(HashMap::default())
195 }
196}
197
198impl<F> ShapeVars<F> {
199 pub fn new() -> Self {
201 Self(HashMap::default())
202 }
203 pub fn len(&self) -> usize {
205 self.0.len()
206 }
207 pub fn is_empty(&self) -> bool {
209 self.0.is_empty()
210 }
211 pub fn insert(&mut self, v: VarIndex, f: F) -> Option<F> {
215 self.0.insert(v, f)
216 }
217
218 pub fn values(&self) -> impl Iterator<Item = &F> {
220 self.0.values()
221 }
222
223 pub fn get(&self, i: VarIndex) -> Option<&F> {
225 self.0.get(&i)
226 }
227
228 pub fn contains_key(&self, i: VarIndex) -> bool {
230 self.0.contains_key(&i)
231 }
232
233 pub fn check<G: Function>(
235 &self,
236 shape: &Shape<G>,
237 ) -> Result<(), MissingVar> {
238 for (v, _) in shape.inner().vars().iter() {
239 match v {
240 Var::X | Var::Y | Var::Z => (),
241 Var::V(i) => {
242 if !self.contains_key(i) {
243 return Err(MissingVar { var: i });
244 }
245 }
246 }
247 }
248 Ok(())
249 }
250}
251
252impl<'a, F> IntoIterator for &'a ShapeVars<F> {
253 type Item = (&'a VarIndex, &'a F);
254 type IntoIter = std::collections::hash_map::Iter<'a, VarIndex, F>;
255 fn into_iter(self) -> Self::IntoIter {
256 self.0.iter()
257 }
258}
259
260pub trait EzShape<F: Function> {
270 fn ez_point_tape(
272 &self,
273 ) -> ShapeTape<<F::PointEval as TracingEvaluator>::Tape>;
274
275 fn ez_interval_tape(
277 &self,
278 ) -> ShapeTape<<F::IntervalEval as TracingEvaluator>::Tape>;
279
280 fn ez_float_slice_tape(
282 &self,
283 ) -> ShapeTape<<F::FloatSliceEval as BulkEvaluator>::Tape>;
284
285 fn ez_grad_slice_tape(
287 &self,
288 ) -> ShapeTape<<F::GradSliceEval as BulkEvaluator>::Tape>;
289
290 fn ez_simplify(&self, trace: &F::Trace) -> Result<Self, BadTrace>
292 where
293 Self: Sized;
294}
295
296impl<F: Function> EzShape<F> for Shape<F> {
297 fn ez_point_tape(
298 &self,
299 ) -> ShapeTape<<F::PointEval as TracingEvaluator>::Tape> {
300 self.point_tape(Default::default())
301 }
302
303 fn ez_interval_tape(
304 &self,
305 ) -> ShapeTape<<F::IntervalEval as TracingEvaluator>::Tape> {
306 self.interval_tape(Default::default())
307 }
308
309 fn ez_float_slice_tape(
310 &self,
311 ) -> ShapeTape<<F::FloatSliceEval as BulkEvaluator>::Tape> {
312 self.float_slice_tape(Default::default())
313 }
314
315 fn ez_grad_slice_tape(
316 &self,
317 ) -> ShapeTape<<F::GradSliceEval as BulkEvaluator>::Tape> {
318 self.grad_slice_tape(Default::default())
319 }
320
321 fn ez_simplify(&self, trace: &F::Trace) -> Result<Self, BadTrace> {
322 let mut workspace = Default::default();
323 self.simplify(trace, Default::default(), &mut workspace)
324 }
325}
326
327impl<F: MathFunction> Shape<F> {
328 pub fn new(ctx: &Context, node: Node) -> Result<Self, BadNode> {
330 let f = F::new(ctx, &[node])?;
331 Ok(Self { f })
332 }
333
334 pub fn new_raw(f: F) -> Self {
339 assert_eq!(f.output_count(), 1);
340 Self { f }
341 }
342}
343
344impl<F: MathFunction> From<Tree> for Shape<F> {
346 fn from(t: Tree) -> Self {
347 let mut ctx = Context::new();
348 let node = ctx.import(&t);
349 Self::new(&ctx, node).unwrap()
350 }
351}
352
353#[derive(Clone)]
355pub struct ShapeTape<T> {
356 tape: T,
357}
358
359impl<T: Tape> ShapeTape<T> {
360 pub fn recycle(self) -> Option<T::Storage> {
362 self.tape.recycle()
363 }
364
365 pub fn vars(&self) -> &VarMap {
367 self.tape.vars()
368 }
369}
370
371#[derive(Debug)]
376pub struct ShapeTracingEval<E: TracingEvaluator> {
377 eval: E,
378 scratch: Vec<E::Data>,
379}
380
381impl<E: TracingEvaluator> Default for ShapeTracingEval<E> {
382 fn default() -> Self {
383 Self {
384 eval: E::default(),
385 scratch: vec![],
386 }
387 }
388}
389
390#[derive(thiserror::Error, Debug, PartialEq)]
392#[error("variable {var:?} must be provided")]
393pub struct MissingVar {
394 pub var: VarIndex,
396}
397
398#[derive(thiserror::Error, Debug)]
400pub enum ShapeTracingEvalError {
401 #[error(transparent)]
403 MissingVar(#[from] MissingVar),
404}
405
406#[derive(thiserror::Error, Debug, PartialEq)]
408pub enum ShapeBulkEvalError {
409 #[error(transparent)]
411 MissingVar(#[from] MissingVar),
412
413 #[error(
415 "slice lengths are mismatched: \
416 slice for {var_a} has {length_a} items; \
417 slice for {var_b} has {length_b} items;"
418 )]
419 MismatchedVarSlices {
420 var_a: Var,
422 length_a: usize,
424 var_b: Var,
426 length_b: usize,
428 },
429}
430
431impl<E: TracingEvaluator> ShapeTracingEval<E>
432where
433 <E as TracingEvaluator>::Data: Transformable,
434{
435 #[inline]
437 pub fn eval<F: Into<E::Data> + Copy>(
438 &mut self,
439 tape: &ShapeTape<E::Tape>,
440 x: F,
441 y: F,
442 z: F,
443 ) -> Result<(E::Data, Option<&E::Trace>), ShapeTracingEvalError> {
444 let h = ShapeVars::<f32>::new();
445 self.eval_raw(tape, x, y, z, None, &h)
446 }
447
448 #[inline]
450 pub fn eval_with_transform<F: Into<E::Data> + Copy>(
451 &mut self,
452 tape: &ShapeTape<E::Tape>,
453 x: F,
454 y: F,
455 z: F,
456 transform: &Matrix4<f32>,
457 ) -> Result<(E::Data, Option<&E::Trace>), ShapeTracingEvalError> {
458 let h = ShapeVars::<f32>::new();
459 self.eval_raw(tape, x, y, z, Some(transform), &h)
460 }
461
462 #[inline]
464 pub fn eval_with_transform_and_vars<
465 F: Into<E::Data> + Copy,
466 V: Into<E::Data> + Copy,
467 >(
468 &mut self,
469 tape: &ShapeTape<E::Tape>,
470 x: F,
471 y: F,
472 z: F,
473 transform: &Matrix4<f32>,
474 vars: &ShapeVars<V>,
475 ) -> Result<(E::Data, Option<&E::Trace>), ShapeTracingEvalError> {
476 self.eval_raw(tape, x, y, z, Some(transform), vars)
477 }
478
479 #[inline]
481 pub fn eval_with_vars<F: Into<E::Data> + Copy, V: Into<E::Data> + Copy>(
482 &mut self,
483 tape: &ShapeTape<E::Tape>,
484 x: F,
485 y: F,
486 z: F,
487 vars: &ShapeVars<V>,
488 ) -> Result<(E::Data, Option<&E::Trace>), ShapeTracingEvalError> {
489 self.eval_raw(tape, x, y, z, None, vars)
490 }
491
492 #[inline]
494 pub fn eval_raw<F: Into<E::Data> + Copy, V: Into<E::Data> + Copy>(
495 &mut self,
496 tape: &ShapeTape<E::Tape>,
497 x: F,
498 y: F,
499 z: F,
500 transform: Option<&Matrix4<f32>>,
501 vars: &ShapeVars<V>,
502 ) -> Result<(E::Data, Option<&E::Trace>), ShapeTracingEvalError> {
503 assert_eq!(
504 tape.tape.output_count(),
505 1,
506 "ShapeTape has multiple outputs"
507 );
508
509 let x = x.into();
510 let y = y.into();
511 let z = z.into();
512 let (x, y, z) = if let Some(t) = transform {
513 Transformable::transform(x, y, z, t)
514 } else {
515 (x, y, z)
516 };
517
518 let vs = tape.vars();
519 self.scratch.resize(vs.len(), 0f32.into());
520 for (var, index) in vs.iter() {
521 match var {
522 Var::X => self.scratch[index] = x,
523 Var::Y => self.scratch[index] = y,
524 Var::Z => self.scratch[index] = z,
525 Var::V(i) => {
526 let Some(value) = vars.get(i) else {
527 return Err(MissingVar { var: i }.into());
528 };
529 self.scratch[index] = (*value).into();
530 }
531 }
532 }
533 let (out, trace) = match self.eval.eval(&tape.tape, &self.scratch) {
534 Ok((out, trace)) => (out, trace),
535 Err(TracingEvalError(TracingArgError::BadVarSlice(..))) => {
536 unreachable!() }
538 };
539 Ok((out[0], trace))
540 }
541}
542
543#[derive(Debug, Default)]
548pub struct ShapeBulkEval<E: BulkEvaluator> {
549 eval: E,
550 scratch: Vec<Vec<E::Data>>,
551}
552
553impl<E: BulkEvaluator> ShapeBulkEval<E>
554where
555 E::Data: From<f32> + Transformable,
556{
557 #[inline]
564 pub fn eval(
565 &mut self,
566 tape: &ShapeTape<E::Tape>,
567 x: &[E::Data],
568 y: &[E::Data],
569 z: &[E::Data],
570 ) -> Result<&[E::Data], ShapeBulkEvalError> {
571 self.eval_raw(tape, x, y, z, None, Self::no_vars)
572 }
573
574 #[inline]
576 pub fn eval_with_transform(
577 &mut self,
578 tape: &ShapeTape<E::Tape>,
579 x: &[E::Data],
580 y: &[E::Data],
581 z: &[E::Data],
582 transform: &Matrix4<f32>,
583 ) -> Result<&[E::Data], ShapeBulkEvalError> {
584 self.eval_raw(tape, x, y, z, Some(transform), Self::no_vars)
585 }
586
587 #[inline]
596 pub fn eval_with_var_arrays<
597 V: std::ops::Deref<Target = [G]>,
598 G: Into<E::Data> + Copy,
599 >(
600 &mut self,
601 tape: &ShapeTape<E::Tape>,
602 x: &[E::Data],
603 y: &[E::Data],
604 z: &[E::Data],
605 vars: &ShapeVars<V>,
606 ) -> Result<&[E::Data], ShapeBulkEvalError> {
607 self.eval_raw(tape, x, y, z, None, Self::var_array(vars))
608 }
609
610 #[inline]
612 pub fn eval_with_transform_and_var_arrays<
613 V: std::ops::Deref<Target = [G]>,
614 G: Into<E::Data> + Copy,
615 >(
616 &mut self,
617 tape: &ShapeTape<E::Tape>,
618 x: &[E::Data],
619 y: &[E::Data],
620 z: &[E::Data],
621 transform: &Matrix4<f32>,
622 vars: &ShapeVars<V>,
623 ) -> Result<&[E::Data], ShapeBulkEvalError> {
624 self.eval_raw(tape, x, y, z, Some(transform), Self::var_array(vars))
625 }
626
627 #[inline]
634 pub fn eval_with_vars<G: Into<E::Data> + Copy>(
635 &mut self,
636 tape: &ShapeTape<E::Tape>,
637 x: &[E::Data],
638 y: &[E::Data],
639 z: &[E::Data],
640 vars: &ShapeVars<G>,
641 ) -> Result<&[E::Data], ShapeBulkEvalError> {
642 self.eval_raw(tape, x, y, z, None, Self::var_value(vars))
643 }
644
645 #[inline]
647 pub fn eval_with_transform_and_vars<G: Into<E::Data> + Copy>(
648 &mut self,
649 tape: &ShapeTape<E::Tape>,
650 x: &[E::Data],
651 y: &[E::Data],
652 z: &[E::Data],
653 transform: &Matrix4<f32>,
654 vars: &ShapeVars<G>,
655 ) -> Result<&[E::Data], ShapeBulkEvalError> {
656 self.eval_raw(tape, x, y, z, Some(transform), Self::var_value(vars))
657 }
658
659 #[inline]
661 fn no_vars(
662 _: &mut [E::Data],
663 var: VarIndex,
664 ) -> Result<(), ShapeBulkEvalError> {
665 Err(MissingVar { var }.into())
666 }
667
668 #[inline]
673 pub fn var_array<
674 V: std::ops::Deref<Target = [G]>,
675 G: Into<E::Data> + Copy,
676 >(
677 vars: &ShapeVars<V>,
678 ) -> impl Fn(&mut [E::Data], VarIndex) -> Result<(), ShapeBulkEvalError>
679 {
680 |data: &mut [E::Data], i: VarIndex| {
681 let vars = vars.get(i).ok_or(MissingVar { var: i })?;
682 if vars.len() != data.len() {
683 return Err(ShapeBulkEvalError::MismatchedVarSlices {
684 var_a: Var::X,
685 length_a: data.len(),
686 var_b: Var::V(i),
687 length_b: vars.len(),
688 });
689 }
690 for (a, b) in data.iter_mut().zip(vars.deref().iter()) {
691 *a = (*b).into();
692 }
693 Ok(())
694 }
695 }
696
697 #[inline]
702 pub fn var_value<G: Into<E::Data> + Copy>(
703 vars: &ShapeVars<G>,
704 ) -> impl Fn(&mut [E::Data], VarIndex) -> Result<(), ShapeBulkEvalError>
705 {
706 |data: &mut [E::Data], i: VarIndex| {
707 let value = vars.get(i).ok_or(MissingVar { var: i })?;
708 data.fill((*value).into());
709 Ok(())
710 }
711 }
712
713 #[inline]
719 pub fn eval_raw<F>(
720 &mut self,
721 tape: &ShapeTape<E::Tape>,
722 x: &[E::Data],
723 y: &[E::Data],
724 z: &[E::Data],
725 transform: Option<&Matrix4<f32>>,
726 copy_vars: F,
727 ) -> Result<&[E::Data], ShapeBulkEvalError>
728 where
729 F: Fn(&mut [E::Data], VarIndex) -> Result<(), ShapeBulkEvalError>,
730 {
731 assert_eq!(
732 tape.tape.output_count(),
733 1,
734 "ShapeTape has multiple outputs" );
736
737 if x.len() != y.len() {
739 return Err(ShapeBulkEvalError::MismatchedVarSlices {
740 var_a: Var::X,
741 length_a: x.len(),
742 var_b: Var::Y,
743 length_b: y.len(),
744 });
745 }
746 if x.len() != z.len() {
747 return Err(ShapeBulkEvalError::MismatchedVarSlices {
748 var_a: Var::X,
749 length_a: x.len(),
750 var_b: Var::Z,
751 length_b: z.len(),
752 });
753 }
754 let n = x.len();
755
756 let vs = tape.vars();
759 self.scratch.resize_with(vs.len().max(1), Vec::new);
760 for s in &mut self.scratch {
761 s.resize(n, 0.0.into());
762 }
763
764 let mut axes = [None; 3];
765 for (var, index) in vs.iter() {
766 match var {
767 Var::X => axes[0] = Some(index),
768 Var::Y => axes[1] = Some(index),
769 Var::Z => axes[2] = Some(index),
770 Var::V(i) => {
771 copy_vars(&mut self.scratch[index], i)?;
772 }
773 }
774 }
775
776 for i in 0..n {
777 let (x, y, z) = if let Some(t) = transform {
778 Transformable::transform(x[i], y[i], z[i], t)
779 } else {
780 (x[i], y[i], z[i])
781 };
782 if let Some(a) = axes[0] {
783 self.scratch[a][i] = x;
784 }
785 if let Some(b) = axes[1] {
786 self.scratch[b][i] = y;
787 }
788 if let Some(c) = axes[2] {
789 self.scratch[c][i] = z;
790 }
791 }
792
793 let out = match self.eval.eval(&tape.tape, &self.scratch) {
794 Ok(out) => out,
795 Err(BulkEvalError(e)) => match e {
796 BulkArgError::BadVarSlice(..)
798 | BulkArgError::MismatchedSlices(..) => unreachable!(),
799 },
800 };
801 Ok(out.borrow(0))
802 }
803}
804
805#[derive(Clone)]
810pub struct BoundShape<'a, F, D> {
811 shape: Shape<F>,
812 vars: BorrowedOrDefault<'a, ShapeVars<D>>,
813}
814
815enum BorrowedOrDefault<'a, T: Default> {
817 Borrowed(&'a T),
818 Default(T),
819}
820
821impl<'a, T: Default> Clone for BorrowedOrDefault<'a, T> {
822 fn clone(&self) -> Self {
823 match self {
824 Self::Borrowed(b) => Self::Borrowed(b),
825 Self::Default(..) => Self::Default(T::default()),
826 }
827 }
828}
829
830impl<'a, T: Default> std::ops::Deref for BorrowedOrDefault<'a, T> {
831 type Target = T;
832 fn deref(&self) -> &T {
833 match self {
834 Self::Borrowed(t) => t,
835 Self::Default(t) => t,
836 }
837 }
838}
839
840impl<T: Default> Default for BorrowedOrDefault<'_, T> {
841 fn default() -> Self {
842 Self::Default(T::default())
843 }
844}
845
846impl<'a, F: Function, D> BoundShape<'a, F, D> {
847 pub fn new(
849 shape: Shape<F>,
850 vars: &'a ShapeVars<D>,
851 ) -> Result<Self, MissingVar> {
852 vars.check(&shape)?;
853 Ok(Self {
854 shape,
855 vars: BorrowedOrDefault::Borrowed(vars),
856 })
857 }
858
859 pub fn shape(&self) -> &Shape<F> {
861 &self.shape
862 }
863
864 pub fn vars(&self) -> &ShapeVars<D> {
866 &self.vars
867 }
868}
869
870impl<'a, F: Function> TryFrom<Shape<F>> for BoundShape<'a, F, f32> {
871 type Error = MissingVar;
872 fn try_from(shape: Shape<F>) -> Result<Self, Self::Error> {
873 if let Some(v) = shape
874 .inner()
875 .vars()
876 .iter()
877 .flat_map(|(v, _i)| match v {
878 Var::X | Var::Y | Var::Z => None,
879 Var::V(i) => Some(i),
880 })
881 .next()
882 {
883 Err(MissingVar { var: v })
884 } else {
885 Ok(BoundShape {
886 shape,
887 vars: BorrowedOrDefault::default(),
888 })
889 }
890 }
891}
892
893pub trait Transformable {
895 fn transform(
897 x: Self,
898 y: Self,
899 z: Self,
900 mat: &Matrix4<f32>,
901 ) -> (Self, Self, Self)
902 where
903 Self: Sized;
904}
905
906impl Transformable for f32 {
907 fn transform(
908 x: f32,
909 y: f32,
910 z: f32,
911 mat: &Matrix4<f32>,
912 ) -> (f32, f32, f32) {
913 let out = mat.transform_point(&Point3::new(x, y, z));
914 (out.x, out.y, out.z)
915 }
916}
917
918impl Transformable for Interval {
919 fn transform(
920 x: Interval,
921 y: Interval,
922 z: Interval,
923 mat: &Matrix4<f32>,
924 ) -> (Interval, Interval, Interval) {
925 let out = [0, 1, 2, 3].map(|i| {
926 let row = mat.row(i);
927 x * row[0] + y * row[1] + z * row[2] + Interval::from(row[3])
928 });
929
930 (out[0] / out[3], out[1] / out[3], out[2] / out[3])
931 }
932}
933
934impl Transformable for Grad {
935 fn transform(
936 x: Grad,
937 y: Grad,
938 z: Grad,
939 mat: &Matrix4<f32>,
940 ) -> (Grad, Grad, Grad) {
941 let out = [0, 1, 2, 3].map(|i| {
942 let row = mat.row(i);
943 x * row[0] + y * row[1] + z * row[2] + Grad::from(row[3])
944 });
945
946 (out[0] / out[3], out[1] / out[3], out[2] / out[3])
947 }
948}
949
950#[cfg(test)]
951mod test {
952 use super::*;
953 use crate::vm::VmShape;
954
955 #[test]
956 fn shape_vars() {
957 let v = Var::new();
958 let s = Tree::x() + Tree::y() + v;
959
960 let mut ctx = Context::new();
961 let s = ctx.import(&s);
962
963 let s = VmShape::new(&ctx, s).unwrap();
964 let vs = s.inner().vars();
965 assert_eq!(vs.len(), 3);
966
967 assert!(vs.get(&Var::X).is_some());
968 assert!(vs.get(&Var::Y).is_some());
969 assert!(vs.get(&Var::Z).is_none());
970 assert!(vs.get(&v).is_some());
971
972 let mut seen = [false; 3];
973 for v in [Var::X, Var::Y, v] {
974 seen[vs[&v]] = true;
975 }
976 assert!(seen.iter().all(|i| *i));
977 }
978
979 #[test]
980 fn shape_bind() {
981 let v = Var::new();
982 let s = Tree::x() + Tree::y() + v;
983
984 let mut ctx = Context::new();
985 let s = ctx.import(&s);
986
987 let s = VmShape::new(&ctx, s).unwrap();
989 let Err(e) = BoundShape::try_from(s.clone()) else {
990 panic!("expected error");
991 };
992 let vi = v.index().unwrap();
993 assert_eq!(e, MissingVar { var: vi });
994
995 let mut m = ShapeVars::new();
997 let Err(e) = s.bind(&m) else {
998 panic!("expected error");
999 };
1000 assert_eq!(e, MissingVar { var: vi });
1001
1002 m.insert(Var::new().index().unwrap(), 1.0f32);
1004 let Err(e) = s.bind(&m) else {
1005 panic!("expected error");
1006 };
1007 assert_eq!(e, MissingVar { var: vi });
1008
1009 m.insert(vi, 2.0f32);
1011 assert!(s.bind(&m).is_ok());
1012 }
1013
1014 #[test]
1015 fn shape_eval_bulk_size() {
1016 let s = Tree::constant(1.0);
1017 let mut ctx = Context::new();
1018 let s = ctx.import(&s);
1019
1020 let s = VmShape::new(&ctx, s).unwrap();
1021 let tape = s.ez_float_slice_tape();
1022 let mut eval = VmShape::new_float_slice_eval();
1023 let out = eval
1024 .eval(&tape, &[1.0, 2.0, 3.0], &[4.0, 5.0, 6.0], &[7.0, 8.0, 9.0])
1025 .unwrap();
1026 assert_eq!(out, [1.0, 1.0, 1.0]);
1027 }
1028}