1use crate::attributes::AttrStorageManager;
2use crate::cmap::{BuilderError, CMap2, CMap3, DartIdType, VertexIdType};
3use crate::geometry::{CoordsFloat, Vector2, Vector3, Vertex2, Vertex3};
4
5#[derive(Clone)]
20pub struct GridDescriptor<const D: usize, T: CoordsFloat> {
21 pub(crate) origin: [T; D],
22 pub(crate) n_cells: Option<[usize; D]>,
23 pub(crate) len_per_cell: Option<[T; D]>,
24 pub(crate) lens: Option<[T; D]>,
25 pub(crate) split_cells: bool,
26}
27
28impl<const D: usize, T: CoordsFloat> Default for GridDescriptor<D, T> {
29 fn default() -> Self {
30 Self {
31 origin: [T::zero(); D],
32 n_cells: None,
33 len_per_cell: None,
34 lens: None,
35 split_cells: false,
36 }
37 }
38}
39
40impl<const D: usize, T: CoordsFloat> GridDescriptor<D, T> {
41 #[must_use = "unused builder object"]
43 pub fn n_cells(mut self, n_cells: [usize; D]) -> Self {
44 self.n_cells = Some(n_cells);
45 self
46 }
47
48 #[must_use = "unused builder object"]
50 pub fn len_per_cell(mut self, len_per_cell: [T; D]) -> Self {
51 self.len_per_cell = Some(len_per_cell);
52 self
53 }
54
55 #[must_use = "unused builder object"]
57 pub fn lens(mut self, lens: [T; D]) -> Self {
58 self.lens = Some(lens);
59 self
60 }
61
62 #[must_use = "unused builder object"]
64 pub fn origin(mut self, origin: [T; D]) -> Self {
65 self.origin = origin;
66 self
67 }
68
69 #[must_use = "unused builder object"]
71 pub fn split_cells(mut self, split: bool) -> Self {
72 self.split_cells = split;
73 self
74 }
75}
76
77macro_rules! check_parameters {
80 ($id: ident, $msg: expr) => {
81 if $id.is_sign_negative() | $id.is_zero() {
82 return Err(BuilderError::InvalidGridParameters($msg));
83 }
84 };
85}
86
87impl<T: CoordsFloat> GridDescriptor<2, T> {
88 #[allow(clippy::type_complexity)]
90 pub(crate) fn parse_2d(self) -> Result<(Vertex2<T>, [usize; 2], [T; 2]), BuilderError> {
91 match (self.n_cells, self.len_per_cell, self.lens) {
92 (Some([nx, ny]), Some([lpx, lpy]), lens) => {
94 if lens.is_some() {
95 eprintln!(
96 "W: All three grid parameters were specified, total lengths will be ignored"
97 );
98 }
99 #[rustfmt::skip]
100 check_parameters!(lpx, "length per x cell is null or negative");
101 #[rustfmt::skip]
102 check_parameters!(lpy, "length per y cell is null or negative");
103 Ok((
104 Vertex2(self.origin[0], self.origin[1]),
105 [nx, ny],
106 [lpx, lpy],
107 ))
108 }
109 (Some([nx, ny]), None, Some([lx, ly])) => {
111 #[rustfmt::skip]
112 check_parameters!(lx, "grid length along x is null or negative");
113 #[rustfmt::skip]
114 check_parameters!(ly, "grid length along y is null or negative");
115 Ok((
116 Vertex2(self.origin[0], self.origin[1]),
117 [nx, ny],
118 [lx / T::from(nx).unwrap(), ly / T::from(ny).unwrap()],
119 ))
120 }
121 (None, Some([lpx, lpy]), Some([lx, ly])) => {
123 #[rustfmt::skip]
124 check_parameters!(lpx, "length per x cell is null or negative");
125 #[rustfmt::skip]
126 check_parameters!(lpy, "length per y cell is null or negative");
127 #[rustfmt::skip]
128 check_parameters!(lx, "grid length along x is null or negative");
129 #[rustfmt::skip]
130 check_parameters!(ly, "grid length along y is null or negative");
131 Ok((
132 Vertex2(self.origin[0], self.origin[1]),
133 [
134 (lx / lpx).ceil().to_usize().unwrap(),
135 (ly / lpy).ceil().to_usize().unwrap(),
136 ],
137 [lpx, lpy],
138 ))
139 }
140 (_, _, _) => Err(BuilderError::MissingGridParameters),
141 }
142 }
143}
144
145impl<T: CoordsFloat> GridDescriptor<3, T> {
146 #[allow(clippy::type_complexity)]
148 pub(crate) fn parse_3d(self) -> Result<(Vertex3<T>, [usize; 3], [T; 3]), BuilderError> {
149 match (self.n_cells, self.len_per_cell, self.lens) {
150 (Some([nx, ny, nz]), Some([lpx, lpy, lpz]), lens) => {
152 if lens.is_some() {
153 eprintln!(
154 "W: All three grid parameters were specified, total lengths will be ignored"
155 );
156 }
157 #[rustfmt::skip]
158 check_parameters!(lpx, "length per x cell is null or negative");
159 #[rustfmt::skip]
160 check_parameters!(lpy, "length per y cell is null or negative");
161 #[rustfmt::skip]
162 check_parameters!(lpz, "length per z cell is null or negative");
163 Ok((
164 Vertex3(self.origin[0], self.origin[1], self.origin[2]),
165 [nx, ny, nz],
166 [lpx, lpy, lpz],
167 ))
168 }
169 (Some([nx, ny, nz]), None, Some([lx, ly, lz])) => {
171 #[rustfmt::skip]
172 check_parameters!(lx, "grid length along x is null or negative");
173 #[rustfmt::skip]
174 check_parameters!(ly, "grid length along y is null or negative");
175 #[rustfmt::skip]
176 check_parameters!(lz, "grid length along z is null or negative");
177 Ok((
178 Vertex3(self.origin[0], self.origin[1], self.origin[2]),
179 [nx, ny, nz],
180 [
181 lx / T::from(nx).unwrap(),
182 ly / T::from(ny).unwrap(),
183 lz / T::from(nz).unwrap(),
184 ],
185 ))
186 }
187 (None, Some([lpx, lpy, lpz]), Some([lx, ly, lz])) => {
189 #[rustfmt::skip]
190 check_parameters!(lpx, "length per x cell is null or negative");
191 #[rustfmt::skip]
192 check_parameters!(lpy, "length per y cell is null or negative");
193 #[rustfmt::skip]
194 check_parameters!(lpz, "length per z cell is null or negative");
195 #[rustfmt::skip]
196 check_parameters!(lx, "grid length along x is null or negative");
197 #[rustfmt::skip]
198 check_parameters!(ly, "grid length along y is null or negative");
199 #[rustfmt::skip]
200 check_parameters!(lz, "grid length along z is null or negative");
201 Ok((
202 Vertex3(self.origin[0], self.origin[1], self.origin[2]),
203 [
204 (lx / lpx).ceil().to_usize().unwrap(),
205 (ly / lpy).ceil().to_usize().unwrap(),
206 (lz / lpz).ceil().to_usize().unwrap(),
207 ],
208 [lpx, lpy, lpz],
209 ))
210 }
211 (_, _, _) => Err(BuilderError::MissingGridParameters),
212 }
213 }
214}
215
216#[allow(clippy::too_many_lines)]
222pub fn build_2d_grid<T: CoordsFloat>(
223 origin: Vertex2<T>,
224 [n_square_x, n_square_y]: [usize; 2],
225 [len_per_x, len_per_y]: [T; 2],
226 manager: AttrStorageManager,
227) -> CMap2<T> {
228 let map: CMap2<T> = CMap2::new_with_undefined_attributes(4 * n_square_x * n_square_y, manager);
229
230 (1..=(4 * n_square_x * n_square_y) as DartIdType)
232 .zip(generate_square_beta_values(n_square_x, n_square_y))
233 .for_each(|(dart, images)| {
234 map.set_betas(dart, images);
235 });
236
237 (0..n_square_y)
241 .flat_map(|y_idx| (0..n_square_x).map(move |x_idx| (y_idx, x_idx)))
243 .for_each(|(y_idx, x_idx)| {
244 let vertex_id = map.vertex_id((1 + x_idx * 4 + y_idx * 4 * n_square_x) as DartIdType);
245 map.force_write_vertex(
246 vertex_id,
247 origin
248 + Vector2(
249 T::from(x_idx).unwrap() * len_per_x,
250 T::from(y_idx).unwrap() * len_per_y,
251 ),
252 );
253 });
254
255 (0..n_square_x).for_each(|x_idx| {
257 let y_idx = n_square_y - 1;
258 let vertex_id = map.vertex_id((4 + x_idx * 4 + y_idx * 4 * n_square_x) as DartIdType);
259 map.force_write_vertex(
260 vertex_id,
261 origin
262 + Vector2(
263 T::from(x_idx).unwrap() * len_per_x,
264 T::from(y_idx + 1).unwrap() * len_per_y,
265 ),
266 );
267 });
268
269 (0..n_square_y).for_each(|y_idx| {
271 let x_idx = n_square_x - 1;
272 let vertex_id = map.vertex_id((2 + x_idx * 4 + y_idx * 4 * n_square_x) as DartIdType);
273 map.force_write_vertex(
274 vertex_id,
275 origin
276 + Vector2(
277 T::from(x_idx + 1).unwrap() * len_per_x,
278 T::from(y_idx).unwrap() * len_per_y,
279 ),
280 );
281 });
282
283 {
285 let (x_idx, y_idx) = (n_square_x - 1, n_square_y - 1);
286 let vertex_id = map.vertex_id((3 + x_idx * 4 + y_idx * 4 * n_square_x) as DartIdType); map.force_write_vertex(
288 vertex_id,
289 origin
290 + Vector2(
291 T::from(x_idx + 1).unwrap() * len_per_x,
292 T::from(y_idx + 1).unwrap() * len_per_y,
293 ),
294 );
295 }
296
297 debug_assert_eq!(map.iter_faces().count(), n_square_x * n_square_y);
301
302 map
303}
304
305#[allow(clippy::inline_always)]
306#[rustfmt::skip]
307#[inline(always)]
308fn generate_square_beta_values(n_x: usize, n_y: usize) -> impl Iterator<Item = [DartIdType; 3]> {
309 (0..n_y).flat_map(move |iy| {
312 (0..n_x).flat_map(move |ix| {
313 let d1 = (1 + 4 * ix + n_x * 4 * iy) as DartIdType;
314 let (d2, d3, d4) = (d1 + 1, d1 + 2, d1 + 3);
315 [
317 [ d4, d2, if iy == 0 { 0 } else { d3 - 4 * n_x as DartIdType } ],
318 [ d1, d3, if ix == n_x-1 { 0 } else { d2 + 6 } ],
319 [ d2, d4, if iy == n_y-1 { 0 } else { d1 + 4 * n_x as DartIdType } ],
320 [ d3, d1, if ix == 0 { 0 } else { d4 - 6 } ],
321 ]
322 .into_iter()
323 })
324 })
325}
326
327#[allow(clippy::too_many_lines)]
329pub fn build_2d_splitgrid<T: CoordsFloat>(
330 origin: Vertex2<T>,
331 [n_square_x, n_square_y]: [usize; 2],
332 [len_per_x, len_per_y]: [T; 2],
333 manager: AttrStorageManager,
334) -> CMap2<T> {
335 let map: CMap2<T> = CMap2::new_with_undefined_attributes(6 * n_square_x * n_square_y, manager);
336
337 (1..=(6 * n_square_x * n_square_y) as DartIdType)
339 .zip(generate_tris_beta_values(n_square_x, n_square_y))
340 .for_each(|(dart, images)| {
341 map.set_betas(dart, images);
342 });
343
344 (0..n_square_y)
348 .flat_map(|y_idx| (0..n_square_x).map(move |x_idx| (y_idx, x_idx)))
350 .for_each(|(y_idx, x_idx)| {
351 let vertex_id = map.vertex_id((1 + x_idx * 6 + y_idx * 6 * n_square_x) as DartIdType);
352 map.force_write_vertex(
353 vertex_id,
354 origin
355 + Vector2(
356 T::from(x_idx).unwrap() * len_per_x,
357 T::from(y_idx).unwrap() * len_per_y,
358 ),
359 );
360 });
361
362 (0..n_square_x).for_each(|x_idx| {
364 let y_idx = n_square_y - 1;
365 let vertex_id = map.vertex_id((4 + x_idx * 6 + y_idx * 6 * n_square_x) as DartIdType);
366 map.force_write_vertex(
367 vertex_id,
368 origin
369 + Vector2(
370 T::from(x_idx).unwrap() * len_per_x,
371 T::from(y_idx + 1).unwrap() * len_per_y,
372 ),
373 );
374 });
375
376 (0..n_square_y).for_each(|y_idx| {
378 let x_idx = n_square_x - 1;
379 let vertex_id = map.vertex_id((2 + x_idx * 6 + y_idx * 6 * n_square_x) as DartIdType);
380 map.force_write_vertex(
381 vertex_id,
382 origin
383 + Vector2(
384 T::from(x_idx + 1).unwrap() * len_per_x,
385 T::from(y_idx).unwrap() * len_per_y,
386 ),
387 );
388 });
389
390 {
392 let (x_idx, y_idx) = (n_square_x - 1, n_square_y - 1);
393 let vertex_id = map.vertex_id((6 + x_idx * 6 + y_idx * 6 * n_square_x) as DartIdType); map.force_write_vertex(
395 vertex_id,
396 origin
397 + Vector2(
398 T::from(x_idx + 1).unwrap() * len_per_x,
399 T::from(y_idx + 1).unwrap() * len_per_y,
400 ),
401 );
402 }
403
404 debug_assert_eq!(map.iter_faces().count(), 2 * n_square_x * n_square_y);
408
409 map
410}
411
412#[allow(clippy::inline_always)]
413#[rustfmt::skip]
414#[inline(always)]
415fn generate_tris_beta_values(n_x: usize, n_y: usize) -> impl Iterator<Item = [DartIdType; 3]> {
416 (0..n_y).flat_map(move |iy| {
419 (0..n_x).flat_map(move |ix| {
420 let d1 = (1 + 6 * ix + n_x * 6 * iy) as DartIdType;
421 let (d2, d3, d4, d5, d6) = (d1 + 1, d1 + 2, d1 + 3, d1 + 4, d1 + 5);
422 [
424 [ d3, d2, if iy == 0 { 0 } else { d6 - 6 * n_x as DartIdType } ],
425 [ d1, d3, d4 ],
426 [ d2, d1, if ix == 0 { 0 } else { d5 - 6 } ],
427 [ d6, d5, d2 ],
428 [ d4, d6, if ix == n_x-1 { 0 } else { d3 + 6 } ],
429 [ d5, d4, if iy == n_y-1 { 0 } else { d1 + 6 * n_x as DartIdType } ],
430 ]
431 .into_iter()
432 })
433 })
434}
435
436#[allow(clippy::too_many_lines)]
440pub fn build_3d_grid<T: CoordsFloat>(
441 origin: Vertex3<T>,
442 n_cells_per_axis: [usize; 3],
443 lengths: [T; 3],
444 manager: AttrStorageManager,
445) -> CMap3<T> {
446 let [n_square_x, n_square_y, n_square_z] = n_cells_per_axis;
447 let n_darts = 24 * n_square_x * n_square_y * n_square_z;
448
449 let map: CMap3<T> = CMap3::new_with_undefined_attributes(n_darts, manager);
450
451 (1..=n_darts as DartIdType)
453 .zip(generate_hex_beta_values(n_cells_per_axis))
454 .for_each(|(dart, images)| {
455 map.set_betas(dart, images);
456 });
457
458 (1..=n_darts as DartIdType)
460 .filter(|d| *d as VertexIdType == map.vertex_id(*d))
461 .for_each(|d| {
462 let v = origin + generate_hex_offset(d, n_cells_per_axis, lengths);
463 map.force_write_vertex(d as VertexIdType, v);
464 });
465
466 debug_assert_eq!(
470 map.iter_volumes().count(),
471 n_square_x * n_square_y * n_square_z
472 );
473
474 map
475}
476
477#[allow(clippy::inline_always)]
495#[rustfmt::skip]
496#[inline(always)]
497fn generate_hex_beta_values(
498 [n_x, n_y, n_z]: [usize; 3],
499) -> impl Iterator<Item = [DartIdType; 4]> {
500 (0..n_z).flat_map(move |iz| {
503 (0..n_y).flat_map(move |iy| {
504 (0..n_x).flat_map(move |ix| {
505 let d1 = (1 + 24 * ix + n_x * 24 * iy + n_x * n_y * 24 * iz) as DartIdType;
506 let ( d2 , d3 , d4 , d5 , d6 , d7 , d8 ,
507 d9 , d10, d11, d12, d13, d14, d15, d16,
508 d17, d18, d19, d20, d21, d22, d23, d24,
509 ) = ( d1 + 1 , d1 + 2 , d1 + 3 , d1 + 4 , d1 + 5 , d1 + 6 , d1 + 7 ,
510 d1 + 8 , d1 + 9 , d1 + 10, d1 + 11, d1 + 12, d1 + 13, d1 + 14, d1 + 15,
511 d1 + 16, d1 + 17, d1 + 18, d1 + 19, d1 + 20, d1 + 21, d1 + 22, d1 + 23,
512 );
513 let noffset_x = 24;
514 let noffset_y = noffset_x * n_x as DartIdType;
515 let noffset_z = noffset_y * n_y as DartIdType;
516
517 [
519 [d4 , d2 , d5 , if iy == 0 { 0 } else { d21 - noffset_y }],
521 [d1 , d3 , d9 , if iy == 0 { 0 } else { d24 - noffset_y }],
522 [d2 , d4 , d13, if iy == 0 { 0 } else { d23 - noffset_y }],
523 [d3 , d1 , d17, if iy == 0 { 0 } else { d22 - noffset_y }],
524 [d8 , d6 , d1 , if iz == 0 { 0 } else { d13 - noffset_z }],
526 [d5 , d7 , d20, if iz == 0 { 0 } else { d16 - noffset_z }],
527 [d6 , d8 , d21, if iz == 0 { 0 } else { d15 - noffset_z }],
528 [d7 , d5 , d10, if iz == 0 { 0 } else { d14 - noffset_z }],
529 [d12, d10, d2 , if ix == n_x - 1 { 0 } else { d17 + noffset_x }],
531 [d9 , d11, d8 , if ix == n_x - 1 { 0 } else { d20 + noffset_x }],
532 [d10, d12, d24, if ix == n_x - 1 { 0 } else { d19 + noffset_x }],
533 [d11, d9 , d14, if ix == n_x - 1 { 0 } else { d18 + noffset_x }],
534 [d16, d14, d3 , if iz == n_z - 1 { 0 } else { d5 + noffset_z }],
536 [d13, d15, d12, if iz == n_z - 1 { 0 } else { d8 + noffset_z }],
537 [d14, d16, d23, if iz == n_z - 1 { 0 } else { d7 + noffset_z }],
538 [d15, d13, d18, if iz == n_z - 1 { 0 } else { d6 + noffset_z }],
539 [d20, d18, d4 , if ix == 0 { 0 } else { d9 - noffset_x }],
541 [d17, d19, d16, if ix == 0 { 0 } else { d12 - noffset_x }],
542 [d18, d20, d22, if ix == 0 { 0 } else { d11 - noffset_x }],
543 [d19, d17, d6 , if ix == 0 { 0 } else { d10 - noffset_x }],
544 [d24, d22, d7 , if iy == n_y - 1 { 0 } else { d1 + noffset_y }],
546 [d21, d23, d19, if iy == n_y - 1 { 0 } else { d4 + noffset_y }],
547 [d22, d24, d15, if iy == n_y - 1 { 0 } else { d3 + noffset_y }],
548 [d23, d21, d11, if iy == n_y - 1 { 0 } else { d2 + noffset_y }],
549 ]
550 .into_iter()
551 })
552 })
553 })
554}
555
556#[allow(
558 clippy::inline_always,
559 clippy::match_same_arms,
560 clippy::too_many_lines,
561 clippy::many_single_char_names
562)]
563#[inline(always)]
564fn generate_hex_offset<T: CoordsFloat>(
565 dart: DartIdType,
566 [n_x, n_y, _]: [usize; 3],
567 [lx, ly, lz]: [T; 3],
568) -> Vector3<T> {
569 let d = dart as usize;
571 let dm = d % 24;
572 let dmm = d % (24 * n_x);
573 let dmmm = d % (24 * n_x * n_y);
574 let p = dm;
575 let x = (dmm - dm) / 24;
576 let y = (dmmm - dmm) / (24 * n_x);
577 let z = dmmm / (24 * n_x * n_y);
578 match p {
579 1 => Vector3(
582 T::from(x).unwrap() * lx,
583 T::from(y).unwrap() * ly,
584 T::from(z).unwrap() * lz,
585 ),
586 2 => Vector3(
587 T::from(x + 1).unwrap() * lx,
588 T::from(y).unwrap() * ly,
589 T::from(z).unwrap() * lz,
590 ),
591 3 => Vector3(
592 T::from(x + 1).unwrap() * lx,
593 T::from(y).unwrap() * ly,
594 T::from(z + 1).unwrap() * lz,
595 ),
596 4 => Vector3(
597 T::from(x).unwrap() * lx,
598 T::from(y).unwrap() * ly,
599 T::from(z + 1).unwrap() * lz,
600 ),
601 5 => Vector3(
603 T::from(x + 1).unwrap() * lx,
604 T::from(y).unwrap() * ly,
605 T::from(z).unwrap() * lz,
606 ),
607 6 => Vector3(
608 T::from(x).unwrap() * lx,
609 T::from(y).unwrap() * ly,
610 T::from(z).unwrap() * lz,
611 ),
612 7 => Vector3(
613 T::from(x).unwrap() * lx,
614 T::from(y + 1).unwrap() * ly,
615 T::from(z).unwrap() * lz,
616 ),
617 8 => Vector3(
618 T::from(x).unwrap() * lx,
619 T::from(y + 1).unwrap() * ly,
620 T::from(z).unwrap() * lz,
621 ),
622 9 => Vector3(
624 T::from(x + 1).unwrap() * lx,
625 T::from(y).unwrap() * ly,
626 T::from(z + 1).unwrap() * lz,
627 ),
628 10 => Vector3(
629 T::from(x + 1).unwrap() * lx,
630 T::from(y).unwrap() * ly,
631 T::from(z).unwrap() * lz,
632 ),
633 11 => Vector3(
634 T::from(x + 1).unwrap() * lx,
635 T::from(y + 1).unwrap() * ly,
636 T::from(z).unwrap() * lz,
637 ),
638 12 => Vector3(
639 T::from(x + 1).unwrap() * lx,
640 T::from(y + 1).unwrap() * ly,
641 T::from(z + 1).unwrap() * lz,
642 ),
643 13 => Vector3(
645 T::from(x).unwrap() * lx,
646 T::from(y).unwrap() * ly,
647 T::from(z + 1).unwrap() * lz,
648 ),
649 14 => Vector3(
650 T::from(x + 1).unwrap() * lx,
651 T::from(y).unwrap() * ly,
652 T::from(z + 1).unwrap() * lz,
653 ),
654 15 => Vector3(
655 T::from(x).unwrap() * lx,
656 T::from(y + 1).unwrap() * ly,
657 T::from(z + 1).unwrap() * lz,
658 ),
659 16 => Vector3(
660 T::from(x + 1).unwrap() * lx,
661 T::from(y + 1).unwrap() * ly,
662 T::from(z + 1).unwrap() * lz,
663 ),
664 17 => Vector3(
666 T::from(x).unwrap() * lx,
667 T::from(y).unwrap() * ly,
668 T::from(z).unwrap() * lz,
669 ),
670 18 => Vector3(
671 T::from(x).unwrap() * lx,
672 T::from(y).unwrap() * ly,
673 T::from(z + 1).unwrap() * lz,
674 ),
675 19 => Vector3(
676 T::from(x).unwrap() * lx,
677 T::from(y + 1).unwrap() * ly,
678 T::from(z + 1).unwrap() * lz,
679 ),
680 20 => Vector3(
681 T::from(x).unwrap() * lx,
682 T::from(y + 1).unwrap() * ly,
683 T::from(z).unwrap() * lz,
684 ),
685 21 => Vector3(
687 T::from(x + 1).unwrap() * lx,
688 T::from(y + 1).unwrap() * ly,
689 T::from(z).unwrap() * lz,
690 ),
691 22 => Vector3(
692 T::from(x).unwrap() * lx,
693 T::from(y + 1).unwrap() * ly,
694 T::from(z).unwrap() * lz,
695 ),
696 23 => Vector3(
697 T::from(x).unwrap() * lx,
698 T::from(y + 1).unwrap() * ly,
699 T::from(z + 1).unwrap() * lz,
700 ),
701 0 => Vector3(
702 T::from(x + 1).unwrap() * lx,
703 T::from(y + 1).unwrap() * ly,
704 T::from(z + 1).unwrap() * lz,
705 ),
706 _ => unreachable!(),
707 }
708}