1pub trait SymmetricQuadraticCoefficients {
72 fn dimension(&self) -> usize;
74
75 fn multiply(&self, input: &[f64], output: &mut [f64]);
77
78 fn coefficient(&self, row: usize, column: usize) -> f64;
80
81 fn visit_upper_triangle(
92 &self,
93 direction: &mut [f64],
94 projected: &mut [f64],
95 mut visit: impl FnMut(usize, usize, f64),
96 ) {
97 let dimension = self.dimension();
98 assert_eq!(direction.len(), dimension);
99 assert_eq!(projected.len(), dimension);
100 direction.fill(0.0);
101 for column in 0..dimension {
102 direction[column] = 1.0;
103 self.multiply(direction, projected);
104 direction[column] = 0.0;
105 for row in 0..=column {
106 visit(row, column, projected[row]);
107 }
108 }
109 }
110
111 fn quadratic_value<T, F>(&self, inputs: &[T], value: F) -> f64
116 where
117 F: Fn(&T) -> f64,
118 {
119 assert_eq!(
120 inputs.len(),
121 self.dimension(),
122 "symmetric quadratic-form dimension mismatch"
123 );
124 let mut out = 0.0;
125 for row in 0..inputs.len() {
126 let row_value = value(&inputs[row]);
127 out += self.coefficient(row, row) * row_value * row_value;
128 for column in row + 1..inputs.len() {
129 out += 2.0 * self.coefficient(row, column) * row_value * value(&inputs[column]);
130 }
131 }
132 out
133 }
134}
135
136fn symmetric_quadratic_form_default<T, C>(
137 inputs: &[T],
138 coefficients: &C,
139 constant: impl Fn(f64) -> T,
140 add: impl Fn(&T, &T) -> T,
141 mul: impl Fn(&T, &T) -> T,
142 scale: impl Fn(&T, f64) -> T,
143) -> T
144where
145 C: SymmetricQuadraticCoefficients,
146{
147 assert_eq!(
148 inputs.len(),
149 coefficients.dimension(),
150 "symmetric quadratic-form dimension mismatch"
151 );
152 let mut out = constant(0.0);
153 for row in 0..inputs.len() {
154 let diagonal = mul(&inputs[row], &inputs[row]);
155 out = add(&out, &scale(&diagonal, coefficients.coefficient(row, row)));
156 for column in row + 1..inputs.len() {
157 let cross = mul(&inputs[row], &inputs[column]);
158 out = add(
159 &out,
160 &scale(&cross, 2.0 * coefficients.coefficient(row, column)),
161 );
162 }
163 }
164 out
165}
166
167fn linear_combination_default<T>(
168 inputs: &[T],
169 weights: &[f64],
170 constant: impl Fn(f64) -> T,
171 add: impl Fn(&T, &T) -> T,
172 scale: impl Fn(&T, f64) -> T,
173) -> T {
174 assert_eq!(
175 inputs.len(),
176 weights.len(),
177 "linear-combination dimension mismatch"
178 );
179 inputs
180 .iter()
181 .zip(weights)
182 .fold(constant(0.0), |sum, (input, &weight)| {
183 add(&sum, &scale(input, weight))
184 })
185}
186
187fn multiply_add_default<T>(
188 left: &T,
189 right: &T,
190 addend: &T,
191 mul: impl Fn(&T, &T) -> T,
192 add: impl Fn(&T, &T) -> T,
193) -> T {
194 add(&mul(left, right), addend)
195}
196
197fn composed_sum_default<T>(
198 inputs: &[T],
199 derivative_stacks: &[[f64; 5]],
200 constant: impl Fn(f64) -> T,
201 add: impl Fn(&T, &T) -> T,
202 compose: impl Fn(&T, [f64; 5]) -> T,
203) -> T {
204 assert_eq!(
205 inputs.len(),
206 derivative_stacks.len(),
207 "composed-sum term-count mismatch"
208 );
209 inputs
210 .iter()
211 .zip(derivative_stacks)
212 .fold(constant(0.0), |sum, (input, &stack)| {
213 add(&sum, &compose(input, stack))
214 })
215}
216
217fn affine_compose_default<T>(
218 input: &T,
219 input_scale: f64,
220 input_shift: f64,
221 derivative_stack: [f64; 5],
222 scale: impl Fn(&T, f64) -> T,
223 add_constant: impl Fn(&T, f64) -> T,
224 compose: impl Fn(&T, [f64; 5]) -> T,
225) -> T {
226 compose(
227 &add_constant(&scale(input, input_scale), input_shift),
228 derivative_stack,
229 )
230}
231
232fn affine_composed_sum_default<T>(
233 inputs: &[T],
234 input_scales: &[f64],
235 derivative_stacks: &[[f64; 5]],
236 constant: impl Fn(f64) -> T,
237 add: impl Fn(&T, &T) -> T,
238 scale: impl Fn(&T, f64) -> T,
239 add_constant: impl Fn(&T, f64) -> T,
240 compose: impl Fn(&T, [f64; 5]) -> T,
241) -> T {
242 assert_eq!(inputs.len(), input_scales.len());
243 assert_eq!(inputs.len(), derivative_stacks.len());
244 inputs.iter().zip(input_scales).zip(derivative_stacks).fold(
245 constant(0.0),
246 |sum, ((input, &input_scale), &stack)| {
247 add(
248 &sum,
249 &affine_compose_default(
250 input,
251 input_scale,
252 0.0,
253 stack,
254 &scale,
255 &add_constant,
256 &compose,
257 ),
258 )
259 },
260 )
261}
262
263fn shared_multiply_add_affine_composed_sum_default<T, const N: usize>(
264 lefts: &[&T; N],
265 right: &T,
266 addend: &T,
267 addend_scales: &[f64; N],
268 input_scales: &[f64; N],
269 derivative_stacks: &[[f64; 5]; N],
270 constant: impl Fn(f64) -> T,
271 add: impl Fn(&T, &T) -> T,
272 mul: impl Fn(&T, &T) -> T,
273 scale: impl Fn(&T, f64) -> T,
274 multiply_add: impl Fn(&T, &T, &T) -> T,
275 affine_compose: impl Fn(&T, f64, f64, [f64; 5]) -> T,
276) -> T {
277 let (representatives, term_sources, source_count) =
278 canonical_shared_source_schedule(|term, representative| {
279 std::ptr::eq(lefts[term], lefts[representative])
280 && addend_scales[term] == addend_scales[representative]
281 });
282 let (value, source_derivatives) =
283 aggregate_shared_source_derivatives(&term_sources, input_scales, derivative_stacks);
284 (0..source_count).fold(constant(value), |sum, source| {
285 let term = representatives[source];
286 let inner = if addend_scales[term] == 0.0 {
287 mul(lefts[term], right)
288 } else if addend_scales[term] == 1.0 {
289 multiply_add(lefts[term], right, addend)
290 } else {
291 multiply_add(lefts[term], right, &scale(addend, addend_scales[term]))
292 };
293 let composed = affine_compose(&inner, 1.0, 0.0, source_derivatives[source]);
294 add(&sum, &composed)
295 })
296}
297
298#[inline(always)]
306pub(crate) fn canonical_shared_source_schedule<const N: usize>(
307 mut equivalent: impl FnMut(usize, usize) -> bool,
308) -> ([usize; N], [usize; N], usize) {
309 let mut representatives = [0; N];
310 let mut term_sources = [0; N];
311 let mut source_count = 0;
312 for term in 0..N {
313 let mut source = 0;
314 while source < source_count && !equivalent(term, representatives[source]) {
315 source += 1;
316 }
317 if source == source_count {
318 representatives[source] = term;
319 source_count += 1;
320 }
321 term_sources[term] = source;
322 }
323 (representatives, term_sources, source_count)
324}
325
326#[inline(always)]
333pub(crate) fn aggregate_shared_source_derivatives<const N: usize>(
334 term_sources: &[usize; N],
335 input_scales: &[f64; N],
336 derivative_stacks: &[[f64; 5]; N],
337) -> (f64, [[f64; 5]; N]) {
338 let mut value = 0.0;
339 let mut source_derivatives = [[0.0; 5]; N];
340 for term in 0..N {
341 value += derivative_stacks[term][0];
342 let source = term_sources[term];
343 let mut scale_power = input_scales[term];
344 for order in 1..5 {
345 source_derivatives[source][order] += derivative_stacks[term][order] * scale_power;
346 scale_power *= input_scales[term];
347 }
348 }
349 (value, source_derivatives)
350}
351
352pub trait JetScalar<const K: usize>: crate::nested_dual::JetField + Copy {
360 fn constant(c: f64) -> Self;
362
363 fn variable(x: f64, axis: usize) -> Self;
368
369 fn symmetric_quadratic_form<C: SymmetricQuadraticCoefficients>(
373 inputs: &[Self],
374 coefficients: &C,
375 ) -> Self {
376 symmetric_quadratic_form_default(
377 inputs,
378 coefficients,
379 Self::constant,
380 crate::nested_dual::JetField::add,
381 crate::nested_dual::JetField::mul,
382 crate::nested_dual::JetField::scale,
383 )
384 }
385
386 fn linear_combination(inputs: &[Self], weights: &[f64]) -> Self {
388 linear_combination_default(
389 inputs,
390 weights,
391 Self::constant,
392 crate::nested_dual::JetField::add,
393 crate::nested_dual::JetField::scale,
394 )
395 }
396
397 fn add_constant(&self, constant: f64) -> Self {
399 self.add(&Self::constant(constant))
400 }
401
402 fn multiply_add(&self, right: &Self, addend: &Self) -> Self {
404 multiply_add_default(
405 self,
406 right,
407 addend,
408 crate::nested_dual::JetField::mul,
409 crate::nested_dual::JetField::add,
410 )
411 }
412
413 fn composed_sum(inputs: &[Self], derivative_stacks: &[[f64; 5]]) -> Self {
415 composed_sum_default(
416 inputs,
417 derivative_stacks,
418 Self::constant,
419 crate::nested_dual::JetField::add,
420 crate::nested_dual::JetField::compose_unary,
421 )
422 }
423
424 fn product(&self, right: &Self) -> Self {
426 self.mul(right)
427 }
428
429 fn affine_compose(
432 &self,
433 input_scale: f64,
434 input_shift: f64,
435 derivative_stack: [f64; 5],
436 ) -> Self {
437 affine_compose_default(
438 self,
439 input_scale,
440 input_shift,
441 derivative_stack,
442 crate::nested_dual::JetField::scale,
443 Self::add_constant,
444 crate::nested_dual::JetField::compose_unary,
445 )
446 }
447
448 fn affine_composed_sum(
450 inputs: &[Self],
451 input_scales: &[f64],
452 derivative_stacks: &[[f64; 5]],
453 ) -> Self {
454 affine_composed_sum_default(
455 inputs,
456 input_scales,
457 derivative_stacks,
458 Self::constant,
459 crate::nested_dual::JetField::add,
460 crate::nested_dual::JetField::scale,
461 Self::add_constant,
462 crate::nested_dual::JetField::compose_unary,
463 )
464 }
465
466 fn shared_multiply_add_affine_composed_sum<const N: usize>(
475 lefts: &[&Self; N],
476 right: &Self,
477 addend: &Self,
478 addend_scales: &[f64; N],
479 input_scales: &[f64; N],
480 derivative_stacks: &[[f64; 5]; N],
481 ) -> Self {
482 shared_multiply_add_affine_composed_sum_default(
483 lefts,
484 right,
485 addend,
486 addend_scales,
487 input_scales,
488 derivative_stacks,
489 Self::constant,
490 crate::nested_dual::JetField::add,
491 crate::nested_dual::JetField::mul,
492 crate::nested_dual::JetField::scale,
493 Self::multiply_add,
494 Self::affine_compose,
495 )
496 }
497
498 fn compose_unary_with(&self, stack_fn: impl Fn(f64) -> [f64; 5]) -> Self {
511 self.compose_unary(stack_fn(self.value()))
512 }
513
514 fn exp(&self) -> Self {
516 let e = self.value().exp();
517 self.compose_unary([e, e, e, e, e])
518 }
519
520 fn sqrt(&self) -> Self {
522 let u = self.value();
523 let s = u.sqrt();
524 self.compose_unary([
525 s,
526 0.5 / s,
527 -0.25 / (u * s),
528 0.375 / (u * u * s),
529 -0.9375 / (u * u * u * s),
530 ])
531 }
532
533 fn ln(&self) -> Self {
537 let u = self.value();
538 let r = 1.0 / u;
539 self.compose_unary([u.ln(), r, -r * r, 2.0 * r * r * r, -6.0 * r * r * r * r])
540 }
541
542 fn recip(&self) -> Self {
544 let r = 1.0 / self.value();
545 let r2 = r * r;
546 self.compose_unary([r, -r2, 2.0 * r2 * r, -6.0 * r2 * r2, 24.0 * r2 * r2 * r])
547 }
548
549 fn powf(&self, a: f64) -> Self {
552 let u = self.value();
553 self.compose_unary([
554 u.powf(a),
555 a * u.powf(a - 1.0),
556 a * (a - 1.0) * u.powf(a - 2.0),
557 a * (a - 1.0) * (a - 2.0) * u.powf(a - 3.0),
558 a * (a - 1.0) * (a - 2.0) * (a - 3.0) * u.powf(a - 4.0),
559 ])
560 }
561
562 fn ln_gamma(&self) -> Self {
567 self.compose_unary(crate::jet_tower::ln_gamma_derivative_stack(self.value()))
568 }
569
570 fn digamma(&self) -> Self {
574 self.compose_unary(crate::jet_tower::digamma_derivative_stack(self.value()))
575 }
576}
577
578impl<S, const K: usize> JetScalar<K> for crate::nested_dual::Dual2<S>
583where
584 S: JetScalar<K>,
585{
586 #[inline]
587 fn constant(c: f64) -> Self {
588 Self {
589 v: S::constant(c),
590 g: S::constant(0.0),
591 h: S::constant(0.0),
592 }
593 }
594
595 #[inline]
596 fn variable(x: f64, axis: usize) -> Self {
597 Self {
598 v: S::variable(x, axis),
599 g: S::constant(0.0),
600 h: S::constant(0.0),
601 }
602 }
603}
604
605pub trait RuntimeJetScalar<'arena>: Clone {
613 type Workspace: ?Sized;
616
617 fn constant(c: f64, dimension: usize, workspace: &'arena Self::Workspace) -> Self;
619 fn variable(x: f64, axis: usize, dimension: usize, workspace: &'arena Self::Workspace) -> Self;
621 fn constant_like(&self, c: f64) -> Self;
628 fn with_value(&self, value: f64) -> Self;
630
631 fn symmetric_quadratic_form<C: SymmetricQuadraticCoefficients>(
634 inputs: &[Self],
635 coefficients: &C,
636 dimension: usize,
637 workspace: &'arena Self::Workspace,
638 ) -> Self {
639 symmetric_quadratic_form_default(
640 inputs,
641 coefficients,
642 |value| Self::constant(value, dimension, workspace),
643 Self::add,
644 Self::mul,
645 Self::scale,
646 )
647 }
648
649 fn linear_combination(
651 inputs: &[Self],
652 weights: &[f64],
653 dimension: usize,
654 workspace: &'arena Self::Workspace,
655 ) -> Self {
656 linear_combination_default(
657 inputs,
658 weights,
659 |value| Self::constant(value, dimension, workspace),
660 Self::add,
661 Self::scale,
662 )
663 }
664
665 fn add_constant(&self, constant: f64) -> Self {
667 self.with_value(self.value() + constant)
668 }
669
670 fn multiply_add(&self, right: &Self, addend: &Self) -> Self {
672 multiply_add_default(self, right, addend, Self::mul, Self::add)
673 }
674
675 fn composed_sum(
677 inputs: &[Self],
678 derivative_stacks: &[[f64; 5]],
679 dimension: usize,
680 workspace: &'arena Self::Workspace,
681 ) -> Self {
682 composed_sum_default(
683 inputs,
684 derivative_stacks,
685 |value| Self::constant(value, dimension, workspace),
686 Self::add,
687 Self::compose_unary,
688 )
689 }
690
691 fn product(&self, right: &Self) -> Self {
693 self.mul(right)
694 }
695
696 fn affine_compose(
698 &self,
699 input_scale: f64,
700 input_shift: f64,
701 derivative_stack: [f64; 5],
702 ) -> Self {
703 affine_compose_default(
704 self,
705 input_scale,
706 input_shift,
707 derivative_stack,
708 Self::scale,
709 |value, constant| value.add_constant(constant),
710 Self::compose_unary,
711 )
712 }
713
714 fn affine_composed_sum(
716 inputs: &[Self],
717 input_scales: &[f64],
718 derivative_stacks: &[[f64; 5]],
719 dimension: usize,
720 workspace: &'arena Self::Workspace,
721 ) -> Self {
722 affine_composed_sum_default(
723 inputs,
724 input_scales,
725 derivative_stacks,
726 |value| Self::constant(value, dimension, workspace),
727 Self::add,
728 Self::scale,
729 |value, constant| value.add_constant(constant),
730 Self::compose_unary,
731 )
732 }
733
734 fn shared_multiply_add_affine_composed_sum<const N: usize>(
742 lefts: &[&Self; N],
743 right: &Self,
744 addend: &Self,
745 addend_scales: &[f64; N],
746 input_scales: &[f64; N],
747 derivative_stacks: &[[f64; 5]; N],
748 dimension: usize,
749 workspace: &'arena Self::Workspace,
750 ) -> Self {
751 shared_multiply_add_affine_composed_sum_default(
752 lefts,
753 right,
754 addend,
755 addend_scales,
756 input_scales,
757 derivative_stacks,
758 |value| Self::constant(value, dimension, workspace),
759 Self::add,
760 Self::mul,
761 Self::scale,
762 Self::multiply_add,
763 |input, scale, shift, stack| input.affine_compose(scale, shift, stack),
764 )
765 }
766 fn dimension(&self) -> usize;
768 fn value(&self) -> f64;
770 fn add(&self, o: &Self) -> Self;
772 fn sub(&self, o: &Self) -> Self;
774 fn mul(&self, o: &Self) -> Self;
776 fn neg(&self) -> Self;
778 fn scale(&self, s: f64) -> Self;
780 fn compose_unary(&self, d: [f64; 5]) -> Self;
782
783 fn exp(&self) -> Self {
785 let e = self.value().exp();
786 self.compose_unary([e, e, e, e, e])
787 }
788
789 fn ln(&self) -> Self {
793 let u = self.value();
794 let r = 1.0 / u;
795 self.compose_unary([u.ln(), r, -r * r, 2.0 * r * r * r, -6.0 * r * r * r * r])
796 }
797
798 fn recip(&self) -> Self {
800 let r = 1.0 / self.value();
801 let r2 = r * r;
802 self.compose_unary([r, -r2, 2.0 * r2 * r, -6.0 * r2 * r2, 24.0 * r2 * r2 * r])
803 }
804}
805
806#[derive(Clone, Copy, Debug, PartialEq)]
814pub struct RuntimeValue {
815 value: f64,
816 dimension: usize,
817}
818
819impl<'arena> RuntimeJetScalar<'arena> for RuntimeValue {
820 type Workspace = ();
821
822 #[inline(always)]
823 fn constant(c: f64, dimension: usize, &(): &'arena Self::Workspace) -> Self {
824 Self {
825 value: c,
826 dimension,
827 }
828 }
829
830 #[inline(always)]
831 fn variable(x: f64, axis: usize, dimension: usize, &(): &'arena Self::Workspace) -> Self {
832 assert!(
833 axis < dimension,
834 "runtime value variable axis out of bounds"
835 );
836 Self {
837 value: x,
838 dimension,
839 }
840 }
841
842 #[inline(always)]
843 fn constant_like(&self, c: f64) -> Self {
844 Self {
845 value: c,
846 dimension: self.dimension,
847 }
848 }
849
850 #[inline(always)]
851 fn with_value(&self, value: f64) -> Self {
852 Self {
853 value,
854 dimension: self.dimension,
855 }
856 }
857
858 #[inline(always)]
859 fn symmetric_quadratic_form<C: SymmetricQuadraticCoefficients>(
860 inputs: &[Self],
861 coefficients: &C,
862 dimension: usize,
863 &(): &'arena Self::Workspace,
864 ) -> Self {
865 assert_eq!(inputs.len(), coefficients.dimension());
866 assert!(inputs.iter().all(|input| input.dimension == dimension));
867 Self {
868 value: coefficients.quadratic_value(inputs, |input| input.value),
869 dimension,
870 }
871 }
872
873 #[inline(always)]
874 fn linear_combination(
875 inputs: &[Self],
876 weights: &[f64],
877 dimension: usize,
878 &(): &'arena Self::Workspace,
879 ) -> Self {
880 assert_eq!(inputs.len(), weights.len());
881 assert!(inputs.iter().all(|input| input.dimension == dimension));
882 let value = inputs
883 .iter()
884 .zip(weights)
885 .map(|(input, &weight)| input.value * weight)
886 .sum();
887 Self { value, dimension }
888 }
889
890 #[inline(always)]
891 fn multiply_add(&self, right: &Self, addend: &Self) -> Self {
892 self.assert_same_dimension(right);
893 self.assert_same_dimension(addend);
894 Self {
895 value: self.value * right.value + addend.value,
896 dimension: self.dimension,
897 }
898 }
899
900 #[inline(always)]
901 fn composed_sum(
902 inputs: &[Self],
903 derivative_stacks: &[[f64; 5]],
904 dimension: usize,
905 &(): &'arena Self::Workspace,
906 ) -> Self {
907 assert_eq!(inputs.len(), derivative_stacks.len());
908 assert!(inputs.iter().all(|input| input.dimension == dimension));
909 Self {
910 value: derivative_stacks.iter().map(|stack| stack[0]).sum(),
911 dimension,
912 }
913 }
914
915 #[inline(always)]
916 fn product(&self, right: &Self) -> Self {
917 self.mul(right)
918 }
919
920 #[inline(always)]
921 fn affine_compose(
922 &self,
923 input_scale: f64,
924 input_shift: f64,
925 derivative_stack: [f64; 5],
926 ) -> Self {
927 affine_compose_default(
933 self,
934 input_scale,
935 input_shift,
936 derivative_stack,
937 Self::scale,
938 |value, constant| value.add_constant(constant),
939 Self::compose_unary,
940 )
941 }
942
943 #[inline(always)]
944 fn affine_composed_sum(
945 inputs: &[Self],
946 input_scales: &[f64],
947 derivative_stacks: &[[f64; 5]],
948 dimension: usize,
949 &(): &'arena Self::Workspace,
950 ) -> Self {
951 assert_eq!(inputs.len(), input_scales.len());
952 assert_eq!(inputs.len(), derivative_stacks.len());
953 assert!(inputs.iter().all(|input| input.dimension == dimension));
954 Self {
955 value: derivative_stacks.iter().map(|stack| stack[0]).sum(),
956 dimension,
957 }
958 }
959
960 #[inline(always)]
961 fn dimension(&self) -> usize {
962 self.dimension
963 }
964
965 #[inline(always)]
966 fn value(&self) -> f64 {
967 self.value
968 }
969
970 #[inline(always)]
971 fn add(&self, other: &Self) -> Self {
972 self.assert_same_dimension(other);
973 Self {
974 value: self.value + other.value,
975 dimension: self.dimension,
976 }
977 }
978
979 #[inline(always)]
980 fn sub(&self, other: &Self) -> Self {
981 self.assert_same_dimension(other);
982 Self {
983 value: self.value - other.value,
984 dimension: self.dimension,
985 }
986 }
987
988 #[inline(always)]
989 fn mul(&self, other: &Self) -> Self {
990 self.assert_same_dimension(other);
991 Self {
992 value: self.value * other.value,
993 dimension: self.dimension,
994 }
995 }
996
997 #[inline(always)]
998 fn neg(&self) -> Self {
999 Self {
1000 value: -self.value,
1001 dimension: self.dimension,
1002 }
1003 }
1004
1005 #[inline(always)]
1006 fn scale(&self, scale: f64) -> Self {
1007 Self {
1008 value: self.value * scale,
1009 dimension: self.dimension,
1010 }
1011 }
1012
1013 #[inline(always)]
1014 fn compose_unary(&self, derivative_stack: [f64; 5]) -> Self {
1015 Self {
1016 value: derivative_stack[0],
1017 dimension: self.dimension,
1018 }
1019 }
1020}
1021
1022impl RuntimeValue {
1023 #[inline(always)]
1024 fn assert_same_dimension(&self, other: &Self) {
1025 assert_eq!(self.dimension, other.dimension);
1026 }
1027}
1028
1029#[derive(Clone, Copy, Debug)]
1034#[repr(transparent)]
1035pub struct FixedRuntimeJet<S, const K: usize> {
1036 inner: S,
1037}
1038
1039impl<S, const K: usize> FixedRuntimeJet<S, K> {
1040 #[inline(always)]
1043 #[must_use]
1044 pub fn from_inner(inner: S) -> Self {
1045 Self { inner }
1046 }
1047
1048 #[inline(always)]
1050 #[must_use]
1051 pub fn into_inner(self) -> S {
1052 self.inner
1053 }
1054}
1055
1056impl<'arena, S: JetScalar<K>, const K: usize> RuntimeJetScalar<'arena> for FixedRuntimeJet<S, K> {
1057 type Workspace = ();
1058
1059 #[inline(always)]
1060 fn constant(c: f64, dimension: usize, &(): &'arena Self::Workspace) -> Self {
1061 assert_eq!(dimension, K, "fixed jet dimension mismatch");
1062 Self {
1063 inner: S::constant(c),
1064 }
1065 }
1066
1067 #[inline(always)]
1068 fn variable(x: f64, axis: usize, dimension: usize, &(): &'arena Self::Workspace) -> Self {
1069 assert_eq!(dimension, K, "fixed jet dimension mismatch");
1070 Self {
1071 inner: S::variable(x, axis),
1072 }
1073 }
1074
1075 #[inline(always)]
1076 fn constant_like(&self, c: f64) -> Self {
1077 Self {
1078 inner: S::constant(c),
1079 }
1080 }
1081
1082 #[inline(always)]
1083 fn with_value(&self, value: f64) -> Self {
1084 Self {
1085 inner: self.inner.compose_unary([value, 1.0, 0.0, 0.0, 0.0]),
1086 }
1087 }
1088
1089 #[inline(always)]
1090 fn symmetric_quadratic_form<C: SymmetricQuadraticCoefficients>(
1091 inputs: &[Self],
1092 coefficients: &C,
1093 dimension: usize,
1094 &(): &'arena Self::Workspace,
1095 ) -> Self {
1096 assert_eq!(dimension, K, "fixed jet dimension mismatch");
1097 assert_eq!(inputs.len(), coefficients.dimension());
1098 let inner =
1104 unsafe { std::slice::from_raw_parts(inputs.as_ptr().cast::<S>(), inputs.len()) };
1105 Self {
1106 inner: S::symmetric_quadratic_form(inner, coefficients),
1107 }
1108 }
1109
1110 #[inline(always)]
1111 fn linear_combination(
1112 inputs: &[Self],
1113 weights: &[f64],
1114 dimension: usize,
1115 &(): &'arena Self::Workspace,
1116 ) -> Self {
1117 assert_eq!(dimension, K, "fixed jet dimension mismatch");
1118 assert_eq!(inputs.len(), weights.len());
1119 let inner =
1123 unsafe { std::slice::from_raw_parts(inputs.as_ptr().cast::<S>(), inputs.len()) };
1124 Self {
1125 inner: S::linear_combination(inner, weights),
1126 }
1127 }
1128
1129 #[inline(always)]
1130 fn add_constant(&self, constant: f64) -> Self {
1131 Self {
1132 inner: self.inner.add_constant(constant),
1133 }
1134 }
1135
1136 #[inline(always)]
1137 fn multiply_add(&self, right: &Self, addend: &Self) -> Self {
1138 Self {
1139 inner: self.inner.multiply_add(&right.inner, &addend.inner),
1140 }
1141 }
1142
1143 #[inline(always)]
1144 fn composed_sum(
1145 inputs: &[Self],
1146 derivative_stacks: &[[f64; 5]],
1147 dimension: usize,
1148 &(): &'arena Self::Workspace,
1149 ) -> Self {
1150 assert_eq!(dimension, K, "fixed jet dimension mismatch");
1151 let inner =
1155 unsafe { std::slice::from_raw_parts(inputs.as_ptr().cast::<S>(), inputs.len()) };
1156 Self {
1157 inner: S::composed_sum(inner, derivative_stacks),
1158 }
1159 }
1160
1161 #[inline(always)]
1162 fn product(&self, right: &Self) -> Self {
1163 Self {
1164 inner: self.inner.product(&right.inner),
1165 }
1166 }
1167
1168 #[inline(always)]
1169 fn affine_compose(
1170 &self,
1171 input_scale: f64,
1172 input_shift: f64,
1173 derivative_stack: [f64; 5],
1174 ) -> Self {
1175 Self {
1176 inner: self
1177 .inner
1178 .affine_compose(input_scale, input_shift, derivative_stack),
1179 }
1180 }
1181
1182 #[inline(always)]
1183 fn affine_composed_sum(
1184 inputs: &[Self],
1185 input_scales: &[f64],
1186 derivative_stacks: &[[f64; 5]],
1187 dimension: usize,
1188 &(): &'arena Self::Workspace,
1189 ) -> Self {
1190 assert_eq!(dimension, K, "fixed jet dimension mismatch");
1191 let inner =
1195 unsafe { std::slice::from_raw_parts(inputs.as_ptr().cast::<S>(), inputs.len()) };
1196 Self {
1197 inner: S::affine_composed_sum(inner, input_scales, derivative_stacks),
1198 }
1199 }
1200
1201 #[inline(always)]
1202 fn shared_multiply_add_affine_composed_sum<const N: usize>(
1203 lefts: &[&Self; N],
1204 right: &Self,
1205 addend: &Self,
1206 addend_scales: &[f64; N],
1207 input_scales: &[f64; N],
1208 derivative_stacks: &[[f64; 5]; N],
1209 dimension: usize,
1210 &(): &'arena Self::Workspace,
1211 ) -> Self {
1212 assert_eq!(dimension, K, "fixed jet dimension mismatch");
1213 let left_inner: [&S; N] = std::array::from_fn(|term| &lefts[term].inner);
1214 Self {
1215 inner: S::shared_multiply_add_affine_composed_sum(
1216 &left_inner,
1217 &right.inner,
1218 &addend.inner,
1219 addend_scales,
1220 input_scales,
1221 derivative_stacks,
1222 ),
1223 }
1224 }
1225
1226 #[inline(always)]
1227 fn dimension(&self) -> usize {
1228 K
1229 }
1230
1231 #[inline(always)]
1232 fn value(&self) -> f64 {
1233 self.inner.value()
1234 }
1235
1236 #[inline(always)]
1237 fn add(&self, o: &Self) -> Self {
1238 Self {
1239 inner: self.inner.add(&o.inner),
1240 }
1241 }
1242
1243 #[inline(always)]
1244 fn sub(&self, o: &Self) -> Self {
1245 Self {
1246 inner: self.inner.sub(&o.inner),
1247 }
1248 }
1249
1250 #[inline(always)]
1251 fn mul(&self, o: &Self) -> Self {
1252 Self {
1253 inner: self.inner.mul(&o.inner),
1254 }
1255 }
1256
1257 #[inline(always)]
1258 fn neg(&self) -> Self {
1259 Self {
1260 inner: self.inner.neg(),
1261 }
1262 }
1263
1264 #[inline(always)]
1265 fn scale(&self, s: f64) -> Self {
1266 Self {
1267 inner: self.inner.scale(s),
1268 }
1269 }
1270
1271 #[inline(always)]
1272 fn compose_unary(&self, d: [f64; 5]) -> Self {
1273 Self {
1274 inner: self.inner.compose_unary(d),
1275 }
1276 }
1277}
1278
1279#[derive(Debug)]
1283pub struct DynamicJetArena {
1284 bump: bumpalo::Bump,
1285}
1286
1287impl DynamicJetArena {
1288 #[must_use]
1290 pub fn new() -> Self {
1291 Self {
1292 bump: bumpalo::Bump::new(),
1293 }
1294 }
1295
1296 #[must_use]
1298 pub fn with_capacity(bytes: usize) -> Self {
1299 Self {
1300 bump: bumpalo::Bump::with_capacity(bytes),
1301 }
1302 }
1303
1304 pub fn reset(&mut self) {
1315 let high_water = self.bump.allocated_bytes();
1316 self.bump.reset();
1317 if self.bump.allocated_bytes() < high_water {
1318 self.bump = bumpalo::Bump::with_capacity(high_water);
1319 }
1320 }
1321
1322 #[must_use]
1325 pub fn allocated_bytes(&self) -> usize {
1326 self.bump.allocated_bytes()
1327 }
1328
1329 #[inline(always)]
1330 fn zeros(&self, len: usize) -> &mut [f64] {
1331 self.bump.alloc_slice_fill_copy(len, 0.0)
1332 }
1333
1334 #[inline(always)]
1338 pub fn alloc_slice_fill_with<T>(&self, len: usize, fill: impl FnMut(usize) -> T) -> &mut [T] {
1339 self.bump.alloc_slice_fill_with(len, fill)
1340 }
1341}
1342
1343impl Default for DynamicJetArena {
1344 fn default() -> Self {
1345 Self::new()
1346 }
1347}
1348
1349#[derive(Clone, Copy, Debug)]
1351pub struct DynamicOrder1<'arena> {
1352 arena: &'arena DynamicJetArena,
1353 pub v: f64,
1355 pub g: &'arena [f64],
1357}
1358
1359impl DynamicOrder1<'_> {
1360 #[inline]
1362 #[must_use]
1363 pub fn g(&self) -> &[f64] {
1364 self.g
1365 }
1366
1367 #[inline]
1368 fn assert_compatible(&self, o: &Self) {
1369 assert_eq!(
1370 self.g.len(),
1371 o.g.len(),
1372 "dynamic first-order jet dimension mismatch"
1373 );
1374 assert!(
1375 std::ptr::eq(self.arena, o.arena),
1376 "dynamic jets belong to different arenas"
1377 );
1378 }
1379}
1380
1381impl<'arena> RuntimeJetScalar<'arena> for DynamicOrder1<'arena> {
1382 type Workspace = DynamicJetArena;
1383
1384 fn constant(c: f64, dimension: usize, arena: &'arena DynamicJetArena) -> Self {
1385 Self {
1386 arena,
1387 v: c,
1388 g: arena.zeros(dimension),
1389 }
1390 }
1391
1392 fn variable(x: f64, axis: usize, dimension: usize, arena: &'arena DynamicJetArena) -> Self {
1393 assert!(
1394 axis < dimension,
1395 "dynamic first-order jet axis out of bounds"
1396 );
1397 let g = arena.zeros(dimension);
1398 g[axis] = 1.0;
1399 Self { arena, v: x, g }
1400 }
1401
1402 #[inline(always)]
1403 fn constant_like(&self, c: f64) -> Self {
1404 Self {
1405 arena: self.arena,
1406 v: c,
1407 g: self.arena.zeros(self.dimension()),
1408 }
1409 }
1410
1411 #[inline(always)]
1412 fn with_value(&self, value: f64) -> Self {
1413 Self {
1414 arena: self.arena,
1415 v: value,
1416 g: self.g,
1417 }
1418 }
1419
1420 fn dimension(&self) -> usize {
1421 self.g.len()
1422 }
1423 fn value(&self) -> f64 {
1424 self.v
1425 }
1426
1427 fn add(&self, o: &Self) -> Self {
1428 self.assert_compatible(o);
1429 let g = self.arena.zeros(self.dimension());
1430 for i in 0..g.len() {
1431 g[i] = self.g[i] + o.g[i];
1432 }
1433 Self {
1434 arena: self.arena,
1435 v: self.v + o.v,
1436 g,
1437 }
1438 }
1439
1440 fn sub(&self, o: &Self) -> Self {
1441 self.assert_compatible(o);
1442 let g = self.arena.zeros(self.dimension());
1443 for i in 0..g.len() {
1444 g[i] = self.g[i] - o.g[i];
1445 }
1446 Self {
1447 arena: self.arena,
1448 v: self.v - o.v,
1449 g,
1450 }
1451 }
1452
1453 fn mul(&self, o: &Self) -> Self {
1454 self.assert_compatible(o);
1455 let g = self.arena.zeros(self.dimension());
1456 for i in 0..g.len() {
1457 g[i] = self.v * o.g[i] + self.g[i] * o.v;
1458 }
1459 Self {
1460 arena: self.arena,
1461 v: self.v * o.v,
1462 g,
1463 }
1464 }
1465
1466 fn neg(&self) -> Self {
1467 self.scale(-1.0)
1468 }
1469
1470 fn scale(&self, s: f64) -> Self {
1471 let g = self.arena.zeros(self.dimension());
1472 for i in 0..g.len() {
1473 g[i] = self.g[i] * s;
1474 }
1475 Self {
1476 arena: self.arena,
1477 v: self.v * s,
1478 g,
1479 }
1480 }
1481
1482 fn compose_unary(&self, d: [f64; 5]) -> Self {
1483 let g = self.arena.zeros(self.dimension());
1484 for i in 0..g.len() {
1485 g[i] = d[1] * self.g[i];
1486 }
1487 Self {
1488 arena: self.arena,
1489 v: d[0],
1490 g,
1491 }
1492 }
1493}
1494
1495#[derive(Clone, Copy, Debug)]
1499pub struct DynamicOrder2<'arena> {
1500 arena: &'arena DynamicJetArena,
1501 pub v: f64,
1503 pub g: &'arena [f64],
1505 pub h: &'arena [f64],
1507}
1508
1509impl DynamicOrder2<'_> {
1510 #[inline]
1520 #[must_use]
1521 pub fn from_channel_functions<'arena>(
1522 value: f64,
1523 dimension: usize,
1524 arena: &'arena DynamicJetArena,
1525 mut gradient: impl FnMut(usize) -> f64,
1526 mut hessian: impl FnMut(usize, usize) -> f64,
1527 ) -> DynamicOrder2<'arena> {
1528 let g = arena.alloc_slice_fill_with(dimension, |axis| gradient(axis));
1529 let h = arena.zeros(dimension * dimension);
1530 for row in 0..dimension {
1531 for column in row..dimension {
1532 let channel = hessian(row, column);
1533 h[row * dimension + column] = channel;
1534 h[column * dimension + row] = channel;
1535 }
1536 }
1537 DynamicOrder2 {
1538 arena,
1539 v: value,
1540 g,
1541 h,
1542 }
1543 }
1544
1545 #[inline]
1547 #[must_use]
1548 pub fn g(&self) -> &[f64] {
1549 self.g
1550 }
1551
1552 #[inline]
1554 #[must_use]
1555 pub fn h(&self) -> &[f64] {
1556 self.h
1557 }
1558
1559 #[inline]
1561 #[must_use]
1562 pub fn h_at(&self, row: usize, col: usize) -> f64 {
1563 self.h[row * self.dimension() + col]
1564 }
1565
1566 #[inline(always)]
1567 fn assert_compatible(&self, o: &Self) {
1568 assert_eq!(
1569 self.g.len(),
1570 o.g.len(),
1571 "dynamic second-order jet dimension mismatch"
1572 );
1573 assert_eq!(
1574 self.h.len(),
1575 o.h.len(),
1576 "dynamic second-order jet Hessian mismatch"
1577 );
1578 assert!(
1579 std::ptr::eq(self.arena, o.arena),
1580 "dynamic jets belong to different arenas"
1581 );
1582 }
1583}
1584
1585impl<'arena> RuntimeJetScalar<'arena> for DynamicOrder2<'arena> {
1586 type Workspace = DynamicJetArena;
1587
1588 #[inline(always)]
1589 fn constant(c: f64, dimension: usize, arena: &'arena DynamicJetArena) -> Self {
1590 Self {
1591 arena,
1592 v: c,
1593 g: arena.zeros(dimension),
1594 h: arena.zeros(dimension * dimension),
1595 }
1596 }
1597
1598 #[inline(always)]
1599 fn variable(x: f64, axis: usize, dimension: usize, arena: &'arena DynamicJetArena) -> Self {
1600 assert!(
1601 axis < dimension,
1602 "dynamic second-order jet axis out of bounds"
1603 );
1604 let g = arena.zeros(dimension);
1605 g[axis] = 1.0;
1606 Self {
1607 arena,
1608 v: x,
1609 g,
1610 h: arena.zeros(dimension * dimension),
1611 }
1612 }
1613
1614 #[inline(always)]
1615 fn constant_like(&self, c: f64) -> Self {
1616 let dimension = self.dimension();
1617 Self {
1618 arena: self.arena,
1619 v: c,
1620 g: self.arena.zeros(dimension),
1621 h: self.arena.zeros(dimension * dimension),
1622 }
1623 }
1624
1625 #[inline(always)]
1626 fn with_value(&self, value: f64) -> Self {
1627 Self {
1628 arena: self.arena,
1629 v: value,
1630 g: self.g,
1631 h: self.h,
1632 }
1633 }
1634
1635 #[inline(always)]
1636 fn symmetric_quadratic_form<C: SymmetricQuadraticCoefficients>(
1637 inputs: &[Self],
1638 coefficients: &C,
1639 dimension: usize,
1640 arena: &'arena DynamicJetArena,
1641 ) -> Self {
1642 assert_eq!(inputs.len(), coefficients.dimension());
1643 assert!(
1644 inputs.iter().all(|input| {
1645 input.dimension() == dimension && std::ptr::eq(input.arena, arena)
1646 }),
1647 "dynamic quadratic-form jets must share dimension and arena"
1648 );
1649 let input_dimension = inputs.len();
1650 let values = arena.zeros(input_dimension);
1651 for (value, input) in values.iter_mut().zip(inputs) {
1652 *value = input.v;
1653 }
1654 let projected = arena.zeros(input_dimension);
1655 coefficients.multiply(values, projected);
1656
1657 let mut value = 0.0;
1658 for axis in 0..input_dimension {
1659 value += values[axis] * projected[axis];
1660 }
1661 let gradient = arena.zeros(dimension);
1662 for primary in 0..dimension {
1663 let mut channel = 0.0;
1664 for axis in 0..input_dimension {
1665 channel += projected[axis] * inputs[axis].g[primary];
1666 }
1667 gradient[primary] = 2.0 * channel;
1668 }
1669 let hessian = arena.zeros(dimension * dimension);
1670 let input_gradient = arena.zeros(input_dimension);
1671 let projected_gradient = arena.zeros(input_dimension);
1672 for primary_b in 0..dimension {
1673 for row in 0..input_dimension {
1674 input_gradient[row] = inputs[row].g[primary_b];
1675 }
1676 coefficients.multiply(input_gradient, projected_gradient);
1677 for primary_a in 0..=primary_b {
1678 let mut inherited = 0.0;
1679 let mut curvature = 0.0;
1680 for row in 0..input_dimension {
1681 inherited += projected[row] * inputs[row].h[primary_a * dimension + primary_b];
1682 curvature += inputs[row].g[primary_a] * projected_gradient[row];
1683 }
1684 let channel = 2.0 * (inherited + curvature);
1685 hessian[primary_a * dimension + primary_b] = channel;
1686 hessian[primary_b * dimension + primary_a] = channel;
1687 }
1688 }
1689 Self {
1690 arena,
1691 v: value,
1692 g: gradient,
1693 h: hessian,
1694 }
1695 }
1696
1697 #[inline(always)]
1698 fn product(&self, right: &Self) -> Self {
1699 self.assert_compatible(right);
1700 let dimension = self.dimension();
1701 let gradient = self.arena.zeros(dimension);
1702 let hessian = self.arena.zeros(dimension * dimension);
1703 for primary in 0..dimension {
1704 gradient[primary] = self.v * right.g[primary] + self.g[primary] * right.v;
1705 for other in primary..dimension {
1706 let index = primary * dimension + other;
1707 let channel = self.v * right.h[index]
1708 + self.g[primary] * right.g[other]
1709 + self.g[other] * right.g[primary]
1710 + self.h[index] * right.v;
1711 hessian[index] = channel;
1712 hessian[other * dimension + primary] = channel;
1713 }
1714 }
1715 Self {
1716 arena: self.arena,
1717 v: self.v * right.v,
1718 g: gradient,
1719 h: hessian,
1720 }
1721 }
1722
1723 #[inline(always)]
1724 fn affine_compose(
1725 &self,
1726 input_scale: f64,
1727 input_shift: f64,
1728 derivative_stack: [f64; 5],
1729 ) -> Self {
1730 assert!(input_shift.is_finite(), "affine input shift must be finite");
1731 let arena = self.arena;
1732 let dimension = self.dimension();
1733 let first = derivative_stack[1] * input_scale;
1734 let second = derivative_stack[2] * input_scale * input_scale;
1735 let gradient = arena.zeros(dimension);
1736 let hessian = arena.zeros(dimension * dimension);
1737 for primary in 0..dimension {
1738 gradient[primary] = first * self.g[primary];
1739 for other in primary..dimension {
1740 let index = primary * dimension + other;
1741 let channel = first * self.h[index] + second * self.g[primary] * self.g[other];
1742 hessian[index] = channel;
1743 hessian[other * dimension + primary] = channel;
1744 }
1745 }
1746 Self {
1747 arena,
1748 v: derivative_stack[0],
1749 g: gradient,
1750 h: hessian,
1751 }
1752 }
1753
1754 #[inline(always)]
1755 fn affine_composed_sum(
1756 inputs: &[Self],
1757 input_scales: &[f64],
1758 derivative_stacks: &[[f64; 5]],
1759 dimension: usize,
1760 arena: &'arena DynamicJetArena,
1761 ) -> Self {
1762 assert_eq!(inputs.len(), input_scales.len());
1763 assert_eq!(inputs.len(), derivative_stacks.len());
1764 assert!(
1765 inputs.iter().all(|input| {
1766 input.dimension() == dimension && std::ptr::eq(input.arena, arena)
1767 }),
1768 "dynamic affine-composed-sum jets must share dimension and arena"
1769 );
1770 let gradient = arena.zeros(dimension);
1771 let hessian = arena.zeros(dimension * dimension);
1772 let mut value = 0.0;
1773 for ((input, &input_scale), stack) in inputs.iter().zip(input_scales).zip(derivative_stacks)
1774 {
1775 let first = stack[1] * input_scale;
1776 let second = stack[2] * input_scale * input_scale;
1777 value += stack[0];
1778 for primary in 0..dimension {
1779 gradient[primary] += first * input.g[primary];
1780 for other in primary..dimension {
1781 let index = primary * dimension + other;
1782 hessian[index] +=
1783 first * input.h[index] + second * input.g[primary] * input.g[other];
1784 }
1785 }
1786 }
1787 for primary in 0..dimension {
1788 for other in primary + 1..dimension {
1789 hessian[other * dimension + primary] = hessian[primary * dimension + other];
1790 }
1791 }
1792 Self {
1793 arena,
1794 v: value,
1795 g: gradient,
1796 h: hessian,
1797 }
1798 }
1799
1800 #[inline(always)]
1801 fn shared_multiply_add_affine_composed_sum<const N: usize>(
1802 lefts: &[&Self; N],
1803 right: &Self,
1804 addend: &Self,
1805 addend_scales: &[f64; N],
1806 input_scales: &[f64; N],
1807 derivative_stacks: &[[f64; 5]; N],
1808 dimension: usize,
1809 arena: &'arena DynamicJetArena,
1810 ) -> Self {
1811 assert!(
1812 lefts.iter().all(|input| {
1813 input.dimension() == dimension && std::ptr::eq(input.arena, arena)
1814 }) && (N == 0 || (right.dimension() == dimension && std::ptr::eq(right.arena, arena))),
1815 "dynamic fused product-composition jets must share dimension and arena"
1816 );
1817 let addend_live = addend_scales.iter().any(|&scale| scale != 0.0);
1818 assert!(
1819 !addend_live || (addend.dimension() == dimension && std::ptr::eq(addend.arena, arena)),
1820 "live dynamic fused addends must share dimension and arena"
1821 );
1822 let (representatives, term_sources, source_count) =
1823 canonical_shared_source_schedule::<N>(|term, representative| {
1824 std::ptr::eq(lefts[term], lefts[representative])
1825 && addend_scales[term] == addend_scales[representative]
1826 });
1827 let (value, source_derivatives) =
1828 aggregate_shared_source_derivatives(&term_sources, input_scales, derivative_stacks);
1829 let source_gradients = arena.zeros(source_count * dimension);
1830 let gradient = arena.zeros(dimension);
1831 let hessian = arena.zeros(dimension * dimension);
1832 let mut right_first = 0.0;
1833 let mut addend_first = 0.0;
1834 for source in 0..source_count {
1835 let term = representatives[source];
1836 let first = source_derivatives[source][1];
1837 right_first += first * lefts[term].v;
1838 addend_first += first * addend_scales[term];
1839 for primary in 0..dimension {
1840 let product_gradient =
1841 lefts[term].v * right.g[primary] + lefts[term].g[primary] * right.v;
1842 let inner_gradient = if addend_scales[term] == 0.0 {
1843 product_gradient
1844 } else if addend_scales[term] == 1.0 {
1845 product_gradient + addend.g[primary]
1846 } else {
1847 product_gradient + addend_scales[term] * addend.g[primary]
1848 };
1849 source_gradients[source * dimension + primary] = inner_gradient;
1850 gradient[primary] += first * lefts[term].g[primary] * right.v;
1851 }
1852 }
1853 if N != 0 {
1854 for primary in 0..dimension {
1855 gradient[primary] += right_first * right.g[primary];
1856 }
1857 }
1858 if addend_live {
1859 for primary in 0..dimension {
1860 gradient[primary] += addend_first * addend.g[primary];
1861 }
1862 }
1863 for primary in 0..dimension {
1864 for other in primary..dimension {
1865 let index = primary * dimension + other;
1866 let mut channel = if N == 0 {
1867 0.0
1868 } else {
1869 right_first * right.h[index]
1870 };
1871 if addend_live {
1872 channel += addend_first * addend.h[index];
1873 }
1874 for source in 0..source_count {
1875 let term = representatives[source];
1876 let local_product_hessian = lefts[term].g[primary] * right.g[other]
1877 + lefts[term].g[other] * right.g[primary]
1878 + lefts[term].h[index] * right.v;
1879 channel += source_derivatives[source][1] * local_product_hessian
1880 + source_derivatives[source][2]
1881 * source_gradients[source * dimension + primary]
1882 * source_gradients[source * dimension + other];
1883 }
1884 hessian[index] = channel;
1885 hessian[other * dimension + primary] = channel;
1886 }
1887 }
1888 Self {
1889 arena,
1890 v: value,
1891 g: gradient,
1892 h: hessian,
1893 }
1894 }
1895
1896 #[inline(always)]
1897 fn multiply_add(&self, right: &Self, addend: &Self) -> Self {
1898 self.assert_compatible(right);
1899 self.assert_compatible(addend);
1900 let dimension = self.dimension();
1901 let gradient = self.arena.zeros(dimension);
1902 let hessian = self.arena.zeros(dimension * dimension);
1903 for primary in 0..dimension {
1904 gradient[primary] =
1905 self.v * right.g[primary] + self.g[primary] * right.v + addend.g[primary];
1906 for other in primary..dimension {
1907 let index = primary * dimension + other;
1908 let channel = self.v * right.h[index]
1909 + self.g[primary] * right.g[other]
1910 + self.g[other] * right.g[primary]
1911 + self.h[index] * right.v
1912 + addend.h[index];
1913 hessian[index] = channel;
1914 hessian[other * dimension + primary] = channel;
1915 }
1916 }
1917 Self {
1918 arena: self.arena,
1919 v: self.v * right.v + addend.v,
1920 g: gradient,
1921 h: hessian,
1922 }
1923 }
1924
1925 #[inline(always)]
1926 fn composed_sum(
1927 inputs: &[Self],
1928 derivative_stacks: &[[f64; 5]],
1929 dimension: usize,
1930 arena: &'arena DynamicJetArena,
1931 ) -> Self {
1932 assert_eq!(inputs.len(), derivative_stacks.len());
1933 assert!(
1934 inputs.iter().all(|input| {
1935 input.dimension() == dimension && std::ptr::eq(input.arena, arena)
1936 }),
1937 "dynamic composed-sum jets must share dimension and arena"
1938 );
1939 let gradient = arena.zeros(dimension);
1940 let hessian = arena.zeros(dimension * dimension);
1941 let mut value = 0.0;
1942 for (input, stack) in inputs.iter().zip(derivative_stacks) {
1943 value += stack[0];
1944 for primary in 0..dimension {
1945 gradient[primary] += stack[1] * input.g[primary];
1946 for other in primary..dimension {
1947 let index = primary * dimension + other;
1948 hessian[index] +=
1949 stack[1] * input.h[index] + stack[2] * input.g[primary] * input.g[other];
1950 }
1951 }
1952 }
1953 for primary in 0..dimension {
1954 for other in primary + 1..dimension {
1955 hessian[other * dimension + primary] = hessian[primary * dimension + other];
1956 }
1957 }
1958 Self {
1959 arena,
1960 v: value,
1961 g: gradient,
1962 h: hessian,
1963 }
1964 }
1965
1966 #[inline(always)]
1967 fn linear_combination(
1968 inputs: &[Self],
1969 weights: &[f64],
1970 dimension: usize,
1971 arena: &'arena DynamicJetArena,
1972 ) -> Self {
1973 assert_eq!(inputs.len(), weights.len());
1974 assert!(
1975 inputs.iter().all(|input| {
1976 input.dimension() == dimension && std::ptr::eq(input.arena, arena)
1977 }),
1978 "dynamic linear-combination jets must share dimension and arena"
1979 );
1980 let mut value = 0.0;
1981 for (input, &weight) in inputs.iter().zip(weights) {
1982 value += input.v * weight;
1983 }
1984 let gradient = arena.zeros(dimension);
1985 let hessian = arena.zeros(dimension * dimension);
1986 for primary in 0..dimension {
1987 for (input, &weight) in inputs.iter().zip(weights) {
1988 gradient[primary] += input.g[primary] * weight;
1989 }
1990 for other in primary..dimension {
1991 let index = primary * dimension + other;
1992 for (input, &weight) in inputs.iter().zip(weights) {
1993 hessian[index] += input.h[index] * weight;
1994 }
1995 hessian[other * dimension + primary] = hessian[index];
1996 }
1997 }
1998 Self {
1999 arena,
2000 v: value,
2001 g: gradient,
2002 h: hessian,
2003 }
2004 }
2005
2006 #[inline(always)]
2007 fn dimension(&self) -> usize {
2008 self.g.len()
2009 }
2010
2011 #[inline(always)]
2012 fn value(&self) -> f64 {
2013 self.v
2014 }
2015
2016 #[inline(always)]
2017 fn add(&self, o: &Self) -> Self {
2018 self.assert_compatible(o);
2019 let dimension = self.dimension();
2020 let g = self.arena.zeros(dimension);
2021 let h = self.arena.zeros(self.h.len());
2022 for i in 0..g.len() {
2023 g[i] = self.g[i] + o.g[i];
2024 }
2025 for row in 0..dimension {
2026 for column in row..dimension {
2027 let index = row * dimension + column;
2028 let channel = self.h[index] + o.h[index];
2029 h[index] = channel;
2030 h[column * dimension + row] = channel;
2031 }
2032 }
2033 Self {
2034 arena: self.arena,
2035 v: self.v + o.v,
2036 g,
2037 h,
2038 }
2039 }
2040
2041 #[inline(always)]
2042 fn sub(&self, o: &Self) -> Self {
2043 self.assert_compatible(o);
2044 let dimension = self.dimension();
2045 let g = self.arena.zeros(dimension);
2046 let h = self.arena.zeros(self.h.len());
2047 for i in 0..g.len() {
2048 g[i] = self.g[i] - o.g[i];
2049 }
2050 for row in 0..dimension {
2051 for column in row..dimension {
2052 let index = row * dimension + column;
2053 let channel = self.h[index] - o.h[index];
2054 h[index] = channel;
2055 h[column * dimension + row] = channel;
2056 }
2057 }
2058 Self {
2059 arena: self.arena,
2060 v: self.v - o.v,
2061 g,
2062 h,
2063 }
2064 }
2065
2066 #[inline(always)]
2067 fn mul(&self, o: &Self) -> Self {
2068 self.assert_compatible(o);
2069 let n = self.dimension();
2070 let g = self.arena.zeros(n);
2071 let h = self.arena.zeros(n * n);
2072 for i in 0..n {
2073 g[i] = self.v * o.g[i] + self.g[i] * o.v;
2074 }
2075 for i in 0..n {
2076 for j in i..n {
2077 let ij = i * n + j;
2078 let hij =
2079 self.v * o.h[ij] + self.g[i] * o.g[j] + self.g[j] * o.g[i] + self.h[ij] * o.v;
2080 h[ij] = hij;
2081 h[j * n + i] = hij;
2082 }
2083 }
2084 Self {
2085 arena: self.arena,
2086 v: self.v * o.v,
2087 g,
2088 h,
2089 }
2090 }
2091
2092 #[inline(always)]
2093 fn neg(&self) -> Self {
2094 self.scale(-1.0)
2095 }
2096
2097 #[inline(always)]
2098 fn scale(&self, s: f64) -> Self {
2099 let dimension = self.dimension();
2100 let g = self.arena.zeros(dimension);
2101 let h = self.arena.zeros(self.h.len());
2102 for i in 0..g.len() {
2103 g[i] = self.g[i] * s;
2104 }
2105 for row in 0..dimension {
2106 for column in row..dimension {
2107 let index = row * dimension + column;
2108 let channel = self.h[index] * s;
2109 h[index] = channel;
2110 h[column * dimension + row] = channel;
2111 }
2112 }
2113 Self {
2114 arena: self.arena,
2115 v: self.v * s,
2116 g,
2117 h,
2118 }
2119 }
2120
2121 #[inline(always)]
2122 fn compose_unary(&self, d: [f64; 5]) -> Self {
2123 let n = self.dimension();
2124 let g = self.arena.zeros(n);
2125 let h = self.arena.zeros(n * n);
2126 for i in 0..n {
2127 g[i] = d[1] * self.g[i];
2128 }
2129 for i in 0..n {
2130 for j in i..n {
2131 let ij = i * n + j;
2132 let channel = d[1] * self.h[ij] + d[2] * self.g[i] * self.g[j];
2133 h[ij] = channel;
2134 h[j * n + i] = channel;
2135 }
2136 }
2137 Self {
2138 arena: self.arena,
2139 v: d[0],
2140 g,
2141 h,
2142 }
2143 }
2144}
2145
2146#[derive(Clone, Copy, Debug)]
2148pub struct DynamicOneSeed<'arena> {
2149 pub base: DynamicOrder2<'arena>,
2151 pub eps: DynamicOrder2<'arena>,
2153}
2154
2155impl<'arena> DynamicOneSeed<'arena> {
2156 #[inline(always)]
2158 #[must_use]
2159 pub fn seed_direction(
2160 x: f64,
2161 axis: usize,
2162 u_axis: f64,
2163 dimension: usize,
2164 arena: &'arena DynamicJetArena,
2165 ) -> Self {
2166 Self {
2167 base: DynamicOrder2::variable(x, axis, dimension, arena),
2168 eps: DynamicOrder2::constant(u_axis, dimension, arena),
2169 }
2170 }
2171
2172 #[inline(always)]
2174 #[must_use]
2175 pub fn contracted_third(&self) -> &[f64] {
2176 self.eps.h()
2177 }
2178}
2179
2180impl<'arena> RuntimeJetScalar<'arena> for DynamicOneSeed<'arena> {
2181 type Workspace = DynamicJetArena;
2182
2183 #[inline(always)]
2184 fn constant(c: f64, dimension: usize, arena: &'arena DynamicJetArena) -> Self {
2185 Self {
2186 base: DynamicOrder2::constant(c, dimension, arena),
2187 eps: DynamicOrder2::constant(0.0, dimension, arena),
2188 }
2189 }
2190
2191 #[inline(always)]
2192 fn variable(x: f64, axis: usize, dimension: usize, arena: &'arena DynamicJetArena) -> Self {
2193 Self {
2194 base: DynamicOrder2::variable(x, axis, dimension, arena),
2195 eps: DynamicOrder2::constant(0.0, dimension, arena),
2196 }
2197 }
2198
2199 #[inline(always)]
2200 fn constant_like(&self, c: f64) -> Self {
2201 Self {
2202 base: self.base.constant_like(c),
2203 eps: self.eps.constant_like(0.0),
2204 }
2205 }
2206
2207 #[inline(always)]
2208 fn with_value(&self, value: f64) -> Self {
2209 Self {
2210 base: self.base.with_value(value),
2211 eps: self.eps,
2212 }
2213 }
2214
2215 #[inline(always)]
2216 fn dimension(&self) -> usize {
2217 self.base.dimension()
2218 }
2219
2220 #[inline(always)]
2221 fn value(&self) -> f64 {
2222 self.base.value()
2223 }
2224
2225 #[inline(always)]
2226 fn add(&self, o: &Self) -> Self {
2227 Self {
2228 base: self.base.add(&o.base),
2229 eps: self.eps.add(&o.eps),
2230 }
2231 }
2232
2233 #[inline(always)]
2234 fn sub(&self, o: &Self) -> Self {
2235 Self {
2236 base: self.base.sub(&o.base),
2237 eps: self.eps.sub(&o.eps),
2238 }
2239 }
2240
2241 #[inline(always)]
2242 fn mul(&self, o: &Self) -> Self {
2243 self.base.assert_compatible(&o.base);
2244 self.eps.assert_compatible(&o.eps);
2245 Self {
2246 base: self.base.mul(&o.base),
2247 eps: DynamicOrder2::from_channel_functions(
2248 self.base.v * o.eps.v + self.eps.v * o.base.v,
2249 self.dimension(),
2250 self.base.arena,
2251 |i| {
2252 self.base.v * o.eps.g[i]
2253 + self.base.g[i] * o.eps.v
2254 + self.eps.v * o.base.g[i]
2255 + self.eps.g[i] * o.base.v
2256 },
2257 |i, j| {
2258 let ij = i * self.dimension() + j;
2259 self.base.v * o.eps.h[ij]
2260 + self.base.g[i] * o.eps.g[j]
2261 + self.base.g[j] * o.eps.g[i]
2262 + self.base.h[ij] * o.eps.v
2263 + self.eps.v * o.base.h[ij]
2264 + self.eps.g[i] * o.base.g[j]
2265 + self.eps.g[j] * o.base.g[i]
2266 + self.eps.h[ij] * o.base.v
2267 },
2268 ),
2269 }
2270 }
2271
2272 #[inline(always)]
2273 fn neg(&self) -> Self {
2274 Self {
2275 base: self.base.neg(),
2276 eps: self.eps.neg(),
2277 }
2278 }
2279
2280 #[inline(always)]
2281 fn scale(&self, s: f64) -> Self {
2282 Self {
2283 base: self.base.scale(s),
2284 eps: self.eps.scale(s),
2285 }
2286 }
2287
2288 #[inline(always)]
2289 fn compose_unary(&self, d: [f64; 5]) -> Self {
2290 let base = self.base.compose_unary(d);
2291 let dimension = self.dimension();
2292 let eps = DynamicOrder2::from_channel_functions(
2293 d[1] * self.eps.v,
2294 dimension,
2295 self.base.arena,
2296 |i| d[2] * self.base.g[i] * self.eps.v + d[1] * self.eps.g[i],
2297 |i, j| {
2298 let ij = i * dimension + j;
2299 d[1] * self.eps.h[ij]
2300 + d[2]
2301 * (self.base.g[i] * self.eps.g[j]
2302 + self.base.g[j] * self.eps.g[i]
2303 + self.base.h[ij] * self.eps.v)
2304 + d[3] * self.base.g[i] * self.base.g[j] * self.eps.v
2305 },
2306 );
2307 Self { base, eps }
2308 }
2309}
2310
2311#[derive(Debug)]
2318pub struct DynamicJetBatchWorkspace {
2319 arena: DynamicJetArena,
2320 lanes: usize,
2321}
2322
2323impl DynamicJetBatchWorkspace {
2324 #[must_use]
2326 pub fn new(lanes: usize) -> Self {
2327 Self {
2328 arena: DynamicJetArena::new(),
2329 lanes,
2330 }
2331 }
2332
2333 pub fn reset(&mut self, lanes: usize) {
2335 self.arena.reset();
2336 self.lanes = lanes;
2337 }
2338
2339 #[must_use]
2341 pub fn allocated_bytes(&self) -> usize {
2342 self.arena.allocated_bytes()
2343 }
2344
2345 #[inline(always)]
2347 pub fn alloc_slice_fill_with<T>(&self, len: usize, fill: impl FnMut(usize) -> T) -> &mut [T] {
2348 self.arena.alloc_slice_fill_with(len, fill)
2349 }
2350}
2351
2352#[derive(Clone, Copy, Debug)]
2359pub struct DynamicOneSeedBatch<'arena> {
2360 pub base: DynamicOrder2<'arena>,
2362 eps: &'arena [DynamicOrder2<'arena>],
2364}
2365
2366impl<'arena> DynamicOneSeedBatch<'arena> {
2367 #[inline(always)]
2369 #[must_use]
2370 pub fn seed_directions(
2371 x: f64,
2372 axis: usize,
2373 dimension: usize,
2374 workspace: &'arena DynamicJetBatchWorkspace,
2375 mut direction_at: impl FnMut(usize) -> f64,
2376 ) -> Self {
2377 let eps = workspace
2378 .arena
2379 .alloc_slice_fill_with(workspace.lanes, |lane| {
2380 DynamicOrder2::constant(direction_at(lane), dimension, &workspace.arena)
2381 });
2382 Self {
2383 base: DynamicOrder2::variable(x, axis, dimension, &workspace.arena),
2384 eps,
2385 }
2386 }
2387
2388 #[inline(always)]
2390 #[must_use]
2391 pub fn lanes(&self) -> usize {
2392 self.eps.len()
2393 }
2394
2395 #[inline(always)]
2397 #[must_use]
2398 pub fn contracted_third(&self, lane: usize) -> &[f64] {
2399 self.eps[lane].h()
2400 }
2401
2402 #[inline(always)]
2403 fn assert_compatible(&self, other: &Self) {
2404 self.base.assert_compatible(&other.base);
2405 assert_eq!(
2406 self.eps.len(),
2407 other.eps.len(),
2408 "dynamic one-seed batch lane mismatch"
2409 );
2410 }
2411}
2412
2413impl<'arena> RuntimeJetScalar<'arena> for DynamicOneSeedBatch<'arena> {
2414 type Workspace = DynamicJetBatchWorkspace;
2415
2416 #[inline(always)]
2417 fn constant(c: f64, dimension: usize, workspace: &'arena DynamicJetBatchWorkspace) -> Self {
2418 let eps = workspace.arena.alloc_slice_fill_with(workspace.lanes, |_| {
2419 DynamicOrder2::constant(0.0, dimension, &workspace.arena)
2420 });
2421 Self {
2422 base: DynamicOrder2::constant(c, dimension, &workspace.arena),
2423 eps,
2424 }
2425 }
2426
2427 #[inline(always)]
2428 fn variable(
2429 x: f64,
2430 axis: usize,
2431 dimension: usize,
2432 workspace: &'arena DynamicJetBatchWorkspace,
2433 ) -> Self {
2434 let eps = workspace.arena.alloc_slice_fill_with(workspace.lanes, |_| {
2435 DynamicOrder2::constant(0.0, dimension, &workspace.arena)
2436 });
2437 Self {
2438 base: DynamicOrder2::variable(x, axis, dimension, &workspace.arena),
2439 eps,
2440 }
2441 }
2442
2443 #[inline(always)]
2444 fn constant_like(&self, c: f64) -> Self {
2445 let eps = self
2446 .base
2447 .arena
2448 .alloc_slice_fill_with(self.lanes(), |_| self.base.constant_like(0.0));
2449 Self {
2450 base: self.base.constant_like(c),
2451 eps,
2452 }
2453 }
2454
2455 #[inline(always)]
2456 fn with_value(&self, value: f64) -> Self {
2457 Self {
2458 base: self.base.with_value(value),
2459 eps: self.eps,
2460 }
2461 }
2462
2463 #[inline(always)]
2464 fn dimension(&self) -> usize {
2465 self.base.dimension()
2466 }
2467
2468 #[inline(always)]
2469 fn value(&self) -> f64 {
2470 self.base.value()
2471 }
2472
2473 #[inline(always)]
2474 fn add(&self, other: &Self) -> Self {
2475 self.assert_compatible(other);
2476 let eps = self
2477 .base
2478 .arena
2479 .alloc_slice_fill_with(self.eps.len(), |lane| self.eps[lane].add(&other.eps[lane]));
2480 Self {
2481 base: self.base.add(&other.base),
2482 eps,
2483 }
2484 }
2485
2486 #[inline(always)]
2487 fn sub(&self, other: &Self) -> Self {
2488 self.assert_compatible(other);
2489 let eps = self
2490 .base
2491 .arena
2492 .alloc_slice_fill_with(self.eps.len(), |lane| self.eps[lane].sub(&other.eps[lane]));
2493 Self {
2494 base: self.base.sub(&other.base),
2495 eps,
2496 }
2497 }
2498
2499 #[inline(always)]
2500 fn mul(&self, other: &Self) -> Self {
2501 self.assert_compatible(other);
2502 let eps = self
2503 .base
2504 .arena
2505 .alloc_slice_fill_with(self.eps.len(), |lane| {
2506 self.base
2507 .mul(&other.eps[lane])
2508 .add(&self.eps[lane].mul(&other.base))
2509 });
2510 Self {
2511 base: self.base.mul(&other.base),
2512 eps,
2513 }
2514 }
2515
2516 #[inline(always)]
2517 fn neg(&self) -> Self {
2518 self.scale(-1.0)
2519 }
2520
2521 #[inline(always)]
2522 fn scale(&self, scale: f64) -> Self {
2523 let eps = self
2524 .base
2525 .arena
2526 .alloc_slice_fill_with(self.eps.len(), |lane| self.eps[lane].scale(scale));
2527 Self {
2528 base: self.base.scale(scale),
2529 eps,
2530 }
2531 }
2532
2533 #[inline(always)]
2534 fn compose_unary(&self, derivatives: [f64; 5]) -> Self {
2535 let fprime = self.base.compose_unary([
2536 derivatives[1],
2537 derivatives[2],
2538 derivatives[3],
2539 derivatives[4],
2540 derivatives[4],
2541 ]);
2542 let eps = self
2543 .base
2544 .arena
2545 .alloc_slice_fill_with(self.eps.len(), |lane| fprime.mul(&self.eps[lane]));
2546 Self {
2547 base: self.base.compose_unary(derivatives),
2548 eps,
2549 }
2550 }
2551}
2552
2553#[derive(Clone, Copy, Debug)]
2560pub struct DynamicTwoSeedBatch<'arena> {
2561 pub base: DynamicOrder2<'arena>,
2563 eps: &'arena [DynamicOrder2<'arena>],
2564 del: &'arena [DynamicOrder2<'arena>],
2565 eps_del: &'arena [DynamicOrder2<'arena>],
2566}
2567
2568impl<'arena> DynamicTwoSeedBatch<'arena> {
2569 #[inline(always)]
2571 #[must_use]
2572 pub fn seed_direction_pairs(
2573 x: f64,
2574 axis: usize,
2575 dimension: usize,
2576 workspace: &'arena DynamicJetBatchWorkspace,
2577 mut direction_pair_at: impl FnMut(usize) -> (f64, f64),
2578 ) -> Self {
2579 let directions = workspace
2580 .arena
2581 .alloc_slice_fill_with(workspace.lanes, |lane| direction_pair_at(lane));
2582 let eps = workspace
2583 .arena
2584 .alloc_slice_fill_with(workspace.lanes, |lane| {
2585 DynamicOrder2::constant(directions[lane].0, dimension, &workspace.arena)
2586 });
2587 let del = workspace
2588 .arena
2589 .alloc_slice_fill_with(workspace.lanes, |lane| {
2590 DynamicOrder2::constant(directions[lane].1, dimension, &workspace.arena)
2591 });
2592 let eps_del = workspace.arena.alloc_slice_fill_with(workspace.lanes, |_| {
2593 DynamicOrder2::constant(0.0, dimension, &workspace.arena)
2594 });
2595 Self {
2596 base: DynamicOrder2::variable(x, axis, dimension, &workspace.arena),
2597 eps,
2598 del,
2599 eps_del,
2600 }
2601 }
2602
2603 #[inline(always)]
2605 #[must_use]
2606 pub fn lanes(&self) -> usize {
2607 self.eps.len()
2608 }
2609
2610 #[inline(always)]
2612 #[must_use]
2613 pub fn contracted_fourth(&self, lane: usize) -> &[f64] {
2614 self.eps_del[lane].h()
2615 }
2616
2617 #[inline(always)]
2618 fn assert_compatible(&self, other: &Self) {
2619 self.base.assert_compatible(&other.base);
2620 assert_eq!(
2621 self.eps.len(),
2622 other.eps.len(),
2623 "dynamic two-seed batch lane mismatch"
2624 );
2625 assert_eq!(
2626 self.del.len(),
2627 self.eps.len(),
2628 "dynamic two-seed batch delta mismatch"
2629 );
2630 assert_eq!(
2631 self.eps_del.len(),
2632 self.eps.len(),
2633 "dynamic two-seed batch cross mismatch"
2634 );
2635 }
2636}
2637
2638impl<'arena> RuntimeJetScalar<'arena> for DynamicTwoSeedBatch<'arena> {
2639 type Workspace = DynamicJetBatchWorkspace;
2640
2641 #[inline(always)]
2642 fn constant(c: f64, dimension: usize, workspace: &'arena Self::Workspace) -> Self {
2643 let zero = workspace.arena.alloc_slice_fill_with(workspace.lanes, |_| {
2644 DynamicOrder2::constant(0.0, dimension, &workspace.arena)
2645 });
2646 Self {
2647 base: DynamicOrder2::constant(c, dimension, &workspace.arena),
2648 eps: zero,
2649 del: zero,
2650 eps_del: zero,
2651 }
2652 }
2653
2654 #[inline(always)]
2655 fn variable(x: f64, axis: usize, dimension: usize, workspace: &'arena Self::Workspace) -> Self {
2656 let zero = workspace.arena.alloc_slice_fill_with(workspace.lanes, |_| {
2657 DynamicOrder2::constant(0.0, dimension, &workspace.arena)
2658 });
2659 Self {
2660 base: DynamicOrder2::variable(x, axis, dimension, &workspace.arena),
2661 eps: zero,
2662 del: zero,
2663 eps_del: zero,
2664 }
2665 }
2666
2667 #[inline(always)]
2668 fn constant_like(&self, c: f64) -> Self {
2669 let zero = self
2670 .base
2671 .arena
2672 .alloc_slice_fill_with(self.lanes(), |_| self.base.constant_like(0.0));
2673 Self {
2674 base: self.base.constant_like(c),
2675 eps: zero,
2676 del: zero,
2677 eps_del: zero,
2678 }
2679 }
2680
2681 #[inline(always)]
2682 fn with_value(&self, value: f64) -> Self {
2683 Self {
2684 base: self.base.with_value(value),
2685 eps: self.eps,
2686 del: self.del,
2687 eps_del: self.eps_del,
2688 }
2689 }
2690
2691 #[inline(always)]
2692 fn dimension(&self) -> usize {
2693 self.base.dimension()
2694 }
2695
2696 #[inline(always)]
2697 fn value(&self) -> f64 {
2698 self.base.value()
2699 }
2700
2701 #[inline(always)]
2702 fn add(&self, other: &Self) -> Self {
2703 self.assert_compatible(other);
2704 let arena = self.base.arena;
2705 let eps =
2706 arena.alloc_slice_fill_with(self.lanes(), |lane| self.eps[lane].add(&other.eps[lane]));
2707 let del =
2708 arena.alloc_slice_fill_with(self.lanes(), |lane| self.del[lane].add(&other.del[lane]));
2709 let eps_del = arena.alloc_slice_fill_with(self.lanes(), |lane| {
2710 self.eps_del[lane].add(&other.eps_del[lane])
2711 });
2712 Self {
2713 base: self.base.add(&other.base),
2714 eps,
2715 del,
2716 eps_del,
2717 }
2718 }
2719
2720 #[inline(always)]
2721 fn sub(&self, other: &Self) -> Self {
2722 self.assert_compatible(other);
2723 let arena = self.base.arena;
2724 let eps =
2725 arena.alloc_slice_fill_with(self.lanes(), |lane| self.eps[lane].sub(&other.eps[lane]));
2726 let del =
2727 arena.alloc_slice_fill_with(self.lanes(), |lane| self.del[lane].sub(&other.del[lane]));
2728 let eps_del = arena.alloc_slice_fill_with(self.lanes(), |lane| {
2729 self.eps_del[lane].sub(&other.eps_del[lane])
2730 });
2731 Self {
2732 base: self.base.sub(&other.base),
2733 eps,
2734 del,
2735 eps_del,
2736 }
2737 }
2738
2739 #[inline(always)]
2740 fn mul(&self, other: &Self) -> Self {
2741 self.assert_compatible(other);
2742 let arena = self.base.arena;
2743 let eps = arena.alloc_slice_fill_with(self.lanes(), |lane| {
2744 self.base
2745 .mul(&other.eps[lane])
2746 .add(&self.eps[lane].mul(&other.base))
2747 });
2748 let del = arena.alloc_slice_fill_with(self.lanes(), |lane| {
2749 self.base
2750 .mul(&other.del[lane])
2751 .add(&self.del[lane].mul(&other.base))
2752 });
2753 let eps_del = arena.alloc_slice_fill_with(self.lanes(), |lane| {
2754 self.base
2755 .mul(&other.eps_del[lane])
2756 .add(&self.eps[lane].mul(&other.del[lane]))
2757 .add(&self.del[lane].mul(&other.eps[lane]))
2758 .add(&self.eps_del[lane].mul(&other.base))
2759 });
2760 Self {
2761 base: self.base.mul(&other.base),
2762 eps,
2763 del,
2764 eps_del,
2765 }
2766 }
2767
2768 #[inline(always)]
2769 fn neg(&self) -> Self {
2770 self.scale(-1.0)
2771 }
2772
2773 #[inline(always)]
2774 fn scale(&self, scale: f64) -> Self {
2775 let arena = self.base.arena;
2776 let eps = arena.alloc_slice_fill_with(self.lanes(), |lane| self.eps[lane].scale(scale));
2777 let del = arena.alloc_slice_fill_with(self.lanes(), |lane| self.del[lane].scale(scale));
2778 let eps_del =
2779 arena.alloc_slice_fill_with(self.lanes(), |lane| self.eps_del[lane].scale(scale));
2780 Self {
2781 base: self.base.scale(scale),
2782 eps,
2783 del,
2784 eps_del,
2785 }
2786 }
2787
2788 #[inline(always)]
2789 fn compose_unary(&self, derivatives: [f64; 5]) -> Self {
2790 let arena = self.base.arena;
2791 let fprime = self.base.compose_unary([
2792 derivatives[1],
2793 derivatives[2],
2794 derivatives[3],
2795 derivatives[4],
2796 derivatives[4],
2797 ]);
2798 let fsecond = self.base.compose_unary([
2799 derivatives[2],
2800 derivatives[3],
2801 derivatives[4],
2802 derivatives[4],
2803 derivatives[4],
2804 ]);
2805 let eps = arena.alloc_slice_fill_with(self.lanes(), |lane| fprime.mul(&self.eps[lane]));
2806 let del = arena.alloc_slice_fill_with(self.lanes(), |lane| fprime.mul(&self.del[lane]));
2807 let eps_del = arena.alloc_slice_fill_with(self.lanes(), |lane| {
2808 fsecond
2809 .mul(&self.eps[lane])
2810 .mul(&self.del[lane])
2811 .add(&fprime.mul(&self.eps_del[lane]))
2812 });
2813 Self {
2814 base: self.base.compose_unary(derivatives),
2815 eps,
2816 del,
2817 eps_del,
2818 }
2819 }
2820}
2821
2822#[derive(Clone, Copy, Debug)]
2824pub struct DynamicTwoSeed<'arena> {
2825 pub base: DynamicOrder2<'arena>,
2827 pub eps: DynamicOrder2<'arena>,
2829 pub del: DynamicOrder2<'arena>,
2831 pub eps_del: DynamicOrder2<'arena>,
2833}
2834
2835impl<'arena> DynamicTwoSeed<'arena> {
2836 #[inline(always)]
2838 #[must_use]
2839 pub fn seed(
2840 x: f64,
2841 axis: usize,
2842 u_axis: f64,
2843 v_axis: f64,
2844 dimension: usize,
2845 arena: &'arena DynamicJetArena,
2846 ) -> Self {
2847 Self {
2848 base: DynamicOrder2::variable(x, axis, dimension, arena),
2849 eps: DynamicOrder2::constant(u_axis, dimension, arena),
2850 del: DynamicOrder2::constant(v_axis, dimension, arena),
2851 eps_del: DynamicOrder2::constant(0.0, dimension, arena),
2852 }
2853 }
2854
2855 #[inline(always)]
2857 #[must_use]
2858 pub fn contracted_fourth(&self) -> &[f64] {
2859 self.eps_del.h()
2860 }
2861}
2862
2863impl<'arena> RuntimeJetScalar<'arena> for DynamicTwoSeed<'arena> {
2864 type Workspace = DynamicJetArena;
2865
2866 #[inline(always)]
2867 fn constant(c: f64, dimension: usize, arena: &'arena DynamicJetArena) -> Self {
2868 Self {
2869 base: DynamicOrder2::constant(c, dimension, arena),
2870 eps: DynamicOrder2::constant(0.0, dimension, arena),
2871 del: DynamicOrder2::constant(0.0, dimension, arena),
2872 eps_del: DynamicOrder2::constant(0.0, dimension, arena),
2873 }
2874 }
2875
2876 #[inline(always)]
2877 fn variable(x: f64, axis: usize, dimension: usize, arena: &'arena DynamicJetArena) -> Self {
2878 Self {
2879 base: DynamicOrder2::variable(x, axis, dimension, arena),
2880 eps: DynamicOrder2::constant(0.0, dimension, arena),
2881 del: DynamicOrder2::constant(0.0, dimension, arena),
2882 eps_del: DynamicOrder2::constant(0.0, dimension, arena),
2883 }
2884 }
2885
2886 #[inline(always)]
2887 fn constant_like(&self, c: f64) -> Self {
2888 Self {
2889 base: self.base.constant_like(c),
2890 eps: self.eps.constant_like(0.0),
2891 del: self.del.constant_like(0.0),
2892 eps_del: self.eps_del.constant_like(0.0),
2893 }
2894 }
2895
2896 #[inline(always)]
2897 fn with_value(&self, value: f64) -> Self {
2898 Self {
2899 base: self.base.with_value(value),
2900 eps: self.eps,
2901 del: self.del,
2902 eps_del: self.eps_del,
2903 }
2904 }
2905
2906 #[inline(always)]
2907 fn dimension(&self) -> usize {
2908 self.base.dimension()
2909 }
2910
2911 #[inline(always)]
2912 fn value(&self) -> f64 {
2913 self.base.value()
2914 }
2915
2916 #[inline(always)]
2917 fn add(&self, o: &Self) -> Self {
2918 Self {
2919 base: self.base.add(&o.base),
2920 eps: self.eps.add(&o.eps),
2921 del: self.del.add(&o.del),
2922 eps_del: self.eps_del.add(&o.eps_del),
2923 }
2924 }
2925
2926 #[inline(always)]
2927 fn sub(&self, o: &Self) -> Self {
2928 Self {
2929 base: self.base.sub(&o.base),
2930 eps: self.eps.sub(&o.eps),
2931 del: self.del.sub(&o.del),
2932 eps_del: self.eps_del.sub(&o.eps_del),
2933 }
2934 }
2935
2936 #[inline(always)]
2937 fn mul(&self, o: &Self) -> Self {
2938 let base = self.base.mul(&o.base);
2939 let eps = self.base.mul(&o.eps).add(&self.eps.mul(&o.base));
2940 let del = self.base.mul(&o.del).add(&self.del.mul(&o.base));
2941 let eps_del = self
2942 .base
2943 .mul(&o.eps_del)
2944 .add(&self.eps.mul(&o.del))
2945 .add(&self.del.mul(&o.eps))
2946 .add(&self.eps_del.mul(&o.base));
2947 Self {
2948 base,
2949 eps,
2950 del,
2951 eps_del,
2952 }
2953 }
2954
2955 #[inline(always)]
2956 fn neg(&self) -> Self {
2957 Self {
2958 base: self.base.neg(),
2959 eps: self.eps.neg(),
2960 del: self.del.neg(),
2961 eps_del: self.eps_del.neg(),
2962 }
2963 }
2964
2965 #[inline(always)]
2966 fn scale(&self, s: f64) -> Self {
2967 Self {
2968 base: self.base.scale(s),
2969 eps: self.eps.scale(s),
2970 del: self.del.scale(s),
2971 eps_del: self.eps_del.scale(s),
2972 }
2973 }
2974
2975 #[inline(always)]
2976 fn compose_unary(&self, d: [f64; 5]) -> Self {
2977 let base = self.base.compose_unary(d);
2978 let fprime = self.base.compose_unary([d[1], d[2], d[3], d[4], d[4]]);
2979 let fsecond = self.base.compose_unary([d[2], d[3], d[4], d[4], d[4]]);
2980 let eps = fprime.mul(&self.eps);
2981 let del = fprime.mul(&self.del);
2982 let eps_del = fsecond
2983 .mul(&self.eps)
2984 .mul(&self.del)
2985 .add(&fprime.mul(&self.eps_del));
2986 Self {
2987 base,
2988 eps,
2989 del,
2990 eps_del,
2991 }
2992 }
2993}
2994
2995impl<const K: usize> std::ops::Add for Order2<K> {
3005 type Output = Self;
3006 #[inline]
3007 fn add(self, o: Self) -> Self {
3008 Order2(self.0 + o.0)
3009 }
3010}
3011
3012impl<const K: usize> std::ops::Add<f64> for Order2<K> {
3013 type Output = Self;
3014 #[inline]
3015 fn add(self, c: f64) -> Self {
3016 Order2(self.0 + c)
3017 }
3018}
3019
3020impl<const K: usize> std::ops::Sub for Order2<K> {
3021 type Output = Self;
3022 #[inline]
3023 fn sub(self, o: Self) -> Self {
3024 Order2(self.0 + o.0.scale(-1.0))
3025 }
3026}
3027
3028impl<const K: usize> std::ops::Sub<f64> for Order2<K> {
3029 type Output = Self;
3030 #[inline]
3031 fn sub(self, c: f64) -> Self {
3032 Order2(self.0 + (-c))
3033 }
3034}
3035
3036impl<const K: usize> std::ops::Mul for Order2<K> {
3037 type Output = Self;
3038 #[inline]
3039 fn mul(self, o: Self) -> Self {
3040 Order2(crate::jet_tower::Tower2::mul(&self.0, &o.0))
3041 }
3042}
3043
3044impl<const K: usize> std::ops::Mul<f64> for Order2<K> {
3045 type Output = Self;
3046 #[inline]
3047 fn mul(self, c: f64) -> Self {
3048 Order2(self.0.scale(c))
3049 }
3050}
3051
3052impl<const K: usize> std::ops::Neg for Order2<K> {
3053 type Output = Self;
3054 #[inline]
3055 fn neg(self) -> Self {
3056 Order2(self.0.scale(-1.0))
3057 }
3058}
3059
3060pub fn filtered_implicit_solve_scalar<const K: usize, S: JetScalar<K>>(
3085 a0: f64,
3086 inv_fa: f64,
3087 iters: usize,
3088 f: impl Fn(&S) -> S,
3089) -> S {
3090 let mut a = S::constant(a0);
3091 for _ in 0..iters {
3092 let residual = f(&a);
3093 a = a.sub(&residual.scale(inv_fa));
3094 }
3095 a
3096}
3097
3098pub fn filtered_implicit_solve_runtime_scalar<'arena, S: RuntimeJetScalar<'arena>>(
3110 a0: f64,
3111 inv_fa: f64,
3112 iters: usize,
3113 dimension: usize,
3114 workspace: &'arena S::Workspace,
3115 f: impl Fn(&S) -> S,
3116) -> S {
3117 let mut a = S::constant(a0, dimension, workspace);
3118 for _ in 0..iters {
3119 let residual = f(&a);
3120 a = a.sub(&residual.scale(inv_fa));
3121 }
3122 a
3123}
3124
3125pub trait HessianPattern<const K: usize, const H: usize> {
3133 const PAIRS: [(usize, usize); H];
3134 const PAIR_BITS: [[u128; K]; K];
3135}
3136
3137pub const fn hessian_pair_bits<const K: usize, const H: usize>(
3140 pairs: [(usize, usize); H],
3141) -> [[u128; K]; K] {
3142 let mut table = [[0u128; K]; K];
3143 let mut slot = 0;
3144 while slot < H {
3145 let (i, j) = pairs[slot];
3146 let bit = 1u128 << slot;
3147 table[i][j] = bit;
3148 table[j][i] = bit;
3149 slot += 1;
3150 }
3151 table
3152}
3153
3154#[derive(Debug)]
3163pub struct PatternedOrder2<P, const K: usize, const H: usize> {
3164 v: f64,
3165 g: [f64; K],
3166 h: [f64; H],
3167 gradient_mask: u128,
3168 hessian_mask: u128,
3169 pattern: std::marker::PhantomData<fn() -> P>,
3170}
3171
3172impl<P, const K: usize, const H: usize> Copy for PatternedOrder2<P, K, H> {}
3173
3174impl<P, const K: usize, const H: usize> Clone for PatternedOrder2<P, K, H> {
3175 fn clone(&self) -> Self {
3176 *self
3177 }
3178}
3179
3180impl<P, const K: usize, const H: usize> PatternedOrder2<P, K, H>
3181where
3182 P: HessianPattern<K, H>,
3183{
3184 #[inline]
3185 #[must_use]
3186 pub fn g(&self) -> [f64; K] {
3187 self.g
3188 }
3189
3190 #[inline]
3194 #[must_use]
3195 pub fn h(&self) -> [[f64; K]; K] {
3196 let mut dense = [[0.0; K]; K];
3197 for (slot, &(i, j)) in P::PAIRS.iter().enumerate() {
3198 dense[i][j] = self.h[slot];
3199 dense[j][i] = self.h[slot];
3200 }
3201 dense
3202 }
3203
3204 #[inline]
3205 fn pair_mask_between(left: u128, right: u128) -> u128 {
3206 let mut result = 0u128;
3207 let mut left_axes = left;
3208 while left_axes != 0 {
3209 let i = left_axes.trailing_zeros() as usize;
3210 left_axes &= left_axes - 1;
3211 let mut right_axes = right;
3212 while right_axes != 0 {
3213 let j = right_axes.trailing_zeros() as usize;
3214 right_axes &= right_axes - 1;
3215 result |= P::PAIR_BITS[i][j];
3216 }
3217 }
3218 result
3219 }
3220}
3221
3222impl<P, const K: usize, const H: usize> JetScalar<K> for PatternedOrder2<P, K, H>
3223where
3224 P: HessianPattern<K, H>,
3225{
3226 #[inline]
3227 fn constant(c: f64) -> Self {
3228 Self {
3229 v: c,
3230 g: [0.0; K],
3231 h: [0.0; H],
3232 gradient_mask: 0,
3233 hessian_mask: 0,
3234 pattern: std::marker::PhantomData,
3235 }
3236 }
3237
3238 #[inline]
3239 fn variable(x: f64, axis: usize) -> Self {
3240 let mut out = Self::constant(x);
3241 if axis < K {
3242 out.g[axis] = 1.0;
3243 out.gradient_mask = 1u128 << axis;
3244 }
3245 out
3246 }
3247}
3248
3249impl<P, const K: usize, const H: usize> crate::nested_dual::JetField for PatternedOrder2<P, K, H>
3250where
3251 P: HessianPattern<K, H>,
3252{
3253 #[inline]
3254 fn value(&self) -> f64 {
3255 self.v
3256 }
3257
3258 #[inline]
3259 fn add(&self, other: &Self) -> Self {
3260 let mut out = Self::constant(self.v + other.v);
3261 out.gradient_mask = self.gradient_mask | other.gradient_mask;
3262 let mut gradient_mask = out.gradient_mask;
3263 while gradient_mask != 0 {
3264 let i = gradient_mask.trailing_zeros() as usize;
3265 gradient_mask &= gradient_mask - 1;
3266 out.g[i] = self.g[i] + other.g[i];
3267 }
3268 out.hessian_mask = self.hessian_mask | other.hessian_mask;
3269 let mut hessian_mask = out.hessian_mask;
3270 while hessian_mask != 0 {
3271 let slot = hessian_mask.trailing_zeros() as usize;
3272 hessian_mask &= hessian_mask - 1;
3273 out.h[slot] = self.h[slot] + other.h[slot];
3274 }
3275 out
3276 }
3277
3278 #[inline]
3279 fn sub(&self, other: &Self) -> Self {
3280 let mut out = Self::constant(self.v - other.v);
3281 out.gradient_mask = self.gradient_mask | other.gradient_mask;
3282 let mut gradient_mask = out.gradient_mask;
3283 while gradient_mask != 0 {
3284 let i = gradient_mask.trailing_zeros() as usize;
3285 gradient_mask &= gradient_mask - 1;
3286 out.g[i] = self.g[i] - other.g[i];
3287 }
3288 out.hessian_mask = self.hessian_mask | other.hessian_mask;
3289 let mut hessian_mask = out.hessian_mask;
3290 while hessian_mask != 0 {
3291 let slot = hessian_mask.trailing_zeros() as usize;
3292 hessian_mask &= hessian_mask - 1;
3293 out.h[slot] = self.h[slot] - other.h[slot];
3294 }
3295 out
3296 }
3297
3298 #[inline]
3299 fn mul(&self, other: &Self) -> Self {
3300 let mut out = Self::constant(self.v * other.v);
3301 out.gradient_mask = self.gradient_mask | other.gradient_mask;
3302 let mut gradient_mask = out.gradient_mask;
3303 while gradient_mask != 0 {
3304 let i = gradient_mask.trailing_zeros() as usize;
3305 gradient_mask &= gradient_mask - 1;
3306 out.g[i] = self.v * other.g[i] + self.g[i] * other.v;
3307 }
3308 out.hessian_mask = self.hessian_mask
3309 | other.hessian_mask
3310 | Self::pair_mask_between(self.gradient_mask, other.gradient_mask);
3311 let mut hessian_mask = out.hessian_mask;
3312 while hessian_mask != 0 {
3313 let slot = hessian_mask.trailing_zeros() as usize;
3314 hessian_mask &= hessian_mask - 1;
3315 let (i, j) = P::PAIRS[slot];
3316 out.h[slot] = self.v * other.h[slot]
3317 + self.g[i] * other.g[j]
3318 + self.g[j] * other.g[i]
3319 + self.h[slot] * other.v;
3320 }
3321 out
3322 }
3323
3324 #[inline]
3325 fn neg(&self) -> Self {
3326 self.scale(-1.0)
3327 }
3328
3329 #[inline]
3330 fn scale(&self, scale: f64) -> Self {
3331 let mut out = Self::constant(self.v * scale);
3332 out.gradient_mask = self.gradient_mask;
3333 let mut gradient_mask = out.gradient_mask;
3334 while gradient_mask != 0 {
3335 let i = gradient_mask.trailing_zeros() as usize;
3336 gradient_mask &= gradient_mask - 1;
3337 out.g[i] = self.g[i] * scale;
3338 }
3339 out.hessian_mask = self.hessian_mask;
3340 let mut hessian_mask = out.hessian_mask;
3341 while hessian_mask != 0 {
3342 let slot = hessian_mask.trailing_zeros() as usize;
3343 hessian_mask &= hessian_mask - 1;
3344 out.h[slot] = self.h[slot] * scale;
3345 }
3346 out
3347 }
3348
3349 #[inline]
3350 fn compose_unary(&self, derivatives: [f64; 5]) -> Self {
3351 let mut out = Self::constant(derivatives[0]);
3352 out.gradient_mask = self.gradient_mask;
3353 let mut gradient_mask = out.gradient_mask;
3354 while gradient_mask != 0 {
3355 let i = gradient_mask.trailing_zeros() as usize;
3356 gradient_mask &= gradient_mask - 1;
3357 out.g[i] = derivatives[1] * self.g[i];
3358 }
3359 out.hessian_mask =
3360 self.hessian_mask | Self::pair_mask_between(self.gradient_mask, self.gradient_mask);
3361 let mut hessian_mask = out.hessian_mask;
3362 while hessian_mask != 0 {
3363 let slot = hessian_mask.trailing_zeros() as usize;
3364 hessian_mask &= hessian_mask - 1;
3365 let (i, j) = P::PAIRS[slot];
3366 out.h[slot] = derivatives[2] * self.g[i] * self.g[j] + derivatives[1] * self.h[slot];
3367 }
3368 out
3369 }
3370}
3371
3372#[derive(Clone, Copy, Debug)]
3385pub struct Order2<const K: usize>(pub crate::jet_tower::Tower2<K>);
3386
3387impl<const K: usize> Order2<K> {
3388 #[inline]
3390 #[must_use]
3391 pub fn g(&self) -> &[f64; K] {
3392 &self.0.g
3393 }
3394
3395 #[inline]
3397 #[must_use]
3398 pub fn h(&self) -> &[[f64; K]; K] {
3399 &self.0.h
3400 }
3401
3402 #[inline]
3404 #[must_use]
3405 pub fn into_channels(self) -> (f64, [f64; K], [[f64; K]; K]) {
3406 let crate::jet_tower::Tower2 { v, g, h } = self.0;
3407 (v, g, h)
3408 }
3409}
3410
3411impl<const K: usize> JetScalar<K> for Order2<K> {
3412 fn constant(c: f64) -> Self {
3413 Order2(crate::jet_tower::Tower2::constant(c))
3414 }
3415 fn variable(x: f64, axis: usize) -> Self {
3416 Order2(crate::jet_tower::Tower2::variable(x, axis))
3417 }
3418
3419 #[inline(always)]
3420 fn symmetric_quadratic_form<C: SymmetricQuadraticCoefficients>(
3421 inputs: &[Self],
3422 coefficients: &C,
3423 ) -> Self {
3424 assert_eq!(inputs.len(), coefficients.dimension());
3425 let input_dimension = inputs.len();
3426 assert!(input_dimension <= K);
3427 let mut values = [0.0; K];
3428 for axis in 0..input_dimension {
3429 values[axis] = inputs[axis].0.v;
3430 }
3431 let mut projected = [0.0; K];
3432 coefficients.multiply(
3433 &values[..input_dimension],
3434 &mut projected[..input_dimension],
3435 );
3436
3437 let mut out = crate::jet_tower::Tower2::zero();
3438 for axis in 0..input_dimension {
3439 out.v += values[axis] * projected[axis];
3440 }
3441 for primary in 0..K {
3442 let mut channel = 0.0;
3443 for axis in 0..input_dimension {
3444 channel += projected[axis] * inputs[axis].0.g[primary];
3445 }
3446 out.g[primary] = 2.0 * channel;
3447 }
3448 let mut input_gradient = [0.0; K];
3449 let mut projected_gradient = [0.0; K];
3450 for primary_b in 0..K {
3451 for row in 0..input_dimension {
3452 input_gradient[row] = inputs[row].0.g[primary_b];
3453 }
3454 coefficients.multiply(
3455 &input_gradient[..input_dimension],
3456 &mut projected_gradient[..input_dimension],
3457 );
3458 for primary_a in 0..=primary_b {
3459 let mut inherited = 0.0;
3460 let mut curvature = 0.0;
3461 for row in 0..input_dimension {
3462 inherited += projected[row] * inputs[row].0.h[primary_a][primary_b];
3463 curvature += inputs[row].0.g[primary_a] * projected_gradient[row];
3464 }
3465 let channel = 2.0 * (inherited + curvature);
3466 out.h[primary_a][primary_b] = channel;
3467 out.h[primary_b][primary_a] = channel;
3468 }
3469 }
3470 Order2(out)
3471 }
3472
3473 #[inline(always)]
3474 fn linear_combination(inputs: &[Self], weights: &[f64]) -> Self {
3475 assert_eq!(inputs.len(), weights.len());
3476 let mut out = crate::jet_tower::Tower2::zero();
3477 for (input, &weight) in inputs.iter().zip(weights) {
3478 out.v += input.0.v * weight;
3479 }
3480 for primary in 0..K {
3481 for (input, &weight) in inputs.iter().zip(weights) {
3482 out.g[primary] += input.0.g[primary] * weight;
3483 }
3484 for other in primary..K {
3485 for (input, &weight) in inputs.iter().zip(weights) {
3486 out.h[primary][other] += input.0.h[primary][other] * weight;
3487 }
3488 out.h[other][primary] = out.h[primary][other];
3489 }
3490 }
3491 Order2(out)
3492 }
3493
3494 #[inline(always)]
3495 fn add_constant(&self, constant: f64) -> Self {
3496 let mut out = *self;
3497 out.0.v += constant;
3498 out
3499 }
3500
3501 #[inline(always)]
3502 fn multiply_add(&self, right: &Self, addend: &Self) -> Self {
3503 let mut out = crate::jet_tower::Tower2::zero();
3504 out.v = self.0.v * right.0.v + addend.0.v;
3505 for primary in 0..K {
3506 out.g[primary] =
3507 self.0.v * right.0.g[primary] + self.0.g[primary] * right.0.v + addend.0.g[primary];
3508 for other in primary..K {
3509 let channel = self.0.v * right.0.h[primary][other]
3510 + self.0.g[primary] * right.0.g[other]
3511 + self.0.g[other] * right.0.g[primary]
3512 + self.0.h[primary][other] * right.0.v
3513 + addend.0.h[primary][other];
3514 out.h[primary][other] = channel;
3515 out.h[other][primary] = channel;
3516 }
3517 }
3518 Order2(out)
3519 }
3520
3521 #[inline(always)]
3522 fn product(&self, right: &Self) -> Self {
3523 let mut out = crate::jet_tower::Tower2::zero();
3524 out.v = self.0.v * right.0.v;
3525 for primary in 0..K {
3526 out.g[primary] = self.0.v * right.0.g[primary] + self.0.g[primary] * right.0.v;
3527 for other in primary..K {
3528 let channel = self.0.v * right.0.h[primary][other]
3529 + self.0.g[primary] * right.0.g[other]
3530 + self.0.g[other] * right.0.g[primary]
3531 + self.0.h[primary][other] * right.0.v;
3532 out.h[primary][other] = channel;
3533 out.h[other][primary] = channel;
3534 }
3535 }
3536 Order2(out)
3537 }
3538
3539 #[inline(always)]
3540 fn affine_compose(
3541 &self,
3542 input_scale: f64,
3543 input_shift: f64,
3544 derivative_stack: [f64; 5],
3545 ) -> Self {
3546 assert!(input_shift.is_finite(), "affine input shift must be finite");
3547 let first = derivative_stack[1] * input_scale;
3548 let second = derivative_stack[2] * input_scale * input_scale;
3549 let mut out = crate::jet_tower::Tower2::zero();
3550 out.v = derivative_stack[0];
3551 for primary in 0..K {
3552 out.g[primary] = first * self.0.g[primary];
3553 for other in primary..K {
3554 let channel =
3555 first * self.0.h[primary][other] + second * self.0.g[primary] * self.0.g[other];
3556 out.h[primary][other] = channel;
3557 out.h[other][primary] = channel;
3558 }
3559 }
3560 Order2(out)
3561 }
3562
3563 #[inline(always)]
3564 fn affine_composed_sum(
3565 inputs: &[Self],
3566 input_scales: &[f64],
3567 derivative_stacks: &[[f64; 5]],
3568 ) -> Self {
3569 assert_eq!(inputs.len(), input_scales.len());
3570 assert_eq!(inputs.len(), derivative_stacks.len());
3571 let mut out = crate::jet_tower::Tower2::zero();
3572 for ((input, &input_scale), stack) in inputs.iter().zip(input_scales).zip(derivative_stacks)
3573 {
3574 let first = stack[1] * input_scale;
3575 let second = stack[2] * input_scale * input_scale;
3576 out.v += stack[0];
3577 for primary in 0..K {
3578 out.g[primary] += first * input.0.g[primary];
3579 for other in primary..K {
3580 out.h[primary][other] += first * input.0.h[primary][other]
3581 + second * input.0.g[primary] * input.0.g[other];
3582 }
3583 }
3584 }
3585 for primary in 0..K {
3586 for other in primary + 1..K {
3587 out.h[other][primary] = out.h[primary][other];
3588 }
3589 }
3590 Order2(out)
3591 }
3592
3593 #[inline(always)]
3594 fn shared_multiply_add_affine_composed_sum<const N: usize>(
3595 lefts: &[&Self; N],
3596 right: &Self,
3597 addend: &Self,
3598 addend_scales: &[f64; N],
3599 input_scales: &[f64; N],
3600 derivative_stacks: &[[f64; 5]; N],
3601 ) -> Self {
3602 let (representatives, term_sources, source_count) =
3603 canonical_shared_source_schedule::<N>(|term, representative| {
3604 std::ptr::eq(lefts[term], lefts[representative])
3605 && addend_scales[term] == addend_scales[representative]
3606 });
3607 let (value, source_derivatives) =
3608 aggregate_shared_source_derivatives(&term_sources, input_scales, derivative_stacks);
3609 let mut source_gradients = [[0.0; K]; N];
3610 let mut out = crate::jet_tower::Tower2::zero();
3611 out.v = value;
3612 let mut right_first = 0.0;
3613 let mut addend_first = 0.0;
3614 for source in 0..source_count {
3615 let term = representatives[source];
3616 let first = source_derivatives[source][1];
3617 right_first += first * lefts[term].0.v;
3618 addend_first += first * addend_scales[term];
3619 for primary in 0..K {
3620 let product_gradient =
3621 lefts[term].0.v * right.0.g[primary] + lefts[term].0.g[primary] * right.0.v;
3622 let inner_gradient = if addend_scales[term] == 0.0 {
3623 product_gradient
3624 } else if addend_scales[term] == 1.0 {
3625 product_gradient + addend.0.g[primary]
3626 } else {
3627 product_gradient + addend_scales[term] * addend.0.g[primary]
3628 };
3629 source_gradients[source][primary] = inner_gradient;
3630 out.g[primary] += first * lefts[term].0.g[primary] * right.0.v;
3631 }
3632 }
3633 if N != 0 {
3634 for primary in 0..K {
3635 out.g[primary] += right_first * right.0.g[primary];
3636 }
3637 }
3638 let addend_live = addend_scales.iter().any(|&scale| scale != 0.0);
3639 if addend_live {
3640 for primary in 0..K {
3641 out.g[primary] += addend_first * addend.0.g[primary];
3642 }
3643 }
3644 for primary in 0..K {
3645 for other in primary..K {
3646 let mut channel = if N == 0 {
3647 0.0
3648 } else {
3649 right_first * right.0.h[primary][other]
3650 };
3651 if addend_live {
3652 channel += addend_first * addend.0.h[primary][other];
3653 }
3654 for source in 0..source_count {
3655 let term = representatives[source];
3656 let local_product_hessian = lefts[term].0.g[primary] * right.0.g[other]
3657 + lefts[term].0.g[other] * right.0.g[primary]
3658 + lefts[term].0.h[primary][other] * right.0.v;
3659 channel += source_derivatives[source][1] * local_product_hessian
3660 + source_derivatives[source][2]
3661 * source_gradients[source][primary]
3662 * source_gradients[source][other];
3663 }
3664 out.h[primary][other] = channel;
3665 out.h[other][primary] = channel;
3666 }
3667 }
3668 Order2(out)
3669 }
3670
3671 #[inline(always)]
3672 fn composed_sum(inputs: &[Self], derivative_stacks: &[[f64; 5]]) -> Self {
3673 assert_eq!(inputs.len(), derivative_stacks.len());
3674 let mut out = crate::jet_tower::Tower2::zero();
3675 for (input, stack) in inputs.iter().zip(derivative_stacks) {
3676 out.v += stack[0];
3677 for primary in 0..K {
3678 out.g[primary] += stack[1] * input.0.g[primary];
3679 for other in primary..K {
3680 out.h[primary][other] += stack[1] * input.0.h[primary][other]
3681 + stack[2] * input.0.g[primary] * input.0.g[other];
3682 }
3683 }
3684 }
3685 for primary in 0..K {
3686 for other in primary + 1..K {
3687 out.h[other][primary] = out.h[primary][other];
3688 }
3689 }
3690 Order2(out)
3691 }
3692}
3693
3694impl<const K: usize> crate::nested_dual::JetField for Order2<K> {
3695 fn value(&self) -> f64 {
3696 self.0.v
3697 }
3698 fn add(&self, o: &Self) -> Self {
3699 Order2(self.0 + o.0)
3700 }
3701 fn sub(&self, o: &Self) -> Self {
3702 Order2(self.0 + o.0.scale(-1.0))
3705 }
3706 fn mul(&self, o: &Self) -> Self {
3707 Order2(crate::jet_tower::Tower2::mul(&self.0, &o.0))
3708 }
3709 fn neg(&self) -> Self {
3710 Order2(self.0.scale(-1.0))
3711 }
3712 fn scale(&self, s: f64) -> Self {
3713 Order2(self.0.scale(s))
3714 }
3715 fn compose_unary(&self, d: [f64; 5]) -> Self {
3716 Order2(self.0.compose_unary([d[0], d[1], d[2]]))
3718 }
3719 fn constant_like(&self, v: f64) -> Self {
3720 <Self as JetScalar<K>>::constant(v)
3724 }
3725 fn with_value(&self, v: f64) -> Self {
3726 let mut out = *self;
3727 out.0.v = v;
3728 out
3729 }
3730}
3731
3732#[derive(Clone, Copy, Debug)]
3757pub struct MappedOrder2Accumulator<const K: usize> {
3758 value: f64,
3759 gradient: [f64; K],
3760 hessian: [[f64; K]; K],
3761}
3762
3763#[derive(Clone, Copy, Debug)]
3770pub struct StaticOrder2Atom<
3771 const N: usize,
3772 const H: usize,
3773 const GRADIENT_BITS: u128,
3774 const HESSIAN_BITS: u128,
3775> {
3776 value: f64,
3777 gradient: [f64; N],
3778 hessian: [f64; H],
3779}
3780
3781impl<const N: usize, const H: usize, const G: u128, const Q: u128> StaticOrder2Atom<N, H, G, Q> {
3782 #[inline(always)]
3784 #[must_use]
3785 pub fn new(value: f64, gradient: [f64; N], hessian: [f64; H]) -> Self {
3786 assert!(H == N * (N + 1) / 2, "invalid packed order-two shape");
3787 assert!(N <= 128 && H <= 128, "static atom sparsity mask overflow");
3788 Self {
3789 value,
3790 gradient,
3791 hessian,
3792 }
3793 }
3794
3795 #[inline(always)]
3797 #[must_use]
3798 pub fn value(&self) -> f64 {
3799 self.value
3800 }
3801
3802 #[inline(always)]
3804 #[must_use]
3805 pub fn gradient(&self) -> [f64; N] {
3806 self.gradient
3807 }
3808
3809 #[inline(always)]
3811 #[must_use]
3812 pub fn hessian_at(&self, row: usize, column: usize) -> f64 {
3813 assert!(
3814 row < N && column < N,
3815 "static atom Hessian axis out of range"
3816 );
3817 let (row, column) = if row <= column {
3818 (row, column)
3819 } else {
3820 (column, row)
3821 };
3822 let index = row * (2 * N - row + 1) / 2 + column - row;
3823 self.hessian[index]
3824 }
3825}
3826
3827pub trait Order2AtomChannels<const N: usize> {
3832 const GRADIENT_BITS: u128;
3834 const HESSIAN_BITS: u128;
3836 fn gradient_at(&self, axis: usize) -> f64;
3838 fn hessian_at(&self, row: usize, column: usize) -> f64;
3840}
3841
3842impl<const N: usize> Order2AtomChannels<N> for Order2<N> {
3843 const GRADIENT_BITS: u128 = low_mask(N);
3844 const HESSIAN_BITS: u128 = low_mask(N * (N + 1) / 2);
3845
3846 #[inline(always)]
3847 fn gradient_at(&self, axis: usize) -> f64 {
3848 self.0.g[axis]
3849 }
3850
3851 #[inline(always)]
3852 fn hessian_at(&self, row: usize, column: usize) -> f64 {
3853 self.0.h[row][column]
3854 }
3855}
3856
3857impl<const N: usize, const H: usize, const G: u128, const Q: u128> Order2AtomChannels<N>
3858 for StaticOrder2Atom<N, H, G, Q>
3859{
3860 const GRADIENT_BITS: u128 = G;
3861 const HESSIAN_BITS: u128 = Q;
3862
3863 #[inline(always)]
3864 fn gradient_at(&self, axis: usize) -> f64 {
3865 self.gradient[axis]
3866 }
3867
3868 #[inline(always)]
3869 fn hessian_at(&self, row: usize, column: usize) -> f64 {
3870 StaticOrder2Atom::hessian_at(self, row, column)
3871 }
3872}
3873
3874const fn low_mask(channels: usize) -> u128 {
3875 if channels >= 128 {
3876 u128::MAX
3877 } else {
3878 (1u128 << channels) - 1
3879 }
3880}
3881
3882impl<const K: usize> MappedOrder2Accumulator<K> {
3883 #[inline(always)]
3885 #[must_use]
3886 pub fn zero() -> Self {
3887 Self {
3888 value: 0.0,
3889 gradient: [0.0; K],
3890 hessian: [[0.0; K]; K],
3891 }
3892 }
3893
3894 #[inline(always)]
3901 pub fn add_composed<const N: usize, const H: usize, A: Order2AtomChannels<N>>(
3902 &mut self,
3903 atom: &A,
3904 axes: [usize; N],
3905 derivatives: [f64; 3],
3906 value_add: bool,
3907 gradient_add: [bool; N],
3908 hessian_add: [bool; H],
3909 ) {
3910 assert!(H == N * (N + 1) / 2, "invalid mapped Hessian write shape");
3911 assert!(N <= 128 && H <= 128, "mapped atom sparsity mask overflow");
3912 assert!(
3913 axes.iter().all(|&axis| axis < K),
3914 "mapped atom axis must be within the global primary dimension"
3915 );
3916 assert!(
3917 axes.iter()
3918 .enumerate()
3919 .all(|(i, axis)| !axes[..i].contains(axis)),
3920 "mapped atom axes must be injective"
3921 );
3922
3923 if value_add {
3924 self.value += derivatives[0];
3925 } else {
3926 self.value = derivatives[0];
3927 }
3928 let mut packed = 0;
3929 for local_i in 0..N {
3930 let global_i = axes[local_i];
3931 if A::GRADIENT_BITS & (1u128 << local_i) != 0 {
3932 let channel = derivatives[1] * atom.gradient_at(local_i);
3933 if gradient_add[local_i] {
3934 self.gradient[global_i] += channel;
3935 } else {
3936 self.gradient[global_i] = channel;
3937 }
3938 }
3939 for local_j in local_i..N {
3940 let global_j = axes[local_j];
3941 let inner_live = A::HESSIAN_BITS & (1u128 << packed) != 0;
3942 let outer_live = A::GRADIENT_BITS & (1u128 << local_i) != 0
3943 && A::GRADIENT_BITS & (1u128 << local_j) != 0;
3944 let channel = if inner_live {
3945 let inner = derivatives[1] * atom.hessian_at(local_i, local_j);
3946 if outer_live {
3947 inner
3948 + derivatives[2] * atom.gradient_at(local_i) * atom.gradient_at(local_j)
3949 } else {
3950 inner
3951 }
3952 } else if outer_live {
3953 derivatives[2] * atom.gradient_at(local_i) * atom.gradient_at(local_j)
3954 } else {
3955 packed += 1;
3956 continue;
3957 };
3958 if hessian_add[packed] {
3959 self.hessian[global_i][global_j] += channel;
3960 if global_i != global_j {
3961 self.hessian[global_j][global_i] += channel;
3962 }
3963 } else {
3964 self.hessian[global_i][global_j] = channel;
3965 if global_i != global_j {
3966 self.hessian[global_j][global_i] = channel;
3967 }
3968 }
3969 packed += 1;
3970 }
3971 }
3972 }
3973
3974 #[inline(always)]
3976 #[must_use]
3977 pub fn into_channels(self) -> (f64, [f64; K], [[f64; K]; K]) {
3978 (self.value, self.gradient, self.hessian)
3979 }
3980}
3981
3982pub trait DynamicOrder2Term {
3989 fn outer_first(&self) -> f64;
3991
3992 fn outer_second(&self) -> f64;
3994
3995 fn inner_gradient(&self, axis: usize) -> f64;
3997
3998 fn inner_hessian(&self, row: usize, column: usize) -> f64;
4000}
4001
4002#[derive(Debug)]
4019pub struct DynamicOrder2Accumulator {
4020 value: f64,
4021 gradient: Vec<f64>,
4022 hessian: Vec<f64>,
4023}
4024
4025impl DynamicOrder2Accumulator {
4026 #[inline(always)]
4028 #[must_use]
4029 pub fn from_composed_sum<T: DynamicOrder2Term, const N: usize>(
4030 dimension: usize,
4031 value: f64,
4032 terms: &[T; N],
4033 ) -> Self {
4034 let mut gradient = vec![0.0; dimension];
4035 let mut hessian = vec![0.0; dimension * dimension];
4036
4037 for axis in 0..dimension {
4038 let mut channel = 0.0;
4039 for term in terms {
4040 channel += term.outer_first() * term.inner_gradient(axis);
4041 }
4042 gradient[axis] = channel;
4043 }
4044
4045 for row in 0..dimension {
4046 for column in row..dimension {
4047 let mut channel = 0.0;
4048 for term in terms {
4049 let row_gradient = term.inner_gradient(row);
4050 let column_gradient = term.inner_gradient(column);
4051 channel += term.outer_second() * row_gradient * column_gradient
4052 + term.outer_first() * term.inner_hessian(row, column);
4053 }
4054 hessian[row * dimension + column] = channel;
4055 hessian[column * dimension + row] = channel;
4056 }
4057 }
4058
4059 Self {
4060 value,
4061 gradient,
4062 hessian,
4063 }
4064 }
4065
4066 #[inline(always)]
4068 #[must_use]
4069 pub fn into_channels(self) -> (f64, Vec<f64>, Vec<f64>) {
4070 (self.value, self.gradient, self.hessian)
4071 }
4072}
4073
4074pub trait Lane: Copy {
4110 const LANES: usize;
4113 fn splat(x: f64) -> Self;
4115 fn add(self, o: Self) -> Self;
4117 fn sub(self, o: Self) -> Self;
4119 fn mul(self, o: Self) -> Self;
4121 fn lane(self, i: usize) -> f64;
4124 fn unary3(self, stack: impl Fn(f64) -> [f64; 3]) -> [Self; 3];
4130 fn unary5(self, stack: impl Fn(f64) -> [f64; 5]) -> [Self; 5];
4139}
4140
4141impl Lane for f64 {
4142 const LANES: usize = 1;
4143 #[inline]
4144 fn splat(x: f64) -> Self {
4145 x
4146 }
4147 #[inline]
4148 fn add(self, o: Self) -> Self {
4149 self + o
4150 }
4151 #[inline]
4152 fn sub(self, o: Self) -> Self {
4153 self - o
4154 }
4155 #[inline]
4156 fn mul(self, o: Self) -> Self {
4157 self * o
4158 }
4159 #[inline]
4160 fn lane(self, i: usize) -> f64 {
4161 assert!(
4162 i < <Self as Lane>::LANES,
4163 "the f64 Lane carries one row; lane {i} does not exist"
4164 );
4165 self
4166 }
4167 #[inline]
4168 fn unary3(self, stack: impl Fn(f64) -> [f64; 3]) -> [Self; 3] {
4169 stack(self)
4170 }
4171 #[inline]
4172 fn unary5(self, stack: impl Fn(f64) -> [f64; 5]) -> [Self; 5] {
4173 stack(self)
4174 }
4175}
4176
4177impl Lane for wide::f64x4 {
4178 const LANES: usize = 4;
4179 #[inline]
4180 fn splat(x: f64) -> Self {
4181 wide::f64x4::splat(x)
4182 }
4183 #[inline]
4184 fn add(self, o: Self) -> Self {
4185 self + o
4186 }
4187 #[inline]
4188 fn sub(self, o: Self) -> Self {
4189 self - o
4190 }
4191 #[inline]
4192 fn mul(self, o: Self) -> Self {
4193 self * o
4194 }
4195 #[inline]
4196 fn lane(self, i: usize) -> f64 {
4197 self.to_array()[i]
4198 }
4199 #[inline]
4200 fn unary3(self, stack: impl Fn(f64) -> [f64; 3]) -> [Self; 3] {
4201 let a = self.to_array();
4202 let mut d0 = [0.0_f64; 4];
4203 let mut d1 = [0.0_f64; 4];
4204 let mut d2 = [0.0_f64; 4];
4205 for i in 0..4 {
4206 let s = stack(a[i]);
4207 d0[i] = s[0];
4208 d1[i] = s[1];
4209 d2[i] = s[2];
4210 }
4211 [
4212 wide::f64x4::new(d0),
4213 wide::f64x4::new(d1),
4214 wide::f64x4::new(d2),
4215 ]
4216 }
4217 #[inline]
4218 fn unary5(self, stack: impl Fn(f64) -> [f64; 5]) -> [Self; 5] {
4219 let a = self.to_array();
4220 let mut d = [[0.0_f64; 4]; 5];
4221 for i in 0..4 {
4222 let s = stack(a[i]);
4223 for (k, dk) in d.iter_mut().enumerate() {
4224 dk[i] = s[k];
4225 }
4226 }
4227 [
4228 wide::f64x4::new(d[0]),
4229 wide::f64x4::new(d[1]),
4230 wide::f64x4::new(d[2]),
4231 wide::f64x4::new(d[3]),
4232 wide::f64x4::new(d[4]),
4233 ]
4234 }
4235}
4236
4237#[derive(Clone, Copy, Debug)]
4247pub struct Order2Lane<L: Lane, const K: usize> {
4248 pub v: L,
4250 pub g: [L; K],
4252 pub h: [[L; K]; K],
4254}
4255
4256pub type Order2Batch<const K: usize> = Order2Lane<wide::f64x4, K>;
4258
4259impl<L: Lane, const K: usize> Order2Lane<L, K> {
4260 #[inline]
4262 pub fn constant(c: L) -> Self {
4263 Order2Lane {
4264 v: c,
4265 g: [L::splat(0.0); K],
4266 h: [[L::splat(0.0); K]; K],
4267 }
4268 }
4269
4270 #[inline]
4274 pub fn variable(value: L, axis: usize) -> Self {
4275 let mut out = Self::constant(value);
4276 out.g[axis] = L::splat(1.0);
4277 out
4278 }
4279
4280 #[inline]
4282 pub fn add(&self, o: &Self) -> Self {
4283 let mut out = *self;
4284 out.v = self.v.add(o.v);
4285 for i in 0..K {
4286 out.g[i] = self.g[i].add(o.g[i]);
4287 for j in 0..K {
4288 out.h[i][j] = self.h[i][j].add(o.h[i][j]);
4289 }
4290 }
4291 out
4292 }
4293
4294 #[inline]
4296 pub fn scale(&self, s: f64) -> Self {
4297 let sl = L::splat(s);
4298 let mut out = *self;
4299 out.v = self.v.mul(sl);
4300 for i in 0..K {
4301 out.g[i] = self.g[i].mul(sl);
4302 for j in 0..K {
4303 out.h[i][j] = self.h[i][j].mul(sl);
4304 }
4305 }
4306 out
4307 }
4308
4309 #[inline]
4312 pub fn sub(&self, o: &Self) -> Self {
4313 self.add(&o.scale(-1.0))
4314 }
4315
4316 #[inline]
4318 pub fn neg(&self) -> Self {
4319 self.scale(-1.0)
4320 }
4321
4322 #[inline]
4337 pub fn mul(&self, o: &Self) -> Self {
4338 let a = self;
4339 let b = o;
4340 let mut out = Self::constant(a.v.mul(b.v));
4341 for i in 0..K {
4342 out.g[i] = a.v.mul(b.g[i]).add(a.g[i].mul(b.v));
4344 }
4345 for i in 0..K {
4346 for j in i..K {
4347 let hij =
4349 a.v.mul(b.h[i][j])
4350 .add(a.g[i].mul(b.g[j]))
4351 .add(a.g[j].mul(b.g[i]))
4352 .add(a.h[i][j].mul(b.v));
4353 out.h[i][j] = hij;
4354 out.h[j][i] = hij;
4355 }
4356 }
4357 out
4358 }
4359
4360 #[inline]
4365 pub fn compose_unary(&self, d: [L; 3]) -> Self {
4366 let mut out = Self::constant(d[0]);
4367 for i in 0..K {
4368 let mut acc = L::splat(0.0);
4369 acc = acc.add(d[1].mul(self.g[i]));
4370 out.g[i] = acc;
4371 }
4372 for i in 0..K {
4373 for j in 0..K {
4374 let mut acc = L::splat(0.0);
4375 acc = acc.add(d[1].mul(self.h[i][j]));
4376 acc = acc.add(d[2].mul(self.g[i]).mul(self.g[j]));
4377 out.h[i][j] = acc;
4378 }
4379 }
4380 out
4381 }
4382
4383 #[inline]
4386 pub fn exp(&self) -> Self {
4387 let d = self.v.unary3(|u| {
4388 let e = u.exp();
4389 [e, e, e]
4390 });
4391 self.compose_unary(d)
4392 }
4393
4394 #[inline]
4397 pub fn ln(&self) -> Self {
4398 let d = self.v.unary3(|u| {
4399 let r = 1.0 / u;
4400 [u.ln(), r, -r * r]
4401 });
4402 self.compose_unary(d)
4403 }
4404
4405 #[inline]
4408 pub fn sqrt(&self) -> Self {
4409 let d = self.v.unary3(|u| {
4410 let s = u.sqrt();
4411 [s, 0.5 / s, -0.25 / (u * s)]
4412 });
4413 self.compose_unary(d)
4414 }
4415
4416 #[inline]
4418 pub fn recip(&self) -> Self {
4419 let d = self.v.unary3(|u| {
4420 let r = 1.0 / u;
4421 let r2 = r * r;
4422 [r, -r2, 2.0 * r2 * r]
4423 });
4424 self.compose_unary(d)
4425 }
4426
4427 #[inline]
4430 pub fn powf(&self, a: f64) -> Self {
4431 let d = self.v.unary3(|u| {
4432 [
4433 u.powf(a),
4434 a * u.powf(a - 1.0),
4435 a * (a - 1.0) * u.powf(a - 2.0),
4436 ]
4437 });
4438 self.compose_unary(d)
4439 }
4440}
4441
4442impl<const K: usize> Order2Batch<K> {
4443 #[inline]
4447 #[must_use]
4448 pub fn lane(&self, i: usize) -> Order2<K> {
4449 let mut t = crate::jet_tower::Tower2::<K>::constant(self.v.lane(i));
4450 for a in 0..K {
4451 t.g[a] = self.g[a].lane(i);
4452 for b in 0..K {
4453 t.h[a][b] = self.h[a][b].lane(i);
4454 }
4455 }
4456 Order2(t)
4457 }
4458}
4459
4460#[derive(Clone, Copy, Debug)]
4476pub struct Order1<const K: usize> {
4477 pub v: f64,
4479 pub g: [f64; K],
4481}
4482
4483impl<const K: usize> Order1<K> {
4484 #[inline]
4486 #[must_use]
4487 pub fn g(&self) -> &[f64; K] {
4488 &self.g
4489 }
4490
4491 #[inline]
4493 #[must_use]
4494 pub fn into_channels(self) -> (f64, [f64; K]) {
4495 (self.v, self.g)
4496 }
4497}
4498
4499impl<const K: usize> JetScalar<K> for Order1<K> {
4500 fn constant(c: f64) -> Self {
4501 Order1 { v: c, g: [0.0; K] }
4503 }
4504 fn variable(x: f64, axis: usize) -> Self {
4505 let mut g = [0.0; K];
4507 g[axis] = 1.0;
4508 Order1 { v: x, g }
4509 }
4510}
4511
4512impl<const K: usize> crate::nested_dual::JetField for Order1<K> {
4513 fn value(&self) -> f64 {
4514 self.v
4515 }
4516 fn add(&self, o: &Self) -> Self {
4517 let mut g = self.g;
4519 for i in 0..K {
4520 g[i] += o.g[i];
4521 }
4522 Order1 { v: self.v + o.v, g }
4523 }
4524 fn sub(&self, o: &Self) -> Self {
4525 self.add(&o.scale(-1.0))
4527 }
4528 fn mul(&self, o: &Self) -> Self {
4529 let a = self;
4534 let b = o;
4535 let mut g = [0.0; K];
4536 for i in 0..K {
4537 g[i] = a.v * b.g[i] + a.g[i] * b.v;
4538 }
4539 Order1 { v: a.v * b.v, g }
4540 }
4541 fn neg(&self) -> Self {
4542 self.scale(-1.0)
4544 }
4545 fn scale(&self, s: f64) -> Self {
4546 let mut g = self.g;
4548 for i in 0..K {
4549 g[i] *= s;
4550 }
4551 Order1 { v: self.v * s, g }
4552 }
4553 fn compose_unary(&self, d: [f64; 5]) -> Self {
4554 let mut g = [0.0; K];
4560 for i in 0..K {
4561 g[i] = d[1] * self.g[i];
4562 }
4563 Order1 { v: d[0], g }
4564 }
4565}
4566
4567#[derive(Clone, Copy, Debug)]
4584pub struct OneSeed<const K: usize> {
4585 pub base: Order2<K>,
4587 pub eps: Order2<K>,
4590}
4591
4592impl<const K: usize> OneSeed<K> {
4593 pub fn seed_direction(x: f64, axis: usize, u_axis: f64) -> Self {
4597 OneSeed {
4598 base: Order2::variable(x, axis),
4599 eps: Order2::constant(u_axis),
4600 }
4601 }
4602
4603 pub fn contracted_third(&self) -> [[f64; K]; K] {
4606 *self.eps.h()
4607 }
4608}
4609
4610impl<const K: usize> JetScalar<K> for OneSeed<K> {
4611 fn constant(c: f64) -> Self {
4612 OneSeed {
4613 base: Order2::constant(c),
4614 eps: Order2::constant(0.0),
4615 }
4616 }
4617 fn variable(x: f64, axis: usize) -> Self {
4618 OneSeed {
4620 base: Order2::variable(x, axis),
4621 eps: Order2::constant(0.0),
4622 }
4623 }
4624}
4625
4626impl<const K: usize> crate::nested_dual::JetField for OneSeed<K> {
4627 fn value(&self) -> f64 {
4628 self.base.value()
4629 }
4630 fn add(&self, o: &Self) -> Self {
4631 OneSeed {
4632 base: self.base.add(&o.base),
4633 eps: self.eps.add(&o.eps),
4634 }
4635 }
4636 fn sub(&self, o: &Self) -> Self {
4637 OneSeed {
4638 base: self.base.sub(&o.base),
4639 eps: self.eps.sub(&o.eps),
4640 }
4641 }
4642 fn mul(&self, o: &Self) -> Self {
4643 let ab = &self.base.0;
4649 let ae = &self.eps.0;
4650 let bb = &o.base.0;
4651 let be = &o.eps.0;
4652 let mut eps = crate::jet_tower::Tower2::<K>::zero();
4653 eps.v = ab.v * be.v + ae.v * bb.v;
4654 for i in 0..K {
4655 eps.g[i] = ab.v * be.g[i] + ab.g[i] * be.v + ae.v * bb.g[i] + ae.g[i] * bb.v;
4656 }
4657 for i in 0..K {
4658 for j in i..K {
4659 let channel = ab.v * be.h[i][j]
4660 + ab.g[i] * be.g[j]
4661 + ab.g[j] * be.g[i]
4662 + ab.h[i][j] * be.v
4663 + ae.v * bb.h[i][j]
4664 + ae.g[i] * bb.g[j]
4665 + ae.g[j] * bb.g[i]
4666 + ae.h[i][j] * bb.v;
4667 eps.h[i][j] = channel;
4668 eps.h[j][i] = channel;
4669 }
4670 }
4671 OneSeed {
4672 base: self.base.mul(&o.base),
4673 eps: Order2(eps),
4674 }
4675 }
4676 fn neg(&self) -> Self {
4677 OneSeed {
4678 base: self.base.neg(),
4679 eps: self.eps.neg(),
4680 }
4681 }
4682 fn scale(&self, s: f64) -> Self {
4683 OneSeed {
4684 base: self.base.scale(s),
4685 eps: self.eps.scale(s),
4686 }
4687 }
4688 fn compose_unary(&self, d: [f64; 5]) -> Self {
4689 let base = self.base.compose_unary([d[0], d[1], d[2], d[3], d[4]]);
4694 let b = &self.base.0;
4695 let e = &self.eps.0;
4696 let mut eps = crate::jet_tower::Tower2::<K>::zero();
4697 eps.v = d[1] * e.v;
4698 for i in 0..K {
4699 eps.g[i] = d[2] * b.g[i] * e.v + d[1] * e.g[i];
4700 }
4701 for i in 0..K {
4702 for j in i..K {
4703 let channel = d[1] * e.h[i][j]
4704 + d[2] * (b.g[i] * e.g[j] + b.g[j] * e.g[i] + b.h[i][j] * e.v)
4705 + d[3] * b.g[i] * b.g[j] * e.v;
4706 eps.h[i][j] = channel;
4707 eps.h[j][i] = channel;
4708 }
4709 }
4710 OneSeed {
4711 base,
4712 eps: Order2(eps),
4713 }
4714 }
4715 fn constant_like(&self, v: f64) -> Self {
4716 OneSeed {
4717 base: self.base.constant_like(v),
4718 eps: self.eps.constant_like(0.0),
4719 }
4720 }
4721 fn with_value(&self, v: f64) -> Self {
4722 OneSeed {
4725 base: self.base.with_value(v),
4726 eps: self.eps,
4727 }
4728 }
4729}
4730
4731#[derive(Clone, Copy, Debug)]
4743pub struct OneSeedLane<L: Lane, const K: usize> {
4744 pub base: Order2Lane<L, K>,
4746 pub eps: Order2Lane<L, K>,
4749}
4750
4751pub type OneSeedBatch<const K: usize> = OneSeedLane<wide::f64x4, K>;
4753
4754impl<L: Lane, const K: usize> OneSeedLane<L, K> {
4755 #[inline]
4757 pub fn constant(c: L) -> Self {
4758 OneSeedLane {
4759 base: Order2Lane::constant(c),
4760 eps: Order2Lane::constant(L::splat(0.0)),
4761 }
4762 }
4763
4764 #[inline]
4767 pub fn variable(value: L, axis: usize) -> Self {
4768 OneSeedLane {
4769 base: Order2Lane::variable(value, axis),
4770 eps: Order2Lane::constant(L::splat(0.0)),
4771 }
4772 }
4773
4774 #[inline]
4779 pub fn seed_direction(value: L, axis: usize, u_axis: L) -> Self {
4780 OneSeedLane {
4781 base: Order2Lane::variable(value, axis),
4782 eps: Order2Lane::constant(u_axis),
4783 }
4784 }
4785
4786 #[inline]
4789 #[must_use]
4790 pub fn contracted_third(&self) -> [[L; K]; K] {
4791 self.eps.h
4792 }
4793
4794 #[inline]
4796 pub fn add(&self, o: &Self) -> Self {
4797 OneSeedLane {
4798 base: self.base.add(&o.base),
4799 eps: self.eps.add(&o.eps),
4800 }
4801 }
4802
4803 #[inline]
4805 pub fn sub(&self, o: &Self) -> Self {
4806 OneSeedLane {
4807 base: self.base.sub(&o.base),
4808 eps: self.eps.sub(&o.eps),
4809 }
4810 }
4811
4812 #[inline]
4814 pub fn mul(&self, o: &Self) -> Self {
4815 let ab = &self.base;
4816 let ae = &self.eps;
4817 let bb = &o.base;
4818 let be = &o.eps;
4819 let mut eps = Order2Lane::constant(ab.v.mul(be.v).add(ae.v.mul(bb.v)));
4820 for i in 0..K {
4821 eps.g[i] =
4822 ab.v.mul(be.g[i])
4823 .add(ab.g[i].mul(be.v))
4824 .add(ae.v.mul(bb.g[i]))
4825 .add(ae.g[i].mul(bb.v));
4826 }
4827 for i in 0..K {
4828 for j in i..K {
4829 let channel =
4830 ab.v.mul(be.h[i][j])
4831 .add(ab.g[i].mul(be.g[j]))
4832 .add(ab.g[j].mul(be.g[i]))
4833 .add(ab.h[i][j].mul(be.v))
4834 .add(ae.v.mul(bb.h[i][j]))
4835 .add(ae.g[i].mul(bb.g[j]))
4836 .add(ae.g[j].mul(bb.g[i]))
4837 .add(ae.h[i][j].mul(bb.v));
4838 eps.h[i][j] = channel;
4839 eps.h[j][i] = channel;
4840 }
4841 }
4842 OneSeedLane {
4843 base: self.base.mul(&o.base),
4844 eps,
4845 }
4846 }
4847
4848 #[inline]
4850 pub fn neg(&self) -> Self {
4851 OneSeedLane {
4852 base: self.base.neg(),
4853 eps: self.eps.neg(),
4854 }
4855 }
4856
4857 #[inline]
4859 pub fn scale(&self, s: f64) -> Self {
4860 OneSeedLane {
4861 base: self.base.scale(s),
4862 eps: self.eps.scale(s),
4863 }
4864 }
4865
4866 #[inline]
4872 pub fn compose_unary(&self, d: [L; 5]) -> Self {
4873 let base = self.base.compose_unary([d[0], d[1], d[2]]);
4874 let b = &self.base;
4875 let e = &self.eps;
4876 let mut eps = Order2Lane::constant(d[1].mul(e.v));
4877 for i in 0..K {
4878 eps.g[i] = d[2].mul(b.g[i]).mul(e.v).add(d[1].mul(e.g[i]));
4879 }
4880 for i in 0..K {
4881 for j in i..K {
4882 let mixed = b.g[i]
4883 .mul(e.g[j])
4884 .add(b.g[j].mul(e.g[i]))
4885 .add(b.h[i][j].mul(e.v));
4886 let channel = d[1]
4887 .mul(e.h[i][j])
4888 .add(d[2].mul(mixed))
4889 .add(d[3].mul(b.g[i]).mul(b.g[j]).mul(e.v));
4890 eps.h[i][j] = channel;
4891 eps.h[j][i] = channel;
4892 }
4893 }
4894 OneSeedLane { base, eps }
4895 }
4896
4897 #[inline]
4899 pub fn exp(&self) -> Self {
4900 let d = self.base.v.unary5(|u| {
4901 let e = u.exp();
4902 [e, e, e, e, e]
4903 });
4904 self.compose_unary(d)
4905 }
4906
4907 #[inline]
4909 pub fn ln(&self) -> Self {
4910 let d = self.base.v.unary5(|u| {
4911 let r = 1.0 / u;
4912 [u.ln(), r, -r * r, 2.0 * r * r * r, -6.0 * r * r * r * r]
4913 });
4914 self.compose_unary(d)
4915 }
4916
4917 #[inline]
4919 pub fn sqrt(&self) -> Self {
4920 let d = self.base.v.unary5(|u| {
4921 let s = u.sqrt();
4922 [
4923 s,
4924 0.5 / s,
4925 -0.25 / (u * s),
4926 0.375 / (u * u * s),
4927 -0.9375 / (u * u * u * s),
4928 ]
4929 });
4930 self.compose_unary(d)
4931 }
4932
4933 #[inline]
4935 pub fn recip(&self) -> Self {
4936 let d = self.base.v.unary5(|u| {
4937 let r = 1.0 / u;
4938 let r2 = r * r;
4939 [r, -r2, 2.0 * r2 * r, -6.0 * r2 * r2, 24.0 * r2 * r2 * r]
4940 });
4941 self.compose_unary(d)
4942 }
4943
4944 #[inline]
4947 pub fn powf(&self, a: f64) -> Self {
4948 let d = self.base.v.unary5(|u| {
4949 [
4950 u.powf(a),
4951 a * u.powf(a - 1.0),
4952 a * (a - 1.0) * u.powf(a - 2.0),
4953 a * (a - 1.0) * (a - 2.0) * u.powf(a - 3.0),
4954 a * (a - 1.0) * (a - 2.0) * (a - 3.0) * u.powf(a - 4.0),
4955 ]
4956 });
4957 self.compose_unary(d)
4958 }
4959
4960 #[inline]
4963 pub fn ln_gamma(&self) -> Self {
4964 let d = self
4965 .base
4966 .v
4967 .unary5(crate::jet_tower::ln_gamma_derivative_stack);
4968 self.compose_unary(d)
4969 }
4970
4971 #[inline]
4974 pub fn digamma(&self) -> Self {
4975 let d = self
4976 .base
4977 .v
4978 .unary5(crate::jet_tower::digamma_derivative_stack);
4979 self.compose_unary(d)
4980 }
4981}
4982
4983impl<const K: usize> OneSeedBatch<K> {
4984 #[inline]
4988 #[must_use]
4989 pub fn lane(&self, i: usize) -> OneSeed<K> {
4990 OneSeed {
4991 base: self.base.lane(i),
4992 eps: self.eps.lane(i),
4993 }
4994 }
4995}
4996
4997#[derive(Clone, Copy, Debug)]
5014pub struct TwoSeed<const K: usize> {
5015 pub base: Order2<K>,
5017 pub eps: Order2<K>,
5019 pub del: Order2<K>,
5021 pub eps_del: Order2<K>,
5024}
5025
5026impl<const K: usize> TwoSeed<K> {
5027 pub fn seed(x: f64, axis: usize, u_axis: f64, v_axis: f64) -> Self {
5031 TwoSeed {
5032 base: Order2::variable(x, axis),
5033 eps: Order2::constant(u_axis),
5034 del: Order2::constant(v_axis),
5035 eps_del: Order2::constant(0.0),
5036 }
5037 }
5038
5039 pub fn contracted_fourth(&self) -> [[f64; K]; K] {
5042 *self.eps_del.h()
5043 }
5044}
5045
5046impl<const K: usize> JetScalar<K> for TwoSeed<K> {
5047 fn constant(c: f64) -> Self {
5048 TwoSeed {
5049 base: Order2::constant(c),
5050 eps: Order2::constant(0.0),
5051 del: Order2::constant(0.0),
5052 eps_del: Order2::constant(0.0),
5053 }
5054 }
5055 fn variable(x: f64, axis: usize) -> Self {
5056 TwoSeed {
5057 base: Order2::variable(x, axis),
5058 eps: Order2::constant(0.0),
5059 del: Order2::constant(0.0),
5060 eps_del: Order2::constant(0.0),
5061 }
5062 }
5063}
5064
5065impl<const K: usize> crate::nested_dual::JetField for TwoSeed<K> {
5066 fn value(&self) -> f64 {
5067 self.base.value()
5068 }
5069 fn add(&self, o: &Self) -> Self {
5070 TwoSeed {
5071 base: self.base.add(&o.base),
5072 eps: self.eps.add(&o.eps),
5073 del: self.del.add(&o.del),
5074 eps_del: self.eps_del.add(&o.eps_del),
5075 }
5076 }
5077 fn sub(&self, o: &Self) -> Self {
5078 TwoSeed {
5079 base: self.base.sub(&o.base),
5080 eps: self.eps.sub(&o.eps),
5081 del: self.del.sub(&o.del),
5082 eps_del: self.eps_del.sub(&o.eps_del),
5083 }
5084 }
5085 fn mul(&self, o: &Self) -> Self {
5086 let a = self;
5087 let b = o;
5088 let base = a.base.mul(&b.base);
5090 let eps = a.base.mul(&b.eps).add(&a.eps.mul(&b.base));
5091 let del = a.base.mul(&b.del).add(&a.del.mul(&b.base));
5092 let eps_del = a
5093 .base
5094 .mul(&b.eps_del)
5095 .add(&a.eps.mul(&b.del))
5096 .add(&a.del.mul(&b.eps))
5097 .add(&a.eps_del.mul(&b.base));
5098 TwoSeed {
5099 base,
5100 eps,
5101 del,
5102 eps_del,
5103 }
5104 }
5105 fn neg(&self) -> Self {
5106 TwoSeed {
5107 base: self.base.neg(),
5108 eps: self.eps.neg(),
5109 del: self.del.neg(),
5110 eps_del: self.eps_del.neg(),
5111 }
5112 }
5113 fn scale(&self, s: f64) -> Self {
5114 TwoSeed {
5115 base: self.base.scale(s),
5116 eps: self.eps.scale(s),
5117 del: self.del.scale(s),
5118 eps_del: self.eps_del.scale(s),
5119 }
5120 }
5121 fn compose_unary(&self, d: [f64; 5]) -> Self {
5122 let base = self.base.compose_unary([d[0], d[1], d[2], d[3], d[4]]);
5132 let fprime = self.base.compose_unary([d[1], d[2], d[3], d[4], d[4]]); let fsecond = self.base.compose_unary([d[2], d[3], d[4], d[4], d[4]]); let eps = fprime.mul(&self.eps);
5135 let del = fprime.mul(&self.del);
5136 let eps_del = fsecond
5137 .mul(&self.eps)
5138 .mul(&self.del)
5139 .add(&fprime.mul(&self.eps_del));
5140 TwoSeed {
5141 base,
5142 eps,
5143 del,
5144 eps_del,
5145 }
5146 }
5147}
5148
5149#[derive(Clone, Copy, Debug)]
5160pub struct TwoSeedLane<L: Lane, const K: usize> {
5161 pub base: Order2Lane<L, K>,
5163 pub eps: Order2Lane<L, K>,
5165 pub del: Order2Lane<L, K>,
5167 pub eps_del: Order2Lane<L, K>,
5170}
5171
5172pub type TwoSeedBatch<const K: usize> = TwoSeedLane<wide::f64x4, K>;
5174
5175impl<L: Lane, const K: usize> TwoSeedLane<L, K> {
5176 #[inline]
5179 pub fn constant(c: L) -> Self {
5180 let z = Order2Lane::constant(L::splat(0.0));
5181 TwoSeedLane {
5182 base: Order2Lane::constant(c),
5183 eps: z,
5184 del: z,
5185 eps_del: z,
5186 }
5187 }
5188
5189 #[inline]
5192 pub fn variable(value: L, axis: usize) -> Self {
5193 let z = Order2Lane::constant(L::splat(0.0));
5194 TwoSeedLane {
5195 base: Order2Lane::variable(value, axis),
5196 eps: z,
5197 del: z,
5198 eps_del: z,
5199 }
5200 }
5201
5202 #[inline]
5206 pub fn seed(value: L, axis: usize, u_axis: L, v_axis: L) -> Self {
5207 TwoSeedLane {
5208 base: Order2Lane::variable(value, axis),
5209 eps: Order2Lane::constant(u_axis),
5210 del: Order2Lane::constant(v_axis),
5211 eps_del: Order2Lane::constant(L::splat(0.0)),
5212 }
5213 }
5214
5215 #[inline]
5219 #[must_use]
5220 pub fn contracted_fourth(&self) -> [[L; K]; K] {
5221 self.eps_del.h
5222 }
5223
5224 #[inline]
5226 pub fn add(&self, o: &Self) -> Self {
5227 TwoSeedLane {
5228 base: self.base.add(&o.base),
5229 eps: self.eps.add(&o.eps),
5230 del: self.del.add(&o.del),
5231 eps_del: self.eps_del.add(&o.eps_del),
5232 }
5233 }
5234
5235 #[inline]
5237 pub fn sub(&self, o: &Self) -> Self {
5238 TwoSeedLane {
5239 base: self.base.sub(&o.base),
5240 eps: self.eps.sub(&o.eps),
5241 del: self.del.sub(&o.del),
5242 eps_del: self.eps_del.sub(&o.eps_del),
5243 }
5244 }
5245
5246 #[inline]
5248 pub fn mul(&self, o: &Self) -> Self {
5249 let a = self;
5250 let b = o;
5251 let base = a.base.mul(&b.base);
5252 let eps = a.base.mul(&b.eps).add(&a.eps.mul(&b.base));
5253 let del = a.base.mul(&b.del).add(&a.del.mul(&b.base));
5254 let eps_del = a
5255 .base
5256 .mul(&b.eps_del)
5257 .add(&a.eps.mul(&b.del))
5258 .add(&a.del.mul(&b.eps))
5259 .add(&a.eps_del.mul(&b.base));
5260 TwoSeedLane {
5261 base,
5262 eps,
5263 del,
5264 eps_del,
5265 }
5266 }
5267
5268 #[inline]
5270 pub fn neg(&self) -> Self {
5271 TwoSeedLane {
5272 base: self.base.neg(),
5273 eps: self.eps.neg(),
5274 del: self.del.neg(),
5275 eps_del: self.eps_del.neg(),
5276 }
5277 }
5278
5279 #[inline]
5281 pub fn scale(&self, s: f64) -> Self {
5282 TwoSeedLane {
5283 base: self.base.scale(s),
5284 eps: self.eps.scale(s),
5285 del: self.del.scale(s),
5286 eps_del: self.eps_del.scale(s),
5287 }
5288 }
5289
5290 #[inline]
5296 pub fn compose_unary(&self, d: [L; 5]) -> Self {
5297 let base = self.base.compose_unary([d[0], d[1], d[2]]);
5298 let fprime = self.base.compose_unary([d[1], d[2], d[3]]);
5299 let fsecond = self.base.compose_unary([d[2], d[3], d[4]]);
5300 let eps = fprime.mul(&self.eps);
5301 let del = fprime.mul(&self.del);
5302 let eps_del = fsecond
5303 .mul(&self.eps)
5304 .mul(&self.del)
5305 .add(&fprime.mul(&self.eps_del));
5306 TwoSeedLane {
5307 base,
5308 eps,
5309 del,
5310 eps_del,
5311 }
5312 }
5313
5314 #[inline]
5316 pub fn exp(&self) -> Self {
5317 let d = self.base.v.unary5(|u| {
5318 let e = u.exp();
5319 [e, e, e, e, e]
5320 });
5321 self.compose_unary(d)
5322 }
5323
5324 #[inline]
5326 pub fn ln(&self) -> Self {
5327 let d = self.base.v.unary5(|u| {
5328 let r = 1.0 / u;
5329 [u.ln(), r, -r * r, 2.0 * r * r * r, -6.0 * r * r * r * r]
5330 });
5331 self.compose_unary(d)
5332 }
5333
5334 #[inline]
5336 pub fn sqrt(&self) -> Self {
5337 let d = self.base.v.unary5(|u| {
5338 let s = u.sqrt();
5339 [
5340 s,
5341 0.5 / s,
5342 -0.25 / (u * s),
5343 0.375 / (u * u * s),
5344 -0.9375 / (u * u * u * s),
5345 ]
5346 });
5347 self.compose_unary(d)
5348 }
5349
5350 #[inline]
5352 pub fn recip(&self) -> Self {
5353 let d = self.base.v.unary5(|u| {
5354 let r = 1.0 / u;
5355 let r2 = r * r;
5356 [r, -r2, 2.0 * r2 * r, -6.0 * r2 * r2, 24.0 * r2 * r2 * r]
5357 });
5358 self.compose_unary(d)
5359 }
5360
5361 #[inline]
5364 pub fn powf(&self, a: f64) -> Self {
5365 let d = self.base.v.unary5(|u| {
5366 [
5367 u.powf(a),
5368 a * u.powf(a - 1.0),
5369 a * (a - 1.0) * u.powf(a - 2.0),
5370 a * (a - 1.0) * (a - 2.0) * u.powf(a - 3.0),
5371 a * (a - 1.0) * (a - 2.0) * (a - 3.0) * u.powf(a - 4.0),
5372 ]
5373 });
5374 self.compose_unary(d)
5375 }
5376
5377 #[inline]
5379 pub fn ln_gamma(&self) -> Self {
5380 let d = self
5381 .base
5382 .v
5383 .unary5(crate::jet_tower::ln_gamma_derivative_stack);
5384 self.compose_unary(d)
5385 }
5386
5387 #[inline]
5390 pub fn digamma(&self) -> Self {
5391 let d = self
5392 .base
5393 .v
5394 .unary5(crate::jet_tower::digamma_derivative_stack);
5395 self.compose_unary(d)
5396 }
5397}
5398
5399impl<const K: usize> TwoSeedBatch<K> {
5400 #[inline]
5404 #[must_use]
5405 pub fn lane(&self, i: usize) -> TwoSeed<K> {
5406 TwoSeed {
5407 base: self.base.lane(i),
5408 eps: self.eps.lane(i),
5409 del: self.del.lane(i),
5410 eps_del: self.eps_del.lane(i),
5411 }
5412 }
5413}
5414
5415impl<const K: usize> JetScalar<K> for crate::jet_tower::Tower3<K> {
5422 fn constant(c: f64) -> Self {
5423 crate::jet_tower::Tower3::constant(c)
5424 }
5425 fn variable(x: f64, axis: usize) -> Self {
5426 crate::jet_tower::Tower3::variable(x, axis)
5427 }
5428}
5429
5430impl<const K: usize> crate::nested_dual::JetField for crate::jet_tower::Tower3<K> {
5431 fn value(&self) -> f64 {
5432 self.v
5433 }
5434 fn add(&self, o: &Self) -> Self {
5435 *self + *o
5436 }
5437 fn sub(&self, o: &Self) -> Self {
5438 *self + o.scale(-1.0)
5439 }
5440 fn mul(&self, o: &Self) -> Self {
5441 crate::jet_tower::Tower3::mul(self, o)
5442 }
5443 fn neg(&self) -> Self {
5444 self.scale(-1.0)
5445 }
5446 fn scale(&self, s: f64) -> Self {
5447 crate::jet_tower::Tower3::scale(self, s)
5448 }
5449 fn compose_unary(&self, d: [f64; 5]) -> Self {
5450 crate::jet_tower::Tower3::compose_unary(self, [d[0], d[1], d[2], d[3]])
5451 }
5452}
5453
5454impl<const K: usize> JetScalar<K> for crate::jet_tower::Tower4<K> {
5469 fn constant(c: f64) -> Self {
5470 crate::jet_tower::Tower4::constant(c)
5471 }
5472 fn variable(x: f64, axis: usize) -> Self {
5473 crate::jet_tower::Tower4::variable(x, axis)
5474 }
5475}
5476
5477impl<const K: usize> crate::nested_dual::JetField for crate::jet_tower::Tower4<K> {
5478 fn value(&self) -> f64 {
5479 self.v
5480 }
5481 fn add(&self, o: &Self) -> Self {
5482 *self + *o
5483 }
5484 fn sub(&self, o: &Self) -> Self {
5485 *self - *o
5486 }
5487 fn mul(&self, o: &Self) -> Self {
5488 crate::jet_tower::Tower4::mul(self, o)
5489 }
5490 fn neg(&self) -> Self {
5491 self.scale(-1.0)
5492 }
5493 fn scale(&self, s: f64) -> Self {
5494 crate::jet_tower::Tower4::scale(self, s)
5495 }
5496 fn compose_unary(&self, d: [f64; 5]) -> Self {
5497 crate::jet_tower::Tower4::compose_unary(self, d)
5498 }
5499}
5500
5501#[cfg(test)]
5502mod tests {
5503 use super::*;
5504 use crate::jet_tower::{RowProgram, Tower4, program_full_tower};
5505 use crate::nested_dual::JetField;
5506
5507 struct DenseSymmetric3([[f64; 3]; 3]);
5508
5509 impl SymmetricQuadraticCoefficients for DenseSymmetric3 {
5510 fn dimension(&self) -> usize {
5511 3
5512 }
5513
5514 fn multiply(&self, input: &[f64], output: &mut [f64]) {
5515 assert_eq!(input.len(), 3);
5516 assert_eq!(output.len(), 3);
5517 for (row, output) in output.iter_mut().enumerate() {
5518 *output = (0..3)
5519 .map(|column| self.0[row][column] * input[column])
5520 .sum();
5521 }
5522 }
5523
5524 fn coefficient(&self, row: usize, column: usize) -> f64 {
5525 self.0[row][column]
5526 }
5527 }
5528
5529 #[test]
5530 fn symmetric_quadratic_order2_lowerings_match_scalar_program() {
5531 const K: usize = 4;
5532 let coefficients = DenseSymmetric3([[1.2, 0.3, -0.2], [0.3, 0.8, 0.15], [-0.2, 0.15, 1.5]]);
5533 let values = [0.4, -0.7, 1.1, 0.25];
5534 let fixed_vars: [Order2<K>; K] =
5535 std::array::from_fn(|axis| Order2::variable(values[axis], axis));
5536 let fixed_inputs = [
5537 fixed_vars[0].mul(&fixed_vars[1]).add(&fixed_vars[3]),
5538 fixed_vars[1].exp().add(&fixed_vars[2].scale(0.4)),
5539 fixed_vars[2].mul(&fixed_vars[2]).sub(&fixed_vars[0]),
5540 ];
5541 let fixed_direct = Order2::symmetric_quadratic_form(&fixed_inputs, &coefficients);
5542 let fixed_scalar = symmetric_quadratic_form_default(
5543 &fixed_inputs,
5544 &coefficients,
5545 Order2::constant,
5546 JetField::add,
5547 JetField::mul,
5548 JetField::scale,
5549 );
5550 let weights = [0.7, -1.1, 0.35];
5551 let fixed_linear_direct = Order2::linear_combination(&fixed_inputs, &weights);
5552 let fixed_linear_scalar = linear_combination_default(
5553 &fixed_inputs,
5554 &weights,
5555 Order2::constant,
5556 JetField::add,
5557 JetField::scale,
5558 );
5559 let derivative_stacks = [
5560 [0.8, -0.3, 0.7, 0.0, 0.0],
5561 [-0.2, 1.1, -0.4, 0.0, 0.0],
5562 [1.4, 0.25, 0.6, 0.0, 0.0],
5563 ];
5564 let fixed_add_direct = fixed_inputs[0].add_constant(0.65);
5565 let fixed_add_scalar = fixed_inputs[0].add(&Order2::constant(0.65));
5566 let fixed_multiply_add_direct =
5567 fixed_inputs[0].multiply_add(&fixed_inputs[1], &fixed_inputs[2]);
5568 let fixed_multiply_add_scalar = multiply_add_default(
5569 &fixed_inputs[0],
5570 &fixed_inputs[1],
5571 &fixed_inputs[2],
5572 JetField::mul,
5573 JetField::add,
5574 );
5575 let fixed_composed_direct = Order2::composed_sum(&fixed_inputs, &derivative_stacks);
5576 let fixed_composed_scalar = composed_sum_default(
5577 &fixed_inputs,
5578 &derivative_stacks,
5579 Order2::constant,
5580 JetField::add,
5581 JetField::compose_unary,
5582 );
5583
5584 let value_vars: [RuntimeValue; K] =
5585 std::array::from_fn(|axis| RuntimeValue::variable(values[axis], axis, K, &()));
5586 let value_inputs = [
5587 value_vars[0].mul(&value_vars[1]).add(&value_vars[3]),
5588 value_vars[1].exp().add(&value_vars[2].scale(0.4)),
5589 value_vars[2].mul(&value_vars[2]).sub(&value_vars[0]),
5590 ];
5591 let value_quadratic =
5592 RuntimeValue::symmetric_quadratic_form(&value_inputs, &coefficients, K, &());
5593 let value_linear = RuntimeValue::linear_combination(&value_inputs, &weights, K, &());
5594 let value_composed = RuntimeValue::composed_sum(&value_inputs, &derivative_stacks, K, &());
5595
5596 let arena = DynamicJetArena::new();
5597 let dynamic_vars: [DynamicOrder2<'_>; K] =
5598 std::array::from_fn(|axis| DynamicOrder2::variable(values[axis], axis, K, &arena));
5599 let dynamic_inputs = [
5600 dynamic_vars[0].mul(&dynamic_vars[1]).add(&dynamic_vars[3]),
5601 dynamic_vars[1].exp().add(&dynamic_vars[2].scale(0.4)),
5602 dynamic_vars[2].mul(&dynamic_vars[2]).sub(&dynamic_vars[0]),
5603 ];
5604 let dynamic_direct =
5605 DynamicOrder2::symmetric_quadratic_form(&dynamic_inputs, &coefficients, K, &arena);
5606 let dynamic_scalar = symmetric_quadratic_form_default(
5607 &dynamic_inputs,
5608 &coefficients,
5609 |value| DynamicOrder2::constant(value, K, &arena),
5610 RuntimeJetScalar::add,
5611 RuntimeJetScalar::mul,
5612 RuntimeJetScalar::scale,
5613 );
5614 let dynamic_linear_direct =
5615 DynamicOrder2::linear_combination(&dynamic_inputs, &weights, K, &arena);
5616 let dynamic_linear_scalar = linear_combination_default(
5617 &dynamic_inputs,
5618 &weights,
5619 |value| DynamicOrder2::constant(value, K, &arena),
5620 RuntimeJetScalar::add,
5621 RuntimeJetScalar::scale,
5622 );
5623 let dynamic_add_direct = dynamic_inputs[0].add_constant(0.65);
5624 let dynamic_add_scalar = dynamic_inputs[0].add(&DynamicOrder2::constant(0.65, K, &arena));
5625 let dynamic_multiply_add_direct =
5626 dynamic_inputs[0].multiply_add(&dynamic_inputs[1], &dynamic_inputs[2]);
5627 let dynamic_multiply_add_scalar = multiply_add_default(
5628 &dynamic_inputs[0],
5629 &dynamic_inputs[1],
5630 &dynamic_inputs[2],
5631 RuntimeJetScalar::mul,
5632 RuntimeJetScalar::add,
5633 );
5634 let dynamic_composed_direct =
5635 DynamicOrder2::composed_sum(&dynamic_inputs, &derivative_stacks, K, &arena);
5636 let dynamic_composed_scalar = composed_sum_default(
5637 &dynamic_inputs,
5638 &derivative_stacks,
5639 |value| DynamicOrder2::constant(value, K, &arena),
5640 RuntimeJetScalar::add,
5641 RuntimeJetScalar::compose_unary,
5642 );
5643
5644 let tolerance = 2.0e-13;
5645 for (label, actual, expected) in [
5646 ("fixed value", fixed_direct.value(), fixed_scalar.value()),
5647 (
5648 "zero-order quadratic value",
5649 value_quadratic.value(),
5650 fixed_direct.value(),
5651 ),
5652 (
5653 "zero-order linear value",
5654 value_linear.value(),
5655 fixed_linear_direct.value(),
5656 ),
5657 (
5658 "zero-order composed value",
5659 value_composed.value(),
5660 fixed_composed_direct.value(),
5661 ),
5662 (
5663 "dynamic value",
5664 dynamic_direct.value(),
5665 dynamic_scalar.value(),
5666 ),
5667 (
5668 "fixed linear value",
5669 fixed_linear_direct.value(),
5670 fixed_linear_scalar.value(),
5671 ),
5672 (
5673 "dynamic linear value",
5674 dynamic_linear_direct.value(),
5675 dynamic_linear_scalar.value(),
5676 ),
5677 (
5678 "fixed add-constant value",
5679 fixed_add_direct.value(),
5680 fixed_add_scalar.value(),
5681 ),
5682 (
5683 "dynamic add-constant value",
5684 dynamic_add_direct.value(),
5685 dynamic_add_scalar.value(),
5686 ),
5687 (
5688 "fixed multiply-add value",
5689 fixed_multiply_add_direct.value(),
5690 fixed_multiply_add_scalar.value(),
5691 ),
5692 (
5693 "dynamic multiply-add value",
5694 dynamic_multiply_add_direct.value(),
5695 dynamic_multiply_add_scalar.value(),
5696 ),
5697 (
5698 "fixed composed-sum value",
5699 fixed_composed_direct.value(),
5700 fixed_composed_scalar.value(),
5701 ),
5702 (
5703 "dynamic composed-sum value",
5704 dynamic_composed_direct.value(),
5705 dynamic_composed_scalar.value(),
5706 ),
5707 ] {
5708 assert!(
5709 (actual - expected).abs() <= tolerance * actual.abs().max(expected.abs()).max(1.0),
5710 "{label}: direct={actual:+.16e}, scalar={expected:+.16e}"
5711 );
5712 }
5713 for primary_a in 0..K {
5714 for (label, actual, expected) in [
5715 (
5716 "fixed gradient",
5717 fixed_direct.g()[primary_a],
5718 fixed_scalar.g()[primary_a],
5719 ),
5720 (
5721 "dynamic gradient",
5722 dynamic_direct.g()[primary_a],
5723 dynamic_scalar.g()[primary_a],
5724 ),
5725 (
5726 "fixed linear gradient",
5727 fixed_linear_direct.g()[primary_a],
5728 fixed_linear_scalar.g()[primary_a],
5729 ),
5730 (
5731 "dynamic linear gradient",
5732 dynamic_linear_direct.g()[primary_a],
5733 dynamic_linear_scalar.g()[primary_a],
5734 ),
5735 (
5736 "fixed add-constant gradient",
5737 fixed_add_direct.g()[primary_a],
5738 fixed_add_scalar.g()[primary_a],
5739 ),
5740 (
5741 "dynamic add-constant gradient",
5742 dynamic_add_direct.g()[primary_a],
5743 dynamic_add_scalar.g()[primary_a],
5744 ),
5745 (
5746 "fixed multiply-add gradient",
5747 fixed_multiply_add_direct.g()[primary_a],
5748 fixed_multiply_add_scalar.g()[primary_a],
5749 ),
5750 (
5751 "dynamic multiply-add gradient",
5752 dynamic_multiply_add_direct.g()[primary_a],
5753 dynamic_multiply_add_scalar.g()[primary_a],
5754 ),
5755 (
5756 "fixed composed-sum gradient",
5757 fixed_composed_direct.g()[primary_a],
5758 fixed_composed_scalar.g()[primary_a],
5759 ),
5760 (
5761 "dynamic composed-sum gradient",
5762 dynamic_composed_direct.g()[primary_a],
5763 dynamic_composed_scalar.g()[primary_a],
5764 ),
5765 ] {
5766 assert!(
5767 (actual - expected).abs()
5768 <= tolerance * actual.abs().max(expected.abs()).max(1.0),
5769 "{label}[{primary_a}]: direct={actual:+.16e}, scalar={expected:+.16e}"
5770 );
5771 }
5772 for primary_b in 0..K {
5773 for (label, actual, expected) in [
5774 (
5775 "fixed Hessian",
5776 fixed_direct.h()[primary_a][primary_b],
5777 fixed_scalar.h()[primary_a][primary_b],
5778 ),
5779 (
5780 "dynamic Hessian",
5781 dynamic_direct.h_at(primary_a, primary_b),
5782 dynamic_scalar.h_at(primary_a, primary_b),
5783 ),
5784 (
5785 "fixed linear Hessian",
5786 fixed_linear_direct.h()[primary_a][primary_b],
5787 fixed_linear_scalar.h()[primary_a][primary_b],
5788 ),
5789 (
5790 "dynamic linear Hessian",
5791 dynamic_linear_direct.h_at(primary_a, primary_b),
5792 dynamic_linear_scalar.h_at(primary_a, primary_b),
5793 ),
5794 (
5795 "fixed add-constant Hessian",
5796 fixed_add_direct.h()[primary_a][primary_b],
5797 fixed_add_scalar.h()[primary_a][primary_b],
5798 ),
5799 (
5800 "dynamic add-constant Hessian",
5801 dynamic_add_direct.h_at(primary_a, primary_b),
5802 dynamic_add_scalar.h_at(primary_a, primary_b),
5803 ),
5804 (
5805 "fixed multiply-add Hessian",
5806 fixed_multiply_add_direct.h()[primary_a][primary_b],
5807 fixed_multiply_add_scalar.h()[primary_a][primary_b],
5808 ),
5809 (
5810 "dynamic multiply-add Hessian",
5811 dynamic_multiply_add_direct.h_at(primary_a, primary_b),
5812 dynamic_multiply_add_scalar.h_at(primary_a, primary_b),
5813 ),
5814 (
5815 "fixed composed-sum Hessian",
5816 fixed_composed_direct.h()[primary_a][primary_b],
5817 fixed_composed_scalar.h()[primary_a][primary_b],
5818 ),
5819 (
5820 "dynamic composed-sum Hessian",
5821 dynamic_composed_direct.h_at(primary_a, primary_b),
5822 dynamic_composed_scalar.h_at(primary_a, primary_b),
5823 ),
5824 ] {
5825 assert!(
5826 (actual - expected).abs()
5827 <= tolerance * actual.abs().max(expected.abs()).max(1.0),
5828 "{label}[{primary_a},{primary_b}]: direct={actual:+.16e}, scalar={expected:+.16e}"
5829 );
5830 }
5831 }
5832 }
5833 }
5834
5835 #[test]
5836 fn compiled_product_affine_and_fused_nodes_match_scalar_program_randomized() {
5837 const K: usize = 4;
5838 const TERMS: usize = 10;
5839
5840 fn sample(state: &mut u64) -> f64 {
5841 *state ^= *state << 13;
5842 *state ^= *state >> 7;
5843 *state ^= *state << 17;
5844 let unit = (*state >> 11) as f64 * (1.0 / ((1_u64 << 53) as f64));
5845 2.0 * unit - 1.0
5846 }
5847
5848 fn arbitrary_order2<const K: usize>(state: &mut u64) -> Order2<K> {
5849 let mut tower = crate::jet_tower::Tower2::zero();
5850 tower.v = sample(state);
5851 for primary in 0..K {
5852 tower.g[primary] = sample(state);
5853 for other in primary..K {
5854 let channel = sample(state);
5855 tower.h[primary][other] = channel;
5856 tower.h[other][primary] = channel;
5857 }
5858 }
5859 Order2(tower)
5860 }
5861
5862 fn close(actual: f64, expected: f64, case: usize, label: &str) {
5863 let tolerance = 2.0e-12 * actual.abs().max(expected.abs()).max(1.0);
5864 assert!(
5865 (actual - expected).abs() <= tolerance,
5866 "case {case} {label}: direct={actual:+.16e}, scalar={expected:+.16e}, tolerance={tolerance:.3e}"
5867 );
5868 }
5869
5870 let mut state = 0x932a_ff1e_c0de_5eed_u64;
5871 for case in 0..256 {
5872 let fixed_inputs: [Order2<K>; TERMS] =
5876 std::array::from_fn(|_| arbitrary_order2(&mut state));
5877 let mut input_scales: [f64; TERMS] = std::array::from_fn(|_| sample(&mut state));
5878 input_scales[0] = -1.25;
5879 input_scales[1] = 0.0;
5880 input_scales[4] = 1.25;
5881 let addend_scales: [f64; TERMS] = std::array::from_fn(|term| match term % 4 {
5882 0 => 0.0,
5883 1 => 1.0,
5884 2 => -0.75,
5885 _ => 0.35,
5886 });
5887 let derivative_stacks: [[f64; 5]; TERMS] =
5888 std::array::from_fn(|_| std::array::from_fn(|_| sample(&mut state)));
5889 let input_shift = sample(&mut state);
5890 let mut fixed_lefts = std::array::from_fn(|term| &fixed_inputs[term]);
5891 fixed_lefts[4] = &fixed_inputs[0];
5892 fixed_lefts[5] = &fixed_inputs[0];
5893 let fixed_right = &fixed_inputs[1];
5894 let fixed_addend = &fixed_inputs[2];
5895
5896 let fixed_product_direct = fixed_inputs[0].product(&fixed_inputs[1]);
5897 let fixed_product_scalar = fixed_inputs[0].mul(&fixed_inputs[1]);
5898 let fixed_affine_direct =
5899 fixed_inputs[2].affine_compose(input_scales[2], input_shift, derivative_stacks[2]);
5900 let fixed_affine_scalar = affine_compose_default(
5901 &fixed_inputs[2],
5902 input_scales[2],
5903 input_shift,
5904 derivative_stacks[2],
5905 JetField::scale,
5906 Order2::add_constant,
5907 JetField::compose_unary,
5908 );
5909 let fixed_sum_direct =
5910 Order2::affine_composed_sum(&fixed_inputs, &input_scales, &derivative_stacks);
5911 let fixed_sum_scalar = affine_composed_sum_default(
5912 &fixed_inputs,
5913 &input_scales,
5914 &derivative_stacks,
5915 Order2::constant,
5916 JetField::add,
5917 JetField::scale,
5918 Order2::add_constant,
5919 JetField::compose_unary,
5920 );
5921 let fixed_fused_direct = Order2::shared_multiply_add_affine_composed_sum(
5922 &fixed_lefts,
5923 fixed_right,
5924 fixed_addend,
5925 &addend_scales,
5926 &input_scales,
5927 &derivative_stacks,
5928 );
5929 let fixed_fused_scalar = shared_multiply_add_affine_composed_sum_default(
5930 &fixed_lefts,
5931 fixed_right,
5932 fixed_addend,
5933 &addend_scales,
5934 &input_scales,
5935 &derivative_stacks,
5936 Order2::constant,
5937 JetField::add,
5938 JetField::mul,
5939 JetField::scale,
5940 Order2::multiply_add,
5941 Order2::affine_compose,
5942 );
5943
5944 let arena = DynamicJetArena::new();
5945 let dynamic_inputs: [DynamicOrder2<'_>; TERMS] = std::array::from_fn(|term| {
5946 DynamicOrder2::from_channel_functions(
5947 fixed_inputs[term].value(),
5948 K,
5949 &arena,
5950 |primary| fixed_inputs[term].g()[primary],
5951 |primary, other| fixed_inputs[term].h()[primary][other],
5952 )
5953 });
5954 let dynamic_product_direct = dynamic_inputs[0].product(&dynamic_inputs[1]);
5955 let dynamic_product_scalar = dynamic_inputs[0].mul(&dynamic_inputs[1]);
5956 let dynamic_affine_direct = dynamic_inputs[2].affine_compose(
5957 input_scales[2],
5958 input_shift,
5959 derivative_stacks[2],
5960 );
5961 let dynamic_affine_scalar = affine_compose_default(
5962 &dynamic_inputs[2],
5963 input_scales[2],
5964 input_shift,
5965 derivative_stacks[2],
5966 RuntimeJetScalar::scale,
5967 |input, constant| input.add_constant(constant),
5968 RuntimeJetScalar::compose_unary,
5969 );
5970 let dynamic_sum_direct = DynamicOrder2::affine_composed_sum(
5971 &dynamic_inputs,
5972 &input_scales,
5973 &derivative_stacks,
5974 K,
5975 &arena,
5976 );
5977 let dynamic_sum_scalar = affine_composed_sum_default(
5978 &dynamic_inputs,
5979 &input_scales,
5980 &derivative_stacks,
5981 |value| DynamicOrder2::constant(value, K, &arena),
5982 RuntimeJetScalar::add,
5983 RuntimeJetScalar::scale,
5984 |input, constant| input.add_constant(constant),
5985 RuntimeJetScalar::compose_unary,
5986 );
5987 let mut dynamic_lefts = std::array::from_fn(|term| &dynamic_inputs[term]);
5988 dynamic_lefts[4] = &dynamic_inputs[0];
5989 dynamic_lefts[5] = &dynamic_inputs[0];
5990 let dynamic_right = &dynamic_inputs[1];
5991 let dynamic_addend = &dynamic_inputs[2];
5992 let dynamic_fused_direct = DynamicOrder2::shared_multiply_add_affine_composed_sum(
5993 &dynamic_lefts,
5994 dynamic_right,
5995 dynamic_addend,
5996 &addend_scales,
5997 &input_scales,
5998 &derivative_stacks,
5999 K,
6000 &arena,
6001 );
6002 let dynamic_fused_scalar = shared_multiply_add_affine_composed_sum_default(
6003 &dynamic_lefts,
6004 dynamic_right,
6005 dynamic_addend,
6006 &addend_scales,
6007 &input_scales,
6008 &derivative_stacks,
6009 |value| DynamicOrder2::constant(value, K, &arena),
6010 RuntimeJetScalar::add,
6011 RuntimeJetScalar::mul,
6012 RuntimeJetScalar::scale,
6013 RuntimeJetScalar::multiply_add,
6014 |input, scale, shift, stack| input.affine_compose(scale, shift, stack),
6015 );
6016
6017 for (label, actual, expected) in [
6018 (
6019 "fixed product value",
6020 fixed_product_direct.value(),
6021 fixed_product_scalar.value(),
6022 ),
6023 (
6024 "fixed affine value",
6025 fixed_affine_direct.value(),
6026 fixed_affine_scalar.value(),
6027 ),
6028 (
6029 "fixed affine sum value",
6030 fixed_sum_direct.value(),
6031 fixed_sum_scalar.value(),
6032 ),
6033 (
6034 "fixed fused value",
6035 fixed_fused_direct.value(),
6036 fixed_fused_scalar.value(),
6037 ),
6038 (
6039 "dynamic product value",
6040 dynamic_product_direct.value(),
6041 dynamic_product_scalar.value(),
6042 ),
6043 (
6044 "dynamic affine value",
6045 dynamic_affine_direct.value(),
6046 dynamic_affine_scalar.value(),
6047 ),
6048 (
6049 "dynamic affine sum value",
6050 dynamic_sum_direct.value(),
6051 dynamic_sum_scalar.value(),
6052 ),
6053 (
6054 "dynamic fused value",
6055 dynamic_fused_direct.value(),
6056 dynamic_fused_scalar.value(),
6057 ),
6058 ] {
6059 close(actual, expected, case, label);
6060 }
6061 for primary in 0..K {
6062 for (label, actual, expected) in [
6063 (
6064 "fixed product gradient",
6065 fixed_product_direct.g()[primary],
6066 fixed_product_scalar.g()[primary],
6067 ),
6068 (
6069 "fixed affine gradient",
6070 fixed_affine_direct.g()[primary],
6071 fixed_affine_scalar.g()[primary],
6072 ),
6073 (
6074 "fixed affine sum gradient",
6075 fixed_sum_direct.g()[primary],
6076 fixed_sum_scalar.g()[primary],
6077 ),
6078 (
6079 "fixed fused gradient",
6080 fixed_fused_direct.g()[primary],
6081 fixed_fused_scalar.g()[primary],
6082 ),
6083 (
6084 "dynamic product gradient",
6085 dynamic_product_direct.g()[primary],
6086 dynamic_product_scalar.g()[primary],
6087 ),
6088 (
6089 "dynamic affine gradient",
6090 dynamic_affine_direct.g()[primary],
6091 dynamic_affine_scalar.g()[primary],
6092 ),
6093 (
6094 "dynamic affine sum gradient",
6095 dynamic_sum_direct.g()[primary],
6096 dynamic_sum_scalar.g()[primary],
6097 ),
6098 (
6099 "dynamic fused gradient",
6100 dynamic_fused_direct.g()[primary],
6101 dynamic_fused_scalar.g()[primary],
6102 ),
6103 ] {
6104 close(actual, expected, case, label);
6105 }
6106 for other in 0..K {
6107 for (label, actual, expected) in [
6108 (
6109 "fixed product Hessian",
6110 fixed_product_direct.h()[primary][other],
6111 fixed_product_scalar.h()[primary][other],
6112 ),
6113 (
6114 "fixed affine Hessian",
6115 fixed_affine_direct.h()[primary][other],
6116 fixed_affine_scalar.h()[primary][other],
6117 ),
6118 (
6119 "fixed affine sum Hessian",
6120 fixed_sum_direct.h()[primary][other],
6121 fixed_sum_scalar.h()[primary][other],
6122 ),
6123 (
6124 "fixed fused Hessian",
6125 fixed_fused_direct.h()[primary][other],
6126 fixed_fused_scalar.h()[primary][other],
6127 ),
6128 (
6129 "dynamic product Hessian",
6130 dynamic_product_direct.h_at(primary, other),
6131 dynamic_product_scalar.h_at(primary, other),
6132 ),
6133 (
6134 "dynamic affine Hessian",
6135 dynamic_affine_direct.h_at(primary, other),
6136 dynamic_affine_scalar.h_at(primary, other),
6137 ),
6138 (
6139 "dynamic affine sum Hessian",
6140 dynamic_sum_direct.h_at(primary, other),
6141 dynamic_sum_scalar.h_at(primary, other),
6142 ),
6143 (
6144 "dynamic fused Hessian",
6145 dynamic_fused_direct.h_at(primary, other),
6146 dynamic_fused_scalar.h_at(primary, other),
6147 ),
6148 ] {
6149 close(actual, expected, case, label);
6150 }
6151 }
6152 }
6153 }
6154 }
6155
6156 #[test]
6157 fn shared_product_composition_accepts_empty_expression() {
6158 const K: usize = 4;
6159 let fixed_terms: [&Order2<K>; 0] = [];
6160 let fixed_shared = Order2::constant(1.0);
6161 let scales: [f64; 0] = [];
6162 let stacks: [[f64; 5]; 0] = [];
6163 let fixed = Order2::shared_multiply_add_affine_composed_sum(
6164 &fixed_terms,
6165 &fixed_shared,
6166 &fixed_shared,
6167 &scales,
6168 &scales,
6169 &stacks,
6170 );
6171 assert_eq!(fixed.value().to_bits(), 0.0_f64.to_bits());
6172 assert!(fixed.g().iter().all(|&channel| channel == 0.0));
6173 assert!(fixed.h().iter().flatten().all(|&channel| channel == 0.0));
6174
6175 let value_terms: [&RuntimeValue; 0] = [];
6176 let value_shared = RuntimeValue::constant(1.0, K, &());
6177 let value = RuntimeValue::shared_multiply_add_affine_composed_sum(
6178 &value_terms,
6179 &value_shared,
6180 &value_shared,
6181 &scales,
6182 &scales,
6183 &stacks,
6184 K,
6185 &(),
6186 );
6187 assert_eq!(value.value().to_bits(), 0.0_f64.to_bits());
6188 assert_eq!(value.dimension(), K);
6189
6190 let arena = DynamicJetArena::new();
6191 let dynamic_terms: [&DynamicOrder2<'_>; 0] = [];
6192 let dynamic_shared = DynamicOrder2::constant(1.0, K, &arena);
6193 let dynamic = DynamicOrder2::shared_multiply_add_affine_composed_sum(
6194 &dynamic_terms,
6195 &dynamic_shared,
6196 &dynamic_shared,
6197 &scales,
6198 &scales,
6199 &stacks,
6200 K,
6201 &arena,
6202 );
6203 assert_eq!(dynamic.value().to_bits(), 0.0_f64.to_bits());
6204 assert!((0..K).all(|axis| dynamic.g()[axis] == 0.0));
6205 assert!((0..K).all(|row| (0..K).all(|column| dynamic.h_at(row, column) == 0.0)));
6206 }
6207
6208 #[test]
6209 fn runtime_fused_product_composition_preserves_tower4_channels() {
6210 const K: usize = 2;
6211 const N: usize = 9;
6212 let vars = [
6213 Tower4::<K>::variable(0.37, 0),
6214 Tower4::<K>::variable(-0.61, 1),
6215 ];
6216 let upstream = [
6217 vars[0].mul(&vars[1]).add(&vars[0].exp()),
6218 vars[1].mul(&vars[1]).add(&vars[0].scale(0.3)),
6219 vars[0].mul(&vars[0]).sub(&vars[1].scale(-0.2)),
6220 ];
6221 let mut lefts: [Tower4<K>; N] = std::array::from_fn(|term| upstream[term % upstream.len()]);
6222 lefts[4] = lefts[0];
6223 lefts[5] = lefts[0];
6224 let right = upstream[1];
6225 let addend = upstream[2];
6226 let addend_scales: [f64; N] = std::array::from_fn(|term| [-0.0, 1.0, -0.7, 0.25][term % 4]);
6227 let mut input_scales: [f64; N] =
6228 std::array::from_fn(|term| [0.0, -1.3, 0.45, 1.1][term % 4]);
6229 input_scales[0] = -1.1;
6230 input_scales[4] = 1.1;
6231 let stacks: [[f64; 5]; N] = std::array::from_fn(|term| {
6232 let t = term as f64 + 1.0;
6233 [0.17 * t, -0.11 * t, 0.07 * t, -0.03 * t, 0.013 * t]
6234 });
6235
6236 let expected = (0..N).fold(Tower4::<K>::constant(0.0), |sum, term| {
6237 let inner = if addend_scales[term] == 0.0 {
6238 lefts[term].mul(&right)
6239 } else if addend_scales[term] == 1.0 {
6240 JetScalar::multiply_add(&lefts[term], &right, &addend)
6241 } else {
6242 JetScalar::multiply_add(&lefts[term], &right, &addend.scale(addend_scales[term]))
6243 };
6244 sum.add(&JetScalar::affine_compose(
6245 &inner,
6246 input_scales[term],
6247 0.0,
6248 stacks[term],
6249 ))
6250 });
6251 let wrapped_lefts: [FixedRuntimeJet<Tower4<K>, K>; N] =
6252 std::array::from_fn(|term| FixedRuntimeJet::from_inner(lefts[term]));
6253 let wrapped_right = FixedRuntimeJet::from_inner(right);
6254 let wrapped_addend = FixedRuntimeJet::from_inner(addend);
6255 let mut wrapped_left_refs: [&FixedRuntimeJet<Tower4<K>, K>; N] =
6256 std::array::from_fn(|term| &wrapped_lefts[term]);
6257 wrapped_left_refs[4] = &wrapped_lefts[0];
6258 wrapped_left_refs[5] = &wrapped_lefts[0];
6259 let actual = FixedRuntimeJet::<Tower4<K>, K>::shared_multiply_add_affine_composed_sum(
6260 &wrapped_left_refs,
6261 &wrapped_right,
6262 &wrapped_addend,
6263 &addend_scales,
6264 &input_scales,
6265 &stacks,
6266 K,
6267 &(),
6268 )
6269 .into_inner();
6270
6271 let same = |label: &str, got: f64, want: f64| {
6272 let tolerance = 2.0e-13 * got.abs().max(want.abs()).max(1.0);
6273 assert!(
6274 (got - want).abs() <= tolerance,
6275 "{label}: got={got:+.17e}, want={want:+.17e}, tolerance={tolerance:.3e}"
6276 );
6277 };
6278 same("value", actual.v, expected.v);
6279 for a in 0..K {
6280 same("gradient", actual.g[a], expected.g[a]);
6281 for b in 0..K {
6282 same("Hessian", actual.h[a][b], expected.h[a][b]);
6283 for c in 0..K {
6284 same("third", actual.t3[a][b][c], expected.t3[a][b][c]);
6285 for d in 0..K {
6286 same("fourth", actual.t4[a][b][c][d], expected.t4[a][b][c][d]);
6287 }
6288 }
6289 }
6290 }
6291 }
6292
6293 fn row_expr<S: JetScalar<2>>(p: &[S; 2]) -> S {
6298 let g = p[0].mul(&p[1]).exp();
6299 let inner = g.add(&S::constant(2.0));
6300 let radic = p[0].mul(&p[0]).add(&S::constant(1.0)).sqrt();
6301 inner.mul(&radic).sub(&p[1].mul(&p[1]).scale(0.5))
6302 }
6303
6304 struct ExprProgram {
6306 p: [f64; 2],
6307 }
6308 impl RowProgram<2> for ExprProgram {
6309 fn n_rows(&self) -> usize {
6310 1
6311 }
6312 fn primaries(&self, row: usize) -> Result<[f64; 2], String> {
6313 if row >= self.n_rows() {
6314 return Err(format!("ExprProgram: row {row} out of range"));
6315 }
6316 Ok(self.p)
6317 }
6318 fn eval<S: JetScalar<2>>(&self, row: usize, p: &[S; 2]) -> Result<S, String> {
6319 if row >= self.n_rows() {
6320 return Err(format!("ExprProgram: row {row} out of range"));
6321 }
6322 Ok(row_expr(p))
6323 }
6324 }
6325
6326 const SEED: [f64; 2] = [0.37, -0.81];
6327 const U: [f64; 2] = [0.6, -0.2];
6328 const V: [f64; 2] = [-0.4, 1.1];
6329 const TOL: f64 = 1e-10;
6330
6331 fn close(a: f64, b: f64, label: &str) {
6332 let band = TOL + TOL * a.abs().max(b.abs());
6333 assert!(
6334 (a - b).abs() <= band,
6335 "{label}: {a:+.15e} vs {b:+.15e} (band {band:.3e})"
6336 );
6337 }
6338
6339 fn tower() -> Tower4<2> {
6340 *program_full_tower(&ExprProgram { p: SEED }, 0).expect("tower")
6341 }
6342
6343 #[test]
6345 fn order2_matches_tower_value_grad_hessian() {
6346 let t = tower();
6347 let vars: [Order2<2>; 2] = std::array::from_fn(|a| Order2::variable(SEED[a], a));
6348 let s = row_expr(&vars);
6349 close(s.value(), t.v, "value");
6350 for a in 0..2 {
6351 close(s.0.g[a], t.g[a], &format!("grad[{a}]"));
6352 for b in 0..2 {
6353 close(s.h()[a][b], t.h[a][b], &format!("hess[{a}][{b}]"));
6354 }
6355 }
6356 }
6357
6358 #[test]
6359 fn mapped_order2_accumulator_matches_dense_overlapping_atoms() {
6360 const K: usize = 4;
6361 let p = [0.2_f64, 0.7, -0.4, 0.3];
6362 let dense_vars: [Order2<K>; K] =
6363 std::array::from_fn(|axis| Order2::variable(p[axis], axis));
6364 let dense_q0 = dense_vars[3].mul(&dense_vars[1]).add(&dense_vars[3].exp());
6365 let dense_q1 = dense_vars[1].mul(&dense_vars[2]).sub(&dense_vars[2]);
6366 let dense = dense_q0.ln().add(&dense_q1.exp());
6367
6368 let local_q0_vars: [Order2<2>; 2] =
6369 std::array::from_fn(|axis| Order2::variable(p[[3, 1][axis]], axis));
6370 let local_q0 = local_q0_vars[0]
6371 .mul(&local_q0_vars[1])
6372 .add(&local_q0_vars[0].exp());
6373 let local_q1_vars: [Order2<2>; 2] =
6374 std::array::from_fn(|axis| Order2::variable(p[[1, 2][axis]], axis));
6375 let local_q1 = local_q1_vars[0]
6376 .mul(&local_q1_vars[1])
6377 .sub(&local_q1_vars[1]);
6378
6379 let q0 = local_q0.value();
6380 let q1_exp = local_q1.value().exp();
6381 let mut lowered = MappedOrder2Accumulator::<K>::zero();
6382 lowered.add_composed(
6383 &local_q0,
6384 [3, 1],
6385 [q0.ln(), q0.recip(), -1.0 / (q0 * q0)],
6386 false,
6387 [false, false],
6388 [false, false, false],
6389 );
6390 lowered.add_composed(
6391 &local_q1,
6392 [1, 2],
6393 [q1_exp, q1_exp, q1_exp],
6394 true,
6395 [true, false],
6396 [true, false, false],
6397 );
6398 let (value, gradient, hessian) = lowered.into_channels();
6399
6400 close(value, dense.value(), "mapped value");
6401 for i in 0..K {
6402 close(gradient[i], dense.g()[i], &format!("mapped gradient[{i}]"));
6403 for j in 0..K {
6404 close(
6405 hessian[i][j],
6406 dense.h()[i][j],
6407 &format!("mapped Hessian[{i},{j}]"),
6408 );
6409 }
6410 }
6411 }
6412
6413 #[test]
6414 #[should_panic(expected = "mapped atom axes must be injective")]
6415 fn mapped_order2_accumulator_rejects_duplicate_axes() {
6416 let vars: [Order2<2>; 2] = std::array::from_fn(|axis| Order2::variable(0.2, axis));
6417 let atom = vars[0].add(&vars[1]);
6418 let mut lowered = MappedOrder2Accumulator::<2>::zero();
6419 lowered.add_composed(
6420 &atom,
6421 [1, 1],
6422 [0.4, 1.0, 0.0],
6423 false,
6424 [false, false],
6425 [false, false, false],
6426 );
6427 }
6428
6429 #[test]
6430 #[should_panic(expected = "mapped atom axis must be within")]
6431 fn mapped_order2_accumulator_rejects_out_of_range_axes() {
6432 let atom = Order2::<1>::variable(0.2, 0);
6433 let mut lowered = MappedOrder2Accumulator::<2>::zero();
6434 lowered.add_composed(&atom, [2], [0.2, 1.0, 0.0], false, [false], [false]);
6435 }
6436
6437 #[test]
6438 fn dynamic_order2_accumulator_matches_dense_composed_sum() {
6439 const K: usize = 4;
6440
6441 struct Term {
6442 first: f64,
6443 second: f64,
6444 gradient: [f64; K],
6445 hessian: [[f64; K]; K],
6446 }
6447
6448 impl DynamicOrder2Term for Term {
6449 fn outer_first(&self) -> f64 {
6450 self.first
6451 }
6452
6453 fn outer_second(&self) -> f64 {
6454 self.second
6455 }
6456
6457 fn inner_gradient(&self, axis: usize) -> f64 {
6458 self.gradient[axis]
6459 }
6460
6461 fn inner_hessian(&self, row: usize, column: usize) -> f64 {
6462 self.hessian[row][column]
6463 }
6464 }
6465
6466 let p = [0.7, -0.3, 0.2, 0.8];
6467 let vars: [Order2<K>; K] = std::array::from_fn(|axis| Order2::variable(p[axis], axis));
6468 let first_atom = vars[0]
6469 .mul(&vars[1])
6470 .add(&vars[2].exp())
6471 .add(&Order2::constant(1.5));
6472 let second_atom = vars[1].mul(&vars[3]).sub(&vars[0]);
6473 let first_value = first_atom.value();
6474 let second_exp = second_atom.value().exp();
6475 let first_stack = [
6476 first_value.ln(),
6477 first_value.recip(),
6478 -1.0 / (first_value * first_value),
6479 0.0,
6480 0.0,
6481 ];
6482 let second_stack = [second_exp, second_exp, second_exp, second_exp, second_exp];
6483 let dense = first_atom
6484 .compose_unary(first_stack)
6485 .add(&second_atom.compose_unary(second_stack));
6486 let terms = [
6487 Term {
6488 first: first_stack[1],
6489 second: first_stack[2],
6490 gradient: *first_atom.g(),
6491 hessian: *first_atom.h(),
6492 },
6493 Term {
6494 first: second_stack[1],
6495 second: second_stack[2],
6496 gradient: *second_atom.g(),
6497 hessian: *second_atom.h(),
6498 },
6499 ];
6500 let (value, gradient, hessian) = DynamicOrder2Accumulator::from_composed_sum(
6501 K,
6502 first_stack[0] + second_stack[0],
6503 &terms,
6504 )
6505 .into_channels();
6506
6507 close(value, dense.value(), "dynamic value");
6508 for row in 0..K {
6509 close(
6510 gradient[row],
6511 dense.g()[row],
6512 &format!("dynamic gradient[{row}]"),
6513 );
6514 for column in 0..K {
6515 close(
6516 hessian[row * K + column],
6517 dense.h()[row][column],
6518 &format!("dynamic Hessian[{row},{column}]"),
6519 );
6520 }
6521 }
6522 }
6523
6524 #[derive(Clone, Copy, Debug)]
6525 struct FullTwoPattern;
6526
6527 impl HessianPattern<2, 3> for FullTwoPattern {
6528 const PAIRS: [(usize, usize); 3] = [(0, 0), (0, 1), (1, 1)];
6529 const PAIR_BITS: [[u128; 2]; 2] = hessian_pair_bits(Self::PAIRS);
6530 }
6531
6532 #[test]
6535 fn patterned_order2_matches_dense_order2() {
6536 type Sparse = PatternedOrder2<FullTwoPattern, 2, 3>;
6537 let dense_vars: [Order2<2>; 2] = std::array::from_fn(|a| Order2::variable(SEED[a], a));
6538 let sparse_vars: [Sparse; 2] = std::array::from_fn(|a| Sparse::variable(SEED[a], a));
6539 let dense = row_expr(&dense_vars);
6540 let sparse = row_expr(&sparse_vars);
6541 close(sparse.value(), dense.value(), "patterned value");
6542 for i in 0..2 {
6543 close(sparse.g()[i], dense.g()[i], &format!("patterned grad[{i}]"));
6544 for j in 0..2 {
6545 close(
6546 sparse.h()[i][j],
6547 dense.h()[i][j],
6548 &format!("patterned hess[{i}][{j}]"),
6549 );
6550 }
6551 }
6552 }
6553
6554 #[test]
6558 fn compose_unary_with_scalar_seam_bit_identical() {
6559 fn rand_unit(state: &mut u64) -> f64 {
6560 let mut x = *state;
6561 x ^= x << 13;
6562 x ^= x >> 7;
6563 x ^= x << 17;
6564 *state = x;
6565 2.0 * ((x >> 11) as f64 / ((1u64 << 53) as f64)) - 1.0
6566 }
6567 fn stack(u: f64) -> [f64; 5] {
6569 [
6570 u.sin(),
6571 u.cos(),
6572 (2.0 * u).sin(),
6573 (0.5 * u).cos(),
6574 u * u - 0.3,
6575 ]
6576 }
6577 fn run<const K: usize>(state: &mut u64, n: usize) -> usize {
6578 for _ in 0..n {
6579 let base = rand_unit(state);
6582 let mut s = Order2::<K>::variable(base, 0);
6583 for a in 1..K {
6584 s = crate::nested_dual::JetField::mul(
6585 &s,
6586 &Order2::<K>::variable(rand_unit(state), a),
6587 );
6588 }
6589 let with = s.compose_unary_with(stack);
6590 let explicit = s.compose_unary(stack(s.value()));
6591 assert_eq!(with.value().to_bits(), explicit.value().to_bits(), "value");
6592 for a in 0..K {
6593 assert_eq!(with.g()[a].to_bits(), explicit.g()[a].to_bits(), "g[{a}]");
6594 for b in 0..K {
6595 assert_eq!(
6596 with.h()[a][b].to_bits(),
6597 explicit.h()[a][b].to_bits(),
6598 "h[{a}][{b}]"
6599 );
6600 }
6601 }
6602 }
6603 n
6604 }
6605 let mut st = 0x9e37_79b9_7f4a_7c15u64;
6606 let total = run::<2>(&mut st, 1100)
6607 + run::<3>(&mut st, 1100)
6608 + run::<4>(&mut st, 1100)
6609 + run::<9>(&mut st, 1100);
6610 assert_eq!(total, 4400);
6611 }
6612
6613 #[test]
6616 fn one_seed_matches_tower_third_contracted() {
6617 let t = tower();
6618 let truth = t.third_contracted(&U);
6619 let vars: [OneSeed<2>; 2] =
6620 std::array::from_fn(|a| OneSeed::seed_direction(SEED[a], a, U[a]));
6621 let s = row_expr(&vars);
6622 close(s.value(), t.v, "value");
6624 for a in 0..2 {
6625 for b in 0..2 {
6626 close(s.base.h()[a][b], t.h[a][b], &format!("base hess[{a}][{b}]"));
6627 }
6628 }
6629 let third = s.contracted_third();
6630 for a in 0..2 {
6631 for b in 0..2 {
6632 close(third[a][b], truth[a][b], &format!("third[{a}][{b}]"));
6633 }
6634 }
6635 }
6636
6637 #[test]
6642 fn fused_one_seed_channels_match_unfused_definition_932() {
6643 const K: usize = 8;
6644
6645 fn random_scalar(state: &mut u64) -> f64 {
6646 *state ^= *state << 13;
6647 *state ^= *state >> 7;
6648 *state ^= *state << 17;
6649 ((*state >> 11) as f64 / ((1_u64 << 53) as f64)) * 2.0 - 1.0
6650 }
6651
6652 fn random_order2<const N: usize>(state: &mut u64) -> Order2<N> {
6653 let mut tower = crate::jet_tower::Tower2::<N>::zero();
6654 tower.v = random_scalar(state);
6655 for axis in 0..N {
6656 tower.g[axis] = random_scalar(state);
6657 }
6658 for row in 0..N {
6659 for column in row..N {
6660 let channel = random_scalar(state);
6661 tower.h[row][column] = channel;
6662 tower.h[column][row] = channel;
6663 }
6664 }
6665 Order2(tower)
6666 }
6667
6668 fn assert_channels_close<const N: usize>(
6669 label: &str,
6670 actual: &OneSeed<N>,
6671 expected: &OneSeed<N>,
6672 ) {
6673 for (part_label, actual_part, expected_part, require_exact_symmetry) in [
6674 ("base", &actual.base.0, &expected.base.0, false),
6675 ("eps", &actual.eps.0, &expected.eps.0, true),
6676 ] {
6677 let check = |channel: &str, got: f64, want: f64| {
6678 let tolerance = 2.0e-14 * got.abs().max(want.abs()).max(1.0);
6679 assert!(
6680 (got - want).abs() <= tolerance,
6681 "{label} {part_label} {channel}: got={got:+.17e} want={want:+.17e}"
6682 );
6683 };
6684 check("value", actual_part.v, expected_part.v);
6685 for row in 0..N {
6686 check(
6687 &format!("gradient[{row}]"),
6688 actual_part.g[row],
6689 expected_part.g[row],
6690 );
6691 for column in 0..N {
6692 check(
6693 &format!("hessian[{row},{column}]"),
6694 actual_part.h[row][column],
6695 expected_part.h[row][column],
6696 );
6697 if require_exact_symmetry {
6698 assert_eq!(
6699 actual_part.h[row][column].to_bits(),
6700 actual_part.h[column][row].to_bits(),
6701 "{label} {part_label} Hessian symmetry at [{row},{column}]"
6702 );
6703 }
6704 }
6705 }
6706 }
6707 }
6708
6709 let mut state = 0x9320_1eed_5eed_cafe_u64;
6710 for sample in 0..256 {
6711 let left = OneSeed {
6712 base: random_order2::<K>(&mut state),
6713 eps: random_order2::<K>(&mut state),
6714 };
6715 let right = OneSeed {
6716 base: random_order2::<K>(&mut state),
6717 eps: random_order2::<K>(&mut state),
6718 };
6719
6720 let fused_product = left.mul(&right);
6721 let unfused_product = OneSeed {
6722 base: left.base.mul(&right.base),
6723 eps: left.base.mul(&right.eps).add(&left.eps.mul(&right.base)),
6724 };
6725 assert_channels_close(
6726 &format!("sample {sample} product"),
6727 &fused_product,
6728 &unfused_product,
6729 );
6730
6731 let derivatives: [f64; 5] = std::array::from_fn(|_| random_scalar(&mut state));
6732 let fused_composition = left.compose_unary(derivatives);
6733 let unfused_composition = OneSeed {
6734 base: left.base.compose_unary(derivatives),
6735 eps: left
6736 .base
6737 .compose_unary([
6738 derivatives[1],
6739 derivatives[2],
6740 derivatives[3],
6741 derivatives[4],
6742 derivatives[4],
6743 ])
6744 .mul(&left.eps),
6745 };
6746 assert_channels_close(
6747 &format!("sample {sample} composition"),
6748 &fused_composition,
6749 &unfused_composition,
6750 );
6751 }
6752 }
6753
6754 #[test]
6758 fn two_seed_matches_tower_fourth_contracted() {
6759 let t = tower();
6760 let truth4 = t.fourth_contracted(&U, &V);
6761 let truth3_u = t.third_contracted(&U);
6762 let truth3_v = t.third_contracted(&V);
6763 let vars: [TwoSeed<2>; 2] = std::array::from_fn(|a| TwoSeed::seed(SEED[a], a, U[a], V[a]));
6764 let s = row_expr(&vars);
6765 close(s.value(), t.v, "value");
6766 for a in 0..2 {
6767 close(s.base.0.g[a], t.g[a], &format!("grad[{a}]"));
6768 for b in 0..2 {
6769 close(s.base.h()[a][b], t.h[a][b], &format!("base hess[{a}][{b}]"));
6770 close(
6771 s.eps.h()[a][b],
6772 truth3_u[a][b],
6773 &format!("eps third_u[{a}][{b}]"),
6774 );
6775 close(
6776 s.del.h()[a][b],
6777 truth3_v[a][b],
6778 &format!("del third_v[{a}][{b}]"),
6779 );
6780 }
6781 }
6782 let fourth = s.contracted_fourth();
6783 for a in 0..2 {
6784 for b in 0..2 {
6785 close(fourth[a][b], truth4[a][b], &format!("fourth[{a}][{b}]"));
6786 }
6787 }
6788 }
6789
6790 #[test]
6794 fn generic_program_seam_matches_tower_for_every_channel() {
6795 let t = tower();
6796 let o2: [Order2<2>; 2] = std::array::from_fn(|a| Order2::variable(SEED[a], a));
6798 let so2 = row_expr(&o2);
6799 close(so2.value(), t.v, "seam order2 value");
6800 let os: [OneSeed<2>; 2] =
6802 std::array::from_fn(|a| OneSeed::seed_direction(SEED[a], a, U[a]));
6803 let third = row_expr(&os).contracted_third();
6804 let truth3 = t.third_contracted(&U);
6805 for a in 0..2 {
6806 for b in 0..2 {
6807 close(third[a][b], truth3[a][b], &format!("seam third[{a}][{b}]"));
6808 }
6809 }
6810 let ts: [TwoSeed<2>; 2] = std::array::from_fn(|a| TwoSeed::seed(SEED[a], a, U[a], V[a]));
6812 let fourth = row_expr(&ts).contracted_fourth();
6813 let truth4 = t.fourth_contracted(&U, &V);
6814 for a in 0..2 {
6815 for b in 0..2 {
6816 close(
6817 fourth[a][b],
6818 truth4[a][b],
6819 &format!("seam fourth[{a}][{b}]"),
6820 );
6821 }
6822 }
6823 }
6824
6825 #[test]
6832 fn tower4_as_jetscalar_matches_program_tower_all_channels() {
6833 let t = tower();
6834 let vars: [Tower4<2>; 2] = std::array::from_fn(|a| Tower4::variable(SEED[a], a));
6835 let s = row_expr(&vars);
6836 close(s.v, t.v, "tower-jetscalar value");
6837 for a in 0..2 {
6838 close(s.g[a], t.g[a], &format!("tower-jetscalar grad[{a}]"));
6839 for b in 0..2 {
6840 close(
6841 s.h[a][b],
6842 t.h[a][b],
6843 &format!("tower-jetscalar hess[{a}][{b}]"),
6844 );
6845 for c in 0..2 {
6846 close(
6847 s.t3[a][b][c],
6848 t.t3[a][b][c],
6849 &format!("tower-jetscalar t3[{a}][{b}][{c}]"),
6850 );
6851 for d in 0..2 {
6852 close(
6853 s.t4[a][b][c][d],
6854 t.t4[a][b][c][d],
6855 &format!("tower-jetscalar t4[{a}][{b}][{c}][{d}]"),
6856 );
6857 }
6858 }
6859 }
6860 }
6861 }
6862
6863 #[test]
6867 fn runtime_directional_jets_match_fixed_packed_algebra_932() {
6868 fn expression<'arena, S: RuntimeJetScalar<'arena>>(vars: &[S]) -> S {
6869 let bilinear = vars[0].mul(&vars[1]);
6870 let curved = vars[2].scale(0.7).add(&vars[3].mul(&vars[3]).scale(-0.2));
6871 bilinear
6872 .add(&curved)
6873 .exp()
6874 .mul(&vars[4].compose_unary([0.4, -0.3, 0.2, -0.1, 0.05]))
6875 }
6876
6877 const K: usize = 5;
6878 let values = [0.2, -0.7, 0.4, 1.1, -0.3];
6879 let direction_u = [0.5, -0.2, 0.7, -0.4, 0.1];
6880 let direction_v = [-0.3, 0.8, 0.2, 0.6, -0.5];
6881 let close = |actual: f64, expected: f64| {
6882 let tolerance = 1.0e-13 * (1.0 + actual.abs().max(expected.abs()));
6883 assert!((actual - expected).abs() <= tolerance);
6884 };
6885
6886 let fixed_one: Vec<FixedRuntimeJet<OneSeed<K>, K>> = (0..K)
6887 .map(|axis| FixedRuntimeJet {
6888 inner: OneSeed::seed_direction(values[axis], axis, direction_u[axis]),
6889 })
6890 .collect();
6891 let arena_one = DynamicJetArena::new();
6892 let dynamic_one: Vec<DynamicOneSeed<'_>> = (0..K)
6893 .map(|axis| {
6894 DynamicOneSeed::seed_direction(values[axis], axis, direction_u[axis], K, &arena_one)
6895 })
6896 .collect();
6897 let fixed_third = expression(&fixed_one).into_inner().contracted_third();
6898 let dynamic_third = expression(&dynamic_one);
6899 for a in 0..K {
6900 for b in 0..K {
6901 assert_eq!(
6902 dynamic_third.contracted_third()[a * K + b].to_bits(),
6903 dynamic_third.contracted_third()[b * K + a].to_bits(),
6904 "arena third Hessian must be exactly symmetric at ({a},{b})"
6905 );
6906 close(
6907 dynamic_third.contracted_third()[a * K + b],
6908 fixed_third[a][b],
6909 );
6910 }
6911 }
6912
6913 let fixed_one_v: Vec<FixedRuntimeJet<OneSeed<K>, K>> = (0..K)
6914 .map(|axis| FixedRuntimeJet {
6915 inner: OneSeed::seed_direction(values[axis], axis, direction_v[axis]),
6916 })
6917 .collect();
6918 let fixed_third_v = expression(&fixed_one_v).into_inner().contracted_third();
6919 let batch_workspace = DynamicJetBatchWorkspace::new(2);
6920 let directions = [direction_u, direction_v];
6921 let batch_vars = batch_workspace.alloc_slice_fill_with(K, |axis| {
6922 DynamicOneSeedBatch::seed_directions(values[axis], axis, K, &batch_workspace, |lane| {
6923 directions[lane][axis]
6924 })
6925 });
6926 let dynamic_batch = expression(batch_vars);
6927 assert_eq!(dynamic_batch.lanes(), 2);
6928 for lane in 0..2 {
6929 let expected = if lane == 0 {
6930 &fixed_third
6931 } else {
6932 &fixed_third_v
6933 };
6934 for a in 0..K {
6935 for b in 0..K {
6936 close(
6937 dynamic_batch.contracted_third(lane)[a * K + b],
6938 expected[a][b],
6939 );
6940 }
6941 }
6942 }
6943
6944 let fixed_two: Vec<FixedRuntimeJet<TwoSeed<K>, K>> = (0..K)
6945 .map(|axis| FixedRuntimeJet {
6946 inner: TwoSeed::seed(values[axis], axis, direction_u[axis], direction_v[axis]),
6947 })
6948 .collect();
6949 let arena_two = DynamicJetArena::new();
6950 let dynamic_two: Vec<DynamicTwoSeed<'_>> = (0..K)
6951 .map(|axis| {
6952 DynamicTwoSeed::seed(
6953 values[axis],
6954 axis,
6955 direction_u[axis],
6956 direction_v[axis],
6957 K,
6958 &arena_two,
6959 )
6960 })
6961 .collect();
6962 let fixed_fourth = expression(&fixed_two).into_inner().contracted_fourth();
6963 let dynamic_fourth = expression(&dynamic_two);
6964 for a in 0..K {
6965 for b in 0..K {
6966 close(
6967 dynamic_fourth.contracted_fourth()[a * K + b],
6968 fixed_fourth[a][b],
6969 );
6970 }
6971 }
6972
6973 let fixed_two_swapped: Vec<FixedRuntimeJet<TwoSeed<K>, K>> = (0..K)
6974 .map(|axis| {
6975 FixedRuntimeJet::from_inner(TwoSeed::seed(
6976 values[axis],
6977 axis,
6978 direction_v[axis],
6979 direction_u[axis],
6980 ))
6981 })
6982 .collect();
6983 let fixed_fourth_swapped = expression(&fixed_two_swapped)
6984 .into_inner()
6985 .contracted_fourth();
6986 let pair_workspace = DynamicJetBatchWorkspace::new(2);
6987 let direction_pairs = [(direction_u, direction_v), (direction_v, direction_u)];
6988 let pair_vars = pair_workspace.alloc_slice_fill_with(K, |axis| {
6989 DynamicTwoSeedBatch::seed_direction_pairs(
6990 values[axis],
6991 axis,
6992 K,
6993 &pair_workspace,
6994 |lane| (direction_pairs[lane].0[axis], direction_pairs[lane].1[axis]),
6995 )
6996 });
6997 let dynamic_pair_batch = expression(pair_vars);
6998 assert_eq!(dynamic_pair_batch.lanes(), 2);
6999 for lane in 0..2 {
7000 let expected = if lane == 0 {
7001 &fixed_fourth
7002 } else {
7003 &fixed_fourth_swapped
7004 };
7005 for a in 0..K {
7006 for b in 0..K {
7007 close(
7008 dynamic_pair_batch.contracted_fourth(lane)[a * K + b],
7009 expected[a][b],
7010 );
7011 }
7012 }
7013 }
7014 }
7015
7016 #[test]
7017 fn dynamic_jet_arena_compacts_fragmented_high_water_932() {
7018 const WORDS_PER_ALLOCATION: usize = 1 << 17;
7019 const ALLOCATIONS: usize = 6;
7020
7021 let mut arena = DynamicJetArena::new();
7022 for lane in 0..ALLOCATIONS {
7023 let allocation = arena.alloc_slice_fill_with(WORDS_PER_ALLOCATION, |_| lane as u64);
7024 std::hint::black_box(allocation);
7025 }
7026 let fragmented_high_water = arena.allocated_bytes();
7027
7028 arena.reset();
7029 let compact_high_water = arena.allocated_bytes();
7030 assert!(
7031 compact_high_water >= fragmented_high_water,
7032 "compacted arena must retain the complete fragmented tape"
7033 );
7034
7035 for lane in 0..ALLOCATIONS {
7036 let allocation = arena.alloc_slice_fill_with(WORDS_PER_ALLOCATION, |_| lane as u64);
7037 std::hint::black_box(allocation);
7038 }
7039 assert_eq!(
7040 arena.allocated_bytes(),
7041 compact_high_water,
7042 "equal replay must fit in the compacted chunk"
7043 );
7044
7045 arena.reset();
7046 assert_eq!(
7047 arena.allocated_bytes(),
7048 compact_high_water,
7049 "stable reset must retain the compacted chunk"
7050 );
7051 }
7052}
7053
7054#[cfg(test)]
7055mod batch_tests {
7056 use super::{
7064 JetScalar, Lane, OneSeed, OneSeedBatch, OneSeedLane, Order2, Order2Batch, Order2Lane,
7065 TwoSeed, TwoSeedBatch, TwoSeedLane,
7066 };
7067 use crate::nested_dual::JetField;
7070
7071 trait RowAlg<const K: usize>: Copy {
7075 fn constant(c: f64) -> Self;
7076 fn add(&self, o: &Self) -> Self;
7077 fn sub(&self, o: &Self) -> Self;
7078 fn mul(&self, o: &Self) -> Self;
7079 fn scale(&self, s: f64) -> Self;
7080 fn exp(&self) -> Self;
7081 fn sqrt(&self) -> Self;
7082 fn recip(&self) -> Self;
7083 }
7084
7085 impl<const K: usize> RowAlg<K> for Order2<K> {
7086 fn constant(c: f64) -> Self {
7087 <Self as JetScalar<K>>::constant(c)
7088 }
7089 fn add(&self, o: &Self) -> Self {
7090 crate::nested_dual::JetField::add(self, o)
7091 }
7092 fn sub(&self, o: &Self) -> Self {
7093 crate::nested_dual::JetField::sub(self, o)
7094 }
7095 fn mul(&self, o: &Self) -> Self {
7096 crate::nested_dual::JetField::mul(self, o)
7097 }
7098 fn scale(&self, s: f64) -> Self {
7099 crate::nested_dual::JetField::scale(self, s)
7100 }
7101 fn exp(&self) -> Self {
7102 JetScalar::exp(self)
7103 }
7104 fn sqrt(&self) -> Self {
7105 JetScalar::sqrt(self)
7106 }
7107 fn recip(&self) -> Self {
7108 JetScalar::recip(self)
7109 }
7110 }
7111
7112 impl<L: Lane, const K: usize> RowAlg<K> for Order2Lane<L, K> {
7113 fn constant(c: f64) -> Self {
7114 Order2Lane::constant(L::splat(c))
7115 }
7116 fn add(&self, o: &Self) -> Self {
7117 Order2Lane::add(self, o)
7118 }
7119 fn sub(&self, o: &Self) -> Self {
7120 Order2Lane::sub(self, o)
7121 }
7122 fn mul(&self, o: &Self) -> Self {
7123 Order2Lane::mul(self, o)
7124 }
7125 fn scale(&self, s: f64) -> Self {
7126 Order2Lane::scale(self, s)
7127 }
7128 fn exp(&self) -> Self {
7129 Order2Lane::exp(self)
7130 }
7131 fn sqrt(&self) -> Self {
7132 Order2Lane::sqrt(self)
7133 }
7134 fn recip(&self) -> Self {
7135 Order2Lane::recip(self)
7136 }
7137 }
7138
7139 fn row_expr<const K: usize, A: RowAlg<K>>(p: &[A; K]) -> A {
7144 let mut s = A::constant(0.3);
7145 for a in 0..K {
7146 let b = (a + 1) % K;
7147 s = s.add(&p[a].mul(&p[b]).scale(0.1 + 0.05 * a as f64));
7148 }
7149 let e = s.exp();
7150 let r = s.mul(&s).add(&A::constant(1.0)).sqrt();
7151 let denom = e.add(&A::constant(2.0));
7152 e.mul(&r).sub(&s.scale(0.5)).mul(&denom.recip())
7153 }
7154
7155 fn rand_unit(state: &mut u64) -> f64 {
7157 let mut x = *state;
7158 x ^= x << 13;
7159 x ^= x >> 7;
7160 x ^= x << 17;
7161 *state = x;
7162 let u = (x >> 11) as f64 / ((1u64 << 53) as f64); 2.0 * u - 1.0
7164 }
7165
7166 fn check_k<const K: usize>(state: &mut u64, batches: usize) -> usize {
7169 let mut verified_rows = 0usize;
7170 for _ in 0..batches {
7171 let rows: [[f64; K]; 4] =
7173 std::array::from_fn(|_| std::array::from_fn(|_| rand_unit(state)));
7174
7175 let prod: [Order2<K>; 4] = std::array::from_fn(|r| {
7177 let p: [Order2<K>; K] = std::array::from_fn(|a| Order2::variable(rows[r][a], a));
7178 row_expr(&p)
7179 });
7180
7181 let scal: [Order2Lane<f64, K>; 4] = std::array::from_fn(|r| {
7183 let p: [Order2Lane<f64, K>; K] =
7184 std::array::from_fn(|a| Order2Lane::variable(rows[r][a], a));
7185 row_expr(&p)
7186 });
7187
7188 let pbatch: [Order2Batch<K>; K] = std::array::from_fn(|a| {
7190 let packed = wide::f64x4::new([rows[0][a], rows[1][a], rows[2][a], rows[3][a]]);
7191 Order2Batch::variable(packed, a)
7192 });
7193 let batch = row_expr(&pbatch);
7194
7195 for r in 0..4 {
7196 let g = prod[r].0;
7197 assert_eq!(scal[r].v.to_bits(), g.v.to_bits(), "K={K} scalar v");
7199 let lr = batch.lane(r).0;
7201 assert_eq!(lr.v.to_bits(), g.v.to_bits(), "K={K} batch lane {r} v");
7202 for a in 0..K {
7203 assert_eq!(
7204 scal[r].g[a].to_bits(),
7205 g.g[a].to_bits(),
7206 "K={K} scalar g[{a}]"
7207 );
7208 assert_eq!(
7209 lr.g[a].to_bits(),
7210 g.g[a].to_bits(),
7211 "K={K} batch lane {r} g[{a}]"
7212 );
7213 for b in 0..K {
7214 assert_eq!(
7215 scal[r].h[a][b].to_bits(),
7216 g.h[a][b].to_bits(),
7217 "K={K} scalar h[{a}][{b}]"
7218 );
7219 assert_eq!(
7220 lr.h[a][b].to_bits(),
7221 g.h[a][b].to_bits(),
7222 "K={K} batch lane {r} h[{a}][{b}]"
7223 );
7224 }
7225 }
7226 verified_rows += 1;
7227 }
7228 }
7229 verified_rows
7230 }
7231
7232 #[test]
7235 fn batch_lanes_bit_identical_to_scalar_per_row() {
7236 let mut state = 0x9E37_79B9_7F4A_7C15_u64;
7237 let mut verified = 0usize;
7238 verified += check_k::<2>(&mut state, 2000);
7239 verified += check_k::<3>(&mut state, 2000);
7240 verified += check_k::<4>(&mut state, 2000);
7241 verified += check_k::<9>(&mut state, 2000);
7242 assert_eq!(verified, 4 * 2000 * 4, "every batch row must be verified");
7244 }
7245
7246 impl<const K: usize> RowAlg<K> for OneSeed<K> {
7255 fn constant(c: f64) -> Self {
7256 <Self as JetScalar<K>>::constant(c)
7257 }
7258 fn add(&self, o: &Self) -> Self {
7259 crate::nested_dual::JetField::add(self, o)
7260 }
7261 fn sub(&self, o: &Self) -> Self {
7262 crate::nested_dual::JetField::sub(self, o)
7263 }
7264 fn mul(&self, o: &Self) -> Self {
7265 crate::nested_dual::JetField::mul(self, o)
7266 }
7267 fn scale(&self, s: f64) -> Self {
7268 crate::nested_dual::JetField::scale(self, s)
7269 }
7270 fn exp(&self) -> Self {
7271 JetScalar::exp(self)
7272 }
7273 fn sqrt(&self) -> Self {
7274 JetScalar::sqrt(self)
7275 }
7276 fn recip(&self) -> Self {
7277 JetScalar::recip(self)
7278 }
7279 }
7280
7281 impl<L: Lane, const K: usize> RowAlg<K> for OneSeedLane<L, K> {
7282 fn constant(c: f64) -> Self {
7283 OneSeedLane::constant(L::splat(c))
7284 }
7285 fn add(&self, o: &Self) -> Self {
7286 OneSeedLane::add(self, o)
7287 }
7288 fn sub(&self, o: &Self) -> Self {
7289 OneSeedLane::sub(self, o)
7290 }
7291 fn mul(&self, o: &Self) -> Self {
7292 OneSeedLane::mul(self, o)
7293 }
7294 fn scale(&self, s: f64) -> Self {
7295 OneSeedLane::scale(self, s)
7296 }
7297 fn exp(&self) -> Self {
7298 OneSeedLane::exp(self)
7299 }
7300 fn sqrt(&self) -> Self {
7301 OneSeedLane::sqrt(self)
7302 }
7303 fn recip(&self) -> Self {
7304 OneSeedLane::recip(self)
7305 }
7306 }
7307
7308 impl<const K: usize> RowAlg<K> for TwoSeed<K> {
7309 fn constant(c: f64) -> Self {
7310 <Self as JetScalar<K>>::constant(c)
7311 }
7312 fn add(&self, o: &Self) -> Self {
7313 crate::nested_dual::JetField::add(self, o)
7314 }
7315 fn sub(&self, o: &Self) -> Self {
7316 crate::nested_dual::JetField::sub(self, o)
7317 }
7318 fn mul(&self, o: &Self) -> Self {
7319 crate::nested_dual::JetField::mul(self, o)
7320 }
7321 fn scale(&self, s: f64) -> Self {
7322 crate::nested_dual::JetField::scale(self, s)
7323 }
7324 fn exp(&self) -> Self {
7325 JetScalar::exp(self)
7326 }
7327 fn sqrt(&self) -> Self {
7328 JetScalar::sqrt(self)
7329 }
7330 fn recip(&self) -> Self {
7331 JetScalar::recip(self)
7332 }
7333 }
7334
7335 impl<L: Lane, const K: usize> RowAlg<K> for TwoSeedLane<L, K> {
7336 fn constant(c: f64) -> Self {
7337 TwoSeedLane::constant(L::splat(c))
7338 }
7339 fn add(&self, o: &Self) -> Self {
7340 TwoSeedLane::add(self, o)
7341 }
7342 fn sub(&self, o: &Self) -> Self {
7343 TwoSeedLane::sub(self, o)
7344 }
7345 fn mul(&self, o: &Self) -> Self {
7346 TwoSeedLane::mul(self, o)
7347 }
7348 fn scale(&self, s: f64) -> Self {
7349 TwoSeedLane::scale(self, s)
7350 }
7351 fn exp(&self) -> Self {
7352 TwoSeedLane::exp(self)
7353 }
7354 fn sqrt(&self) -> Self {
7355 TwoSeedLane::sqrt(self)
7356 }
7357 fn recip(&self) -> Self {
7358 TwoSeedLane::recip(self)
7359 }
7360 }
7361
7362 fn check_oneseed<const K: usize>(state: &mut u64, batches: usize) -> usize {
7363 let mut rows_checked = 0;
7364 for _ in 0..batches {
7365 let rows: [[f64; K]; 4] =
7366 std::array::from_fn(|_| std::array::from_fn(|_| rand_unit(state)));
7367 let u: [[f64; K]; 4] =
7369 std::array::from_fn(|_| std::array::from_fn(|_| rand_unit(state)));
7370
7371 let prod: [OneSeed<K>; 4] = std::array::from_fn(|r| {
7373 let p: [OneSeed<K>; K] =
7374 std::array::from_fn(|a| OneSeed::seed_direction(rows[r][a], a, u[r][a]));
7375 row_expr(&p)
7376 });
7377
7378 let scal: [OneSeedLane<f64, K>; 4] = std::array::from_fn(|r| {
7380 let p: [OneSeedLane<f64, K>; K] =
7381 std::array::from_fn(|a| OneSeedLane::seed_direction(rows[r][a], a, u[r][a]));
7382 row_expr(&p)
7383 });
7384
7385 let pbatch: [OneSeedBatch<K>; K] = std::array::from_fn(|a| {
7387 let val = wide::f64x4::new([rows[0][a], rows[1][a], rows[2][a], rows[3][a]]);
7388 let uu = wide::f64x4::new([u[0][a], u[1][a], u[2][a], u[3][a]]);
7389 OneSeedBatch::seed_direction(val, a, uu)
7390 });
7391 let batch = row_expr(&pbatch);
7392
7393 for r in 0..4 {
7394 let want = prod[r].contracted_third();
7395 let got_scal = scal[r].contracted_third();
7396 let got_batch = batch.lane(r).contracted_third();
7397 assert_eq!(
7399 scal[r].base.v.to_bits(),
7400 prod[r].base.value().to_bits(),
7401 "OneSeed K={K} scalar value"
7402 );
7403 assert_eq!(
7404 batch.lane(r).base.value().to_bits(),
7405 prod[r].base.value().to_bits(),
7406 "OneSeed K={K} batch lane {r} value"
7407 );
7408 for a in 0..K {
7409 for b in 0..K {
7410 assert_eq!(
7411 got_scal[a][b].to_bits(),
7412 want[a][b].to_bits(),
7413 "OneSeed K={K} scalar third[{a}][{b}]"
7414 );
7415 assert_eq!(
7416 got_batch[a][b].to_bits(),
7417 want[a][b].to_bits(),
7418 "OneSeed K={K} batch lane {r} third[{a}][{b}]"
7419 );
7420 }
7421 }
7422 rows_checked += 1;
7423 }
7424 }
7425 rows_checked
7426 }
7427
7428 fn check_twoseed<const K: usize>(state: &mut u64, batches: usize) -> usize {
7429 let mut rows_checked = 0;
7430 for _ in 0..batches {
7431 let rows: [[f64; K]; 4] =
7432 std::array::from_fn(|_| std::array::from_fn(|_| rand_unit(state)));
7433 let u: [[f64; K]; 4] =
7434 std::array::from_fn(|_| std::array::from_fn(|_| rand_unit(state)));
7435 let v: [[f64; K]; 4] =
7436 std::array::from_fn(|_| std::array::from_fn(|_| rand_unit(state)));
7437
7438 let prod: [TwoSeed<K>; 4] = std::array::from_fn(|r| {
7439 let p: [TwoSeed<K>; K] =
7440 std::array::from_fn(|a| TwoSeed::seed(rows[r][a], a, u[r][a], v[r][a]));
7441 row_expr(&p)
7442 });
7443
7444 let scal: [TwoSeedLane<f64, K>; 4] = std::array::from_fn(|r| {
7445 let p: [TwoSeedLane<f64, K>; K] =
7446 std::array::from_fn(|a| TwoSeedLane::seed(rows[r][a], a, u[r][a], v[r][a]));
7447 row_expr(&p)
7448 });
7449
7450 let pbatch: [TwoSeedBatch<K>; K] = std::array::from_fn(|a| {
7451 let val = wide::f64x4::new([rows[0][a], rows[1][a], rows[2][a], rows[3][a]]);
7452 let uu = wide::f64x4::new([u[0][a], u[1][a], u[2][a], u[3][a]]);
7453 let vv = wide::f64x4::new([v[0][a], v[1][a], v[2][a], v[3][a]]);
7454 TwoSeedBatch::seed(val, a, uu, vv)
7455 });
7456 let batch = row_expr(&pbatch);
7457
7458 for r in 0..4 {
7459 let want = prod[r].contracted_fourth();
7460 let got_scal = scal[r].contracted_fourth();
7461 let got_batch = batch.lane(r).contracted_fourth();
7462 assert_eq!(
7463 scal[r].base.v.to_bits(),
7464 prod[r].base.value().to_bits(),
7465 "TwoSeed K={K} scalar value"
7466 );
7467 assert_eq!(
7468 batch.lane(r).base.value().to_bits(),
7469 prod[r].base.value().to_bits(),
7470 "TwoSeed K={K} batch lane {r} value"
7471 );
7472 for a in 0..K {
7473 for b in 0..K {
7474 assert_eq!(
7475 got_scal[a][b].to_bits(),
7476 want[a][b].to_bits(),
7477 "TwoSeed K={K} scalar fourth[{a}][{b}]"
7478 );
7479 assert_eq!(
7480 got_batch[a][b].to_bits(),
7481 want[a][b].to_bits(),
7482 "TwoSeed K={K} batch lane {r} fourth[{a}][{b}]"
7483 );
7484 }
7485 }
7486 rows_checked += 1;
7487 }
7488 }
7489 rows_checked
7490 }
7491
7492 #[test]
7496 fn oneseed_lanes_contracted_third_bit_identical() {
7497 let mut state = 0x1234_5678_9ABC_DEF0_u64;
7498 let batches = 2000;
7499 let rows_checked = check_oneseed::<2>(&mut state, batches)
7500 + check_oneseed::<3>(&mut state, batches)
7501 + check_oneseed::<4>(&mut state, batches)
7502 + check_oneseed::<9>(&mut state, batches);
7503 assert_eq!(rows_checked, 4 * batches * 4);
7506 }
7507
7508 #[test]
7512 fn twoseed_lanes_contracted_fourth_bit_identical() {
7513 let mut state = 0x0FED_CBA9_8765_4321_u64;
7514 let batches = 2000;
7515 let rows_checked = check_twoseed::<2>(&mut state, batches)
7516 + check_twoseed::<3>(&mut state, batches)
7517 + check_twoseed::<4>(&mut state, batches)
7518 + check_twoseed::<9>(&mut state, batches);
7519 assert_eq!(rows_checked, 4 * batches * 4);
7522 }
7523}
7524
7525#[cfg(test)]
7526mod unit_tests {
7527 use super::{
7528 DynamicJetArena, DynamicOrder2, JetScalar, OneSeed, Order1, Order2, RuntimeJetScalar,
7529 filtered_implicit_solve_scalar,
7530 };
7531 use crate::nested_dual::{Dual2, JetField};
7532
7533 fn family_program<const K: usize, S: JetScalar<K>>(x: &S, y: &S, theta: &S) -> S {
7537 let xy = x.mul(y);
7538 let exponential = theta.mul(&xy).exp();
7539 let theta_squared_x_squared = theta.mul(theta).mul(&x.mul(x)).scale(0.375);
7540 let theta_y_cubed = theta.mul(&y.mul(y).mul(y)).scale(-0.2);
7541 exponential
7542 .add(&theta_squared_x_squared)
7543 .add(&theta_y_cubed)
7544 }
7545
7546 fn analytic_family_first<const K: usize, S: JetScalar<K>>(x: &S, y: &S, theta: &S) -> S {
7547 let xy = x.mul(y);
7548 let exponential = theta.mul(&xy).exp();
7549 xy.mul(&exponential)
7550 .add(&theta.mul(&x.mul(x)).scale(0.75))
7551 .add(&y.mul(y).mul(y).scale(-0.2))
7552 }
7553
7554 fn analytic_family_second<const K: usize, S: JetScalar<K>>(x: &S, y: &S, theta: &S) -> S {
7555 let xy = x.mul(y);
7556 let exponential = theta.mul(&xy).exp();
7557 xy.mul(&xy).mul(&exponential).add(&x.mul(x).scale(0.75))
7558 }
7559
7560 fn assert_channel_close(actual: f64, expected: f64, channel: &str) {
7561 let tolerance = 256.0 * f64::EPSILON * (1.0 + actual.abs().max(expected.abs()));
7562 assert!(
7563 (actual - expected).abs() <= tolerance,
7564 "{channel}: actual={actual:.17e}, expected={expected:.17e}, tolerance={tolerance:.3e}"
7565 );
7566 }
7567
7568 fn assert_order2_channels<const K: usize>(
7569 actual: &Order2<K>,
7570 expected: &Order2<K>,
7571 prefix: &str,
7572 ) {
7573 assert_channel_close(actual.value(), expected.value(), &format!("{prefix}.value"));
7574 for a in 0..K {
7575 assert_channel_close(actual.g()[a], expected.g()[a], &format!("{prefix}.g[{a}]"));
7576 for b in 0..K {
7577 assert_channel_close(
7578 actual.h()[a][b],
7579 expected.h()[a][b],
7580 &format!("{prefix}.h[{a}][{b}]"),
7581 );
7582 }
7583 }
7584 }
7585
7586 #[test]
7594 fn runtime_shaped_value_primitives_are_exact_and_skip_constant_composition_932() {
7595 use std::time::Instant;
7596
7597 const K: usize = 48;
7598 let arena = DynamicJetArena::new();
7599 let variable = DynamicOrder2::variable(0.75, 7, K, &arena);
7600
7601 let constant = variable.constant_like(1.25);
7602 assert_eq!(constant.value().to_bits(), 1.25_f64.to_bits());
7603 assert_eq!(constant.dimension(), K);
7604 assert!(constant.g().iter().all(|&channel| channel == 0.0));
7605 assert!(constant.h().iter().all(|&channel| channel == 0.0));
7606
7607 let replaced = variable.with_value(-2.5);
7608 assert_eq!(replaced.value().to_bits(), (-2.5_f64).to_bits());
7609 assert_eq!(replaced.g(), variable.g());
7610 assert_eq!(replaced.h(), variable.h());
7611
7612 fn best_ns(mut evaluate: impl FnMut(f64) -> f64, iterations: usize) -> f64 {
7613 let mut best = f64::INFINITY;
7614 for _ in 0..5 {
7615 let mut checksum = 0.0_f64;
7616 let started = Instant::now();
7617 for _ in 0..iterations {
7618 checksum += evaluate(0.75 + checksum * 1e-18);
7619 }
7620 assert!(checksum.is_finite());
7621 best = best.min(started.elapsed().as_secs_f64());
7622 }
7623 best * 1e9 / iterations as f64
7624 }
7625
7626 let iterations = if cfg!(debug_assertions) { 200 } else { 20_000 };
7627 let mut direct_arena = DynamicJetArena::new();
7628 let direct_ns = best_ns(
7629 |value| {
7630 direct_arena.reset();
7631 let variable = DynamicOrder2::variable(value, 7, K, &direct_arena);
7632 let constant = variable.constant_like(1.25);
7633 constant.value() + constant.g()[K - 1] + constant.h()[K * K - 1]
7634 },
7635 iterations,
7636 );
7637 let mut composed_arena = DynamicJetArena::new();
7638 let composed_ns = best_ns(
7639 |value| {
7640 composed_arena.reset();
7641 let variable = DynamicOrder2::variable(value, 7, K, &composed_arena);
7642 let constant = variable.compose_unary([1.25, 0.0, 0.0, 0.0, 0.0]);
7643 constant.value() + constant.g()[K - 1] + constant.h()[K * K - 1]
7644 },
7645 iterations,
7646 );
7647 eprintln!(
7648 "RUNTIME-CONSTANT-932 dimension={K} direct={direct_ns:.2} ns \
7649 composed={composed_ns:.2} ns composed_over_direct={:.6}",
7650 composed_ns / direct_ns,
7651 );
7652 }
7653
7654 #[test]
7657 fn dual2_order2_extracts_exact_family_value_gradient_hessian_channels() {
7658 const K: usize = 2;
7659 let x0 = 0.7;
7660 let y0 = -0.45;
7661 let theta0 = 0.6;
7662 let x = <Dual2<Order2<K>> as JetScalar<K>>::variable(x0, 0);
7663 let y = <Dual2<Order2<K>> as JetScalar<K>>::variable(y0, 1);
7664 let theta = Dual2 {
7665 v: Order2::constant(theta0),
7666 g: Order2::constant(1.0),
7667 h: Order2::constant(0.0),
7668 };
7669
7670 let actual = family_program(&x, &y, &theta);
7671 let reference_x = Order2::variable(x0, 0);
7672 let reference_y = Order2::variable(y0, 1);
7673 let reference_theta = Order2::constant(theta0);
7674 let expected_first = analytic_family_first(&reference_x, &reference_y, &reference_theta);
7675 let expected_second = analytic_family_second(&reference_x, &reference_y, &reference_theta);
7676
7677 assert_order2_channels(&actual.g, &expected_first, "family_first");
7678 assert_order2_channels(&actual.h, &expected_second, "family_second");
7679 }
7680
7681 #[test]
7684 fn dual2_oneseed_extracts_exact_family_hessian_drift() {
7685 const K: usize = 2;
7686 let x0 = 0.7;
7687 let y0 = -0.45;
7688 let theta0 = 0.6;
7689 let direction = [0.3, -0.8];
7690
7691 let mut x = <Dual2<OneSeed<K>> as JetScalar<K>>::variable(x0, 0);
7692 let mut y = <Dual2<OneSeed<K>> as JetScalar<K>>::variable(y0, 1);
7693 x.v.eps = Order2::constant(direction[0]);
7694 y.v.eps = Order2::constant(direction[1]);
7695 let theta = Dual2 {
7696 v: OneSeed::constant(theta0),
7697 g: OneSeed::constant(1.0),
7698 h: OneSeed::constant(0.0),
7699 };
7700
7701 let actual = family_program(&x, &y, &theta);
7702 let reference_x = OneSeed::seed_direction(x0, 0, direction[0]);
7703 let reference_y = OneSeed::seed_direction(y0, 1, direction[1]);
7704 let reference_theta = OneSeed::constant(theta0);
7705 let expected = analytic_family_first(&reference_x, &reference_y, &reference_theta);
7706
7707 assert_order2_channels(&actual.g.eps, &expected.eps, "family_first_drift");
7708 }
7709
7710 #[test]
7714 fn order2_constant_has_zero_derivatives() {
7715 let s = Order2::<3>::constant(7.5);
7716 assert_eq!(s.value(), 7.5);
7717 for a in 0..3 {
7718 assert_eq!(s.g()[a], 0.0, "grad[{a}] should be zero");
7719 for b in 0..3 {
7720 assert_eq!(s.h()[a][b], 0.0, "hess[{a}][{b}] should be zero");
7721 }
7722 }
7723 }
7724
7725 #[test]
7727 fn order2_variable_has_unit_gradient_in_seeded_slot() {
7728 let x = -2.5_f64;
7729 let s = Order2::<4>::variable(x, 2);
7730 assert_eq!(s.value(), x);
7731 for a in 0..4 {
7732 let expected_g = if a == 2 { 1.0 } else { 0.0 };
7733 assert_eq!(s.g()[a], expected_g, "grad[{a}]");
7734 for b in 0..4 {
7735 assert_eq!(s.h()[a][b], 0.0, "hess[{a}][{b}] should be zero");
7736 }
7737 }
7738 }
7739
7740 #[test]
7743 fn order2_add_sub_roundtrip() {
7744 let p = Order2::<2>::variable(3.0, 0);
7745 let q = Order2::<2>::variable(2.0, 1);
7746 let pq = crate::nested_dual::JetField::add(&p, &q);
7747 assert_eq!(pq.value(), 5.0, "add value");
7749 let back = crate::nested_dual::JetField::sub(&pq, &q);
7750 for a in 0..2 {
7752 assert_eq!(back.g()[a], p.g()[a], "grad[{a}] roundtrip");
7753 }
7754 }
7755
7756 #[test]
7759 fn order2_mul_satisfies_leibniz_rule() {
7760 let pv = 3.0_f64;
7761 let qv = -2.0_f64;
7762 let p = Order2::<2>::variable(pv, 0);
7763 let q = Order2::<2>::variable(qv, 1);
7764 let pq = crate::nested_dual::JetField::mul(&p, &q);
7765 assert_eq!(pq.value(), pv * qv, "value = p·q");
7766 assert_eq!(pq.g()[0], qv, "∂(p·q)/∂p = q");
7767 assert_eq!(pq.g()[1], pv, "∂(p·q)/∂q = p");
7768 assert_eq!(pq.h()[0][1], 1.0, "∂²(p·q)/∂p∂q = 1");
7769 assert_eq!(pq.h()[1][0], 1.0, "∂²(p·q)/∂q∂p = 1 (symmetric)");
7770 assert_eq!(pq.h()[0][0], 0.0, "∂²(p·q)/∂p² = 0");
7771 assert_eq!(pq.h()[1][1], 0.0, "∂²(p·q)/∂q² = 0");
7772 }
7773
7774 #[test]
7776 fn order2_scale_multiplies_all_channels() {
7777 let p = Order2::<2>::variable(4.0, 0);
7778 let s = 2.5_f64;
7779 let ps = crate::nested_dual::JetField::scale(&p, s);
7780 assert_eq!(ps.value(), 4.0 * s);
7781 assert_eq!(ps.g()[0], 1.0 * s);
7782 assert_eq!(ps.g()[1], 0.0);
7783 }
7784
7785 #[test]
7788 fn order2_exp_derivative_stack_correct() {
7789 let p0 = 1.0_f64;
7790 let p = Order2::<1>::variable(p0, 0);
7791 let ep = JetScalar::exp(&p);
7792 let e = p0.exp();
7793 assert!((ep.value() - e).abs() < 1e-15, "exp value");
7794 assert!((ep.g()[0] - e).abs() < 1e-15, "d/dp exp(p) = exp(p)");
7795 assert!((ep.h()[0][0] - e).abs() < 1e-15, "d²/dp² exp(p) = exp(p)");
7796 }
7797
7798 #[test]
7800 fn order2_ln_derivative_stack_correct() {
7801 let p0 = 2.0_f64;
7802 let p = Order2::<1>::variable(p0, 0);
7803 let lnp = JetScalar::ln(&p);
7804 assert!((lnp.value() - p0.ln()).abs() < 1e-15, "ln value");
7805 assert!((lnp.g()[0] - 1.0 / p0).abs() < 1e-15, "d/dp ln(p) = 1/p");
7806 assert!(
7807 (lnp.h()[0][0] - (-1.0 / (p0 * p0))).abs() < 1e-15,
7808 "d²/dp² ln(p) = -1/p²"
7809 );
7810 }
7811
7812 #[test]
7813 fn dynamic_order2_ln_uses_runtime_scalar_derivative_stack() {
7814 let p0 = 2.0_f64;
7815 let arena = DynamicJetArena::new();
7816 let p = DynamicOrder2::variable(p0, 0, 1, &arena);
7817 let lnp = RuntimeJetScalar::ln(&p);
7818 assert!((lnp.value() - p0.ln()).abs() < 1e-15, "ln value");
7819 assert!((lnp.g()[0] - 1.0 / p0).abs() < 1e-15, "d/dp ln(p) = 1/p");
7820 assert!(
7821 (lnp.h_at(0, 0) - (-1.0 / (p0 * p0))).abs() < 1e-15,
7822 "d²/dp² ln(p) = -1/p²"
7823 );
7824 }
7825
7826 #[test]
7828 fn order2_exp_ln_roundtrip_at_value() {
7829 let p0 = 0.8_f64;
7830 let p = Order2::<1>::variable(p0, 0);
7831 let roundtrip = JetScalar::ln(&JetScalar::exp(&p));
7832 assert!((roundtrip.value() - p0).abs() < 1e-14, "ln(exp(p)) ≈ p");
7833 }
7834
7835 #[test]
7839 fn order1_constant_has_zero_gradient() {
7840 let s = Order1::<3>::constant(-5.0);
7841 assert_eq!(s.value(), -5.0);
7842 for a in 0..3 {
7843 assert_eq!(s.g()[a], 0.0, "g[{a}] should be zero");
7844 }
7845 }
7846
7847 #[test]
7849 fn order1_variable_has_unit_gradient_in_seeded_slot() {
7850 let s = Order1::<3>::variable(2.0, 1);
7851 assert_eq!(s.value(), 2.0);
7852 assert_eq!(s.g()[0], 0.0);
7853 assert_eq!(s.g()[1], 1.0);
7854 assert_eq!(s.g()[2], 0.0);
7855 }
7856
7857 #[test]
7859 fn order1_mul_satisfies_product_rule() {
7860 let pv = 3.0_f64;
7861 let qv = -2.0_f64;
7862 let p = Order1::<2>::variable(pv, 0);
7863 let q = Order1::<2>::variable(qv, 1);
7864 let pq = crate::nested_dual::JetField::mul(&p, &q);
7865 assert_eq!(pq.value(), pv * qv);
7866 assert_eq!(pq.g()[0], qv, "∂(p·q)/∂p = q");
7867 assert_eq!(pq.g()[1], pv, "∂(p·q)/∂q = p");
7868 }
7869
7870 #[test]
7872 fn order1_exp_has_correct_value_and_gradient() {
7873 let p0 = 0.5_f64;
7874 let p = Order1::<2>::variable(p0, 0);
7875 let ep = JetScalar::exp(&p);
7876 let e = p0.exp();
7877 assert!((ep.value() - e).abs() < 1e-15, "exp value");
7878 assert!((ep.g()[0] - e).abs() < 1e-15, "d/dp exp(p)");
7879 assert_eq!(ep.g()[1], 0.0, "irrelevant gradient slot is zero");
7880 }
7881
7882 #[test]
7884 fn order1_and_order2_agree_on_value_and_gradient() {
7885 let p0 = 1.3_f64;
7886 let q0 = -0.7_f64;
7887 let p1 = Order1::<2>::variable(p0, 0);
7889 let q1 = Order1::<2>::variable(q0, 1);
7890 let expr1 = JetScalar::exp(&crate::nested_dual::JetField::add(
7891 &crate::nested_dual::JetField::mul(&p1, &q1),
7892 &p1,
7893 ));
7894
7895 let p2 = Order2::<2>::variable(p0, 0);
7896 let q2 = Order2::<2>::variable(q0, 1);
7897 let expr2 = JetScalar::exp(&crate::nested_dual::JetField::add(
7898 &crate::nested_dual::JetField::mul(&p2, &q2),
7899 &p2,
7900 ));
7901
7902 assert!(
7903 (expr1.value() - expr2.value()).abs() < 1e-14,
7904 "value mismatch"
7905 );
7906 for a in 0..2 {
7907 assert!(
7908 (expr1.g()[a] - expr2.g()[a]).abs() < 1e-14,
7909 "gradient[{a}] mismatch"
7910 );
7911 }
7912 }
7913
7914 #[test]
7919 fn filtered_implicit_solve_linear_constraint_gives_exact_jet() {
7920 let theta0 = 3.0_f64;
7921 let theta = Order2::<1>::variable(theta0, 0);
7922 let a = filtered_implicit_solve_scalar::<1, Order2<1>>(theta0, 1.0, 2, |a_jet| {
7924 crate::nested_dual::JetField::sub(a_jet, &theta)
7925 });
7926 assert!((a.value() - theta0).abs() < 1e-14, "value = theta0");
7927 assert!((a.g()[0] - 1.0).abs() < 1e-14, "gradient = 1");
7929 assert!(a.h()[0][0].abs() < 1e-14, "hessian = 0");
7931 }
7932
7933 #[test]
7936 fn filtered_implicit_solve_quadratic_constraint_matches_analytic_derivatives() {
7937 let theta0 = 4.0_f64;
7938 let a0 = theta0.sqrt();
7939 let inv_fa = 1.0 / (2.0 * a0);
7940 let theta = Order2::<1>::variable(theta0, 0);
7941 let a = filtered_implicit_solve_scalar::<1, Order2<1>>(a0, inv_fa, 2, |a_jet| {
7943 let aa = crate::nested_dual::JetField::mul(a_jet, a_jet);
7944 crate::nested_dual::JetField::sub(&aa, &theta)
7945 });
7946 let tol = 1e-12;
7947 assert!((a.value() - a0).abs() < tol, "value = sqrt(theta0)");
7948 let expected_g = 0.5 / a0;
7949 assert!(
7950 (a.g()[0] - expected_g).abs() < tol,
7951 "da/dtheta = 1/(2*sqrt)"
7952 );
7953 let expected_h = -0.25 / (theta0 * a0);
7954 assert!(
7955 (a.h()[0][0] - expected_h).abs() < tol,
7956 "d2a/dtheta2 = -1/(4*theta^1.5)"
7957 );
7958 }
7959}