File Coverage

blib/lib/OBO/Util/Ontolome.pm
Criterion Covered Total %
statement 472 578 81.6
branch 109 156 69.8
condition 22 57 38.6
subroutine 7 7 100.0
pod 4 4 100.0
total 614 802 76.5


line stmt bran cond sub pod time code
1             # $Id: Ontolome.pm 2014-10-22 erick.antezana $
2             #
3             # Module : Ontolome.pm
4             # Purpose : Management of a set of ontologies.
5             # License : Copyright (c) 2006-2015 by Erick Antezana. All rights reserved.
6             # This program is free software; you can redistribute it and/or
7             # modify it under the same terms as Perl itself.
8             # Contact : Erick Antezana
9             #
10             package OBO::Util::Ontolome;
11              
12             our @ISA = qw(OBO::Util::ObjectSet);
13 1     1   989 use OBO::Util::ObjectSet;
  1         69  
  1         30  
14              
15 1     1   6 use strict;
  1         2  
  1         27  
16 1     1   6 use warnings;
  1         2  
  1         6205  
17              
18             =head2 union
19              
20             Usage - $ome->union($o1, $o2, ...)
21             Returns - an ontology (OBO::Core::Ontology) being the union of the parameters (ontologies)
22             Args - the ontologies (OBO::Core::Ontology) to be united
23             Function - creates an ontology having the union of terms and relationships from the given ontologies
24             Remark 1 - the IDspace's are collected and added to the result ontology
25             Remark 2 - the union is made on the basis of the IDs
26             Remark 3 - the default namespace is taken from the last ontology argument
27             Remark 4 - the merging order is important while merging definitions: the one from the last ontology will be taken
28            
29             =cut
30              
31             sub union () {
32 3     3 1 12 my ($self, @ontos) = @_;
33 3         11 my $result = OBO::Core::Ontology->new();
34            
35 3         14 $result->saved_by('ONTO-perl');
36 3         10 $result->remarks('Union of ontologies');
37            
38 3         4 my $default_namespace;
39             my $default_relationship_id_prefix;
40            
41 3         6 foreach my $ontology (@ontos) {
42 6         19 $result->remarks($ontology->remarks()->get_set()); # add all the remark's of the ontologies
43 6         25 $result->treat_xrefs_as_equivalent($ontology->treat_xrefs_as_equivalent->get_set()); # treat-xrefs-as-equivalent
44 6         21 $result->treat_xrefs_as_is_a($ontology->treat_xrefs_as_is_a->get_set()); # treat_xrefs_as_is_a
45 6         61 $result->idspaces($ontology->idspaces()->get_set()); # assuming the same idspace
46 6         19 $result->subset_def_map($ontology->subset_def_map()); # add all subset_def_map's by default
47 6         19 $result->synonym_type_def_set($ontology->synonym_type_def_set()->get_set()); # add all synonym_type_def_set by default
48 6         19 $default_namespace = $ontology->default_namespace(); # keep the namespace of the last ontology argument
49 6         18 $default_relationship_id_prefix = $ontology->default_relationship_id_prefix(); # keep the default relationship ID prefix of the last ontology argument
50              
51 6         8 my @terms = @{$ontology->get_terms()};
  6         25  
52 6         15 foreach my $term (@terms){
53 12         34 my $term_id = $term->id();
54 12         37 my $current_term = $result->get_term_by_id($term_id); # N.B. it could also be $result->get_term_by_name_or_synonym()
55 12 100       26 if ($current_term) { # TODO && $current_term is in $term->namespace() i.e. check if they belong to an identical namespace
56 8         23 $current_term->is_anonymous($term->is_anonymous());
57 8         23 foreach ($term->alt_id()->get_set()) {
58 0         0 $current_term->alt_id($_);
59             }
60 8 100 66     26 $current_term->def($term->def()) if (!defined $current_term->def()->text() && $term->def()->text()); # TODO implement the case where the def xref's are not balanced!
61 8         33 foreach ($term->namespace()) {
62 0         0 $current_term->namespace($_);
63             }
64 8 50 33     25 $current_term->comment($term->comment()) if (!defined $current_term->comment() && $term->comment());
65 8         22 foreach ($term->subset()) {
66 0         0 $current_term->subset($_);
67             }
68 8         25 foreach ($term->synonym_set()) {
69             # Special case: the synonym is identical and the scope is not...
70             # Solution : take the one from the last ontology and avoid an entry with something like:
71             # synonym: "lateral root-cap-epidermal stem cell" EXACT []
72             # synonym: "lateral root-cap-epidermal stem cell" RELATED []
73 0         0 $current_term->synonym_set($_);
74             }
75 8         24 foreach ($term->xref_set()->get_set()) {
76 4         12 $current_term->xref_set()->add($_);
77             }
78 8         27 foreach ($term->intersection_of()) {
79 0         0 $current_term->intersection_of($_);
80             }
81 8         24 foreach ($term->union_of()) {
82 0         0 $current_term->union_of($_);
83             }
84 8         25 foreach ($term->disjoint_from()) {
85 0         0 $current_term->disjoint_from($_);
86             }
87 8         26 $current_term->created_by($term->created_by());
88 8         21 $current_term->creation_date($term->creation_date());
89 8         23 $current_term->is_obsolete($term->is_obsolete());
90 8         41 foreach ($term->replaced_by()->get_set()) {
91 0         0 $current_term->replaced_by($_);
92             }
93 8         24 foreach ($term->consider()->get_set()) {
94 0         0 $current_term->consider($_);
95             }
96 8         25 $current_term->builtin($term->builtin());
97            
98             # fix the rel's
99 8         11 my @rels = @{$ontology->get_relationships_by_target_term($term)};
  8         24  
100 8         20 foreach my $r (@rels) {
101 4         43 my $cola = $r->tail();
102 4         11 my $tail_id = $cola->id();
103            
104             #croak 'There is no ID for the tail term linked to: ', $term->id() if (!$tail_id);
105            
106 4         10 my $tail = $result->get_term_by_id($tail_id); # Is $cola already present in the growing ontology?
107 4 100       11 if (!defined $tail) {
108 2         7 my $new_term = OBO::Core::Term->new();
109 2         6 $new_term->id($tail_id);
110 2         6 $new_term->name($cola->name());
111 2         6 $result->add_term($new_term); # add $cola if it is not present yet!
112 2         6 $tail = $result->get_term_by_id($tail_id);
113             }
114 4         12 my $r_type = $r->type(); # e.g. is_a
115 4         14 my $rel_type = $ontology->get_relationship_type_by_id($r_type);
116 4 100       11 $result->has_relationship_type($rel_type) || $result->add_relationship_type_as_string($rel_type->id(), $r_type);
117            
118 4         15 $result->create_rel($tail, $r_type, $current_term);
119             }
120             } else {
121 4         13 my $new_term = OBO::Core::Term->new();
122 4         11 $new_term->id($term_id);
123 4         30 $new_term->name($term->name());
124 4         14 $result->add_term($new_term);
125 4         10 push @terms, $term; # trick to visit again the just added term which wasn't treated yet
126             }
127             }
128            
129             #
130             # Add relationships
131             #
132 6         9 my @relationships = @{$ontology->get_relationships()};
  6         20  
133 6         12 foreach my $rela (@relationships){
134 4         13 my $rel_type_id = $rela->type();
135 4         11 my $onto_rela_type = $ontology->get_relationship_type_by_id($rel_type_id);
136 4         9 my $rel_type = $result->get_relationship_type_by_id($rel_type_id);
137            
138 4 50       17 if (!defined $rel_type) {
    50          
139 0         0 my $rt_name = $onto_rela_type->name();
140 0 0       0 my $rel_type_name = (defined $rt_name)?$rt_name:$rel_type_id;
141 0         0 $result->add_relationship_type_as_string($rel_type_id, $rel_type_id);
142 0         0 $rel_type = $result->get_relationship_type_by_id($rel_type_id);
143             } elsif (!$result->has_relationship_type($rel_type)) {
144 0         0 $result->add_relationship_type($rel_type); # add rel types between rel's (typical is_a, part_of)
145 0         0 $rel_type = $result->get_relationship_type_by_id($rel_type_id);
146             }
147            
148 4 50       9 if ($onto_rela_type) {
149 4         12 $rel_type->is_anonymous($onto_rela_type->is_anonymous());
150            
151 4         13 foreach ($onto_rela_type->alt_id()->get_set()) {
152 0         0 $rel_type->alt_id($_);
153             }
154            
155 4         15 $rel_type->builtin($onto_rela_type->builtin());
156            
157 4 50 33     11 $rel_type->def($onto_rela_type->def()) if (!defined $rel_type->def()->text() && $onto_rela_type->def()->text()); # TODO implement the case where the def xref's are not balanced!
158            
159 4         14 foreach ($onto_rela_type->namespace()) {
160 0         0 $rel_type->namespace($_);
161             }
162            
163 4 50 33     15 $rel_type->comment($onto_rela_type->comment()) if (!defined $rel_type->comment() && $onto_rela_type->comment());
164            
165 4         14 foreach ($onto_rela_type->subset()) {
166 0         0 $rel_type->subset($_);
167             }
168 4         15 foreach ($onto_rela_type->synonym_set()) {
169 0         0 $rel_type->synonym_set($_);
170             }
171 4         15 foreach ($onto_rela_type->xref_set()->get_set()) {
172 0         0 $rel_type->xref_set()->add($_);
173             }
174 4         17 foreach my $domain ($onto_rela_type->domain()->get_set()) {
175 0         0 $rel_type->xref_set()->add($domain);
176             }
177 4         15 foreach my $range ($onto_rela_type->range()->get_set()) {
178 0         0 $rel_type->xref_set()->add($range);
179             }
180 4         14 $rel_type->is_anti_symmetric($onto_rela_type->is_anti_symmetric());
181 4         15 $rel_type->is_cyclic($onto_rela_type->is_cyclic());
182 4         14 $rel_type->is_reflexive($onto_rela_type->is_reflexive());
183 4         14 $rel_type->is_symmetric($onto_rela_type->is_symmetric());
184 4         13 $rel_type->is_transitive($onto_rela_type->is_transitive());
185            
186 4         10 my $ir = $onto_rela_type->inverse_of();
187 4 50       9 $rel_type->inverse_of($ir) if (defined $ir);
188            
189 4         14 $rel_type->transitive_over($onto_rela_type->transitive_over()->get_set());
190            
191 4         14 foreach my $holds_over_chain ($onto_rela_type->holds_over_chain()) {
192 0         0 $rel_type->holds_over_chain(@{$holds_over_chain}[0], @{$holds_over_chain}[1]);
  0         0  
  0         0  
193             }
194            
195 4         12 $rel_type->is_functional($onto_rela_type->is_functional());
196 4         12 $rel_type->is_inverse_functional($onto_rela_type->is_inverse_functional());
197            
198 4         13 $rel_type->created_by($onto_rela_type->created_by());
199 4         13 $rel_type->creation_date($onto_rela_type->creation_date());
200            
201 4         11 $rel_type->modified_by($onto_rela_type->modified_by());
202 4         11 $rel_type->modification_date($onto_rela_type->modification_date());
203            
204 4         11 $rel_type->is_obsolete($onto_rela_type->is_obsolete());
205            
206 4         13 foreach ($onto_rela_type->replaced_by()->get_set()) {
207 0         0 $rel_type->replaced_by($_);
208             }
209            
210 4         15 foreach ($onto_rela_type->consider()->get_set()) {
211 0         0 $rel_type->consider($_);
212             }
213            
214 4         14 $rel_type->is_metadata_tag($onto_rela_type->is_metadata_tag());
215            
216 4         13 $rel_type->is_class_level($onto_rela_type->is_class_level());
217            
218             } else {
219             # TODO Why do we have this case?
220             }
221            
222             #
223             # link the rels:
224             #
225 4         12 my $rel_id = $rela->id();
226 4 50       12 if (! $result->has_relationship_id($rel_id)) {
227 0         0 $result->add_relationship($rela); # add rel's between rel's
228             }
229             }
230            
231             #
232             # Add relationship types
233             #
234 6         8 my @relationship_types = @{$ontology->get_relationship_types()};
  6         20  
235 6         12 foreach my $relationship_type (@relationship_types){
236 4         13 my $relationship_type_id = $relationship_type->id();
237 4         12 my $current_relationship_type = $result->get_relationship_type_by_id($relationship_type_id); # N.B. it could also be $result->get_relationship_type_by_name_or_synonym()
238 4 50       10 if ($current_relationship_type) { # TODO && $current_relationship_type is in $relationship_type->namespace() i.e. check if they belong to an identical namespace
239 4         11 $current_relationship_type->is_anonymous($relationship_type->is_anonymous());
240 4         11 foreach ($relationship_type->namespace()) {
241 0         0 $current_relationship_type->namespace($_);
242             }
243 4         12 foreach ($relationship_type->alt_id()->get_set()) {
244 0         0 $current_relationship_type->alt_id($_);
245             }
246 4         13 $current_relationship_type->builtin($relationship_type->builtin());
247 4 50 33     12 $current_relationship_type->def($relationship_type->def()) if (!defined $current_relationship_type->def()->text() && $relationship_type->def()->text()); # TODO implement the case where the def xref's are not balanced!
248 4 50 33     14 $current_relationship_type->comment($relationship_type->comment()) if (!defined $current_relationship_type->comment() && $relationship_type->comment());
249 4         12 foreach ($relationship_type->subset()) {
250 0         0 $current_relationship_type->subset($_);
251             }
252 4         12 foreach ($relationship_type->synonym_set()) {
253 0         0 $current_relationship_type->synonym_set($_);
254             }
255 4         13 foreach ($relationship_type->xref_set()->get_set()) {
256 0         0 $current_relationship_type->xref_set()->add($_);
257             }
258 4         14 foreach ($relationship_type->domain()->get_set()) {
259 0         0 $current_relationship_type->domain($_);
260             }
261 4         13 foreach ($relationship_type->range()->get_set()) {
262 0         0 $current_relationship_type->range($_);
263             }
264 4         13 $current_relationship_type->is_anti_symmetric($relationship_type->is_anti_symmetric());
265 4         11 $current_relationship_type->is_cyclic($relationship_type->is_cyclic());
266 4         12 $current_relationship_type->is_reflexive($relationship_type->is_reflexive());
267 4         11 $current_relationship_type->is_symmetric($relationship_type->is_symmetric());
268 4         11 $current_relationship_type->is_transitive($relationship_type->is_transitive());
269            
270 4         11 $current_relationship_type->inverse_of($relationship_type->inverse_of());
271            
272 4         12 foreach ($relationship_type->transitive_over()->get_set()) {
273 0         0 $current_relationship_type->transitive_over($_);
274             }
275 4         22 foreach ($relationship_type->holds_over_chain()) {
276 0         0 $current_relationship_type->holds_over_chain(@{$_}[0], @{$_}[1]);
  0         0  
  0         0  
277             }
278 4         12 $current_relationship_type->is_functional($relationship_type->is_functional());
279 4         11 $current_relationship_type->is_inverse_functional($relationship_type->is_inverse_functional());
280 4         12 foreach ($relationship_type->intersection_of()) {
281 0         0 $current_relationship_type->intersection_of($_);
282             }
283 4         13 foreach ($relationship_type->union_of()) {
284 0         0 $current_relationship_type->union_of($_);
285             }
286 4         13 foreach ($relationship_type->disjoint_from()) {
287 0         0 $current_relationship_type->disjoint_from($_);
288             }
289 4         13 $current_relationship_type->created_by($relationship_type->created_by());
290 4         11 $current_relationship_type->creation_date($relationship_type->creation_date());
291 4         13 $current_relationship_type->modified_by($relationship_type->modified_by());
292 4         11 $current_relationship_type->modification_date($relationship_type->modification_date());
293 4         11 $current_relationship_type->is_obsolete($relationship_type->is_obsolete());
294 4         11 foreach ($relationship_type->replaced_by()->get_set()) {
295 0         0 $current_relationship_type->replaced_by($_);
296             }
297 4         12 foreach ($relationship_type->consider()->get_set()) {
298 0         0 $current_relationship_type->consider($_);
299             }
300 4         13 $current_relationship_type->is_metadata_tag($relationship_type->is_metadata_tag());
301 4         11 $current_relationship_type->is_class_level($relationship_type->is_class_level());
302             } else {
303 0         0 my $new_relationship_type = OBO::Core::RelationshipType->new();
304 0         0 $new_relationship_type->id($relationship_type_id);
305 0         0 $new_relationship_type->name($relationship_type->name());
306 0         0 $result->add_relationship_type($new_relationship_type);
307 0         0 push @relationship_types, $relationship_type; # trick to visit again the just added relationship_type which wasn't treated yet
308             }
309             }
310            
311             #
312             # Add instances
313             #
314 6         7 my @instances = @{$ontology->get_instances()};
  6         19  
315 6         18 foreach my $term (@instances){
316             #TODO
317             }
318             }
319 3 50       8 $result->default_relationship_id_prefix($default_relationship_id_prefix) if (defined $default_relationship_id_prefix);
320 3 50       8 $result->default_namespace($default_namespace) if (defined $default_namespace);
321              
322 3         9 return $result;
323             }
324              
325             =head2 intersection
326              
327             Usage - $ome->intersection($o1, $o2)
328             Return - an ontology (OBO::Core::Ontology) holding the 'intersection' of $o1 and $o2
329             Args - the two ontologies (OBO::Core::Ontology) to be intersected
330             Function - finds the intersection ontology from $o1 and $o2. All the common terms by ID
331             are added to the resulting ontology. This method provides a way of comparing two
332             ontologies. The resulting ontology gives hints about the missing and identical
333             terms (comparison done by term ID). A closer analysis should be done to identify
334             the differences
335             Remark - Performance issues with huge ontologies
336            
337             =cut
338              
339             sub intersection () {
340 8     8 1 22 my ($self, $onto1, $onto2) = @_;
341 8         27 my $result = OBO::Core::Ontology->new();
342 8         31 $result->saved_by('ONTO-perl');
343 8         26 $result->default_relationship_id_prefix($onto1->default_relationship_id_prefix()); # use the default_relationship_id_prefix of the first argument
344 8         21 $result->default_namespace($onto1->default_namespace()); # use the default_namespace of the first argument
345 8         50 $result->remarks('Intersection of ontologies');
346            
347             #
348             # treat_xrefs_as_equivalent
349             #
350 8         26 my @txae1 = $onto1->treat_xrefs_as_equivalent->get_set();
351 8         27 my @txae2 = $onto2->treat_xrefs_as_equivalent->get_set();
352 8 50 33     26 if ($#txae1 > 0 && $#txae2 > 0) {
353 0         0 my %inter = ();
354 0         0 foreach my $ids_xref (@txae1, @txae2) {
355 0         0 $inter{$ids_xref}++;
356             }
357 0         0 $result->treat_xrefs_as_equivalent(sort keys %inter);
358             }
359            
360             #
361             # treat_xrefs_as_is_a
362             #
363 8         24 my @txaia1 = $onto1->treat_xrefs_as_is_a->get_set();
364 8         26 my @txaia2 = $onto2->treat_xrefs_as_is_a->get_set();
365 8 50 33     26 if ($#txaia1 > 0 && $#txaia2 > 0) {
366 0         0 my %inter = ();
367 0         0 foreach my $ids_xref (@txaia1, @txaia2) {
368 0         0 $inter{$ids_xref}++;
369             }
370 0         0 $result->treat_xrefs_as_is_a(sort keys %inter);
371             }
372            
373             # the IDspace's of both ontologies are added to the intersection ontology
374 8         23 $result->idspaces($onto1->idspaces()->get_set());
375 8         25 $result->idspaces($onto2->idspaces()->get_set());
376            
377 8         26 $result->subset_def_map($onto1->subset_def_map()); # add all subset_def_map's by default
378              
379 8         13 foreach my $term (@{$onto1->get_terms()}){
  8         25  
380 61         155 my $current_term = $onto2->get_term_by_id($term->id()); ### could also be $result->get_term_by_name_or_synonym()
381 61 100       136 if (defined $current_term) { # term intersection
382 48         145 $result->add_term($term); # added the term from onto2
383 48         130 foreach my $ins ($term->class_of()->get_set()) {
384 0         0 $result->add_instance($ins); # add its instances
385             }
386             }
387             }
388 8         30 my $onto1_number_relationships = $onto1->get_number_of_relationships();
389 8         24 my $onto2_number_relationships = $onto2->get_number_of_relationships();
390 8 100       21 my $min_number_rels_onto1_onto2 = ($onto1_number_relationships < $onto2_number_relationships)?$onto1_number_relationships:$onto2_number_relationships;
391            
392 8         8 my @terms = @{$result->get_terms()};
  8         22  
393            
394 8         30 my $stop = OBO::Util::Set->new();
395 8         14 map {$stop->add($_->id())} @terms;
  48         141  
396            
397             # path of references
398 8         13 my @pr1;
399             my @pr2;
400            
401             # link the common terms
402 8         14 foreach my $term (@terms) {
403 48         125 my $term_id = $term->id();
404            
405             #
406             # path of references: onto1 and onto2
407             #
408            
409             # onto1
410 48         141 my @pref1 = $onto1->get_paths_term_terms($term_id, $stop);
411 48         97 push @pr1, [@pref1];
412            
413             # onto2
414 48         140 my @pref2 = $onto2->get_paths_term_terms($term_id, $stop);
415 48         148 push @pr2, [@pref2];
416             }
417            
418             # pr1
419 8         12 my %cand;
420 8         14 foreach my $pref (@pr1) {
421 48         74 foreach my $ref (@$pref) {
422 177         470 my $type = @$ref[0]->type(); # first type
423 177         227 my $invalid = 0;
424 177         174 my $r_type;
425 177         238 foreach my $tt (@$ref) {
426 396         907 $r_type = $tt->type();
427 396 100       898 if ($type ne $r_type) {
428 93         101 $invalid = 1;
429 93         125 last; # no more walking
430             }
431             }
432 177 100       361 if (!$invalid) {
433 84         226 my $f = @$ref[0]->tail()->id();
434 84         232 my $l = @$ref[$#$ref]->head()->id();
435 84         227 $cand{$f.'->'.$r_type.'->'.$l} = 1; # there could be more than 1 path
436 84         168 $invalid = 0;
437             }
438             }
439             }
440              
441             # pr2
442 8         10 my %r_cand;
443 8         12 foreach my $pref (@pr2) {
444 48         78 foreach my $ref (@$pref) {
445 182         478 my $type = @$ref[0]->type(); # first type
446 182         224 my $invalid = 0;
447 182         178 my $r_type;
448 182         258 foreach my $tt (@$ref) {
449 399         922 $r_type = $tt->type();
450 399 100       884 if ($type ne $r_type) { # ONLY identical rel types in the path are admitted!!!
451             #warn 'INVALID REL: ', $tt->id();
452 101         111 $invalid = 1;
453 101         119 last; # no more walking
454             }
455             }
456 182 100       382 if (!$invalid) {
457 81         223 my $f = @$ref[0]->tail()->id();
458 81         224 my $l = @$ref[$#$ref]->head()->id();
459 81         164 $cand{$f.'->'.$r_type.'->'.$l}++;
460 81         184 $r_cand{$f.'->'.$l} = $r_type;
461 81         154 $invalid = 0;
462             }
463             }
464             }
465            
466             # cleaning candidates
467 8         50 foreach (sort keys (%cand)) {
468 84 100       189 delete $cand{$_} if ($cand{$_} < 2);
469             }
470            
471             # candidates simplified
472 8         19 my %cola;
473 8         21 foreach (keys (%cand)) {
474 67 50       372 my $f = $1, my $r = $2, my $l = $3 if ($_ =~ /(.*)->(.*)->(.*)/);
475 67         147 $cola{$f} .= $l.' '; # hold the candidates
476             }
477            
478             # transitive reduction
479 8         56 while ( my ($k, $v) = each(%cola)) {
480 30         96 my $V = OBO::Util::Set->new();
481 30         81 $V->add($v);
482            
483 30         75 my @T = split (' ', $v);
484 30         46 my %target = ();
485 30         65 my $r_type = $r_cand{$k.'->'.$T[$#T]}; # check
486            
487 30         65 while ($#T > -1) {
488 200         290 my $n = pop @T;
489 200         375 $target{$r_type.'->'.$n}++;
490 200 100       524 if (!$V->contains($n)) {
491 123         295 $V->add($n);
492 123 100       526 push @T, split(' ', $cola{$n}) if ($cola{$n});
493             }
494             }
495            
496 30         89 while (my ($t, $veces) = each(%target)) {
497 123 100       415 if ($veces > 1) { # if so, the delete $k->$t
498 53         233 delete $cand{$k.'->'.$t};
499             }
500             }
501             }
502            
503             # after 'transitive reduction' we have
504 8         24 while (my ($k, $v) = each(%cand)) {
505 43 50       243 my $s = $1, my $r_type = $2, my $t = $3 if ($k =~ /(.*)->(.*)->(.*)/);
506 43         110 my $source = $result->get_term_by_id($s);
507 43         110 my $target = $result->get_term_by_id($t);
508            
509 43 100       121 if (!($result->has_relationship_type_id($r_type))) {
510 9         26 $result->add_relationship_type_as_string($r_type, $r_type); # ID = NAME
511             }
512 43         135 $result->create_rel($source, $r_type, $target);
513             }
514 8         185 return $result;
515             }
516              
517             =head2 transitive_closure
518              
519             Usage - $ome->transitive_closure($o, @transitive_relationship_types)
520             Return - an ontology (OBO::Core::Ontology) with the transitive closure
521             Args - the ontology (OBO::Core::Ontology) to be expanded and
522             optionally an array with the transitive relationship
523             types (by default: 'is_a' and 'part_of') to be considered
524             Function - expands all the transitive relationships (e.g. is_a, part_of) along the
525             hierarchy and generates a new ontology holding all possible paths
526             Remark - Performance issues with huge ontologies.
527             - an experimental code is enabled (flag: $composition) based on http://www.geneontology.org/GO.ontology.relations.shtml
528            
529             =cut
530              
531             sub transitive_closure () {
532 1     1 1 118 my ($self, $ontology, @trans_rts, $composition) = @_;
533            
534 1         4 my @default_trans_rts = ('is_a', 'part_of');
535 1 50       4 if (scalar @trans_rts > 0) {
536 0         0 @default_trans_rts = @trans_rts;
537             }
538            
539 1         6 my $result = OBO::Core::Ontology->new();
540 1         5 $result->saved_by('ONTO-perl');
541 1         4 $result->idspaces($ontology->idspaces()->get_set());
542 1         4 $result->default_relationship_id_prefix($ontology->default_relationship_id_prefix());
543 1         4 $result->default_namespace($ontology->default_namespace());
544 1         5 $result->remarks('Ontology with transitive closures');
545 1         4 $result->treat_xrefs_as_equivalent($ontology->treat_xrefs_as_equivalent->get_set()); # treat-xrefs-as-equivalent
546 1         4 $result->treat_xrefs_as_is_a($ontology->treat_xrefs_as_is_a->get_set()); # treat_xrefs_as_is_a
547 1         4 $result->subset_def_map($ontology->subset_def_map()); # add all subset_def_map's by default
548 1         33 $result->synonym_type_def_set($ontology->synonym_type_def_set()->get_set()); # add all synonym_type_def_set by default
549            
550 1         3 my @terms = @{$ontology->get_terms()};
  1         5  
551 1         4 foreach my $term (@terms) {
552 26         73 my $current_term = $result->get_term_by_id($term->id());
553 26 100       52 if (defined $current_term) { # TODO && $current_term is in $term->namespace() i.e. check if they belong to an identical namespace
554 13 50 33     34 $current_term->is_anonymous(1) if (!defined $current_term->is_anonymous() && $term->is_anonymous());
555 13         32 foreach ($term->alt_id()->get_set()) {
556 0         0 $current_term->alt_id($_);
557             }
558 13 50 33     42 $current_term->def($term->def()) if (!defined $current_term->def()->text() && $term->def()->text()); # TODO implement the case where the def xref's are not balanced!
559 13         38 foreach ($term->namespace()) {
560 0         0 $current_term->namespace($_);
561             }
562 13 50 33     45 $current_term->comment($term->comment()) if (!defined $current_term->comment() && $term->comment());
563 13         35 foreach ($term->subset()) {
564 0         0 $current_term->subset($_);
565             }
566 13         39 foreach ($term->synonym_set()) {
567 0         0 $current_term->synonym_set($_);
568             }
569 13         36 foreach ($term->xref_set()->get_set()) {
570 0         0 $current_term->xref_set()->add($_);
571             }
572 13         43 foreach ($term->intersection_of()) {
573 0         0 $current_term->intersection_of($_);
574             }
575 13         41 foreach ($term->union_of()) {
576 0         0 $current_term->union_of($_);
577             }
578 13         36 foreach ($term->disjoint_from()) {
579 0         0 $current_term->disjoint_from($_);
580             }
581 13 50 33     38 $current_term->is_obsolete(1) if (!defined $current_term->is_obsolete() && $term->is_obsolete());
582 13         36 foreach ($term->replaced_by()->get_set()) {
583 0         0 $current_term->replaced_by($_);
584             }
585 13         37 foreach ($term->consider()->get_set()) {
586 0         0 $current_term->consider($_);
587             }
588 13 50 33     37 $current_term->builtin(1) if (!defined $current_term->builtin() && $term->builtin());
589            
590             # fix the rel's
591 13         15 my @rels = @{$ontology->get_relationships_by_target_term($term)};
  13         33  
592 13         27 foreach my $r (@rels) {
593 15         44 my $cola = $r->tail();
594 15         72 my $cola_id = $cola->id();
595            
596             #croak 'There is no ID for the tail term linked to: ', $term->id() if (!$tail_id);
597            
598 15         41 my $tail = $result->get_term_by_id($cola_id); # Is $cola already present in the growing ontology?
599 15 50       35 if (!defined $tail) {
600 0         0 $result->add_term($cola); # add $cola if it is not present!
601 0         0 foreach my $ins ($cola->class_of()->get_set()) {
602 0         0 $result->add_instance($ins); # add its instances
603             }
604 0         0 $tail = $result->get_term_by_id($cola_id);
605            
606 0         0 my @more_rels = @{$ontology->get_relationships_by_target_term($cola)};
  0         0  
607 0         0 @rels = (@rels, @more_rels); # trick to 'recursively' visit the just added rel
608             }
609 15         38 my $r_type = $r->type();
610            
611             #
612             # relationship type
613             #
614 15         41 my $rel_type = $ontology->get_relationship_type_by_id($r_type);
615 15 100       42 $result->has_relationship_type($rel_type) || $result->add_relationship_type($rel_type);
616            
617 15         105 $r->id($cola_id.'_'.$r_type.'_'.$current_term->id());
618 15         44 $r->link($tail, $current_term);
619            
620             # add the relationship after adding its type
621 15         41 $result->add_relationship($r);
622             }
623             } else {
624 13         36 $result->add_term($term);
625 13         35 foreach my $ins ($term->class_of()->get_set()) {
626 0         0 $result->add_instance($ins); # add its instances
627             }
628 13         31 push @terms, $term; # trick to 'recursively' visit the just added term
629             }
630             }
631 1         2 foreach my $rel (@{$ontology->get_relationships()}) {
  1         6  
632 15 50       39 if (! $result->has_relationship_id($rel->id())) {
633 0         0 my $rel_type = $ontology->get_relationship_type_by_id($rel->type());
634 0 0       0 $result->has_relationship_type($rel_type) || $result->add_relationship_type($rel_type);
635            
636             # add the relationship after adding its type
637 0         0 $result->add_relationship($rel);
638             }
639             }
640 1         3 @terms = @{$result->get_terms()}; # set 'terms' (avoding the pushed ones)
  1         4  
641            
642 1         6 my $stop = OBO::Util::Set->new();
643 1         3 map { $stop->add($_->id()) } @terms;
  13         34  
644              
645             #print STDERR "\nNUMBER OF RELS BEFORE = ", $result->get_number_of_relationships();
646              
647             # link the common terms
648 1         3 foreach my $term (@terms) {
649 13         35 my $term_id = $term->id();
650             # path of references:
651 13         22 foreach my $type_of_rel (@default_trans_rts) {
652             #$result->create_rel($term, $type_of_rel, $term); # reflexive one (not working line since ONTO-PERL does not allow more that one reflexive relationship)
653              
654             # take the paths from the original ontology
655 26         75 my @ref_paths = $ontology->get_paths_term_terms_same_rel($term_id, $stop, $type_of_rel);
656              
657 26         51 foreach my $ref_path (@ref_paths) {
658             #next if !defined @$ref_path[0]; # reflexive relationships (e.g. GO:0000011_is_a_GO:0000011) are problematic...
659 23         69 my $f = @$ref_path[0]->tail();
660 23         62 my $l = @$ref_path[$#$ref_path]->head();
661 23         70 $result->create_rel($f, $type_of_rel, $l); # add the transitive closure relationship!
662            
663 23         57 my $new_rel_id = $f->id().'_'.$type_of_rel.'_'.$l->id();
664             #print STDERR "NEW_transitive_closure_relationship1: ".$new_rel_id."\n";
665             }
666             }
667             }
668            
669             #
670             # compositions: isa*partof=>partof and partof*isa=>partof
671             #
672 1         3 $composition = 1; # experimental code: ENABLED !!!!!!!!!!!!!!!!!!!!
673            
674 1 50       5 if ($composition) { # http://wiki.geneontology.org/index.php/Relation_composition
675            
676             #
677             #
678             #
679             #print STDERR "\n\nNUMBER OF RELS AFTER 1 = ", $result->get_number_of_relationships();
680            
681             #@terms = @{$result->get_terms()}; # set 'terms' (avoding the pushed ones)
682             #my $stop = OBO::Util::Set->new();
683             #map {$stop->add($_->id())} @terms;
684            
685 1         2 foreach my $term (@terms) {
686 13         38 my $term1_id = $term->id();
687            
688 13         60 foreach my $term2_id ($stop->get_set()) {
689            
690 169 100       379 next if ($term1_id eq $term2_id); # reflexive relationships are skipped
691            
692 156         460 my @ref_paths = $result->get_paths_term1_term2($term1_id, $term2_id);
693            
694             #print STDERR "\n\tNUMBER_OF_PATHS: ", scalar @ref_paths;
695 156         345 foreach my $ref_path (@ref_paths) {
696            
697 235 100       591 next if !defined @$ref_path[0];
698 226 100       545 next if !defined @$ref_path[1]; # two elements (at least) are needed to make the composition
699            
700 183         547 my $left_entry = @$ref_path[0]->tail();
701 183         532 my $left_type = @$ref_path[0]->type();
702 183         497 my $right_entry = @$ref_path[1]->head();
703 183         508 my $right_type = @$ref_path[1]->type();
704            
705 183 100       379 if ($left_type eq $right_type) {
706            
707 68         192 my $new_rel_id = $left_entry->id().'_'.$left_type.'_'.$right_entry->id();
708 68 100       196 if (!$result->has_relationship_id($new_rel_id)) {
709 3         10 $result->create_rel($left_entry, $left_type, $right_entry); # add a missed transitivity relationship!
710             #print STDERR "\nNEW_TRANSITIVITY: ".$new_rel_id;
711             }
712             } else {
713 115         318 my $new_rel_id = $left_entry->id()."_part_of_".$right_entry->id();
714            
715 115 100       355 if (!$result->has_relationship_id($new_rel_id)) {
716 28         96 $result->create_rel($left_entry, 'part_of', $right_entry); # add the composed relationship!
717             #print STDERR "\tNEW_COMPOSITION: ".$new_rel_id."\n";
718             }
719             }
720             }
721             }
722             }
723              
724             #print STDERR "\n\nNUMBER OF RELS AFTER 2 = ", $result->get_number_of_relationships();
725              
726            
727             #
728             # second transitivity pass on the NEW ontology so far!
729             #
730             {
731 1         3 @terms = @{$result->get_terms()}; # set 'terms' (avoding the pushed ones)
  1         2  
  1         6  
732            
733 1         10 my $stop = OBO::Util::Set->new();
734 1         4 map {$stop->add($_->id())} @terms;
  13         41  
735            
736             # link the common terms
737 1         4 foreach my $term (@terms) {
738 13         40 my $term_id = $term->id();
739             # path of references:
740 13         26 foreach my $type_of_rel (@default_trans_rts) {
741             #$result->create_rel($term, $type_of_rel, $term); # reflexive one (not working line since ONTO-PERL does not allow more that one reflexive relationship)
742            
743             # take the paths from the original ontology
744 26         88 my @ref_paths = $result->get_paths_term_terms_same_rel($term_id, $stop, $type_of_rel);
745            
746 26         51 foreach my $ref_path (@ref_paths) {
747 140 50       305 next if !defined @$ref_path[0]; # reflexive relationships (e.g. GO:0000011_is_a_GO:0000011) are problematic...
748 140         387 my $f = @$ref_path[0]->tail();
749 140         411 my $l = @$ref_path[$#$ref_path]->head();
750 140         375 my $new_rel_id = $f->id().'_'.$type_of_rel.'_'.$l->id();
751            
752 140 100       400 if (!$result->has_relationship_id($new_rel_id)) {
753 9         29 $result->create_rel($f, $type_of_rel, $l); # add the transitive closure relationship!
754             #print STDERR "\nNEW_transitive_closure_relationship2: ".$new_rel_id;
755             }
756             }
757             }
758             }
759             }
760              
761             #print STDERR "\n\nNUMBER OF RELS AFTER 3 = ", $result->get_number_of_relationships();
762            
763             #
764             # second composition pass: needed?
765             #
766 1         6 if (1) {
767 1         2 @terms = @{$result->get_terms()}; # set 'terms' (avoding the pushed ones)
  1         5  
768            
769 1         8 my $stop = OBO::Util::Set->new();
770 1         2 map {$stop->add($_->id())} @terms;
  13         37  
771            
772 1         2 foreach my $term (@terms) {
773 13         44 my $term1_id = $term->id();
774            
775 13         70 foreach my $term2_id ($stop->get_set()) {
776            
777 169 100       436 next if ($term1_id eq $term2_id); # reflexive relationships are skipped
778            
779 156         470 my @ref_paths = $result->get_paths_term1_term2($term1_id, $term2_id);
780            
781 156         399 foreach my $ref_path (@ref_paths) {
782            
783 347 50       824 next if !defined @$ref_path[0];
784 347 100       825 next if !defined @$ref_path[1]; # two elements (at least) are needed to make the composition
785            
786 284         838 my $left_entry = @$ref_path[0]->tail();
787 284         841 my $left_type = @$ref_path[0]->type();
788 284         806 my $right_entry = @$ref_path[1]->head();
789 284         821 my $right_type = @$ref_path[1]->type();
790            
791             #next if ($left_type eq $right_type); # done above already
792 284 100       571 if ($left_type eq $right_type) {
793            
794 104         305 my $new_rel_id = $left_entry->id().'_'.$left_type.'_'.$right_entry->id();
795 104 50       317 if (!$result->has_relationship_id($new_rel_id)) {
796 0         0 $result->create_rel($left_entry, $left_type, $right_entry); # add a missed transitivity relationship!
797 0         0 print STDERR "\nNEW_TRANSITIVITY: ".$new_rel_id;
798             }
799             } else {
800 180         476 my $new_rel_id = $left_entry->id()."_part_of_".$right_entry->id();
801            
802 180 50       584 if (!$result->has_relationship_id($new_rel_id)) {
803 0         0 $result->create_rel($left_entry, 'part_of', $right_entry); # add the composed relationship!
804 0         0 print STDERR "\tNEW_COMPOSITION: ".$new_rel_id."\n";
805             }
806             }
807             }
808             }
809             }
810             #print STDERR "\n\nNUMBER OF RELS AFTER 4 = ", $result->get_number_of_relationships();
811             }
812             }
813             #print STDERR "\n\nNUMBER OF RELS AFTER 5 = ", $result->get_number_of_relationships();
814            
815 1         11 return $result;
816             }
817              
818             =head2 transitive_reduction
819              
820             Usage - $ome->transitive_reduction($o, @transitive_relationship_types)
821             Return - an ontology (OBO::Core::Ontology) ensuring transitive reduction
822             Args - an ontology (OBO::Core::Ontology) on which the transitive reduction algorithm will be applied
823             and optionally an array with the transitive relationship types (by default: 'is_a' and 'part_of') to be considered
824             Function - reduces all the transitive relationships (e.g. is_a, part_of) along the
825             hierarchy and generates a new ontology holding the minimal paths (relationships)
826             Remark - Performance issues with huge ontologies.
827            
828             =cut
829              
830             sub transitive_reduction () {
831 2     2 1 12 my ($self, $ontology, @trans_rts) = @_;
832 2         12 my @default_trans_rts = ('is_a', 'part_of', 'located_in');
833 2 50       16 if (scalar @trans_rts > 0) {
834 0         0 @default_trans_rts = @trans_rts;
835             }
836            
837 2         19 my $result = OBO::Core::Ontology->new();
838 2         15 $result->saved_by('ONTO-perl');
839 2         13 $result->idspaces($ontology->idspaces()->get_set());
840 2         15 $result->default_relationship_id_prefix($ontology->default_relationship_id_prefix());
841 2         10 $result->default_namespace($ontology->default_namespace());
842 2         12 $result->remarks('Ontology with transitive reduction');
843 2         25 $result->treat_xrefs_as_equivalent($ontology->treat_xrefs_as_equivalent->get_set()); # treat-xrefs-as-equivalent
844 2         13 $result->treat_xrefs_as_is_a($ontology->treat_xrefs_as_is_a->get_set()); # treat_xrefs_as_is_a
845 2         10 $result->subset_def_map($ontology->subset_def_map()); # add all subset_def_map's by default
846 2         10 $result->synonym_type_def_set($ontology->synonym_type_def_set()->get_set()); # add all synonym_type_def_set by default
847            
848 2         5 my @terms = @{$ontology->get_terms()};
  2         12  
849 2         10 foreach my $term (@terms) {
850 52         187 my $current_term = $result->get_term_by_id($term->id());
851 52 100       132 if (defined $current_term) { # TODO && $current_term is in $term->namespace() i.e. check if they belong to an identical namespace
852 26 50 33     78 $current_term->is_anonymous(1) if (!defined $current_term->is_anonymous() && $term->is_anonymous());
853 26         78 foreach ($term->alt_id()->get_set()) {
854 0         0 $current_term->alt_id($_);
855             }
856 26 50 33     87 $current_term->def($term->def()) if (!defined $current_term->def()->text() && $term->def()->text()); # TODO implement the case where the def xref's are not balanced!
857 26         80 foreach ($term->namespace()) {
858 0         0 $current_term->namespace($_);
859             }
860 26 50 33     95 $current_term->comment($term->comment()) if (!defined $current_term->comment() && $term->comment());
861 26         622 foreach ($term->subset()) {
862 0         0 $current_term->subset($_);
863             }
864 26         95 foreach ($term->synonym_set()) {
865 0         0 $current_term->synonym_set($_);
866             }
867 26         99 foreach ($term->xref_set()->get_set()) {
868 0         0 $current_term->xref_set()->add($_);
869             }
870 26         99 foreach ($term->intersection_of()) {
871 0         0 $current_term->intersection_of($_);
872             }
873 26         88 foreach ($term->union_of()) {
874 0         0 $current_term->union_of($_);
875             }
876 26         92 foreach ($term->disjoint_from()) {
877 0         0 $current_term->disjoint_from($_);
878             }
879 26 50 33     137 $current_term->is_obsolete(1) if (!defined $current_term->is_obsolete() && $term->is_obsolete());
880 26         84 foreach ($term->replaced_by()->get_set()) {
881 0         0 $current_term->replaced_by($_);
882             }
883 26         85 foreach ($term->consider()->get_set()) {
884 0         0 $current_term->consider($_);
885             }
886 26 50 33     120 $current_term->builtin(1) if (!defined $current_term->builtin() && $term->builtin());
887            
888             # fix the rel's
889 26         37 my @rels = @{$ontology->get_relationships_by_target_term($term)};
  26         86  
890 26         68 foreach my $r (@rels) {
891 78         244 my $cola = $r->tail();
892 78         215 my $cola_id = $cola->id();
893            
894             #croak 'There is no ID for the tail term linked to: ', $term->id() if (!$tail_id);
895            
896 78         203 my $tail = $result->get_term_by_id($cola_id); # Is $cola already present in the growing ontology?
897 78 50       166 if (!defined $tail) {
898 0         0 $result->add_term($cola); # add $cola if it is not present!
899 0         0 foreach my $ins ($cola->class_of()->get_set()) {
900 0         0 $result->add_instance($ins); # add its instances
901             }
902 0         0 $tail = $result->get_term_by_id($cola_id);
903            
904 0         0 my @more_rels = @{$ontology->get_relationships_by_target_term($cola)};
  0         0  
905 0         0 @rels = (@rels, @more_rels); # trick to 'recursively' visit the just added rel
906             }
907            
908 78         226 my $r_type = $r->type();
909            
910             #
911             # relationship type
912             #
913 78         208 my $rel_type = $ontology->get_relationship_type_by_id($r_type);
914 78 100       221 $result->has_relationship_type($rel_type) || $result->add_relationship_type($rel_type);
915              
916             # add the relationship after adding its type
917 78         307 $r->id($cola_id.'_'.$r_type.'_'.$current_term->id());
918 78         243 $r->link($tail, $current_term);
919 78         249 $result->add_relationship($r);
920             }
921             } else {
922 26         99 $result->add_term($term);
923 26         94 foreach my $ins ($term->class_of()->get_set()) {
924 0         0 $result->add_instance($ins); # add its instances
925             }
926 26         103 push @terms, $term; # trick to 'recursively' visit the just added term
927             }
928             }
929              
930             #
931             # In this loop, relationships of the Typedefs are added
932             #
933 2         4 foreach my $rel (@{$ontology->get_relationships()}) {
  2         10  
934 78 50       207 if (!$result->has_relationship_id($rel->id())) {
935 0         0 my $rel_type = $ontology->get_relationship_type_by_id($rel->type());
936 0 0       0 $result->has_relationship_type($rel_type) || $result->add_relationship_type($rel_type);
937            
938             # add the relationship after adding its type
939 0         0 $result->add_relationship($rel);
940             }
941             }
942            
943             #
944             # Add NON-USED relationship types
945             #
946 2         8 foreach my $rel_type ( @{$ontology->get_relationship_types_sorted_by_id()} ) {
  2         9  
947 4 50       12 $result->has_relationship_type($rel_type) || $result->add_relationship_type($rel_type);
948             }
949              
950 2         5 @terms = @{$result->get_terms()}; # set 'terms' (avoding the pushed ones)
  2         7  
951              
952 2         11 my $stop = OBO::Util::Set->new();
953 2         4 map {$stop->add($_->id())} @terms;
  26         71  
954              
955             # delete implicit rel's
956 2         5 foreach my $term (@terms) {
957 26         78 my $term_id = $term->id();
958             # path of references:
959 26         50 foreach my $type_of_rel (@default_trans_rts) {
960             #$result->create_rel($term, $type_of_rel, $term); # reflexive one (not working line since ONTO-PERL does not allow more that one reflexive relationship)
961              
962             # take the paths from the original ontology
963 78         241 my @ref_paths = $result->get_paths_term_terms_same_rel($term_id, $stop, $type_of_rel);
964              
965 78         176 foreach my $ref_path (@ref_paths) {
966 167 50       389 next if !defined @$ref_path[0];
967 167         212 my $i = $#$ref_path;
968 167         507 my $f = @$ref_path[0]->tail();
969 167         468 my $l = @$ref_path[$i]->head();
970 167         460 my $v = $result->get_relationship_by_id($f->id().'_'.$type_of_rel.'_'.$l->id());
971            
972 167 100 100     857 if ($v && ($i > 0)) {
973 33         88 $result->delete_relationship($v);
974             }
975             }
976             }
977             }
978            
979             # delete compositon of rel's
980 2         5 foreach my $term (@terms) {
981 26         80 my $term_id = $term->id();
982 26         100 foreach my $term2_id ($stop->get_set()) {
983 338 100       791 next if ($term_id eq $term2_id); # reflexive
984 312         862 my @ref_paths = $result->get_paths_term1_term2($term_id, $term2_id);
985            
986 312         598 my $rel_id = $term_id."_part_of_".$term2_id; # deleting the "part of" relationships added by following the simplest rule: isa*partof=>partof and partof*isa=>partof
987            
988 312 100       900 next if (!$result->has_relationship_id($rel_id));
989            
990 29         77 foreach my $ref_path (@ref_paths) {
991 68 50       175 next if !defined @$ref_path[0]; # reflexive relationships (e.g. GO:0000011_is_a_GO:0000011) are problematic...
992 68 100       207 next if !defined @$ref_path[1]; # two elements (at least) are needed to make the composition
993            
994 39         116 my $left_entry = @$ref_path[0]->tail();
995 39         115 my $left_type = @$ref_path[0]->type();
996 39         60 my $i = $#$ref_path;
997 39         105 my $right_entry = @$ref_path[$i]->head();
998 39         104 my $right_type = @$ref_path[$i]->type();
999            
1000             #next if ($left_type eq $right_type);
1001              
1002 39         109 my $new_rel_id = $left_entry->id()."_part_of_".$right_entry->id(); # deleting the "part of" relationships added by following the simplest rule: isa*partof=>partof and partof*isa=>partof
1003              
1004 39 100       116 if ($result->has_relationship_id($new_rel_id)) {
1005 15         48 my $v = $result->get_relationship_by_id($new_rel_id);
1006 15         50 $result->delete_relationship($v); # delete the composed relationship!
1007             }
1008             }
1009             }
1010             }
1011            
1012 2         18 return $result;
1013             }
1014              
1015             1;
1016              
1017             __END__