Repository navigation
[feat] An alternative iterator implementation which owns the BDD. - #27
Conversation
WalkthroughThis pull request introduces an iterator-based API for traversing satisfying valuations of a BDD in lexicographic order. It adds a public Changes
Sequence DiagramsequenceDiagram
participant Client
participant Bdd
participant ValuationsIterator
participant PathFinding as Path Finding Logic
Client->>Bdd: iter_satisfying_valuations(min, max)
Bdd->>Bdd: upcast to Bdd64 if needed
Bdd->>ValuationsIterator: new(bdd64, min, max)
ValuationsIterator->>PathFinding: find_first_path()
PathFinding-->>ValuationsIterator: initial path
loop next() calls
Client->>ValuationsIterator: next()
alt path exists
ValuationsIterator->>ValuationsIterator: construct BTreeMap from path
ValuationsIterator->>PathFinding: find_next_path()
PathFinding-->>ValuationsIterator: advanced path
ValuationsIterator-->>Client: Some(valuation)
else no more paths
ValuationsIterator-->>Client: None
end
end
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes
Poem
Pre-merge checks and finishing touches✅ Passed checks (3 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
f1c0be4 to
7d9d095
Compare
There was a problem hiding this comment.
Actionable comments posted: 0
🧹 Nitpick comments (5)
src/split/bdd.rs (1)
796-836: Consider consolidating duplicate conversion logic.The
bdd_16_to_64andbdd_32_to_64functions are nearly identical, differing only in their input types. While the existingimpl_unchecked_from!macro handles the opposite direction (downcasting), you could reduce duplication here.One approach using a generic helper:
+fn bdd_to_64<TNodeId, TVarId>(bdd: &BddImpl<TNodeId, TVarId>) -> Bdd64 +where + TNodeId: NodeIdAny + Into<NodeId64>, + TVarId: VarIdPackedAny + Into<VarIdPacked64>, +{ + let new_nodes = bdd.nodes.iter().map(|x| { + if x.is_zero() { + BddNodeImpl::zero() + } else if x.is_one() { + BddNodeImpl::one() + } else { + BddNodeImpl::new(x.variable.into(), x.low.into(), x.high.into()) + } + }).collect(); + Bdd64 { + root: bdd.root.into(), + nodes: new_nodes, + } +} + -fn bdd_16_to_64(bdd: &Bdd16) -> Bdd64 { ... } -fn bdd_32_to_64(bdd: &Bdd32) -> Bdd64 { ... }This would require the appropriate
Intotrait bounds to be satisfied, which may need verification.src/split/bdd_iterator.rs (4)
7-15: Consider documenting whyPositiveNodestores the unused node ID.The
#[allow(dead_code)]on line 10 indicates the second field inPositiveNodeis never read, whileNegativeNodeuses its node ID to look up the high link when advancing. A brief comment explaining this asymmetry would help future maintainers understand the design choice (e.g., keeping variants structurally consistent, or for potential future use).
68-81: The TODO on line 71 flags an important validation gap.If
max_variableis less than the largest variable in the BDD, the iterator could produce valuations that don't include all BDD variables. Theextend_pathloop exits whennext_var > self.max_variable, potentially leaving BDD decisions unresolved.Consider adding validation (as noted in the TODO) or documenting this as a precondition. The
Bdd::iter_satisfying_valuationsdoc comment says variables must be within bounds, but without enforcement, misuse could silently produce incorrect results.Would you like me to suggest validation logic that checks the BDD's used variables against the provided range?
378-382: Update stale test comments.These comments describe previous buggy behavior ("Current: Panics...", "Current: Returns 3 valuations...") that appears to be fixed now since the tests pass. Consider updating or removing these comments to avoid confusion.
#[test] fn test_single_literal_with_free_variables() { - // Issue: When a literal BDD has free variables in the range that lead to terminal nodes, - // the iterator fails to handle the terminal node when extending the path. // Expected: Should return 4 valuations (v0 and v2 are free, v1 must be true) - // Current: Panics when trying to unpack variable from terminal node + // v0 and v2 are free, v1 must be true#[test] fn test_xor_two_variables() { - // Issue: XOR creates paths that lead to terminal nodes, causing failures. // Expected: Should return 2 valuations: (true, false), (false, true) - // Current: Returns 3 valuations (includes an extra valuation due to iterator behavior)Also applies to: 451-453
590-590: Minor: preferis_empty()overlen() > 0.- assert!(iter_valuations.len() > 0); + assert!(!iter_valuations.is_empty());
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (3)
src/split/bdd.rs(3 hunks)src/split/bdd_iterator.rs(1 hunks)src/split/mod.rs(1 hunks)
🧰 Additional context used
🧬 Code graph analysis (2)
src/split/bdd.rs (2)
src/split/bdd_iterator.rs (1)
new(69-81)src/bdd_node.rs (6)
new(35-35)new(114-123)zero(47-47)zero(133-139)one(49-49)one(141-147)
src/split/bdd_iterator.rs (1)
src/split/bdd.rs (17)
root(65-65)root(345-347)root(609-615)new_false(44-44)new_false(312-317)new_false(495-497)new_literal(46-46)new_literal(319-329)new_literal(500-510)get(68-68)get(349-351)from(471-473)from(477-479)from(483-485)new_true(42-42)new_true(305-310)new_true(490-492)
🔇 Additional comments (7)
src/split/mod.rs (1)
6-6: LGTM!The module declaration and public re-export follow the established pattern in this file, correctly exposing
ValuationsIteratoras part of the public API while keeping the implementation module crate-internal.Also applies to: 13-13
src/split/bdd.rs (1)
676-695: LGTM! Method correctly implements owned iterator pattern.The implementation properly upcasts all BDD variants to
Bdd64before passing ownership to the iterator. The clone on line 691 for theBdd64case is consistent with the owned iterator design.One consideration: the doc comment mentions panicking if variables are outside bounds, but the actual assertion (
min_variable <= max_variable) is inValuationsIterator::new. Additionally, the assertion at line 152 infind_first_pathvalidates thatmin_variable <= root_var. You may want to clarify in the doc that the panic can also occur ifmin > max.src/split/bdd_iterator.rs (5)
46-51: LGTM!The struct correctly encapsulates the owned BDD and iteration state. Using
Option<Vec<PathChoice>>elegantly represents both the current path and the exhausted state.
53-66: LGTM!The
Iteratorimplementation correctly captures the current valuation before advancing to the next path. The use ofBTreeMapprovides predictable ordering of variables in the output.
86-129: LGTM!The
find_next_pathcorrectly implements lexicographic advancement by backtracking through the path until finding a choice that can progress (negative → positive). The check on line 113 properly skips invalid paths where the high link leads to zero.
132-172: LGTM!The
find_first_pathcorrectly handles all cases:
- False BDD returns
None- True BDD (root is
1) treats all variables as free- Non-trivial BDD correctly identifies whether the first variable is a decision node or free choice
- Choosing negative/low paths first ensures lexicographically smallest valuation is found first
176-227: LGTM!The
extend_pathmethod correctly extends a partial path to a complete valuation by:
- Handling terminal
1nodes by treating remaining variables as free- Making forced positive choices when the low link leads to zero
- Otherwise preferring negative choices for lexicographic ordering
- Properly validating BDD invariants with assertions
Summary by CodeRabbit
✏️ Tip: You can customize this high-level summary in your review settings.