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-2014 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   664 use OBO::Util::ObjectSet;
  1         2  
  1         25  
14              
15 1     1   4 use strict;
  1         1  
  1         30  
16 1     1   4 use warnings;
  1         2  
  1         4614  
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 11 my ($self, @ontos) = @_;
33 3         9 my $result = OBO::Core::Ontology->new();
34            
35 3         8 $result->saved_by('ONTO-perl');
36 3         7 $result->remarks('Union of ontologies');
37            
38 3         3 my $default_namespace;
39             my $default_relationship_id_prefix;
40            
41 3         7 foreach my $ontology (@ontos) {
42 6         13 $result->remarks($ontology->remarks()->get_set()); # add all the remark's of the ontologies
43 6         16 $result->treat_xrefs_as_equivalent($ontology->treat_xrefs_as_equivalent->get_set()); # treat-xrefs-as-equivalent
44 6         18 $result->treat_xrefs_as_is_a($ontology->treat_xrefs_as_is_a->get_set()); # treat_xrefs_as_is_a
45 6         14 $result->idspaces($ontology->idspaces()->get_set()); # assuming the same idspace
46 6         14 $result->subset_def_map($ontology->subset_def_map()); # add all subset_def_map's by default
47 6         14 $result->synonym_type_def_set($ontology->synonym_type_def_set()->get_set()); # add all synonym_type_def_set by default
48 6         13 $default_namespace = $ontology->default_namespace(); # keep the namespace of the last ontology argument
49 6         12 $default_relationship_id_prefix = $ontology->default_relationship_id_prefix(); # keep the default relationship ID prefix of the last ontology argument
50              
51 6         5 my @terms = @{$ontology->get_terms()};
  6         14  
52 6         10 foreach my $term (@terms){
53 12         22 my $term_id = $term->id();
54 12         23 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       17 if ($current_term) { # TODO && $current_term is in $term->namespace() i.e. check if they belong to an identical namespace
56 8         17 $current_term->is_anonymous($term->is_anonymous());
57 8         16 foreach ($term->alt_id()->get_set()) {
58 0         0 $current_term->alt_id($_);
59             }
60 8 100 66     21 $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         30 foreach ($term->namespace()) {
62 0         0 $current_term->namespace($_);
63             }
64 8 50 33     20 $current_term->comment($term->comment()) if (!defined $current_term->comment() && $term->comment());
65 8         19 foreach ($term->subset()) {
66 0         0 $current_term->subset($_);
67             }
68 8         18 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         18 foreach ($term->xref_set()->get_set()) {
76 4         10 $current_term->xref_set()->add($_);
77             }
78 8         21 foreach ($term->intersection_of()) {
79 0         0 $current_term->intersection_of($_);
80             }
81 8         17 foreach ($term->union_of()) {
82 0         0 $current_term->union_of($_);
83             }
84 8         18 foreach ($term->disjoint_from()) {
85 0         0 $current_term->disjoint_from($_);
86             }
87 8         17 $current_term->created_by($term->created_by());
88 8         14 $current_term->creation_date($term->creation_date());
89 8         17 $current_term->is_obsolete($term->is_obsolete());
90 8         28 foreach ($term->replaced_by()->get_set()) {
91 0         0 $current_term->replaced_by($_);
92             }
93 8         18 foreach ($term->consider()->get_set()) {
94 0         0 $current_term->consider($_);
95             }
96 8         17 $current_term->builtin($term->builtin());
97            
98             # fix the rel's
99 8         8 my @rels = @{$ontology->get_relationships_by_target_term($term)};
  8         18  
100 8         14 foreach my $r (@rels) {
101 4         11 my $cola = $r->tail();
102 4         8 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         9 my $tail = $result->get_term_by_id($tail_id); # Is $cola already present in the growing ontology?
107 4 100       10 if (!defined $tail) {
108 2         5 my $new_term = OBO::Core::Term->new();
109 2         5 $new_term->id($tail_id);
110 2         4 $new_term->name($cola->name());
111 2         4 $result->add_term($new_term); # add $cola if it is not present yet!
112 2         4 $tail = $result->get_term_by_id($tail_id);
113             }
114 4         10 my $r_type = $r->type(); # e.g. is_a
115 4         8 my $rel_type = $ontology->get_relationship_type_by_id($r_type);
116 4 100       8 $result->has_relationship_type($rel_type) || $result->add_relationship_type_as_string($rel_type->id(), $r_type);
117            
118 4         13 $result->create_rel($tail, $r_type, $current_term);
119             }
120             } else {
121 4         9 my $new_term = OBO::Core::Term->new();
122 4         8 $new_term->id($term_id);
123 4         8 $new_term->name($term->name());
124 4         9 $result->add_term($new_term);
125 4         7 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         6 my @relationships = @{$ontology->get_relationships()};
  6         16  
133 6         8 foreach my $rela (@relationships){
134 4         10 my $rel_type_id = $rela->type();
135 4         8 my $onto_rela_type = $ontology->get_relationship_type_by_id($rel_type_id);
136 4         7 my $rel_type = $result->get_relationship_type_by_id($rel_type_id);
137            
138 4 50       11 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       7 if ($onto_rela_type) {
149 4         9 $rel_type->is_anonymous($onto_rela_type->is_anonymous());
150            
151 4         7 foreach ($onto_rela_type->alt_id()->get_set()) {
152 0         0 $rel_type->alt_id($_);
153             }
154            
155 4         11 $rel_type->builtin($onto_rela_type->builtin());
156            
157 4 50 33     8 $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         10 foreach ($onto_rela_type->namespace()) {
160 0         0 $rel_type->namespace($_);
161             }
162            
163 4 50 33     9 $rel_type->comment($onto_rela_type->comment()) if (!defined $rel_type->comment() && $onto_rela_type->comment());
164            
165 4         10 foreach ($onto_rela_type->subset()) {
166 0         0 $rel_type->subset($_);
167             }
168 4         10 foreach ($onto_rela_type->synonym_set()) {
169 0         0 $rel_type->synonym_set($_);
170             }
171 4         12 foreach ($onto_rela_type->xref_set()->get_set()) {
172 0         0 $rel_type->xref_set()->add($_);
173             }
174 4         12 foreach my $domain ($onto_rela_type->domain()->get_set()) {
175 0         0 $rel_type->xref_set()->add($domain);
176             }
177 4         10 foreach my $range ($onto_rela_type->range()->get_set()) {
178 0         0 $rel_type->xref_set()->add($range);
179             }
180 4         10 $rel_type->is_anti_symmetric($onto_rela_type->is_anti_symmetric());
181 4         9 $rel_type->is_cyclic($onto_rela_type->is_cyclic());
182 4         9 $rel_type->is_reflexive($onto_rela_type->is_reflexive());
183 4         8 $rel_type->is_symmetric($onto_rela_type->is_symmetric());
184 4         9 $rel_type->is_transitive($onto_rela_type->is_transitive());
185            
186 4         7 my $ir = $onto_rela_type->inverse_of();
187 4 50       8 $rel_type->inverse_of($ir) if (defined $ir);
188            
189 4         10 $rel_type->transitive_over($onto_rela_type->transitive_over()->get_set());
190            
191 4         10 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         8 $rel_type->is_functional($onto_rela_type->is_functional());
196 4         9 $rel_type->is_inverse_functional($onto_rela_type->is_inverse_functional());
197            
198 4         9 $rel_type->created_by($onto_rela_type->created_by());
199 4         8 $rel_type->creation_date($onto_rela_type->creation_date());
200            
201 4         8 $rel_type->modified_by($onto_rela_type->modified_by());
202 4         7 $rel_type->modification_date($onto_rela_type->modification_date());
203            
204 4         9 $rel_type->is_obsolete($onto_rela_type->is_obsolete());
205            
206 4         7 foreach ($onto_rela_type->replaced_by()->get_set()) {
207 0         0 $rel_type->replaced_by($_);
208             }
209            
210 4         10 foreach ($onto_rela_type->consider()->get_set()) {
211 0         0 $rel_type->consider($_);
212             }
213            
214 4         9 $rel_type->is_metadata_tag($onto_rela_type->is_metadata_tag());
215            
216 4         8 $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         10 my $rel_id = $rela->id();
226 4 50       10 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         6 my @relationship_types = @{$ontology->get_relationship_types()};
  6         17  
235 6         9 foreach my $relationship_type (@relationship_types){
236 4         7 my $relationship_type_id = $relationship_type->id();
237 4         9 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       8 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         7 $current_relationship_type->is_anonymous($relationship_type->is_anonymous());
240 4         9 foreach ($relationship_type->namespace()) {
241 0         0 $current_relationship_type->namespace($_);
242             }
243 4         11 foreach ($relationship_type->alt_id()->get_set()) {
244 0         0 $current_relationship_type->alt_id($_);
245             }
246 4         8 $current_relationship_type->builtin($relationship_type->builtin());
247 4 50 33     6 $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     10 $current_relationship_type->comment($relationship_type->comment()) if (!defined $current_relationship_type->comment() && $relationship_type->comment());
249 4         10 foreach ($relationship_type->subset()) {
250 0         0 $current_relationship_type->subset($_);
251             }
252 4         9 foreach ($relationship_type->synonym_set()) {
253 0         0 $current_relationship_type->synonym_set($_);
254             }
255 4         10 foreach ($relationship_type->xref_set()->get_set()) {
256 0         0 $current_relationship_type->xref_set()->add($_);
257             }
258 4         10 foreach ($relationship_type->domain()->get_set()) {
259 0         0 $current_relationship_type->domain($_);
260             }
261 4         10 foreach ($relationship_type->range()->get_set()) {
262 0         0 $current_relationship_type->range($_);
263             }
264 4         9 $current_relationship_type->is_anti_symmetric($relationship_type->is_anti_symmetric());
265 4         9 $current_relationship_type->is_cyclic($relationship_type->is_cyclic());
266 4         7 $current_relationship_type->is_reflexive($relationship_type->is_reflexive());
267 4         30 $current_relationship_type->is_symmetric($relationship_type->is_symmetric());
268 4         9 $current_relationship_type->is_transitive($relationship_type->is_transitive());
269            
270 4         8 $current_relationship_type->inverse_of($relationship_type->inverse_of());
271            
272 4         7 foreach ($relationship_type->transitive_over()->get_set()) {
273 0         0 $current_relationship_type->transitive_over($_);
274             }
275 4         9 foreach ($relationship_type->holds_over_chain()) {
276 0         0 $current_relationship_type->holds_over_chain(@{$_}[0], @{$_}[1]);
  0         0  
  0         0  
277             }
278 4         9 $current_relationship_type->is_functional($relationship_type->is_functional());
279 4         7 $current_relationship_type->is_inverse_functional($relationship_type->is_inverse_functional());
280 4         8 foreach ($relationship_type->intersection_of()) {
281 0         0 $current_relationship_type->intersection_of($_);
282             }
283 4         10 foreach ($relationship_type->union_of()) {
284 0         0 $current_relationship_type->union_of($_);
285             }
286 4         10 foreach ($relationship_type->disjoint_from()) {
287 0         0 $current_relationship_type->disjoint_from($_);
288             }
289 4         9 $current_relationship_type->created_by($relationship_type->created_by());
290 4         8 $current_relationship_type->creation_date($relationship_type->creation_date());
291 4         8 $current_relationship_type->modified_by($relationship_type->modified_by());
292 4         7 $current_relationship_type->modification_date($relationship_type->modification_date());
293 4         7 $current_relationship_type->is_obsolete($relationship_type->is_obsolete());
294 4         9 foreach ($relationship_type->replaced_by()->get_set()) {
295 0         0 $current_relationship_type->replaced_by($_);
296             }
297 4         9 foreach ($relationship_type->consider()->get_set()) {
298 0         0 $current_relationship_type->consider($_);
299             }
300 4         9 $current_relationship_type->is_metadata_tag($relationship_type->is_metadata_tag());
301 4         8 $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         15  
315 6         14 foreach my $term (@instances){
316             #TODO
317             }
318             }
319 3 50       7 $result->default_relationship_id_prefix($default_relationship_id_prefix) if (defined $default_relationship_id_prefix);
320 3 50       5 $result->default_namespace($default_namespace) if (defined $default_namespace);
321              
322 3         8 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 19 my ($self, $onto1, $onto2) = @_;
341 8         25 my $result = OBO::Core::Ontology->new();
342 8         22 $result->saved_by('ONTO-perl');
343 8         20 $result->default_relationship_id_prefix($onto1->default_relationship_id_prefix()); # use the default_relationship_id_prefix of the first argument
344 8         20 $result->default_namespace($onto1->default_namespace()); # use the default_namespace of the first argument
345 8         44 $result->remarks('Intersection of ontologies');
346            
347             #
348             # treat_xrefs_as_equivalent
349             #
350 8         20 my @txae1 = $onto1->treat_xrefs_as_equivalent->get_set();
351 8         19 my @txae2 = $onto2->treat_xrefs_as_equivalent->get_set();
352 8 50 33     25 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         20 my @txaia1 = $onto1->treat_xrefs_as_is_a->get_set();
364 8         19 my @txaia2 = $onto2->treat_xrefs_as_is_a->get_set();
365 8 50 33     23 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         19 $result->idspaces($onto1->idspaces()->get_set());
375 8         17 $result->idspaces($onto2->idspaces()->get_set());
376            
377 8         20 $result->subset_def_map($onto1->subset_def_map()); # add all subset_def_map's by default
378              
379 8         11 foreach my $term (@{$onto1->get_terms()}){
  8         19  
380 61         106 my $current_term = $onto2->get_term_by_id($term->id()); ### could also be $result->get_term_by_name_or_synonym()
381 61 100       101 if (defined $current_term) { # term intersection
382 48         76 $result->add_term($term); # added the term from onto2
383 48         86 foreach my $ins ($term->class_of()->get_set()) {
384 0         0 $result->add_instance($ins); # add its instances
385             }
386             }
387             }
388 8         29 my $onto1_number_relationships = $onto1->get_number_of_relationships();
389 8         16 my $onto2_number_relationships = $onto2->get_number_of_relationships();
390 8 100       17 my $min_number_rels_onto1_onto2 = ($onto1_number_relationships < $onto2_number_relationships)?$onto1_number_relationships:$onto2_number_relationships;
391            
392 8         11 my @terms = @{$result->get_terms()};
  8         17  
393            
394 8         25 my $stop = OBO::Util::Set->new();
395 8         14 map {$stop->add($_->id())} @terms;
  48         73  
396            
397             # path of references
398 8         10 my @pr1;
399             my @pr2;
400            
401             # link the common terms
402 8         9 foreach my $term (@terms) {
403 48         114 my $term_id = $term->id();
404            
405             #
406             # path of references: onto1 and onto2
407             #
408            
409             # onto1
410 48         101 my @pref1 = $onto1->get_paths_term_terms($term_id, $stop);
411 48         68 push @pr1, [@pref1];
412            
413             # onto2
414 48         112 my @pref2 = $onto2->get_paths_term_terms($term_id, $stop);
415 48         109 push @pr2, [@pref2];
416             }
417            
418             # pr1
419 8         11 my %cand;
420 8         13 foreach my $pref (@pr1) {
421 48         55 foreach my $ref (@$pref) {
422 177         291 my $type = @$ref[0]->type(); # first type
423 177         139 my $invalid = 0;
424 177         123 my $r_type;
425 177         160 foreach my $tt (@$ref) {
426 396         560 $r_type = $tt->type();
427 396 100       624 if ($type ne $r_type) {
428 93         66 $invalid = 1;
429 93         64 last; # no more walking
430             }
431             }
432 177 100       264 if (!$invalid) {
433 84         141 my $f = @$ref[0]->tail()->id();
434 84         156 my $l = @$ref[$#$ref]->head()->id();
435 84         167 $cand{$f.'->'.$r_type.'->'.$l} = 1; # there could be more than 1 path
436 84         121 $invalid = 0;
437             }
438             }
439             }
440              
441             # pr2
442 8         8 my %r_cand;
443 8         13 foreach my $pref (@pr2) {
444 48         52 foreach my $ref (@$pref) {
445 182         323 my $type = @$ref[0]->type(); # first type
446 182         137 my $invalid = 0;
447 182         114 my $r_type;
448 182         160 foreach my $tt (@$ref) {
449 399         534 $r_type = $tt->type();
450 399 100       622 if ($type ne $r_type) { # ONLY identical rel types in the path are admitted!!!
451             #warn 'INVALID REL: ', $tt->id();
452 101         72 $invalid = 1;
453 101         76 last; # no more walking
454             }
455             }
456 182 100       268 if (!$invalid) {
457 81         132 my $f = @$ref[0]->tail()->id();
458 81         150 my $l = @$ref[$#$ref]->head()->id();
459 81         121 $cand{$f.'->'.$r_type.'->'.$l}++;
460 81         135 $r_cand{$f.'->'.$l} = $r_type;
461 81         102 $invalid = 0;
462             }
463             }
464             }
465            
466             # cleaning candidates
467 8         47 foreach (sort keys (%cand)) {
468 84 100       127 delete $cand{$_} if ($cand{$_} < 2);
469             }
470            
471             # candidates simplified
472 8         11 my %cola;
473 8         17 foreach (keys (%cand)) {
474 67 50       294 my $f = $1, my $r = $2, my $l = $3 if ($_ =~ /(.*)->(.*)->(.*)/);
475 67         98 $cola{$f} .= $l.' '; # hold the candidates
476             }
477            
478             # transitive reduction
479 8         29 while ( my ($k, $v) = each(%cola)) {
480 30         68 my $V = OBO::Util::Set->new();
481 30         54 $V->add($v);
482            
483 30         60 my @T = split (' ', $v);
484 30         38 my %target = ();
485 30         52 my $r_type = $r_cand{$k.'->'.$T[$#T]}; # check
486            
487 30         55 while ($#T > -1) {
488 200         187 my $n = pop @T;
489 200         251 $target{$r_type.'->'.$n}++;
490 200 100       304 if (!$V->contains($n)) {
491 123         181 $V->add($n);
492 123 100       394 push @T, split(' ', $cola{$n}) if ($cola{$n});
493             }
494             }
495            
496 30         64 while (my ($t, $veces) = each(%target)) {
497 123 100       283 if ($veces > 1) { # if so, the delete $k->$t
498 53         150 delete $cand{$k.'->'.$t};
499             }
500             }
501             }
502            
503             # after 'transitive reduction' we have
504 8         22 while (my ($k, $v) = each(%cand)) {
505 43 50       253 my $s = $1, my $r_type = $2, my $t = $3 if ($k =~ /(.*)->(.*)->(.*)/);
506 43         87 my $source = $result->get_term_by_id($s);
507 43         72 my $target = $result->get_term_by_id($t);
508            
509 43 100       73 if (!($result->has_relationship_type_id($r_type))) {
510 9         24 $result->add_relationship_type_as_string($r_type, $r_type); # ID = NAME
511             }
512 43         106 $result->create_rel($source, $r_type, $target);
513             }
514 8         172 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 72 my ($self, $ontology, @trans_rts, $composition) = @_;
533            
534 1         3 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         5 my $result = OBO::Core::Ontology->new();
540 1         3 $result->saved_by('ONTO-perl');
541 1         3 $result->idspaces($ontology->idspaces()->get_set());
542 1         4 $result->default_relationship_id_prefix($ontology->default_relationship_id_prefix());
543 1         3 $result->default_namespace($ontology->default_namespace());
544 1         2 $result->remarks('Ontology with transitive closures');
545 1         3 $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         3 $result->subset_def_map($ontology->subset_def_map()); # add all subset_def_map's by default
548 1         3 $result->synonym_type_def_set($ontology->synonym_type_def_set()->get_set()); # add all synonym_type_def_set by default
549            
550 1         1 my @terms = @{$ontology->get_terms()};
  1         3  
551 1         3 foreach my $term (@terms) {
552 26         43 my $current_term = $result->get_term_by_id($term->id());
553 26 100       37 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     25 $current_term->is_anonymous(1) if (!defined $current_term->is_anonymous() && $term->is_anonymous());
555 13         25 foreach ($term->alt_id()->get_set()) {
556 0         0 $current_term->alt_id($_);
557             }
558 13 50 33     29 $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         27 foreach ($term->namespace()) {
560 0         0 $current_term->namespace($_);
561             }
562 13 50 33     25 $current_term->comment($term->comment()) if (!defined $current_term->comment() && $term->comment());
563 13         23 foreach ($term->subset()) {
564 0         0 $current_term->subset($_);
565             }
566 13         28 foreach ($term->synonym_set()) {
567 0         0 $current_term->synonym_set($_);
568             }
569 13         27 foreach ($term->xref_set()->get_set()) {
570 0         0 $current_term->xref_set()->add($_);
571             }
572 13         28 foreach ($term->intersection_of()) {
573 0         0 $current_term->intersection_of($_);
574             }
575 13         26 foreach ($term->union_of()) {
576 0         0 $current_term->union_of($_);
577             }
578 13         28 foreach ($term->disjoint_from()) {
579 0         0 $current_term->disjoint_from($_);
580             }
581 13 50 33     27 $current_term->is_obsolete(1) if (!defined $current_term->is_obsolete() && $term->is_obsolete());
582 13         24 foreach ($term->replaced_by()->get_set()) {
583 0         0 $current_term->replaced_by($_);
584             }
585 13         26 foreach ($term->consider()->get_set()) {
586 0         0 $current_term->consider($_);
587             }
588 13 50 33     26 $current_term->builtin(1) if (!defined $current_term->builtin() && $term->builtin());
589            
590             # fix the rel's
591 13         13 my @rels = @{$ontology->get_relationships_by_target_term($term)};
  13         27  
592 13         16 foreach my $r (@rels) {
593 15         32 my $cola = $r->tail();
594 15         28 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         30 my $tail = $result->get_term_by_id($cola_id); # Is $cola already present in the growing ontology?
599 15 50       37 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         22 my $r_type = $r->type();
610            
611             #
612             # relationship type
613             #
614 15         27 my $rel_type = $ontology->get_relationship_type_by_id($r_type);
615 15 100       25 $result->has_relationship_type($rel_type) || $result->add_relationship_type($rel_type);
616            
617 15         40 $r->id($cola_id.'_'.$r_type.'_'.$current_term->id());
618 15         29 $r->link($tail, $current_term);
619            
620             # add the relationship after adding its type
621 15         27 $result->add_relationship($r);
622             }
623             } else {
624 13         23 $result->add_term($term);
625 13         22 foreach my $ins ($term->class_of()->get_set()) {
626 0         0 $result->add_instance($ins); # add its instances
627             }
628 13         21 push @terms, $term; # trick to 'recursively' visit the just added term
629             }
630             }
631 1         2 foreach my $rel (@{$ontology->get_relationships()}) {
  1         4  
632 15 50       24 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         2 @terms = @{$result->get_terms()}; # set 'terms' (avoding the pushed ones)
  1         3  
641            
642 1         4 my $stop = OBO::Util::Set->new();
643 1         2 map { $stop->add($_->id()) } @terms;
  13         18  
644              
645             #print STDERR "\nNUMBER OF RELS BEFORE = ", $result->get_number_of_relationships();
646              
647             # link the common terms
648 1         2 foreach my $term (@terms) {
649 13         25 my $term_id = $term->id();
650             # path of references:
651 13         15 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         50 my @ref_paths = $ontology->get_paths_term_terms_same_rel($term_id, $stop, $type_of_rel);
656              
657 26         39 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         51 my $f = @$ref_path[0]->tail();
660 23         41 my $l = @$ref_path[$#$ref_path]->head();
661 23         45 $result->create_rel($f, $type_of_rel, $l); # add the transitive closure relationship!
662            
663 23         38 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         2 $composition = 1; # experimental code: ENABLED !!!!!!!!!!!!!!!!!!!!
673            
674 1 50       4 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       338 next if ($term1_id eq $term2_id); # reflexive relationships are skipped
691            
692 156         327 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         290 foreach my $ref_path (@ref_paths) {
696            
697 235 100       439 next if !defined @$ref_path[0];
698 226 100       416 next if !defined @$ref_path[1]; # two elements (at least) are needed to make the composition
699            
700 183         340 my $left_entry = @$ref_path[0]->tail();
701 183         351 my $left_type = @$ref_path[0]->type();
702 183         337 my $right_entry = @$ref_path[1]->head();
703 183         312 my $right_type = @$ref_path[1]->type();
704            
705 183 100       274 if ($left_type eq $right_type) {
706            
707 68         129 my $new_rel_id = $left_entry->id().'_'.$left_type.'_'.$right_entry->id();
708 68 100       133 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         168 my $new_rel_id = $left_entry->id()."_part_of_".$right_entry->id();
714            
715 115 100       229 if (!$result->has_relationship_id($new_rel_id)) {
716 28         72 $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         2 @terms = @{$result->get_terms()}; # set 'terms' (avoding the pushed ones)
  1         3  
  1         5  
732            
733 1         9 my $stop = OBO::Util::Set->new();
734 1         3 map {$stop->add($_->id())} @terms;
  13         20  
735            
736             # link the common terms
737 1         3 foreach my $term (@terms) {
738 13         48 my $term_id = $term->id();
739             # path of references:
740 13         18 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         50 my @ref_paths = $result->get_paths_term_terms_same_rel($term_id, $stop, $type_of_rel);
745            
746 26         35 foreach my $ref_path (@ref_paths) {
747 140 50       238 next if !defined @$ref_path[0]; # reflexive relationships (e.g. GO:0000011_is_a_GO:0000011) are problematic...
748 140         231 my $f = @$ref_path[0]->tail();
749 140         237 my $l = @$ref_path[$#$ref_path]->head();
750 140         216 my $new_rel_id = $f->id().'_'.$type_of_rel.'_'.$l->id();
751            
752 140 100       235 if (!$result->has_relationship_id($new_rel_id)) {
753 9         23 $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         3 if (1) {
767 1         1 @terms = @{$result->get_terms()}; # set 'terms' (avoding the pushed ones)
  1         4  
768            
769 1         7 my $stop = OBO::Util::Set->new();
770 1         2 map {$stop->add($_->id())} @terms;
  13         21  
771            
772 1         2 foreach my $term (@terms) {
773 13         32 my $term1_id = $term->id();
774            
775 13         64 foreach my $term2_id ($stop->get_set()) {
776            
777 169 100       332 next if ($term1_id eq $term2_id); # reflexive relationships are skipped
778            
779 156         337 my @ref_paths = $result->get_paths_term1_term2($term1_id, $term2_id);
780            
781 156         304 foreach my $ref_path (@ref_paths) {
782            
783 347 50       578 next if !defined @$ref_path[0];
784 347 100       577 next if !defined @$ref_path[1]; # two elements (at least) are needed to make the composition
785            
786 284         484 my $left_entry = @$ref_path[0]->tail();
787 284         536 my $left_type = @$ref_path[0]->type();
788 284         489 my $right_entry = @$ref_path[1]->head();
789 284         451 my $right_type = @$ref_path[1]->type();
790            
791             #next if ($left_type eq $right_type); # done above already
792 284 100       411 if ($left_type eq $right_type) {
793            
794 104         152 my $new_rel_id = $left_entry->id().'_'.$left_type.'_'.$right_entry->id();
795 104 50       191 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         299 my $new_rel_id = $left_entry->id()."_part_of_".$right_entry->id();
801            
802 180 50       353 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         9 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 11 my ($self, $ontology, @trans_rts) = @_;
832 2         5 my @default_trans_rts = ('is_a', 'part_of', 'located_in');
833 2 50       7 if (scalar @trans_rts > 0) {
834 0         0 @default_trans_rts = @trans_rts;
835             }
836            
837 2         10 my $result = OBO::Core::Ontology->new();
838 2         8 $result->saved_by('ONTO-perl');
839 2         7 $result->idspaces($ontology->idspaces()->get_set());
840 2         8 $result->default_relationship_id_prefix($ontology->default_relationship_id_prefix());
841 2         5 $result->default_namespace($ontology->default_namespace());
842 2         6 $result->remarks('Ontology with transitive reduction');
843 2         6 $result->treat_xrefs_as_equivalent($ontology->treat_xrefs_as_equivalent->get_set()); # treat-xrefs-as-equivalent
844 2         7 $result->treat_xrefs_as_is_a($ontology->treat_xrefs_as_is_a->get_set()); # treat_xrefs_as_is_a
845 2         5 $result->subset_def_map($ontology->subset_def_map()); # add all subset_def_map's by default
846 2         5 $result->synonym_type_def_set($ontology->synonym_type_def_set()->get_set()); # add all synonym_type_def_set by default
847            
848 2         4 my @terms = @{$ontology->get_terms()};
  2         6  
849 2         6 foreach my $term (@terms) {
850 52         95 my $current_term = $result->get_term_by_id($term->id());
851 52 100       69 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     50 $current_term->is_anonymous(1) if (!defined $current_term->is_anonymous() && $term->is_anonymous());
853 26         52 foreach ($term->alt_id()->get_set()) {
854 0         0 $current_term->alt_id($_);
855             }
856 26 50 33     62 $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         50 foreach ($term->namespace()) {
858 0         0 $current_term->namespace($_);
859             }
860 26 50 33     54 $current_term->comment($term->comment()) if (!defined $current_term->comment() && $term->comment());
861 26         49 foreach ($term->subset()) {
862 0         0 $current_term->subset($_);
863             }
864 26         51 foreach ($term->synonym_set()) {
865 0         0 $current_term->synonym_set($_);
866             }
867 26         53 foreach ($term->xref_set()->get_set()) {
868 0         0 $current_term->xref_set()->add($_);
869             }
870 26         54 foreach ($term->intersection_of()) {
871 0         0 $current_term->intersection_of($_);
872             }
873 26         51 foreach ($term->union_of()) {
874 0         0 $current_term->union_of($_);
875             }
876 26         51 foreach ($term->disjoint_from()) {
877 0         0 $current_term->disjoint_from($_);
878             }
879 26 50 33     48 $current_term->is_obsolete(1) if (!defined $current_term->is_obsolete() && $term->is_obsolete());
880 26         50 foreach ($term->replaced_by()->get_set()) {
881 0         0 $current_term->replaced_by($_);
882             }
883 26         53 foreach ($term->consider()->get_set()) {
884 0         0 $current_term->consider($_);
885             }
886 26 50 33     46 $current_term->builtin(1) if (!defined $current_term->builtin() && $term->builtin());
887            
888             # fix the rel's
889 26         259 my @rels = @{$ontology->get_relationships_by_target_term($term)};
  26         62  
890 26         84 foreach my $r (@rels) {
891 78         143 my $cola = $r->tail();
892 78         123 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         137 my $tail = $result->get_term_by_id($cola_id); # Is $cola already present in the growing ontology?
897 78 50       121 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         118 my $r_type = $r->type();
909            
910             #
911             # relationship type
912             #
913 78         138 my $rel_type = $ontology->get_relationship_type_by_id($r_type);
914 78 100       125 $result->has_relationship_type($rel_type) || $result->add_relationship_type($rel_type);
915              
916             # add the relationship after adding its type
917 78         273 $r->id($cola_id.'_'.$r_type.'_'.$current_term->id());
918 78         141 $r->link($tail, $current_term);
919 78         139 $result->add_relationship($r);
920             }
921             } else {
922 26         39 $result->add_term($term);
923 26         42 foreach my $ins ($term->class_of()->get_set()) {
924 0         0 $result->add_instance($ins); # add its instances
925             }
926 26         45 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         8  
934 78 50       114 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         25 foreach my $rel_type ( @{$ontology->get_relationship_types_sorted_by_id()} ) {
  2         9  
947 4 50       7 $result->has_relationship_type($rel_type) || $result->add_relationship_type($rel_type);
948             }
949              
950 2         2 @terms = @{$result->get_terms()}; # set 'terms' (avoding the pushed ones)
  2         6  
951              
952 2         9 my $stop = OBO::Util::Set->new();
953 2         4 map {$stop->add($_->id())} @terms;
  26         40  
954              
955             # delete implicit rel's
956 2         4 foreach my $term (@terms) {
957 26         38 my $term_id = $term->id();
958             # path of references:
959 26         26 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         136 my @ref_paths = $result->get_paths_term_terms_same_rel($term_id, $stop, $type_of_rel);
964              
965 78         136 foreach my $ref_path (@ref_paths) {
966 167 50       264 next if !defined @$ref_path[0];
967 167         145 my $i = $#$ref_path;
968 167         285 my $f = @$ref_path[0]->tail();
969 167         270 my $l = @$ref_path[$i]->head();
970 167         263 my $v = $result->get_relationship_by_id($f->id().'_'.$type_of_rel.'_'.$l->id());
971            
972 167 100 100     511 if ($v && ($i > 0)) {
973 33         65 $result->delete_relationship($v);
974             }
975             }
976             }
977             }
978            
979             # delete compositon of rel's
980 2         4 foreach my $term (@terms) {
981 26         88 my $term_id = $term->id();
982 26         127 foreach my $term2_id ($stop->get_set()) {
983 338 100       573 next if ($term_id eq $term2_id); # reflexive
984 312         565 my @ref_paths = $result->get_paths_term1_term2($term_id, $term2_id);
985            
986 312         525 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       734 next if (!$result->has_relationship_id($rel_id));
989            
990 29         58 foreach my $ref_path (@ref_paths) {
991 68 50       139 next if !defined @$ref_path[0]; # reflexive relationships (e.g. GO:0000011_is_a_GO:0000011) are problematic...
992 68 100       160 next if !defined @$ref_path[1]; # two elements (at least) are needed to make the composition
993            
994 39         94 my $left_entry = @$ref_path[0]->tail();
995 39         94 my $left_type = @$ref_path[0]->type();
996 39         55 my $i = $#$ref_path;
997 39         75 my $right_entry = @$ref_path[$i]->head();
998 39         65 my $right_type = @$ref_path[$i]->type();
999            
1000             #next if ($left_type eq $right_type);
1001              
1002 39         73 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       72 if ($result->has_relationship_id($new_rel_id)) {
1005 15         54 my $v = $result->get_relationship_by_id($new_rel_id);
1006 15         45 $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__