LIMA
Libre Multilingual Analyzer — C++ API
Loading...
Searching...
No Matches
EventTemplateMerging.cpp
Go to the documentation of this file.
1// Copyright 2002-2021 CEA LIST
2// SPDX-FileCopyrightText: 2022 CEA LIST <gael.de-chalendar@cea.fr>
3//
4// SPDX-License-Identifier: MIT
5
6/************************************************************************
7 *
8 * @file EventTemplateMerging.cpp
9 * @author besancon (besanconr@zoe.cea.fr)
10 * @date Mon Sep 26 2011
11 * copyright Copyright (C) 2011 by CEA - LIST
12 *
13 ***********************************************************************/
14
16#include "EventTemplateData.h"
17
27
28using namespace Lima::Common::AnnotationGraphs;
30using namespace std;
31
32namespace Lima {
33namespace LinguisticProcessing {
34namespace EventAnalysis {
35
36//----------------------------------------------------------------------
37// factory for process unit
39
41m_templateDefinition(0),
42m_mandatoryElements(),
43m_maxCharCompatibleEvents(200),
44m_useSentenceBounds(false),
45m_sentenceBoundsData("SentenceBoundaries")
46{
47}
48
52
55 Manager* manager)
56
57{
58 MediaId language=manager->getInitializationParameters().media;
59
60 try {
61 std::string templateResource=unitConfiguration.getParamsValueAtKey("eventTemplate");
62 auto res = LinguisticResources::single().getResource(language,templateResource);
63 if (res) {
64 m_templateDefinition = std::dynamic_pointer_cast<EventTemplateDefinitionResource>(res);
65 }
66 }
69 LERROR << "TemplateMerging: Missing 'eventTemplate' parameter in EventTemplateMerging definition";
70 //throw InvalidConfiguration;
71 }
72 catch (std::exception& e) {
74 LERROR << "TemplateMerging: Missing ressource for 'eventTemplate' parameter" << e.what();
75 //throw InvalidConfiguration;
76 }
77
78 try {
79 m_mandatoryElements=unitConfiguration.getListsValueAtKey("mandatoryElements");
80 }
81 catch (Common::XMLConfigurationFiles::NoSuchList& ) { } // optional
82
83 bool optionalMention(false);
84 try {
85 // by default, the event mention should be mandatory (it is for brat, for instance):
86 // add a parameter to specify if the event mention can be optional
87 optionalMention=unitConfiguration.getBooleanParameter("optionalMention");
88 }
89 catch (Common::XMLConfigurationFiles::NoSuchParam& ) { } // optional
90 catch (Common::XMLConfigurationFiles::NoSuchList& ) { } // optional
91
92 if (! optionalMention) {
93 // add event mention as a mandatory element
94 m_mandatoryElements.push_back(m_templateDefinition->getMention());
95 }
96
97 try {
98 std::string s=unitConfiguration.getParamsValueAtKey("maxCharCompatibleEvents");
99 m_maxCharCompatibleEvents=std::stoul(s);
100 }
101 catch (Common::XMLConfigurationFiles::NoSuchParam& ) { } // optional: keep default value
102 try {
103 m_useSentenceBounds=
104 unitConfiguration.getBooleanParameter("useSentenceBounds");
105 }
107 // optional parameter: keep default value
108 }
109
110 try
111 {
112 string sentenceBoundsData=unitConfiguration.getParamsValueAtKey("sentenceBoundsData");
113 if (! sentenceBoundsData.empty()) {
114 m_sentenceBoundsData=sentenceBoundsData;
115 }
116 }
118 {
119 // optional parameter: keep default value
120 }
121
122}
123
125{
127 LDEBUG << "EventTemplateMerging process";
129
130 // get EventTemplateData
131 auto eventData = std::dynamic_pointer_cast<EventTemplateData>(analysis.getData("EventTemplateData"));
132 if (eventData==0) {
133 LERROR << "TemplateMerging:missing data 'EventTemplateData'";
134 return MISSING_DATA;
135 }
136
137 LimaStatusCode returnCode=SUCCESS_ID;
138 if (m_useSentenceBounds) {
139 returnCode=mergeEventTemplatesOnEachSentence(analysis, eventData.get());
140 }
141 else {
142 returnCode=mergeEventTemplates(eventData.get(),0,0);
143 }
144
145 cleanEventTemplates(eventData.get());
146
147 TimeUtils::logElapsedTime("EventTemplateMerging");
148 return returnCode;
149}
150
151LimaStatusCode EventTemplateMerging::mergeEventTemplatesOnEachSentence(AnalysisContent& analysis, EventTemplateData* eventData) const
152{
154 // get sentence bounds
155 auto sb = std::dynamic_pointer_cast<SegmentationData>(analysis.getData(m_sentenceBoundsData));
156 if (nullptr==sb)
157 {
158 LERROR << "no sentence bounds"<< m_sentenceBoundsData << "defined ! abort";
159 return MISSING_DATA;
160 }
162 for (const auto& segment: sb->getSegments()) {
163 res = mergeEventTemplates(eventData,segment.getPosBegin(),segment.getPosEnd());
164 if (res!=SUCCESS_ID) {
165 break;
166 }
167 }
168 return res;
169}
170
171// utility function to check if a template is considered or ignored
172bool EventTemplateMerging::ignoreTemplate(const EventTemplate& event, uint64_t numTemplate,
173 uint64_t posBegin, uint64_t posEnd,
174 const std::set<uint64_t>& toRemove) const
175{
176// if (event.getPosEnd()==0 || event.getTemplateElements().size()==0) {
177// EVENTANALYSISLOGINIT;
178// LDEBUG << "Template"<< numTemplate << "ignored: since it is not initialized";
179// return true;
180// }
181
182 if (event.getPosEnd()<=posBegin || (posEnd>0 && event.getPosBegin()>=posEnd) ) {
184 LDEBUG << "Template"<< numTemplate << "ignored: not in the considered segment"
185 <<"["<<event.getPosBegin()<<","<<event.getPosEnd()<<"]";
186 return true;
187 }
188
189 if (event.getType()!=m_templateDefinition->getMention()) {
191 LDEBUG << "Template"<< numTemplate << "ignored: not the considered event type ("<<event.getType()<<"/"<<m_templateDefinition->getMention()<<")";
192 return true;
193 }
194
195 // do we keep merging with other templates if this one is to be removed ?
196 if (toRemove.find(numTemplate)!=toRemove.end()) {
198 LDEBUG << "Template"<< numTemplate << "ignored: already merged";
199 return true;
200 }
201
202 return false;
203}
204
205
206LimaStatusCode EventTemplateMerging::mergeEventTemplates(EventTemplateData* eventData, uint64_t posBegin, uint64_t posEnd) const
207{
208 // ad hoc strategy for merging event templates
210
211 // merge templates according to their positions , and positions of intermediate entities
212 LDEBUG << "EventTemplateMerging::mergeEventTemplates(): merge templates of type" << m_templateDefinition->getName() << "in ["<<posBegin<<","<<posEnd<<"]";
213 LDEBUG << "nb events =" << eventData->size();
214
215 std::set<uint64_t> toRemove;
216 uint64_t numTemplate=0;
217 for (EventTemplateData::iterator it1=eventData->begin(),it_end=eventData->end();it1!=it_end;it1++,numTemplate++)
218 {
219 if (ignoreTemplate(*it1,numTemplate,posBegin,posEnd,toRemove)) {
220 continue;
221 }
222
223 // begin comparison with next template
224 EventTemplateData::iterator it2=it1;
225 uint64_t numOtherTemplate=numTemplate;
226 for (it2++,numOtherTemplate++; it2!=it_end; it2++,numOtherTemplate++)
227 {
228 if (ignoreTemplate(*it2,numOtherTemplate,posBegin,posEnd,toRemove)) {
229 continue;
230 }
231
232 TemplateElements& templateElements2=(*it2).getTemplateElements();
233 if (templateElements2.empty()) {
234 continue;
235 }
236
237 LDEBUG << "TemplateMerging: compare templates"
238 << numTemplate << "and" << numOtherTemplate << ":\n"
239 << *it1 << "\n" << *it2;
240
241 unsigned int templateToRemove(0);
242 // single function to test compatibility and perform the merge, returns true if the merge is successful
243 // in this case, the templateToRemove contains the index of the event template that has been merged and
244 // can be removed
245 if (mergeTemplates(*it1,*it2,numTemplate,numOtherTemplate,templateToRemove)) {
246 toRemove.insert(templateToRemove);
247 // stop if this template has been merged into another
248 if (templateToRemove==numTemplate) {
249 break;
250 }
251 }
252 }
253 }
254
255 // remove merged templates : get template number in reverse
256 for (set<uint64_t>::reverse_iterator it=toRemove.rbegin(),
257 it_end=toRemove.rend(); it!=it_end; it++)
258 {
259 LDEBUG << "Erase template" << (*it)+1;
260 eventData->erase(eventData->begin()+(*it));
261 }
262 LDEBUG << "EventTemplateMerging::mergeEventTemplates() Done";
263 return SUCCESS_ID;
264}
265
266void EventTemplateMerging::cleanEventTemplates(EventTemplateData* eventData) const
267{
269 LDEBUG << "EventTemplateMerging::cleanEventTemplates()" << m_templateDefinition->getName();
270 // additional step if mandatory elements are specified : keep only templates with mandatory elements
271 if (! m_mandatoryElements.empty()) {
272 EventTemplateData::iterator it=eventData->begin();
273 uint64_t n=1; // only for debug messages
274 while (it!=eventData->end()) {
275 bool toRemove=false;
276 const TemplateElements& elements=(*it).getTemplateElements();
277 for (deque<string>::const_iterator m=m_mandatoryElements.begin(),m_end=m_mandatoryElements.end();m!=m_end;m++) {
278 if (elements.find(*m)==elements.end()) {
279 LDEBUG << " Erase template" << n << "because it does not contain mandatory element" << *m << ":" << elements;
280 toRemove=true;
281 break;
282 }
283 }
284 if (toRemove) {
285 it=eventData->erase(it);
286 }
287 else {
288 it++;
289 n++;
290 }
291 }
292 }
293 LDEBUG << "EventTemplateMerging::cleanEventTemplates()" << m_templateDefinition->getName() << "Done";
294}
295
296bool EventTemplateMerging::compatibleTemplates(const EventTemplate& e1, const EventTemplate& e2,
297 unsigned int numTemplate1, unsigned int numTemplate2,
298 TemplateElements& mergedElements) const
299// map<string,pair<string, int> >& matchingElements) const
300{
301 // numTemplate and numOtherTemplate are only there for debug messages
302
304 // possible incompatibility based on positions
305 uint64_t posdiff=0;
306 // if overlap, posdiff=0
307 if (e1.getPosBegin() <= e2.getPosEnd() && e2.getPosBegin() <= e1.getPosEnd()) {
308 posdiff=0;
309 }
310 else if (e1.getPosBegin()>e2.getPosBegin()) {
311 posdiff=e1.getPosBegin()-e2.getPosEnd();
312 }
313 else {
314 posdiff=e2.getPosBegin()-e1.getPosEnd();
315 }
316 if (posdiff >m_maxCharCompatibleEvents) {
317 LDEBUG << "TemplateMerging: templates"
318 << numTemplate1 << "and" << numTemplate2
319 << "not compatible: too much apart ("<< posdiff << "chars)";
320 return false;
321 }
322
323 const TemplateElements& templateElements1=e1.getTemplateElements();
324 const TemplateElements& templateElements2=e2.getTemplateElements();
325
326 std::vector<EventTemplateElement> merged;
327 // merge template1 in template2: store the roles in template 1 that do correspond to one in template2
328 // (to add the ones that do not, at the end)
329 std::set<std::string> alreadyMerged;
330
331 // templateElements is a multimap: for each role, each template has a set of values
332 for (TemplateElements::const_iterator elt2=templateElements2.begin(),
333 elt2_end=templateElements2.end(); elt2!=elt2_end; )
334 {
335 const std::string& role2=(*elt2).first;
336 unsigned int cardinality=m_templateDefinition->getCardinality(role2);
337 // get all values for this role: use pointers to avoid copies
338 vector<const EventTemplateElement*> values2;
339
340 // Advance to next non-duplicate entry.
341 while (elt2 != templateElements2.end() && role2 == (*elt2).first) {
342 values2.push_back(&((*elt2).second));
343 ++elt2;
344 }
345
346 pair<TemplateElements::const_iterator,TemplateElements::const_iterator> found1=templateElements1.equal_range(role2);
347 if (found1.first!=found1.second) {
348 // the same role exists in other template, check values
349 vector<const EventTemplateElement*> values1;
350 for (TemplateElements::const_iterator it=found1.first,it_end=found1.second;it!=it_end;++it) {
351 values1.push_back(&((*it).second));
352 }
353 if (compatibleValues(values2,values1,cardinality,merged)) {
354 for (const auto& value: merged) {
355 mergedElements.emplace(role2,value);
356 }
357 alreadyMerged.insert(role2);
358 }
359 else {
360 // different values, assume templates of different elements
361 LDEBUG << "TemplateMerging: templates"
362 << numTemplate1 << "and" << numTemplate2
363 << ":" << role2 << ": incompatible values";
364 mergedElements.clear();
365 return false;
366 }
367 }
368 else { // try to find mapping through constraint
369 bool mappingFound=false;
370 for (TemplateElements::const_iterator elt1=templateElements1.begin(),
371 elt1_end=templateElements1.end(); elt1!=elt1_end; )
372 {
373 const std::string& role1=(*elt1).first;
374 int mapping=m_templateDefinition->existsMapping(role1,role2);
375 if (mapping!=0) {
376 mappingFound=true;
377 // compatible role, check values
378 // gather values from other template : advance to next non-duplicate entry
379 vector<const EventTemplateElement*> values1;
380 while (elt1 != templateElements1.end() && role1 == (*elt1).first) {
381 values1.push_back(&((*elt1).second));
382 ++elt1;
383 }
384 if (compatibleValues(values2,values1,cardinality,merged)) {
385 for (const auto& value: merged) {
386 mergedElements.emplace(role2,value);
387 }
388 alreadyMerged.insert(role1);
389 }
390 else {
391 // different values, assume templates of different elements
392 LDEBUG << "TemplateMerging: templates"
393 << numTemplate1 << "and" << numTemplate2
394 << ":" << role2 << "/" << (*elt1).first << ": incompatible values";
395 mergedElements.clear();
396 return false;
397 }
398 // do not search for another match in mapping, take first
399 break;
400 }
401 else {
402 // no mapping: advance to next non-duplicate entry
403 do {
404 elt1++;
405 } while (elt1 != templateElements1.end() && role1== (*elt1).first);
406 }
407 }
408 if (! mappingFound) {
409 LDEBUG << "TemplateMerging: templates"
410 << numTemplate1 << "and" << numTemplate2
411 << ":" << role2 << ": not found in" << numTemplate1;
412 // add it to the merged template
413 for (const auto& v2: values2) {
414 mergedElements.emplace(role2,*v2);
415 }
416 }
417 }
418
419 /*
420 //--begin old code--
421 const EventTemplateElement& elt=(*fill).second;
422
423 TemplateElements::const_iterator found=templateElements1.find(role);
424 if (found!=templateElements1.end()) {
425 // same role, check value
426 if (elt.isSimilar((*found).second)) {
427 LDEBUG << "TemplateMerging: templates"
428 << (numTemplate+1) << "and" << (numOtherTemplate+1)
429 << ":" << role << ": compatible value";
430 matchingElements[role]=make_pair(role,0);
431 }
432 else {
433 // different values, assume templates of different elements
434 LDEBUG << "TemplateMerging: templates"
435 << (numTemplate+1) << "and" << (numOtherTemplate+1)
436 << ":" << role << ": incompatible value";
437 return false;
438 }
439 }
440 else { // try to find mapping through constraint
441 bool mappingFound=false;
442 for (TemplateElements::const_iterator e=templateElements1.begin(),
443 e_end=templateElements1.end(); e!=e_end; e++)
444 {
445 int mapping=m_templateDefinition->existsMapping((*e).first,role);
446 if (mapping!=0) {
447 mappingFound=true;
448 // same role, check value
449 if (elt.isSimilar((*e).second)) {
450 matchingElements[(*e).first]=make_pair(role,mapping);
451 }
452 else {
453 // different values, assume templates of different elements
454 LDEBUG << "TemplateMerging: templates"
455 << (numTemplate+1) << "and" << (numOtherTemplate+1)
456 << ":" << role << "/" << (*e).first << ": incompatible value";
457 return false;
458 }
459 // do not search for another match in mapping, take first
460 break;
461 }
462 }
463 if (! mappingFound) {
464 LDEBUG << "TemplateMerging: templates"
465 << (numTemplate+1) << "and" << (numOtherTemplate+1)
466 << ":" << role << ": not found";
467 }
468 }
469 //--end old code--
470 */
471 }
472 // add in mergedElements the one from template1 that were not already merged
473 for (const auto& elt1: templateElements1) {
474 if (alreadyMerged.find(elt1.first)==alreadyMerged.end()) {
475 mergedElements.emplace(elt1.first,elt1.second);
476 }
477 }
478
479 return true;
480}
481
482bool EventTemplateMerging::compatibleValues(const std::vector<const EventTemplateElement*> values1,
483 const std::vector<const EventTemplateElement*> values2,
484 unsigned int cardinality,
485 std::vector<EventTemplateElement>& merged) const
486{
487 // a clean way to check compatibility of multiple values could be to use a general assignment problem solution
488 // (such as Kuhn-Munkres hungarian algorithm)
489 // seems a bit complex to implement: use a simpler greedy method (not optimal, but suppose the cardinality of
490 // values is generally one anyway)
491 vector<bool> done(values2.size(),false);
492 for (const auto val1: values1) {
493 for (unsigned int i(0),n=values2.size();i<n;i++) {
494 if (done[i]) {
495 continue;
496 }
497 if (val1->isSimilar(*(values2[i]))) {
498 done[i]=true;
499 }
500 }
501 merged.push_back(*val1);
502 }
503 for (unsigned int i(0),n=values2.size();i<n;i++) {
504 if (! done[i]) {
505 if (merged.size()+1>cardinality) {
506 merged.clear();
507 return false;
508 }
509 merged.push_back(*(values2[i]));
510 }
511 }
512 return true;
513}
514
515
516bool EventTemplateMerging::
517mergeTemplates(EventTemplate& e1, EventTemplate& e2,
518 unsigned int numTemplate1, unsigned int numTemplate2,
519 unsigned int& templateToRemove) const
520{
521 // merge isn't always in the same order: we want to keep elements from the template with the smallest span,
522 // to avoid having info from all over the place, even if they are equal (e.g. for brat visualization after extraction)
523 // sometimes, info is merged from e1 to e2, sometimes from e1 to e2
524 // templateToRemove indicates which (between numTemplate and numOtherTemplate) has been merged into the other
525 // and can be removed
526
527 // for the moment, reuse the existing compatibleTemplates function
528 // (probably not optimal)
529
531
532 // while comparing templates for compatibility, store matching elements:
533 // map associating a role1 with a pair (role2,mapping) where the other_role is the role that matches
534 // role1 and the mapping: 0 if equality, otherwise oriented mapping: 1 if mapping elt1 -> elt2,
535 // -1 if mapping elt2 -> elt1
536 //map<string,pair<string, int> > matchingElements;
537
538 // while comparing templates for compatibility, create the merged version of the template
539 // replace the whole template by the merged template (not as efficient as changing each template element,
540 // but easier to code/maintain)
541 TemplateElements mergedElements;
542
543 // by default merge 1 into 2
544 // use pointers instead of references to reassign them if merge is the other way
545 //TemplateElements* sourceElements=&(e1.getTemplateElements());
546 //TemplateElements* targetElements=&(e2.getTemplateElements());
547 EventTemplate* templateToModify=&e2;
548
549 // heuristic: keep the templates with the most elements
550 if (e1.getTemplateElements().size() > e2.getTemplateElements().size()) {
551 if (!compatibleTemplates(e2,e1,numTemplate2,numTemplate1,mergedElements)) {
552 return false;
553 }
554 // sourceElements=&(e2.getTemplateElements());
555 // targetElements=&(e1.getTemplateElements());
556 templateToModify=&e1;
557 templateToRemove=numTemplate2;
558 }
559 else if (!compatibleTemplates(e1,e2,numTemplate1,numTemplate2,mergedElements)) {
560 return false;
561 }
562 else {
563 LDEBUG << "TemplateMerging: merge" << numTemplate1 << "into" << numTemplate2;
564 templateToRemove=numTemplate1;
565 }
566
567 templateToModify->setTemplateElements(mergedElements);
568
569 /*
570 for (const auto& elt1: *sourceElements)
571 {
572 map<string,pair<string,int> >::const_iterator match=matchingElements.find(elt1.first);
573 if (match==matchingElements.end()) {
574 // no matching: add element from first template in second one
575 LDEBUG << "TemplateMerging: add element" << elt1.first;
576 targetElements->insert(elt1);
577 }
578 else {
579 // matching exists : check value
580 if ((*match).second.second==-1) {
581 // mapping from 1 to 2 : remove role from 2 and insert role from 1
582 LDEBUG << "TemplateMerging: replace element" << (*match).second.first << "with" << elt1.first;
583 targetElements->insert(elt1);
584 targetElements->erase((*match).second.first);
585 }
586 else if ((*match).second.second==1) {
587 LDEBUG << "TemplateMerging: mapping 1: ignore"<< elt1.first << "(keep" << (*match).second.first << ")";
588 // mapping from 2 to 1 : do not insert element in second template, keep role
589 }
590 else {
591 // else (0) : same role : do not insert element in second template
592 LDEBUG << "TemplateMerging: mapping 0: ignore"<< elt1.first;
593 }
594 }
595 }
596 */
597 // returns the template to remove
598 if (templateToRemove==numTemplate1) {
599 LDEBUG << "TemplateMerging: keep template" << numTemplate2 << e2;
600 }
601 else {
602 LDEBUG << "TemplateMerging: keep template"<< numTemplate1 << e1;
603 }
604 return true;
605}
606
607
608} // end namespace
609} // end namespace
610} // end namespace
This file is the main header file for the data related to annotation graphs.
#define EVENTTEMPLATEMERGING_CLASSID
#define LDEBUG
Definition LimaCommon.h:157
#define LERROR
Definition LimaCommon.h:161
#define EVENTANALYSISLOGINIT
Defines a Factory to create Object of type Base.
Holds all data that pass through the ProcessUnits Analysis data are shared pointers,...
std::shared_ptr< AnalysisData > getData(const QString &id)
return AnalysisData by id
std::deque< std::string > & getListsValueAtKey(const std::string &key)
return a message when a 'param' was not found
Manage initialization of InitializableObjects using configuration module and parameters.
const InitializationParameters & getInitializationParameters() const
get Initialization Parameters
LimaStatusCode process(AnalysisContent &analysis) const override
Process on data in analysisContent.
void init(Common::XMLConfigurationFiles::GroupConfigurationStructure &unitConfiguration, Manager *manager) override
initialize with parameters from configuration file.
static const LinguisticResources & single()
const singleton accessor
Definition Singleton.h:51
static void logElapsedTime(const std::string &mess, const std::string &taskCategory=std::string(""))
log the number of microseconds since last UpdateCurrentTime
static void updateCurrentTime(const std::string &taskCategory=std::string(""))
store current time for new elapsed time computation
std::multimap< std::string, EventTemplateElement > TemplateElements
NAUTITIA.
LimaStatusCode
Definition LimaCommon.h:236
@ SUCCESS_ID
Definition LimaCommon.h:237
@ MISSING_DATA
Definition LimaCommon.h:243
STL namespace.
launch exception related to the configuration file parsing