1use crate::block::{BlockCode, Jacobian, SoftDecode, container_traits, expect_len, max_star};
51use comnum_core::{Result, ensure, ensure_shape};
52
53fn parity(x: u32) -> u8 {
54 (x.count_ones() & 1) as u8
55}
56
57#[derive(Clone, Debug, PartialEq, Eq)]
62pub struct Trellis {
63 pub(crate) constraint: u32,
65 pub(crate) n_out: usize,
67 pub(crate) next: Vec<[usize; 2]>,
69 pub(crate) out: Vec<[u32; 2]>,
71 pub(crate) tail: Vec<u8>,
73}
74
75impl Trellis {
76 pub fn n_states(&self) -> usize {
78 self.next.len()
79 }
80
81 pub fn constraint(&self) -> u32 {
83 self.constraint
84 }
85
86 pub fn n_out(&self) -> usize {
88 self.n_out
89 }
90
91 pub fn next(&self) -> &[[usize; 2]] {
93 &self.next
94 }
95
96 pub fn out(&self) -> &[[u32; 2]] {
99 &self.out
100 }
101
102 pub fn tail(&self) -> &[u8] {
105 &self.tail
106 }
107
108 pub fn feedforward(constraint: u32, generators: &[u32]) -> Result<Trellis> {
114 let m = Self::memory(constraint)?;
115 ensure(!generators.is_empty(), || "a convolutional code needs at least one generator".into())?;
116 let states = 1usize << m;
117 let mut next = Vec::with_capacity(states);
118 let mut out = Vec::with_capacity(states);
119 for s in 0..states as u32 {
120 let mut nx = [0usize; 2];
121 let mut o = [0u32; 2];
122 for u in 0..2u32 {
123 let reg = (u << m) | s;
124 nx[u as usize] = (reg >> 1) as usize;
125 o[u as usize] = generators.iter().fold(0, |acc, g| (acc << 1) | u32::from(parity(reg & g)));
126 }
127 next.push(nx);
128 out.push(o);
129 }
130 Ok(Trellis { constraint, n_out: generators.len(), next, out, tail: vec![0; states] })
131 }
132
133 fn memory(constraint: u32) -> Result<u32> {
135 ensure((1..=24).contains(&constraint), || format!("the constraint length must be 1 to 24, got {constraint}"))?;
136 Ok(constraint - 1)
137 }
138
139 pub fn recursive(constraint: u32, feedback: u32, forward: u32) -> Result<Trellis> {
146 Trellis::recursive_parities(constraint, feedback, &[forward])
147 }
148
149 pub fn recursive_parities(constraint: u32, feedback: u32, forwards: &[u32]) -> Result<Trellis> {
170 let m = Self::memory(constraint)?;
171 ensure(!forwards.is_empty() && forwards.len() < 31, || {
172 format!("a recursive code needs 1 to 30 feed-forward polynomials, got {}", forwards.len())
173 })?;
174 let states = 1usize << m;
175 let fb = feedback & ((1 << m) - 1);
176 let mut next = Vec::with_capacity(states);
177 let mut out = Vec::with_capacity(states);
178 let mut tail = Vec::with_capacity(states);
179 for s in 0..states as u32 {
180 let mut nx = [0usize; 2];
181 let mut o = [0u32; 2];
182 for u in 0..2u32 {
183 let w = u ^ u32::from(parity(s & fb));
184 let reg = (w << m) | s;
185 nx[u as usize] = (reg >> 1) as usize;
186 o[u as usize] = forwards.iter().fold(u, |acc, &g| (acc << 1) | u32::from(parity(reg & g)));
187 }
188 next.push(nx);
189 out.push(o);
190 tail.push(parity(s & fb));
191 }
192 Ok(Trellis { constraint, n_out: forwards.len() + 1, next, out, tail })
193 }
194
195 pub fn encode(&self, bits: &[u8], terminate: bool) -> Vec<u8> {
198 let tail = if terminate { self.constraint as usize - 1 } else { 0 };
199 let mut out = Vec::with_capacity((bits.len() + tail) * self.n_out);
200 let mut s = self.encode_from(0, bits, &mut out);
201 for _ in 0..tail {
202 let u = self.tail[s];
203 s = self.encode_from(s, &[u], &mut out);
204 }
205 out
206 }
207
208 pub fn encode_tail_biting(&self, bits: &[u8]) -> Result<Vec<u8>> {
229 self.check_feedforward()?;
230 let m = self.constraint as usize - 1;
231 ensure_shape(bits.len() >= m, || format!("tail biting needs at least K - 1 = {m} bits, got {}", bits.len()))?;
232 let start = bits[bits.len() - m..].iter().fold(0, |s, &b| self.next[s][usize::from(b & 1)]);
233 let mut out = Vec::with_capacity(bits.len() * self.n_out);
234 self.encode_from(start, bits, &mut out);
235 Ok(out)
236 }
237
238 fn encode_from(&self, mut s: usize, bits: &[u8], out: &mut Vec<u8>) -> usize {
240 for &b in bits {
241 let u = usize::from(b & 1);
242 let o = self.out[s][u];
243 for i in (0..self.n_out).rev() {
244 out.push(((o >> i) & 1) as u8);
245 }
246 s = self.next[s][u];
247 }
248 s
249 }
250
251 fn check_feedforward(&self) -> Result<()> {
253 let m = self.constraint - 1;
254 let ff = self.next.iter().enumerate().all(|(s, nx)| (0..2).all(|u| nx[u] == ((u << m) | s) >> 1));
255 ensure(ff, || "tail biting is defined here for feed-forward codes only".into())
256 }
257
258 fn branch(&self, o: u32, llr: &[f64]) -> f64 {
260 llr.iter().enumerate().map(|(i, l)| if (o >> (self.n_out - 1 - i)) & 1 == 0 { l / 2.0 } else { -l / 2.0 }).sum()
261 }
262
263 fn branches(&self, llr: &[f64], words: &mut Vec<f64>, metrics: &mut [f64]) {
267 let n_words = if self.n_out < 16 { 1usize << self.n_out } else { usize::MAX };
268 if n_words <= metrics.len() {
269 words.clear();
270 words.extend((0..n_words as u32).map(|o| self.branch(o, llr)));
271 for (m, o) in metrics.iter_mut().zip(self.out.iter().flatten()) {
272 *m = words[*o as usize];
273 }
274 } else {
275 for (m, o) in metrics.iter_mut().zip(self.out.iter().flatten()) {
276 *m = self.branch(*o, llr);
277 }
278 }
279 }
280}
281
282pub fn viterbi(trellis: &Trellis, llr: &[f64], terminated: bool) -> Result<Vec<u8>> {
289 let steps = steps_of(trellis, llr)?;
290 let ns = trellis.n_states();
291 let mut metric = vec![f64::NEG_INFINITY; ns];
292 metric[0] = 0.0;
293 let mut from = vec![0u32; steps * ns];
294 forward(trellis, llr, &mut metric, &mut from);
295 let end = if terminated { 0 } else { best_state(&metric) };
296 let (mut bits, _) = traceback(&from, ns, end);
297 if terminated {
298 bits.truncate(steps.saturating_sub(trellis.constraint as usize - 1));
299 }
300 Ok(bits)
301}
302
303pub fn viterbi_tail_biting(trellis: &Trellis, llr: &[f64], passes: usize) -> Result<Vec<u8>> {
320 ensure(passes > 0, || "the wrap-around Viterbi algorithm needs at least one pass".into())?;
321 let steps = steps_of(trellis, llr)?;
322 let ns = trellis.n_states();
323 let mut metric = vec![0.0; ns];
324 let mut from = vec![0u32; steps * ns];
325 for _ in 1..passes {
326 forward(trellis, llr, &mut metric, &mut from);
327 let end = best_state(&metric);
328 let (bits, first) = traceback(&from, ns, end);
329 if first == end {
330 return Ok(bits);
331 }
332 let top = metric[end];
333 for m in &mut metric {
334 *m -= top;
335 }
336 }
337 let start = metric.clone();
338 forward(trellis, llr, &mut metric, &mut from);
339 let end = best_state(&metric);
340 let (bits, first) = traceback(&from, ns, end);
341 if first == end {
342 return Ok(bits);
343 }
344 let best = (0..ns)
346 .map(|s| (s, traceback(&from, ns, s)))
347 .filter(|(s, (_, first))| first == s)
348 .max_by(|(a, (_, fa)), (b, (_, fb))| (metric[*a] - start[*fa]).total_cmp(&(metric[*b] - start[*fb])));
349 Ok(best.map_or(bits, |(_, (b, _))| b))
350}
351
352fn steps_of(trellis: &Trellis, llr: &[f64]) -> Result<usize> {
354 let n = trellis.n_out;
355 ensure_shape(llr.len() % n == 0, || format!("{} LLRs is not a multiple of {n}", llr.len()))?;
356 Ok(llr.len() / n)
357}
358
359fn forward(trellis: &Trellis, llr: &[f64], metric: &mut Vec<f64>, from: &mut [u32]) {
363 let (n, ns) = (trellis.n_out, trellis.n_states());
364 let mut next = vec![f64::NEG_INFINITY; ns];
365 let (mut words, mut gamma) = (Vec::new(), vec![0.0; 2 * ns]);
366 for (t, row) in from.chunks_exact_mut(ns).enumerate() {
367 trellis.branches(&llr[t * n..(t + 1) * n], &mut words, &mut gamma);
368 next.fill(f64::NEG_INFINITY);
369 for s in 0..ns {
370 if metric[s] == f64::NEG_INFINITY {
371 continue;
372 }
373 for u in 0..2 {
374 let ns2 = trellis.next[s][u];
375 let m = metric[s] + gamma[2 * s + u];
376 if m > next[ns2] {
377 next[ns2] = m;
378 row[ns2] = (2 * s + u) as u32;
379 }
380 }
381 }
382 std::mem::swap(metric, &mut next);
383 }
384}
385
386fn best_state(metric: &[f64]) -> usize {
388 (0..metric.len()).max_by(|a, b| metric[*a].total_cmp(&metric[*b])).unwrap_or(0)
389}
390
391fn traceback(from: &[u32], ns: usize, end: usize) -> (Vec<u8>, usize) {
393 let steps = from.len() / ns;
394 let mut s = end;
395 let mut bits = vec![0u8; steps];
396 for t in (0..steps).rev() {
397 let su = from[t * ns + s] as usize;
398 bits[t] = (su & 1) as u8;
399 s = su >> 1;
400 }
401 (bits, s)
402}
403
404pub fn bcjr(trellis: &Trellis, llr: &[f64], apriori: &[f64], terminated: bool, jacobian: Jacobian) -> Result<Vec<f64>> {
412 let mut app = Vec::new();
413 bcjr_into(trellis, llr, apriori, terminated, jacobian, &mut BcjrWorkspace::default(), &mut app)?;
414 Ok(app)
415}
416
417#[derive(Default)]
419pub(crate) struct BcjrWorkspace {
420 gamma: Vec<f64>,
422 alpha: Vec<f64>,
424 beta: Vec<f64>,
425 words: Vec<f64>,
426}
427
428pub(crate) fn bcjr_into(
430 trellis: &Trellis,
431 llr: &[f64],
432 apriori: &[f64],
433 terminated: bool,
434 jacobian: Jacobian,
435 ws: &mut BcjrWorkspace,
436 app: &mut Vec<f64>,
437) -> Result<()> {
438 let n = trellis.n_out;
439 if llr.len() % n != 0 || (!apriori.is_empty() && apriori.len() != llr.len() / n) {
440 return Err(comnum_core::Error::Shape("inconsistent channel and a priori LLRs".into()));
441 }
442 let steps = llr.len() / n;
443 let ns = trellis.n_states();
444 let ninf = f64::NEG_INFINITY;
445 ws.gamma.resize(steps * 2 * ns, 0.0);
447 for (t, g) in ws.gamma.chunks_exact_mut(2 * ns).enumerate() {
448 trellis.branches(&llr[t * n..(t + 1) * n], &mut ws.words, g);
449 let la = apriori.get(t).copied().unwrap_or(0.0);
450 for (i, g) in g.iter_mut().enumerate() {
451 *g += if i % 2 == 0 { la / 2.0 } else { -la / 2.0 };
452 }
453 }
454 let gamma = &ws.gamma;
455 let alpha = &mut ws.alpha;
456 alpha.clear();
457 alpha.resize((steps + 1) * ns, ninf);
458 alpha[0] = 0.0;
459 for t in 0..steps {
460 let (now, later) = alpha[t * ns..(t + 2) * ns].split_at_mut(ns);
461 for s in 0..ns {
462 if now[s] == ninf {
463 continue;
464 }
465 for u in 0..2 {
466 let s2 = trellis.next[s][u];
467 later[s2] = max_star(later[s2], now[s] + gamma[(t * ns + s) * 2 + u], jacobian);
468 }
469 }
470 }
471 let beta = &mut ws.beta;
472 beta.clear();
473 beta.resize((steps + 1) * ns, ninf);
474 if terminated {
475 beta[steps * ns] = 0.0;
476 } else {
477 beta[steps * ns..].fill(0.0);
478 }
479 for t in (0..steps).rev() {
480 let (now, later) = beta[t * ns..(t + 2) * ns].split_at_mut(ns);
481 for s in 0..ns {
482 let mut b = ninf;
483 for u in 0..2 {
484 b = max_star(b, gamma[(t * ns + s) * 2 + u] + later[trellis.next[s][u]], jacobian);
485 }
486 now[s] = b;
487 }
488 }
489 app.clear();
490 for t in 0..steps {
491 let mut l = [ninf; 2];
492 for s in 0..ns {
493 let a = alpha[t * ns + s];
494 if a == ninf {
495 continue;
496 }
497 for u in 0..2 {
498 l[u] =
499 max_star(l[u], a + gamma[(t * ns + s) * 2 + u] + beta[(t + 1) * ns + trellis.next[s][u]], jacobian);
500 }
501 }
502 app.push(l[0] - l[1]);
503 }
504 Ok(())
505}
506
507#[derive(Clone, Debug, PartialEq, Eq)]
510pub struct ConvolutionalCode {
511 trellis: Trellis,
513 k: usize,
515 pub hard_decision: bool,
517}
518
519impl ConvolutionalCode {
520 pub fn new(trellis: Trellis, k: usize) -> Result<ConvolutionalCode> {
526 ensure(k >= 1, || "a convolutional code needs at least one information bit per block".into())?;
527 Ok(ConvolutionalCode { trellis, k, hard_decision: false })
528 }
529
530 pub fn k7(k: usize) -> Result<ConvolutionalCode> {
535 let trellis = Trellis::feedforward(7, &[0o133, 0o171]).unwrap_or_else(|_| unreachable!("a valid code"));
536 ConvolutionalCode::new(trellis, k)
537 }
538
539 pub fn trellis(&self) -> &Trellis {
541 &self.trellis
542 }
543}
544
545impl BlockCode for ConvolutionalCode {
546 fn k(&self) -> usize {
547 self.k
548 }
549
550 fn n(&self) -> usize {
551 self.trellis.n_out * (self.k + self.trellis.constraint as usize - 1)
552 }
553
554 fn encode_block(&self, info: &[u8]) -> Result<Vec<u8>> {
555 expect_len(info.len(), self.k, "bits")?;
556 Ok(self.trellis.encode(info, true))
557 }
558
559 fn decode_block(&self, llr: &[f64]) -> Result<Vec<u8>> {
560 expect_len(llr.len(), self.n(), "LLRs")?;
561 if self.hard_decision {
562 let h: Vec<f64> = llr
564 .iter()
565 .map(|l| {
566 if *l < 0.0 {
567 -1.0
568 } else if *l > 0.0 {
569 1.0
570 } else {
571 0.0
572 }
573 })
574 .collect();
575 viterbi(&self.trellis, &h, true)
576 } else {
577 viterbi(&self.trellis, llr, true)
578 }
579 }
580}
581
582impl SoftDecode for ConvolutionalCode {
583 fn app_into(&self, llr: &[f64], apriori: Option<&[f64]>, out: &mut [f64]) -> Result<usize> {
586 expect_len(llr.len(), self.n(), "LLRs")?;
587 expect_len(out.len(), self.k, "a posteriori LLRs")?;
588 let la: Vec<f64> = match apriori {
589 None => Vec::new(),
590 Some(a) => {
591 expect_len(a.len(), self.k, "a priori LLRs")?;
592 a.iter().copied().chain(std::iter::repeat_n(0.0, self.trellis.constraint as usize - 1)).collect()
593 }
594 };
595 let app = bcjr(&self.trellis, llr, &la, true, Jacobian::Exact)?;
596 out.copy_from_slice(&app[..self.k]);
597 Ok(1)
598 }
599}
600
601container_traits!(ConvolutionalCode);
602
603#[cfg(test)]
604mod tests {
605 use super::*;
606 use crate::block::testing::awgn_llr;
607 use comnum_core::rng::Rng;
608 use comnum_core::signal::hard_decisions;
609
610 #[test]
611 fn the_k7_code_has_free_distance_10() {
612 let t = Trellis::feedforward(7, &[0o133, 0o171]).unwrap();
614 let c = t.encode(&[1, 0, 0, 0, 0, 0, 0], true);
615 assert_eq!(c.iter().map(|b| u32::from(*b)).sum::<u32>(), 10);
616 }
617
618 #[test]
619 fn viterbi_corrects_errors_and_bcjr_agrees() {
620 let code = ConvolutionalCode::k7(200).unwrap();
621 let mut r = Rng::new(1);
622 let info: Vec<u8> = (0..200).map(|_| r.bit()).collect();
623 let c = code.encode_block(&info).unwrap();
624 let y = awgn_llr(&c, 0.6, &mut r);
625 assert!(hard_decisions(&y).iter().zip(&c).filter(|(a, b)| a != b).count() > 5);
626 assert_eq!(code.decode_block(&y).unwrap(), info);
627 let app = bcjr(&code.trellis, &y, &[], true, Jacobian::Exact).unwrap();
628 assert_eq!(hard_decisions(&app[..200]), info);
629 let maxlog = bcjr(&code.trellis, &y, &[], true, Jacobian::MaxLog).unwrap();
630 assert_eq!(hard_decisions(&maxlog[..200]), info);
631 }
632
633 #[test]
634 fn branch_tables_and_reused_workspaces_change_nothing() {
635 let mut r = Rng::new(5);
636 let codes = [
639 Trellis::feedforward(7, &[0o133, 0o171]).unwrap(),
640 Trellis::recursive(4, 0o13, 0o15).unwrap(),
641 Trellis::feedforward(3, &[5, 7, 7, 5, 6]).unwrap(),
642 ];
643 let (mut ws, mut app) = (BcjrWorkspace::default(), Vec::new());
644 for t in &codes {
645 let (ns, n) = (t.n_states(), t.n_out);
646 let l: Vec<f64> = (0..n).map(|_| r.gaussian()).collect();
647 let (mut words, mut m) = (Vec::new(), vec![0.0; 2 * ns]);
648 t.branches(&l, &mut words, &mut m);
649 for s in 0..ns {
650 for u in 0..2 {
651 assert_eq!(m[2 * s + u].to_bits(), t.branch(t.out[s][u], &l).to_bits());
652 }
653 }
654 for len in [30, 7, 50] {
655 let bits: Vec<u8> = (0..len).map(|_| r.bit()).collect();
656 let c = t.encode(&bits, true);
657 let clean: Vec<f64> = c.iter().map(|b| 4.0 * (1.0 - 2.0 * f64::from(*b))).collect();
658 assert_eq!(viterbi(t, &clean, true).unwrap(), bits);
659 let y: Vec<f64> = clean.iter().map(|v| v + 2.0 * r.gaussian()).collect();
660 let la: Vec<f64> = (0..y.len() / n).map(|_| r.gaussian()).collect();
661 for (terminated, jacobian) in [(true, Jacobian::Exact), (false, Jacobian::MaxLog)] {
662 bcjr_into(t, &y, &la, terminated, jacobian, &mut ws, &mut app).unwrap();
663 let fresh = bcjr(t, &y, &la, terminated, jacobian).unwrap();
664 assert!(
665 app.iter().zip(&fresh).all(|(a, b)| a.to_bits() == b.to_bits()) && app.len() == fresh.len()
666 );
667 }
668 }
669 }
670 }
671
672 #[test]
673 fn the_soft_output_is_the_bcjr_of_the_information_bits() {
674 let code = ConvolutionalCode::k7(60).unwrap();
675 let mut r = Rng::new(3);
676 let info: Vec<u8> = (0..60).map(|_| r.bit()).collect();
677 let y = awgn_llr(&code.encode_block(&info).unwrap(), 0.8, &mut r);
678 let la: Vec<f64> = (0..60).map(|_| r.gaussian()).collect();
679 let former = bcjr(&code.trellis, &y, &[], true, Jacobian::Exact).unwrap();
680 let app = code.app(&y, None).unwrap();
681 assert!(app.iter().zip(&former[..60]).all(|(a, b)| a.to_bits() == b.to_bits()));
682 let padded: Vec<f64> = la.iter().copied().chain([0.0; 6]).collect();
683 let former = bcjr(&code.trellis, &y, &padded, true, Jacobian::Exact).unwrap();
684 let app = code.app(&y, Some(&la)).unwrap();
685 assert!(app.iter().zip(&former[..60]).all(|(a, b)| a.to_bits() == b.to_bits()));
686 assert!(code.app(&y[1..], None).is_err() && code.app(&y, Some(&la[1..])).is_err());
687 assert!(ConvolutionalCode::k7(0).is_err());
688 }
689
690 fn tail_biting_ml(t: &Trellis, llr: &[f64]) -> Vec<u8> {
691 let ns = t.n_states();
693 let steps = llr.len() / t.n_out();
694 let mut best = (f64::NEG_INFINITY, Vec::new());
695 for s0 in 0..ns {
696 let mut metric = vec![f64::NEG_INFINITY; ns];
697 metric[s0] = 0.0;
698 let mut from = vec![0u32; steps * ns];
699 forward(t, llr, &mut metric, &mut from);
700 if metric[s0] > best.0 {
701 best = (metric[s0], traceback(&from, ns, s0).0);
702 }
703 }
704 best.1
705 }
706
707 #[test]
708 fn the_wava_decodes_the_lte_code_as_the_maximum_likelihood_decoder() {
709 let t = Trellis::feedforward(7, &[0o133, 0o171, 0o165]).unwrap();
710 let mut rng = comnum_core::rng::Rng::new(36_212);
711 let (mut agree, mut errors, trials) = (0, 0, 300);
712 for _ in 0..trials {
713 let bits: Vec<u8> = (0..40).map(|_| (rng.uniform() < 0.5) as u8).collect();
714 let c = t.encode_tail_biting(&bits).unwrap();
715 let es_n0 = 10f64.powf(-0.2);
717 let llr: Vec<f64> = c
718 .iter()
719 .map(|&b| {
720 let y = 1.0 - 2.0 * f64::from(b) + rng.gaussian() / (2.0 * es_n0).sqrt();
721 4.0 * es_n0 * y
722 })
723 .collect();
724 let wava = viterbi_tail_biting(&t, &llr, 4).unwrap();
725 agree += usize::from(wava == tail_biting_ml(&t, &llr));
726 errors += usize::from(wava != bits);
727 }
728 assert!(agree >= trials - 3, "{agree} of {trials}");
729 assert!(errors < trials / 10, "{errors} block errors of {trials}");
730 }
731
732 #[test]
733 fn tail_biting_starts_and_ends_in_the_state_of_the_last_inputs() {
734 let t = Trellis::feedforward(7, &[0o133, 0o171, 0o165]).unwrap();
735 let bits = [1u8, 0, 1, 1, 0, 0, 1, 0, 1, 1];
736 let c = t.encode_tail_biting(&bits).unwrap();
737 let mut warm = bits[4..].to_vec();
739 warm.extend_from_slice(&bits);
740 assert_eq!(c, t.encode(&warm, false)[3 * 6..]);
741 assert!(t.encode_tail_biting(&bits[..5]).is_err());
742 assert!(Trellis::recursive(4, 0o13, 0o15).unwrap().encode_tail_biting(&bits).is_err());
743 assert!(viterbi_tail_biting(&t, &[0.0; 4], 2).is_err());
744 assert!(viterbi_tail_biting(&t, &[0.0; 3], 0).is_err());
745 }
746
747 #[test]
748 fn recursive_codes_terminate_and_are_systematic() {
749 let t = Trellis::recursive(4, 0o13, 0o15).unwrap();
750 let bits = [1, 0, 1, 1, 0, 0, 1, 0];
751 let c = t.encode(&bits, true);
752 assert_eq!(c.len(), 2 * (8 + 3));
753 assert_eq!((0..8).map(|i| c[2 * i]).collect::<Vec<_>>(), bits);
755 let mut s = 0;
757 for &b in &bits {
758 s = t.next[s][usize::from(b)];
759 }
760 for _ in 0..3 {
761 s = t.next[s][usize::from(t.tail[s])];
762 }
763 assert_eq!(s, 0);
764 }
765}