1use crate::eval::cluster_encoder::{
32 ClusterEmbedding, ClusterEncoder, ClusterMention, LocalCluster, MergeScorer,
33};
34use std::collections::HashMap;
35
36#[cfg(feature = "candle")]
37use {
38 anno::backends::encoder_candle::CandleTextEncoder,
39 candle_core::{DType, Device, Module, Tensor, D},
40 candle_nn::{layer_norm, linear, LayerNorm, Linear, VarBuilder},
41};
42
43#[derive(Debug, Clone)]
49pub struct NeuralClusterConfig {
50 pub hidden_dim: usize,
52 pub num_heads: usize,
54 pub dropout: f32,
56 pub use_cls_pooling: bool,
58 pub max_mentions: usize,
60 pub merge_threshold: f32,
62}
63
64impl Default for NeuralClusterConfig {
65 fn default() -> Self {
66 Self {
67 hidden_dim: 768, num_heads: 12,
69 dropout: 0.1,
70 use_cls_pooling: false, max_mentions: 50,
72 merge_threshold: 0.5,
73 }
74 }
75}
76
77#[cfg(feature = "candle")]
82pub struct CandleClusterEncoder<E: CandleTextEncoder> {
88 encoder: E,
90 pooling_layer: ClusterPoolingLayer,
92 config: NeuralClusterConfig,
94 device: Device,
96}
97
98#[cfg(feature = "candle")]
99impl<E: CandleTextEncoder> CandleClusterEncoder<E> {
100 pub fn new(encoder: E, config: NeuralClusterConfig) -> crate::Result<Self> {
102 let device = Device::cuda_if_available(0).unwrap_or(Device::Cpu);
103 let pooling_layer = ClusterPoolingLayer::new(&config, &device)?;
104
105 Ok(Self {
106 encoder,
107 pooling_layer,
108 config,
109 device,
110 })
111 }
112
113 fn encode_cluster_impl(&self, cluster: &LocalCluster) -> crate::Result<Vec<f32>> {
115 if cluster.mentions.is_empty() {
116 return Ok(vec![0.0; self.config.hidden_dim]);
117 }
118
119 let mentions: Vec<&ClusterMention> = cluster
121 .mentions
122 .iter()
123 .take(self.config.max_mentions)
124 .collect();
125
126 let mut mention_embeddings = Vec::new();
128 for mention in &mentions {
129 let (embeddings, seq_len) = self.encoder.encode(&mention.text)?;
130
131 let hidden_dim = self.config.hidden_dim;
133 let mut pooled = vec![0.0f32; hidden_dim];
134 if seq_len > 0 {
135 for i in 0..seq_len {
136 for j in 0..hidden_dim {
137 pooled[j] += embeddings[i * hidden_dim + j];
138 }
139 }
140 for p in &mut pooled {
141 *p /= seq_len as f32;
142 }
143 }
144 mention_embeddings.push(pooled);
145 }
146
147 let num_mentions = mention_embeddings.len();
149 let flat: Vec<f32> = mention_embeddings.into_iter().flatten().collect();
150 let tensor = Tensor::from_vec(flat, (num_mentions, self.config.hidden_dim), &self.device)
151 .map_err(|e: candle_core::Error| crate::Error::Inference(e.to_string()))?;
152
153 let pooled = self.pooling_layer.forward(&tensor)?;
155
156 let result = pooled
158 .to_vec1::<f32>()
159 .map_err(|e: candle_core::Error| crate::Error::Inference(e.to_string()))?;
160
161 Ok(result)
162 }
163}
164
165#[cfg(feature = "candle")]
166impl<E: CandleTextEncoder> ClusterEncoder for CandleClusterEncoder<E> {
167 fn encode_cluster(
168 &self,
169 cluster: &LocalCluster,
170 _hidden_states: Option<&[Vec<f32>]>,
171 ) -> ClusterEmbedding {
172 let embedding = self
173 .encode_cluster_impl(cluster)
174 .unwrap_or_else(|_| vec![0.0; self.config.hidden_dim]);
175
176 ClusterEmbedding {
177 cluster_id: cluster.id,
178 context_id: cluster.context_id,
179 embedding,
180 mention_count: cluster.mentions.len(),
181 }
182 }
183
184 fn embedding_dim(&self) -> usize {
185 self.config.hidden_dim
186 }
187}
188
189#[cfg(feature = "candle")]
194struct ClusterPoolingLayer {
200 wq: Linear,
202 wk: Linear,
204 wv: Linear,
206 wo: Linear,
208 ln: LayerNorm,
210 num_heads: usize,
212 head_dim: usize,
214}
215
216#[cfg(feature = "candle")]
217impl ClusterPoolingLayer {
218 fn new(config: &NeuralClusterConfig, device: &Device) -> crate::Result<Self> {
219 let varmap = candle_nn::VarMap::new();
220 let vb = VarBuilder::from_varmap(&varmap, DType::F32, device);
221
222 let hidden_dim = config.hidden_dim;
223 let num_heads = config.num_heads;
224 let head_dim = hidden_dim / num_heads;
225
226 let wq = linear(hidden_dim, hidden_dim, vb.pp("wq"))
227 .map_err(|e| crate::Error::Inference(format!("Linear wq: {}", e)))?;
228 let wk = linear(hidden_dim, hidden_dim, vb.pp("wk"))
229 .map_err(|e| crate::Error::Inference(format!("Linear wk: {}", e)))?;
230 let wv = linear(hidden_dim, hidden_dim, vb.pp("wv"))
231 .map_err(|e| crate::Error::Inference(format!("Linear wv: {}", e)))?;
232 let wo = linear(hidden_dim, hidden_dim, vb.pp("wo"))
233 .map_err(|e| crate::Error::Inference(format!("Linear wo: {}", e)))?;
234 let ln = layer_norm(hidden_dim, 1e-5, vb.pp("ln"))
235 .map_err(|e| crate::Error::Inference(format!("LayerNorm: {}", e)))?;
236
237 Ok(Self {
238 wq,
239 wk,
240 wv,
241 wo,
242 ln,
243 num_heads,
244 head_dim,
245 })
246 }
247
248 fn forward(&self, x: &Tensor) -> crate::Result<Tensor> {
250 let (seq_len, hidden_dim) = x
251 .dims2()
252 .map_err(|e| crate::Error::Inference(format!("Dims: {}", e)))?;
253
254 let q = self
256 .wq
257 .forward(x)
258 .map_err(|e| crate::Error::Inference(format!("Q: {}", e)))?;
259 let k = self
260 .wk
261 .forward(x)
262 .map_err(|e| crate::Error::Inference(format!("K: {}", e)))?;
263 let v = self
264 .wv
265 .forward(x)
266 .map_err(|e| crate::Error::Inference(format!("V: {}", e)))?;
267
268 let q = q
270 .reshape((seq_len, self.num_heads, self.head_dim))
271 .map_err(|e| crate::Error::Inference(format!("Q reshape: {}", e)))?
272 .transpose(0, 1)
273 .map_err(|e| crate::Error::Inference(format!("Q transpose: {}", e)))?;
274 let k = k
275 .reshape((seq_len, self.num_heads, self.head_dim))
276 .map_err(|e| crate::Error::Inference(format!("K reshape: {}", e)))?
277 .transpose(0, 1)
278 .map_err(|e| crate::Error::Inference(format!("K transpose: {}", e)))?;
279 let v = v
280 .reshape((seq_len, self.num_heads, self.head_dim))
281 .map_err(|e| crate::Error::Inference(format!("V reshape: {}", e)))?
282 .transpose(0, 1)
283 .map_err(|e| crate::Error::Inference(format!("V transpose: {}", e)))?;
284
285 let scale = (self.head_dim as f64).sqrt();
287 let scores = q
288 .matmul(
289 &k.transpose(1, 2)
290 .map_err(|e| crate::Error::Inference(format!("K^T: {}", e)))?,
291 )
292 .map_err(|e| crate::Error::Inference(format!("QK^T: {}", e)))?
293 .affine(1.0 / scale, 0.0)
294 .map_err(|e| crate::Error::Inference(format!("Scale: {}", e)))?;
295
296 let attn = candle_nn::ops::softmax(&scores, D::Minus1)
297 .map_err(|e| crate::Error::Inference(format!("Softmax: {}", e)))?;
298
299 let context = attn
301 .matmul(&v)
302 .map_err(|e| crate::Error::Inference(format!("Attn*V: {}", e)))?;
303
304 let context = context
306 .transpose(0, 1)
307 .map_err(|e| crate::Error::Inference(format!("Context transpose: {}", e)))?
308 .reshape((seq_len, hidden_dim))
309 .map_err(|e| crate::Error::Inference(format!("Context reshape: {}", e)))?;
310
311 let out = self
313 .wo
314 .forward(&context)
315 .map_err(|e| crate::Error::Inference(format!("Wo: {}", e)))?;
316 let out = (x + &out).map_err(|e| crate::Error::Inference(format!("Residual: {}", e)))?;
317 let out = self
318 .ln
319 .forward(&out)
320 .map_err(|e| crate::Error::Inference(format!("LayerNorm: {}", e)))?;
321
322 out.mean(0)
324 .map_err(|e| crate::Error::Inference(format!("Mean pool: {}", e)))
325 }
326}
327
328#[cfg(feature = "candle")]
333pub struct NeuralMergeScorer {
339 bilinear: Linear,
341 classifier: Linear,
343 device: Device,
345}
346
347#[cfg(feature = "candle")]
348impl NeuralMergeScorer {
349 pub fn new(hidden_dim: usize) -> crate::Result<Self> {
351 let device = Device::cuda_if_available(0).unwrap_or(Device::Cpu);
352 let varmap = candle_nn::VarMap::new();
353 let vb = VarBuilder::from_varmap(&varmap, DType::F32, &device);
354
355 let bilinear = linear(hidden_dim * 2, hidden_dim, vb.pp("bilinear"))
357 .map_err(|e| crate::Error::Inference(format!("Bilinear: {}", e)))?;
358 let classifier = linear(hidden_dim, 1, vb.pp("classifier"))
359 .map_err(|e| crate::Error::Inference(format!("Classifier: {}", e)))?;
360
361 Ok(Self {
362 bilinear,
363 classifier,
364 device,
365 })
366 }
367
368 fn score_impl(&self, emb_a: &[f32], emb_b: &[f32]) -> crate::Result<f32> {
370 let concat: Vec<f32> = emb_a.iter().chain(emb_b.iter()).cloned().collect();
372 let input = Tensor::from_vec(concat, (1, emb_a.len() + emb_b.len()), &self.device)
373 .map_err(|e| crate::Error::Inference(format!("Input tensor: {}", e)))?;
374
375 let hidden = self
377 .bilinear
378 .forward(&input)
379 .map_err(|e| crate::Error::Inference(format!("Bilinear forward: {}", e)))?;
380 let hidden = hidden
381 .relu()
382 .map_err(|e| crate::Error::Inference(format!("ReLU: {}", e)))?;
383 let logit = self
384 .classifier
385 .forward(&hidden)
386 .map_err(|e| crate::Error::Inference(format!("Classifier forward: {}", e)))?;
387
388 let prob = candle_nn::ops::sigmoid(&logit)
390 .map_err(|e| crate::Error::Inference(format!("Sigmoid: {}", e)))?;
391
392 let score = prob
393 .to_vec2::<f32>()
394 .map_err(|e| crate::Error::Inference(format!("To vec: {}", e)))?[0][0];
395
396 Ok(score)
397 }
398}
399
400#[cfg(feature = "candle")]
401impl MergeScorer for NeuralMergeScorer {
402 fn score(&self, embedding_a: &ClusterEmbedding, embedding_b: &ClusterEmbedding) -> f32 {
403 self.score_impl(&embedding_a.embedding, &embedding_b.embedding)
404 .unwrap_or(0.0)
405 }
406}
407
408pub struct CDCRAdapter;
416
417impl CDCRAdapter {
418 pub fn documents_to_clusters(
420 docs: &[crate::eval::cdcr::Document],
421 ) -> HashMap<usize, Vec<LocalCluster>> {
422 let mut all_clusters = HashMap::new();
423
424 for (doc_idx, doc) in docs.iter().enumerate() {
425 let mut clusters = Vec::new();
426
427 for (chain_idx, chain) in doc.coref_chains.iter().enumerate() {
429 let mut cluster = LocalCluster::new(chain_idx, doc_idx);
430
431 for mention in &chain.mentions {
432 cluster.add_mention(ClusterMention {
433 start: mention.start,
434 end: mention.end,
435 text: mention.text.clone(),
436 context_id: doc_idx,
437 });
438 }
439
440 cluster.compute_canonical();
441 clusters.push(cluster);
442 }
443
444 let chained_starts: std::collections::HashSet<usize> = doc
446 .coref_chains
447 .iter()
448 .flat_map(|c| c.mentions.iter().map(|m| m.start))
449 .collect();
450
451 for entity in &doc.entities {
452 if !chained_starts.contains(&entity.start()) {
453 let mut cluster = LocalCluster::new(clusters.len(), doc_idx);
454 cluster.add_mention(ClusterMention {
455 start: entity.start(),
456 end: entity.end(),
457 text: entity.text.clone(),
458 context_id: doc_idx,
459 });
460 cluster.compute_canonical();
461 clusters.push(cluster);
462 }
463 }
464
465 all_clusters.insert(doc_idx, clusters);
466 }
467
468 all_clusters
469 }
470
471 pub fn clusters_to_crossdoc(
473 merged: &[crate::eval::cluster_encoder::MergedCluster],
474 docs: &[crate::eval::cdcr::Document],
475 ) -> Vec<crate::eval::cdcr::CrossDocCluster> {
476 merged
477 .iter()
478 .map(|m| {
479 let mut cluster = crate::eval::cdcr::CrossDocCluster::new(
480 m.id as u64,
481 m.canonical.as_deref().unwrap_or(""),
482 );
483
484 for mention in &m.mentions {
485 if let Some(doc) = docs.get(mention.context_id) {
486 let entity_idx = doc
488 .entities
489 .iter()
490 .position(|e| e.start() == mention.start && e.end() == mention.end)
491 .unwrap_or(0);
492 cluster.add_mention(&doc.id, entity_idx);
493 }
494 }
495
496 cluster
497 })
498 .collect()
499 }
500}
501
502pub struct UnifiedCrossContextResolver<E: ClusterEncoder, S: MergeScorer> {
512 encoder: E,
513 scorer: S,
514 config: CrossContextConfig,
515}
516
517#[derive(Debug, Clone)]
519pub struct CrossContextConfig {
520 pub window_size: usize,
522 pub window_overlap: usize,
524 pub merge_threshold: f32,
526}
527
528impl Default for CrossContextConfig {
529 fn default() -> Self {
530 Self {
531 window_size: 4000,
532 window_overlap: 256,
533 merge_threshold: 0.5,
534 }
535 }
536}
537
538impl<E: ClusterEncoder, S: MergeScorer> UnifiedCrossContextResolver<E, S> {
539 pub fn new(encoder: E, scorer: S, config: CrossContextConfig) -> Self {
541 Self {
542 encoder,
543 scorer,
544 config,
545 }
546 }
547
548 pub fn resolve_documents(
550 &self,
551 docs: &[crate::eval::cdcr::Document],
552 ) -> Vec<crate::eval::cdcr::CrossDocCluster> {
553 let local_clusters = CDCRAdapter::documents_to_clusters(docs);
555
556 let merged = self.merge_clusters(&local_clusters);
558
559 CDCRAdapter::clusters_to_crossdoc(&merged, docs)
561 }
562
563 fn merge_clusters(
565 &self,
566 local_clusters: &HashMap<usize, Vec<LocalCluster>>,
567 ) -> Vec<crate::eval::cluster_encoder::MergedCluster> {
568 let mut embeddings: Vec<ClusterEmbedding> = Vec::new();
570 for clusters in local_clusters.values() {
571 for cluster in clusters {
572 let emb = self.encoder.encode_cluster(cluster, None);
573 embeddings.push(emb);
574 }
575 }
576
577 if embeddings.is_empty() {
578 return Vec::new();
579 }
580
581 let mut merge_pairs: Vec<(usize, usize, f32)> = Vec::new();
583 for (i, emb_a) in embeddings.iter().enumerate() {
584 for (j, emb_b) in embeddings.iter().enumerate().skip(i + 1) {
585 if emb_a.context_id == emb_b.context_id {
586 continue;
587 }
588
589 let score = self.scorer.score(emb_a, emb_b);
590 if score >= self.config.merge_threshold {
591 merge_pairs.push((i, j, score));
592 }
593 }
594 }
595
596 merge_pairs.sort_by(|a, b| b.2.partial_cmp(&a.2).unwrap_or(std::cmp::Ordering::Equal));
598
599 let n = embeddings.len();
601 let mut parent: Vec<usize> = (0..n).collect();
602 let mut rank: Vec<usize> = vec![0; n];
603
604 fn find(parent: &mut [usize], i: usize) -> usize {
605 if parent[i] != i {
606 parent[i] = find(parent, parent[i]);
607 }
608 parent[i]
609 }
610
611 fn union(parent: &mut [usize], rank: &mut [usize], x: usize, y: usize) {
612 let px = find(parent, x);
613 let py = find(parent, y);
614 if px == py {
615 return;
616 }
617 match rank[px].cmp(&rank[py]) {
618 std::cmp::Ordering::Less => parent[px] = py,
619 std::cmp::Ordering::Greater => parent[py] = px,
620 std::cmp::Ordering::Equal => {
621 parent[py] = px;
622 rank[px] += 1;
623 }
624 }
625 }
626
627 for (i, j, _) in merge_pairs {
628 union(&mut parent, &mut rank, i, j);
629 }
630
631 let mut cluster_map: HashMap<usize, Vec<usize>> = HashMap::new();
633 for i in 0..n {
634 let root = find(&mut parent, i);
635 cluster_map.entry(root).or_default().push(i);
636 }
637
638 cluster_map
639 .into_iter()
640 .enumerate()
641 .map(|(merged_id, (_root, indices))| {
642 let mut merged = crate::eval::cluster_encoder::MergedCluster {
643 id: merged_id,
644 source_clusters: Vec::new(),
645 mentions: Vec::new(),
646 canonical: None,
647 };
648
649 for idx in indices {
650 let emb = &embeddings[idx];
651 merged
652 .source_clusters
653 .push((emb.context_id, emb.cluster_id));
654
655 if let Some(clusters) = local_clusters.get(&emb.context_id) {
657 if let Some(cluster) = clusters.iter().find(|c| c.id == emb.cluster_id) {
658 merged.mentions.extend(cluster.mentions.clone());
659 if merged.canonical.is_none() {
660 merged.canonical = cluster.canonical.clone();
661 }
662 }
663 }
664 }
665
666 merged
667 })
668 .collect()
669 }
670}
671
672pub struct IncrementalCorefAdapter;
681
682impl IncrementalCorefAdapter {
683 pub fn windows_to_clusters(windows: &[WindowOutput]) -> HashMap<usize, Vec<LocalCluster>> {
687 let mut all_clusters = HashMap::new();
688
689 for (window_idx, output) in windows.iter().enumerate() {
690 let mut clusters = Vec::new();
691
692 for (chain_idx, chain) in output.chains.iter().enumerate() {
693 let mut cluster = LocalCluster::new(chain_idx, window_idx);
694
695 for mention in &chain.mentions {
696 cluster.add_mention(ClusterMention {
697 start: mention.start,
698 end: mention.end,
699 text: mention.text.clone(),
700 context_id: window_idx,
701 });
702 }
703
704 cluster.compute_canonical();
705 clusters.push(cluster);
706 }
707
708 all_clusters.insert(window_idx, clusters);
709 }
710
711 all_clusters
712 }
713
714 pub fn clusters_to_chains(
716 merged: &[crate::eval::cluster_encoder::MergedCluster],
717 ) -> Vec<crate::eval::coref::CorefChain> {
718 use crate::eval::coref::{CorefChain, Mention, MentionType};
719
720 merged
721 .iter()
722 .map(|m| {
723 let mentions: Vec<Mention> = m
724 .mentions
725 .iter()
726 .map(|cm| Mention {
727 text: cm.text.clone(),
728 start: cm.start,
729 end: cm.end,
730 head_start: None,
731 head_end: None,
732 entity_type: None,
733 mention_type: Some(MentionType::Proper),
734 })
735 .collect();
736
737 CorefChain::new(mentions)
738 })
739 .collect()
740 }
741}
742
743#[derive(Debug, Clone)]
745pub struct WindowOutput {
746 pub window_idx: usize,
748 pub start_offset: usize,
750 pub end_offset: usize,
752 pub chains: Vec<crate::eval::coref::CorefChain>,
754}
755
756impl WindowOutput {
757 pub fn new(
759 window_idx: usize,
760 start_offset: usize,
761 end_offset: usize,
762 chains: Vec<crate::eval::coref::CorefChain>,
763 ) -> Self {
764 Self {
765 window_idx,
766 start_offset,
767 end_offset,
768 chains,
769 }
770 }
771}
772
773impl<E: ClusterEncoder, S: MergeScorer> UnifiedCrossContextResolver<E, S> {
775 pub fn resolve_long_document_windows(
782 &self,
783 windows: &[WindowOutput],
784 ) -> Vec<crate::eval::coref::CorefChain> {
785 let local_clusters = IncrementalCorefAdapter::windows_to_clusters(windows);
787
788 let merged = self.merge_clusters(&local_clusters);
790
791 IncrementalCorefAdapter::clusters_to_chains(&merged)
793 }
794
795 pub fn config(&self) -> &CrossContextConfig {
797 &self.config
798 }
799}
800
801#[cfg(test)]
806mod tests {
807 use super::*;
808 use crate::eval::cluster_encoder::{CosineMergeScorer, HeuristicClusterEncoder};
809
810 #[test]
811 fn test_cdcr_adapter_empty() {
812 let docs: Vec<crate::eval::cdcr::Document> = vec![];
813 let clusters = CDCRAdapter::documents_to_clusters(&docs);
814 assert!(clusters.is_empty());
815 }
816
817 #[test]
818 fn test_cdcr_adapter_single_doc() {
819 use crate::eval::cdcr::Document;
820 use anno::{Entity, EntityType};
821
822 let doc = Document::new("doc1", "Obama visited France.").with_entities(vec![Entity::new(
823 "Obama",
824 EntityType::Person,
825 0,
826 5,
827 0.9,
828 )]);
829
830 let clusters = CDCRAdapter::documents_to_clusters(&[doc]);
831 assert_eq!(clusters.len(), 1);
832 assert!(!clusters[&0].is_empty());
833 }
834
835 #[test]
836 fn test_unified_resolver() {
837 use crate::eval::cdcr::Document;
838 use anno::{Entity, EntityType};
839
840 let encoder = HeuristicClusterEncoder::new(64);
841 let scorer = CosineMergeScorer::new();
842 let config = CrossContextConfig::default();
843
844 let resolver = UnifiedCrossContextResolver::new(encoder, scorer, config);
845
846 let docs =
847 vec![
848 Document::new("doc1", "Barack Obama gave a speech.").with_entities(vec![
849 Entity::new("Barack Obama", EntityType::Person, 0, 12, 0.9),
850 ]),
851 Document::new("doc2", "Obama met with leaders.").with_entities(vec![Entity::new(
852 "Obama",
853 EntityType::Person,
854 0,
855 5,
856 0.9,
857 )]),
858 ];
859
860 let result = resolver.resolve_documents(&docs);
861 assert!(!result.is_empty());
863 }
864
865 #[test]
866 fn test_neural_config_default() {
867 let config = NeuralClusterConfig::default();
868 assert_eq!(config.hidden_dim, 768);
869 assert_eq!(config.num_heads, 12);
870 assert!(!config.use_cls_pooling);
871 }
872
873 #[test]
874 fn test_incremental_adapter_empty() {
875 let windows: Vec<WindowOutput> = vec![];
876 let clusters = IncrementalCorefAdapter::windows_to_clusters(&windows);
877 assert!(clusters.is_empty());
878 }
879
880 #[test]
881 fn test_incremental_adapter_single_window() {
882 use crate::eval::coref::{CorefChain, Mention};
883 use anno::MentionType;
884
885 fn new_mention(text: &str, start: usize, end: usize, mt: MentionType) -> Mention {
886 Mention {
887 text: text.to_string(),
888 start,
889 end,
890 head_start: None,
891 head_end: None,
892 entity_type: None,
893 mention_type: Some(mt),
894 }
895 }
896
897 let chain = CorefChain::new(vec![
898 new_mention("Obama", 0, 5, MentionType::Proper),
899 new_mention("he", 20, 22, MentionType::Pronominal),
900 ]);
901
902 let window = WindowOutput::new(0, 0, 100, vec![chain]);
903 let clusters = IncrementalCorefAdapter::windows_to_clusters(&[window]);
904
905 assert_eq!(clusters.len(), 1);
906 assert_eq!(clusters[&0].len(), 1);
907 assert_eq!(clusters[&0][0].mentions.len(), 2);
908 }
909
910 #[test]
911 fn test_incremental_adapter_multi_window() {
912 use crate::eval::coref::{CorefChain, Mention};
913 use anno::MentionType;
914
915 fn new_mention(text: &str, start: usize, end: usize, mt: MentionType) -> Mention {
916 Mention {
917 text: text.to_string(),
918 start,
919 end,
920 head_start: None,
921 head_end: None,
922 entity_type: None,
923 mention_type: Some(mt),
924 }
925 }
926
927 let window1 = WindowOutput::new(
928 0,
929 0,
930 100,
931 vec![CorefChain::new(vec![new_mention(
932 "Obama",
933 0,
934 5,
935 MentionType::Proper,
936 )])],
937 );
938
939 let window2 = WindowOutput::new(
940 1,
941 80,
942 180,
943 vec![CorefChain::new(vec![new_mention(
944 "the President",
945 90,
946 103,
947 MentionType::Nominal,
948 )])],
949 );
950
951 let clusters = IncrementalCorefAdapter::windows_to_clusters(&[window1, window2]);
952
953 assert_eq!(clusters.len(), 2);
954 assert_eq!(clusters[&0].len(), 1);
955 assert_eq!(clusters[&1].len(), 1);
956 }
957
958 #[test]
959 fn test_long_document_resolution() {
960 use crate::eval::coref::{CorefChain, Mention};
961 use anno::MentionType;
962
963 fn new_mention(text: &str, start: usize, end: usize, mt: MentionType) -> Mention {
964 Mention {
965 text: text.to_string(),
966 start,
967 end,
968 head_start: None,
969 head_end: None,
970 entity_type: None,
971 mention_type: Some(mt),
972 }
973 }
974
975 let encoder = HeuristicClusterEncoder::new(64);
976 let scorer = CosineMergeScorer::new();
977 let config = CrossContextConfig {
978 merge_threshold: 0.3,
979 ..Default::default()
980 };
981
982 let resolver = UnifiedCrossContextResolver::new(encoder, scorer, config);
983
984 let window1 = WindowOutput::new(
986 0,
987 0,
988 1000,
989 vec![CorefChain::new(vec![new_mention(
990 "Barack Obama",
991 0,
992 12,
993 MentionType::Proper,
994 )])],
995 );
996
997 let window2 = WindowOutput::new(
998 1,
999 800,
1000 1800,
1001 vec![CorefChain::new(vec![new_mention(
1002 "Obama",
1003 900,
1004 905,
1005 MentionType::Proper,
1006 )])],
1007 );
1008
1009 let chains = resolver.resolve_long_document_windows(&[window1, window2]);
1010
1011 assert!(!chains.is_empty());
1013 }
1014}