File Coverage

blib/lib/FrameNet/WordNet/Detour.pm
Criterion Covered Total %
statement 13 15 86.6
branch n/a
condition n/a
subroutine 5 5 100.0
pod n/a
total 18 20 90.0


line stmt bran cond sub pod time code
1             package FrameNet::WordNet::Detour;
2              
3             require Exporter;
4             our @ISA = qw(Exporter);
5             our $VERSION = "1.1";
6              
7 2     2   69216 use strict;
  2         5  
  2         76  
8 2     2   10 use warnings;
  2         4  
  2         57  
9 2     2   2361 use Storable;
  2         7769  
  2         197  
10 2     2   18 use Carp;
  2         4  
  2         129  
11 2     2   1926 use XML::TreeBuilder;
  0            
  0            
12             use FrameNet::WordNet::Detour::Frame;
13             use FrameNet::WordNet::Detour::Data;
14             use WordNet::QueryData;
15             use WordNet::Similarity::path;
16             use File::Spec;
17              
18             my $VCACHE = "0.92b";
19              
20              
21             sub new
22             {
23             my $class = shift;
24             my $this = {};
25              
26             $class = ref $class || $class;
27              
28             bless $this, $class;
29              
30             my %params = @_;
31              
32             my $tuser = "user";
33             $tuser = $ENV{'USER'} if (defined $ENV{'USER'});
34             $this->{'tmp_filename_prefix'} = "$tuser-";
35            
36              
37             ### WNHOME ###
38             if (defined $params{-wnquerydata}) {
39             $this->{'wn'} = $params{-wnquerydata}
40             } elsif (defined $params{-wnhome}) {
41             $this->{'wnhome'} = $params{-wnhome}
42             } elsif (defined $ENV{'WNHOME'}) {
43             $this->{'wnhome'} = $ENV{'WNHOME'}
44             } else {
45             croak "Error: WordNet could not be found. Did you set \$WNHOME?\n";
46             }
47              
48             ### WordNet::Similarity ###
49              
50             if (defined $params{-wnsimilarity}) {
51             $this->{'sim'} = $params{-wnsimilarity}
52             };
53              
54            
55             ### FNHOME ###
56             if (defined $params{-fnhome}) {
57             $this->{'fnhome'} = $params{-fnhome}
58             } elsif (defined $ENV{'FNHOME'}) {
59             $this->{'fnhome'} = $ENV{'FNHOME'}
60             } else {
61             croak "Error: FrameNet could not be found. Did you set \$FNHOME?\n";
62             }
63             #
64              
65             # Searching for the frames.xml file
66             if($this->{'fnhome'} ne "") {
67             $this->{'fnxml'} = "";
68             my $infix = "xml";
69             $infix = "frXML" if (-e File::Spec->catfile(($this->{'fnhome'},"frXML"),"frames.xml"));
70             $this->{'fnxml'} = File::Spec->catfile(($this->{'fnhome'},$infix),"frames.xml");
71             carp "Warning: frames.xml could not be found." if ($this->{'fnxml'} eq "");
72             };
73              
74             $this->_initialize(\%params);
75              
76             return $this;
77             }
78              
79             sub _initialize {
80             my $self = shift;
81             my $par = shift;
82              
83             if (defined $par->{-cached}) {
84             $self->{'cached'} = $par->{-cached}
85             } else {
86             $self->{'cached'} = 0;
87             };
88              
89             if (defined $par->{-cachecounter}) {
90             $self->{'cachecounter'} = $par->{-cachecounter};
91             } else {
92             $self->{'cachecounter'} = 1;
93             };
94             $self->{'cachecounterstart'} = $self->{'cachecounter'};
95              
96             if (defined $par->{-limited}) {
97             $self->{'limited'} = $par->{-limited}
98             } else {
99             $self->{'limited'} = 0;
100             };
101              
102             if (defined $par->{-matched}) {
103             $self->{'matched'} = $par->{-matched}
104             } else {
105             $self->{'matched'} = 0;
106             };
107              
108             if (defined $par->{-verbosity}) {
109             $self->{'verbosity'} = $par->{-verbosity}
110             } else {
111             $self->{'verbosity'} = 0;
112             };
113              
114             $self->{'results'} = {};
115            
116             $self->_set_cache_name;
117              
118             $self->{'luhashname'} = File::Spec->catfile((File::Spec->tmpdir),$self->{'tmp_filename_prefix'}.
119             "FrameNet-WordNet-Detour-".$VCACHE."-luhash.dat");
120              
121              
122             };
123              
124             sub reset {
125             my $self = shift;
126             $self->{'results'} = [];
127             $self->{'result'} = {};
128             };
129              
130             sub _set_cache_name {
131             my $self = shift;
132             $self->{'resulthashname'} = File::Spec->catfile((File::Spec->tmpdir),$self->{'tmp_filename_prefix'}.
133             "FrameNet-WordNet-Detour-".
134             $VCACHE."-results_".$self->{'limited'}.($self->{'matched'}?"1":"").".dat");
135             }
136              
137             sub _init_WordNet {
138             my $self = shift;
139             my $dictpath = File::Spec->catdir(($self->{'wnhome'},"dict"));
140             if (! $self->{'wn'}) {
141             $self->{'wn'} = WordNet::QueryData->new($dictpath);
142             };
143             if (! $self->{'sim'}) {
144             $self->{'sim'} = WordNet::Similarity::path->new($self->{'wn'});
145             }
146             }
147              
148             sub query ($$) {
149             my $self = shift;
150             my $synset = shift;
151              
152             $self->reset;
153            
154             if (not defined $synset) {
155             my $msg = "No Synset specified.\n";
156             carp "$msg" if ($self->{'verbosity'});
157             return FrameNet::WordNet::Detour::Data->new({},$synset,$msg);
158             }
159              
160             if ($synset =~ /^[\w\- ']+#[nva]#\d+$/i) {
161             # Caching
162             if ($self->{'cached'}) {
163             my $KnownResults;
164             if (-e $self->{'resulthashname'}) {
165             $KnownResults = retrieve($self->{'resulthashname'});
166             if (exists($KnownResults->{$synset})) {
167             print STDERR "Found synset in cache\n" if($self->{'verbosity'});
168             return
169             FrameNet::WordNet::Detour::Data->new($KnownResults->{$synset},
170             $synset,'OK')
171             if (exists($KnownResults->{$synset}));
172             }
173             };
174             };
175             $self->_init_WordNet;
176             my ($word, $pos, $sense) = split(/#/, $synset);
177            
178             if (scalar($self->{'wn'}->querySense("$word#$pos")) == 0) {
179             my $msg = "\'$synset\' not listed in WordNet";
180             carp "$msg" if ($self->{'verbosity'});
181             return FrameNet::WordNet::Detour::Data->new({},$synset,$msg);
182             };
183             return FrameNet::WordNet::Detour::Data->new($self->_basicQuery($synset),$synset,'OK');
184             }
185              
186             # if the query-word is underspecified (e.g. get#v),
187             # we query each possible sense once, collect a list of results
188             # and return it
189            
190             elsif ($synset =~ /^[\w\- ']+#[nva]$/i) {
191             if ($self->{'cached'}) {
192             my $KnownResults = {};
193             $KnownResults = retrieve($self->{'resulthashname'})
194             if (-e $self->{'resulthashname'});
195             return $self->query($KnownResults->{$synset})
196             if (exists $KnownResults->{$synset});
197             };
198             $self->_init_WordNet;
199             my @senses = $self->{'wn'}->querySense($synset);
200             if ((scalar @senses) == 0) {
201             my $msg = "\'$synset\' not listed in WordNet";
202             carp "$msg" if ($self->{'verbosity'});
203             return [FrameNet::WordNet::Detour::Data->new({},$synset,$msg)];
204             };
205              
206             if ($self->{'cached'}) {
207             my $KnownResults = {};
208             $KnownResults = retrieve($self->{'resulthashname'})
209             if (-e $self->{'resulthashname'});
210             $KnownResults->{$synset} = \@senses;
211             store($KnownResults,$self->{'resulthashname'});
212             };
213             return $self->query(\@senses);
214             }
215              
216             elsif (ref($synset) eq "ARRAY") {
217             my @r = ();
218             foreach my $sense (@$synset) {
219             push(@r, $self->query($sense));
220             };
221             return \@r;
222             }
223            
224             else {
225             my $msg = "Query (\'$synset\') not well-formed";
226             carp $msg if ($self->{'verbosity'});
227             return FrameNet::WordNet::Detour::Data->new({},$synset, $msg);
228             }
229             };
230              
231             sub _basicQuery {
232             my ($self,$synset) = @_;
233            
234             print STDERR "Querying: $synset ...\n" if ($self->{'verbosity'});
235              
236              
237             $self->{'similarities'} = {};
238             $self->{'synset'} = $synset;
239             my @tmp = split(/#/,$synset);
240             $self->{'in_word'} = $tmp[0];
241            
242             $self->{'result'}{'raw'} = $self->_weight_frames($self->_generate_candidate_frames($synset));
243             $self->{'result'}{'sorted'} = $self->_sort_by_weight;
244              
245             print STDERR "Best result(s): ".(join(' ',$self->best_frame))."\n"
246             if ($self->{'verbosity'});
247            
248             # Caching
249             if ($self->{'cached'}) {
250             $self->{'KnownResults'} = retrieve($self->{'resulthashname'})
251             if (! defined $self->{'KnownResults'} and -e $self->{'resulthashname'});
252             if ($self->{'cachecounter'} == 1) {
253             # my $KnownResults = retrieve($self->{'resulthashname'})
254             # if (-e $self->{'resulthashname'});
255             $self->{'KnownResults'}->{$synset} = $self->{'result'};
256             store($self->{'KnownResults'},$self->{'resulthashname'});
257             $self->{'cachecounter'} = $self->{'cachecounterstart'};
258             } else {
259             $self->{'KnownResults'}->{$synset} = $self->{'result'};
260             $self->{'cachecounter'} -= 1;
261             }
262             };
263            
264             return $self->{'result'};
265             };
266              
267              
268             sub cached {
269             my $self = shift;
270             $self->{'cached'} = 1;
271             };
272              
273             sub uncached {
274             my $self = shift;
275             $self->{'cached'} = 0;
276             };
277              
278             sub limited {
279             my $self = shift;
280             $self->{'limited'} = 1;
281             $self->_set_cache_name;
282             };
283              
284             sub unlimited {
285             my $self = shift;
286             $self->{'limited'} = 0;
287             $self->_set_cache_name;
288             };
289              
290             sub matched {
291             my $self = shift;
292             $self->{'matched'} = 1;
293             $self->_set_cache_name;
294             };
295              
296             sub unmatched {
297             my $self = shift;
298             $self->{'matched'} = 0;
299             $self->_set_cache_name;
300             };
301              
302             sub set_verbose {
303             my $self = shift;
304             $self->{'verbosity'} = 1;
305             };
306              
307             sub unset_verbose {
308             my $self = shift;
309             $self->{'verbosity'} = 0;
310             }
311              
312             sub set_debug {
313             my $self = shift;
314             $self->{'verbosity'} = 2;
315             };
316              
317             sub _generate_candidate_frames {
318             # synset format: car#n#2...
319             my ($self,$synset) = @_;
320              
321             my $pos = (split('#', $synset))[1];
322             my $MatchingFrames;
323              
324             my %CandidateSynsets;
325            
326             if ($synset =~ /\d$/) {
327             # first add input synset
328             $CandidateSynsets{"$synset"} = 1;
329             } else {
330             # takes all Senses matching the given word and part-of-speech
331             foreach my $sense ($self->{'wn'}->querySense($synset)) {
332             $CandidateSynsets{"$sense"} = 1;
333             }
334             }
335            
336             # second collect hypernyms
337             # initially self
338             my @currentSynsets = ("$synset");
339            
340            
341             # add hype syns for each synset member
342             while (1) {
343             my @newSynsets;
344             if ($pos !~ /a/) {
345             foreach my $synset (@currentSynsets) {
346             push (@newSynsets,$self->{'wn'}->querySense($synset,'hype'));
347             }
348             };
349             @currentSynsets = @newSynsets;
350             foreach my $newsynset (@newSynsets){
351             $CandidateSynsets{lc("$newsynset")} = 1;
352             };
353            
354             # stop if no more hypernyms found
355             if (! @newSynsets) {last};
356             };
357            
358             # compute all members of input and hypernym synsets
359             my %AllCandidates;
360            
361             foreach my $candidatesynset (keys %CandidateSynsets) {
362            
363             # synonyms and antonyms of candidate synset
364             foreach my $tmpsynset ($self->{'wn'}->querySense("$candidatesynset",'syns'),
365             $self->{'wn'}->queryWord("$candidatesynset",'ants')) {
366             # HERE !! !!!
367            
368             $AllCandidates{lc("$tmpsynset")} = 1 if((! $self->{'limited'}) or
369             $self->{'synset'} !~ /$tmpsynset/);
370             };
371             };
372            
373            
374             print STDERR "Synsets considered: " if ($self->{'verbosity'});
375             print STDERR "\n" if ($self->{'verbosity'} gt 1);
376             # lookup all candidates
377             foreach my $candidatesynset (keys %AllCandidates) {
378             # print STDERR "!!".$candidatesynset."!!\n";
379             next if ($self->{'limited'} and $candidatesynset eq $self->{'synset'});
380             $MatchingFrames = _mergeResultHashes($MatchingFrames,$self->_lookup_synset($candidatesynset));
381             if ($candidatesynset =~ / /i) {
382             my $synset_with_underscores = $candidatesynset;
383             $synset_with_underscores =~ s/ /_/ig;
384             $MatchingFrames = _mergeResultHashes($MatchingFrames,$self->_lookup_synset($synset_with_underscores));
385             } elsif ($candidatesynset =~ /_/i) {
386             my $synset_with_spaces = $candidatesynset;
387             $synset_with_spaces =~ s/_/ /ig;
388             $MatchingFrames = _mergeResultHashes($MatchingFrames,$self->_lookup_synset($synset_with_spaces));
389             }
390            
391             };
392            
393             $self->{'numberOfSynsets'} = scalar (keys %AllCandidates);
394             #print STDERR "numberOfSynsets: ".scalar(keys %AllCandidates)."\n";
395             print STDERR "\n" if ($self->{'verbosity'});
396            
397             return $MatchingFrames;
398             };
399              
400             sub _lookup_synset {
401             my ($self,$synset) = @_;
402             my $MatchingFrames;
403              
404             my $synsetprint = $synset;
405             if ($synsetprint =~ / /gi) {
406             $synsetprint = "\'$synsetprint\'";
407             }
408              
409             ### Retrieve /tmp/$usr-lu2frame-hash.pl if exists and up-to-date; else create it###
410             if (! -e $self->{'luhashname'}) # || ((stat($self->{'luhashname'}))[9] < (stat($FNXML))[9])) {
411             {
412             $self->_make_lu2frames_hash();
413             };
414             my %TmpHash = %{retrieve($self->{'luhashname'})};
415             my $Lu2Frames = $TmpHash{'lu2frames'};
416             my $FrameNames = $TmpHash{'frameNames'};
417              
418             my ($string,$pos,$sense) = split('#',$synset);
419            
420             # LIMITED SYSTEM: skip input word
421             if (! $self->{'limited'} || $string ne $self->{'in_word'}) {
422             $self->{'relatedness'}{"$synset"} = 0;
423             if ($pos ne "a") {
424             $self->{'relatedness'}{"$synset"} =
425             $self->{'sim'}->getRelatedness($synset,$self->{'synset'});
426             } else {
427             $self->{'relatedness'}{"$synset"} = 1;
428             }
429             my ($error, $errorString) = $self->{'sim'}->getError();
430             print STDERR "$errorString\n" if($error && $self->{'verbosity'});
431            
432              
433             print STDERR $synsetprint."(".(int(($self->{'relatedness'}{"$synset"}*1000)+.5)/1000).") "
434             if ($self->{'verbosity'});
435             # Is there a LU?
436             if (exists $Lu2Frames->{lc("$string\.$pos")}) {
437            
438             foreach my $_frameName (@{$Lu2Frames->{lc("$string\.$pos")}}) {
439             $MatchingFrames->{'lu'}->{$_frameName}->{"$synset"} = 1;
440             };
441             } elsif ($self->{'matched'}) {
442            
443            
444             # is there a matching frame name?
445             foreach my $frameName (keys %{$FrameNames}) {
446             my $alpha_string = $string;
447             my $alpha_frameName = lc($frameName);
448            
449             # remove all non-word chars (including _) in alpha_
450             $alpha_string =~ s/[\W,_]//g;
451             $alpha_frameName =~ s/[\W,_]//g;
452            
453             # $alpha_string is prefix or suffix of $alpha_frameName or the other way round
454             if ((substr($alpha_frameName,-length($alpha_string),length($alpha_string)) eq $alpha_string) ||
455             (substr($alpha_frameName,0,length($alpha_string)) eq $alpha_string) ||
456             (substr($alpha_string,-length($alpha_frameName),length($alpha_frameName)) eq $alpha_frameName) ||
457             (substr($alpha_string,0,length($alpha_frameName)) eq $alpha_frameName)
458             ) {
459             $MatchingFrames->{'match'}->{$frameName}->{"$synset"} = 1;
460             };
461             };
462             };
463             };
464             # };
465              
466             if ($self->{'verbosity'} gt 1) {
467             if (! $self->{'limited'} || $string ne $self->{'in_word'}) {
468             print STDERR "\n evokes(lu): [".
469             join(' ',keys(%{$MatchingFrames->{'lu'}})).
470             "]\n";
471             print STDERR " evokes(match): [".
472             join(' ',keys(%{$MatchingFrames->{'match'}})).
473             "]\n" if ($self->{'matched'});
474             };
475             };
476             return $MatchingFrames;
477             };
478              
479              
480             sub _weight_frames {
481             my ($self,$MatchingFrames) = @_;
482             my $AllResult;
483              
484             my $SpreadingFactor;
485             my $FrameSpreading;
486              
487             foreach my $reason ('lu','match') {
488             foreach my $frameName (keys %{$MatchingFrames->{$reason}}) {
489             foreach my $fee (keys %{$MatchingFrames->{$reason}->{$frameName}}) {
490             $SpreadingFactor->{$fee} += 1;
491             $FrameSpreading->{$frameName}++;
492             };
493             };
494             };
495            
496              
497            
498             print STDERR "All Frames: " if ($self->{'verbosity'});
499              
500             foreach my $reason ('lu','match') {
501             foreach my $frameName (keys %{$MatchingFrames->{$reason}}) {
502            
503             $AllResult->{$frameName} =
504             FrameNet::WordNet::Detour::Frame->new;
505             $AllResult->{$frameName}->name($frameName);
506            
507             foreach my $fee (keys %{$MatchingFrames->{$reason}->{$frameName}}) {
508              
509             $AllResult->{$frameName}->_fees_add($fee);
510            
511             my $weight = $self->{'relatedness'}{"$fee"};
512            
513             $AllResult->{$frameName}->_sims_add($weight);
514              
515             # cheat for adjectives!!!
516             if (!$weight) {$weight = 0.5};
517            
518             # Square of distance
519             $weight = $weight * $weight;
520            
521             # divided by Spreading Factor
522             $weight /= $SpreadingFactor->{$fee};
523            
524             $AllResult->{$frameName}->_add_weight($weight);
525             };
526             # print STDERR "frameSpreading ($frameName): ".$FrameSpreading->{$frameName}."\n";
527             #$AllResult->{$frameName}->weight($AllResult->{$frameName}->weight /
528             # $FrameSpreading->{$frameName});
529            
530             print STDERR $frameName.
531             "(".(int((($AllResult->{$frameName}->{'weight'})*1000)+.5)/1000).") "
532             if ($self->{'verbosity'});
533             };
534             };
535            
536             print STDERR "\n" if ($self->{'verbosity'});
537              
538              
539            
540             return $AllResult;
541             };
542              
543              
544             sub _sort_by_weight {
545             my ($self) = @_;
546            
547             my $AllResult = $self->{'result'}->{'raw'};
548              
549             my $ResultsByWeight;
550             foreach my $frame (keys %$AllResult) {
551             my $weight = $AllResult->{$frame}->weight;
552             $ResultsByWeight->{$weight}->{$frame} = $AllResult->{$frame};
553             };
554            
555             return $ResultsByWeight;
556             };
557              
558              
559             sub best_frame {
560             my $self = shift;
561             my $f = $self->_n_results(1);
562             return keys %$f;
563             };
564              
565             sub _n_results {
566             my ($self,$number_results) = @_;
567            
568             my $ResultsByWeight = $self->_sort_by_weight();
569              
570             my $ResultHash;
571              
572             my $result_counter = 1;
573            
574             foreach my $weight (reverse(sort(keys %$ResultsByWeight))) {
575             if ($result_counter <= $number_results) {
576             foreach my $frame (keys %{$ResultsByWeight->{$weight}}) {
577             $ResultHash->{$frame} = $ResultsByWeight->{$weight}->{$frame};
578             };
579             };
580             $result_counter++;
581             };
582             return $ResultHash;
583             };
584              
585              
586             sub _make_lu2frames_hash {
587             my $self = shift;
588             print STDERR "Generating LU index may take a while...\n" if ($self->{'verbosity'});
589              
590             my $file = $self->{'fnxml'};
591              
592             my $tree = XML::TreeBuilder->new();
593             $tree->parse_file($file);
594            
595             my $FrameNames;
596             my $Lu2Frames;
597            
598             foreach my $frame ($tree->find_by_tag_name('frame')){
599             $FrameNames->{lc($frame->attr('name'))} = 1;
600             foreach my $lu ($frame->find_by_tag_name('lexunit')) {
601             push(@{$Lu2Frames->{lc($lu->attr('name'))}},$frame->attr('name'));
602             };
603             };
604            
605             store ({'lu2frames'=>$Lu2Frames, 'frameNames'=>$FrameNames}, $self->{'luhashname'});
606             };
607              
608              
609             sub _mergeResultHashes {
610             my ($H1,$H2) = @_;
611             foreach my $reason ('lu','match') {
612             foreach my $frameName (keys %{$H2->{$reason}}) {
613             foreach my $fee (keys %{$H2->{$reason}->{$frameName}}) {
614             if (exists $H1->{$reason}->{$frameName}->{$fee}) {
615             $H1->{$reason}->{$frameName}->{$fee} += $H2->{$reason}->{$frameName}->{$fee}
616             } else {
617             $H1->{$reason}->{$frameName}->{$fee} = $H2->{$reason}->{$frameName}->{$fee}
618             };
619             };
620             };
621             };
622             return $H1;
623             };
624              
625             sub version {
626             my $self = shift;
627             return $VERSION;
628             };
629              
630             sub round {
631             my $class = shift;
632             return int(((shift)*1000)+0.5)/1000;
633             };
634              
635             1;
636              
637              
638             __END__