Skip to main content

p3_binary_field/
extension.rs

1//! Extension-field structure between the byte-aligned levels of the Wiedemann tower.
2//!
3//! Each level `GF(2^b)` of the tower contains every level below it, so for byte-aligned levels
4//! `Lower = GF(2^l)` and `Upper = GF(2^u)` with `l < u`, `Upper` is a degree-`u/l` extension of
5//! `Lower`. The basis is the tower basis: writing an element of `Upper` as an unsigned integer,
6//! basis coefficient `i` is the `l`-bit chunk at bit `i * l`, and basis element `i` is `1 << (i * l)`.
7//!
8//! On a little-endian target that chunking is exactly the in-memory byte layout, so the basis
9//! coefficients of an element can be borrowed in place rather than copied out. The levels below
10//! `GF(2^8)` are excluded because they do not fill their backing integer, and so their chunks
11//! would not all be canonical representatives.
12
13use alloc::vec::Vec;
14use core::ops::{Add, Mul, Sub};
15use core::{ptr, slice};
16
17use p3_field::extension::HasFrobenius;
18use p3_field::op_assign_macros::{impl_add_base_field, impl_mul_base_field, impl_sub_base_field};
19use p3_field::{
20    Algebra, BasedVectorSpace, ExtensionField, Field, PackedFieldExtension, Powers,
21    PrimeCharacteristicRing,
22};
23
24use crate::tower::TowerLevel;
25use crate::{BinaryField8, BinaryField16, BinaryField32, BinaryField64, BinaryField128};
26
27/// Make one byte-aligned tower level an extension of a byte-aligned level below it.
28///
29/// The parameters are the upper level with its backing integer, then the lower level with its
30/// backing integer.
31macro_rules! binary_tower_extension {
32    ($upper:ty, $upper_repr:ty, $lower:ty, $lower_repr:ty) => {
33        impl From<$lower> for $upper {
34            /// The lower level occupies the low `BITS` bits; the other coefficients vanish.
35            #[inline]
36            fn from(x: $lower) -> Self {
37                Self::from_repr(<$upper_repr>::from(x.to_repr()))
38            }
39        }
40
41        impl_add_base_field!($upper, $lower);
42        impl_sub_base_field!($upper, $lower);
43        impl_mul_base_field!($upper, $lower);
44
45        impl Algebra<$lower> for $upper {}
46
47        impl BasedVectorSpace<$lower> for $upper {
48            const DIMENSION: usize = <$upper>::BITS / <$lower>::BITS;
49
50            #[inline]
51            fn as_basis_coefficients_slice(&self) -> &[$lower] {
52                const {
53                    assert!(
54                        cfg!(target_endian = "little"),
55                        "the tower basis coincides with the memory layout only on little-endian targets"
56                    );
57                    // The lower level fills its backing integer, so every chunk of `self` is
58                    // already a canonical representative.
59                    assert!(<$lower>::BITS == <$lower_repr>::BITS as usize);
60                    assert!(
61                        size_of::<$upper>()
62                            == <$upper as BasedVectorSpace<$lower>>::DIMENSION
63                                * size_of::<$lower>()
64                    );
65                    assert!(align_of::<$lower>() <= align_of::<$upper>());
66                }
67
68                // SAFETY: both levels are `repr(transparent)` over unsigned integers, so `self`
69                // is `DIMENSION` contiguous, padding-free `$lower` values, aligned at least as
70                // strictly as `$lower` requires; the assertions above check the size, alignment
71                // and endianness this relies on, and that every chunk is canonical. The returned
72                // slice borrows `self`, so it cannot outlive it.
73                unsafe {
74                    slice::from_raw_parts(
75                        ptr::from_ref(self).cast::<$lower>(),
76                        <$upper as BasedVectorSpace<$lower>>::DIMENSION,
77                    )
78                }
79            }
80
81            #[inline]
82            fn from_basis_coefficients_fn<Fn: FnMut(usize) -> $lower>(mut f: Fn) -> Self {
83                let mut repr: $upper_repr = 0;
84                for i in 0..<$upper as BasedVectorSpace<$lower>>::DIMENSION {
85                    repr |= <$upper_repr>::from(f(i).to_repr()) << (i * <$lower>::BITS);
86                }
87                Self::from_repr(repr)
88            }
89
90            #[inline]
91            fn from_basis_coefficients_iter<I: ExactSizeIterator<Item = $lower>>(
92                iter: I,
93            ) -> Option<Self> {
94                let dimension = <$upper as BasedVectorSpace<$lower>>::DIMENSION;
95                (iter.len() == dimension).then(|| {
96                    let mut repr: $upper_repr = 0;
97                    // Zipping against the dimension bounds the shift even if `len` lied.
98                    for (i, c) in (0..dimension).zip(iter) {
99                        repr |= <$upper_repr>::from(c.to_repr()) << (i * <$lower>::BITS);
100                    }
101                    Self::from_repr(repr)
102                })
103            }
104
105            /// The tower basis coincides with the byte layout, so the whole buffer already *is*
106            /// its coefficients: flattening is one copy, not one per element.
107            ///
108            /// The inverse direction is not symmetric, and `reconstitute_from_base` keeps the
109            /// default that builds a fresh buffer: a `Vec<$lower>` is allocated at `$lower`'s
110            /// alignment, which is weaker than `$upper`'s, and freeing an allocation under a
111            /// `Layout` whose alignment differs from the one it was made with is undefined
112            /// behaviour.
113            #[inline]
114            fn flatten_to_base(vec: Vec<Self>) -> Vec<$lower> {
115                let d = <$upper as BasedVectorSpace<$lower>>::DIMENSION;
116
117                // SAFETY: exactly the argument in `as_basis_coefficients_slice` above, extended
118                // to the whole buffer: a `Vec<$upper>` is `vec.len()` contiguous, padding-free
119                // `$upper` values, each of which is `d` contiguous `$lower` values in tower-basis
120                // order. The const assertions there cover the endianness, size, alignment and
121                // canonicity this relies on. The slice borrows `vec`, so it cannot outlive it.
122                let flat: &[$lower] =
123                    unsafe { slice::from_raw_parts(vec.as_ptr().cast::<$lower>(), vec.len() * d) };
124                flat.to_vec()
125            }
126        }
127
128        impl ExtensionField<$lower> for $upper {
129            type ExtensionPacking = Self;
130
131            #[inline]
132            fn is_in_basefield(&self) -> bool {
133                // The base field is exactly the elements whose coefficients above the
134                // constant one vanish.
135                (self.to_repr() >> <$lower>::BITS) == 0
136            }
137
138            #[inline]
139            fn as_base(&self) -> Option<$lower> {
140                <Self as ExtensionField<$lower>>::is_in_basefield(self)
141                    .then(|| <$lower>::from_repr(self.to_repr() as $lower_repr))
142            }
143        }
144
145        /// A tower level packs one element per vector, so every lane index is `0`.
146        impl PackedFieldExtension<$lower, $upper> for $upper {
147            #[inline]
148            fn from_ext_fn(f: impl Fn(usize) -> Self) -> Self {
149                f(0)
150            }
151
152            #[inline]
153            fn from_ext_slice(slice: &[Self]) -> Self {
154                assert_eq!(slice.len(), 1);
155                slice[0]
156            }
157
158            #[inline]
159            fn extract(&self, lane: usize) -> Self {
160                assert_eq!(lane, 0, "lane index out of range");
161                *self
162            }
163
164            #[inline]
165            fn add_assign_lane(&mut self, lane: usize, value: Self) {
166                assert_eq!(lane, 0, "lane index out of range");
167                *self += value;
168            }
169
170            #[inline]
171            fn packed_ext_powers(base: Self) -> Powers<Self> {
172                base.powers()
173            }
174        }
175
176        impl HasFrobenius<$lower> for $upper {
177            /// `x ↦ x^n` for `n = 2^BITS` the order of the base field.
178            #[inline]
179            fn frobenius(&self) -> Self {
180                self.exp_power_of_2(<$lower>::BITS)
181            }
182
183            #[inline]
184            fn repeated_frobenius(&self, count: usize) -> Self {
185                // The Galois group is cyclic of order `DIMENSION`, so `count` reduces modulo it.
186                let count = count % <$upper as BasedVectorSpace<$lower>>::DIMENSION;
187                self.exp_power_of_2(<$lower>::BITS * count)
188            }
189
190            #[inline]
191            fn pseudo_inv(&self) -> Self {
192                // Inversion through the norm to the level below is cheaper than the
193                // `n^D - 2` exponentiation the contract is phrased in terms of.
194                self.try_inverse().unwrap_or(Self::ZERO)
195            }
196        }
197    };
198}
199
200binary_tower_extension!(BinaryField16, u16, BinaryField8, u8);
201binary_tower_extension!(BinaryField32, u32, BinaryField8, u8);
202binary_tower_extension!(BinaryField64, u64, BinaryField8, u8);
203binary_tower_extension!(BinaryField128, u128, BinaryField8, u8);
204binary_tower_extension!(BinaryField32, u32, BinaryField16, u16);
205binary_tower_extension!(BinaryField64, u64, BinaryField16, u16);
206binary_tower_extension!(BinaryField128, u128, BinaryField16, u16);
207binary_tower_extension!(BinaryField64, u64, BinaryField32, u32);
208binary_tower_extension!(BinaryField128, u128, BinaryField32, u32);
209binary_tower_extension!(BinaryField128, u128, BinaryField64, u64);
210
211#[cfg(test)]
212mod tests {
213    use alloc::vec::Vec;
214
215    use p3_field::extension::HasFrobenius;
216    use p3_field::{BasedVectorSpace, ExtensionField, PrimeCharacteristicRing};
217
218    use crate::tower::TowerLevel;
219    use crate::{BinaryField8, BinaryField16, BinaryField32, BinaryField64, BinaryField128};
220
221    /// A fixed nonzero element of `GF(2^128)` used across the tests.
222    const A128: u128 = 0x1234_5678_9abc_def0_0fed_cba9_8765_4321;
223
224    /// Check that the basis coefficients over `$lower` really are coordinates in the basis
225    /// returned by `ith_basis_element`, and that both reassembly routes recover the element.
226    macro_rules! assert_basis_round_trip {
227        ($upper:ty, $lower:ty, $val:expr) => {{
228            let a = <$upper>::from_repr($val);
229            let dim = <$upper as BasedVectorSpace<$lower>>::DIMENSION;
230            let coeffs = BasedVectorSpace::<$lower>::as_basis_coefficients_slice(&a);
231            assert_eq!(coeffs.len(), dim);
232
233            // `a = Σ coeffs[i] · basis[i]` is the defining property of a coordinate map.
234            let recomposed = (0..dim)
235                .map(|i| {
236                    <$upper as BasedVectorSpace<$lower>>::ith_basis_element(i).unwrap() * coeffs[i]
237                })
238                .sum::<$upper>();
239            assert_eq!(recomposed, a);
240
241            assert_eq!(
242                <$upper as BasedVectorSpace<$lower>>::from_basis_coefficients_slice(coeffs),
243                Some(a)
244            );
245            assert_eq!(
246                <$upper as BasedVectorSpace<$lower>>::from_basis_coefficients_fn(|i| coeffs[i]),
247                a
248            );
249        }};
250    }
251
252    /// Check that the mixed-type operators agree with embedding the scalar first.
253    macro_rules! assert_mixed_ops_match_embedding {
254        ($upper:ty, $lower:ty, $val:expr, $scalar:expr) => {{
255            let a = <$upper>::from_repr($val);
256            let s = <$lower>::from_repr($scalar);
257            let s_up = <$upper>::from(s);
258
259            assert_eq!(a + s, a + s_up);
260            assert_eq!(a - s, a - s_up);
261            assert_eq!(a * s, a * s_up);
262
263            let mut acc = a;
264            acc += s;
265            assert_eq!(acc, a + s_up);
266            let mut acc = a;
267            acc -= s;
268            assert_eq!(acc, a - s_up);
269            let mut acc = a;
270            acc *= s;
271            assert_eq!(acc, a * s_up);
272        }};
273    }
274
275    /// Check that Frobenius over `$lower` fixes `$lower` pointwise, has order `DIMENSION`, and
276    /// that `repeated_frobenius` iterates it.
277    macro_rules! assert_frobenius_over {
278        ($upper:ty, $lower:ty, $val:expr) => {{
279            let dim = <$upper as BasedVectorSpace<$lower>>::DIMENSION;
280
281            // Frobenius fixes the base field: `x ↦ x^|lower|` is the identity on `lower`.
282            for i in 0..=u8::MAX {
283                let embedded = <$upper>::from(<$lower>::from_repr(i.into()));
284                assert_eq!(
285                    HasFrobenius::<$lower>::frobenius(&embedded),
286                    embedded,
287                    "Frobenius must fix the embedded base field"
288                );
289            }
290
291            // It is not the identity away from the base field: the generator of `$upper` over
292            // `$lower` is the first basis element beyond the base field.
293            let x = <$upper as BasedVectorSpace<$lower>>::ith_basis_element(1).unwrap();
294            assert_ne!(
295                HasFrobenius::<$lower>::frobenius(&x),
296                x,
297                "Frobenius must move elements outside the base field"
298            );
299
300            // `repeated_frobenius(count)` iterates `frobenius` and has order `DIMENSION`.
301            let a = <$upper>::from_repr($val);
302            let mut acc = a;
303            for count in 0..(2 * dim + 3) {
304                assert_eq!(HasFrobenius::<$lower>::repeated_frobenius(&a, count), acc);
305                acc = HasFrobenius::<$lower>::frobenius(&acc);
306            }
307
308            // `pseudo_inv` is inversion, extended by `0 ↦ 0`.
309            assert_eq!(
310                HasFrobenius::<$lower>::pseudo_inv(&<$upper>::ZERO),
311                <$upper>::ZERO
312            );
313            assert_eq!(a * HasFrobenius::<$lower>::pseudo_inv(&a), <$upper>::ONE);
314        }};
315    }
316
317    #[test]
318    fn gf2_128_over_gf2_8_basis_coefficients_are_bytes() {
319        let a = BinaryField128::from_repr(0x0f0e_0d0c_0b0a_0908_0706_0504_0302_0100);
320        let coeffs = BasedVectorSpace::<BinaryField8>::as_basis_coefficients_slice(&a);
321        assert_eq!(coeffs.len(), 16);
322        for (i, c) in coeffs.iter().enumerate() {
323            assert_eq!(*c, BinaryField8::from_repr(i as u8));
324        }
325        assert_eq!(
326            <BinaryField128 as BasedVectorSpace<BinaryField8>>::from_basis_coefficients_slice(
327                coeffs
328            ),
329            Some(a)
330        );
331    }
332
333    #[test]
334    fn mixed_mul_matches_embedding() {
335        let a = BinaryField128::from_repr(A128);
336        let s = BinaryField8::from_repr(0xa7);
337        assert_eq!(a * s, a * BinaryField128::from(s));
338    }
339
340    #[test]
341    fn dimension_is_the_ratio_of_bit_widths() {
342        assert_eq!(
343            <BinaryField16 as BasedVectorSpace<BinaryField8>>::DIMENSION,
344            2
345        );
346        assert_eq!(
347            <BinaryField32 as BasedVectorSpace<BinaryField8>>::DIMENSION,
348            4
349        );
350        assert_eq!(
351            <BinaryField64 as BasedVectorSpace<BinaryField8>>::DIMENSION,
352            8
353        );
354        assert_eq!(
355            <BinaryField128 as BasedVectorSpace<BinaryField8>>::DIMENSION,
356            16
357        );
358        assert_eq!(
359            <BinaryField32 as BasedVectorSpace<BinaryField16>>::DIMENSION,
360            2
361        );
362        assert_eq!(
363            <BinaryField64 as BasedVectorSpace<BinaryField16>>::DIMENSION,
364            4
365        );
366        assert_eq!(
367            <BinaryField128 as BasedVectorSpace<BinaryField16>>::DIMENSION,
368            8
369        );
370        assert_eq!(
371            <BinaryField64 as BasedVectorSpace<BinaryField32>>::DIMENSION,
372            2
373        );
374        assert_eq!(
375            <BinaryField128 as BasedVectorSpace<BinaryField32>>::DIMENSION,
376            4
377        );
378        assert_eq!(
379            <BinaryField128 as BasedVectorSpace<BinaryField64>>::DIMENSION,
380            2
381        );
382    }
383
384    #[test]
385    fn basis_coefficients_round_trip_on_every_pair() {
386        assert_basis_round_trip!(BinaryField16, BinaryField8, 0xfedc);
387        assert_basis_round_trip!(BinaryField32, BinaryField8, 0xfedc_ba98);
388        assert_basis_round_trip!(BinaryField64, BinaryField8, 0xfedc_ba98_7654_3210);
389        assert_basis_round_trip!(BinaryField128, BinaryField8, A128);
390        assert_basis_round_trip!(BinaryField32, BinaryField16, 0xfedc_ba98);
391        assert_basis_round_trip!(BinaryField64, BinaryField16, 0xfedc_ba98_7654_3210);
392        assert_basis_round_trip!(BinaryField128, BinaryField16, A128);
393        assert_basis_round_trip!(BinaryField64, BinaryField32, 0xfedc_ba98_7654_3210);
394        assert_basis_round_trip!(BinaryField128, BinaryField32, A128);
395        assert_basis_round_trip!(BinaryField128, BinaryField64, A128);
396    }
397
398    #[test]
399    fn mixed_ops_match_embedding_on_every_pair() {
400        assert_mixed_ops_match_embedding!(BinaryField16, BinaryField8, 0xfedc, 0xa7);
401        assert_mixed_ops_match_embedding!(BinaryField32, BinaryField8, 0xfedc_ba98, 0xa7);
402        assert_mixed_ops_match_embedding!(BinaryField64, BinaryField8, 0xfedc_ba98_7654_3210, 0xa7);
403        assert_mixed_ops_match_embedding!(BinaryField128, BinaryField8, A128, 0xa7);
404        assert_mixed_ops_match_embedding!(BinaryField32, BinaryField16, 0xfedc_ba98, 0xa73b);
405        assert_mixed_ops_match_embedding!(
406            BinaryField64,
407            BinaryField16,
408            0xfedc_ba98_7654_3210,
409            0xa73b
410        );
411        assert_mixed_ops_match_embedding!(BinaryField128, BinaryField16, A128, 0xa73b);
412        assert_mixed_ops_match_embedding!(
413            BinaryField64,
414            BinaryField32,
415            0xfedc_ba98_7654_3210,
416            0xa73b_c91d
417        );
418        assert_mixed_ops_match_embedding!(BinaryField128, BinaryField32, A128, 0xa73b_c91d);
419        assert_mixed_ops_match_embedding!(
420            BinaryField128,
421            BinaryField64,
422            A128,
423            0xa73b_c91d_5566_7788
424        );
425    }
426
427    #[test]
428    fn frobenius_fixes_exactly_the_lower_level_on_every_pair() {
429        assert_frobenius_over!(BinaryField16, BinaryField8, 0xfedc);
430        assert_frobenius_over!(BinaryField32, BinaryField8, 0xfedc_ba98);
431        assert_frobenius_over!(BinaryField64, BinaryField8, 0xfedc_ba98_7654_3210);
432        assert_frobenius_over!(BinaryField128, BinaryField8, A128);
433        assert_frobenius_over!(BinaryField32, BinaryField16, 0xfedc_ba98);
434        assert_frobenius_over!(BinaryField64, BinaryField16, 0xfedc_ba98_7654_3210);
435        assert_frobenius_over!(BinaryField128, BinaryField16, A128);
436        assert_frobenius_over!(BinaryField64, BinaryField32, 0xfedc_ba98_7654_3210);
437        assert_frobenius_over!(BinaryField128, BinaryField32, A128);
438        assert_frobenius_over!(BinaryField128, BinaryField64, A128);
439    }
440
441    /// The order of the base field, not its bit width, is the Frobenius exponent: over
442    /// `GF(2^8)` the map is `x ↦ x^256`, which fixes all 256 elements of `GF(2^8)`.
443    #[test]
444    fn frobenius_exponent_is_the_order_of_the_base_field() {
445        let a = BinaryField128::from_repr(A128);
446        assert_eq!(
447            HasFrobenius::<BinaryField8>::frobenius(&a),
448            a.exp_power_of_2(8)
449        );
450        assert_eq!(
451            HasFrobenius::<BinaryField64>::frobenius(&a),
452            a.exp_power_of_2(64)
453        );
454    }
455
456    #[test]
457    fn is_in_basefield_detects_the_embedded_subfield() {
458        let low = BinaryField128::from(BinaryField8::from_repr(0xa7));
459        assert!(ExtensionField::<BinaryField8>::is_in_basefield(&low));
460        assert_eq!(
461            ExtensionField::<BinaryField8>::as_base(&low),
462            Some(BinaryField8::from_repr(0xa7))
463        );
464
465        // Sixteen bits wide: inside `GF(2^16)`, outside `GF(2^8)`.
466        let mid = BinaryField128::from_repr(0xa73b);
467        assert!(!ExtensionField::<BinaryField8>::is_in_basefield(&mid));
468        assert_eq!(ExtensionField::<BinaryField8>::as_base(&mid), None);
469        assert!(ExtensionField::<BinaryField16>::is_in_basefield(&mid));
470        assert_eq!(
471            ExtensionField::<BinaryField16>::as_base(&mid),
472            Some(BinaryField16::from_repr(0xa73b))
473        );
474    }
475
476    #[test]
477    fn from_basis_coefficients_iter_checks_the_length() {
478        let coeffs = [BinaryField64::ONE, BinaryField64::ZERO];
479        assert_eq!(
480            <BinaryField128 as BasedVectorSpace<BinaryField64>>::from_basis_coefficients_iter(
481                coeffs.into_iter()
482            ),
483            Some(BinaryField128::ONE)
484        );
485        assert_eq!(
486            <BinaryField128 as BasedVectorSpace<BinaryField64>>::from_basis_coefficients_iter(
487                coeffs[..1].iter().copied()
488            ),
489            None
490        );
491        assert_eq!(
492            <BinaryField128 as BasedVectorSpace<BinaryField64>>::from_basis_coefficients_slice(&[]),
493            None
494        );
495    }
496
497    /// Flattening concatenates each element's coefficient bytes in memory order, and
498    /// reconstituting inverts it.
499    #[test]
500    fn flatten_and_reconstitute_round_trip() {
501        let elems: Vec<BinaryField128> = (0..64u128)
502            .map(|i| {
503                BinaryField128::from_repr(i.wrapping_mul(0x9e37_79b9_7f4a_7c15_f39c_c060_5ced_c834))
504            })
505            .collect();
506
507        // Every coefficient position must take a nonzero value somewhere in the fixture,
508        // or a shift that lands in the wrong position could OR in zeros and still round-trip.
509        for pos in 0..16 {
510            assert!(
511                elems.iter().any(|e| {
512                    BasedVectorSpace::<BinaryField8>::as_basis_coefficients_slice(e)[pos]
513                        != BinaryField8::ZERO
514                }),
515                "coefficient position {pos} is never nonzero"
516            );
517        }
518
519        let flat =
520            <BinaryField128 as BasedVectorSpace<BinaryField8>>::flatten_to_base(elems.clone());
521        assert_eq!(flat.len(), elems.len() * 16);
522        for (i, e) in elems.iter().enumerate() {
523            assert_eq!(&flat[i * 16..(i + 1) * 16], e.as_basis_coefficients_slice());
524        }
525
526        let back = <BinaryField128 as BasedVectorSpace<BinaryField8>>::reconstitute_from_base(flat);
527        assert_eq!(back, elems);
528    }
529
530    /// Every byte of every `GF(2^8)` value survives a pack and an unpack.
531    #[test]
532    fn reconstitute_is_exhaustive_over_the_byte_level() {
533        let all: Vec<BinaryField8> = (0..=u8::MAX).map(BinaryField8::from_repr).collect();
534        let packed =
535            <BinaryField16 as BasedVectorSpace<BinaryField8>>::reconstitute_from_base(all.clone());
536        assert_eq!(packed.len(), 128);
537        let flat = <BinaryField16 as BasedVectorSpace<BinaryField8>>::flatten_to_base(packed);
538        assert_eq!(flat, all);
539    }
540
541    /// The embedding is multiplicative and additive, i.e. a ring homomorphism.
542    #[test]
543    fn embedding_is_a_ring_homomorphism() {
544        for a in 0..=u8::MAX {
545            for b in 0..=u8::MAX {
546                let (x, y) = (BinaryField8::from_repr(a), BinaryField8::from_repr(b));
547                assert_eq!(
548                    BinaryField128::from(x * y),
549                    BinaryField128::from(x) * BinaryField128::from(y)
550                );
551                assert_eq!(
552                    BinaryField128::from(x + y),
553                    BinaryField128::from(x) + BinaryField128::from(y)
554                );
555            }
556        }
557        assert_eq!(BinaryField128::from(BinaryField8::ONE), BinaryField128::ONE);
558    }
559}