1use super::Algebra;
2use crate::{
3 BottomUpTa, CondensedTa, DetBottomUpTa, Explicit, FxHashMap, IndexedBottomUpTa, InvHom,
4 OutputCodec, ProbabilityScorer, Signature, SpaceJoinCodec, StateId, StateUniverse, Symbol,
5 SymbolSet, TextVisualizationCodec, TopDownTa, VisualRepresentation, WeightScorer,
6 heuristic::IntersectionHeuristic,
7 homomorphism::{HomLabel, Homomorphism},
8};
9use fixedbitset::FixedBitSet;
10use smallvec::SmallVec;
11use std::convert::Infallible;
12use std::fmt;
13
14pub const CONCAT: &str = "*";
16
17#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
19pub struct Span {
20 pub start: usize,
22 pub end: usize,
24}
25
26impl fmt::Display for Span {
27 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
28 write!(f, "[{}-{}]", self.start, self.end)
29 }
30}
31
32impl Span {
33 pub fn new(start: usize, end: usize) -> Self {
35 Self { start, end }
36 }
37
38 pub fn len(self) -> usize {
40 self.end - self.start
41 }
42
43 pub fn is_empty(self) -> bool {
45 self.start == self.end
46 }
47}
48
49#[derive(Clone, Copy, Debug, PartialEq, Eq)]
51pub(crate) struct SpanProductSibling {
52 pub(crate) product: StateId,
53 pub(crate) right_state: StateId,
54}
55
56#[derive(Debug, Default)]
62pub(crate) struct SpanProductSiblingFinder {
63 left_slots_by_end: FxHashMap<(usize, StateId), usize>,
64 right_slots_by_start: FxHashMap<(usize, StateId), usize>,
65 left_lists: Vec<Vec<SpanProductSibling>>,
66 right_lists: Vec<Vec<SpanProductSibling>>,
67 seen_left: FixedBitSet,
68 seen_right: FixedBitSet,
69}
70
71impl SpanProductSiblingFinder {
72 pub(crate) fn activate(
76 &mut self,
77 product: StateId,
78 left_state: StateId,
79 right_state: StateId,
80 span: Span,
81 position: usize,
82 ) -> bool {
83 match position {
84 0 => {
85 if self.seen_left.len() <= product.index() {
86 self.seen_left.grow(product.index() + 1);
87 }
88 if self.seen_left.contains(product.index()) {
89 return false;
90 }
91 self.seen_left.set(product.index(), true);
92 let slot = *self
93 .left_slots_by_end
94 .entry((span.end, left_state))
95 .or_insert_with(|| {
96 let slot = self.left_lists.len();
97 self.left_lists.push(Vec::new());
98 slot
99 });
100 self.left_lists[slot].push(SpanProductSibling {
101 product,
102 right_state,
103 });
104 true
105 }
106 1 => {
107 if self.seen_right.len() <= product.index() {
108 self.seen_right.grow(product.index() + 1);
109 }
110 if self.seen_right.contains(product.index()) {
111 return false;
112 }
113 self.seen_right.set(product.index(), true);
114 let slot = *self
115 .right_slots_by_start
116 .entry((span.start, left_state))
117 .or_insert_with(|| {
118 let slot = self.right_lists.len();
119 self.right_lists.push(Vec::new());
120 slot
121 });
122 self.right_lists[slot].push(SpanProductSibling {
123 product,
124 right_state,
125 });
126 true
127 }
128 _ => false,
129 }
130 }
131
132 #[cfg(test)]
133 pub(crate) fn sibling_products_into(
134 &self,
135 span: Span,
136 position: usize,
137 required_left: StateId,
138 out: &mut Vec<SpanProductSibling>,
139 ) {
140 out.clear();
141 out.extend_from_slice(self.siblings_slice(span, position, required_left));
142 }
143
144 pub(crate) fn siblings_slice(
151 &self,
152 span: Span,
153 position: usize,
154 required_left: StateId,
155 ) -> &[SpanProductSibling] {
156 let slot = match position {
157 0 => self
158 .right_slots_by_start
159 .get(&(span.end, required_left))
160 .copied()
161 .and_then(|slot| self.right_lists.get(slot)),
162 1 => self
163 .left_slots_by_end
164 .get(&(span.start, required_left))
165 .copied()
166 .and_then(|slot| self.left_lists.get(slot)),
167 _ => None,
168 };
169 slot.map_or(&[], Vec::as_slice)
170 }
171}
172
173#[derive(Clone, Debug)]
179pub struct StringAlgebra {
180 signature: Signature,
181 concat: Symbol,
182 display_codec: TextVisualizationCodec<SpaceJoinCodec>,
183}
184
185impl StringAlgebra {
186 pub fn new() -> Self {
188 let mut signature = Signature::new();
189 let concat = signature.intern(CONCAT.to_owned(), 2).unwrap();
190 Self {
191 signature,
192 concat,
193 display_codec: TextVisualizationCodec::new(SpaceJoinCodec),
194 }
195 }
196
197 pub fn with_signature(mut signature: Signature) -> Self {
199 let concat = signature.intern(CONCAT.to_owned(), 2).unwrap();
200 Self {
201 signature,
202 concat,
203 display_codec: TextVisualizationCodec::new(SpaceJoinCodec),
204 }
205 }
206
207 pub fn concat_symbol(&self) -> Symbol {
209 self.concat
210 }
211
212 pub fn intern_word(&mut self, word: impl Into<String>) -> Symbol {
214 self.signature.intern(word.into(), 0).unwrap()
215 }
216
217 pub fn parse_string(&mut self, input: &str) -> Vec<Symbol> {
219 input
220 .split_whitespace()
221 .map(|word| self.intern_word(word.to_owned()))
222 .collect()
223 }
224
225 pub fn decompose(&self, sentence: Vec<Symbol>) -> StringDecompositionAutomaton {
227 StringDecompositionAutomaton::new(self.concat, sentence)
228 }
229}
230
231impl Default for StringAlgebra {
232 fn default() -> Self {
233 Self::new()
234 }
235}
236
237impl Algebra for StringAlgebra {
238 type InternalValue = Vec<Symbol>;
239 type Value = Vec<String>;
240 type ParseError = Infallible;
241
242 fn signature(&self) -> &Signature {
243 &self.signature
244 }
245
246 fn evaluate(
247 &self,
248 symbol: Symbol,
249 children: &[Self::InternalValue],
250 ) -> Option<Self::InternalValue> {
251 if symbol == self.concat {
252 let [left, right] = children else {
253 return None;
254 };
255 let mut out = Vec::with_capacity(left.len() + right.len());
256 out.extend_from_slice(left);
257 out.extend_from_slice(right);
258 Some(out)
259 } else if children.is_empty() {
260 Some(vec![symbol])
261 } else {
262 None
263 }
264 }
265
266 fn parse_object(&mut self, input: &str) -> Result<Self::InternalValue, Self::ParseError> {
267 Ok(self.parse_string(input))
268 }
269
270 fn to_external(&self, value: &Self::InternalValue) -> Self::Value {
271 value
272 .iter()
273 .map(|&symbol| self.signature.resolve(symbol).to_owned())
274 .collect()
275 }
276
277 fn visualize(&self, value: &Self::Value) -> VisualRepresentation {
278 self.display_codec.encode(value)
279 }
280}
281
282#[derive(Clone, Debug)]
284pub struct StringDecompositionAutomaton {
285 concat: Symbol,
286 sentence: Vec<Symbol>,
287 positions_by_word: FxHashMap<Symbol, Vec<usize>>,
288}
289
290impl StringDecompositionAutomaton {
291 pub fn new(concat: Symbol, sentence: Vec<Symbol>) -> Self {
293 let mut positions_by_word = FxHashMap::default();
294 for (position, &word) in sentence.iter().enumerate() {
295 positions_by_word
296 .entry(word)
297 .or_insert_with(Vec::new)
298 .push(position);
299 }
300 Self {
301 concat,
302 sentence,
303 positions_by_word,
304 }
305 }
306
307 pub fn concat_symbol(&self) -> Symbol {
309 self.concat
310 }
311
312 pub fn len(&self) -> usize {
314 self.sentence.len()
315 }
316
317 pub fn sentence(&self) -> &[Symbol] {
320 &self.sentence
321 }
322
323 pub fn is_empty(&self) -> bool {
325 self.sentence.is_empty()
326 }
327
328 pub fn rule_count(&self) -> usize {
330 let n = self.len();
331 n + n.saturating_sub(1) * n * (n + 1) / 6
332 }
333
334 fn valid_span(&self, span: Span) -> bool {
335 span.start < span.end && span.end <= self.len()
336 }
337}
338
339impl BottomUpTa for StringDecompositionAutomaton {
340 type State = Span;
341
342 fn step(&self, f: Symbol, children: &[Span], out: &mut dyn FnMut(Span)) {
343 if f == self.concat {
344 let [left, right] = children else {
345 return;
346 };
347 if self.valid_span(*left) && self.valid_span(*right) && left.end == right.start {
348 out(Span::new(left.start, right.end));
349 }
350 return;
351 }
352
353 if !children.is_empty() {
354 return;
355 }
356 if let Some(positions) = self.positions_by_word.get(&f) {
357 for &i in positions {
358 out(Span::new(i, i + 1));
359 }
360 }
361 }
362
363 fn is_accepting(&self, q: &Span) -> bool {
364 *q == Span::new(0, self.len())
365 }
366}
367
368impl DetBottomUpTa for StringDecompositionAutomaton {
369 fn step_det(&self, f: Symbol, children: &[Span]) -> Option<Span> {
370 if f == self.concat {
371 let [left, right] = children else {
372 return None;
373 };
374 return (self.valid_span(*left) && self.valid_span(*right) && left.end == right.start)
375 .then_some(Span::new(left.start, right.end));
376 }
377
378 if !children.is_empty() {
379 return None;
380 }
381
382 self.positions_by_word
383 .get(&f)
384 .and_then(|positions| (positions.len() == 1).then_some(positions[0]))
385 .map(|i| Span::new(i, i + 1))
386 }
387}
388
389impl StateUniverse for StringDecompositionAutomaton {
390 fn all_states(&self, out: &mut dyn FnMut(Span)) {
391 for start in 0..self.len() {
392 for end in start + 1..=self.len() {
393 out(Span::new(start, end));
394 }
395 }
396 }
397}
398
399impl IndexedBottomUpTa for StringDecompositionAutomaton {
400 fn step_partial(
401 &self,
402 f: Symbol,
403 position: usize,
404 state_at_position: &Span,
405 out: &mut dyn FnMut(&[Span], Span),
406 ) {
407 if f != self.concat || !self.valid_span(*state_at_position) {
408 return;
409 }
410
411 match position {
412 0 => {
413 let left = *state_at_position;
414 for end in left.end + 1..=self.len() {
415 let right = Span::new(left.end, end);
416 let children = [left, right];
417 out(&children, Span::new(left.start, end));
418 }
419 }
420 1 => {
421 let right = *state_at_position;
422 for start in 0..right.start {
423 let left = Span::new(start, right.start);
424 let children = [left, right];
425 out(&children, Span::new(start, right.end));
426 }
427 }
428 _ => {}
429 }
430 }
431}
432
433impl TopDownTa for StringDecompositionAutomaton {
434 fn step_topdown(&self, parent: &Span, out: &mut dyn FnMut(Symbol, &[Span])) {
435 if !self.valid_span(*parent) {
436 return;
437 }
438 if parent.len() == 1 {
439 let word = self.sentence[parent.start];
440 out(word, &[]);
441 return;
442 }
443 for split in parent.start + 1..parent.end {
444 let children = [Span::new(parent.start, split), Span::new(split, parent.end)];
445 out(self.concat, &children);
446 }
447 }
448
449 fn initial_states(&self, out: &mut dyn FnMut(Span)) {
450 if !self.is_empty() {
451 out(Span::new(0, self.len()));
452 }
453 }
454}
455
456impl CondensedTa for StringDecompositionAutomaton {
457 fn condensed_rules(&self, out: &mut dyn FnMut(&[Span], &SymbolSet, Span)) {
458 let mut lexical_children = SmallVec::<[Span; 0]>::new();
459 for (i, &word) in self.sentence.iter().enumerate() {
460 let mut symbols = SymbolSet::new();
461 symbols.insert(word);
462 out(&lexical_children, &symbols, Span::new(i, i + 1));
463 lexical_children.clear();
464 }
465
466 let mut concat_symbols = SymbolSet::new();
467 concat_symbols.insert(self.concat);
468 for start in 0..self.len() {
469 for split in start + 1..self.len() {
470 for end in split + 1..=self.len() {
471 let children = [Span::new(start, split), Span::new(split, end)];
472 out(&children, &concat_symbols, Span::new(start, end));
473 }
474 }
475 }
476 }
477
478 fn condensed_nullary_rules(&self, out: &mut dyn FnMut(&SymbolSet, Span)) {
479 for (i, &word) in self.sentence.iter().enumerate() {
480 let mut symbols = SymbolSet::new();
481 symbols.insert(word);
482 out(&symbols, Span::new(i, i + 1));
483 }
484 }
485
486 fn condensed_rules_by_child(
487 &self,
488 position: usize,
489 state: &Span,
490 out: &mut dyn FnMut(&[Span], &SymbolSet, Span),
491 ) {
492 if !self.valid_span(*state) {
493 return;
494 }
495
496 let mut symbols = SymbolSet::new();
497 symbols.insert(self.concat);
498 match position {
499 0 => {
500 let left = *state;
501 for end in left.end + 1..=self.len() {
502 let children = [left, Span::new(left.end, end)];
503 out(&children, &symbols, Span::new(left.start, end));
504 }
505 }
506 1 => {
507 let right = *state;
508 for start in 0..right.start {
509 let children = [Span::new(start, right.start), right];
510 out(&children, &symbols, Span::new(start, right.end));
511 }
512 }
513 _ => {}
514 }
515 }
516}
517
518#[derive(Clone, Copy, Debug, PartialEq, Eq)]
524enum YieldToken {
525 Word,
527 Child(usize),
529}
530
531fn walk_frontier(
533 arena: &packed_term_arena::tree::TreeArena<HomLabel>,
534 node: packed_term_arena::tree::Tree,
535) -> Vec<YieldToken> {
536 match *arena.get_label(node) {
537 HomLabel::Var(i) => vec![YieldToken::Child(i)],
538 HomLabel::Symbol(_) => {
539 let children = arena.get_children(node);
540 if children.is_empty() {
541 vec![YieldToken::Word]
542 } else {
543 children
544 .iter()
545 .flat_map(|&c| walk_frontier(arena, c))
546 .collect()
547 }
548 }
549 }
550}
551
552#[derive(Clone, Debug)]
554struct YieldTemplate {
555 tokens: Vec<YieldToken>,
557}
558
559impl YieldTemplate {
560 fn word_count(&self) -> usize {
562 self.tokens
563 .iter()
564 .filter(|&&t| t == YieldToken::Word)
565 .count()
566 }
567
568 fn children_in_order(&self) -> Vec<usize> {
570 let mut seen = Vec::new();
571 for &t in &self.tokens {
572 if let YieldToken::Child(i) = t
573 && !seen.contains(&i)
574 {
575 seen.push(i);
576 }
577 }
578 seen
579 }
580
581 fn words_left_of_child(&self, p: usize) -> usize {
583 let mut count = 0;
584 for &t in &self.tokens {
585 match t {
586 YieldToken::Word => count += 1,
587 YieldToken::Child(i) if i == p => break,
588 _ => {}
589 }
590 }
591 count
592 }
593
594 fn words_right_of_child(&self, p: usize) -> usize {
596 let mut count = 0;
597 let mut after = false;
598 for &t in &self.tokens {
599 match t {
600 YieldToken::Child(i) if i == p => after = true,
601 YieldToken::Word if after => count += 1,
602 _ => {}
603 }
604 }
605 count
606 }
607
608 fn children_left_of(&self, p: usize) -> Vec<usize> {
610 let mut result = Vec::new();
611 for &t in &self.tokens {
612 match t {
613 YieldToken::Child(i) if i == p => break,
614 YieldToken::Child(i) if !result.contains(&i) => {
615 result.push(i);
616 }
617 _ => {}
618 }
619 }
620 result
621 }
622
623 fn children_right_of(&self, p: usize) -> Vec<usize> {
625 let mut result = Vec::new();
626 let mut after = false;
627 for &t in &self.tokens {
628 match t {
629 YieldToken::Child(i) if i == p => after = true,
630 YieldToken::Child(i) if after && !result.contains(&i) => {
631 result.push(i);
632 }
633 _ => {}
634 }
635 }
636 result
637 }
638}
639
640pub(crate) struct SxHeuristic {
648 sx: Vec<Box<[f64]>>,
650 n: usize,
652 stride: usize,
654 zero: f64,
656}
657
658impl IntersectionHeuristic<StringDecompositionAutomaton> for SxHeuristic {
659 fn outside_estimate(&self, left: StateId, span: &Span) -> f64 {
660 let l = span.start;
661 let r = self.n.saturating_sub(span.end);
662 self.sx
663 .get(left.index())
664 .and_then(|row| row.get(l * self.stride + r))
665 .copied()
666 .unwrap_or(self.zero)
667 }
668}
669
670impl IntersectionHeuristic<InvHom<'_, StringDecompositionAutomaton>> for SxHeuristic {
671 fn outside_estimate(&self, left: StateId, span: &Span) -> f64 {
672 <Self as IntersectionHeuristic<StringDecompositionAutomaton>>::outside_estimate(
674 self, left, span,
675 )
676 }
677}
678
679impl SxHeuristic {
680 fn lookup_lr(&self, left: StateId, l: usize, r: usize) -> f64 {
681 self.sx
682 .get(left.index())
683 .and_then(|row| row.get(l * self.stride + r))
684 .copied()
685 .unwrap_or(self.zero)
686 }
687
688 pub fn new(grammar: &Explicit, hom: &Homomorphism, concat: Symbol, n: usize) -> Self {
694 Self::new_with(grammar, hom, concat, n, &ProbabilityScorer)
695 }
696
697 pub fn new_with<S: WeightScorer>(
698 grammar: &Explicit,
699 hom: &Homomorphism,
700 concat: Symbol,
701 n: usize,
702 scorer: &S,
703 ) -> Self {
704 let num_states = grammar.num_states() as usize;
705 let arena = hom.arena();
706
707 let rules: Vec<_> = grammar.rules().collect();
711
712 let templates: Vec<Option<YieldTemplate>> = rules
715 .iter()
716 .map(|rule| {
717 hom.get(rule.symbol).map(|term| {
718 let tokens = walk_frontier(arena, term);
719 let _ = concat; YieldTemplate { tokens }
725 })
726 })
727 .collect();
728
729 const INF: usize = usize::MAX / 2;
734 let mut minwidth = vec![INF; num_states];
735
736 let mut changed = true;
738 while changed {
739 changed = false;
740 for (rule_idx, rule) in rules.iter().enumerate() {
741 let Some(tmpl) = &templates[rule_idx] else {
742 continue;
743 };
744 let t_words = tmpl.word_count();
745 let children = tmpl.children_in_order();
746 let arity = children.len();
747
748 let child_min_sum: usize = if arity == 0 {
750 0
751 } else {
752 let mut sum: usize = 0;
753 let mut feasible = true;
754 for &ci in &children {
755 let child_state = rule.children.get(ci).copied();
756 let child_state = match child_state {
757 Some(s) if !s.is_stuck() => s,
758 _ => {
759 feasible = false;
760 break;
761 }
762 };
763 let mw = minwidth[child_state.index()];
764 if mw == INF {
765 feasible = false;
766 break;
767 }
768 sum = sum.saturating_add(mw);
769 }
770 if !feasible {
771 continue;
772 }
773 sum
774 };
775
776 let new_min = t_words.saturating_add(child_min_sum);
777 let ri = rule.result.index();
778 if new_min < minwidth[ri] {
779 minwidth[ri] = new_min;
780 changed = true;
781 }
782 }
783 }
784
785 let mut bi = vec![vec![scorer.zero(); n + 1]; num_states];
790
791 for (rule_idx, rule) in rules.iter().enumerate() {
804 let Some(tmpl) = &templates[rule_idx] else {
805 continue;
806 };
807 let t_words = tmpl.word_count();
808 let children = tmpl.children_in_order();
809 if !children.is_empty() {
810 continue;
811 } if t_words == 0 || t_words > n {
813 continue;
814 }
815 let ri = rule.result.index();
816 let candidate = scorer.rule_score(rule.weight);
817 if scorer.better(candidate, bi[ri][t_words]) {
818 bi[ri][t_words] = candidate;
819 }
820 }
821
822 for w in 0..=n {
825 let mut inner_changed = true;
827 while inner_changed {
828 inner_changed = false;
829 for (rule_idx, rule) in rules.iter().enumerate() {
830 let Some(tmpl) = &templates[rule_idx] else {
831 continue;
832 };
833 let t_words = tmpl.word_count();
834 let children = tmpl.children_in_order();
835 if children.len() != 1 || t_words != 0 {
836 continue;
837 }
838 let child_idx = children[0];
839 let child_state = match rule.children.get(child_idx) {
840 Some(&s) if !s.is_stuck() => s,
841 _ => continue,
842 };
843 let child_bi = bi[child_state.index()][w];
844 if child_bi == scorer.zero() {
845 continue;
846 }
847 let candidate = scorer.times(scorer.rule_score(rule.weight), child_bi);
848 let ri = rule.result.index();
849 if scorer.better(candidate, bi[ri][w]) {
850 bi[ri][w] = candidate;
851 inner_changed = true;
852 }
853 }
854 }
855
856 for (rule_idx, rule) in rules.iter().enumerate() {
859 let Some(tmpl) = &templates[rule_idx] else {
860 continue;
861 };
862 let t_words = tmpl.word_count();
863 let children = tmpl.children_in_order();
864 let arity = children.len();
865
866 if arity == 0 {
868 continue;
869 }
870 if arity == 1 && t_words == 0 {
871 continue;
872 }
873
874 if t_words > w {
876 continue;
877 }
878 let remaining = w - t_words;
879
880 if arity == 1 {
881 let child_idx = children[0];
883 let child_state = match rule.children.get(child_idx) {
884 Some(&s) if !s.is_stuck() => s,
885 _ => continue,
886 };
887 let w_child = remaining;
888 if w_child > n {
889 continue;
890 }
891 let child_bi = bi[child_state.index()][w_child];
892 if child_bi == scorer.zero() {
893 continue;
894 }
895 let candidate = scorer.times(scorer.rule_score(rule.weight), child_bi);
896 let ri = rule.result.index();
897 if scorer.better(candidate, bi[ri][w]) {
898 bi[ri][w] = candidate;
899 }
900 } else if arity == 2 {
901 let child0_idx = children[0];
903 let child1_idx = children[1];
904 let child0_state = match rule.children.get(child0_idx) {
905 Some(&s) if !s.is_stuck() => s,
906 _ => continue,
907 };
908 let child1_state = match rule.children.get(child1_idx) {
909 Some(&s) if !s.is_stuck() => s,
910 _ => continue,
911 };
912 let mw0 = minwidth[child0_state.index()];
913 let mw1 = minwidth[child1_state.index()];
914
915 for w0 in mw0..=remaining.saturating_sub(if mw1 < INF { mw1 } else { break }) {
916 let w1 = remaining - w0;
917 if w1 < mw1 || mw1 == INF {
918 continue;
919 }
920 let bi0 = bi[child0_state.index()][w0];
921 if bi0 == scorer.zero() {
922 continue;
923 }
924 let bi1 = bi[child1_state.index()][w1];
925 if bi1 == scorer.zero() {
926 continue;
927 }
928 let candidate =
929 scorer.times(scorer.times(scorer.rule_score(rule.weight), bi0), bi1);
930 let ri = rule.result.index();
931 if scorer.better(candidate, bi[ri][w]) {
932 bi[ri][w] = candidate;
933 }
934 }
935 } else {
936 let child_states: Vec<_> = children
938 .iter()
939 .map(|&ci| rule.children.get(ci).copied().filter(|s| !s.is_stuck()))
940 .collect();
941 if child_states.iter().any(|s| s.is_none()) {
942 continue;
943 }
944 let child_states: Vec<StateId> =
945 child_states.into_iter().map(|s| s.unwrap()).collect();
946 let mins: Vec<usize> =
947 child_states.iter().map(|s| minwidth[s.index()]).collect();
948 if mins.contains(&INF) {
949 continue;
950 }
951 let min_sum: usize = mins.iter().sum();
952 if remaining < min_sum {
953 continue;
954 }
955
956 let mut best_prod = scorer.zero();
958 enumerate_splits(
959 &child_states,
960 &mins,
961 remaining,
962 0,
963 1.0,
964 rule.weight,
965 &bi,
966 scorer,
967 &mut |prod| {
968 if scorer.better(prod, best_prod) {
969 best_prod = prod;
970 }
971 },
972 );
973 if best_prod != scorer.zero() {
974 let ri = rule.result.index();
975 if scorer.better(best_prod, bi[ri][w]) {
976 bi[ri][w] = best_prod;
977 }
978 }
979 }
980 }
981 }
982
983 let stride = n + 1;
989 let mut sx = vec![vec![scorer.zero(); stride * stride]; num_states];
990
991 grammar.initial_states(&mut |state| {
993 if !state.is_stuck() && state.index() < num_states {
994 sx[state.index()][0] = scorer.one(); }
996 });
997
998 for lr in 0..=n {
1000 let mut level_changed = true;
1004 while level_changed {
1005 level_changed = false;
1006 for (rule_idx, rule) in rules.iter().enumerate() {
1007 let Some(tmpl) = &templates[rule_idx] else {
1008 continue;
1009 };
1010 let t_words = tmpl.word_count();
1011 let children = tmpl.children_in_order();
1012 if children.len() != 1 || t_words != 0 {
1013 continue;
1014 }
1015
1016 let child_idx = children[0];
1017 let child_state = match rule.children.get(child_idx) {
1018 Some(&s) if !s.is_stuck() => s,
1019 _ => continue,
1020 };
1021
1022 let parent_state = rule.result;
1023 for l in 0..=lr {
1025 let r = lr - l;
1026 if l + r > n {
1027 continue;
1028 }
1029 let idx = l * stride + r;
1030 let parent_sx = sx[parent_state.index()][idx];
1031 if parent_sx == scorer.zero() {
1032 continue;
1033 }
1034 let candidate = scorer.times(parent_sx, scorer.rule_score(rule.weight));
1035 if scorer.better(candidate, sx[child_state.index()][idx]) {
1036 sx[child_state.index()][idx] = candidate;
1037 level_changed = true;
1038 }
1039 }
1040 }
1041 }
1042
1043 for (rule_idx, rule) in rules.iter().enumerate() {
1046 let Some(tmpl) = &templates[rule_idx] else {
1047 continue;
1048 };
1049 let t_words = tmpl.word_count();
1050 let children_order = tmpl.children_in_order();
1051 let arity = children_order.len();
1052
1053 if arity == 0 {
1054 continue;
1055 } if arity == 1 && t_words == 0 {
1059 continue;
1060 }
1061
1062 let parent_state = rule.result;
1063
1064 for l_a in 0..=lr {
1065 let r_a = lr - l_a;
1066 if l_a + r_a > n {
1067 continue;
1068 }
1069 let parent_sx = sx[parent_state.index()][l_a * stride + r_a];
1070 if parent_sx == scorer.zero() {
1071 continue;
1072 }
1073
1074 let parent_width = n - l_a - r_a;
1075 if t_words > parent_width {
1076 continue;
1077 }
1078 let remaining_for_children = parent_width - t_words;
1079
1080 if arity == 1 {
1081 let child_idx = children_order[0];
1083 let child_state = match rule.children.get(child_idx) {
1084 Some(&s) if !s.is_stuck() => s,
1085 _ => continue,
1086 };
1087 let wl_p = tmpl.words_left_of_child(child_idx);
1088 let wr_p = tmpl.words_right_of_child(child_idx);
1089 let l_c = l_a + wl_p;
1091 let r_c = r_a + wr_p;
1092 if l_c + r_c > n {
1093 continue;
1094 }
1095 let candidate = scorer.times(parent_sx, scorer.rule_score(rule.weight));
1096 if scorer.better(candidate, sx[child_state.index()][l_c * stride + r_c]) {
1097 sx[child_state.index()][l_c * stride + r_c] = candidate;
1098 }
1099 } else if arity == 2 {
1100 let child0_idx = children_order[0];
1102 let child1_idx = children_order[1];
1103 let child0_state = match rule.children.get(child0_idx) {
1104 Some(&s) if !s.is_stuck() => s,
1105 _ => continue,
1106 };
1107 let child1_state = match rule.children.get(child1_idx) {
1108 Some(&s) if !s.is_stuck() => s,
1109 _ => continue,
1110 };
1111 let mw0 = minwidth[child0_state.index()];
1112 let mw1 = minwidth[child1_state.index()];
1113 let wl0 = tmpl.words_left_of_child(child0_idx);
1114 let wr0 = tmpl.words_right_of_child(child0_idx);
1115 let wl1 = tmpl.words_left_of_child(child1_idx);
1116 let wr1 = tmpl.words_right_of_child(child1_idx);
1117
1118 if mw1 < INF {
1120 let max_w1 = remaining_for_children.saturating_sub(if mw0 < INF {
1121 mw0
1122 } else {
1123 0
1124 });
1125 let child1_bi = &bi[child1_state.index()];
1126 for (w1, &bi1) in
1127 child1_bi.iter().enumerate().take(max_w1 + 1).skip(mw1)
1128 {
1129 let w0 = remaining_for_children - w1;
1130 if mw0 < INF && w0 < mw0 {
1131 continue;
1132 }
1133 if bi1 == scorer.zero() {
1134 continue;
1135 }
1136 let l_c0 = l_a + wl0;
1140 let r_c0 = r_a + wr0 + w1;
1141 if l_c0 + r_c0 > n {
1142 continue;
1143 }
1144 let candidate = scorer.times(
1145 scorer.times(parent_sx, scorer.rule_score(rule.weight)),
1146 bi1,
1147 );
1148 if scorer.better(
1149 candidate,
1150 sx[child0_state.index()][l_c0 * stride + r_c0],
1151 ) {
1152 sx[child0_state.index()][l_c0 * stride + r_c0] = candidate;
1153 }
1154 }
1155 }
1156
1157 if mw0 < INF {
1159 let max_w0 = remaining_for_children.saturating_sub(if mw1 < INF {
1160 mw1
1161 } else {
1162 0
1163 });
1164 let child0_bi = &bi[child0_state.index()];
1165 for (w0, &bi0) in
1166 child0_bi.iter().enumerate().take(max_w0 + 1).skip(mw0)
1167 {
1168 let w1 = remaining_for_children - w0;
1169 if mw1 < INF && w1 < mw1 {
1170 continue;
1171 }
1172 if bi0 == scorer.zero() {
1173 continue;
1174 }
1175 let l_c1 = l_a + wl1 + w0;
1178 let r_c1 = r_a + wr1;
1179 if l_c1 + r_c1 > n {
1180 continue;
1181 }
1182 let candidate = scorer.times(
1183 scorer.times(parent_sx, scorer.rule_score(rule.weight)),
1184 bi0,
1185 );
1186 if scorer.better(
1187 candidate,
1188 sx[child1_state.index()][l_c1 * stride + r_c1],
1189 ) {
1190 sx[child1_state.index()][l_c1 * stride + r_c1] = candidate;
1191 }
1192 }
1193 }
1194 } else {
1195 for (p_pos, &p_child_idx) in children_order.iter().enumerate() {
1198 let child_p_state = match rule.children.get(p_child_idx) {
1199 Some(&s) if !s.is_stuck() => s,
1200 _ => continue,
1201 };
1202
1203 let siblings: Vec<(usize, StateId)> = children_order
1204 .iter()
1205 .enumerate()
1206 .filter(|&(q_pos, _)| q_pos != p_pos)
1207 .map(|(_, &q_child_idx)| {
1208 let q_state = rule
1209 .children
1210 .get(q_child_idx)
1211 .copied()
1212 .unwrap_or(StateId::STUCK);
1213 (q_child_idx, q_state)
1214 })
1215 .collect();
1216
1217 if siblings.iter().any(|(_, s)| s.is_stuck()) {
1218 continue;
1219 }
1220
1221 let sib_states: Vec<StateId> =
1222 siblings.iter().map(|(_, s)| *s).collect();
1223 let sib_mins: Vec<usize> =
1224 sib_states.iter().map(|s| minwidth[s.index()]).collect();
1225 if sib_mins.contains(&INF) {
1226 continue;
1227 }
1228 let sib_min_sum: usize = sib_mins.iter().sum();
1229
1230 let mwp = minwidth[child_p_state.index()];
1231 let max_sibling_total = if mwp < INF {
1232 remaining_for_children.saturating_sub(mwp)
1233 } else {
1234 remaining_for_children
1235 };
1236
1237 if sib_min_sum > max_sibling_total {
1238 continue;
1239 }
1240
1241 let wl_p = tmpl.words_left_of_child(p_child_idx);
1242 let wr_p = tmpl.words_right_of_child(p_child_idx);
1243 let left_sibs = tmpl.children_left_of(p_child_idx);
1244 let right_sibs = tmpl.children_right_of(p_child_idx);
1245
1246 enumerate_sibling_splits(
1248 &sib_states,
1249 &sib_mins,
1250 sib_min_sum,
1251 max_sibling_total,
1252 &bi,
1253 scorer,
1254 &mut |sib_widths: &[usize], sib_bi_prod: f64| {
1255 let mut wl_from_sibs = 0usize;
1257 let mut wr_from_sibs = 0usize;
1258 for (qi, &(q_child_idx, _)) in siblings.iter().enumerate() {
1259 if left_sibs.contains(&q_child_idx) {
1260 wl_from_sibs += sib_widths[qi];
1261 } else if right_sibs.contains(&q_child_idx) {
1262 wr_from_sibs += sib_widths[qi];
1263 }
1264 }
1265 let l_cp = l_a + wl_p + wl_from_sibs;
1266 let r_cp = r_a + wr_p + wr_from_sibs;
1267 if l_cp + r_cp > n {
1268 return;
1269 }
1270 let candidate = scorer.times(
1271 scorer.times(parent_sx, scorer.rule_score(rule.weight)),
1272 sib_bi_prod,
1273 );
1274 if scorer.better(
1275 candidate,
1276 sx[child_p_state.index()][l_cp * stride + r_cp],
1277 ) {
1278 sx[child_p_state.index()][l_cp * stride + r_cp] = candidate;
1279 }
1280 },
1281 );
1282 }
1283 }
1284 }
1285 }
1286 }
1287
1288 let sx_boxed: Vec<Box<[f64]>> = sx.into_iter().map(|v| v.into_boxed_slice()).collect();
1289
1290 SxHeuristic {
1291 sx: sx_boxed,
1292 n,
1293 stride,
1294 zero: scorer.zero(),
1295 }
1296 }
1297
1298 pub fn to_bytes(&self) -> Vec<u8> {
1301 let mut buf = Vec::new();
1302 buf.extend_from_slice(b"SXCACH01");
1303 buf.extend_from_slice(&(self.n as u64).to_le_bytes());
1304 buf.extend_from_slice(&(self.stride as u64).to_le_bytes());
1305 buf.extend_from_slice(&self.zero.to_le_bytes());
1306 buf.extend_from_slice(&(self.sx.len() as u64).to_le_bytes());
1307 for row in &self.sx {
1308 buf.extend_from_slice(&(row.len() as u64).to_le_bytes());
1309 for &v in row.iter() {
1310 buf.extend_from_slice(&v.to_le_bytes());
1311 }
1312 }
1313 buf
1314 }
1315
1316 pub fn from_bytes(bytes: &[u8]) -> Option<Self> {
1318 let mut pos = 0;
1319 let read_u64 = |bytes: &[u8], pos: &mut usize| -> Option<u64> {
1320 let b = bytes.get(*pos..*pos + 8)?;
1321 *pos += 8;
1322 Some(u64::from_le_bytes(b.try_into().ok()?))
1323 };
1324 if bytes.get(..8) != Some(b"SXCACH01") {
1325 return None;
1326 }
1327 pos += 8;
1328 let n = read_u64(bytes, &mut pos)? as usize;
1329 let stride = read_u64(bytes, &mut pos)? as usize;
1330 let b = bytes.get(pos..pos + 8)?;
1331 pos += 8;
1332 let zero = f64::from_le_bytes(b.try_into().ok()?);
1333 let num_states = read_u64(bytes, &mut pos)? as usize;
1334 let mut sx = Vec::with_capacity(num_states);
1335 for _ in 0..num_states {
1336 let row_len = read_u64(bytes, &mut pos)? as usize;
1337 let mut row = Vec::with_capacity(row_len);
1338 for _ in 0..row_len {
1339 let b = bytes.get(pos..pos + 8)?;
1340 pos += 8;
1341 row.push(f64::from_le_bytes(b.try_into().ok()?));
1342 }
1343 sx.push(row.into_boxed_slice());
1344 }
1345 Some(SxHeuristic {
1346 sx,
1347 n,
1348 stride,
1349 zero,
1350 })
1351 }
1352}
1353
1354pub struct UniversalSxHeuristic {
1366 inner: SxHeuristic,
1367}
1368
1369impl UniversalSxHeuristic {
1370 pub fn new(grammar: &Explicit, hom: &Homomorphism, concat: Symbol, n_max: usize) -> Self {
1372 Self {
1373 inner: SxHeuristic::new(grammar, hom, concat, n_max),
1374 }
1375 }
1376
1377 pub fn new_with<S: WeightScorer>(
1379 grammar: &Explicit,
1380 hom: &Homomorphism,
1381 concat: Symbol,
1382 n_max: usize,
1383 scorer: &S,
1384 ) -> Self {
1385 Self {
1386 inner: SxHeuristic::new_with(grammar, hom, concat, n_max, scorer),
1387 }
1388 }
1389
1390 pub fn n_max(&self) -> usize {
1392 self.inner.n
1393 }
1394
1395 pub fn for_sentence(&self, n: usize) -> SentenceSxHeuristic<'_> {
1397 SentenceSxHeuristic { table: self, n }
1398 }
1399
1400 pub fn to_bytes(&self) -> Vec<u8> {
1402 self.inner.to_bytes()
1403 }
1404
1405 pub fn from_bytes(bytes: &[u8]) -> Option<Self> {
1407 SxHeuristic::from_bytes(bytes).map(|inner| Self { inner })
1408 }
1409}
1410
1411#[derive(Clone, Copy)]
1413pub struct SentenceSxHeuristic<'a> {
1414 table: &'a UniversalSxHeuristic,
1415 n: usize,
1416}
1417
1418impl IntersectionHeuristic<StringDecompositionAutomaton> for SentenceSxHeuristic<'_> {
1419 fn outside_estimate(&self, left: StateId, span: &Span) -> f64 {
1420 let l = span.start;
1421 let r = self.n.saturating_sub(span.end);
1422 self.table.inner.lookup_lr(left, l, r)
1423 }
1424}
1425
1426impl IntersectionHeuristic<InvHom<'_, StringDecompositionAutomaton>> for SentenceSxHeuristic<'_> {
1427 fn outside_estimate(&self, left: StateId, span: &Span) -> f64 {
1428 <Self as IntersectionHeuristic<StringDecompositionAutomaton>>::outside_estimate(
1429 self, left, span,
1430 )
1431 }
1432}
1433
1434#[allow(clippy::too_many_arguments)]
1436fn enumerate_splits(
1437 child_states: &[StateId],
1438 mins: &[usize],
1439 remaining: usize,
1440 pos: usize,
1441 acc: f64,
1442 rule_weight: f64,
1443 bi: &[Vec<f64>],
1444 scorer: &impl WeightScorer,
1445 out: &mut impl FnMut(f64),
1446) {
1447 if pos == child_states.len() {
1448 if remaining == 0 {
1449 out(scorer.times(scorer.rule_score(rule_weight), acc));
1450 }
1451 return;
1452 }
1453 let min_w = mins[pos];
1454 let max_w = {
1455 let rest_min: usize = mins[pos + 1..].iter().sum();
1457 remaining.saturating_sub(rest_min)
1458 };
1459 let state = child_states[pos];
1460 for w in min_w..=max_w {
1461 let child_bi = bi[state.index()][w];
1462 if child_bi == scorer.zero() {
1463 continue;
1464 }
1465 enumerate_splits(
1466 child_states,
1467 mins,
1468 remaining - w,
1469 pos + 1,
1470 scorer.times(acc, child_bi),
1471 rule_weight,
1472 bi,
1473 scorer,
1474 out,
1475 );
1476 }
1477}
1478
1479fn enumerate_sibling_splits(
1481 sib_states: &[StateId],
1482 sib_mins: &[usize],
1483 sib_min_sum: usize,
1484 max_total: usize,
1485 bi: &[Vec<f64>],
1486 scorer: &impl WeightScorer,
1487 out: &mut impl FnMut(&[usize], f64),
1488) {
1489 let k = sib_states.len();
1490 let mut widths = vec![0usize; k];
1491 let total_range = sib_min_sum..=max_total;
1492 for total in total_range {
1493 enumerate_sibling_splits_inner(
1495 sib_states,
1496 sib_mins,
1497 total,
1498 0,
1499 &mut widths,
1500 scorer.one(),
1501 bi,
1502 scorer,
1503 out,
1504 );
1505 }
1506}
1507
1508#[allow(clippy::too_many_arguments)]
1509fn enumerate_sibling_splits_inner(
1510 sib_states: &[StateId],
1511 sib_mins: &[usize],
1512 remaining: usize,
1513 pos: usize,
1514 widths: &mut Vec<usize>,
1515 acc: f64,
1516 bi: &[Vec<f64>],
1517 scorer: &impl WeightScorer,
1518 out: &mut impl FnMut(&[usize], f64),
1519) {
1520 if pos == sib_states.len() {
1521 if remaining == 0 {
1522 out(widths, acc);
1523 }
1524 return;
1525 }
1526 let min_w = sib_mins[pos];
1527 let rest_min: usize = sib_mins[pos + 1..].iter().sum();
1528 let max_w = remaining.saturating_sub(rest_min);
1529 let state = sib_states[pos];
1530 for w in min_w..=max_w {
1531 let child_bi = bi[state.index()][w];
1532 if child_bi == scorer.zero() {
1533 continue;
1534 }
1535 widths[pos] = w;
1536 enumerate_sibling_splits_inner(
1537 sib_states,
1538 sib_mins,
1539 remaining - w,
1540 pos + 1,
1541 widths,
1542 scorer.times(acc, child_bi),
1543 bi,
1544 scorer,
1545 out,
1546 );
1547 }
1548}
1549
1550#[cfg(test)]
1551mod tests {
1552 use super::*;
1553
1554 #[test]
1555 fn displays_spans_with_dash_bounds() {
1556 assert_eq!(Span::new(2, 5).to_string(), "[2-5]");
1557 }
1558
1559 #[test]
1560 fn span_product_sibling_finder_returns_adjacent_products_in_both_directions() {
1561 let left_product = StateId(10);
1562 let right_product = StateId(11);
1563 let left_state = StateId(0);
1564 let right_state = StateId(1);
1565 let left_span = Span::new(0, 1);
1566 let right_span = Span::new(1, 3);
1567
1568 let mut finder = SpanProductSiblingFinder::default();
1569 assert!(finder.activate(right_product, right_state, StateId(21), right_span, 1));
1570
1571 let mut products = Vec::new();
1572 finder.sibling_products_into(left_span, 0, right_state, &mut products);
1573 assert_eq!(
1574 products,
1575 vec![SpanProductSibling {
1576 product: right_product,
1577 right_state: StateId(21)
1578 }]
1579 );
1580
1581 let mut finder = SpanProductSiblingFinder::default();
1582 assert!(finder.activate(left_product, left_state, StateId(20), left_span, 0));
1583
1584 products.clear();
1585 finder.sibling_products_into(right_span, 1, left_state, &mut products);
1586 assert_eq!(
1587 products,
1588 vec![SpanProductSibling {
1589 product: left_product,
1590 right_state: StateId(20)
1591 }]
1592 );
1593 }
1594
1595 #[test]
1596 fn span_product_sibling_finder_filters_by_left_state_and_activation() {
1597 let product = StateId(10);
1598 let wanted_left = StateId(0);
1599 let other_left = StateId(1);
1600 let right_state = StateId(20);
1601
1602 let mut finder = SpanProductSiblingFinder::default();
1603 let mut products = Vec::new();
1604 finder.sibling_products_into(Span::new(0, 1), 0, wanted_left, &mut products);
1605 assert!(products.is_empty());
1606
1607 assert!(finder.activate(product, other_left, right_state, Span::new(1, 3), 1));
1608 finder.sibling_products_into(Span::new(0, 1), 0, wanted_left, &mut products);
1609 assert!(products.is_empty());
1610
1611 finder.sibling_products_into(Span::new(0, 1), 0, other_left, &mut products);
1612 assert_eq!(
1613 products,
1614 vec![SpanProductSibling {
1615 product,
1616 right_state
1617 }]
1618 );
1619 }
1620
1621 #[test]
1622 fn span_product_sibling_finder_activation_is_idempotent_per_position() {
1623 let product = StateId(10);
1624 let left = StateId(0);
1625 let right = StateId(20);
1626 let span = Span::new(0, 1);
1627
1628 let mut finder = SpanProductSiblingFinder::default();
1629 assert!(finder.activate(product, left, right, span, 0));
1630 assert!(!finder.activate(product, left, right, span, 0));
1631 assert!(finder.activate(product, left, right, span, 1));
1632 assert!(!finder.activate(product, left, right, span, 1));
1633 }
1634
1635 #[test]
1636 fn span_product_sibling_finder_separates_products_with_same_right_span() {
1637 let left_a = StateId(0);
1638 let left_b = StateId(1);
1639 let product_a = StateId(10);
1640 let product_b = StateId(11);
1641 let span = Span::new(1, 2);
1642
1643 let mut finder = SpanProductSiblingFinder::default();
1644 assert!(finder.activate(product_a, left_a, StateId(20), span, 1));
1645 assert!(finder.activate(product_b, left_b, StateId(21), span, 1));
1646
1647 let mut products = Vec::new();
1648 finder.sibling_products_into(Span::new(0, 1), 0, left_a, &mut products);
1649 assert_eq!(
1650 products,
1651 vec![SpanProductSibling {
1652 product: product_a,
1653 right_state: StateId(20)
1654 }]
1655 );
1656
1657 finder.sibling_products_into(Span::new(0, 1), 0, left_b, &mut products);
1658 assert_eq!(
1659 products,
1660 vec![SpanProductSibling {
1661 product: product_b,
1662 right_state: StateId(21)
1663 }]
1664 );
1665 }
1666
1667 #[test]
1668 fn span_product_sibling_finder_allocates_only_populated_slots() {
1669 let mut finder = SpanProductSiblingFinder::default();
1670 let sparse_left = StateId(1_000_000);
1671
1672 assert!(finder.activate(
1673 StateId(0),
1674 sparse_left,
1675 StateId(0),
1676 Span::new(10_000, 20_000),
1677 0,
1678 ));
1679 assert_eq!(finder.left_slots_by_end.len(), 1);
1680 assert_eq!(finder.left_lists.len(), 1);
1681 assert!(finder.right_slots_by_start.is_empty());
1682 }
1683
1684 #[test]
1685 fn lexical_lookup_handles_repeated_words() {
1686 let mut alg = StringAlgebra::new();
1687 let a = alg.intern_word("a");
1688 let b = alg.intern_word("b");
1689 let decomp = alg.decompose(vec![a, b, a]);
1690
1691 let mut spans = Vec::new();
1692 decomp.step(a, &[], &mut |q| spans.push(q));
1693 assert_eq!(spans, vec![Span::new(0, 1), Span::new(2, 3)]);
1694 }
1695
1696 #[test]
1697 fn string_decomposition_step_det_handles_concat() {
1698 let mut alg = StringAlgebra::new();
1699 let a = alg.intern_word("a");
1700 let b = alg.intern_word("b");
1701 let decomp = alg.decompose(vec![a, b]);
1702 let concat = alg.concat_symbol();
1703
1704 assert_eq!(
1705 decomp.step_det(concat, &[Span::new(0, 1), Span::new(1, 2)]),
1706 Some(Span::new(0, 2))
1707 );
1708 assert_eq!(
1709 decomp.step_det(concat, &[Span::new(1, 2), Span::new(0, 1)]),
1710 None
1711 );
1712 }
1713
1714 #[test]
1715 fn string_decomposition_step_det_rejects_repeated_word_nullaries() {
1716 let mut alg = StringAlgebra::new();
1717 let a = alg.intern_word("a");
1718 let decomp = alg.decompose(vec![a, a]);
1719
1720 assert_eq!(decomp.step_det(a, &[]), None);
1721 }
1722
1723 #[test]
1724 fn concat_requires_adjacency() {
1725 let mut alg = StringAlgebra::new();
1726 let a = alg.intern_word("a");
1727 let decomp = alg.decompose(vec![a, a, a]);
1728 let concat = alg.concat_symbol();
1729
1730 let mut ok = Vec::new();
1731 decomp.step(concat, &[Span::new(0, 1), Span::new(1, 3)], &mut |q| {
1732 ok.push(q)
1733 });
1734 assert_eq!(ok, vec![Span::new(0, 3)]);
1735
1736 let mut bad = Vec::new();
1737 decomp.step(concat, &[Span::new(0, 1), Span::new(2, 3)], &mut |q| {
1738 bad.push(q)
1739 });
1740 assert!(bad.is_empty());
1741 }
1742
1743 #[test]
1744 fn universe_is_callback_enumerated() {
1745 let mut alg = StringAlgebra::new();
1746 let a = alg.intern_word("a");
1747 let decomp = alg.decompose(vec![a, a, a, a]);
1748
1749 let mut count = 0;
1750 decomp.all_states(&mut |_| count += 1);
1751 assert_eq!(count, 10);
1752 }
1753
1754 #[test]
1755 fn condensed_rule_count_matches_cky_formula() {
1756 let mut alg = StringAlgebra::new();
1757 let a = alg.intern_word("a");
1758 let decomp = alg.decompose(vec![a, a, a, a]);
1759
1760 let mut count = 0;
1761 decomp.condensed_rules(&mut |_, _, _| count += 1);
1762 assert_eq!(count, decomp.rule_count());
1763 }
1764
1765 #[test]
1766 fn indexed_condensed_rules_by_child_enumerates_adjacent_spans() {
1767 let mut alg = StringAlgebra::new();
1768 let a = alg.intern_word("a");
1769 let decomp = alg.decompose(vec![a, a, a]);
1770
1771 let mut rules = Vec::new();
1772 decomp.condensed_rules_by_child(0, &Span::new(0, 1), &mut |children, symbols, result| {
1773 rules.push((children.to_vec(), symbols.clone(), result));
1774 });
1775
1776 assert_eq!(rules.len(), 2);
1777 assert_eq!(rules[0].0, vec![Span::new(0, 1), Span::new(1, 2)]);
1778 assert_eq!(rules[0].2, Span::new(0, 2));
1779 assert_eq!(rules[1].0, vec![Span::new(0, 1), Span::new(1, 3)]);
1780 assert_eq!(rules[1].2, Span::new(0, 3));
1781 assert!(
1782 rules
1783 .iter()
1784 .all(|(_, symbols, _)| { symbols.contains(decomp.concat_symbol()) })
1785 );
1786 }
1787
1788 fn build_tiny_grammar_and_hom() -> (
1809 crate::Explicit,
1810 crate::Homomorphism,
1811 Symbol,
1812 crate::StateId,
1813 crate::StateId,
1814 crate::StateId,
1815 ) {
1816 use crate::ExplicitBuilder;
1817
1818 let mut hom_arena = packed_term_arena::tree::TreeArena::new();
1819
1820 let mut sig = crate::Signature::new();
1822 let concat = sig.intern("*".to_owned(), 2).unwrap();
1823 let sym_a = sig.intern("a".to_owned(), 0).unwrap();
1824 let sym_b = sig.intern("b".to_owned(), 0).unwrap();
1825 let g_ab = Symbol(10); let g_a = Symbol(11); let g_b = Symbol(12); let v0 = hom_arena.add_node(HomLabel::Var(0), vec![]);
1836 let v1 = hom_arena.add_node(HomLabel::Var(1), vec![]);
1837 let concat_term = hom_arena.add_node(HomLabel::Symbol(concat), vec![v0, v1]);
1838 let a_word_term = hom_arena.add_node(HomLabel::Symbol(sym_a), vec![]);
1840 let b_word_term = hom_arena.add_node(HomLabel::Symbol(sym_b), vec![]);
1842
1843 let mut hom = Homomorphism::with_arena(hom_arena);
1844 hom.add(g_ab, 2, concat_term).unwrap();
1845 hom.add(g_a, 0, a_word_term).unwrap();
1846 hom.add(g_b, 0, b_word_term).unwrap();
1847
1848 let mut b = ExplicitBuilder::new();
1850 let s_a = b.new_state(); let s_b = b.new_state(); let s_s = b.new_state(); b.add_weighted_rule(g_a, vec![], s_a, 0.8);
1855 b.add_weighted_rule(g_b, vec![], s_b, 0.7);
1856 b.add_weighted_rule(g_ab, vec![s_a, s_b], s_s, 0.9);
1857 b.add_accepting(s_s);
1858
1859 (b.build(), hom, concat, s_a, s_b, s_s)
1860 }
1861
1862 #[test]
1863 fn sx_heuristic_bi_and_sx_values_match_hand_computation() {
1864 let (grammar, hom, concat, s_a, s_b, s_s) = build_tiny_grammar_and_hom();
1865 let universal = UniversalSxHeuristic::new(&grammar, &hom, concat, 2);
1866 let h = universal.for_sentence(2);
1867
1868 let est_a = <SentenceSxHeuristic<'_> as IntersectionHeuristic<
1870 StringDecompositionAutomaton,
1871 >>::outside_estimate(&h, s_a, &Span::new(0, 1));
1872 let expected_a = 0.9 * 0.7; assert!(
1874 (est_a - expected_a).abs() < 1e-9,
1875 "SX(s_A, 0, 1) = {est_a}, expected {expected_a}"
1876 );
1877
1878 let est_b = <SentenceSxHeuristic<'_> as IntersectionHeuristic<
1880 StringDecompositionAutomaton,
1881 >>::outside_estimate(&h, s_b, &Span::new(1, 2));
1882 let expected_b = 0.9 * 0.8; assert!(
1884 (est_b - expected_b).abs() < 1e-9,
1885 "SX(s_B, 1, 0) = {est_b}, expected {expected_b}"
1886 );
1887
1888 let est_s = <SentenceSxHeuristic<'_> as IntersectionHeuristic<
1890 StringDecompositionAutomaton,
1891 >>::outside_estimate(&h, s_s, &Span::new(0, 2));
1892 assert!(
1893 (est_s - 1.0).abs() < 1e-9,
1894 "SX(s_S, 0, 0) = {est_s}, expected 1.0"
1895 );
1896 }
1897
1898 #[test]
1899 fn sx_heuristic_outside_returns_zero_for_impossible_spans() {
1900 let (grammar, hom, concat, s_a, _s_b, _s_s) = build_tiny_grammar_and_hom();
1901 let universal = UniversalSxHeuristic::new(&grammar, &hom, concat, 2);
1902 let h = universal.for_sentence(2);
1903
1904 let est = <SentenceSxHeuristic<'_> as IntersectionHeuristic<
1908 StringDecompositionAutomaton,
1909 >>::outside_estimate(&h, s_a, &Span::new(0, 0));
1910 assert_eq!(est, 0.0, "SX(s_A, 0, 2) should be 0 (no room)");
1911 }
1912
1913 #[test]
1914 fn universal_sx_covers_shorter_sentences() {
1915 let (grammar, hom, concat, s_a, s_b, _s_s) = build_tiny_grammar_and_hom();
1918 let universal = UniversalSxHeuristic::new(&grammar, &hom, concat, 3);
1919 let h2 = universal.for_sentence(2);
1920 let universal_direct = UniversalSxHeuristic::new(&grammar, &hom, concat, 2);
1921 let h2_direct = universal_direct.for_sentence(2);
1922
1923 for (state, span) in [(s_a, Span::new(0, 1)), (s_b, Span::new(1, 2))] {
1924 let via_universal = <SentenceSxHeuristic<'_> as IntersectionHeuristic<
1925 StringDecompositionAutomaton,
1926 >>::outside_estimate(&h2, state, &span);
1927 let direct = <SentenceSxHeuristic<'_> as IntersectionHeuristic<
1928 StringDecompositionAutomaton,
1929 >>::outside_estimate(&h2_direct, state, &span);
1930 assert!(
1931 (via_universal - direct).abs() < 1e-12,
1932 "universal(n_max=3).for_sentence(2) != direct(n=2) for state {:?} span {:?}: {} vs {}",
1933 state,
1934 span,
1935 via_universal,
1936 direct
1937 );
1938 }
1939 }
1940}