3 # BioPerl module for Bio::Ontology::SimpleOntologyEngine
5 # Cared for by Peter Dimitrov <dimitrov@gnf.org>
7 # Copyright Peter Dimitrov
8 # (c) Peter Dimitrov, dimitrov@gnf.org, 2002.
9 # (c) GNF, Genomics Institute of the Novartis Research Foundation, 2002.
11 # You may distribute this module under the same terms as perl itself.
12 # Refer to the Perl Artistic License (see the license accompanying this
13 # software package, or see http://www.perl.com/language/misc/Artistic.html)
14 # for the terms under which you may use, modify, and redistribute this module.
16 # THIS PACKAGE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR IMPLIED
17 # WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF
18 # MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
20 # POD documentation - main docs before the code
24 Bio::Ontology::SimpleOntologyEngine - Implementation of OntologyEngineI interface
28 my $soe = Bio::Ontology::SimpleOntologyEngine->new;
32 This is a "simple" implementation of Bio::Ontology::OntologyEngineI.
38 User feedback is an integral part of the evolution of this and other
39 Bioperl modules. Send your comments and suggestions preferably to
40 the Bioperl mailing list. Your participation is much appreciated.
42 bioperl-l@bioperl.org - General discussion
43 http://bioperl.org/wiki/Mailing_lists - About the mailing lists
47 Report bugs to the Bioperl bug tracking system to help us keep track
48 of the bugs and their resolution. Bug reports can be submitted via
51 http://bugzilla.open-bio.org/
53 =head1 AUTHOR - Peter Dimitrov
55 Email dimitrov@gnf.org
59 Hilmar Lapp, hlapp at gmx.net
63 The rest of the documentation details each of the object methods.
64 Internal methods are usually preceded with a _
69 # Let the code begin...
72 package Bio
::Ontology
::SimpleOntologyEngine
;
75 use Bio
::Ontology
::RelationshipFactory
;
78 use base
qw(Bio::Root::Root Bio::Ontology::OntologyEngineI);
83 Usage : $soe = Bio::Ontology::SimpleOntologyEngine->new;
84 Function: Initializes the ontology engine.
85 Example : $soe = Bio::Ontology::SimpleOntologyEngine->new;
86 Returns : Object of class SimpleOntologyEngine.
92 my ($class, @args) = @_;
93 my $self = $class->SUPER::new
(@args);
96 $self->_term_store( {} );
97 $self->_relationship_store( {} );
98 $self->_inverted_relationship_store( {} );
99 $self->_relationship_type_store( {} );
100 $self->_instantiated_terms_store( {} );
102 # set defaults for the factories
103 $self->relationship_factory(Bio
::Ontology
::RelationshipFactory
->new(
104 -type
=> "Bio::Ontology::Relationship"));
108 =head2 _instantiated_terms_store
110 Title : _instantiated_terms_store
111 Usage : $obj->_instantiated_terms_store($newval)
119 sub _instantiated_terms_store
{
120 my ($self, $value) = @_;
122 if( defined $value) {
123 $self->{'_instantiated_terms_store'} = $value;
125 return $self->{'_instantiated_terms_store'};
128 =head2 mark_instantiated
130 Title : mark_instantiated
131 Usage : $self->mark_instantiated(TermI terms): TermI
132 Function: Marks TermI objects as fully instantiated,
133 allowing for proper counting of the number of terms in the term store.
134 The TermI objects has to be already stored in the term store in order
136 Example : $self->mark_instantiated($term);
137 Returns : its argument or throws an exception if a term is not
139 Args : array of objects of class TermI.
143 sub mark_instantiated
{
144 my ($self, @terms) = @_;
146 foreach my $term (@terms) {
147 $self->throw( "term ".$term->identifier." not in the term store\n" )
148 if !defined $self->_term_store->{$term->identifier};
149 $self->_instantiated_terms_store->{$term->identifier} = 1;
155 =head2 mark_uninstantiated
157 Title : mark_uninstantiated
158 Usage : $self->mark_uninstantiated(TermI terms): TermI
159 Function: Marks TermI objects as not fully instantiated,
160 Example : $self->mark_uninstantiated($term);
161 Returns : its argument or throws an exception if a term is not
162 in the term store(if the term is not marked it does nothing).
163 Args : array of objects of class TermI.
167 sub mark_uninstantiated
{
168 my ($self, @terms) = @_;
170 foreach my $term (@terms) {
171 $self->throw( "term ".$term->identifier." not in the term store\n" )
172 if !defined $self->_term_store->{$term->identifier};
173 delete $self->_instantiated_terms_store->{$term->identifier}
174 if defined $self->_instantiated_terms_store->{$term->identifier};
183 Usage : $obj->_term_store($newval)
186 Returns : reference to an array of Bio::Ontology::TermI objects
187 Args : reference to an array of Bio::Ontology::TermI objects
192 my ($self, $value) = @_;
194 if( defined $value) {
195 if ( defined $self->{'_term_store'}) {
196 $self->throw("_term_store already defined\n");
199 $self->{'_term_store'} = $value;
203 return $self->{'_term_store'};
209 Usage : add_term(TermI term): TermI
210 Function: Adds TermI object to the ontology engine term store.
211 Marks the term fully instantiated by default.
212 Example : $soe->add_term($term)
213 Returns : its argument.
214 Args : object of class TermI.
219 my ($self, $term) = @_;
220 my $term_store = $self->_term_store;
222 if ( defined $term_store -> {$term->identifier}) {
223 $self->throw( "term ".$term->identifier." already defined\n" );
226 $term_store->{$term->identifier} = $term;
227 $self->_instantiated_terms_store->{$term->identifier} = 1;
233 =head2 get_term_by_identifier
235 Title : get_term_by_identifier
236 Usage : get_term_by_identifier(String id): TermI
237 Function: Retrieves terms from the term store by their identifier
238 field, or an empty list if not there.
239 Example : $term = $soe->get_term_by_identifier("IPR000001");
240 Returns : An array of zero or more Bio::Ontology::TermI objects.
241 Args : An array of identifier strings
245 sub get_term_by_identifier
{
246 my ($self, @ids) = @_;
249 foreach my $id (@ids) {
250 my $term = $self->_term_store->{$id};
251 push @ans, $term if defined $term;
257 =head2 _get_number_rels
259 Title : get_number_rels
268 sub _get_number_rels
{
272 foreach my $entry ($self->_relationship_store) {
273 $num_rels += scalar keys %$entry;
278 =head2 _get_number_terms
280 Title : _get_number_terms
289 sub _get_number_terms
{
292 return scalar $self->_filter_unmarked( values %{$self->_term_store} );
296 =head2 _relationship_store
298 Title : _storerelationship_store
299 Usage : $obj->relationship_store($newval)
302 Returns : reference to an array of Bio::Ontology::TermI objects
303 Args : reference to an array of Bio::Ontology::TermI objects
307 sub _relationship_store
{
308 my ($self, $value) = @_;
310 if( defined $value) {
311 if ( defined $self->{'_relationship_store'}) {
312 $self->throw("_relationship_store already defined\n");
315 $self->{'_relationship_store'} = $value;
319 return $self->{'_relationship_store'};
322 =head2 _inverted_relationship_store
324 Title : _inverted_relationship_store
328 Returns : reference to an array of Bio::Ontology::TermI objects
329 Args : reference to an array of Bio::Ontology::TermI objects
333 sub _inverted_relationship_store
{
334 my ($self, $value) = @_;
336 if( defined $value) {
337 if ( defined $self->{'_inverted_relationship_store'}) {
338 $self->throw("_inverted_relationship_store already defined\n");
341 $self->{'_inverted_relationship_store'} = $value;
345 return $self->{'_inverted_relationship_store'};
348 =head2 _relationship_type_store
350 Title : _relationship_type_store
351 Usage : $obj->_relationship_type_store($newval)
354 Returns : reference to an array of Bio::Ontology::RelationshipType objects
355 Args : reference to an array of Bio::Ontology::RelationshipType objects
359 sub _relationship_type_store
{
360 my ($self, $value) = @_;
362 if( defined $value) {
363 if ( defined $self->{'_relationship_type_store'}) {
364 $self->throw("_relationship_type_store already defined\n");
367 $self->{'_relationship_type_store'} = $value;
371 return $self->{'_relationship_type_store'};
374 =head2 _add_relationship_simple
376 Title : _add_relationship_simple
385 sub _add_relationship_simple
{
386 my ($self, $store, $rel, $inverted) = @_;
391 $parent_id = $rel->subject_term->identifier;
392 $child_id = $rel->object_term->identifier;
395 $parent_id = $rel->object_term->identifier;
396 $child_id = $rel->subject_term->identifier;
398 if(defined $store->{$parent_id} && (defined $store->{$parent_id}->{$child_id}) &&
399 ($store->{$parent_id}->{$child_id}->name != $rel->predicate_term->name)){
400 $self->throw("relationship ".Dumper
($rel->predicate_term).
401 " between ".$parent_id." and ".$child_id.
402 " already defined as ".
403 Dumper
($store->{$parent_id}->{$child_id})."\n");
406 $store->{$parent_id}->{$child_id} = $rel->predicate_term;
410 =head2 add_relationship
412 Title : add_relationship
413 Usage : add_relationship(RelationshipI relationship): RelationshipI
414 Function: Adds a relationship object to the ontology engine.
416 Returns : Its argument.
417 Args : A RelationshipI object.
421 sub add_relationship
{
422 my ($self, $rel) = @_;
424 $self->_add_relationship_simple($self->_relationship_store,
426 $self->_add_relationship_simple($self->_inverted_relationship_store,
428 $self->_relationship_type_store->{
429 $self->_unique_termid($rel->predicate_term)} = $rel->predicate_term;
434 =head2 get_relationships
436 Title : get_relationships
437 Usage : get_relationships(): RelationshipI
438 Function: Retrieves all relationship objects.
440 Returns : Array of RelationshipI objects
445 sub get_relationships
{
449 my $store = $self->_relationship_store;
450 my $relfact = $self->relationship_factory();
452 my @parent_ids = $term ?
453 # if a term is supplied then only get the term's parents
454 (map { $_->identifier(); } $self->get_parent_terms($term)) :
455 # otherwise use all parent ids
457 # add the term as a parent too if one is supplied
458 push(@parent_ids,$term->identifier) if $term;
460 foreach my $parent_id (@parent_ids) {
461 my $parent_entry = $store->{$parent_id};
463 # if a term is supplied, add a relationship for the parent to the term
464 # except if the parent is the term itself (we added that one before)
465 if($term && ($parent_id ne $term->identifier())) {
466 my @parent_terms = $self->get_term_by_identifier($parent_id);
467 foreach my $parent_term (@parent_terms) {
469 $relfact->create_object(-object_term
=> $parent_term,
470 -subject_term
=> $term,
472 $parent_entry->{$term->identifier},
473 -ontology
=> $term->ontology())
478 # otherwise, i.e., no term supplied, or the parent equals the
480 my @parent_terms = $term ?
481 ($term) : $self->get_term_by_identifier($parent_id);
482 foreach my $child_id (keys %$parent_entry) {
483 my $rel_info = $parent_entry->{$child_id};
484 my ($subj_term) = $self->get_term_by_identifier($child_id);
486 foreach my $parent_term (@parent_terms) {
488 $relfact->create_object(-object_term
=> $parent_term,
489 -subject_term
=> $subj_term,
490 -predicate_term
=> $rel_info,
491 -ontology
=>$parent_term->ontology
502 =head2 get_all_relationships
504 Title : get_all_relationships
505 Usage : get_all_relationships(): RelationshipI
506 Function: Retrieves all relationship objects.
508 Returns : Array of RelationshipI objects
513 sub get_all_relationships
{
514 return shift->get_relationships();
517 =head2 get_predicate_terms
519 Title : get_predicate_terms
520 Usage : get_predicate_terms(): TermI
521 Function: Retrives all relationship types stored in the engine
523 Returns : reference to an array of Bio::Ontology::RelationshipType objects
528 sub get_predicate_terms
{
531 return values %{$self->_relationship_type_store};
546 my ($self, $term, @rel_types) = @_;
548 foreach my $rel_type (@rel_types) {
549 if($rel_type->identifier || $term->identifier) {
550 return 1 if $rel_type->identifier eq $term->identifier;
552 return 1 if $rel_type->name eq $term->name;
559 =head2 _typed_traversal
561 Title : _typed_traversal
570 sub _typed_traversal
{
571 my ($self, $rel_store, $level, $term_id, @rel_types) = @_;
572 return if !defined($rel_store->{$term_id});
573 my %parent_entry = %{$rel_store->{$term_id}};
574 my @children = keys %parent_entry;
578 if (@rel_types > 0) {
581 foreach my $child_id (@children) {
583 if $self->_is_rel_type( $rel_store->{$term_id}->{$child_id},
593 foreach my $child_id (@ans) {
594 push @ans1, $self->_typed_traversal($rel_store,
595 $level - 1, $child_id, @rel_types)
596 if defined $rel_store->{$child_id};
604 =head2 get_child_terms
606 Title : get_child_terms
607 Usage : get_child_terms(TermI term, TermI predicate_terms): TermI
608 get_child_terms(TermI term, RelationshipType predicate_terms): TermI
609 Function: Retrieves all child terms of a given term, that satisfy a
610 relationship among those that are specified in the second
611 argument or undef otherwise. get_child_terms is a special
612 case of get_descendant_terms, limiting the search to the
615 Returns : Array of TermI objects.
616 Args : First argument is the term of interest, second is the list of
617 relationship type terms.
622 my ($self, $term, @relationship_types) = @_;
624 $self->throw("must provide TermI compliant object")
625 unless defined($term) && $term->isa("Bio::Ontology::TermI");
627 return $self->_filter_unmarked(
628 $self->get_term_by_identifier(
629 $self->_typed_traversal($self->_relationship_store,
632 @relationship_types) ) );
635 =head2 get_descendant_terms
637 Title : get_descendant_terms
638 Usage : get_descendant_terms(TermI term, TermI rel_types): TermI
639 get_child_terms(TermI term, RelationshipType predicate_terms): TermI
640 Function: Retrieves all descendant terms of a given term, that
641 satisfy a relationship among those that are specified in
642 the second argument or undef otherwise. Uses
643 _typed_traversal to find all descendants.
646 Returns : Array of TermI objects.
647 Args : First argument is the term of interest, second is the list of
648 relationship type terms.
652 sub get_descendant_terms
{
653 my ($self, $term, @relationship_types) = @_;
655 $self->throw("must provide TermI compliant object")
656 unless defined($term) && $term->isa("Bio::Ontology::TermI");
658 return $self->_filter_unmarked(
659 $self->_filter_repeated(
660 $self->get_term_by_identifier(
661 $self->_typed_traversal($self->_relationship_store,
664 @relationship_types) ) ) );
667 =head2 get_parent_terms
669 Title : get_parent_terms
670 Usage : get_parent_terms(TermI term, TermI predicate_terms): TermI
671 get_child_terms(TermI term, RelationshipType predicate_terms): TermI
672 Function: Retrieves all parent terms of a given term, that satisfy a
673 relationship among those that are specified in the second
674 argument or undef otherwise. get_parent_terms is a special
675 case of get_ancestor_terms, limiting the search to the
679 Returns : Array of TermI objects.
680 Args : First argument is the term of interest, second is the list of relationship type terms.
684 sub get_parent_terms
{
685 my ($self, $term, @relationship_types) = @_;
686 $self->throw("term must be a valid object, not undef") unless defined $term;
688 return $self->_filter_unmarked(
689 $self->get_term_by_identifier(
690 $self->_typed_traversal($self->_inverted_relationship_store,
693 @relationship_types) ) );
696 =head2 get_ancestor_terms
698 Title : get_ancestor_terms
699 Usage : get_ancestor_terms(TermI term, TermI predicate_terms): TermI
700 get_child_terms(TermI term, RelationshipType predicate_terms): TermI
701 Function: Retrieves all ancestor terms of a given term, that satisfy
702 a relationship among those that are specified in the second
703 argument or undef otherwise. Uses _typed_traversal to find
707 Returns : Array of TermI objects.
708 Args : First argument is the term of interest, second is the list
709 of relationship type terms.
713 sub get_ancestor_terms
{
714 my ($self, $term, @relationship_types) = @_;
715 $self->throw("term must be a valid object, not undef") unless defined $term;
717 return $self->_filter_unmarked(
718 $self->_filter_repeated(
719 $self->get_term_by_identifier(
720 $self->_typed_traversal($self->_inverted_relationship_store,
723 @relationship_types) ) ) );
726 =head2 get_leaf_terms
728 Title : get_leaf_terms
729 Usage : get_leaf_terms(): TermI
730 Function: Retrieves all leaf terms from the ontology. Leaf term is a term w/o descendants.
731 Example : @leaf_terms = $obj->get_leaf_terms()
732 Returns : Array of TermI objects.
741 foreach my $term (values %{$self->_term_store}) {
742 push @leaf_terms, $term
743 if !defined $self->_relationship_store->{$term->identifier} &&
744 defined $self->_instantiated_terms_store->{$term->identifier};
750 =head2 get_root_terms
752 Title : get_root_terms
753 Usage : get_root_terms(): TermI
754 Function: Retrieves all root terms from the ontology. Root term is a term w/o descendants.
755 Example : @root_terms = $obj->get_root_terms()
756 Returns : Array of TermI objects.
765 foreach my $term (values %{$self->_term_store}) {
766 push @root_terms, $term
767 if !defined $self->_inverted_relationship_store->{$term->identifier} &&
768 defined $self->_instantiated_terms_store->{$term->identifier};
774 =head2 _filter_repeated
776 Title : _filter_repeated
777 Usage : @lst = $self->_filter_repeated(@old_lst);
778 Function: Removes repeated terms
780 Returns : List of unique TermI objects
781 Args : List of TermI objects
785 sub _filter_repeated
{
786 my ($self, @args) = @_;
789 foreach my $element (@args) {
790 $h{$element->identifier} = $element if !defined $h{$element->identifier};
798 Title : get_all_terms
799 Usage : get_all_terms(): TermI
800 Function: Retrieves all terms currently stored in the ontology.
801 Example : @all_terms = $obj->get_all_terms()
802 Returns : Array of TermI objects.
810 return $self->_filter_unmarked( values %{$self->_term_store} );
816 Usage : ($term) = $oe->find_terms(-identifier => "SO:0000263");
817 Function: Find term instances matching queries for their attributes.
819 This implementation can efficiently resolve queries by
823 Returns : an array of zero or more Bio::Ontology::TermI objects
824 Args : Named parameters. The following parameters should be recognized
825 by any implementations:
827 -identifier query by the given identifier
828 -name query by the given name
833 my ($self,@args) = @_;
836 my ($id,$name) = $self->_rearrange([qw(IDENTIFIER NAME)],@args);
839 @terms = $self->get_term_by_identifier($id);
841 @terms = $self->get_all_terms();
844 @terms = grep { $_->name() eq $name; } @terms;
850 =head2 relationship_factory
852 Title : relationship_factory
853 Usage : $fact = $obj->relationship_factory()
854 Function: Get/set the object factory to be used when relationship
855 objects are created by the implementation on-the-fly.
858 Returns : value of relationship_factory (a Bio::Factory::ObjectFactoryI
860 Args : on set, a Bio::Factory::ObjectFactoryI compliant object
864 sub relationship_factory
{
867 return $self->{'relationship_factory'} = shift if @_;
868 return $self->{'relationship_factory'};
874 Usage : $fact = $obj->term_factory()
875 Function: Get/set the object factory to be used when term objects are
876 created by the implementation on-the-fly.
878 Note that this ontology engine implementation does not
879 create term objects on the fly, and therefore setting this
880 attribute is meaningless.
883 Returns : value of term_factory (a Bio::Factory::ObjectFactoryI
885 Args : on set, a Bio::Factory::ObjectFactoryI compliant object
893 $self->warn("setting term factory, but ".ref($self).
894 " does not create terms on-the-fly");
895 return $self->{'term_factory'} = shift;
897 return $self->{'term_factory'};
900 =head2 _filter_unmarked
902 Title : _filter_unmarked
903 Usage : _filter_unmarked(TermI terms): TermI
904 Function: Removes the uninstantiated terms from the list of terms
906 Returns : array of fully instantiated TermI objects
907 Args : array of TermI objects
911 sub _filter_unmarked
{
912 my ($self, @terms) = @_;
913 my @filtered_terms = ();
915 if ( scalar(@terms) >= 1) {
916 foreach my $term (@terms) {
917 push @filtered_terms, $term
918 if defined $self->_instantiated_terms_store->{$term->identifier};
922 return @filtered_terms;
925 =head2 remove_term_by_id
927 Title : remove_term_by_id
928 Usage : remove_term_by_id(String id): TermI
929 Function: Removes TermI object from the ontology engine using the
930 string id as an identifier. Current implementation does not
931 enforce consistency of the relationships using that term.
932 Example : $term = $soe->remove_term_by_id($id);
933 Returns : Object of class TermI or undef if not found.
934 Args : The string identifier of a term.
938 sub remove_term_by_id
{
939 my ($self, $id) = @_;
941 if ( $self->get_term_by_identifier($id) ) {
942 my $term = $self->{_term_store
}->{$id};
943 delete $self->{_term_store
}->{$id};
947 $self->warn("Term with id '$id' is not in the term store");
955 Usage : print $sv->to_string();
956 Function: Currently returns formatted string containing the number of
957 terms and number of relationships from the ontology engine.
958 Example : print $sv->to_string();
968 $s .= "-- # Terms:\n";
969 $s .= scalar($self->get_all_terms)."\n";
970 $s .= "-- # Relationships:\n";
971 $s .= $self->_get_number_rels."\n";
976 =head2 _unique_termid
978 Title : _unique_termid
980 Function: Returns a string that can be used as ID using fail-over
983 If the identifier attribute is not set, it uses the
984 combination of name and ontology name, provided both are
985 set. If they are not, it returns the name alone.
987 Note that this is a private method. Call from inheriting
988 classes but not from outside.
992 Args : a Bio::Ontology::TermI compliant object
1000 return $term->identifier() if $term->identifier();
1001 my $id = $term->ontology->name() if $term->ontology();
1007 $id .= $term->name();
1011 #################################################################
1013 #################################################################
1015 *get_relationship_types
= \
&get_predicate_terms
;