Skip to main content

anno_eval/eval/dataset/synthetic/
relations.rs

1//! Synthetic relation extraction examples.
2//!
3//! # Overview
4//!
5//! Relation extraction identifies semantic relationships between entity pairs:
6//! - **Employment**: Person WORKS_FOR Organization
7//! - **Foundation**: Person FOUNDED Organization  
8//! - **Location**: Entity LOCATED_IN Location
9//! - **Family**: Person SIBLING/PARENT_OF Person
10//!
11//! # Research Alignment
12//!
13//! From DocRED (arXiv:1906.06127):
14//! > "Document-level relation extraction requires integrating
15//! > information across sentences."
16//!
17//! Benchmark datasets include TACRED, DocRED, and SciERC.
18
19use crate::eval::relation::RelationGold;
20
21/// A synthetic example with entities and relations.
22#[derive(Debug, Clone)]
23pub struct RelationExample {
24    /// The text
25    pub text: String,
26    /// Gold standard relations
27    pub relations: Vec<RelationGold>,
28    /// Difficulty level
29    pub difficulty: Difficulty,
30    /// Domain
31    pub domain: Domain,
32}
33
34/// Difficulty level for relation examples.
35#[derive(Debug, Clone, Copy, PartialEq, Eq)]
36pub enum Difficulty {
37    /// Single relation, clear trigger
38    Easy,
39    /// Multiple relations in one sentence
40    Medium,
41    /// Implicit relations, long distance
42    Hard,
43}
44
45/// Domain for relation examples.
46#[derive(Debug, Clone, Copy, PartialEq, Eq)]
47pub enum Domain {
48    /// General news
49    General,
50    /// Business/corporate
51    Business,
52    /// Scientific/academic
53    Scientific,
54    /// Biographical
55    Biography,
56}
57
58/// Generate all relation extraction synthetic examples.
59pub fn dataset() -> Vec<RelationExample> {
60    let mut examples = Vec::new();
61
62    // Easy: Single relation with clear trigger
63    examples.extend(easy_relations());
64
65    // Medium: Multiple relations
66    examples.extend(medium_relations());
67
68    // Hard: Implicit and long-distance
69    examples.extend(hard_relations());
70
71    // Domain-specific
72    examples.extend(business_domain());
73    examples.extend(scientific_domain());
74    examples.extend(biography_domain());
75
76    examples
77}
78
79/// Easy: Single relation with explicit trigger word.
80fn easy_relations() -> Vec<RelationExample> {
81    vec![
82        RelationExample {
83            text: "Steve Jobs founded Apple in 1976.".to_string(),
84            relations: vec![RelationGold::new(
85                (0, 10),
86                "PER",
87                "Steve Jobs",
88                (19, 24),
89                "ORG",
90                "Apple",
91                "FOUNDED",
92            )],
93            difficulty: Difficulty::Easy,
94            domain: Domain::Business,
95        },
96        RelationExample {
97            text: "Mary works for Google in California.".to_string(),
98            relations: vec![
99                RelationGold::new(
100                    (0, 4),
101                    "PER",
102                    "Mary",
103                    (15, 21),
104                    "ORG",
105                    "Google",
106                    "WORKS_FOR",
107                ),
108                RelationGold::new(
109                    (15, 21),
110                    "ORG",
111                    "Google",
112                    (25, 35),
113                    "LOC",
114                    "California",
115                    "LOCATED_IN",
116                ),
117            ],
118            difficulty: Difficulty::Easy,
119            domain: Domain::Business,
120        },
121        RelationExample {
122            text: "The Eiffel Tower is located in Paris, France.".to_string(),
123            relations: vec![
124                RelationGold::new(
125                    (4, 16),
126                    "LOC",
127                    "Eiffel Tower",
128                    (31, 36),
129                    "LOC",
130                    "Paris",
131                    "LOCATED_IN",
132                ),
133                RelationGold::new(
134                    (31, 36),
135                    "LOC",
136                    "Paris",
137                    (38, 44),
138                    "LOC",
139                    "France",
140                    "LOCATED_IN",
141                ),
142            ],
143            difficulty: Difficulty::Easy,
144            domain: Domain::General,
145        },
146        RelationExample {
147            text: "Tim Cook is the CEO of Apple Inc.".to_string(),
148            relations: vec![RelationGold::new(
149                (0, 8),
150                "PER",
151                "Tim Cook",
152                (23, 32),
153                "ORG",
154                "Apple Inc",
155                "CEO_OF",
156            )],
157            difficulty: Difficulty::Easy,
158            domain: Domain::Business,
159        },
160        RelationExample {
161            text: "Amazon acquired Whole Foods in 2017.".to_string(),
162            relations: vec![RelationGold::new(
163                (0, 6),
164                "ORG",
165                "Amazon",
166                (16, 27),
167                "ORG",
168                "Whole Foods",
169                "ACQUIRED",
170            )],
171            difficulty: Difficulty::Easy,
172            domain: Domain::Business,
173        },
174    ]
175}
176
177/// Medium: Multiple relations or more complex triggers.
178fn medium_relations() -> Vec<RelationExample> {
179    vec![
180        RelationExample {
181            text: "Bill Gates and Paul Allen co-founded Microsoft in Seattle.".to_string(),
182            relations: vec![
183                RelationGold::new(
184                    (0, 10),
185                    "PER",
186                    "Bill Gates",
187                    (37, 46),
188                    "ORG",
189                    "Microsoft",
190                    "FOUNDED",
191                ),
192                RelationGold::new(
193                    (15, 25),
194                    "PER",
195                    "Paul Allen",
196                    (37, 46),
197                    "ORG",
198                    "Microsoft",
199                    "FOUNDED",
200                ),
201                RelationGold::new(
202                    (37, 46),
203                    "ORG",
204                    "Microsoft",
205                    (50, 57),
206                    "LOC",
207                    "Seattle",
208                    "LOCATED_IN",
209                ),
210            ],
211            difficulty: Difficulty::Medium,
212            domain: Domain::Business,
213        },
214        RelationExample {
215            text: "Dr. Smith at Harvard published research with Dr. Jones from MIT.".to_string(),
216            relations: vec![
217                RelationGold::new(
218                    (0, 9),
219                    "PER",
220                    "Dr. Smith",
221                    (13, 20),
222                    "ORG",
223                    "Harvard",
224                    "AFFILIATED_WITH",
225                ),
226                RelationGold::new(
227                    (45, 54),
228                    "PER",
229                    "Dr. Jones",
230                    (60, 63),
231                    "ORG",
232                    "MIT",
233                    "AFFILIATED_WITH",
234                ),
235                RelationGold::new(
236                    (0, 9),
237                    "PER",
238                    "Dr. Smith",
239                    (45, 54),
240                    "PER",
241                    "Dr. Jones",
242                    "COLLABORATED_WITH",
243                ),
244            ],
245            difficulty: Difficulty::Medium,
246            domain: Domain::Scientific,
247        },
248        RelationExample {
249            text: "Alice, Bob's sister, married Charlie, who works at IBM.".to_string(),
250            relations: vec![
251                RelationGold::new((0, 5), "PER", "Alice", (7, 10), "PER", "Bob", "SIBLING_OF"),
252                RelationGold::new(
253                    (0, 5),
254                    "PER",
255                    "Alice",
256                    (29, 36),
257                    "PER",
258                    "Charlie",
259                    "SPOUSE_OF",
260                ),
261                RelationGold::new(
262                    (29, 36),
263                    "PER",
264                    "Charlie",
265                    (51, 54),
266                    "ORG",
267                    "IBM",
268                    "WORKS_FOR",
269                ),
270            ],
271            difficulty: Difficulty::Medium,
272            domain: Domain::Biography,
273        },
274    ]
275}
276
277/// Hard: Implicit relations or long-distance dependencies.
278fn hard_relations() -> Vec<RelationExample> {
279    vec![
280        RelationExample {
281            // Implicit relation - no trigger word
282            text: "Sundar Pichai, born in India, leads Google's AI efforts.".to_string(),
283            relations: vec![
284                RelationGold::new(
285                    (0, 13), "PER", "Sundar Pichai",
286                    (23, 28), "LOC", "India",
287                    "BORN_IN",
288                ),
289                RelationGold::new(
290                    (0, 13), "PER", "Sundar Pichai",
291                    (36, 42), "ORG", "Google",
292                    "WORKS_FOR",
293                ),
294            ],
295            difficulty: Difficulty::Hard,
296            domain: Domain::Biography,
297        },
298        RelationExample {
299            // Long-distance relation
300            text: "The company, which was established in 1998 by Larry Page and Sergey Brin, is headquartered in Mountain View.".to_string(),
301            relations: vec![
302                RelationGold::new(
303                    (45, 55), "PER", "Larry Page",
304                    (0, 11), "ORG", "The company",
305                    "FOUNDED",
306                ),
307                RelationGold::new(
308                    (60, 71), "PER", "Sergey Brin",
309                    (0, 11), "ORG", "The company",
310                    "FOUNDED",
311                ),
312                RelationGold::new(
313                    (0, 11), "ORG", "The company",
314                    (92, 105), "LOC", "Mountain View",
315                    "LOCATED_IN",
316                ),
317            ],
318            difficulty: Difficulty::Hard,
319            domain: Domain::Business,
320        },
321    ]
322}
323
324/// Business domain examples.
325fn business_domain() -> Vec<RelationExample> {
326    vec![
327        RelationExample {
328            text: "Nvidia, led by Jensen Huang, designs chips in Santa Clara.".to_string(),
329            relations: vec![
330                RelationGold::new(
331                    (15, 27),
332                    "PER",
333                    "Jensen Huang",
334                    (0, 6),
335                    "ORG",
336                    "Nvidia",
337                    "CEO_OF",
338                ),
339                RelationGold::new(
340                    (0, 6),
341                    "ORG",
342                    "Nvidia",
343                    (46, 57),
344                    "LOC",
345                    "Santa Clara",
346                    "LOCATED_IN",
347                ),
348            ],
349            difficulty: Difficulty::Medium,
350            domain: Domain::Business,
351        },
352        RelationExample {
353            text: "Netflix is headquartered in Los Gatos, California.".to_string(),
354            relations: vec![
355                RelationGold::new(
356                    (0, 7),
357                    "ORG",
358                    "Netflix",
359                    (28, 37),
360                    "LOC",
361                    "Los Gatos",
362                    "LOCATED_IN",
363                ),
364                RelationGold::new(
365                    (28, 37),
366                    "LOC",
367                    "Los Gatos",
368                    (39, 49),
369                    "LOC",
370                    "California",
371                    "LOCATED_IN",
372                ),
373            ],
374            difficulty: Difficulty::Easy,
375            domain: Domain::Business,
376        },
377    ]
378}
379
380/// Scientific domain examples.
381fn scientific_domain() -> Vec<RelationExample> {
382    vec![
383        RelationExample {
384            text: "CRISPR was developed by Jennifer Doudna at UC Berkeley.".to_string(),
385            relations: vec![
386                RelationGold::new(
387                    (24, 39),
388                    "PER",
389                    "Jennifer Doudna",
390                    (0, 6),
391                    "MISC",
392                    "CRISPR",
393                    "DEVELOPED",
394                ),
395                RelationGold::new(
396                    (24, 39),
397                    "PER",
398                    "Jennifer Doudna",
399                    (43, 54),
400                    "ORG",
401                    "UC Berkeley",
402                    "AFFILIATED_WITH",
403                ),
404            ],
405            difficulty: Difficulty::Medium,
406            domain: Domain::Scientific,
407        },
408        RelationExample {
409            text: "Einstein published the theory of relativity while at the Swiss Patent Office."
410                .to_string(),
411            relations: vec![
412                RelationGold::new(
413                    (0, 8),
414                    "PER",
415                    "Einstein",
416                    (23, 44),
417                    "MISC",
418                    "theory of relativity",
419                    "AUTHORED",
420                ),
421                RelationGold::new(
422                    (0, 8),
423                    "PER",
424                    "Einstein",
425                    (58, 77),
426                    "ORG",
427                    "Swiss Patent Office",
428                    "WORKS_FOR",
429                ),
430            ],
431            difficulty: Difficulty::Medium,
432            domain: Domain::Scientific,
433        },
434    ]
435}
436
437/// Biographical domain examples.
438fn biography_domain() -> Vec<RelationExample> {
439    vec![
440        RelationExample {
441            text: "Barack Obama, born in Honolulu, served as the 44th President.".to_string(),
442            relations: vec![RelationGold::new(
443                (0, 12),
444                "PER",
445                "Barack Obama",
446                (22, 30),
447                "LOC",
448                "Honolulu",
449                "BORN_IN",
450            )],
451            difficulty: Difficulty::Easy,
452            domain: Domain::Biography,
453        },
454        RelationExample {
455            text: "Marie Curie and Pierre Curie, her husband, won the Nobel Prize.".to_string(),
456            relations: vec![RelationGold::new(
457                (0, 11),
458                "PER",
459                "Marie Curie",
460                (16, 28),
461                "PER",
462                "Pierre Curie",
463                "SPOUSE_OF",
464            )],
465            difficulty: Difficulty::Medium,
466            domain: Domain::Biography,
467        },
468    ]
469}
470
471/// Get statistics about the relation dataset.
472pub fn stats() -> RelationStats {
473    let all = dataset();
474    let total_relations: usize = all.iter().map(|ex| ex.relations.len()).sum();
475
476    let mut relation_types = std::collections::HashMap::new();
477    for ex in &all {
478        for rel in &ex.relations {
479            *relation_types.entry(rel.relation_type.clone()).or_insert(0) += 1;
480        }
481    }
482
483    RelationStats {
484        total_examples: all.len(),
485        total_relations,
486        relation_types,
487    }
488}
489
490/// Statistics about relation dataset.
491#[derive(Debug, Clone)]
492pub struct RelationStats {
493    /// Total number of examples.
494    pub total_examples: usize,
495    /// Total relations.
496    pub total_relations: usize,
497    /// Count per relation type.
498    pub relation_types: std::collections::HashMap<String, usize>,
499}
500
501#[cfg(test)]
502mod tests {
503    use super::*;
504
505    #[test]
506    fn test_dataset_not_empty() {
507        let examples = dataset();
508        assert!(!examples.is_empty());
509        assert!(examples.len() >= 10, "Should have at least 10 examples");
510    }
511
512    #[test]
513    fn test_has_multiple_relation_types() {
514        let s = stats();
515        assert!(
516            s.relation_types.len() >= 5,
517            "Should have at least 5 relation types"
518        );
519    }
520
521    #[test]
522    fn test_entity_spans_valid() {
523        for example in dataset() {
524            for rel in &example.relations {
525                assert!(
526                    rel.head_span.1 <= example.text.len(),
527                    "Head span exceeds text length in: {}",
528                    example.text
529                );
530                assert!(
531                    rel.tail_span.1 <= example.text.len(),
532                    "Tail span exceeds text length in: {}",
533                    example.text
534                );
535                assert!(
536                    rel.head_span.0 < rel.head_span.1,
537                    "Invalid head span in: {}",
538                    example.text
539                );
540                assert!(
541                    rel.tail_span.0 < rel.tail_span.1,
542                    "Invalid tail span in: {}",
543                    example.text
544                );
545            }
546        }
547    }
548
549    #[test]
550    fn test_stats() {
551        let s = stats();
552        assert!(s.total_examples > 0);
553        assert!(s.total_relations > 0);
554        assert!(!s.relation_types.is_empty());
555    }
556}