1use serde::{Deserialize, Serialize};
46
47#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
49pub enum BridgingType {
50 PartWhole,
52 SetMembership,
54 Role,
56 Attribute,
58 ProducerProduct,
60 EventParticipant,
62 Location,
64 Comparative,
66 Other(String),
68}
69
70impl Default for BridgingType {
71 fn default() -> Self {
72 Self::Other(String::new())
73 }
74}
75
76impl BridgingType {
77 pub fn from_label(label: &str) -> Self {
79 match label.to_lowercase().as_str() {
80 "part-whole" | "meronymy" | "part_whole" => Self::PartWhole,
81 "set-membership" | "set_membership" | "set" => Self::SetMembership,
82 "role" | "functional" => Self::Role,
83 "attribute" | "property" => Self::Attribute,
84 "producer-product" | "producer_product" => Self::ProducerProduct,
85 "event-participant" | "event_participant" | "participant" => Self::EventParticipant,
86 "location" | "spatial" => Self::Location,
87 "comparative" => Self::Comparative,
88 other => Self::Other(other.to_string()),
89 }
90 }
91
92 pub fn as_label(&self) -> &str {
94 match self {
95 Self::PartWhole => "part-whole",
96 Self::SetMembership => "set-membership",
97 Self::Role => "role",
98 Self::Attribute => "attribute",
99 Self::ProducerProduct => "producer-product",
100 Self::EventParticipant => "event-participant",
101 Self::Location => "location",
102 Self::Comparative => "comparative",
103 Self::Other(s) => s.as_str(),
104 }
105 }
106}
107
108#[derive(Debug, Clone, Serialize, Deserialize)]
110pub struct BridgingLink {
111 pub anaphor: BridgingMention,
113 pub antecedent: BridgingMention,
115 pub bridging_type: BridgingType,
117 pub confidence: f64,
119 pub is_lexical: bool,
121}
122
123impl BridgingLink {
124 pub fn new(
126 anaphor: BridgingMention,
127 antecedent: BridgingMention,
128 bridging_type: BridgingType,
129 ) -> Self {
130 Self {
131 anaphor,
132 antecedent,
133 bridging_type,
134 confidence: 1.0,
135 is_lexical: false,
136 }
137 }
138
139 pub fn is_part_whole(&self) -> bool {
141 matches!(self.bridging_type, BridgingType::PartWhole)
142 }
143
144 pub fn is_set_membership(&self) -> bool {
146 matches!(self.bridging_type, BridgingType::SetMembership)
147 }
148}
149
150#[derive(Debug, Clone, Serialize, Deserialize)]
152pub struct BridgingMention {
153 pub text: String,
155 pub start: usize,
157 pub end: usize,
159 pub head: Option<String>,
161 pub sentence_idx: Option<usize>,
163}
164
165impl BridgingMention {
166 pub fn new(text: &str, start: usize, end: usize) -> Self {
168 Self {
169 text: text.to_string(),
170 start,
171 end,
172 head: None,
173 sentence_idx: None,
174 }
175 }
176
177 pub fn with_head(mut self, head: &str) -> Self {
179 self.head = Some(head.to_string());
180 self
181 }
182
183 pub fn with_sentence(mut self, idx: usize) -> Self {
185 self.sentence_idx = Some(idx);
186 self
187 }
188}
189
190#[derive(Debug, Clone, Default, Serialize, Deserialize)]
192pub struct BridgingDocument {
193 pub id: String,
195 pub text: String,
197 pub links: Vec<BridgingLink>,
199}
200
201impl BridgingDocument {
202 pub fn new(id: &str, text: &str) -> Self {
204 Self {
205 id: id.to_string(),
206 text: text.to_string(),
207 links: Vec::new(),
208 }
209 }
210
211 pub fn add_link(&mut self, link: BridgingLink) {
213 self.links.push(link);
214 }
215
216 pub fn len(&self) -> usize {
218 self.links.len()
219 }
220
221 pub fn is_empty(&self) -> bool {
223 self.links.is_empty()
224 }
225
226 pub fn links_by_type(&self, bridging_type: &BridgingType) -> Vec<&BridgingLink> {
228 self.links
229 .iter()
230 .filter(|l| &l.bridging_type == bridging_type)
231 .collect()
232 }
233}
234
235#[derive(Debug, Clone, Default, Serialize, Deserialize)]
237pub struct BridgingMetrics {
238 pub precision: f64,
240 pub recall: f64,
242 pub f1: f64,
244 pub predicted: usize,
246 pub gold: usize,
248 pub correct: usize,
250 pub by_type: std::collections::HashMap<String, (f64, f64, f64)>, }
253
254impl BridgingMetrics {
255 pub fn compute(predicted: &[BridgingDocument], gold: &[BridgingDocument]) -> Self {
257 let mut total_pred = 0;
258 let mut total_gold = 0;
259 let mut total_correct = 0;
260 let mut by_type: std::collections::HashMap<String, (usize, usize, usize)> =
261 std::collections::HashMap::new();
262
263 for (pred_doc, gold_doc) in predicted.iter().zip(gold.iter()) {
264 total_pred += pred_doc.links.len();
265 total_gold += gold_doc.links.len();
266
267 for pred_link in &pred_doc.links {
269 let type_key = pred_link.bridging_type.as_label().to_string();
270 by_type.entry(type_key.clone()).or_insert((0, 0, 0)).0 += 1;
271
272 for gold_link in &gold_doc.links {
273 if Self::links_match(pred_link, gold_link) {
274 total_correct += 1;
275 by_type.entry(type_key.clone()).or_insert((0, 0, 0)).2 += 1;
276 break;
277 }
278 }
279 }
280
281 for gold_link in &gold_doc.links {
282 let type_key = gold_link.bridging_type.as_label().to_string();
283 by_type.entry(type_key).or_insert((0, 0, 0)).1 += 1;
284 }
285 }
286
287 let precision = if total_pred > 0 {
288 total_correct as f64 / total_pred as f64
289 } else {
290 0.0
291 };
292 let recall = if total_gold > 0 {
293 total_correct as f64 / total_gold as f64
294 } else {
295 0.0
296 };
297 let f1 = if precision + recall > 0.0 {
298 2.0 * precision * recall / (precision + recall)
299 } else {
300 0.0
301 };
302
303 let by_type_metrics: std::collections::HashMap<String, (f64, f64, f64)> = by_type
305 .into_iter()
306 .map(|(k, (pred, gold, corr))| {
307 let p = if pred > 0 {
308 corr as f64 / pred as f64
309 } else {
310 0.0
311 };
312 let r = if gold > 0 {
313 corr as f64 / gold as f64
314 } else {
315 0.0
316 };
317 let f = if p + r > 0.0 {
318 2.0 * p * r / (p + r)
319 } else {
320 0.0
321 };
322 (k, (p, r, f))
323 })
324 .collect();
325
326 Self {
327 precision,
328 recall,
329 f1,
330 predicted: total_pred,
331 gold: total_gold,
332 correct: total_correct,
333 by_type: by_type_metrics,
334 }
335 }
336
337 fn links_match(a: &BridgingLink, b: &BridgingLink) -> bool {
339 a.anaphor.start == b.anaphor.start
340 && a.anaphor.end == b.anaphor.end
341 && a.antecedent.start == b.antecedent.start
342 && a.antecedent.end == b.antecedent.end
343 }
344}
345
346#[cfg(test)]
347mod tests {
348 use super::*;
349
350 #[test]
351 fn test_bridging_type_from_label() {
352 assert_eq!(
353 BridgingType::from_label("part-whole"),
354 BridgingType::PartWhole
355 );
356 assert_eq!(
357 BridgingType::from_label("SET-MEMBERSHIP"),
358 BridgingType::SetMembership
359 );
360 assert_eq!(BridgingType::from_label("role"), BridgingType::Role);
361 }
362
363 #[test]
364 fn test_bridging_link_creation() {
365 let anaphor = BridgingMention::new("the engine", 20, 30);
366 let antecedent = BridgingMention::new("The car", 0, 7);
367 let link = BridgingLink::new(anaphor, antecedent, BridgingType::PartWhole);
368
369 assert!(link.is_part_whole());
370 assert!(!link.is_set_membership());
371 assert_eq!(link.bridging_type.as_label(), "part-whole");
372 }
373
374 #[test]
375 fn test_bridging_document() {
376 let mut doc = BridgingDocument::new("doc1", "The car broke down. The engine failed.");
377
378 let anaphor = BridgingMention::new("The engine", 20, 30).with_sentence(1);
379 let antecedent = BridgingMention::new("The car", 0, 7).with_sentence(0);
380 let link = BridgingLink::new(anaphor, antecedent, BridgingType::PartWhole);
381
382 doc.add_link(link);
383
384 assert_eq!(doc.len(), 1);
385 assert!(!doc.is_empty());
386 assert_eq!(doc.links_by_type(&BridgingType::PartWhole).len(), 1);
387 }
388
389 #[test]
390 fn test_bridging_metrics() {
391 let mut pred_doc = BridgingDocument::new("doc1", "");
392 pred_doc.add_link(BridgingLink::new(
393 BridgingMention::new("the engine", 20, 30),
394 BridgingMention::new("The car", 0, 7),
395 BridgingType::PartWhole,
396 ));
397
398 let mut gold_doc = BridgingDocument::new("doc1", "");
399 gold_doc.add_link(BridgingLink::new(
400 BridgingMention::new("the engine", 20, 30),
401 BridgingMention::new("The car", 0, 7),
402 BridgingType::PartWhole,
403 ));
404
405 let metrics = BridgingMetrics::compute(&[pred_doc], &[gold_doc]);
406
407 assert_eq!(metrics.precision, 1.0);
408 assert_eq!(metrics.recall, 1.0);
409 assert_eq!(metrics.f1, 1.0);
410 }
411}