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 pub owes: Vec<(String, Vec<String>)>,
94}
95
96pub struct NetworkSpec {
97 pub terminals: Vec<String>,
98 pub candidates: Vec<CandidateSpec>,
99 pub axis_families: Vec<(String, Vec<Vec<String>>)>,
101 pub join_requirements: Vec<(String, Vec<String>, Vec<String>)>,
103 pub subsumed_arms: Vec<(String, String)>,
105 pub cover_limit: usize,
106 pub state_limit: usize,
107}
108
109#[derive(PartialEq, Eq, Debug)]
110pub enum LimitKind {
111 Covers,
112 States,
113}
114
115#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
118enum Kind {
119 Axis = 0, Colocated = 1, Connected = 2, Cover = 3, Joined = 4, Labelable = 5, Paired = 6, }
127
128struct Ob {
131 kind: Kind,
132 subject: Vec<u32>,
133 satisfiers: Vec<u32>,
134}
135
136impl Ob {
137 fn key(&self) -> (usize, Kind, &[u32]) {
138 (self.satisfiers.len(), self.kind, &self.subject)
139 }
140}
141
142struct AxisMember {
143 nodes: Vec<u32>,
144 bits: Bits,
145 subject_rank: u32,
148}
149
150struct AxisFamily {
151 representative: u32,
152 members: Vec<AxisMember>,
153}
154
155struct Side {
156 subject: Vec<u32>,
157 carrier_bits: Bits,
158 materializer_bits: Bits,
159 satisfiers: Vec<u32>,
160}
161
162struct Net {
163 n_nodes: usize,
164 node_names: Vec<String>,
165 terminals: Vec<u32>,
166 binding_keys: Vec<Bits>,
167 grain: Vec<Bits>,
168 grain_nonempty: Vec<bool>,
169 row_complete: Vec<bool>,
170 binder_bits: Vec<Bits>,
172 binders: Vec<Vec<u32>>,
173 full_binder_bits: Vec<Bits>,
174 full_binders: Vec<Vec<u32>>,
175 bound_terminals: Vec<Bits>,
177 join_partners: Vec<Bits>,
178 functional_partners: Vec<Bits>,
179 func_succ: Vec<Bits>,
180 func_pred: Vec<Bits>,
181 chain_completers: Vec<Bits>,
183 axis_families: Vec<AxisFamily>,
184 axis_family_index: HashMap<u32, usize>,
185 sides: Vec<Side>,
186 subsumed: Vec<Option<u32>>,
188 cover_satisfiers: Vec<Vec<u32>>,
190 owed_full_bits: Vec<Bits>,
193 owed_completers: Vec<Bits>,
194 owes: Vec<Vec<(usize, Vec<usize>)>>,
196}
197
198fn intern(sorted: &[String], name: &str) -> u32 {
199 sorted
200 .binary_search_by(|probe| probe.as_str().cmp(name))
201 .unwrap_or_else(|_| panic!("uninterned name: {name}")) as u32
202}
203
204impl Net {
205 fn build(spec: &NetworkSpec) -> Net {
206 let mut node_names: Vec<String> =
207 spec.candidates.iter().map(|c| c.node.clone()).collect();
208 node_names.sort();
209 node_names.dedup();
210 let n_nodes = node_names.len();
211
212 let mut addr_names: Vec<String> = Vec::new();
213 addr_names.extend(spec.terminals.iter().cloned());
214 for candidate in &spec.candidates {
215 addr_names.extend(candidate.bindings.iter().map(|(a, _)| a.clone()));
216 addr_names.extend(candidate.grain.iter().cloned());
217 for (terminal, columns) in &candidate.owes {
218 addr_names.push(terminal.clone());
219 addr_names.extend(columns.iter().cloned());
220 }
221 }
222 for (representative, _) in &spec.axis_families {
223 addr_names.push(representative.clone());
224 }
225 for (canonical, left, right) in &spec.join_requirements {
226 addr_names.push(canonical.clone());
227 addr_names.extend(left.iter().cloned());
228 addr_names.extend(right.iter().cloned());
229 }
230 addr_names.sort();
231 addr_names.dedup();
232 let n_addrs = addr_names.len();
233
234 let node_id = |name: &str| intern(&node_names, name) as usize;
235 let addr_id = |name: &str| intern(&addr_names, name) as usize;
236
237 let mut binding_keys = vec![Bits::new(n_addrs); n_nodes];
238 let mut partial_binds = vec![Bits::new(n_addrs); n_nodes];
239 let mut grain = vec![Bits::new(n_addrs); n_nodes];
240 let mut partial_is_full = vec![false; n_nodes];
241 for candidate in &spec.candidates {
242 let node = node_id(&candidate.node);
243 partial_is_full[node] = candidate.partial_is_full;
244 for (address, partial) in &candidate.bindings {
245 let addr = addr_id(address);
246 binding_keys[node].insert(addr);
247 if *partial {
248 partial_binds[node].insert(addr);
249 }
250 }
251 for address in &candidate.grain {
252 grain[node].insert(addr_id(address));
253 }
254 }
255 let grain_nonempty: Vec<bool> = grain.iter().map(|g| !g.is_empty()).collect();
256 let row_complete: Vec<bool> = (0..n_nodes)
258 .map(|node| partial_is_full[node] || !grain[node].intersects(&partial_binds[node]))
259 .collect();
260 let full_binds: Vec<Bits> = (0..n_nodes)
262 .map(|node| {
263 if partial_is_full[node] {
264 binding_keys[node].clone()
265 } else {
266 let mut bits = binding_keys[node].clone();
267 for (word, partial) in bits.words.iter_mut().zip(&partial_binds[node].words) {
268 *word &= !partial;
269 }
270 bits
271 }
272 })
273 .collect();
274
275 let terminals: Vec<u32> = spec.terminals.iter().map(|t| addr_id(t) as u32).collect();
276 let n_terms = terminals.len();
277
278 let mut binder_bits = vec![Bits::new(n_nodes); n_terms];
279 let mut full_binder_bits = vec![Bits::new(n_nodes); n_terms];
280 for (position, terminal) in terminals.iter().enumerate() {
281 for node in 0..n_nodes {
282 if binding_keys[node].contains(*terminal as usize) {
283 binder_bits[position].insert(node);
284 }
285 if full_binds[node].contains(*terminal as usize) {
286 full_binder_bits[position].insert(node);
287 }
288 }
289 }
290 let binders: Vec<Vec<u32>> = binder_bits
291 .iter()
292 .map(|bits| bits.ones().map(|n| n as u32).collect())
293 .collect();
294 let full_binders: Vec<Vec<u32>> = full_binder_bits
295 .iter()
296 .map(|bits| bits.ones().map(|n| n as u32).collect())
297 .collect();
298 let mut bound_terminals = vec![Bits::new(n_terms); n_nodes];
299 for (position, bits) in binder_bits.iter().enumerate() {
300 for node in bits.ones() {
301 bound_terminals[node].insert(position);
302 }
303 }
304
305 let mut join_partners = vec![Bits::new(n_nodes); n_nodes];
307 let mut functional_partners = vec![Bits::new(n_nodes); n_nodes];
308 let mut func_succ = vec![Bits::new(n_nodes); n_nodes];
309 let mut func_pred = vec![Bits::new(n_nodes); n_nodes];
310 let mut shared = Bits::new(n_addrs);
311 for left in 0..n_nodes {
312 for right in (left + 1)..n_nodes {
313 for (index, word) in shared.words.iter_mut().enumerate() {
314 *word = binding_keys[left].words[index] & binding_keys[right].words[index];
315 }
316 if shared.is_empty() {
317 continue;
318 }
319 join_partners[left].insert(right);
320 join_partners[right].insert(left);
321 if grain[left].is_subset(&shared) || grain[right].is_subset(&shared) {
325 functional_partners[left].insert(right);
326 functional_partners[right].insert(left);
327 }
328 if grain_nonempty[right] && grain[right].is_subset(&shared) {
331 func_succ[left].insert(right);
332 func_pred[right].insert(left);
333 }
334 if grain_nonempty[left] && grain[left].is_subset(&shared) {
335 func_succ[right].insert(left);
336 func_pred[left].insert(right);
337 }
338 }
339 }
340
341 let completers_of = |full: &Bits| {
344 let mut seen = full.clone();
345 let mut stack: Vec<usize> = seen.ones().collect();
346 while let Some(current) = stack.pop() {
347 for origin in func_pred[current].ones() {
348 if seen.contains(origin) {
349 continue;
350 }
351 seen.insert(origin);
352 if row_complete[origin] {
353 stack.push(origin);
354 }
355 }
356 }
357 seen
358 };
359 let chain_completers: Vec<Bits> = full_binder_bits.iter().map(completers_of).collect();
360
361 let mut owed_addrs: Vec<usize> = spec
364 .candidates
365 .iter()
366 .flat_map(|c| c.owes.iter().flat_map(|(_, cols)| cols.iter()))
367 .map(|a| addr_id(a))
368 .collect();
369 owed_addrs.sort();
370 owed_addrs.dedup();
371 let owed_full_bits: Vec<Bits> = owed_addrs
372 .iter()
373 .map(|addr| {
374 let mut bits = Bits::new(n_nodes);
375 for node in 0..n_nodes {
376 if full_binds[node].contains(*addr) {
377 bits.insert(node);
378 }
379 }
380 bits
381 })
382 .collect();
383 let owed_completers: Vec<Bits> = owed_full_bits.iter().map(completers_of).collect();
384 let mut owes: Vec<Vec<(usize, Vec<usize>)>> = vec![Vec::new(); n_nodes];
385 for candidate in &spec.candidates {
386 let node = node_id(&candidate.node);
387 for (terminal, columns) in &candidate.owes {
388 let Some(position) = terminals.iter().position(|t| *t as usize == addr_id(terminal))
389 else {
390 continue;
391 };
392 let mut indices: Vec<usize> = columns
393 .iter()
394 .map(|c| owed_addrs.binary_search(&addr_id(c)).unwrap())
395 .collect();
396 indices.sort();
397 indices.dedup();
398 owes[node].push((position, indices));
399 }
400 }
401
402 let mut subsumed: Vec<Option<u32>> = vec![None; n_nodes];
403 for (arm, union_node) in &spec.subsumed_arms {
404 subsumed[node_id(arm)] = Some(node_id(union_node) as u32);
405 }
406 let prune = |satisfiers: Vec<u32>| prune_arms(&subsumed, satisfiers);
407
408 let mut axis_families: Vec<AxisFamily> = spec
411 .axis_families
412 .iter()
413 .map(|(representative, members)| {
414 let mut index_strings: Vec<String> =
415 (0..members.len()).map(|i| i.to_string()).collect();
416 index_strings.sort();
417 AxisFamily {
418 representative: addr_id(representative) as u32,
419 members: members
420 .iter()
421 .enumerate()
422 .map(|(index, nodes)| {
423 let ids: Vec<u32> =
424 nodes.iter().map(|n| node_id(n) as u32).collect();
425 let mut bits = Bits::new(n_nodes);
426 for id in &ids {
427 bits.insert(*id as usize);
428 }
429 AxisMember {
430 nodes: ids,
431 bits,
432 subject_rank: index_strings
433 .binary_search(&index.to_string())
434 .unwrap() as u32,
435 }
436 })
437 .collect(),
438 }
439 })
440 .collect();
441 axis_families.sort_by_key(|family| family.representative);
442 let axis_family_index: HashMap<u32, usize> = axis_families
443 .iter()
444 .enumerate()
445 .map(|(index, family)| (family.representative, index))
446 .collect();
447
448 let mut sides: Vec<Side> = Vec::new();
451 for (canonical, left, right) in &spec.join_requirements {
452 let canonical_addr = addr_id(canonical);
453 for keys in [left, right] {
454 if keys.is_empty() {
455 continue;
456 }
457 let mut key_ids: Vec<u32> = keys.iter().map(|k| addr_id(k) as u32).collect();
458 key_ids.sort();
459 key_ids.dedup();
460 let mut key_bits = Bits::new(n_addrs);
461 for key in &key_ids {
462 key_bits.insert(*key as usize);
463 }
464 let mut carrier_bits = Bits::new(n_nodes);
465 let mut materializer_bits = Bits::new(n_nodes);
466 let mut materializers: Vec<u32> = Vec::new();
467 for node in 0..n_nodes {
468 if !key_bits.is_subset(&binding_keys[node]) {
469 continue;
470 }
471 carrier_bits.insert(node);
472 if binding_keys[node].contains(canonical_addr) {
473 materializer_bits.insert(node);
474 materializers.push(node as u32);
475 }
476 }
477 materializers.sort_by_key(|node| {
480 (!grain[*node as usize].is_subset(&key_bits), *node)
481 });
482 let mut subject = vec![canonical_addr as u32];
483 subject.extend(key_ids.iter());
484 sides.push(Side {
485 subject,
486 carrier_bits,
487 materializer_bits,
488 satisfiers: prune(materializers),
489 });
490 }
491 }
492
493 let cover_satisfiers: Vec<Vec<u32>> =
494 binders.iter().map(|list| prune(list.clone())).collect();
495
496 Net {
497 n_nodes,
498 node_names,
499 terminals,
500 binding_keys,
501 grain,
502 grain_nonempty,
503 row_complete,
504 binder_bits,
505 binders,
506 full_binder_bits,
507 full_binders,
508 bound_terminals,
509 join_partners,
510 functional_partners,
511 func_succ,
512 func_pred,
513 chain_completers,
514 axis_families,
515 axis_family_index,
516 sides,
517 subsumed,
518 cover_satisfiers,
519 owed_full_bits,
520 owed_completers,
521 owes,
522 }
523 }
524
525 fn profile(&self, chosen: &Bits) -> Vec<u8> {
527 self.terminals
528 .iter()
529 .enumerate()
530 .map(|(position, terminal)| {
531 if let Some(family_index) = self.axis_family_index.get(terminal) {
532 if self.axis_families[*family_index]
533 .members
534 .iter()
535 .all(|member| member.bits.intersects(chosen))
536 {
537 return 2;
538 }
539 } else if self.full_binder_bits[position].intersects(chosen) {
540 return 2;
541 }
542 if self.binder_bits[position].intersects(chosen) {
543 1
544 } else {
545 0
546 }
547 })
548 .collect()
549 }
550
551 fn components(&self, chosen: &Bits) -> Vec<Bits> {
554 let mut assigned = Bits::new(self.n_nodes);
555 let mut components: Vec<Bits> = Vec::new();
556 for start in chosen.ones() {
557 if assigned.contains(start) {
558 continue;
559 }
560 let mut component = Bits::new(self.n_nodes);
561 component.insert(start);
562 assigned.insert(start);
563 let mut stack = vec![start];
564 while let Some(current) = stack.pop() {
565 for partner in self.join_partners[current].ones() {
566 if chosen.contains(partner) && !assigned.contains(partner) {
567 assigned.insert(partner);
568 component.insert(partner);
569 stack.push(partner);
570 }
571 }
572 }
573 components.push(component);
574 }
575 components
576 }
577
578 fn label_chain(&self, source: usize, position: usize, chosen: &Bits) -> Option<Vec<u32>> {
583 self.label_chain_to(
584 source,
585 &self.full_binder_bits[position],
586 &self.chain_completers[position],
587 chosen,
588 )
589 }
590
591 fn label_chain_to(
592 &self,
593 source: usize,
594 full: &Bits,
595 completers: &Bits,
596 chosen: &Bits,
597 ) -> Option<Vec<u32>> {
598 let mut walked = Bits::new(self.n_nodes);
599 walked.insert(source);
600 let mut origins = Bits::new(self.n_nodes);
601 origins.insert(source);
602 let mut stack = vec![source];
603 while let Some(current) = stack.pop() {
604 for node in self.func_succ[current].ones() {
605 if walked.contains(node) || !chosen.contains(node) {
606 continue;
607 }
608 if full.contains(node) {
609 return None;
610 }
611 walked.insert(node);
612 if self.row_complete[node] {
613 origins.insert(node);
614 stack.push(node);
615 }
616 }
617 }
618 let frontier: Vec<u32> = completers
619 .ones()
620 .filter(|node| !chosen.contains(*node) && self.func_pred[*node].intersects(&origins))
621 .map(|node| node as u32)
622 .collect();
623 Some(frontier)
624 }
625
626 fn scarcest_pending(&self, chosen: &Bits) -> Option<Ob> {
630 let mut best: Option<Ob> = None;
631 let mut consider = |candidate: Ob| {
632 match &best {
633 Some(current) if current.key() <= candidate.key() => {}
634 _ => best = Some(candidate),
635 }
636 };
637 for (position, terminal) in self.terminals.iter().enumerate() {
639 if self.binder_bits[position].intersects(chosen) {
640 continue;
641 }
642 if self.cover_satisfiers[position].is_empty() {
643 continue;
644 }
645 consider(Ob {
646 kind: Kind::Cover,
647 subject: vec![*terminal],
648 satisfiers: self.cover_satisfiers[position].clone(),
649 });
650 }
651 for family in &self.axis_families {
655 for member in &family.members {
656 if !member.bits.intersects(chosen) {
657 consider(Ob {
658 kind: Kind::Axis,
659 subject: vec![family.representative, member.subject_rank],
660 satisfiers: prune_arms(&self.subsumed, member.nodes.clone()),
661 });
662 }
663 }
664 }
665 for side in &self.sides {
667 if !side.carrier_bits.intersects(chosen)
668 || side.materializer_bits.intersects(chosen)
669 || side.satisfiers.is_empty()
670 {
671 continue;
672 }
673 consider(Ob {
674 kind: Kind::Paired,
675 subject: side.subject.clone(),
676 satisfiers: side.satisfiers.clone(),
677 });
678 }
679 for (position, terminal) in self.terminals.iter().enumerate() {
681 if !self.binder_bits[position].intersects(chosen) {
682 continue;
683 }
684 let mut frontier = Bits::new(self.n_nodes);
685 let mut discharged = false;
686 for source in self.binder_bits[position].ones() {
687 if !chosen.contains(source) {
688 continue;
689 }
690 let mut pending = false;
691 if let Some((_, columns)) = self.owes[source].iter().find(|(p, _)| *p == position) {
692 for column in columns {
693 if let Some(nodes) = self.label_chain_to(
694 source,
695 &self.owed_full_bits[*column],
696 &self.owed_completers[*column],
697 chosen,
698 ) {
699 pending = true;
700 for node in nodes {
701 frontier.insert(node as usize);
702 }
703 }
704 }
705 }
706 if !pending {
707 discharged = true;
708 break;
709 }
710 }
711 if !discharged {
712 consider(Ob {
713 kind: Kind::Joined,
714 subject: vec![*terminal],
715 satisfiers: prune_arms(
716 &self.subsumed,
717 frontier.ones().map(|n| n as u32).collect(),
718 ),
719 });
720 }
721 }
722 let chosen_count = chosen.count();
723 for source in chosen.ones() {
724 if !self.bound_terminals[source].is_empty() {
726 for (position, terminal) in self.terminals.iter().enumerate() {
727 if self.bound_terminals[source].contains(position)
728 || !self.chain_completers[position].contains(source)
729 {
730 continue;
731 }
732 if let Some(frontier) = self.label_chain(source, position, chosen) {
733 let satisfiers = prune_arms(&self.subsumed, frontier);
734 if !satisfiers.is_empty() {
735 consider(Ob {
736 kind: Kind::Labelable,
737 subject: vec![source as u32, *terminal],
738 satisfiers,
739 });
740 }
741 }
742 }
743 }
744 if chosen_count >= 2 && self.grain_nonempty[source] {
746 let mut others = chosen.clone();
747 others.words[source / 64] &= !(1u64 << (source % 64));
748 if !self.functional_partners[source].intersects(&others) {
749 let mut extras: Vec<u32> = (0..self.n_nodes)
750 .filter(|extra| {
751 !chosen.contains(*extra)
752 && self.grain[source].is_subset(&self.binding_keys[*extra])
753 && self.functional_partners[*extra].intersects(&others)
754 })
755 .map(|extra| extra as u32)
756 .collect();
757 extras.sort_by_key(|extra| {
758 (self.binding_keys[*extra as usize].count(), *extra)
759 });
760 let satisfiers = prune_arms(&self.subsumed, extras);
761 if !satisfiers.is_empty() {
762 consider(Ob {
763 kind: Kind::Colocated,
764 subject: vec![source as u32],
765 satisfiers,
766 });
767 }
768 }
769 }
770 }
771 if best.is_none() && chosen_count > 1 {
773 let components = self.components(chosen);
774 if components.len() > 1 {
775 let mut mergers: Vec<u32> = Vec::new();
776 let mut adjacent: Vec<u32> = Vec::new();
777 for node in 0..self.n_nodes {
778 if chosen.contains(node) {
779 continue;
780 }
781 let touched = components
782 .iter()
783 .filter(|component| self.join_partners[node].intersects(component))
784 .count();
785 if touched >= 1 {
786 adjacent.push(node as u32);
787 }
788 if touched >= 2 {
789 mergers.push(node as u32);
790 }
791 }
792 let satisfiers = prune_arms(
793 &self.subsumed,
794 if mergers.is_empty() { adjacent } else { mergers },
795 );
796 if !satisfiers.is_empty() {
797 let mut subject: Vec<u32> = components
798 .iter()
799 .map(|component| component.ones().next().unwrap() as u32)
800 .collect();
801 subject.sort();
802 best = Some(Ob {
803 kind: Kind::Connected,
804 subject,
805 satisfiers,
806 });
807 }
808 }
809 }
810 best
811 }
812}
813
814fn prune_arms(subsumed: &[Option<u32>], satisfiers: Vec<u32>) -> Vec<u32> {
817 if subsumed.iter().all(|entry| entry.is_none()) {
818 return satisfiers;
819 }
820 let present: HashSet<u32> = satisfiers.iter().copied().collect();
821 let kept: Vec<u32> = satisfiers
822 .iter()
823 .copied()
824 .filter(|node| match subsumed[*node as usize] {
825 Some(union_node) => !present.contains(&union_node),
826 None => true,
827 })
828 .collect();
829 kept
830}
831
832pub fn enumerate_covers(spec: &NetworkSpec) -> (Vec<Vec<String>>, Option<LimitKind>) {
836 let net = Net::build(spec);
837 let mut covers: Vec<Bits> = Vec::new();
838 let mut emitted: Vec<(Bits, Vec<u8>)> = Vec::new();
839 let mut visited: HashSet<Bits> = HashSet::new();
840 let mut level: Vec<Bits> = vec![Bits::new(net.n_nodes)];
841 let mut limit: Option<LimitKind> = None;
842 'walk: while !level.is_empty() {
843 let mut next_level: Vec<Bits> = Vec::new();
846 for chosen in level {
847 if visited.contains(&chosen) {
848 continue;
849 }
850 if covers.len() >= spec.cover_limit {
851 limit = Some(LimitKind::Covers);
852 break 'walk;
853 }
854 if visited.len() >= spec.state_limit {
855 limit = Some(LimitKind::States);
856 break 'walk;
857 }
858 visited.insert(chosen.clone());
859 if !emitted.is_empty() {
860 let profile = net.profile(&chosen);
861 if emitted.iter().any(|(prior, prior_profile)| {
862 prior.is_proper_subset(&chosen) && *prior_profile == profile
863 }) {
864 continue;
865 }
866 }
867 if let Some(first) = net.scarcest_pending(&chosen) {
868 for node in &first.satisfiers {
869 next_level.push(chosen.with(*node as usize));
870 }
871 continue;
872 }
873 let profile = net.profile(&chosen);
874 for (position, _) in net.terminals.iter().enumerate() {
875 if net.full_binder_bits[position].intersects(&chosen) {
876 continue;
877 }
878 for node in &net.full_binders[position] {
881 if net.binders[position].contains(node) {
882 next_level.push(chosen.with(*node as usize));
883 }
884 }
885 }
886 covers.push(chosen.clone());
887 emitted.push((chosen, profile));
888 }
889 level = next_level;
890 }
891 let names: Vec<Vec<String>> = covers
892 .iter()
893 .map(|cover| {
894 cover
895 .ones()
896 .map(|node| net.node_names[node].clone())
897 .collect()
898 })
899 .collect();
900 (names, limit)
901}
902
903#[cfg(test)]
904mod tests {
905 use super::*;
906
907 fn candidate(node: &str, bindings: &[(&str, bool)], grain: &[&str]) -> CandidateSpec {
908 CandidateSpec {
909 node: node.to_string(),
910 bindings: bindings
911 .iter()
912 .map(|(a, p)| (a.to_string(), *p))
913 .collect(),
914 grain: grain.iter().map(|g| g.to_string()).collect(),
915 owes: Vec::new(),
916 partial_is_full: false,
917 }
918 }
919
920 fn spec(terminals: &[&str], candidates: Vec<CandidateSpec>) -> NetworkSpec {
921 NetworkSpec {
922 terminals: terminals.iter().map(|t| t.to_string()).collect(),
923 candidates,
924 axis_families: vec![],
925 join_requirements: vec![],
926 subsumed_arms: vec![],
927 cover_limit: 4096,
928 state_limit: 10_000,
929 }
930 }
931
932 #[test]
933 fn single_source_cover() {
934 let s = spec(
935 &["a", "b"],
936 vec![candidate("ds~one", &[("a", false), ("b", false)], &["a"])],
937 );
938 let (covers, limit) = enumerate_covers(&s);
939 assert_eq!(limit, None);
940 assert_eq!(covers, vec![vec!["ds~one".to_string()]]);
941 }
942
943 #[test]
944 fn state_limit_reports_states() {
945 let s = NetworkSpec {
946 state_limit: 1,
947 ..spec(
948 &["a", "b"],
949 vec![
950 candidate("ds~one", &[("a", false), ("k", false)], &["a"]),
951 candidate("ds~two", &[("b", false), ("k", false)], &["b"]),
952 ],
953 )
954 };
955 let (covers, limit) = enumerate_covers(&s);
956 assert_eq!(limit, Some(LimitKind::States));
957 assert!(covers.is_empty());
958 }
959
960 #[test]
961 fn soft_branch_emits_full_binder_upgrade() {
962 let s = spec(
965 &["launch", "name"],
966 vec![
967 candidate(
968 "ds~launch",
969 &[("launch", false), ("vehicle", false), ("name", true)],
970 &["launch"],
971 ),
972 candidate(
973 "ds~vehicle",
974 &[("vehicle", false), ("name", false)],
975 &["vehicle"],
976 ),
977 ],
978 );
979 let (covers, limit) = enumerate_covers(&s);
980 assert_eq!(limit, None);
981 assert_eq!(
982 covers,
983 vec![
984 vec!["ds~launch".to_string()],
985 vec!["ds~launch".to_string(), "ds~vehicle".to_string()],
986 ]
987 );
988 }
989
990 #[test]
991 fn subsumed_arm_branches_only_onto_the_union() {
992 let mut s = spec(
993 &["k"],
994 vec![
995 candidate("ds~arm_a", &[("k", true)], &["k"]),
996 candidate("ds~arm_b", &[("k", true)], &["k"]),
997 candidate("ds~union", &[("k", false)], &["k"]),
998 ],
999 );
1000 s.subsumed_arms = vec![
1001 ("ds~arm_a".to_string(), "ds~union".to_string()),
1002 ("ds~arm_b".to_string(), "ds~union".to_string()),
1003 ];
1004 let (covers, limit) = enumerate_covers(&s);
1005 assert_eq!(limit, None);
1006 assert_eq!(covers, vec![vec!["ds~union".to_string()]]);
1007 }
1008
1009 #[test]
1010 fn disconnected_cover_is_bridged() {
1011 let s = spec(
1012 &["a_val", "b_val"],
1013 vec![
1014 candidate("ds~fact_a", &[("a_id", false), ("sk", false), ("a_val", false)], &["a_id"]),
1015 candidate("ds~fact_b", &[("b_id", false), ("ok", false), ("b_val", false)], &["b_id"]),
1016 candidate("ds~bridge", &[("sk", false), ("ok", false)], &["sk", "ok"]),
1017 ],
1018 );
1019 let (covers, limit) = enumerate_covers(&s);
1020 assert_eq!(limit, None);
1021 assert!(covers.iter().any(|cover| cover.len() == 3));
1022 assert!(!covers.contains(&vec![
1024 "ds~fact_a".to_string(),
1025 "ds~fact_b".to_string()
1026 ]));
1027 }
1028
1029 #[test]
1030 fn empty_terminals_emit_the_empty_cover() {
1031 let s = spec(&[], vec![candidate("ds~one", &[("a", false)], &["a"])]);
1032 let (covers, limit) = enumerate_covers(&s);
1033 assert_eq!(limit, None);
1034 assert_eq!(covers, vec![Vec::<String>::new()]);
1035 }
1036}