1use crate::array::*;
2use crate::category::*;
3
4use core::fmt::Debug;
5use core::ops::{Add, BitOr, Shr};
6use num_traits::{One, Zero};
7
8#[derive(Eq)]
10pub struct FiniteFunction<K: ArrayKind> {
11 pub table: K::Index,
12 pub target: K::I,
13}
14
15impl<K: ArrayKind> PartialEq for FiniteFunction<K> {
17 fn eq(&self, other: &Self) -> bool {
18 self.table == other.table && self.target == other.target
19 }
20}
21
22impl<K: ArrayKind> FiniteFunction<K> {
24 pub fn new(table: K::Index, target: K::I) -> Option<FiniteFunction<K>> {
26 if let Some(true) = table.max().map(|m| m >= target) {
29 return None;
30 }
31 Some(FiniteFunction { table, target })
32 }
33
34 pub fn terminal(a: K::I) -> Self {
37 let table = K::Index::fill(K::I::zero(), a);
38 let target = K::I::one();
39 FiniteFunction { table, target }
40 }
41
42 pub fn constant(a: K::I, x: K::I, b: K::I) -> Self {
69 let table = K::Index::fill(x.clone(), a);
70 let target = x + b + K::I::one(); FiniteFunction { table, target }
72 }
73
74 pub fn inject0(&self, b: K::I) -> FiniteFunction<K> {
86 FiniteFunction {
87 table: self.table.clone(),
88 target: b + self.target(),
89 }
90 }
91
92 pub fn inject1(&self, a: K::I) -> FiniteFunction<K> {
104 FiniteFunction {
105 table: a.clone() + &self.table,
106 target: a + self.target.clone(),
107 }
108 }
109
110 pub fn to_initial(&self) -> FiniteFunction<K> {
112 Self::initial(self.target.clone())
113 }
114
115 pub fn coequalizer(&self, other: &Self) -> Option<FiniteFunction<K>> {
116 if self.source() != other.source() || self.target() != other.target() {
118 return None;
119 }
120
121 let (table, target) =
122 K::Index::connected_components(&self.table, &other.table, self.target());
123 Some(FiniteFunction { table, target })
124 }
125
126 pub fn coequalizer_universal(&self, f: &Self) -> Option<Self>
127 where
128 K::Type<K::I>: Array<K, K::I> + PartialEq,
129 {
130 let table = coequalizer_universal(self, f.table.as_ref())?.into();
131 let target = f.target();
132 Some(FiniteFunction { table, target })
133 }
134
135 pub fn transpose(a: K::I, b: K::I) -> FiniteFunction<K> {
144 if a.is_zero() {
145 return Self::initial(a);
146 }
147
148 let n = b.clone() * a.clone();
149 let i = K::Index::arange(&K::I::zero(), &n);
150 let (q, r) = i.quot_rem(a);
151 FiniteFunction {
152 target: n,
153 table: r.mul_constant_add(b, &q),
155 }
156 }
157
158 pub fn injections(&self, a: &FiniteFunction<K>) -> Option<Self> {
177 let s = self;
178 let p = self.table.cumulative_sum();
179
180 let k = (a >> s)?;
182 let r = k.table.segmented_arange();
183
184 let repeats = k.table;
185 let values = p.gather(a.table.get_range(..));
186 let z = repeats.repeat(values.get_range(..));
187
188 Some(FiniteFunction {
189 table: r + z,
190 target: p.get(p.len() - K::I::one()),
191 })
192 }
193
194 pub fn cumulative_sum(&self) -> Self {
212 let extended_table = self.table.cumulative_sum();
213 let target = extended_table.get(self.source());
214 let table = Array::from_slice(extended_table.get_range(..self.source()));
215 FiniteFunction { table, target }
216 }
217}
218
219impl<K: ArrayKind> FiniteFunction<K>
220where
221 K::Type<K::I>: NaturalArray<K>,
222{
223 pub fn is_injective(&self) -> bool {
225 if self.source().is_zero() {
226 return true;
227 }
228
229 let counts = self.table.bincount(self.target.clone());
230 counts.max().map_or(true, |m| m <= K::I::one())
231 }
232
233 pub fn has_disjoint_image(&self, other: &Self) -> bool {
240 if self.target != other.target {
241 return false;
242 }
243
244 let mut seen = K::Index::fill(K::I::zero(), self.target.clone());
245 if !self.table.is_empty() {
246 seen.scatter_assign_constant(&self.table, K::I::one());
247 }
248
249 let other_seen = seen.gather(other.table.get_range(..));
250 other_seen.max().is_none_or(|m| m < K::I::one())
251 }
252
253 #[cfg(any(feature = "experimental", test))]
258 pub(crate) fn image_complement_injection(&self) -> Option<Self> {
259 let mut marker = K::Index::fill(K::I::zero(), self.target.clone());
260 if !self.table.is_empty() {
261 marker.scatter_assign_constant(&self.table, K::I::one());
262 }
263 let kept_ix = marker.zero();
264 FiniteFunction::new(kept_ix, self.target.clone())
265 }
266
267 #[cfg(any(feature = "experimental", test))]
271 pub(crate) fn canonical_image_injection(&self) -> Option<Self> {
272 let (unique, _) = self.table.sparse_bincount();
273 FiniteFunction::new(unique, self.target.clone())
274 }
275
276 #[cfg(any(feature = "experimental", test))]
281 pub(crate) fn coproduct_many(&self, others: &[&Self]) -> Option<Self> {
282 let target = self.target.clone();
283 for m in others {
284 if m.target != target {
285 return None;
286 }
287 }
288
289 let mut tables = Vec::<&K::Index>::with_capacity(others.len() + 1);
290 tables.push(&self.table);
291 for m in others {
292 tables.push(&m.table);
293 }
294
295 Some(FiniteFunction {
296 table: K::Index::concatenate_many(&tables),
297 target,
298 })
299 }
300
301 #[cfg(any(feature = "experimental", test))]
315 pub(crate) fn inverse_with_fill(&self, fill: K::I) -> Option<Self> {
316 if !self.is_injective() {
317 return None;
318 }
319
320 if self.source().is_zero() {
321 if self.target().is_zero() {
322 return Some(FiniteFunction {
323 table: K::Index::empty(),
324 target: K::I::zero(),
325 });
326 }
327 return None;
328 }
329
330 if fill >= self.source() {
331 return None;
332 }
333
334 let mut inverse = K::Index::fill(fill, self.target());
335 let values = K::Index::arange(&K::I::zero(), &self.source());
336 inverse.scatter_assign(&self.table, values);
337 Some(FiniteFunction {
338 table: inverse,
339 target: self.source(),
340 })
341 }
342
343 #[cfg(any(feature = "experimental", test))]
348 pub(crate) fn factor_through_injective(&self, inj: &Self) -> Self {
349 assert_eq!(
350 self.target(),
351 inj.target(),
352 "factor_through_injective requires parallel maps"
353 );
354 assert!(
355 inj.is_injective(),
356 "factor_through_injective requires an injective map"
357 );
358
359 let values: K::Type<K::I> = K::Index::arange(&K::I::zero(), &inj.source()).into();
361 let inverse: K::Type<K::I> = values.scatter(inj.table.get_range(..), inj.target());
362 let table: K::Index = inverse.gather(self.table.get_range(..)).into();
363 let factored = FiniteFunction {
364 table,
365 target: inj.source(),
366 };
367
368 let recomposed = factored
369 .compose(inj)
370 .expect("factor_through_injective: invalid codomain after factoring");
371 assert!(
372 recomposed == *self,
373 "factor_through_injective requires image(self) subset image(inj)"
374 );
375
376 factored
377 }
378}
379
380pub fn coequalizer_universal<K: ArrayKind, T>(
383 q: &FiniteFunction<K>,
384 f: &K::Type<T>,
385) -> Option<K::Type<T>>
386where
387 K::Type<T>: Array<K, T> + PartialEq,
388{
389 if q.source() != f.len() {
390 return None;
391 }
392
393 let table = f.scatter(q.table.get_range(..), q.target());
395
396 use crate::semifinite::SemifiniteFunction;
399 let u = SemifiniteFunction(table);
400
401 let f_prime = (q >> &u).expect("by construction");
404 if f_prime.0 == *f {
405 Some(u.0)
406 } else {
407 None
408 }
409}
410
411impl<K: ArrayKind> Arrow for FiniteFunction<K> {
412 type Object = K::I;
413
414 fn source(&self) -> Self::Object {
415 self.table.len()
416 }
417
418 fn target(&self) -> Self::Object {
419 self.target.clone()
420 }
421
422 fn identity(a: Self::Object) -> Self {
423 let table = K::Index::arange(&K::I::zero(), &a);
424 let target = a.clone();
425 FiniteFunction { table, target }
426 }
427
428 fn compose(&self, other: &Self) -> Option<Self> {
429 if self.target == other.source() {
430 let table = other.table.gather(self.table.get_range(..));
431 let target = other.target.clone();
432 Some(FiniteFunction { table, target })
433 } else {
434 None
435 }
436 }
437}
438
439impl<K: ArrayKind> Coproduct for FiniteFunction<K> {
440 fn initial_object() -> Self::Object {
441 K::I::zero()
442 }
443
444 fn initial(a: Self::Object) -> Self {
445 Self {
446 table: K::Index::empty(),
447 target: a.clone(),
448 }
449 }
450
451 fn coproduct(&self, other: &Self) -> Option<Self> {
452 if self.target != other.target {
453 return None;
454 }
455
456 Some(Self {
457 table: self.table.concatenate(&other.table),
458 target: self.target.clone(),
459 })
460 }
461
462 fn inj0(a: Self::Object, b: Self::Object) -> Self {
466 let table = K::Index::arange(&K::I::zero(), &a);
467 let target = a.clone() + b.clone();
468 Self { table, target }
469 }
470
471 fn inj1(a: Self::Object, b: Self::Object) -> Self {
485 let target = a.clone() + b.clone();
486 let table = K::Index::arange(&a, &target);
487 Self { table, target }
488 }
489}
490
491impl<K: ArrayKind> Monoidal for FiniteFunction<K> {
492 fn unit() -> Self::Object {
494 K::I::zero()
495 }
496
497 fn tensor(&self, other: &Self) -> Self {
498 let table = self
501 .table
502 .concatenate(&(self.target.clone() + &other.table));
503 let target = self.target.clone() + other.target.clone();
504 Self { table, target }
505 }
506}
507
508impl<K: ArrayKind> SymmetricMonoidal for FiniteFunction<K> {
509 fn twist(a: K::I, b: K::I) -> Self {
510 let target = a.clone() + b.clone();
513 let lhs = K::Index::arange(&b, &target);
514 let rhs = K::Index::arange(&K::I::zero(), &b);
515 let table = lhs.concatenate(&rhs);
516 Self { table, target }
517 }
518}
519
520impl<K: ArrayKind> Shr<&FiniteFunction<K>> for &FiniteFunction<K> {
522 type Output = Option<FiniteFunction<K>>;
523
524 fn shr(self, rhs: &FiniteFunction<K>) -> Option<FiniteFunction<K>> {
525 self.compose(rhs)
526 }
527}
528
529impl<K: ArrayKind> Add<&FiniteFunction<K>> for &FiniteFunction<K> {
531 type Output = Option<FiniteFunction<K>>;
532
533 fn add(self, rhs: &FiniteFunction<K>) -> Option<FiniteFunction<K>> {
534 self.coproduct(rhs)
535 }
536}
537
538impl<K: ArrayKind> BitOr<&FiniteFunction<K>> for &FiniteFunction<K> {
540 type Output = FiniteFunction<K>;
541 fn bitor(self, rhs: &FiniteFunction<K>) -> FiniteFunction<K> {
542 self.tensor(rhs)
543 }
544}
545
546impl<K: ArrayKind> Clone for FiniteFunction<K> {
547 fn clone(&self) -> Self {
548 Self {
549 table: self.table.clone(),
550 target: self.target.clone(),
551 }
552 }
553}
554
555impl<K: ArrayKind> Debug for FiniteFunction<K>
556where
557 K::Index: Debug,
558{
559 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
560 f.debug_struct("FiniteFunction")
561 .field("table", &self.table)
562 .field("target", &self.target)
563 .finish()
564 }
565}
566
567#[cfg(test)]
568mod tests {
569 use super::FiniteFunction;
570 use crate::array::vec::{VecArray, VecKind};
571 use crate::category::Arrow;
572 use proptest::prelude::{Just, Strategy};
573 use proptest::{prop_assert, prop_assert_eq, proptest};
574
575 fn ff(table: Vec<usize>, target: usize) -> FiniteFunction<VecKind> {
576 FiniteFunction::new(VecArray(table), target).expect("valid finite function")
577 }
578
579 fn finite_function_strategy(
580 max_source: usize,
581 max_target: usize,
582 ) -> impl Strategy<Value = FiniteFunction<VecKind>> {
583 (0..=max_source, 0..=max_target).prop_flat_map(|(source, target)| {
584 let source = if target == 0 { 0 } else { source };
585 proptest::collection::vec(0..target, source).prop_map(move |table| ff(table, target))
586 })
587 }
588
589 fn injective_function_strategy(
590 max_source: usize,
591 max_target: usize,
592 ) -> impl Strategy<Value = FiniteFunction<VecKind>> {
593 (0..=max_target).prop_flat_map(move |target| {
594 (0..=core::cmp::min(max_source, target)).prop_flat_map(move |source| {
595 proptest::sample::subsequence((0..target).collect::<Vec<_>>(), source)
596 .prop_map(move |table| ff(table, target))
597 })
598 })
599 }
600
601 fn parallel_maps_strategy(
602 max_maps: usize,
603 max_source: usize,
604 max_target: usize,
605 ) -> impl Strategy<Value = (FiniteFunction<VecKind>, Vec<FiniteFunction<VecKind>>)> {
606 (0..=max_target, 1usize..=max_maps).prop_flat_map(move |(target, n_maps)| {
607 if target == 0 {
608 let base = ff(vec![], 0);
609 let others = vec![ff(vec![], 0); n_maps - 1];
610 Just((base, others)).boxed()
611 } else {
612 proptest::collection::vec(
613 proptest::collection::vec(0..target, 0..=max_source),
614 n_maps,
615 )
616 .prop_map(move |tables| {
617 let mut it = tables.into_iter();
618 let base = ff(it.next().expect("at least one map"), target);
619 let others = it.map(|t| ff(t, target)).collect();
620 (base, others)
621 })
622 .boxed()
623 }
624 })
625 }
626
627 fn factorable_through_injective_strategy(
628 max_source: usize,
629 max_target: usize,
630 ) -> impl Strategy<
631 Value = (
632 FiniteFunction<VecKind>,
633 FiniteFunction<VecKind>,
634 FiniteFunction<VecKind>,
635 ),
636 > {
637 injective_function_strategy(max_source, max_target).prop_flat_map(move |inj| {
638 let b = inj.source();
639 if b == 0 {
640 let g = ff(vec![], 0);
641 let self_map = (&g >> &inj).expect("typed composition");
642 Just((self_map, inj, g)).boxed()
643 } else {
644 proptest::collection::vec(0..b, 0..=max_source)
645 .prop_map(move |table| {
646 let g = ff(table, b);
647 let self_map = (&g >> &inj).expect("typed composition");
648 (self_map, inj.clone(), g)
649 })
650 .boxed()
651 }
652 })
653 }
654
655 proptest! {
656 #[test]
657 fn canonical_image_injection_characterizes_image(f in finite_function_strategy(8, 8)) {
658 let i = f.canonical_image_injection().expect("always valid");
659 prop_assert!(i.is_injective());
660 prop_assert_eq!(i.target(), f.target());
661
662 let mut used = vec![false; f.target()];
663 for &x in &f.table.0 {
664 used[x] = true;
665 }
666
667 let mut seen = vec![false; f.target()];
668 for &x in &i.table.0 {
669 prop_assert!(used[x]);
670 prop_assert!(!seen[x]);
671 seen[x] = true;
672 }
673 prop_assert_eq!(used.clone(), seen);
674 prop_assert_eq!(i.source(), used.iter().filter(|&&b| b).count());
675 }
676
677 #[test]
678 fn has_disjoint_image_matches_set_disjointness(
679 f in finite_function_strategy(8, 8),
680 g in finite_function_strategy(8, 8),
681 ) {
682 if f.target() != g.target() {
683 prop_assert!(!f.has_disjoint_image(&g));
684 return Ok(());
685 }
686
687 let mut seen_f = vec![false; f.target()];
688 for &x in &f.table.0 {
689 seen_f[x] = true;
690 }
691 let mut seen_g = vec![false; g.target()];
692 for &x in &g.table.0 {
693 seen_g[x] = true;
694 }
695
696 let expected = (0..f.target()).all(|i| !(seen_f[i] && seen_g[i]));
697 prop_assert_eq!(f.has_disjoint_image(&g), expected);
698 }
699
700 #[test]
701 fn image_complement_injection_is_exact_complement(f in finite_function_strategy(8, 8)) {
702 let k = f.image_complement_injection().expect("always valid");
703 prop_assert!(k.is_injective());
704 prop_assert_eq!(k.target(), f.target());
705
706 let mut used = vec![false; f.target()];
707 for &x in &f.table.0 {
708 used[x] = true;
709 }
710
711 let mut seen = vec![false; f.target()];
712 for &x in &k.table.0 {
713 prop_assert!(!used[x]);
714 prop_assert!(!seen[x]);
715 seen[x] = true;
716 }
717
718 for i in 0..f.target() {
719 prop_assert_eq!(seen[i], !used[i]);
720 }
721
722 for i in 0..f.target() {
724 prop_assert!(!(used[i] && seen[i]));
725 }
726 }
727
728 #[test]
729 fn coproduct_many_matches_iterated_coproduct((f0, others) in parallel_maps_strategy(4, 6, 8)) {
730 let refs: Vec<&FiniteFunction<VecKind>> = others.iter().collect();
731 let actual = f0.coproduct_many(&refs).expect("parallel maps");
732 let expected = others.iter().fold(f0.clone(), |acc, m| {
733 (&acc + m).expect("parallel maps")
734 });
735 prop_assert_eq!(actual, expected);
736 }
737
738 #[test]
739 fn inverse_with_fill_left_inverts_injective_maps(
740 f in injective_function_strategy(8, 8),
741 fill in 0usize..8,
742 ) {
743 if f.source() == 0 {
744 prop_assert_eq!(f.inverse_with_fill(fill), if f.target() == 0 { Some(ff(vec![], 0)) } else { None });
745 } else if fill < f.source() {
746 let inv = f.inverse_with_fill(fill).expect("valid inverse");
747 prop_assert_eq!(&f >> &inv, Some(FiniteFunction::<VecKind>::identity(f.source())));
748
749 let mut preimage = vec![None; f.target()];
750 for (i, &y) in f.table.0.iter().enumerate() {
751 preimage[y] = Some(i);
752 }
753 for (y, &x) in inv.table.0.iter().enumerate() {
754 match preimage[y] {
755 Some(i) => prop_assert_eq!(x, i),
756 None => prop_assert_eq!(x, fill),
757 }
758 }
759 } else {
760 prop_assert_eq!(f.inverse_with_fill(fill), None);
761 }
762 }
763
764 #[test]
765 fn factor_through_injective_recovers_original_factor(
766 (self_map, inj, g) in factorable_through_injective_strategy(8, 8),
767 ) {
768 let factored = self_map.factor_through_injective(&inj);
769
770 prop_assert_eq!(factored.clone(), g);
771 prop_assert_eq!(&factored >> &inj, Some(self_map));
772 }
773 }
774
775 #[test]
776 fn coproduct_many_returns_none_on_target_mismatch() {
777 let f = ff(vec![0, 1], 3);
778 let g = ff(vec![0], 2);
779 assert!(f.coproduct_many(&[&g]).is_none());
780 }
781
782 #[test]
783 fn inverse_with_fill_rejects_non_injective_map() {
784 let f = ff(vec![0, 0], 2);
785 assert_eq!(f.inverse_with_fill(0), None);
786 }
787}