1use crate::alphabet::{validate_alphabet, validate_stable_id};
4use crate::{AggregateRuleError, AlphabetId, SerialAlphabet};
5use std::collections::{BTreeMap, BTreeSet};
6use std::fmt::{Display, Formatter};
7
8pub type SymbolCount<S> = (S, usize);
10
11#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
13pub struct ProjectionId(String);
14
15impl ProjectionId {
16 pub fn try_new(value: impl Into<String>) -> Result<Self, AggregateRuleError> {
18 let value = value.into();
19 validate_stable_id(&value).map_err(|reason| AggregateRuleError::InvalidProjectionId {
20 value: value.clone(),
21 reason,
22 })?;
23 Ok(Self(value))
24 }
25
26 pub fn as_str(&self) -> &str {
28 &self.0
29 }
30}
31
32impl Display for ProjectionId {
33 fn fmt(&self, formatter: &mut Formatter<'_>) -> std::fmt::Result {
34 Display::fmt(&self.0, formatter)
35 }
36}
37
38#[derive(Clone, Debug, PartialEq, Eq)]
40pub struct ProjectedClassSpec<S> {
41 pub id: ProjectionId,
43 pub symbols: Vec<S>,
45 pub multiplicity: usize,
47}
48
49impl<S> ProjectedClassSpec<S> {
50 pub fn new(id: ProjectionId, symbols: Vec<S>, multiplicity: usize) -> Self {
52 Self {
53 id,
54 symbols,
55 multiplicity,
56 }
57 }
58}
59
60#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
62pub enum AggregateRuleKind {
63 ExhaustiveExactlyOnce,
65 NoRepeat,
67 DeclaredMultiplicity,
69 DeclaredOmissions,
71 ProjectedAggregate,
73 FreeOrder,
75}
76
77#[derive(Clone, Debug, PartialEq, Eq)]
80pub enum AggregateRule {
81 ExhaustiveExactlyOnce,
83 NoRepeat,
85 DeclaredMultiplicity(DeclaredCounts),
87 DeclaredOmissions(DeclaredCounts),
89 ProjectedAggregate(ProjectedRule),
91 FreeOrder,
93}
94
95#[derive(Clone, Debug, PartialEq, Eq)]
97pub struct DeclaredCounts {
98 alphabet_id: AlphabetId,
99 expected: Vec<usize>,
100}
101
102#[derive(Clone, Debug, PartialEq, Eq)]
104pub struct ProjectedClassRule {
105 id: ProjectionId,
106 members: Vec<usize>,
107 multiplicity: usize,
108}
109
110#[derive(Clone, Debug, PartialEq, Eq)]
112pub struct ProjectedRule {
113 alphabet_id: AlphabetId,
114 cardinality: usize,
115 classes: Vec<ProjectedClassRule>,
116 class_by_position: Vec<usize>,
117}
118
119impl AggregateRule {
120 pub const fn exhaustive_exactly_once() -> Self {
122 Self::ExhaustiveExactlyOnce
123 }
124
125 pub const fn no_repeat() -> Self {
127 Self::NoRepeat
128 }
129
130 pub const fn free_order() -> Self {
132 Self::FreeOrder
133 }
134
135 pub fn declared_multiplicity<A, I>(
137 alphabet: &A,
138 declarations: I,
139 ) -> Result<Self, AggregateRuleError>
140 where
141 A: SerialAlphabet,
142 I: IntoIterator<Item = (A::Symbol, usize)>,
143 {
144 let positions = validate_alphabet(alphabet)?;
145 let mut expected = vec![None; alphabet.symbols().len()];
146 for (symbol, multiplicity) in declarations {
147 let Some(&position) = positions.get(&symbol) else {
148 return Err(AggregateRuleError::ForeignSymbol {
149 alphabet_id: alphabet.id().clone(),
150 });
151 };
152 if expected[position].is_some() {
153 return Err(AggregateRuleError::DuplicateDeclaration { position });
154 }
155 if multiplicity == 0 {
156 return Err(AggregateRuleError::ZeroMultiplicity { position });
157 }
158 expected[position] = Some(multiplicity);
159 }
160 let expected = expected
161 .into_iter()
162 .enumerate()
163 .map(|(position, count)| {
164 count.ok_or(AggregateRuleError::MissingDeclaration { position })
165 })
166 .collect::<Result<Vec<_>, _>>()?;
167 checked_total(&expected)?;
168 Ok(Self::DeclaredMultiplicity(DeclaredCounts {
169 alphabet_id: alphabet.id().clone(),
170 expected,
171 }))
172 }
173
174 pub fn declared_omissions<A, I>(alphabet: &A, omissions: I) -> Result<Self, AggregateRuleError>
176 where
177 A: SerialAlphabet,
178 I: IntoIterator<Item = A::Symbol>,
179 {
180 let positions = validate_alphabet(alphabet)?;
181 let mut expected = vec![1; alphabet.symbols().len()];
182 let mut omitted = BTreeSet::new();
183 for symbol in omissions {
184 let Some(&position) = positions.get(&symbol) else {
185 return Err(AggregateRuleError::ForeignSymbol {
186 alphabet_id: alphabet.id().clone(),
187 });
188 };
189 if !omitted.insert(position) {
190 return Err(AggregateRuleError::DuplicateDeclaration { position });
191 }
192 expected[position] = 0;
193 }
194 if omitted.is_empty() {
195 return Err(AggregateRuleError::NoOmissions);
196 }
197 if omitted.len() == alphabet.symbols().len() {
198 return Err(AggregateRuleError::OmitsEverything(alphabet.id().clone()));
199 }
200 Ok(Self::DeclaredOmissions(DeclaredCounts {
201 alphabet_id: alphabet.id().clone(),
202 expected,
203 }))
204 }
205
206 pub fn projected_aggregate<A, I>(alphabet: &A, classes: I) -> Result<Self, AggregateRuleError>
208 where
209 A: SerialAlphabet,
210 I: IntoIterator<Item = ProjectedClassSpec<A::Symbol>>,
211 {
212 let positions = validate_alphabet(alphabet)?;
213 let mut class_ids = BTreeSet::new();
214 let mut class_by_position = vec![None; alphabet.symbols().len()];
215 let mut compiled = Vec::new();
216 let mut total = 0usize;
217 for spec in classes {
218 if !class_ids.insert(spec.id.clone()) {
219 return Err(AggregateRuleError::DuplicateProjectionId(spec.id));
220 }
221 if spec.symbols.is_empty() {
222 return Err(AggregateRuleError::EmptyProjectionClass(spec.id));
223 }
224 let class_index = compiled.len();
225 let mut members = Vec::with_capacity(spec.symbols.len());
226 for symbol in spec.symbols {
227 let Some(&position) = positions.get(&symbol) else {
228 return Err(AggregateRuleError::ForeignSymbol {
229 alphabet_id: alphabet.id().clone(),
230 });
231 };
232 if class_by_position[position].replace(class_index).is_some() {
233 return Err(AggregateRuleError::DuplicateProjectionMember { position });
234 }
235 members.push(position);
236 }
237 total = total
238 .checked_add(spec.multiplicity)
239 .ok_or(AggregateRuleError::MultiplicityOverflow)?;
240 compiled.push(ProjectedClassRule {
241 id: spec.id,
242 members,
243 multiplicity: spec.multiplicity,
244 });
245 }
246 for (position, class) in class_by_position.iter().enumerate() {
247 if class.is_none() {
248 return Err(AggregateRuleError::MissingProjectionMember { position });
249 }
250 }
251 if total == 0 {
252 return Err(AggregateRuleError::OmitsEverything(alphabet.id().clone()));
253 }
254 Ok(Self::ProjectedAggregate(ProjectedRule {
255 alphabet_id: alphabet.id().clone(),
256 cardinality: alphabet.symbols().len(),
257 classes: compiled,
258 class_by_position: class_by_position.into_iter().flatten().collect(),
259 }))
260 }
261
262 pub const fn kind(&self) -> AggregateRuleKind {
264 match self {
265 Self::ExhaustiveExactlyOnce => AggregateRuleKind::ExhaustiveExactlyOnce,
266 Self::NoRepeat => AggregateRuleKind::NoRepeat,
267 Self::DeclaredMultiplicity(_) => AggregateRuleKind::DeclaredMultiplicity,
268 Self::DeclaredOmissions(_) => AggregateRuleKind::DeclaredOmissions,
269 Self::ProjectedAggregate(_) => AggregateRuleKind::ProjectedAggregate,
270 Self::FreeOrder => AggregateRuleKind::FreeOrder,
271 }
272 }
273
274 pub fn declared_counts<A>(
276 &self,
277 alphabet: &A,
278 ) -> Result<Option<Vec<SymbolCount<A::Symbol>>>, AggregateRuleError>
279 where
280 A: SerialAlphabet,
281 {
282 let counts = match self {
283 Self::DeclaredMultiplicity(counts) | Self::DeclaredOmissions(counts) => counts,
284 _ => return Ok(None),
285 };
286 counts.validate_for(alphabet)?;
287 Ok(Some(
288 alphabet
289 .symbols()
290 .iter()
291 .cloned()
292 .zip(counts.expected.iter().copied())
293 .collect(),
294 ))
295 }
296
297 pub fn projected_classes<A>(
299 &self,
300 alphabet: &A,
301 ) -> Result<Option<Vec<ProjectedClassSpec<A::Symbol>>>, AggregateRuleError>
302 where
303 A: SerialAlphabet,
304 {
305 let Self::ProjectedAggregate(rule) = self else {
306 return Ok(None);
307 };
308 rule.validate_for(alphabet)?;
309 Ok(Some(
310 rule.classes
311 .iter()
312 .map(|class| {
313 ProjectedClassSpec::new(
314 class.id.clone(),
315 class
316 .members
317 .iter()
318 .map(|&position| alphabet.symbols()[position].clone())
319 .collect(),
320 class.multiplicity,
321 )
322 })
323 .collect(),
324 ))
325 }
326
327 pub(crate) fn declared(&self) -> Option<&DeclaredCounts> {
328 match self {
329 Self::DeclaredMultiplicity(counts) | Self::DeclaredOmissions(counts) => Some(counts),
330 _ => None,
331 }
332 }
333
334 pub(crate) fn projected(&self) -> Option<&ProjectedRule> {
335 match self {
336 Self::ProjectedAggregate(rule) => Some(rule),
337 _ => None,
338 }
339 }
340}
341
342impl DeclaredCounts {
343 pub(crate) fn validate_for<A: SerialAlphabet>(
344 &self,
345 alphabet: &A,
346 ) -> Result<(), AggregateRuleError> {
347 validate_binding(&self.alphabet_id, self.expected.len(), alphabet)
348 }
349
350 pub(crate) fn expected(&self) -> &[usize] {
351 &self.expected
352 }
353}
354
355impl ProjectedRule {
356 pub(crate) fn validate_for<A: SerialAlphabet>(
357 &self,
358 alphabet: &A,
359 ) -> Result<(), AggregateRuleError> {
360 validate_binding(&self.alphabet_id, self.cardinality, alphabet)
361 }
362
363 pub(crate) fn required_len(&self) -> Result<usize, AggregateRuleError> {
364 checked_total(
365 &self
366 .classes
367 .iter()
368 .map(|class| class.multiplicity)
369 .collect::<Vec<_>>(),
370 )
371 }
372
373 pub(crate) fn class_by_position(&self) -> &[usize] {
374 &self.class_by_position
375 }
376
377 pub(crate) fn classes(&self) -> &[ProjectedClassRule] {
378 &self.classes
379 }
380}
381
382impl ProjectedClassRule {
383 pub(crate) fn id(&self) -> &ProjectionId {
384 &self.id
385 }
386
387 pub(crate) fn multiplicity(&self) -> usize {
388 self.multiplicity
389 }
390}
391
392#[derive(Clone, Debug, PartialEq, Eq)]
394pub struct ProjectedClassEvidence {
395 pub id: ProjectionId,
397 pub expected: usize,
399 pub observed: usize,
401}
402
403#[derive(Clone, Debug, PartialEq, Eq)]
405pub struct AggregateLedger<S>
406where
407 S: Clone + Eq + Ord + std::fmt::Debug,
408{
409 pub(crate) alphabet_id: AlphabetId,
410 pub(crate) rule: AggregateRuleKind,
411 pub(crate) series_len: usize,
412 pub(crate) observed: BTreeMap<S, usize>,
413 pub(crate) expected: Option<BTreeMap<S, usize>>,
414 pub(crate) omitted: Vec<S>,
415 pub(crate) repeated: Vec<S>,
416 pub(crate) projected: Vec<ProjectedClassEvidence>,
417}
418
419impl<S> AggregateLedger<S>
420where
421 S: Clone + Eq + Ord + std::fmt::Debug,
422{
423 pub fn alphabet_id(&self) -> &AlphabetId {
425 &self.alphabet_id
426 }
427
428 pub fn rule(&self) -> AggregateRuleKind {
430 self.rule
431 }
432
433 pub fn series_len(&self) -> usize {
435 self.series_len
436 }
437
438 pub fn observed_count(&self, symbol: &S) -> Option<usize> {
440 self.observed.get(symbol).copied()
441 }
442
443 pub fn expected_count(&self, symbol: &S) -> Option<usize> {
445 self.expected
446 .as_ref()
447 .and_then(|counts| counts.get(symbol).copied())
448 }
449
450 pub fn omitted_symbols(&self) -> &[S] {
452 &self.omitted
453 }
454
455 pub fn repeated_symbols(&self) -> &[S] {
457 &self.repeated
458 }
459
460 pub fn projected_classes(&self) -> &[ProjectedClassEvidence] {
462 &self.projected
463 }
464
465 pub fn is_exhaustive_exactly_once(&self) -> bool {
467 self.omitted.is_empty()
468 && self.repeated.is_empty()
469 && self.observed.values().all(|count| *count == 1)
470 }
471}
472
473fn validate_binding<A: SerialAlphabet>(
474 rule_id: &AlphabetId,
475 cardinality: usize,
476 alphabet: &A,
477) -> Result<(), AggregateRuleError> {
478 validate_alphabet(alphabet)?;
479 if rule_id != alphabet.id() {
480 return Err(AggregateRuleError::AlphabetMismatch {
481 rule_id: rule_id.clone(),
482 series_id: alphabet.id().clone(),
483 });
484 }
485 if cardinality != alphabet.symbols().len() {
486 return Err(AggregateRuleError::CardinalityMismatch {
487 expected: cardinality,
488 found: alphabet.symbols().len(),
489 });
490 }
491 Ok(())
492}
493
494fn checked_total(counts: &[usize]) -> Result<usize, AggregateRuleError> {
495 counts.iter().try_fold(0usize, |total, &count| {
496 total
497 .checked_add(count)
498 .ok_or(AggregateRuleError::MultiplicityOverflow)
499 })
500}