Skip to main content

clay_codes/
coords.rs

1//! Coordinate system helpers for Clay codes
2//!
3//! Clay codes use a 3D coordinate system (x, y, z) where:
4//! - x: position within y-section (0 to q-1)
5//! - y: y-section index (0 to t-1)
6//! - z: layer/plane index (0 to α-1)
7//!
8//! Key concepts:
9//! - **y-section**: Nodes with the same y-coordinate
10//! - **Companion layer**: For vertex (x, y, z), its companion is at layer z_sw
11//! - **Intersection Score (IS)**: Count of erased "red" vertices in a layer
12//! - **Plane vector**: The z-coordinates that make up layer z (base-q representation)
13
14use std::collections::BTreeSet;
15
16/// Get the companion (swap) layer for a given vertex
17///
18/// For a vertex at (x, y, z), the companion layer z_sw is computed by
19/// swapping x with z_y (the y-th digit of z in base q).
20///
21/// # Arguments
22/// * `z` - Current layer index
23/// * `x` - x-coordinate within y-section
24/// * `y` - y-section index
25/// * `z_y` - The y-th digit of z in base q (from plane_vector)
26/// * `q` - Coupling factor (d - k + 1)
27///
28/// # Returns
29/// The companion layer index z_sw
30pub fn get_companion_layer(z: usize, x: usize, y: usize, z_y: usize, q: usize) -> usize {
31    if x == z_y {
32        // Unpaired (red) vertex - companion is itself
33        return z;
34    }
35
36    // Compute z_sw by swapping x with z_y at position y
37    // z_sw = z - z_y * q^y + x * q^y
38    let q_pow_y = q.pow(y as u32);
39    z - z_y * q_pow_y + x * q_pow_y
40}
41
42/// Get the plane (layer) vector for a given z
43///
44/// Converts z to base-q representation, giving the z_y value for each y-section.
45/// This is used to identify "red" (unpaired) vertices in each layer.
46///
47/// The representation stores most significant digit at index 0:
48/// - z_vec[0] = coefficient for q^(t-1) (MSB)
49/// - z_vec[t-1] = coefficient for q^0 (LSB)
50///
51/// # Arguments
52/// * `z` - Layer index
53/// * `t` - Number of y-sections
54/// * `q` - Base (coupling factor)
55///
56/// # Returns
57/// Vector where element y contains the coefficient for q^(t-1-y)
58pub fn get_plane_vector(z: usize, t: usize, q: usize) -> Vec<usize> {
59    let mut result = vec![0usize; t];
60    let mut remaining = z;
61
62    for i in 0..t {
63        result[t - 1 - i] = remaining % q;
64        remaining /= q;
65    }
66
67    result
68}
69
70/// Get the maximum intersection score for a set of erased chunks
71///
72/// The intersection score (IS) of a layer is the count of erased vertices
73/// that are "red" (unpaired) in that layer.
74///
75/// # Arguments
76/// * `erased_chunks` - Set of erased chunk indices
77/// * `t` - Number of y-sections
78/// * `q` - Coupling factor
79/// * `sub_chunk_no` - Total number of sub-chunks (α = q^t)
80///
81/// # Returns
82/// Maximum IS across all layers
83pub fn get_max_iscore(erased_chunks: &BTreeSet<usize>, t: usize, q: usize, sub_chunk_no: usize) -> usize {
84    let mut max_iscore = 0;
85
86    for z in 0..sub_chunk_no {
87        let plane_vec = get_plane_vector(z, t, q);
88        let mut iscore = 0;
89
90        for &erased in erased_chunks {
91            let y = erased / q;
92            let x = erased % q;
93            if y < t && plane_vec[y] == x {
94                iscore += 1;
95            }
96        }
97
98        max_iscore = max_iscore.max(iscore);
99    }
100
101    max_iscore
102}
103
104/// Set the decoding order for planes based on intersection score
105///
106/// Planes (layers) are processed in order of increasing intersection score.
107/// This ensures that we can always recover the necessary U values for each layer.
108///
109/// # Arguments
110/// * `order` - Output array to fill with layer indices in processing order
111/// * `erasures` - Set of erased chunk indices
112/// * `t` - Number of y-sections
113/// * `q` - Coupling factor
114/// * `sub_chunk_no` - Total number of sub-chunks (α)
115pub fn set_planes_sequential_decoding_order(
116    order: &mut [usize],
117    erasures: &BTreeSet<usize>,
118    t: usize,
119    q: usize,
120    sub_chunk_no: usize,
121) {
122    // Calculate IS for each plane and sort by IS
123    let mut planes_with_is: Vec<(usize, usize)> = (0..sub_chunk_no)
124        .map(|z| {
125            let plane_vec = get_plane_vector(z, t, q);
126            let is = erasures.iter().filter(|&&e| {
127                let y = e / q;
128                let x = e % q;
129                y < t && plane_vec[y] == x
130            }).count();
131            (z, is)
132        })
133        .collect();
134
135    // Sort by IS (ascending), then by z for stability
136    planes_with_is.sort_by_key(|&(z, is)| (is, z));
137
138    // Fill order array
139    for (i, (z, _)) in planes_with_is.iter().enumerate() {
140        if i < order.len() {
141            order[i] = *z;
142        }
143    }
144}
145
146/// Check if a vertex is unpaired (red) in its layer
147///
148/// A vertex (x, y, z) is unpaired if x == z_y, where z_y is the y-th digit of z in base q.
149///
150/// # Arguments
151/// * `x` - x-coordinate
152/// * `y` - y-section
153/// * `z` - layer index
154/// * `q` - coupling factor
155///
156/// # Returns
157/// true if the vertex is unpaired (red)
158#[inline]
159pub fn is_unpaired(x: usize, y: usize, z: usize, q: usize) -> bool {
160    let z_y = (z / q.pow(y as u32)) % q;
161    x == z_y
162}
163
164/// Get the z_y value (y-th digit of z in base q)
165#[inline]
166pub fn get_z_y(z: usize, y: usize, q: usize) -> usize {
167    (z / q.pow(y as u32)) % q
168}
169
170/// Convert node index to (x, y) coordinates
171#[inline]
172pub fn node_to_xy(node: usize, q: usize) -> (usize, usize) {
173    (node % q, node / q)
174}
175
176/// Convert (x, y) coordinates to node index
177#[inline]
178pub fn xy_to_node(x: usize, y: usize, q: usize) -> usize {
179    y * q + x
180}
181
182#[cfg(test)]
183mod tests {
184    use super::*;
185
186    #[test]
187    fn test_plane_vector() {
188        // For q=2, t=2 (MSB at index 0, LSB at index t-1):
189        // z=0 (binary 00) -> [0,0]
190        // z=1 (binary 01) -> [0,1]
191        // z=2 (binary 10) -> [1,0]
192        // z=3 (binary 11) -> [1,1]
193        assert_eq!(get_plane_vector(0, 2, 2), vec![0, 0]);
194        assert_eq!(get_plane_vector(1, 2, 2), vec![0, 1]);
195        assert_eq!(get_plane_vector(2, 2, 2), vec![1, 0]);
196        assert_eq!(get_plane_vector(3, 2, 2), vec![1, 1]);
197
198        // For q=3, t=2: z=5 = 1*3 + 2 -> [1,2] (MSB=1, LSB=2)
199        assert_eq!(get_plane_vector(5, 2, 3), vec![1, 2]);
200    }
201
202    #[test]
203    fn test_companion_layer() {
204        let q = 2;
205
206        // Test case from paper: z=2 (plane_vec=[1,0]), x=1, y=0
207        // z_y = 0, x != z_y, so companion exists
208        // z_sw = 2 - 0*1 + 1*1 = 3
209        let z_y = get_z_y(2, 0, q);
210        assert_eq!(z_y, 0);
211        assert_eq!(get_companion_layer(2, 1, 0, z_y, q), 3);
212
213        // Unpaired case: z=3, x=1, y=0, z_y=1, x==z_y
214        let z_y = get_z_y(3, 0, q);
215        assert_eq!(z_y, 1);
216        assert_eq!(get_companion_layer(3, 1, 0, z_y, q), 3); // Returns self
217    }
218
219    #[test]
220    fn test_is_unpaired() {
221        let q = 2;
222        // z=0: plane_vec=[0,0], unpaired at x=0 for y=0 and y=1
223        assert!(is_unpaired(0, 0, 0, q));
224        assert!(is_unpaired(0, 1, 0, q));
225        assert!(!is_unpaired(1, 0, 0, q));
226
227        // z=3: plane_vec=[1,1], unpaired at x=1 for y=0 and y=1
228        assert!(is_unpaired(1, 0, 3, q));
229        assert!(is_unpaired(1, 1, 3, q));
230        assert!(!is_unpaired(0, 0, 3, q));
231    }
232
233    #[test]
234    fn test_node_conversion() {
235        let q = 3;
236        assert_eq!(node_to_xy(0, q), (0, 0));
237        assert_eq!(node_to_xy(5, q), (2, 1));
238        assert_eq!(xy_to_node(2, 1, q), 5);
239    }
240}