Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
52 commits
Select commit Hold shift + click to select a range
981b041
Add node-based catamorphism POC
Adam-Vandervorst Jan 9, 2026
949d2d6
Thrashing towards fully functional recursive caching cata.
luketpeterson Jan 21, 2026
54b8daa
Implementing new API structure for recursive cata. No loss in perf s…
luketpeterson Jan 21, 2026
cd6ea5c
Slight tweak to node_recursive_cata for byte node, to give the optimi…
luketpeterson Jan 21, 2026
3fb75fe
Getting rid of dead code
luketpeterson Jan 21, 2026
69bce41
Working through all the cases in pair node (on paper)
luketpeterson Jan 21, 2026
3a8e826
Handling every case in the PairNode for recursive_cata (still no paths)
luketpeterson Jan 22, 2026
42c3587
Adding correct path handling to node_recursive_cata for PairNode
luketpeterson Jan 22, 2026
542c283
Re-adding caching to recursive_cata. Slight perf hit, but it's unavo…
luketpeterson Jan 22, 2026
32973bf
Putting empty-node check back into recursive cata, to avoid reading b…
luketpeterson Jan 22, 2026
ce6a8f8
Filling in remaining node type branches for recursive_cata
luketpeterson Jan 22, 2026
73130ea
Adding recursive_cata_stack_overflow_smoke test
luketpeterson Jan 22, 2026
58ea1f1
Adding a test for recursive cata, and adding the stepping version wit…
luketpeterson Jan 23, 2026
df4e48a
Reorganizing recursive_cata so it can be part of the public API
luketpeterson Jan 23, 2026
2567f33
Adding catamorphism benchmark to put two cata implementations head-to…
luketpeterson Jan 23, 2026
a8b0ee9
Merge branch 'master' into osplit-valcount
luketpeterson Aug 23, 2026
95cdb38
Collapsing recursive_cata_stepping implementation into a trait default
luketpeterson Aug 26, 2026
ee5ce1b
Tweaking the Summarization API to support a cheap(ish) shim for the o…
luketpeterson Aug 26, 2026
c63191a
Adding some more correctness tests for the summarize mechanism
luketpeterson Aug 26, 2026
9699447
Fixing bug in new summarize API caused by not carrying value forward …
luketpeterson Aug 26, 2026
7342db5
Improving documentation for `recursive_cata` callback args
luketpeterson Aug 29, 2026
f0104fa
Renaming closures in Summarization
luketpeterson Aug 29, 2026
640b82b
Adding fallible exit path to recursive cata
luketpeterson Aug 29, 2026
2a3a4f5
Removing COMPUTE_MASKS generic constant in recursive cata, because it…
luketpeterson Aug 29, 2026
d4d3fba
Increasing val_count benchmark sizes
luketpeterson Aug 29, 2026
939a817
Adding iterative zipper-backed implementation of Summarization trait
luketpeterson Aug 31, 2026
c34f509
Adding adaptor for single-function algebra on top of summarization trait
luketpeterson Aug 31, 2026
ec0cf15
Splitting Catamorphism trait in two, where the `_cached` flavors land…
luketpeterson Aug 31, 2026
d9ff9d4
Implementing default into_cata_cached_fallible in terms of into_cata_…
luketpeterson Aug 31, 2026
a4bc2cd
Halving the tax imposed by the compatibility shim between the single-…
luketpeterson Aug 31, 2026
8d826ba
Creating trait with selectable "engines" to dictate whether to use re…
luketpeterson Aug 31, 2026
f5912a9
Getting rid of "into" semantics that take ownership for CatamorphismC…
luketpeterson Sep 1, 2026
39b68ca
Simplifying the multi-trait CatamorphismCached interface. Getting ri…
luketpeterson Sep 1, 2026
c6629cb
Merge branch 'master' into osplit-valcount
luketpeterson Sep 2, 2026
0eb4535
Adding tests and a harness to compare recursive vs iterative catamorp…
luketpeterson Sep 2, 2026
6dea77e
Another more comprehensive recursive cata test
luketpeterson Sep 2, 2026
a122a88
More recusrive cata testing - fixing all_dense_nodes failure with smo…
luketpeterson Sep 2, 2026
ff7c4a4
Fixing fold order in one pair node case
luketpeterson Sep 2, 2026
ac0f07b
Fixing another listnode recursive cata case
luketpeterson Sep 2, 2026
f068141
Fixing another case to the guts of the pairnode recursive cata table …
luketpeterson Sep 2, 2026
aea1813
Partial fix for one of the failures when zipper focus starts in the m…
luketpeterson Sep 2, 2026
33ccd37
Adding `path_assert_len` to ZipperPathBuffer trait
luketpeterson Sep 2, 2026
7fa79f4
Relaxing CatamorphismCached contract to work from the focus, whatever…
luketpeterson Sep 2, 2026
5a252b8
Fixing another recursive cata case in PairNode
luketpeterson Sep 2, 2026
ec13529
Fixing (hopfully last) PairNode recusrive logic edge case
luketpeterson Sep 2, 2026
bdd31ad
Improving documentation around the factored cata, so the agent doesn'…
luketpeterson Sep 2, 2026
657fed1
Fixing test that was based on a misunderstanding of contract
luketpeterson Sep 2, 2026
0b11c23
Updating side-effecting cata behavior to respect the zipper focus, ra…
luketpeterson Sep 2, 2026
c9f8e8a
Cleanups and minor fixes
luketpeterson Sep 2, 2026
45f8791
Adding miri-specific input vectors to the to some of the new tests th…
luketpeterson Sep 2, 2026
67d6224
Moving `val_count` method from ZipperMoving trait to CatamorphismCach…
luketpeterson Sep 2, 2026
3839f31
Improving ergonomics of using `CatamorphismCachedIterative` trait by …
luketpeterson Sep 2, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -117,9 +117,14 @@ harness = false
name = "product_zipper"
harness = false

[[bench]]
name = "catamorphism"
harness = false

[[bench]]
name = "sla"
harness = false
required-features = ["viz"]

[[bench]]
name = "multiplicities"
Expand Down
2 changes: 1 addition & 1 deletion benches/act_paths.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ use divan::{Bencher, Divan, counter::ItemsCount};
use pathmap::PathMap;
use pathmap::arena_compact::{ACTOutputStream, ArenaCompactTree};
use pathmap::paths_serialization::{for_each_deserialized_path, serialize_paths};
use pathmap::zipper::ZipperMoving;
use pathmap::morphisms::CatamorphismCachedIterative;
use rand::{Rng, SeedableRng, rngs::StdRng};
use std::path::Path;

Expand Down
2 changes: 1 addition & 1 deletion benches/binary_keys.rs
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ fn binary_descend_until_max_bytes(bencher: Bencher, n: u64) {
});
}

#[divan::bench(args = [125, 250, 500, 1000, 2000, 4000])]
#[divan::bench(args = [125, 250, 500, 1000, 2000, 4000, 100000])]
fn binary_val_count_bench(bencher: Bencher, n: u64) {

let keys = make_keys(n as usize, 1);
Expand Down
140 changes: 140 additions & 0 deletions benches/catamorphism.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
use divan::{Divan, Bencher, black_box};
use core::convert::Infallible;
use pathmap::alloc::GlobalAlloc;
use pathmap::morphisms::CatamorphismCached;
use pathmap::utils::ByteMask;
use pathmap::utils::ints::gen_int_range;
use pathmap::PathMap;

fn main() {
// Run registered benchmarks.
let divan = Divan::from_args()
.sample_count(4000);

divan.main();
}

fn build_map(count: u64) -> PathMap<()> {
// Dense range of u64 keys encoded as paths; sized to keep benches fast and stable.
gen_int_range::<(), 8, u64>(0, count, 1, ())
}

const MAP_COUNT: u64 = 20_000_000;

// A complete binary trie keeps every internal node as a two-entry LineListNode.
const BINARY_TREE_DEPTH: usize = 18;
const BINARY_TREE_LEAF_COUNT: usize = 1 << BINARY_TREE_DEPTH;

fn build_binary_tree_map() -> PathMap<()> {
let mut map = PathMap::new();
for leaf in 0..BINARY_TREE_LEAF_COUNT {
let mut path = [0u8; BINARY_TREE_DEPTH];
for (level, byte) in path.iter_mut().enumerate() {
*byte = ((leaf >> (BINARY_TREE_DEPTH - level - 1)) & 1) as u8;
}
map.insert(path, ());
}
map
}

#[divan::bench()]
fn factored_cata_jumping_val_count(bencher: Bencher) {
let map = build_map(MAP_COUNT);
let mut sink = 0usize;
bencher.bench_local(|| {
let rz = map.read_zipper();
*black_box(&mut sink) = CatamorphismCached::<(), GlobalAlloc>::factored_cata_jumping::<_, _, Infallible, _, _, _, false>(&rz,
|_| Ok(0usize),
|_mask, w: usize, total| { *total += w; Ok(()) },
|_mask, v, total, _| Ok((v.is_some() as usize) + total.unwrap_or(0)),
).unwrap();
});
assert_eq!(sink, MAP_COUNT as usize);
}

#[divan::bench()]
fn factored_cata_binary_tree_leaf_count(bencher: Bencher) {
let map = build_binary_tree_map();
let mut sink = 0usize;
bencher.bench_local(|| {
let rz = map.read_zipper();
*black_box(&mut sink) = CatamorphismCached::<(), GlobalAlloc>
::factored_cata_jumping::<_, _, Infallible, _, _, _, false>(&rz,
|_| Ok(0usize),
|_mask, child_count: usize, total| {
*total += child_count;
Ok(())
},
|_mask, value, total, _| Ok((value.is_some() as usize) + total.unwrap_or(0)),
)
.unwrap();
});
assert_eq!(sink, BINARY_TREE_LEAF_COUNT);
}

#[divan::bench()]
fn cached_jumping_cata_val_count(bencher: Bencher) {
let map = build_map(MAP_COUNT);
let mut sink = 0usize;
bencher.bench_local(|| {
let rz = map.read_zipper();
*black_box(&mut sink) = CatamorphismCached::<(), GlobalAlloc>::cata_jumping_cached(&rz, |_mask: &ByteMask, children: &mut [usize], val, _sub_path| {
let mut sum: usize = children.iter().sum();
if val.is_some() {
sum += 1;
}
sum
});
});
assert_eq!(sink, MAP_COUNT as usize);
}

#[divan::bench()]
fn factored_cata_jumping_total_len(bencher: Bencher) {
let map = build_map(MAP_COUNT);
let mut sink = (0usize, 0usize);
bencher.bench_local(|| {
let rz = map.read_zipper();
*black_box(&mut sink) = CatamorphismCached::<(), GlobalAlloc>::factored_cata_jumping::<_, _, Infallible, _, _, _, true>(&rz,
|_| Ok((0usize, 0usize)),
|_mask: &ByteMask, w: (usize, usize), acc: &mut (usize, usize)| {
acc.0 += w.0;
// Every folded child hangs below exactly one branch byte. `prefix` accounts
// for compressed runs separately in `summarize_f` below.
acc.1 += w.1 + w.0;
Ok(())
},
|_mask: &ByteMask, val, acc, prefix| {
let (count, total_len) = acc.unwrap_or((0, 0));
let count = count + val.is_some() as usize;
Ok((count, total_len + count * prefix.len()))
},
).unwrap();
});
assert_eq!(sink, (MAP_COUNT as usize, MAP_COUNT as usize * 8));
}

#[divan::bench()]
fn cached_jumping_cata_total_len(bencher: Bencher) {
let map = build_map(MAP_COUNT);
let mut sink = (0usize, 0usize);
bencher.bench_local(|| {
let rz = map.read_zipper();
*black_box(&mut sink) = CatamorphismCached::<(), GlobalAlloc>::cata_jumping_cached(&rz, |mask: &ByteMask, children: &mut [(usize, usize)], val, sub_path| {
let mut count = 0usize;
let mut total_len = 0usize;
let prefix_len = sub_path.len();
if val.is_some() {
count += 1;
total_len += prefix_len;
}
for (_byte, child) in mask.iter().zip(children.iter_mut()) {
count += child.0;
// The child is below one mask byte as well as this callback's prefix.
total_len += child.1 + child.0 * (prefix_len + 1);
}
(count, total_len)
});
});
assert_eq!(sink, (MAP_COUNT as usize, MAP_COUNT as usize * 8));
}
5 changes: 1 addition & 4 deletions benches/cities.rs
Original file line number Diff line number Diff line change
Expand Up @@ -171,10 +171,7 @@ fn cities_val_count(bencher: Bencher) {
#[cfg(feature="arena_compact")]
#[divan::bench()]
fn cities_val_count_act(bencher: Bencher) {
use pathmap::{
arena_compact::ArenaCompactTree,
zipper::ZipperMoving,
};
use pathmap::{morphisms::CatamorphismCachedIterative, arena_compact::ArenaCompactTree};
let pairs = read_data();
let mut map = PathMap::new();
let mut unique_count = 0;
Expand Down
2 changes: 1 addition & 1 deletion benches/multiplicities.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use pathmap::*;
use pathmap::zipper::{ZipperMoving, ZipperWriting};
use pathmap::zipper::{CatamorphismCached, ZipperWriting};

fn main() {
const SILLY_LARGE_COUNTS: bool = false;
Expand Down
3 changes: 1 addition & 2 deletions benches/oeis.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
use std::io::Read;
use std::usize;
use pathmap::PathMap;
use pathmap::zipper::{Zipper, ZipperValues, ZipperMoving, ZipperPath, ZipperWriting, ZipperCreation};
use pathmap::zipper::{Zipper, ZipperValues, ZipperMoving, ZipperPath, ZipperWriting, ZipperCreation, CatamorphismCached};
use num::BigInt;
use divan::{Divan, Bencher, black_box};

Expand Down
8 changes: 4 additions & 4 deletions benches/product_zipper.rs
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ fn val_count_cata<V>(_bm: &ByteMask, vals: &mut[usize], _val: Option<&V>, _path:
fn introspecting_pathmap_pathmap(bencher: Bencher) {
use pathmap::{
PathMap,
morphisms::Catamorphism,
morphisms::CatamorphismSideEffecting,
zipper::{ProductZipper},
};
let mut sink = 0;
Expand All @@ -106,7 +106,7 @@ fn introspecting_pathmap_pathmap(bencher: Bencher) {
fn generic_pathmap_pathmap(bencher: Bencher) {
use pathmap::{
PathMap,
morphisms::Catamorphism,
morphisms::CatamorphismSideEffecting,
zipper::{ProductZipperG},
};
let mut sink = 0;
Expand All @@ -125,7 +125,7 @@ fn generic_act_act(bencher: Bencher) {
use pathmap::{
PathMap,
arena_compact::{ArenaCompactTree},
morphisms::Catamorphism,
morphisms::CatamorphismSideEffecting,
zipper::{ProductZipperG},
};
let mut sink = 0;
Expand All @@ -146,7 +146,7 @@ fn generic_pathmap_act(bencher: Bencher) {
use pathmap::{
PathMap,
arena_compact::{ArenaCompactTree},
morphisms::Catamorphism,
morphisms::CatamorphismSideEffecting,
zipper::{ProductZipperG},
};
let mut sink = 0;
Expand Down
5 changes: 1 addition & 4 deletions benches/shakespeare.rs
Original file line number Diff line number Diff line change
Expand Up @@ -171,10 +171,7 @@ fn shakespeare_sentences_val_count(bencher: Bencher) {
#[cfg(feature="arena_compact")]
#[divan::bench()]
fn shakespeare_sentences_val_count_act(bencher: Bencher) {
use pathmap::{
arena_compact::ArenaCompactTree,
zipper::ZipperMoving,
};
use pathmap::{morphisms::CatamorphismCachedIterative, arena_compact::ArenaCompactTree};
let strings = read_data(false);
let mut map = PathMap::new();
let mut unique_count = 0;
Expand Down
5 changes: 2 additions & 3 deletions benches/sla.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
#![allow(unused)]
use std::hash::{Hasher, Hash};
use std::time::Instant;
use num_traits::Zero;
Expand All @@ -7,7 +6,7 @@ use rand::prelude::StdRng;
use rand::{Rng, SeedableRng};
use rand_distr::Distribution;
use pathmap::*;
use pathmap::morphisms::Catamorphism;
use pathmap::morphisms::CatamorphismCached;
use pathmap::ring::{AlgebraicResult, Lattice};
use pathmap::utils::{BitMask, ByteMask, ints::{indices_to_weave, indices_to_bob}};
use pathmap::zipper::{ReadZipperUntracked, WriteZipperUntracked, Zipper, ZipperMoving, ZipperValues, ZipperWriting};
Expand Down Expand Up @@ -643,4 +642,4 @@ fn main() {

tipover_attention_bob();
tipover_attention_weave(0.02);
}
}
2 changes: 1 addition & 1 deletion benches/sparse_keys.rs
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,7 @@ fn sparse_descend_until_max_bytes(bencher: Bencher, n: u64) {
});
}

#[divan::bench(args = [125, 250, 500, 1000, 2000, 4000])]
#[divan::bench(args = [125, 250, 500, 1000, 2000, 4000, 20_000, 100_000])]
fn sparse_val_count_bench(bencher: Bencher, n: u64) {

let mut r = StdRng::seed_from_u64(1);
Expand Down
9 changes: 3 additions & 6 deletions benches/superdense_keys.rs
Original file line number Diff line number Diff line change
Expand Up @@ -311,7 +311,7 @@ fn from_prefix_key(k: Vec<u8>) -> u64 {
u64::from_le_bytes(buf) & (!0u64 >> shift)
}

#[divan::bench(sample_size = 1, args = [100, 200, 400, 800, 1600, 3200, 20_000])]
#[divan::bench(sample_size = 1, args = [100, 200, 400, 800, 1600, 3200, 20_000, 100_000])]
fn superdense_val_count_bench(bencher: Bencher, n: u64) {

let mut map: PathMap<u64> = PathMap::new();
Expand All @@ -326,12 +326,9 @@ fn superdense_val_count_bench(bencher: Bencher, n: u64) {
}

#[cfg(feature="arena_compact")]
#[divan::bench(sample_size = 1, args = [100, 200, 400, 800, 1600, 3200, 20_000])]
#[divan::bench(sample_size = 1, args = [100, 200, 400, 800, 1600, 3200, 20_000, 100_000])]
fn superdense_val_count_bench_act(bencher: Bencher, n: u64) {
use pathmap::{
arena_compact::ArenaCompactTree,
zipper::ZipperMoving,
};
use pathmap::{morphisms::CatamorphismCachedIterative, arena_compact::ArenaCompactTree};
let mut map: PathMap<u64> = PathMap::new();
for i in 0..n { map.set_val_at(prefix_key(&i), i); }
let act = ArenaCompactTree::from_zipper(map.read_zipper(), |&v| v);
Expand Down
12 changes: 6 additions & 6 deletions pathmap-derive/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -490,12 +490,6 @@ fn derive_poly_zipper_with_traits(
}
}

fn val_count(&self) -> usize {
match self {
#(#variant_arms => inner.val_count(),)*
}
}

fn descend_to<K: AsRef<[u8]>>(&mut self, k: K) {
match self {
#(#variant_arms => inner.descend_to(k),)*
Expand Down Expand Up @@ -653,6 +647,12 @@ fn derive_poly_zipper_with_traits(
impl #impl_generics pathmap::zipper::ZipperPathBuffer for #enum_name #ty_generics
#zipper_path_buffer_where
{
unsafe fn path_assert_len(&self, len: usize) -> &[u8] {
match self {
#(#variant_arms => unsafe { inner.path_assert_len(len) },)*
}
}

unsafe fn origin_path_assert_len(&self, len: usize) -> &[u8] {
match self {
#(#variant_arms => unsafe { inner.origin_path_assert_len(len) },)*
Expand Down
Loading