AnnotationDiffer.java
001 /*
002  *  Copyright (c) 1995-2011, The University of Sheffield. See the file
003  *  COPYRIGHT.txt in the software or at http://gate.ac.uk/gate/COPYRIGHT.txt
004  *
005  *  This file is part of GATE (see http://gate.ac.uk/), and is free
006  *  software, licenced under the GNU Library General Public License,
007  *  Version 2, June 1991 (in the distribution as file licence.html,
008  *  and also available at http://gate.ac.uk/gate/licence.html).
009  *
010  *  Valentin Tablan 28/01/2003
011  *
012  *  $Id: AnnotationDiffer.java 17606 2014-03-09 12:12:49Z markagreenwood $
013  *
014  */
015 package gate.util;
016 
017 import java.math.RoundingMode;
018 import java.text.NumberFormat;
019 import java.util.*;
020 
021 import gate.Annotation;
022 
023 /**
024  * This class provides the logic used by the Annotation Diff tool. It starts 
025  * with two collections of annotation objects, one of key annotations
026  * (representing the gold standard) and one of response annotations 
027  * (representing the system's responses). It will then pair the keys and 
028  * responses in a way that maximises the score. Each key - response pair gets a 
029  * score of {@link #CORRECT_VALUE} (2), {@link #PARTIALLY_CORRECT_VALUE} (1) or 
030  {@link #WRONG_VALUE} (0)depending on whether the two annotations match are 
031  * overlapping or completely unmatched. Each pairing also has a type of 
032  {@link #CORRECT_TYPE}{@link #PARTIALLY_CORRECT_TYPE}
033  {@link #SPURIOUS_TYPE} or {@link #MISSING_TYPE} further detailing the type of
034  * error for the wrong matches (<i>missing</i> being the keys that weren't 
035  * matched to a response while  <i>spurious</i> are the responses that were 
036  * over-generated and are not matching any key.
037  *   
038  * Precision, recall and f-measure are also calculated.
039  */
040 public class AnnotationDiffer {
041 
042   /**
043    * Constructor to be used when you have a collection of AnnotationDiffer
044    * and want to consider it as only one AnnotationDiffer.
045    * Then you can only use the methods getPrecision/Recall/FMeasure...().
046    @param differs collection to be regrouped in one AnnotationDiffer
047    */
048   public AnnotationDiffer(Collection<AnnotationDiffer> differs) {
049     correctMatches = 0;
050     partiallyCorrectMatches = 0;
051     missing = 0;
052     spurious = 0;
053     int keyCount = 0;
054     int responseCount = 0;
055     for (AnnotationDiffer differ : differs) {
056       // set the correct, partial, spurious and missing values to be
057       // the sum of those in the collection
058       correctMatches += differ.getCorrectMatches();
059       partiallyCorrectMatches += differ.getPartiallyCorrectMatches();
060       missing += differ.getMissing();
061       spurious += differ.getSpurious();
062       keyCount += differ.getKeysCount();
063       responseCount += differ.getResponsesCount();
064     }
065     keyList = new ArrayList<Annotation>(Collections.nCopies(keyCount, (Annotationnull));
066     responseList = new ArrayList<Annotation>(Collections.nCopies(responseCount, (Annotationnull));    
067   }
068 
069   public AnnotationDiffer() {
070     // empty constructor
071   }
072 
073   /**
074    * Interface representing a pairing between a key annotation and a response 
075    * one.
076    */
077   public static interface Pairing{
078     /**
079      * Gets the key annotation of the pairing. Can be <tt>null</tt> (for 
080      * spurious matches).
081      @return an {@link Annotation} object.
082      */
083     public Annotation getKey();
084 
085     public int getResponseIndex();
086     public int getValue();
087     public int getKeyIndex();
088     public int getScore();
089     public void setType(int i);
090     public void remove();
091     
092     /**
093      * Gets the response annotation of the pairing. Can be <tt>null</tt> (for 
094      * missing matches).
095      @return an {@link Annotation} object.
096      */
097     public Annotation getResponse();
098     
099     /**
100      * Gets the type of the pairing, one of {@link #CORRECT_TYPE},
101      {@link #PARTIALLY_CORRECT_TYPE}{@link #SPURIOUS_TYPE} or
102      {@link #MISSING_TYPE},
103      @return an <tt>int</tt> value.
104      */
105     public int getType();
106   }
107 
108   /**
109    * Computes a diff between two collections of annotations.
110    @param key the collection of key annotations.
111    @param response the collection of response annotations.
112    @return a list of {@link Pairing} objects representing the pairing set
113    * that results in the best score.
114    */
115   public List<Pairing> calculateDiff(Collection<Annotation> key, Collection<Annotation> response){
116     
117     //initialise data structures
118     if(key == null || key.size() == 0)
119       keyList = new ArrayList<Annotation>();
120     else
121       keyList = new ArrayList<Annotation>(key);
122 
123     if(response == null || response.size() == 0)
124       responseList = new ArrayList<Annotation>();
125     else
126       responseList = new ArrayList<Annotation>(response);
127 
128     if(correctAnnotations != null) {
129       correctAnnotations.clear();
130     }
131     else {
132       correctAnnotations = new HashSet<Annotation>();
133     }
134     if(partiallyCorrectAnnotations != null) {
135       partiallyCorrectAnnotations.clear();
136     }
137     else {
138       partiallyCorrectAnnotations = new HashSet<Annotation>();
139     }
140     if(missingAnnotations != null) {
141       missingAnnotations.clear();
142     }
143     else {
144       missingAnnotations = new HashSet<Annotation>();
145     }
146     if(spuriousAnnotations != null) {
147       spuriousAnnotations.clear();
148     }
149     else {
150       spuriousAnnotations = new HashSet<Annotation>();
151     }
152     
153     keyChoices = new ArrayList<List<Pairing>>(keyList.size());
154     keyChoices.addAll(Collections.nCopies(keyList.size()(List<Pairing>null));
155     responseChoices = new ArrayList<List<Pairing>>(responseList.size());
156     responseChoices.addAll(Collections.nCopies(responseList.size()(List<Pairing>null));
157 
158     possibleChoices = new ArrayList<Pairing>();
159 
160     //1) try all possible pairings
161     for(int i = 0; i < keyList.size(); i++){
162       for(int j =0; j < responseList.size(); j++){
163         Annotation keyAnn = keyList.get(i);
164         Annotation resAnn = responseList.get(j);
165         PairingImpl choice = null;
166         if(keyAnn.coextensive(resAnn)){
167           //we have full overlap -> CORRECT or WRONG
168           if(keyAnn.isCompatible(resAnn, significantFeaturesSet)){
169             //we have a full match
170             choice = new PairingImpl(i, j, CORRECT_VALUE);
171           }else{
172             //the two annotations are coextensive but don't match
173             //we have a missmatch
174             choice = new PairingImpl(i, j, MISMATCH_VALUE);
175           }
176         }else if(keyAnn.overlaps(resAnn)){
177           //we have partial overlap -> PARTIALLY_CORRECT or WRONG
178           if(keyAnn.isPartiallyCompatible(resAnn, significantFeaturesSet)){
179             choice = new PairingImpl(i, j, PARTIALLY_CORRECT_VALUE);
180           }else{
181             choice = new PairingImpl(i, j, WRONG_VALUE);
182           }
183         }
184 
185         //add the new choice if any
186         if (choice != null) {
187           addPairing(choice, i, keyChoices);
188           addPairing(choice, j, responseChoices);
189           possibleChoices.add(choice);
190         }
191       }//for j
192     }//for i
193 
194     //2) from all possible pairings, find the maximal set that also
195     //maximises the total score
196     Collections.sort(possibleChoices, new PairingScoreComparator());
197     Collections.reverse(possibleChoices);
198     finalChoices = new ArrayList<Pairing>();
199     correctMatches = 0;
200     partiallyCorrectMatches = 0;
201     missing = 0;
202     spurious = 0;
203 
204     while(!possibleChoices.isEmpty()){
205       PairingImpl bestChoice = (PairingImpl)possibleChoices.remove(0);
206       bestChoice.consume();
207       finalChoices.add(bestChoice);
208       switch(bestChoice.value){
209         case CORRECT_VALUE:{
210           correctAnnotations.add(bestChoice.getResponse());
211           correctMatches++;
212           bestChoice.setType(CORRECT_TYPE);
213           break;
214         }
215         case PARTIALLY_CORRECT_VALUE:{
216           partiallyCorrectAnnotations.add(bestChoice.getResponse());
217           partiallyCorrectMatches++;
218           bestChoice.setType(PARTIALLY_CORRECT_TYPE);
219           break;
220         }
221         case MISMATCH_VALUE:{
222           //this is a missing and a spurious annotations together
223           missingAnnotations.add(bestChoice.getKey());
224           missing ++;
225           spuriousAnnotations.add(bestChoice.getResponse());
226           spurious ++;
227           bestChoice.setType(MISMATCH_TYPE);
228           break;
229         }
230         case WRONG_VALUE:{
231           if(bestChoice.getKey() != null){
232             //we have a missed key
233             if(missingAnnotations == nullmissingAnnotations = new HashSet<Annotation>();
234             missingAnnotations.add(bestChoice.getKey());
235             missing ++;
236             bestChoice.setType(MISSING_TYPE);
237           }
238           if(bestChoice.getResponse() != null){
239             //we have a spurious response
240             if(spuriousAnnotations == nullspuriousAnnotations = new HashSet<Annotation>();
241             spuriousAnnotations.add(bestChoice.getResponse());
242             spurious ++;
243             bestChoice.setType(SPURIOUS_TYPE);
244           }
245           break;
246         }
247         default:{
248           throw new GateRuntimeException("Invalid pairing type: " +
249                   bestChoice.value);
250         }
251       }
252     }
253     //add choices for the incorrect matches (MISSED, SPURIOUS)
254     //get the unmatched keys
255     for(int i = 0; i < keyChoices.size(); i++){
256       List<Pairing> aList = keyChoices.get(i);
257       if(aList == null || aList.isEmpty()){
258         if(missingAnnotations == nullmissingAnnotations = new HashSet<Annotation>();
259         missingAnnotations.add((keyList.get(i)));
260         Pairing choice = new PairingImpl(i, -1, WRONG_VALUE);
261         choice.setType(MISSING_TYPE);
262         finalChoices.add(choice);
263         missing ++;
264       }
265     }
266 
267     //get the unmatched responses
268     for(int i = 0; i < responseChoices.size(); i++){
269       List<Pairing> aList = responseChoices.get(i);
270       if(aList == null || aList.isEmpty()){
271         if(spuriousAnnotations == nullspuriousAnnotations = new HashSet<Annotation>();
272         spuriousAnnotations.add((responseList.get(i)));
273         PairingImpl choice = new PairingImpl(-1, i, WRONG_VALUE);
274         choice.setType(SPURIOUS_TYPE);
275         finalChoices.add(choice);
276         spurious ++;
277       }
278     }
279 
280     return finalChoices;
281   }
282 
283   /**
284    * Gets the strict precision (the ratio of correct responses out of all the 
285    * provided responses).
286    @return <tt>double</tt> value.
287    */
288   public double getPrecisionStrict(){
289     if(responseList.size() == 0) {
290       return 1.0;
291     }
292     return correctMatches/(double)responseList.size();
293   }
294 
295   /**
296    * Gets the strict recall (the ratio of key matched to a response out of all 
297    * the keys).
298    @return <tt>double</tt> value.
299    */  
300   public double getRecallStrict(){
301     if(keyList.size() == 0) {
302       return 1.0;
303     }
304     return correctMatches/(double)keyList.size();
305   }
306 
307   /**
308    * Gets the lenient precision (where the partial matches are considered as
309    * correct).
310    @return <tt>double</tt> value.
311    */
312   public double getPrecisionLenient(){
313     if(responseList.size() == 0) {
314       return 1.0;
315     }
316     return ((double)correctMatches + partiallyCorrectMatches/ responseList.size();
317   }
318 
319   /**
320    * Gets the average of the strict and lenient precision values.
321    @return <tt>double</tt> value.
322    */
323   public double getPrecisionAverage() {
324     return (getPrecisionLenient() + getPrecisionStrict()) (2.0);
325   }
326 
327   /**
328    * Gets the lenient recall (where the partial matches are considered as
329    * correct).
330    @return <tt>double</tt> value.
331    */
332   public double getRecallLenient(){
333     if(keyList.size() == 0) {
334       return 1.0;
335     }
336     return ((double)correctMatches + partiallyCorrectMatches/ keyList.size();
337   }
338 
339   /**
340    * Gets the average of the strict and lenient recall values.
341    @return <tt>double</tt> value.
342    */
343   public double getRecallAverage() {
344     return (getRecallLenient() + getRecallStrict()) (2.0);
345   }
346 
347   /**
348    * Gets the strict F-Measure (the harmonic weighted mean of the strict
349    * precision and the strict recall) using the provided parameter as relative 
350    * weight. 
351    @param beta The relative weight of precision and recall. A value of 1 
352    * gives equal weights to precision and recall. A value of 0 takes the recall 
353    * value completely out of the equation.
354    @return <tt>double</tt>value.
355    */
356   public double getFMeasureStrict(double beta){
357     double precision = getPrecisionStrict();
358     double recall = getRecallStrict();
359     double betaSq = beta * beta;
360     double answer = (((betaSq + 1* precision * recall (betaSq * precision + recall));
361     if(Double.isNaN(answer)) answer = 0.0;
362     return answer;
363   }
364 
365   /**
366    * Gets the lenient F-Measure (F-Measure where the lenient precision and 
367    * recall values are used) using the provided parameter as relative weight. 
368    @param beta The relative weight of precision and recall. A value of 1 
369    * gives equal weights to precision and recall. A value of 0 takes the recall 
370    * value completely out of the equation.
371    @return <tt>double</tt>value.
372    */
373   public double getFMeasureLenient(double beta){
374     double precision = getPrecisionLenient();
375     double recall = getRecallLenient();
376     double betaSq = beta * beta;
377     double answer = (((betaSq + 1* precision * recall(betaSq * precision + recall));
378     if(Double.isNaN(answer)) answer = 0.0;
379     return answer;
380   }
381 
382   /**
383    * Gets the average of strict and lenient F-Measure values.
384    @param beta The relative weight of precision and recall. A value of 1 
385    * gives equal weights to precision and recall. A value of 0 takes the recall 
386    * value completely out of the equation.
387    @return <tt>double</tt>value.
388    */  
389   public double getFMeasureAverage(double beta) {
390     double answer = (getFMeasureLenient(beta+ getFMeasureStrict(beta)) (2.0);
391     return answer;
392   }
393 
394   /**
395    * Gets the number of correct matches.
396    @return an <tt>int<tt> value.
397    */
398   public int getCorrectMatches(){
399     return correctMatches;
400   }
401 
402   /**
403    * Gets the number of partially correct matches.
404    @return an <tt>int<tt> value.
405    */
406   public int getPartiallyCorrectMatches(){
407     return partiallyCorrectMatches;
408   }
409 
410   /**
411    * Gets the number of pairings of type {@link #MISSING_TYPE}.
412    @return an <tt>int<tt> value.
413    */
414   public int getMissing(){
415     return missing;
416   }
417 
418   /**
419    * Gets the number of pairings of type {@link #SPURIOUS_TYPE}.
420    @return an <tt>int<tt> value.
421    */
422   public int getSpurious(){
423     return spurious;
424   }
425 
426   /**
427    * Gets the number of pairings of type {@link #SPURIOUS_TYPE}.
428    @return an <tt>int<tt> value.
429    */
430   public int getFalsePositivesStrict(){
431     return responseList.size() - correctMatches;
432   }
433 
434   /**
435    * Gets the number of responses that aren't either correct or partially 
436    * correct.
437    @return an <tt>int<tt> value.
438    */
439   public int getFalsePositivesLenient(){
440     return responseList.size() - correctMatches - partiallyCorrectMatches;
441   }
442 
443   /**
444    * Gets the number of keys provided.
445    @return an <tt>int<tt> value.
446    */
447   public int getKeysCount() {
448     return keyList.size();
449   }
450 
451   /**
452    * Gets the number of responses provided.
453    @return an <tt>int<tt> value.
454    */
455   public int getResponsesCount() {
456     return responseList.size();
457   }
458 
459   /**
460    * Prints to System.out the pairings that are not correct.
461    */
462   public void printMissmatches(){
463     //get the partial correct matches
464     for (Pairing aChoice : finalChoices) {
465       switch(aChoice.getValue()){
466         case PARTIALLY_CORRECT_VALUE:{
467           System.out.println("Missmatch (partially correct):");
468           System.out.println("Key: " + keyList.get(aChoice.getKeyIndex()).toString());
469           System.out.println("Response: " + responseList.get(aChoice.getResponseIndex()).toString());
470           break;
471         }
472       }
473     }
474 
475     //get the unmatched keys
476     for(int i = 0; i < keyChoices.size(); i++){
477       List<Pairing> aList = keyChoices.get(i);
478       if(aList == null || aList.isEmpty()){
479         System.out.println("Missed Key: " + keyList.get(i).toString());
480       }
481     }
482 
483     //get the unmatched responses
484     for(int i = 0; i < responseChoices.size(); i++){
485       List<Pairing> aList = responseChoices.get(i);
486       if(aList == null || aList.isEmpty()){
487         System.out.println("Spurious Response: " + responseList.get(i).toString());
488       }
489     }
490   }
491 
492 
493 
494   /**
495    * Performs some basic checks over the internal data structures from the last
496    * run.
497    @throws Exception
498    */
499   void sanityCheck()throws Exception{
500     //all keys and responses should have at most one choice left
501     for (List<Pairing> choices : keyChoices)  {
502       if(choices != null){
503         if(choices.size() 1){
504           throw new Exception("Multiple choices found!");
505         }
506         else if(!choices.isEmpty()){
507           //size must be 1
508           Pairing aChoice = choices.get(0);
509           //the SAME choice should be found for the associated response
510           List<?> otherChoices = responseChoices.get(aChoice.getResponseIndex());
511           if(otherChoices == null ||
512              otherChoices.size() != ||
513              otherChoices.get(0!= aChoice){
514             throw new Exception("Reciprocity error!");
515           }
516         }
517       }
518     }
519 
520     for (List<Pairing> choices : responseChoices) {
521       if(choices != null){
522         if(choices.size() 1){
523           throw new Exception("Multiple choices found!");
524         }
525         else if(!choices.isEmpty()){
526           //size must be 1
527           Pairing aChoice = choices.get(0);
528           //the SAME choice should be found for the associated response
529           List<?> otherChoices = keyChoices.get(aChoice.getKeyIndex());
530           if(otherChoices == null){
531             throw new Exception("Reciprocity error : null!");
532           }else if(otherChoices.size() != 1){
533             throw new Exception("Reciprocity error: not 1!");
534           }else if(otherChoices.get(0!= aChoice){
535             throw new Exception("Reciprocity error: different!");
536           }
537         }
538       }
539     }
540   }
541   
542   /**
543    * Adds a new pairing to the internal data structures.
544    @param pairing the pairing to be added
545    @param index the index in the list of pairings
546    @param listOfPairings the list of {@link Pairing}s where the
547    * pairing should be added
548    */
549   protected void addPairing(Pairing pairing, int index, List<List<Pairing>> listOfPairings){
550     List<Pairing> existingChoices = listOfPairings.get(index);
551     if(existingChoices == null){
552       existingChoices = new ArrayList<Pairing>();
553       listOfPairings.set(index, existingChoices);
554     }
555     existingChoices.add(pairing);
556   }
557 
558   /**
559    * Gets the set of features considered significant for the matching algorithm.
560    @return a Set.
561    */
562   public java.util.Set<?> getSignificantFeaturesSet() {
563     return significantFeaturesSet;
564   }
565 
566   /**
567    * Set the set of features considered significant for the matching algorithm.
568    * A <tt>null</tt> value means that all features are significant, an empty 
569    * set value means that no features are significant while a set of String 
570    * values specifies that only features with names included in the set are 
571    * significant. 
572    @param significantFeaturesSet a Set of String values or <tt>null<tt>.
573    */
574   public void setSignificantFeaturesSet(Set<String> significantFeaturesSet) {
575     this.significantFeaturesSet = significantFeaturesSet;
576   }
577 
578   /**
579    * Represents a pairing of a key annotation with a response annotation and
580    * the associated score for that pairing.
581    */
582    public class PairingImpl implements Pairing{
583     PairingImpl(int keyIndex, int responseIndex, int value) {
584       this.keyIndex = keyIndex;
585       this.responseIndex = responseIndex;
586       this.value = value;
587       scoreCalculated = false;
588       }
589 
590     @Override
591     public int getScore(){
592       if(scoreCalculatedreturn score;
593       else{
594         calculateScore();
595         return score;
596       }
597     }
598 
599     @Override
600     public int getKeyIndex() {
601       return this.keyIndex;
602     }
603     
604     @Override
605     public int getResponseIndex() {
606       return this.responseIndex;
607     }
608     
609     @Override
610     public int getValue() {
611       return this.value;
612     }
613     
614     @Override
615     public Annotation getKey(){
616       return keyIndex == -null : keyList.get(keyIndex);
617     }
618 
619     @Override
620     public Annotation getResponse(){
621       return responseIndex == -null :
622         responseList.get(responseIndex);
623     }
624 
625     @Override
626     public int getType(){
627       return type;
628     }
629     
630 
631     @Override
632     public void setType(int type) {
633       this.type = type;
634     }
635 
636     /**
637      * Removes all mutually exclusive OTHER choices possible from
638      * the data structures.
639      <tt>this</tt> gets removed from {@link #possibleChoices} as well.
640      */
641     public void consume(){
642       possibleChoices.remove(this);
643       List<Pairing> sameKeyChoices = keyChoices.get(keyIndex);
644       sameKeyChoices.remove(this);
645       possibleChoices.removeAll(sameKeyChoices);
646 
647       List<Pairing> sameResponseChoices = responseChoices.get(responseIndex);
648       sameResponseChoices.remove(this);
649       possibleChoices.removeAll(sameResponseChoices);
650 
651       for (Pairing item : new ArrayList<Pairing>(sameKeyChoices)) {
652         item.remove();
653       }
654       for (Pairing item : new ArrayList<Pairing>(sameResponseChoices)) {
655         item.remove();
656       }
657       sameKeyChoices.add(this);
658       sameResponseChoices.add(this);
659     }
660 
661     /**
662      * Removes this choice from the two lists it belongs to
663      */
664     @Override
665     public void remove(){
666       List<Pairing> fromKey = keyChoices.get(keyIndex);
667       fromKey.remove(this);
668       List<Pairing> fromResponse = responseChoices.get(responseIndex);
669       fromResponse.remove(this);
670     }
671 
672     /**
673      * Calculates the score for this choice as:
674      * type - sum of all the types of all OTHER mutually exclusive choices
675      */
676     void calculateScore(){
677       //this needs to be a set so we don't count conflicts twice
678       Set<Pairing> conflictSet = new HashSet<Pairing>();
679       //add all the choices from the same response annotation
680       conflictSet.addAll(responseChoices.get(responseIndex));
681       //add all the choices from the same key annotation
682       conflictSet.addAll(keyChoices.get(keyIndex));
683       //remove this choice from the conflict set
684       conflictSet.remove(this);
685       score = value;
686       for (Pairing item : conflictSet) {
687         score -= item.getValue();
688       }
689       scoreCalculated = true;
690     }
691 
692     /**
693      * The index in the key collection of the key annotation for this pairing
694      */
695     int keyIndex;
696     /**
697      * The index in the response collection of the response annotation for this
698      * pairing
699      */
700     int responseIndex;
701     
702     /**
703      * The type of this pairing.
704      */
705     int type;
706     
707     /**
708      * The value for this pairing. This value depends only on this pairing, not
709      * on the conflict set.
710      */
711     int value;
712     
713     /**
714      * The score of this pairing (calculated based on value and conflict set).
715      */
716     int score;
717     boolean scoreCalculated;
718   }
719 
720    /**
721     * Compares two pairings:
722     * the better score is preferred;
723     * for the same score the better type is preferred (exact matches are
724     * preffered to partial ones).
725     */   
726   protected static class PairingScoreComparator implements Comparator<Pairing> {
727     /**
728      * Compares two choices:
729      * the better score is preferred;
730      * for the same score the better type is preferred (exact matches are
731      * preffered to partial ones).
732      @return a positive value if the first pairing is better than the second,
733      * zero if they score the same or negative otherwise.
734      */
735 
736     @Override
737     public int compare(Pairing first, Pairing second){
738       //compare by score
739       int res = first.getScore() - second.getScore();
740       //compare by type
741       if(res == 0res = first.getType() - second.getType();
742       //compare by completeness (a wrong match with both key and response
743       //is "better" than one with only key or response
744       if(res == 0){
745         res = (first.getKey() == null 1+
746               (first.getResponse() == null 1+
747               (second.getKey() == null : -1+
748               (second.getResponse() == null : -1);
749       }
750       return res;
751     }
752   }
753 
754     /**
755      * Compares two choices based on start offset of key (or response
756      * if key not present) and type if offsets are equal.
757      */
758   public static class PairingOffsetComparator implements Comparator<Pairing> {
759     /**
760      * Compares two choices based on start offset of key (or response
761      * if key not present) and type if offsets are equal.
762      */
763     @Override
764     public int compare(Pairing first, Pairing second){
765       Annotation key1 = first.getKey();
766       Annotation key2 = second.getKey();
767       Annotation res1 = first.getResponse();
768       Annotation res2 = second.getResponse();
769       Long start1 = key1 == null null : key1.getStartNode().getOffset();
770       if(start1 == nullstart1 = res1.getStartNode().getOffset();
771       Long start2 = key2 == null null : key2.getStartNode().getOffset();
772       if(start2 == nullstart2 = res2.getStartNode().getOffset();
773       int res = start1.compareTo(start2);
774       if(res == 0){
775         //compare by type
776         res = second.getType() - first.getType();
777       }
778 
779 //
780 //
781 //
782 //      //choices with keys are smaller than ones without
783 //      if(key1 == null && key2 != null) return 1;
784 //      if(key1 != null && key2 == null) return -1;
785 //      if(key1 == null){
786 //        //both keys are null
787 //        res = res1.getStartNode().getOffset().
788 //            compareTo(res2.getStartNode().getOffset());
789 //        if(res == 0) res = res1.getEndNode().getOffset().
790 //              compareTo(res2.getEndNode().getOffset());
791 //        if(res == 0) res = second.getType() - first.getType();
792 //      }else{
793 //        //both keys are present
794 //        res = key1.getStartNode().getOffset().compareTo(
795 //            key2.getStartNode().getOffset());
796 //
797 //        if(res == 0){
798 //          //choices with responses are smaller than ones without
799 //          if(res1 == null && res2 != null) return 1;
800 //          if(res1 != null && res2 == null) return -1;
801 //          if(res1 != null){
802 //            res = res1.getStartNode().getOffset().
803 //                compareTo(res2.getStartNode().getOffset());
804 //          }
805 //          if(res == 0)res = key1.getEndNode().getOffset().compareTo(
806 //                  key2.getEndNode().getOffset());
807 //          if(res == 0 && res1 != null){
808 //              res = res1.getEndNode().getOffset().
809 //                  compareTo(res2.getEndNode().getOffset());
810 //          }
811 //          if(res == 0) res = second.getType() - first.getType();
812 //        }
813 //      }
814       return res;
815     }
816 
817   }
818 
819   /**
820    * A method that returns specific type of annotations
821    @param type
822    @return {@link Set} of {@link Annotation}s.
823    */
824   public Set<Annotation> getAnnotationsOfType(int type) {
825     switch(type) {
826       case CORRECT_TYPE:
827         return (correctAnnotations == null)new HashSet<Annotation>() : correctAnnotations;
828       case PARTIALLY_CORRECT_TYPE:
829         return (partiallyCorrectAnnotations == nullnew HashSet<Annotation>() : partiallyCorrectAnnotations;
830       case SPURIOUS_TYPE:
831         return (spuriousAnnotations == nullnew HashSet<Annotation>() : spuriousAnnotations;
832       case MISSING_TYPE:
833         return (missingAnnotations == nullnew HashSet<Annotation>() : missingAnnotations;
834       default:
835         return new HashSet<Annotation>();
836     }
837   }
838 
839   /**
840    @return annotation type for all the annotations
841    */
842   public String getAnnotationType() {
843     if (!keyList.isEmpty()) {
844       return keyList.iterator().next().getType();
845     else if (!responseList.isEmpty()) {
846       return responseList.iterator().next().getType();
847     else {
848       return "";
849     }
850   }
851 
852   public List<String> getMeasuresRow(Object[] measures, String title) {
853     NumberFormat f = NumberFormat.getInstance(Locale.ENGLISH);
854     f.setMaximumFractionDigits(4);
855     f.setMinimumFractionDigits(4);
856     f.setRoundingMode(RoundingMode.HALF_UP);
857     
858     List<String> row = new ArrayList<String>();
859     row.add(title);
860     row.add(Integer.toString(getCorrectMatches()));
861     row.add(Integer.toString(getMissing()));
862     row.add(Integer.toString(getSpurious()));
863     row.add(Integer.toString(getPartiallyCorrectMatches()));
864     for (Object object : measures) {
865       String measure = (Stringobject;
866       double beta = Double.valueOf(
867         measure.substring(1,measure.indexOf('-')));
868       if (measure.endsWith("strict")) {
869         row.add(f.format(getPrecisionStrict()));
870         row.add(f.format(getRecallStrict()));
871         row.add(f.format(getFMeasureStrict(beta)));
872       else if (measure.endsWith("lenient")) {
873         row.add(f.format(getPrecisionLenient()));
874         row.add(f.format(getRecallLenient()));
875         row.add(f.format(getFMeasureLenient(beta)));
876       else if (measure.endsWith("average")) {
877         row.add(f.format(getPrecisionAverage()));
878         row.add(f.format(getRecallAverage()));
879         row.add(f.format(getFMeasureAverage(beta)));
880       }
881     }
882     return row;
883   }
884 
885   public Set<Annotation> correctAnnotations, partiallyCorrectAnnotations,
886                  missingAnnotations, spuriousAnnotations;
887 
888 
889   /** Type for correct pairings (when the key and response match completely)*/
890   public static final int CORRECT_TYPE = 0;
891   
892   /** 
893    * Type for partially correct pairings (when the key and response match 
894    * in type and significant features but the spans are just overlapping and 
895    * not identical.
896    */
897   public static final int PARTIALLY_CORRECT_TYPE = 1;
898   
899   /**
900    * Type for missing pairings (where the key was not matched to a response).
901    */
902   public static final int MISSING_TYPE = 2;
903 
904   /**
905    * Type for spurious pairings (where the response is not matching any key).
906    */
907   public static final int SPURIOUS_TYPE = 3;
908   
909   /**
910    * Type for mismatched pairings (where the key and response are co-extensive
911    * but they don't match).
912    */
913   public static final int MISMATCH_TYPE = 4;
914 
915   /**
916    * Score for a correct pairing.
917    */
918   private static final int CORRECT_VALUE = 3;
919 
920   /**
921    * Score for a partially correct pairing.
922    */
923   private static final int PARTIALLY_CORRECT_VALUE = 2;
924   
925   
926   /**
927    * Score for a mismatched pairing (higher then for WRONG as at least the 
928    * offsets were right).
929    */
930   private static final int MISMATCH_VALUE = 1;
931   
932   /**
933    * Score for a wrong (missing or spurious) pairing.
934    */  
935   private static final int WRONG_VALUE = 0;
936 
937   /**
938    * The set of significant features used for matching.
939    */
940   private java.util.Set<?> significantFeaturesSet;
941 
942   /**
943    * The number of correct matches.
944    */
945   protected int correctMatches;
946 
947   /**
948    * The number of partially correct matches.
949    */
950   protected int partiallyCorrectMatches;
951   
952   /**
953    * The number of missing matches.
954    */
955   protected int missing;
956   
957   /**
958    * The number of spurious matches.
959    */  
960   protected int spurious;
961 
962   /**
963    * A list with all the key annotations
964    */
965   protected List<Annotation> keyList;
966 
967   /**
968    * A list with all the response annotations
969    */
970   protected List<Annotation> responseList;
971 
972   /**
973    * A list of lists representing all possible choices for each key
974    */
975   protected List<List<Pairing>> keyChoices;
976 
977   /**
978    * A list of lists representing all possible choices for each response
979    */
980   protected List<List<Pairing>> responseChoices;
981 
982   /**
983    * All the posible choices are added to this list for easy iteration.
984    */
985   protected List<Pairing> possibleChoices;
986 
987   /**
988    * A list with the choices selected for the best result.
989    */
990   protected List<Pairing> finalChoices;
991 
992 }