Skip to main content

sim_lib_serial_core/
permutation.rs

1//! Validated finite ordinal maps and ordered block partitions.
2
3use crate::{BlockPartitionError, OrdinalMapError};
4
5/// A complete bijection from output positions to input positions.
6///
7/// Entry `i` names the input position copied into output position `i`. The
8/// constructor validates the complete finite map before it can be applied.
9#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
10pub struct OrdinalMap {
11    output_to_input: Vec<usize>,
12}
13
14impl OrdinalMap {
15    /// Validates a complete bijection over `0..output_to_input.len()`.
16    pub fn try_new(output_to_input: Vec<usize>) -> Result<Self, OrdinalMapError> {
17        let cardinality = output_to_input.len();
18        let mut first_outputs = vec![None; cardinality];
19        for (output, &input) in output_to_input.iter().enumerate() {
20            if input >= cardinality {
21                return Err(OrdinalMapError::OutOfRange {
22                    output,
23                    input,
24                    cardinality,
25                });
26            }
27            if let Some(first_output) = first_outputs[input].replace(output) {
28                return Err(OrdinalMapError::DuplicateInput {
29                    input,
30                    first_output,
31                    duplicate_output: output,
32                });
33            }
34        }
35        Ok(Self { output_to_input })
36    }
37
38    /// Constructs the identity map of the requested cardinality.
39    pub fn identity(cardinality: usize) -> Self {
40        Self {
41            output_to_input: (0..cardinality).collect(),
42        }
43    }
44
45    /// Constructs a retrograde map of the requested cardinality.
46    pub fn retrograde(cardinality: usize) -> Self {
47        Self {
48            output_to_input: (0..cardinality).rev().collect(),
49        }
50    }
51
52    /// Constructs a left rotation by `steps`, reduced modulo the cardinality.
53    pub fn rotation(cardinality: usize, steps: usize) -> Self {
54        if cardinality == 0 {
55            return Self::identity(0);
56        }
57        let shift = steps % cardinality;
58        Self {
59            output_to_input: (0..cardinality)
60                .map(|output| (output + shift) % cardinality)
61                .collect(),
62        }
63    }
64
65    /// Returns the finite domain cardinality.
66    pub fn cardinality(&self) -> usize {
67        self.output_to_input.len()
68    }
69
70    /// Returns the validated output-to-input ordinal map.
71    pub fn output_to_input(&self) -> &[usize] {
72        &self.output_to_input
73    }
74
75    /// Returns whether this map preserves every position.
76    pub fn is_identity(&self) -> bool {
77        self.output_to_input
78            .iter()
79            .enumerate()
80            .all(|(position, &input)| position == input)
81    }
82
83    /// Applies this map to a slice after checking its cardinality.
84    pub fn apply<T: Clone>(&self, source: &[T]) -> Result<Vec<T>, OrdinalMapError> {
85        if source.len() != self.cardinality() {
86            return Err(OrdinalMapError::CardinalityMismatch {
87                expected: self.cardinality(),
88                found: source.len(),
89            });
90        }
91        self.output_to_input
92            .iter()
93            .enumerate()
94            .map(|(output, &input)| {
95                source
96                    .get(input)
97                    .cloned()
98                    .ok_or(OrdinalMapError::OutOfRange {
99                        output,
100                        input,
101                        cardinality: source.len(),
102                    })
103            })
104            .collect()
105    }
106
107    /// Returns the exact inverse map.
108    pub fn inverse(&self) -> Result<Self, OrdinalMapError> {
109        let mut inverse = vec![0; self.cardinality()];
110        for (output, &input) in self.output_to_input.iter().enumerate() {
111            let Some(slot) = inverse.get_mut(input) else {
112                return Err(OrdinalMapError::OutOfRange {
113                    output,
114                    input,
115                    cardinality: self.cardinality(),
116                });
117            };
118            *slot = output;
119        }
120        Self::try_new(inverse)
121    }
122
123    /// Composes `self` followed by `next` into one canonical map.
124    pub fn compose(&self, next: &Self) -> Result<Self, OrdinalMapError> {
125        if self.cardinality() != next.cardinality() {
126            return Err(OrdinalMapError::CompositionCardinalityMismatch {
127                first: self.cardinality(),
128                second: next.cardinality(),
129            });
130        }
131        let mut composed = Vec::with_capacity(self.cardinality());
132        for (output, &intermediate) in next.output_to_input.iter().enumerate() {
133            let Some(&input) = self.output_to_input.get(intermediate) else {
134                return Err(OrdinalMapError::OutOfRange {
135                    output,
136                    input: intermediate,
137                    cardinality: self.cardinality(),
138                });
139            };
140            composed.push(input);
141        }
142        Self::try_new(composed)
143    }
144
145    /// Returns the deterministic canonical ordinal representation.
146    pub fn canonical_form(&self) -> String {
147        let ordinals = self
148            .output_to_input
149            .iter()
150            .map(usize::to_string)
151            .collect::<Vec<_>>()
152            .join(",");
153        format!("ordinal-map/v1:[{ordinals}]")
154    }
155}
156
157/// Compatibility name emphasizing that an [`OrdinalMap`] is a permutation.
158pub type OrdinalPermutation = OrdinalMap;
159
160/// An ordered, exhaustive partition of source positions into non-empty blocks.
161///
162/// Applying a partition concatenates its blocks in declaration order. This is
163/// a validated structural spelling of an ordinal permutation, not a search or
164/// partition enumerator.
165#[derive(Clone, Debug, PartialEq, Eq)]
166pub struct BlockPartition {
167    cardinality: usize,
168    blocks: Vec<Vec<usize>>,
169    order_map: OrdinalMap,
170}
171
172impl BlockPartition {
173    /// Validates non-empty blocks that cover every source position exactly once.
174    pub fn try_new(
175        cardinality: usize,
176        blocks: Vec<Vec<usize>>,
177    ) -> Result<Self, BlockPartitionError> {
178        let mut flattened = Vec::with_capacity(cardinality);
179        for (block, positions) in blocks.iter().enumerate() {
180            if positions.is_empty() {
181                return Err(BlockPartitionError::EmptyBlock { block });
182            }
183            flattened.extend(positions.iter().copied());
184        }
185        if flattened.len() != cardinality {
186            return Err(BlockPartitionError::CardinalityMismatch {
187                expected: cardinality,
188                found: flattened.len(),
189            });
190        }
191        let order_map = OrdinalMap::try_new(flattened)?;
192        Ok(Self {
193            cardinality,
194            blocks,
195            order_map,
196        })
197    }
198
199    /// Builds contiguous blocks from positive block lengths.
200    pub fn contiguous(block_lengths: Vec<usize>) -> Result<Self, BlockPartitionError> {
201        let mut cardinality = 0usize;
202        let mut blocks = Vec::with_capacity(block_lengths.len());
203        for (block, length) in block_lengths.into_iter().enumerate() {
204            if length == 0 {
205                return Err(BlockPartitionError::EmptyBlock { block });
206            }
207            let end = cardinality
208                .checked_add(length)
209                .ok_or(BlockPartitionError::CardinalityOverflow)?;
210            blocks.push((cardinality..end).collect());
211            cardinality = end;
212        }
213        Self::try_new(cardinality, blocks)
214    }
215
216    /// Returns the number of source positions covered by the partition.
217    pub fn cardinality(&self) -> usize {
218        self.cardinality
219    }
220
221    /// Returns the ordered blocks of source positions.
222    pub fn blocks(&self) -> &[Vec<usize>] {
223        &self.blocks
224    }
225
226    /// Returns the exact linearization map induced by the ordered blocks.
227    pub fn order_map(&self) -> &OrdinalMap {
228        &self.order_map
229    }
230}