1use std::collections::HashMap;
18use std::collections::HashSet;
19
20#[derive(Clone, PartialEq, Eq, Hash, Debug)]
23pub struct Bits {
24 words: Vec<u64>,
25}
26
27impl Bits {
28 fn new(universe: usize) -> Self {
29 Bits {
30 words: vec![0; universe.div_ceil(64)],
31 }
32 }
33
34 fn insert(&mut self, index: usize) {
35 self.words[index / 64] |= 1u64 << (index % 64);
36 }
37
38 fn contains(&self, index: usize) -> bool {
39 (self.words[index / 64] >> (index % 64)) & 1 == 1
40 }
41
42 fn with(&self, index: usize) -> Bits {
43 let mut out = self.clone();
44 out.insert(index);
45 out
46 }
47
48 fn intersects(&self, other: &Bits) -> bool {
49 self.words.iter().zip(&other.words).any(|(a, b)| a & b != 0)
50 }
51
52 fn is_subset(&self, other: &Bits) -> bool {
53 self.words.iter().zip(&other.words).all(|(a, b)| a & !b == 0)
54 }
55
56 fn is_proper_subset(&self, other: &Bits) -> bool {
57 self.is_subset(other) && self != other
58 }
59
60 fn count(&self) -> usize {
61 self.words.iter().map(|w| w.count_ones() as usize).sum()
62 }
63
64 fn is_empty(&self) -> bool {
65 self.words.iter().all(|w| *w == 0)
66 }
67
68 fn ones(&self) -> impl Iterator<Item = usize> + '_ {
71 self.words.iter().enumerate().flat_map(|(wi, w)| {
72 let mut word = *w;
73 std::iter::from_fn(move || {
74 if word == 0 {
75 return None;
76 }
77 let bit = word.trailing_zeros() as usize;
78 word &= word - 1;
79 Some(wi * 64 + bit)
80 })
81 })
82 }
83}
84
85pub struct CandidateSpec {
86 pub node: String,
87 pub bindings: Vec<(String, bool)>,
89 pub grain: Vec<String>,
90 pub partial_is_full: bool,
92}
93
94pub struct NetworkSpec {
95 pub terminals: Vec<String>,
96 pub candidates: Vec<CandidateSpec>,
97 pub axis_families: Vec<(String, Vec<Vec<String>>)>,
99 pub join_requirements: Vec<(String, Vec<String>, Vec<String>)>,
101 pub subsumed_arms: Vec<(String, String)>,
103 pub cover_limit: usize,
104 pub state_limit: usize,
105}
106
107#[derive(PartialEq, Eq, Debug)]
108pub enum LimitKind {
109 Covers,
110 States,
111}
112
113#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
116enum Kind {
117 Axis = 0, Colocated = 1, Connected = 2, Cover = 3, Labelable = 4, Paired = 5, }
124
125struct Ob {
128 kind: Kind,
129 subject: Vec<u32>,
130 satisfiers: Vec<u32>,
131}
132
133impl Ob {
134 fn key(&self) -> (usize, Kind, &[u32]) {
135 (self.satisfiers.len(), self.kind, &self.subject)
136 }
137}
138
139struct AxisMember {
140 nodes: Vec<u32>,
141 bits: Bits,
142 subject_rank: u32,
145}
146
147struct AxisFamily {
148 representative: u32,
149 members: Vec<AxisMember>,
150}
151
152struct Side {
153 subject: Vec<u32>,
154 carrier_bits: Bits,
155 materializer_bits: Bits,
156 satisfiers: Vec<u32>,
157}
158
159struct Net {
160 n_nodes: usize,
161 node_names: Vec<String>,
162 terminals: Vec<u32>,
163 binding_keys: Vec<Bits>,
164 grain: Vec<Bits>,
165 grain_nonempty: Vec<bool>,
166 row_complete: Vec<bool>,
167 binder_bits: Vec<Bits>,
169 binders: Vec<Vec<u32>>,
170 full_binder_bits: Vec<Bits>,
171 full_binders: Vec<Vec<u32>>,
172 bound_terminals: Vec<Bits>,
174 join_partners: Vec<Bits>,
175 functional_partners: Vec<Bits>,
176 func_succ: Vec<Bits>,
177 func_pred: Vec<Bits>,
178 chain_completers: Vec<Bits>,
180 axis_families: Vec<AxisFamily>,
181 axis_family_index: HashMap<u32, usize>,
182 sides: Vec<Side>,
183 subsumed: Vec<Option<u32>>,
185 cover_satisfiers: Vec<Vec<u32>>,
187}
188
189fn intern(sorted: &[String], name: &str) -> u32 {
190 sorted
191 .binary_search_by(|probe| probe.as_str().cmp(name))
192 .unwrap_or_else(|_| panic!("uninterned name: {name}")) as u32
193}
194
195impl Net {
196 fn build(spec: &NetworkSpec) -> Net {
197 let mut node_names: Vec<String> =
198 spec.candidates.iter().map(|c| c.node.clone()).collect();
199 node_names.sort();
200 node_names.dedup();
201 let n_nodes = node_names.len();
202
203 let mut addr_names: Vec<String> = Vec::new();
204 addr_names.extend(spec.terminals.iter().cloned());
205 for candidate in &spec.candidates {
206 addr_names.extend(candidate.bindings.iter().map(|(a, _)| a.clone()));
207 addr_names.extend(candidate.grain.iter().cloned());
208 }
209 for (representative, _) in &spec.axis_families {
210 addr_names.push(representative.clone());
211 }
212 for (canonical, left, right) in &spec.join_requirements {
213 addr_names.push(canonical.clone());
214 addr_names.extend(left.iter().cloned());
215 addr_names.extend(right.iter().cloned());
216 }
217 addr_names.sort();
218 addr_names.dedup();
219 let n_addrs = addr_names.len();
220
221 let node_id = |name: &str| intern(&node_names, name) as usize;
222 let addr_id = |name: &str| intern(&addr_names, name) as usize;
223
224 let mut binding_keys = vec![Bits::new(n_addrs); n_nodes];
225 let mut partial_binds = vec![Bits::new(n_addrs); n_nodes];
226 let mut grain = vec![Bits::new(n_addrs); n_nodes];
227 let mut partial_is_full = vec![false; n_nodes];
228 for candidate in &spec.candidates {
229 let node = node_id(&candidate.node);
230 partial_is_full[node] = candidate.partial_is_full;
231 for (address, partial) in &candidate.bindings {
232 let addr = addr_id(address);
233 binding_keys[node].insert(addr);
234 if *partial {
235 partial_binds[node].insert(addr);
236 }
237 }
238 for address in &candidate.grain {
239 grain[node].insert(addr_id(address));
240 }
241 }
242 let grain_nonempty: Vec<bool> = grain.iter().map(|g| !g.is_empty()).collect();
243 let row_complete: Vec<bool> = (0..n_nodes)
245 .map(|node| partial_is_full[node] || !grain[node].intersects(&partial_binds[node]))
246 .collect();
247 let full_binds: Vec<Bits> = (0..n_nodes)
249 .map(|node| {
250 if partial_is_full[node] {
251 binding_keys[node].clone()
252 } else {
253 let mut bits = binding_keys[node].clone();
254 for (word, partial) in bits.words.iter_mut().zip(&partial_binds[node].words) {
255 *word &= !partial;
256 }
257 bits
258 }
259 })
260 .collect();
261
262 let terminals: Vec<u32> = spec.terminals.iter().map(|t| addr_id(t) as u32).collect();
263 let n_terms = terminals.len();
264
265 let mut binder_bits = vec![Bits::new(n_nodes); n_terms];
266 let mut full_binder_bits = vec![Bits::new(n_nodes); n_terms];
267 for (position, terminal) in terminals.iter().enumerate() {
268 for node in 0..n_nodes {
269 if binding_keys[node].contains(*terminal as usize) {
270 binder_bits[position].insert(node);
271 }
272 if full_binds[node].contains(*terminal as usize) {
273 full_binder_bits[position].insert(node);
274 }
275 }
276 }
277 let binders: Vec<Vec<u32>> = binder_bits
278 .iter()
279 .map(|bits| bits.ones().map(|n| n as u32).collect())
280 .collect();
281 let full_binders: Vec<Vec<u32>> = full_binder_bits
282 .iter()
283 .map(|bits| bits.ones().map(|n| n as u32).collect())
284 .collect();
285 let mut bound_terminals = vec![Bits::new(n_terms); n_nodes];
286 for (position, bits) in binder_bits.iter().enumerate() {
287 for node in bits.ones() {
288 bound_terminals[node].insert(position);
289 }
290 }
291
292 let mut join_partners = vec![Bits::new(n_nodes); n_nodes];
294 let mut functional_partners = vec![Bits::new(n_nodes); n_nodes];
295 let mut func_succ = vec![Bits::new(n_nodes); n_nodes];
296 let mut func_pred = vec![Bits::new(n_nodes); n_nodes];
297 let mut shared = Bits::new(n_addrs);
298 for left in 0..n_nodes {
299 for right in (left + 1)..n_nodes {
300 for (index, word) in shared.words.iter_mut().enumerate() {
301 *word = binding_keys[left].words[index] & binding_keys[right].words[index];
302 }
303 if shared.is_empty() {
304 continue;
305 }
306 join_partners[left].insert(right);
307 join_partners[right].insert(left);
308 if grain[left].is_subset(&shared) || grain[right].is_subset(&shared) {
312 functional_partners[left].insert(right);
313 functional_partners[right].insert(left);
314 }
315 if grain_nonempty[right] && grain[right].is_subset(&shared) {
318 func_succ[left].insert(right);
319 func_pred[right].insert(left);
320 }
321 if grain_nonempty[left] && grain[left].is_subset(&shared) {
322 func_succ[right].insert(left);
323 func_pred[left].insert(right);
324 }
325 }
326 }
327
328 let chain_completers: Vec<Bits> = (0..n_terms)
331 .map(|position| {
332 let mut seen = full_binder_bits[position].clone();
333 let mut stack: Vec<usize> = seen.ones().collect();
334 while let Some(current) = stack.pop() {
335 for origin in func_pred[current].ones() {
336 if seen.contains(origin) {
337 continue;
338 }
339 seen.insert(origin);
340 if row_complete[origin] {
341 stack.push(origin);
342 }
343 }
344 }
345 seen
346 })
347 .collect();
348
349 let mut subsumed: Vec<Option<u32>> = vec![None; n_nodes];
350 for (arm, union_node) in &spec.subsumed_arms {
351 subsumed[node_id(arm)] = Some(node_id(union_node) as u32);
352 }
353 let prune = |satisfiers: Vec<u32>| prune_arms(&subsumed, satisfiers);
354
355 let mut axis_families: Vec<AxisFamily> = spec
358 .axis_families
359 .iter()
360 .map(|(representative, members)| {
361 let mut index_strings: Vec<String> =
362 (0..members.len()).map(|i| i.to_string()).collect();
363 index_strings.sort();
364 AxisFamily {
365 representative: addr_id(representative) as u32,
366 members: members
367 .iter()
368 .enumerate()
369 .map(|(index, nodes)| {
370 let ids: Vec<u32> =
371 nodes.iter().map(|n| node_id(n) as u32).collect();
372 let mut bits = Bits::new(n_nodes);
373 for id in &ids {
374 bits.insert(*id as usize);
375 }
376 AxisMember {
377 nodes: ids,
378 bits,
379 subject_rank: index_strings
380 .binary_search(&index.to_string())
381 .unwrap() as u32,
382 }
383 })
384 .collect(),
385 }
386 })
387 .collect();
388 axis_families.sort_by_key(|family| family.representative);
389 let axis_family_index: HashMap<u32, usize> = axis_families
390 .iter()
391 .enumerate()
392 .map(|(index, family)| (family.representative, index))
393 .collect();
394
395 let mut sides: Vec<Side> = Vec::new();
398 for (canonical, left, right) in &spec.join_requirements {
399 let canonical_addr = addr_id(canonical);
400 for keys in [left, right] {
401 if keys.is_empty() {
402 continue;
403 }
404 let mut key_ids: Vec<u32> = keys.iter().map(|k| addr_id(k) as u32).collect();
405 key_ids.sort();
406 key_ids.dedup();
407 let mut key_bits = Bits::new(n_addrs);
408 for key in &key_ids {
409 key_bits.insert(*key as usize);
410 }
411 let mut carrier_bits = Bits::new(n_nodes);
412 let mut materializer_bits = Bits::new(n_nodes);
413 let mut materializers: Vec<u32> = Vec::new();
414 for node in 0..n_nodes {
415 if !key_bits.is_subset(&binding_keys[node]) {
416 continue;
417 }
418 carrier_bits.insert(node);
419 if binding_keys[node].contains(canonical_addr) {
420 materializer_bits.insert(node);
421 materializers.push(node as u32);
422 }
423 }
424 materializers.sort_by_key(|node| {
427 (!grain[*node as usize].is_subset(&key_bits), *node)
428 });
429 let mut subject = vec![canonical_addr as u32];
430 subject.extend(key_ids.iter());
431 sides.push(Side {
432 subject,
433 carrier_bits,
434 materializer_bits,
435 satisfiers: prune(materializers),
436 });
437 }
438 }
439
440 let cover_satisfiers: Vec<Vec<u32>> =
441 binders.iter().map(|list| prune(list.clone())).collect();
442
443 Net {
444 n_nodes,
445 node_names,
446 terminals,
447 binding_keys,
448 grain,
449 grain_nonempty,
450 row_complete,
451 binder_bits,
452 binders,
453 full_binder_bits,
454 full_binders,
455 bound_terminals,
456 join_partners,
457 functional_partners,
458 func_succ,
459 func_pred,
460 chain_completers,
461 axis_families,
462 axis_family_index,
463 sides,
464 subsumed,
465 cover_satisfiers,
466 }
467 }
468
469 fn profile(&self, chosen: &Bits) -> Vec<u8> {
471 self.terminals
472 .iter()
473 .enumerate()
474 .map(|(position, terminal)| {
475 if let Some(family_index) = self.axis_family_index.get(terminal) {
476 if self.axis_families[*family_index]
477 .members
478 .iter()
479 .all(|member| member.bits.intersects(chosen))
480 {
481 return 2;
482 }
483 } else if self.full_binder_bits[position].intersects(chosen) {
484 return 2;
485 }
486 if self.binder_bits[position].intersects(chosen) {
487 1
488 } else {
489 0
490 }
491 })
492 .collect()
493 }
494
495 fn components(&self, chosen: &Bits) -> Vec<Bits> {
498 let mut assigned = Bits::new(self.n_nodes);
499 let mut components: Vec<Bits> = Vec::new();
500 for start in chosen.ones() {
501 if assigned.contains(start) {
502 continue;
503 }
504 let mut component = Bits::new(self.n_nodes);
505 component.insert(start);
506 assigned.insert(start);
507 let mut stack = vec![start];
508 while let Some(current) = stack.pop() {
509 for partner in self.join_partners[current].ones() {
510 if chosen.contains(partner) && !assigned.contains(partner) {
511 assigned.insert(partner);
512 component.insert(partner);
513 stack.push(partner);
514 }
515 }
516 }
517 components.push(component);
518 }
519 components
520 }
521
522 fn label_chain(&self, source: usize, position: usize, chosen: &Bits) -> Option<Vec<u32>> {
527 let full = &self.full_binder_bits[position];
528 let mut walked = Bits::new(self.n_nodes);
529 walked.insert(source);
530 let mut origins = Bits::new(self.n_nodes);
531 origins.insert(source);
532 let mut stack = vec![source];
533 while let Some(current) = stack.pop() {
534 for node in self.func_succ[current].ones() {
535 if walked.contains(node) || !chosen.contains(node) {
536 continue;
537 }
538 if full.contains(node) {
539 return None;
540 }
541 walked.insert(node);
542 if self.row_complete[node] {
543 origins.insert(node);
544 stack.push(node);
545 }
546 }
547 }
548 let frontier: Vec<u32> = self.chain_completers[position]
549 .ones()
550 .filter(|node| !chosen.contains(*node) && self.func_pred[*node].intersects(&origins))
551 .map(|node| node as u32)
552 .collect();
553 Some(frontier)
554 }
555
556 fn scarcest_pending(&self, chosen: &Bits) -> Option<Ob> {
560 let mut best: Option<Ob> = None;
561 let mut consider = |candidate: Ob| {
562 match &best {
563 Some(current) if current.key() <= candidate.key() => {}
564 _ => best = Some(candidate),
565 }
566 };
567 for (position, terminal) in self.terminals.iter().enumerate() {
569 if self.binder_bits[position].intersects(chosen) {
570 continue;
571 }
572 if self.cover_satisfiers[position].is_empty() {
573 continue;
574 }
575 consider(Ob {
576 kind: Kind::Cover,
577 subject: vec![*terminal],
578 satisfiers: self.cover_satisfiers[position].clone(),
579 });
580 }
581 for family in &self.axis_families {
585 for member in &family.members {
586 if !member.bits.intersects(chosen) {
587 consider(Ob {
588 kind: Kind::Axis,
589 subject: vec![family.representative, member.subject_rank],
590 satisfiers: prune_arms(&self.subsumed, member.nodes.clone()),
591 });
592 }
593 }
594 }
595 for side in &self.sides {
597 if !side.carrier_bits.intersects(chosen)
598 || side.materializer_bits.intersects(chosen)
599 || side.satisfiers.is_empty()
600 {
601 continue;
602 }
603 consider(Ob {
604 kind: Kind::Paired,
605 subject: side.subject.clone(),
606 satisfiers: side.satisfiers.clone(),
607 });
608 }
609 let chosen_count = chosen.count();
610 for source in chosen.ones() {
611 if !self.bound_terminals[source].is_empty() {
613 for (position, terminal) in self.terminals.iter().enumerate() {
614 if self.bound_terminals[source].contains(position)
615 || !self.chain_completers[position].contains(source)
616 {
617 continue;
618 }
619 if let Some(frontier) = self.label_chain(source, position, chosen) {
620 let satisfiers = prune_arms(&self.subsumed, frontier);
621 if !satisfiers.is_empty() {
622 consider(Ob {
623 kind: Kind::Labelable,
624 subject: vec![source as u32, *terminal],
625 satisfiers,
626 });
627 }
628 }
629 }
630 }
631 if chosen_count >= 2 && self.grain_nonempty[source] {
633 let mut others = chosen.clone();
634 others.words[source / 64] &= !(1u64 << (source % 64));
635 if !self.functional_partners[source].intersects(&others) {
636 let mut extras: Vec<u32> = (0..self.n_nodes)
637 .filter(|extra| {
638 !chosen.contains(*extra)
639 && self.grain[source].is_subset(&self.binding_keys[*extra])
640 && self.functional_partners[*extra].intersects(&others)
641 })
642 .map(|extra| extra as u32)
643 .collect();
644 extras.sort_by_key(|extra| {
645 (self.binding_keys[*extra as usize].count(), *extra)
646 });
647 let satisfiers = prune_arms(&self.subsumed, extras);
648 if !satisfiers.is_empty() {
649 consider(Ob {
650 kind: Kind::Colocated,
651 subject: vec![source as u32],
652 satisfiers,
653 });
654 }
655 }
656 }
657 }
658 if best.is_none() && chosen_count > 1 {
660 let components = self.components(chosen);
661 if components.len() > 1 {
662 let mut mergers: Vec<u32> = Vec::new();
663 let mut adjacent: Vec<u32> = Vec::new();
664 for node in 0..self.n_nodes {
665 if chosen.contains(node) {
666 continue;
667 }
668 let touched = components
669 .iter()
670 .filter(|component| self.join_partners[node].intersects(component))
671 .count();
672 if touched >= 1 {
673 adjacent.push(node as u32);
674 }
675 if touched >= 2 {
676 mergers.push(node as u32);
677 }
678 }
679 let satisfiers = prune_arms(
680 &self.subsumed,
681 if mergers.is_empty() { adjacent } else { mergers },
682 );
683 if !satisfiers.is_empty() {
684 let mut subject: Vec<u32> = components
685 .iter()
686 .map(|component| component.ones().next().unwrap() as u32)
687 .collect();
688 subject.sort();
689 best = Some(Ob {
690 kind: Kind::Connected,
691 subject,
692 satisfiers,
693 });
694 }
695 }
696 }
697 best
698 }
699}
700
701fn prune_arms(subsumed: &[Option<u32>], satisfiers: Vec<u32>) -> Vec<u32> {
704 if subsumed.iter().all(|entry| entry.is_none()) {
705 return satisfiers;
706 }
707 let present: HashSet<u32> = satisfiers.iter().copied().collect();
708 let kept: Vec<u32> = satisfiers
709 .iter()
710 .copied()
711 .filter(|node| match subsumed[*node as usize] {
712 Some(union_node) => !present.contains(&union_node),
713 None => true,
714 })
715 .collect();
716 kept
717}
718
719pub fn enumerate_covers(spec: &NetworkSpec) -> (Vec<Vec<String>>, Option<LimitKind>) {
723 let net = Net::build(spec);
724 let mut covers: Vec<Bits> = Vec::new();
725 let mut emitted: Vec<(Bits, Vec<u8>)> = Vec::new();
726 let mut visited: HashSet<Bits> = HashSet::new();
727 let mut level: Vec<Bits> = vec![Bits::new(net.n_nodes)];
728 let mut limit: Option<LimitKind> = None;
729 'walk: while !level.is_empty() {
730 let mut next_level: Vec<Bits> = Vec::new();
733 for chosen in level {
734 if visited.contains(&chosen) {
735 continue;
736 }
737 if covers.len() >= spec.cover_limit {
738 limit = Some(LimitKind::Covers);
739 break 'walk;
740 }
741 if visited.len() >= spec.state_limit {
742 limit = Some(LimitKind::States);
743 break 'walk;
744 }
745 visited.insert(chosen.clone());
746 if !emitted.is_empty() {
747 let profile = net.profile(&chosen);
748 if emitted.iter().any(|(prior, prior_profile)| {
749 prior.is_proper_subset(&chosen) && *prior_profile == profile
750 }) {
751 continue;
752 }
753 }
754 if let Some(first) = net.scarcest_pending(&chosen) {
755 for node in &first.satisfiers {
756 next_level.push(chosen.with(*node as usize));
757 }
758 continue;
759 }
760 let profile = net.profile(&chosen);
761 for (position, _) in net.terminals.iter().enumerate() {
762 if net.full_binder_bits[position].intersects(&chosen) {
763 continue;
764 }
765 for node in &net.full_binders[position] {
768 if net.binders[position].contains(node) {
769 next_level.push(chosen.with(*node as usize));
770 }
771 }
772 }
773 covers.push(chosen.clone());
774 emitted.push((chosen, profile));
775 }
776 level = next_level;
777 }
778 let names: Vec<Vec<String>> = covers
779 .iter()
780 .map(|cover| {
781 cover
782 .ones()
783 .map(|node| net.node_names[node].clone())
784 .collect()
785 })
786 .collect();
787 (names, limit)
788}
789
790#[cfg(test)]
791mod tests {
792 use super::*;
793
794 fn candidate(node: &str, bindings: &[(&str, bool)], grain: &[&str]) -> CandidateSpec {
795 CandidateSpec {
796 node: node.to_string(),
797 bindings: bindings
798 .iter()
799 .map(|(a, p)| (a.to_string(), *p))
800 .collect(),
801 grain: grain.iter().map(|g| g.to_string()).collect(),
802 partial_is_full: false,
803 }
804 }
805
806 fn spec(terminals: &[&str], candidates: Vec<CandidateSpec>) -> NetworkSpec {
807 NetworkSpec {
808 terminals: terminals.iter().map(|t| t.to_string()).collect(),
809 candidates,
810 axis_families: vec![],
811 join_requirements: vec![],
812 subsumed_arms: vec![],
813 cover_limit: 4096,
814 state_limit: 10_000,
815 }
816 }
817
818 #[test]
819 fn single_source_cover() {
820 let s = spec(
821 &["a", "b"],
822 vec![candidate("ds~one", &[("a", false), ("b", false)], &["a"])],
823 );
824 let (covers, limit) = enumerate_covers(&s);
825 assert_eq!(limit, None);
826 assert_eq!(covers, vec![vec!["ds~one".to_string()]]);
827 }
828
829 #[test]
830 fn state_limit_reports_states() {
831 let s = NetworkSpec {
832 state_limit: 1,
833 ..spec(
834 &["a", "b"],
835 vec![
836 candidate("ds~one", &[("a", false), ("k", false)], &["a"]),
837 candidate("ds~two", &[("b", false), ("k", false)], &["b"]),
838 ],
839 )
840 };
841 let (covers, limit) = enumerate_covers(&s);
842 assert_eq!(limit, Some(LimitKind::States));
843 assert!(covers.is_empty());
844 }
845
846 #[test]
847 fn soft_branch_emits_full_binder_upgrade() {
848 let s = spec(
851 &["launch", "name"],
852 vec![
853 candidate(
854 "ds~launch",
855 &[("launch", false), ("vehicle", false), ("name", true)],
856 &["launch"],
857 ),
858 candidate(
859 "ds~vehicle",
860 &[("vehicle", false), ("name", false)],
861 &["vehicle"],
862 ),
863 ],
864 );
865 let (covers, limit) = enumerate_covers(&s);
866 assert_eq!(limit, None);
867 assert_eq!(
868 covers,
869 vec![
870 vec!["ds~launch".to_string()],
871 vec!["ds~launch".to_string(), "ds~vehicle".to_string()],
872 ]
873 );
874 }
875
876 #[test]
877 fn subsumed_arm_branches_only_onto_the_union() {
878 let mut s = spec(
879 &["k"],
880 vec![
881 candidate("ds~arm_a", &[("k", true)], &["k"]),
882 candidate("ds~arm_b", &[("k", true)], &["k"]),
883 candidate("ds~union", &[("k", false)], &["k"]),
884 ],
885 );
886 s.subsumed_arms = vec![
887 ("ds~arm_a".to_string(), "ds~union".to_string()),
888 ("ds~arm_b".to_string(), "ds~union".to_string()),
889 ];
890 let (covers, limit) = enumerate_covers(&s);
891 assert_eq!(limit, None);
892 assert_eq!(covers, vec![vec!["ds~union".to_string()]]);
893 }
894
895 #[test]
896 fn disconnected_cover_is_bridged() {
897 let s = spec(
898 &["a_val", "b_val"],
899 vec![
900 candidate("ds~fact_a", &[("a_id", false), ("sk", false), ("a_val", false)], &["a_id"]),
901 candidate("ds~fact_b", &[("b_id", false), ("ok", false), ("b_val", false)], &["b_id"]),
902 candidate("ds~bridge", &[("sk", false), ("ok", false)], &["sk", "ok"]),
903 ],
904 );
905 let (covers, limit) = enumerate_covers(&s);
906 assert_eq!(limit, None);
907 assert!(covers.iter().any(|cover| cover.len() == 3));
908 assert!(!covers.contains(&vec![
910 "ds~fact_a".to_string(),
911 "ds~fact_b".to_string()
912 ]));
913 }
914
915 #[test]
916 fn empty_terminals_emit_the_empty_cover() {
917 let s = spec(&[], vec![candidate("ds~one", &[("a", false)], &["a"])]);
918 let (covers, limit) = enumerate_covers(&s);
919 assert_eq!(limit, None);
920 assert_eq!(covers, vec![Vec::<String>::new()]);
921 }
922}