From 220d52faf99acb1f8dbb4afe8a62476b0f925850 Mon Sep 17 00:00:00 2001 From: Oliver Maus Date: Thu, 31 Jul 2025 15:46:58 +0200 Subject: [PATCH 01/14] Add .ContainsTerm functionality --- src/Ontology.NET/Ontology.fs | 15 +++++++++++++++ tests/Ontology.NET.Tests/Ontology.Tests.fs | 12 ++++++++++++ 2 files changed, 27 insertions(+) diff --git a/src/Ontology.NET/Ontology.fs b/src/Ontology.NET/Ontology.fs index 972142a..517b1ff 100644 --- a/src/Ontology.NET/Ontology.fs +++ b/src/Ontology.NET/Ontology.fs @@ -127,6 +127,13 @@ type Ontology() = member this.AddTerm(term : CvTerm) = FGraph.addNode term.Accession term this :?> Ontology + /// + /// Checks if a term exists under the given ID. + /// + /// The ID of the term whose presence in the Ontology shall be checked. + member this.ContainsTerm(termId) = + FGraph.containsNode termId this + /// /// Returns the CvTerm under the given term ID. /// @@ -835,6 +842,14 @@ type Ontology() = static member addTerm term (onto : Ontology) = onto.AddTerm(term) + /// + /// Checks if a term exists under the given ID. + /// + /// The ID of the term whose presence in the Ontology shall be checked. + /// The Ontology where the presence of the term shall be checked. + static member containsTerm termId (onto : Ontology) = + onto.ContainsTerm(termId) + /// /// Returns the CvTerm under the given term ID. /// diff --git a/tests/Ontology.NET.Tests/Ontology.Tests.fs b/tests/Ontology.NET.Tests/Ontology.Tests.fs index dba9ba7..a09343d 100644 --- a/tests/Ontology.NET.Tests/Ontology.Tests.fs +++ b/tests/Ontology.NET.Tests/Ontology.Tests.fs @@ -253,4 +253,16 @@ module OntologyTests = Expect.sequenceEqual actual2 expected2 "Relations differ" ] + testList "ContainsTerm" [ + let testOnto = Ontology().AddTerm(CvTerm.create("TO:0", "test", "TO")) + + testCase "gives correct check: true" <| fun _ -> + let actual = testOnto.ContainsTerm("TO:0") + Expect.isTrue actual "Returns false but should be true" + + testCase "gives correct check: false" <| fun _ -> + let actual = testOnto.ContainsTerm("TO:1") + Expect.isFalse actual "Returns true but should be false" + ] + ] \ No newline at end of file From 1be80c9ef450c1e6d9721e39df087703424d24f6 Mon Sep 17 00:00:00 2001 From: Oliver Maus Date: Thu, 31 Jul 2025 17:37:02 +0200 Subject: [PATCH 02/14] Add .TryGetRelations Also updated method and parameter names where necessary. --- src/Ontology.NET/Ontology.fs | 122 ++++++++++++--------- tests/Ontology.NET.Tests/Ontology.Tests.fs | 36 +++++- 2 files changed, 104 insertions(+), 54 deletions(-) diff --git a/src/Ontology.NET/Ontology.fs b/src/Ontology.NET/Ontology.fs index 517b1ff..5ab5cdb 100644 --- a/src/Ontology.NET/Ontology.fs +++ b/src/Ontology.NET/Ontology.fs @@ -173,72 +173,85 @@ type Ontology() = /// /// Removes the given term from the Ontology. Also removes all of its relations. /// - /// The ID of the term that gets removed. - member this.RemoveTerm(term) = - FGraph.removeNode term this :?> Ontology + /// The ID of the term that gets removed. + member this.RemoveTerm(termId) = + FGraph.removeNode termId this :?> Ontology /// /// Adds a relation of source term to target term to the Ontology. /// - /// The ID of the term from which the relation originates. - /// The ID of the term that is related to the source term. + /// The ID of the term from which the relation originates. + /// The ID of the term that is related to the source term. /// The relation between both terms. /// Thrown when source term or target term are not presen in the Ontology. - member this.AddRelation(sourceTerm, targetTerm, relation) = - match FGraph.containsNode sourceTerm this, FGraph.containsNode targetTerm this with + member this.AddRelation(sourceTermId, targetTermId, relation) = + match FGraph.containsNode sourceTermId this, FGraph.containsNode targetTermId this with | true, true -> - setOrAddEdge sourceTerm targetTerm relation this :?> Ontology + setOrAddEdge sourceTermId targetTermId relation this :?> Ontology | false, true -> - raise (System.ArgumentException($"source term {sourceTerm} does not exist in the Ontology.", sourceTerm)) + raise (System.ArgumentException($"source term {sourceTermId} does not exist in the Ontology.", sourceTermId)) | true, false -> - raise (System.ArgumentException($"target term {targetTerm} does not exist in the Ontology.", targetTerm)) + raise (System.ArgumentException($"target term {targetTermId} does not exist in the Ontology.", targetTermId)) | false, false -> - raise (System.ArgumentException($"terms {sourceTerm} and {targetTerm} do not exist in the Ontology.")) + raise (System.ArgumentException($"terms {sourceTermId} and {targetTermId} do not exist in the Ontology.")) /// /// Returns the set of Relations from source to target term. /// - /// ID of the source term (where the relation originates). - /// ID of the target term (where the relation points to). + /// ID of the source term (where the relation originates). + /// ID of the target term (where the relation points to). /// Thrown when there is no relation from source to target term in the Ontology. - member this.GetRelation(sourceTerm, targetTerm) = - if FGraph.containsEdge sourceTerm targetTerm this then - FGraph.findEdge sourceTerm targetTerm this + member this.GetRelations(sourceTermId, targetTermId) = + if FGraph.containsEdge sourceTermId targetTermId this then + FGraph.findEdge sourceTermId targetTermId this |> fun (_,_,e) -> e - else raise (System.ArgumentException($"There is no relation from source terms {sourceTerm} to target term {targetTerm} in the Ontology.")) + else raise (System.ArgumentException($"There is no relation from source terms {sourceTermId} to target term {targetTermId} in the Ontology.")) // TO DO: Replace this with the code below as soon as the `FGraph.tryFindEdge` bug is fixed and a new version with the fix is released. - //match FGraph.tryFindEdge sourceTerm targetTerm this with + //match FGraph.tryFindEdge sourceTermId targetTermId this with //| Some (_,_,e) -> e - //| None -> raise (System.ArgumentException($"There is no relation from source terms {sourceTerm} to target term {targetTerm} in the Ontology.")) + //| None -> raise (System.ArgumentException($"There is no relation from source terms {sourceTermId} to target term {targetTermId} in the Ontology.")) + + /// + /// Returns the set of Relations from source to target term if they exist. Else returns None. + /// + /// ID of the source term (where the relation originates). + /// ID of the target term (where the relation points to). + member this.TryGetRelations(sourceTermId, targetTermId) = + if FGraph.containsEdge sourceTermId targetTermId this then + Some (FGraph.findEdge sourceTermId targetTermId this |> fun (_,_,e) -> e) + else None + // TO DO: Replace this with the code below as soon as the `FGraph.tryFindEdge` bug is fixed and a new version with the fix is released. + //FGraph.tryFindEdge sourceTermId targetTermId this + //|> Option.map (fun (_,_,e) -> e) /// /// Returns all relations of the Ontology as a sequence of source term ID * target term ID * relations. /// - member this.GetRelations() = + member this.GetAllRelations() = FGraph.toEdgeSeq this /// /// Removes all relations from given source to target term. Relations directed vice versa (from target to source term) are unaffected. /// - /// ID of the source term (where the relation originates). + /// ID of the source term (where the relation originates). /// ID of the target term (where the relation points to). - member this.RemoveRelations(sourceTerm, targetTerm) = - FGraph.removeEdge sourceTerm targetTerm this :?> Ontology + member this.RemoveRelations(sourceTermId, targetTermId) = + FGraph.removeEdge sourceTermId targetTermId this :?> Ontology /// /// Removes the given relation from given source to target term. Relations directed vice versa (from target to source term) are unaffected. /// - /// ID of the source term (where the relation originates). + /// ID of the source term (where the relation originates). /// ID of the target term (where the relation points to). /// The relation to be removed from the set of relations from source to target term. /// Thrown when no relation from source term to target term exists. - member this.RemoveRelation(sourceTerm, targetTerm, relation) = + member this.RemoveRelation(sourceTermId, targetTermId, relation) = try - FGraph.findEdge sourceTerm targetTerm this + FGraph.findEdge sourceTermId targetTermId this |> fun (_,_,e) -> - FGraph.setEdgeData sourceTerm targetTerm (Set.remove relation e) this :?> Ontology + FGraph.setEdgeData sourceTermId targetTermId (Set.remove relation e) this :?> Ontology with _ -> - raise (System.ArgumentException($"no relation from source term {sourceTerm} to target term {targetTerm}.")) + raise (System.ArgumentException($"no relation from source term {sourceTermId} to target term {targetTermId}.")) // TO DO: Replace this with the code below as soon as the `FGraph.tryFindEdge` bug is fixed and a new version with the fix is released. //match FGraph.tryFindEdge sourceTerm targetTerm this with //| Some (_,_,e) -> @@ -285,10 +298,10 @@ type Ontology() = /// /// Returns the target relations of the given term as (target term ID * relations) sequence. /// - /// The ID of the term whose target relations shall be returned. + /// The ID of the term whose target relations shall be returned. /// Target relations to the given term look like this: "given term -> target term" - member this.GetTargetTermRelations(termID) = - this[termID] + member this.GetTargetTermRelations(termId) = + this[termId] |> fun (_,_,s) -> s |> Seq.map (fun e -> e.Key, e.Value) @@ -787,7 +800,7 @@ type Ontology() = /// The Ontology that gets merged into this one. member this.MergeWith(onto : Ontology) = let newTerms = onto.GetTerms() - let newRelations = onto.GetRelations() + let newRelations = onto.GetAllRelations() newTerms |> Seq.iter ( fun (termId,cvTerm) -> @@ -893,47 +906,56 @@ type Ontology() = /// /// Adds a relation of source term to target term to the Ontology. /// - /// The ID of the term from which the relation originates. - /// The ID of the term that is related to the source term. + /// The ID of the term from which the relation originates. + /// The ID of the term that is related to the source term. /// The relation between both terms. /// The Ontology in which the relation shall be added. - static member addRelation sourceTerm targetTerm relation (onto : Ontology) = - onto.AddRelation(sourceTerm, targetTerm, relation) + static member addRelation sourceTermId targetTermId relation (onto : Ontology) = + onto.AddRelation(sourceTermId, targetTermId, relation) /// /// Returns the set of Relations from source to target term. /// - /// ID of the source term (where the relation originates). - /// ID of the target term (where the relation points to). + /// ID of the source term (where the relation originates). + /// ID of the target term (where the relation points to). + /// The Ontology in which to look for the relation. + static member getRelations sourceTermId targetTermId (onto : Ontology) = + onto.GetRelations(sourceTermId, targetTermId) + + /// + /// Returns the set of Relations from source to target term if they exist. Else returns None. + /// + /// ID of the source term (where the relation originates). + /// ID of the target term (where the relation points to). /// The Ontology in which to look for the relation. - static member getRelation sourceTerm targetTerm (onto : Ontology) = - onto.GetRelation(sourceTerm, targetTerm) + static member tryGetRelations sourceTermId targetTermId (onto : Ontology) = + onto.TryGetRelations(sourceTermId, targetTermId) /// /// Returns all relations of the Ontology as a sequence of source term ID * target term ID * relations. /// /// The Ontology in which to look for the relation. - static member getRelations (onto : Ontology) = - onto.GetRelations() + static member getAllRelations (onto : Ontology) = + onto.GetAllRelations() /// /// Removes all relations from given source to target term. Relations directed vice versa (from target to source term) are unaffected. /// - /// ID of the source term (where the relation originates). - /// ID of the target term (where the relation points to). + /// ID of the source term (where the relation originates). + /// ID of the target term (where the relation points to). /// The Ontology from which the relations shall be removed. - static member removeRelations sourceTerm targetTerm (onto : Ontology) = - onto.RemoveRelations(sourceTerm, targetTerm) + static member removeRelations sourceTermId targetTermId (onto : Ontology) = + onto.RemoveRelations(sourceTermId, targetTermId) /// /// Removes the given relation from given source to target term. Relations directed vice versa (from target to source term) are unaffected. /// - /// ID of the source term (where the relation originates). - /// ID of the target term (where the relation points to). + /// ID of the source term (where the relation originates). + /// ID of the target term (where the relation points to). /// The relation to be removed from the set of relations from source to target term. /// The Ontology from which the relation shall be removed. - static member removeRelation sourceTerm targetTerm relation (onto : Ontology) = - onto.RemoveRelation(sourceTerm, targetTerm, relation) + static member removeRelation sourceTermId targetTermId relation (onto : Ontology) = + onto.RemoveRelation(sourceTermId, targetTermId, relation) /// /// Returns the term IDs of all terms that have an Xref relation to the given term with the given Ontology. diff --git a/tests/Ontology.NET.Tests/Ontology.Tests.fs b/tests/Ontology.NET.Tests/Ontology.Tests.fs index a09343d..aecb22a 100644 --- a/tests/Ontology.NET.Tests/Ontology.Tests.fs +++ b/tests/Ontology.NET.Tests/Ontology.Tests.fs @@ -55,9 +55,9 @@ module OntologyTests = Expect.equal actual expected "CvTerms differ" ] - testList "GetRelation" [ - testCase "gets relation correctly" <| fun _ -> - let actual = ReferenceObjects.testOnto1.GetRelation("test:01", "test:02") + testList "GetRelations" [ + testCase "gets relations correctly" <| fun _ -> + let actual = ReferenceObjects.testOnto1.GetRelations("test:01", "test:02") let expected = set [Xref] Expect.equal actual expected "Relations are not equal" ] @@ -242,7 +242,7 @@ module OntologyTests = o let res = testOnto1.MergeWith(testOnto2) let actual1 = res.GetTerms() |> Seq.toList - let actual2 = res.GetRelations() |> Seq.toList + let actual2 = res.GetAllRelations() |> Seq.toList let expected1 = [ "TO1:1", CvTerm.create("TO1:1", "test1", "TO1"); "TO1:2", CvTerm.create("TO1:2", "test2", "TO1"); "TO3:1", CvTerm.create("TO3:1", "", ""); "TO2:1", CvTerm.create("TO2:1", "test1", "TO2"); "TO2:2", CvTerm.create("TO2:2", "test2", "TO2") ] @@ -265,4 +265,32 @@ module OntologyTests = Expect.isFalse actual "Returns true but should be false" ] + testList "GetAllRelations" [ + testCase "returns all relations correctly" <| fun _ -> + let actual = ReferenceObjects.testOnto1.GetAllRelations() + let expected = [ + "test:01", "test:02", Set.singleton Xref + "test:01", "test:04", Set.singleton IsA + "test:02", "test:04", Set.singleton IsA + "test:03", "test:02", Set.singleton Xref + "test:03", "test:04", Set.singleton IsA + ] + Expect.sequenceEqual actual expected "Relations differ" + ] + + testList "TryGetRelations" [ + testCase "gets Some relations" <| fun _ -> + let actual = ReferenceObjects.testOnto1.TryGetRelations("test:01", "test:02") + Expect.isSome actual "Relations are not there though they should" + + testCase "gets relations correctly" <| fun _ -> + let actual = ReferenceObjects.testOnto1.TryGetRelations("test:01", "test:02") + let expected = Some <| set [Xref] + Expect.equal actual expected "Relations are not equal" + + testCase "gets None when relations are not existing" <| fun _ -> + let actual = ReferenceObjects.testOnto1.TryGetRelations("test:02", "test:01") + Expect.isNone actual "Relations are there though they shouldn't" + ] + ] \ No newline at end of file From a2a6887b8c3e6d66080dc09ab0e571ca5e9d92f2 Mon Sep 17 00:00:00 2001 From: Oliver Maus Date: Thu, 31 Jul 2025 18:31:38 +0200 Subject: [PATCH 03/14] Add .HasRelations method(s) --- src/Ontology.NET/Ontology.fs | 49 +++++++++++++++------- tests/Ontology.NET.Tests/Ontology.Tests.fs | 10 +++++ 2 files changed, 44 insertions(+), 15 deletions(-) diff --git a/src/Ontology.NET/Ontology.fs b/src/Ontology.NET/Ontology.fs index 5ab5cdb..a6c5b25 100644 --- a/src/Ontology.NET/Ontology.fs +++ b/src/Ontology.NET/Ontology.fs @@ -120,13 +120,6 @@ type Ontology() = // basic functionality: - /// - /// Adds a CvTerm to the Ontology. - /// - /// The CvTerm that gets added to the Ontology. - member this.AddTerm(term : CvTerm) = - FGraph.addNode term.Accession term this :?> Ontology - /// /// Checks if a term exists under the given ID. /// @@ -134,6 +127,13 @@ type Ontology() = member this.ContainsTerm(termId) = FGraph.containsNode termId this + /// + /// Adds a CvTerm to the Ontology. + /// + /// The CvTerm that gets added to the Ontology. + member this.AddTerm(term : CvTerm) = + FGraph.addNode term.Accession term this :?> Ontology + /// /// Returns the CvTerm under the given term ID. /// @@ -177,6 +177,15 @@ type Ontology() = member this.RemoveTerm(termId) = FGraph.removeNode termId this :?> Ontology + /// + /// Checks if at least 1 relation from source term to target term exists. + /// + /// The ID of the term from which the relation originates. + /// The ID of the term that is related to the source term. + /// Does not check if a relation exists from target to source term. + member this.HasRelations(sourceTermId, targetTermId) = + FGraph.containsEdge sourceTermId targetTermId this + /// /// Adds a relation of source term to target term to the Ontology. /// @@ -847,14 +856,6 @@ type Ontology() = // accompanying static methods (to existing instance methods): - /// - /// Adds a CvTerm to the Ontology. - /// - /// The CvTerm that gets added to the Ontology. - /// The Ontology to which the term shall be added. - static member addTerm term (onto : Ontology) = - onto.AddTerm(term) - /// /// Checks if a term exists under the given ID. /// @@ -863,6 +864,14 @@ type Ontology() = static member containsTerm termId (onto : Ontology) = onto.ContainsTerm(termId) + /// + /// Adds a CvTerm to the Ontology. + /// + /// The CvTerm that gets added to the Ontology. + /// The Ontology to which the term shall be added. + static member addTerm term (onto : Ontology) = + onto.AddTerm(term) + /// /// Returns the CvTerm under the given term ID. /// @@ -903,6 +912,16 @@ type Ontology() = static member removeTerm term (onto : Ontology) = onto.RemoveTerm(term) + /// + /// Checks if at least 1 relation from source term to target term exists. + /// + /// The ID of the term from which the relation originates. + /// The ID of the term that is related to the source term. + /// The Ontology in which the relation shall be searched for. + /// Does not check if a relation exists from target to source term. + static member hasRelations sourceTermId targetTermId (onto : Ontology) = + onto.HasRelations(sourceTermId, targetTermId) + /// /// Adds a relation of source term to target term to the Ontology. /// diff --git a/tests/Ontology.NET.Tests/Ontology.Tests.fs b/tests/Ontology.NET.Tests/Ontology.Tests.fs index aecb22a..2652e76 100644 --- a/tests/Ontology.NET.Tests/Ontology.Tests.fs +++ b/tests/Ontology.NET.Tests/Ontology.Tests.fs @@ -293,4 +293,14 @@ module OntologyTests = Expect.isNone actual "Relations are there though they shouldn't" ] + testList "HasRelations" [ + testCase "gives correct check: true" <| fun _ -> + let actual = ReferenceObjects.testOnto1.HasRelations("test:01", "test:02") + Expect.isTrue actual "Returns false but should be true" + + testCase "gives correct check: false" <| fun _ -> + let actual = ReferenceObjects.testOnto1.HasRelations("test:02", "test:01") + Expect.isFalse actual "Returns true but should be false" + ] + ] \ No newline at end of file From 437627c917a1e6bdb624257c98165e018a6ae029 Mon Sep 17 00:00:00 2001 From: Oliver Maus Date: Thu, 31 Jul 2025 19:13:56 +0200 Subject: [PATCH 04/14] Add .HasRelation functionality --- src/Ontology.NET/Ontology.fs | 25 ++++++++++++++++++++++ tests/Ontology.NET.Tests/Ontology.Tests.fs | 10 +++++++++ 2 files changed, 35 insertions(+) diff --git a/src/Ontology.NET/Ontology.fs b/src/Ontology.NET/Ontology.fs index a6c5b25..e3848d3 100644 --- a/src/Ontology.NET/Ontology.fs +++ b/src/Ontology.NET/Ontology.fs @@ -186,6 +186,20 @@ type Ontology() = member this.HasRelations(sourceTermId, targetTermId) = FGraph.containsEdge sourceTermId targetTermId this + /// + /// Checks if the given relation from source term to target term exists. + /// + /// The ID of the term from which the relation originates. + /// The ID of the term that is related to the source term. + /// The relation whose presence shall be checked. + /// Does not check if the relation exists from target to source term. + member this.HasRelation(sourceTermId, targetTermId, relation) = + match this.TryGetRelations(sourceTermId, targetTermId) with + | None -> + false + | Some r -> + Set.contains relation r + /// /// Adds a relation of source term to target term to the Ontology. /// @@ -922,6 +936,17 @@ type Ontology() = static member hasRelations sourceTermId targetTermId (onto : Ontology) = onto.HasRelations(sourceTermId, targetTermId) + /// + /// Checks if the given relation from source term to target term exists. + /// + /// The ID of the term from which the relation originates. + /// The ID of the term that is related to the source term. + /// The relation whose presence shall be checked. + /// The Ontology in which the relation shall be searched for. + /// Does not check if the relation exists from target to source term. + static member hasRelation sourceTermId targetTermId relation (onto : Ontology) = + onto.HasRelation(sourceTermId, targetTermId, relation) + /// /// Adds a relation of source term to target term to the Ontology. /// diff --git a/tests/Ontology.NET.Tests/Ontology.Tests.fs b/tests/Ontology.NET.Tests/Ontology.Tests.fs index 2652e76..393d268 100644 --- a/tests/Ontology.NET.Tests/Ontology.Tests.fs +++ b/tests/Ontology.NET.Tests/Ontology.Tests.fs @@ -303,4 +303,14 @@ module OntologyTests = Expect.isFalse actual "Returns true but should be false" ] + testList "HasRelation" [ + testCase "gives correct check: true" <| fun _ -> + let actual = ReferenceObjects.testOnto1.HasRelation("test:01", "test:02", Xref) + Expect.isTrue actual "Returns false but should be true" + + testCase "gives correct check: false" <| fun _ -> + let actual = ReferenceObjects.testOnto1.HasRelation("test:01", "test:02", IsA) + Expect.isFalse actual "Returns true but should be false" + ] + ] \ No newline at end of file From a7d439d90a5796814290a25bb8169e9e02c6b9aa Mon Sep 17 00:00:00 2001 From: Oliver Maus Date: Fri, 1 Aug 2025 17:53:22 +0200 Subject: [PATCH 05/14] Add .fromTriplets + fix critical bug in OntologyHelperFunctions --- src/Ontology.NET/Ontology.fs | 50 +++++++++++++++++----- tests/Ontology.NET.Tests/Ontology.Tests.fs | 19 ++++++++ 2 files changed, 59 insertions(+), 10 deletions(-) diff --git a/src/Ontology.NET/Ontology.fs b/src/Ontology.NET/Ontology.fs index e3848d3..5c16569 100644 --- a/src/Ontology.NET/Ontology.fs +++ b/src/Ontology.NET/Ontology.fs @@ -15,23 +15,36 @@ module internal OntologyGraphHelpers = let setOrAddEdgeObo sourceTerm searchedTermKey (relationType : RelationType) oboOnto graph = let cvtTarget = OboOntology.getOrCreateTerm searchedTermKey oboOnto |> OboTerm.toCvTerm if FGraph.containsNode cvtTarget.Accession graph then - match FGraph.tryFindEdge sourceTerm.Accession cvtTarget.Accession graph with - | Some (nk1,nk2,alreadyExistingEdge) -> + if FGraph.containsEdge sourceTerm.Accession cvtTarget.Accession graph then + let _, _, alreadyExistingEdge = FGraph.findEdge sourceTerm.Accession cvtTarget.Accession graph FGraph.setEdgeData sourceTerm.Accession cvtTarget.Accession (Set.add relationType alreadyExistingEdge) graph - | None -> + else FGraph.addEdge sourceTerm.Accession cvtTarget.Accession (Set (List.singleton relationType)) graph |> ignore + // TO DO: Replace this with the code below as soon as the `FGraph.tryFindEdge` bug is fixed and a new version with the fix is released. + //match FGraph.tryFindEdge sourceTerm.Accession cvtTarget.Accession graph with + //| Some (nk1,nk2,alreadyExistingEdge) -> + // FGraph.setEdgeData sourceTerm.Accession cvtTarget.Accession (Set.add relationType alreadyExistingEdge) graph + //| None -> + // FGraph.addEdge sourceTerm.Accession cvtTarget.Accession (Set (List.singleton relationType)) graph + //|> ignore else let missingTargetTerm = CvTerm.create(searchedTermKey, "", "") FGraph.addElement sourceTerm.Accession sourceTerm missingTargetTerm.Accession missingTargetTerm (Set (List.singleton relationType)) graph |> ignore // CAUTION: fails if one of the terms doesn't exist in the given Ontology! - let setOrAddEdge sourceTerm targetTerm (relation : RelationType) onto = - match FGraph.tryFindEdge sourceTerm targetTerm onto with - | Some (_, _, edgeData) -> - FGraph.setEdgeData sourceTerm targetTerm (Set.add relation edgeData) onto - | None -> - FGraph.addEdge sourceTerm targetTerm (Set.singleton relation) onto + let setOrAddEdge sourceTermId targetTermId (relation : RelationType) onto = + if FGraph.containsEdge sourceTermId targetTermId onto then + let _, _, currRel = FGraph.findEdge sourceTermId targetTermId onto + FGraph.setEdgeData sourceTermId targetTermId (Set.add relation currRel) onto + else + FGraph.addEdge sourceTermId targetTermId (Set.singleton relation) onto + // TO DO: Replace this with the code below as soon as the `FGraph.tryFindEdge` bug is fixed and a new version with the fix is released. + //match FGraph.tryFindEdge sourceTerm targetTerm onto with + //| Some (_, _, edgeData) -> + // FGraph.setEdgeData sourceTerm targetTerm (Set.add relation edgeData) onto + //| None -> + // FGraph.addEdge sourceTerm targetTerm (Set.singleton relation) onto open OntologyGraphHelpers @@ -46,7 +59,7 @@ type Ontology() = // parsing functionality: /// - /// Takes a given OboOntology and transforms it into an Ontology, with the term IDs as node keys, the terms as CvTerms as node data and the relations as edges. + /// Takes a given OboOntology and returns the corresponding Ontology, with the term IDs as node keys, the terms as CvTerms as node data and the relations as edges. /// /// If a relation points to a term that is not present in the given OboOntology, initializes them as new CvTerms but with name and ref = "<missing>". static member fromOboOntology (oboOnto : OboOntology) = @@ -117,6 +130,23 @@ type Ontology() = |> Seq.map Ontology.fromOboOntology |> Ontology.mergeAll + /// + /// Takes a sequence of triplets and returns the corresponding Ontology. + /// + /// The triplets in the form of source term * relation * target term that serve as the informational basis for the ontology that gets created out of them. + static member fromTriplets (triplets : (CvTerm * RelationType * CvTerm) seq) = + let onto = Ontology() + + triplets + |> Seq.iter ( + fun (term1,rel,term2) -> + onto.AddTerm(term1) |> ignore + onto.AddTerm(term2) |> ignore + onto.AddRelation(term1.Accession, term2.Accession, rel) |> ignore + ) + + onto + // basic functionality: diff --git a/tests/Ontology.NET.Tests/Ontology.Tests.fs b/tests/Ontology.NET.Tests/Ontology.Tests.fs index 393d268..5154619 100644 --- a/tests/Ontology.NET.Tests/Ontology.Tests.fs +++ b/tests/Ontology.NET.Tests/Ontology.Tests.fs @@ -313,4 +313,23 @@ module OntologyTests = Expect.isFalse actual "Returns true but should be false" ] + testList "fromTriplets" [ + testCase "returns correct Ontology" <| fun _ -> + let trips = [ + CvTerm.create("TR:1", "tripletTerm1", "TR"), IsA, CvTerm.create("TR:2", "tripletTerm2", "TR") + CvTerm.create("TR:1", "tripletTerm1", "TR"), Xref, CvTerm.create("TR:3", "tripletTerm3", "TR") + CvTerm.create("TR:2", "tripletTerm2", "TR"), Custom "has_a", CvTerm.create("TR:3", "tripletTerm3", "TR") + CvTerm.create("TR:1", "tripletTerm1", "TR"), Term (CvTerm.create("RO:9999999", "uses", "RO")), CvTerm.create("TR:4", "tripletTerm4", "TR") + CvTerm.create("TR:1", "tripletTerm1", "TR"), Custom "optional use", CvTerm.create("TR:4", "tripletTerm4", "TR") + ] + let actual = Ontology.fromTriplets trips |> FGraph.toSeq + let expected = [ + "TR:1", CvTerm.create("TR:1", "tripletTerm1", "TR"), "TR:2", CvTerm.create("TR:2", "tripletTerm2", "TR"), Set.singleton IsA + "TR:1", CvTerm.create("TR:1", "tripletTerm1", "TR"), "TR:3", CvTerm.create("TR:3", "tripletTerm3", "TR"), Set.singleton Xref + "TR:1", CvTerm.create("TR:1", "tripletTerm1", "TR"), "TR:4", CvTerm.create("TR:4", "tripletTerm4", "TR"), Set [Term (CvTerm.create("RO:9999999", "uses", "RO")); Custom "optional use"] + "TR:2", CvTerm.create("TR:2", "tripletTerm2", "TR"), "TR:3", CvTerm.create("TR:3", "tripletTerm3", "TR"), Set.singleton <| Custom "has_a" + ] + Expect.sequenceEqual actual expected "Ontologies differ but they shouldn't" + ] + ] \ No newline at end of file From 0b5c987c4a75a7aa003915d15b742a86f124fcca Mon Sep 17 00:00:00 2001 From: Oliver Maus Date: Fri, 1 Aug 2025 18:16:45 +0200 Subject: [PATCH 06/14] (WIP) Add functionality for parsing to OboOntology --- src/Ontology.NET/OBO/OboOntology.fs | 50 ++++++++++++++++++++++++++++- src/Ontology.NET/OBO/OboTerm.fs | 18 +++++++++-- src/Ontology.NET/Ontology.fs | 17 ++++++++++ 3 files changed, 81 insertions(+), 4 deletions(-) diff --git a/src/Ontology.NET/OBO/OboOntology.fs b/src/Ontology.NET/OBO/OboOntology.fs index 8381886..e1190fa 100644 --- a/src/Ontology.NET/OBO/OboOntology.fs +++ b/src/Ontology.NET/OBO/OboOntology.fs @@ -613,4 +613,52 @@ module OboOntology = | _ -> yield! loop en (lineNumber + 1) | false -> () } - loop en 1 \ No newline at end of file + loop en 1 + + +type OboOntologyHeaderData = + + { + FormatVersion : string + DataVersion : string option + Ontology : string option + Date : DateTime option + SavedBy : string option + AutoGeneratedBy : string option + Subsetdefs : string list + Imports : string list // needs its own type (Record?) + Synonymtypedefs : string list // rethink type, maybe create a mother type (Union? Maybe Record'd be better) + Idspaces : string list // rethink as own Record type + DefaultRelationshipIdPrefix : string option + IdMappings : string list // rethink: maybe a new record? or TermRelation? + Remarks : string list + TreatXrefsAsEquivalents : string list + TreatXrefsAsGenusDifferentias : string list // rethink: maybe a new record? or plain string option? + TreatXrefsAsRelationships : string list // maybe better as its own (Record/Union?) type + TreatXrefsAsIsAs : string list + RelaxUniqueIdentifierAssumptionForNamespaces : string list + RelaxUniqueLabelAssumptionForNamespaces : string list + } + + static member create formatVersion dataVersion ontology date savedBy autoGeneratedBy subsetdefs imports synonymtypedefs idspaces defaultRelationshipIdPrefix idMappings remarks treatXrefsAsEquivalents treatXrefsAsGenusDifferentias treatXrefsAsRelationships treatXrefsAsIsAs relaxUniqueIdentifierAssumptionForNamespaces relaxUniqueLabelAssumptionForNamespaces = + { + FormatVersion = formatVersion + DataVersion = dataVersion + Ontology = ontology + Date = date + SavedBy = savedBy + AutoGeneratedBy = autoGeneratedBy + Subsetdefs = subsetdefs + Imports = imports + Synonymtypedefs = synonymtypedefs + Idspaces = idspaces + DefaultRelationshipIdPrefix = defaultRelationshipIdPrefix + IdMappings = idMappings + Remarks = remarks + TreatXrefsAsEquivalents = treatXrefsAsEquivalents + TreatXrefsAsGenusDifferentias = treatXrefsAsGenusDifferentias + TreatXrefsAsRelationships = treatXrefsAsRelationships + TreatXrefsAsIsAs = treatXrefsAsIsAs + RelaxUniqueIdentifierAssumptionForNamespaces = relaxUniqueIdentifierAssumptionForNamespaces + RelaxUniqueLabelAssumptionForNamespaces = relaxUniqueLabelAssumptionForNamespaces + } \ No newline at end of file diff --git a/src/Ontology.NET/OBO/OboTerm.fs b/src/Ontology.NET/OBO/OboTerm.fs index e814103..10c11b6 100644 --- a/src/Ontology.NET/OBO/OboTerm.fs +++ b/src/Ontology.NET/OBO/OboTerm.fs @@ -1,11 +1,12 @@ namespace Ontology.NET.OBO +open System +open System.Text.RegularExpressions + open DBXref open TermSynonym -open System - open ControlledVocabulary open FSharpAux @@ -517,10 +518,21 @@ type OboTerm = /// Takes a relationship and returns a tuple consisting of the name of the relationship and the ID of the OboTerm it matches. static member deconstructRelationship relationship = - let pattern = System.Text.RegularExpressions.Regex @"^(?.+?) (?[^ ]+:\d+)(?: .*)?$" + let pattern = Regex @"^(?.+?) (?[^ ]+:\d+)(?: .*)?$" let regexMatch = pattern.Match relationship regexMatch.Groups["relName"].Value, regexMatch.Groups["id"].Value + /// + /// Takes the ID of a target term and the name of a relationship and constructs the relationship from them. + /// + /// The ID of the term that the relationship targets. + /// The name of the relationship, e.g. "part_of". + static member constructRelationship targetTermId relationshipName = + let whiteSpacePattern = Regex(@"\s") + if whiteSpacePattern.Match(relationshipName).Success then + raise (System.ArgumentException($"relationshipName {relationshipName} must not contain white spaces.", relationshipName)) + $"{relationshipName} {targetTermId}" + /// Returns the OboTerm's relationships as a triple consisting of the term's ID, the name of the relationship, and the related term's ID. member this.GetRelatedTermIds() = this.Relationships diff --git a/src/Ontology.NET/Ontology.fs b/src/Ontology.NET/Ontology.fs index 5c16569..1b6c995 100644 --- a/src/Ontology.NET/Ontology.fs +++ b/src/Ontology.NET/Ontology.fs @@ -147,6 +147,23 @@ type Ontology() = onto + static member toOboOntology headerData (onto : Ontology) = + + let terms = + onto.GetTerms() + |> Seq.map snd + |> Seq.map ( + fun cvt -> + let relations = onto.GetTargetTermRelations(cvt.Accession) + OboTerm.Create( + cvt.Accession, + Name = cvt.Name + Relationships = + ) + ) + + OboOntology.Create + // basic functionality: From f92a6862bc994a36fde09fe844578d583d2b69f5 Mon Sep 17 00:00:00 2001 From: Oliver Maus Date: Mon, 4 Aug 2025 18:14:12 +0200 Subject: [PATCH 07/14] Add constructionRelationship, fix some bugs due to new record --- src/Ontology.NET/OBO/OboTerm.fs | 2 +- src/Ontology.NET/Ontology.fs | 32 +++++++-------- .../OBO/OboOntology.Tests.fs | 40 +++++++++---------- tests/Ontology.NET.Tests/OBO/OboTerm.Tests.fs | 10 +++++ 4 files changed, 47 insertions(+), 37 deletions(-) diff --git a/src/Ontology.NET/OBO/OboTerm.fs b/src/Ontology.NET/OBO/OboTerm.fs index 10c11b6..6748b28 100644 --- a/src/Ontology.NET/OBO/OboTerm.fs +++ b/src/Ontology.NET/OBO/OboTerm.fs @@ -531,7 +531,7 @@ type OboTerm = let whiteSpacePattern = Regex(@"\s") if whiteSpacePattern.Match(relationshipName).Success then raise (System.ArgumentException($"relationshipName {relationshipName} must not contain white spaces.", relationshipName)) - $"{relationshipName} {targetTermId}" + $"{targetTermId} {relationshipName}" /// Returns the OboTerm's relationships as a triple consisting of the term's ID, the name of the relationship, and the related term's ID. member this.GetRelatedTermIds() = diff --git a/src/Ontology.NET/Ontology.fs b/src/Ontology.NET/Ontology.fs index 1b6c995..e8b583b 100644 --- a/src/Ontology.NET/Ontology.fs +++ b/src/Ontology.NET/Ontology.fs @@ -147,22 +147,22 @@ type Ontology() = onto - static member toOboOntology headerData (onto : Ontology) = - - let terms = - onto.GetTerms() - |> Seq.map snd - |> Seq.map ( - fun cvt -> - let relations = onto.GetTargetTermRelations(cvt.Accession) - OboTerm.Create( - cvt.Accession, - Name = cvt.Name - Relationships = - ) - ) - - OboOntology.Create + //static member toOboOntology headerData (onto : Ontology) = + + // let terms = + // onto.GetTerms() + // |> Seq.map snd + // |> Seq.map ( + // fun cvt -> + // let relations = onto.GetTargetTermRelations(cvt.Accession) + // OboTerm.Create( + // cvt.Accession, + // Name = cvt.Name + // Relationships = + // ) + // ) + + // OboOntology.Create // basic functionality: diff --git a/tests/Ontology.NET.Tests/OBO/OboOntology.Tests.fs b/tests/Ontology.NET.Tests/OBO/OboOntology.Tests.fs index a4ce412..c72d9a0 100644 --- a/tests/Ontology.NET.Tests/OBO/OboOntology.Tests.fs +++ b/tests/Ontology.NET.Tests/OBO/OboOntology.Tests.fs @@ -71,25 +71,25 @@ module OboOntologyTests = Expect.isSome testFile2 $"Could not read testFile2: {testFile2Path}" Expect.isSome testFile3 $"Could not read testFile3: {testFile3Path}" testCase "reads correct headers correctly" <| fun _ -> - let formatVersionActual = Option.map (fun o -> o.FormatVersion) testFile1 - let dataVersionActual = Option.map (fun o -> o.DataVersion) testFile1 |> Option.flatten - let ontologyActual = Option.map (fun o -> o.Ontology) testFile1 |> Option.flatten - let dateActual = Option.map (fun o -> o.Date) testFile1 |> Option.flatten - let savedByActual = Option.map (fun o -> o.SavedBy) testFile1 |> Option.flatten - let autoGeneratedByActual = Option.map (fun o -> o.AutoGeneratedBy) testFile1 |> Option.flatten - let subsetdefsActual = Option.map (fun o -> o.Subsetdefs) testFile1 - let importsActual = Option.map (fun o -> o.Imports) testFile1 - let synonymtypedefsActual = Option.map (fun o -> o.Synonymtypedefs) testFile1 - let idSpacesActual = Option.map (fun o -> o.Idspaces) testFile1 - let defaultRelationshipIdPrefixActual = Option.map (fun o -> o.DefaultRelationshipIdPrefix) testFile1 |> Option.flatten - let idMappingsActual = Option.map (fun o -> o.IdMappings) testFile1 - let remarksActual = Option.map (fun o -> o.Remarks) testFile1 - let treatXrefsAsEquivalentsActual = Option.map (fun o -> o.TreatXrefsAsEquivalents) testFile1 - let treatXrefsAsGenusDifferentiasActual = Option.map (fun o -> o.TreatXrefsAsGenusDifferentias) testFile1 - let treatXrefsAsRelationshipsActual = Option.map (fun o -> o.TreatXrefsAsRelationships) testFile1 - let treatXrefsAsIsAsActual = Option.map (fun o -> o.TreatXrefsAsIsAs) testFile1 - let relaxUniqueIdentifierAssumptionForNamespacesActual = Option.map (fun o -> o.RelaxUniqueIdentifierAssumptionForNamespaces) testFile1 - let relaxUniqueLabelAssumptionForNamespacesActual = Option.map (fun o -> o.RelaxUniqueLabelAssumptionForNamespaces) testFile1 + let formatVersionActual = testFile1 |> Option.map (fun o -> o.FormatVersion) + let dataVersionActual = testFile1 |> Option.map (fun o -> o.DataVersion) |> Option.flatten + let ontologyActual = testFile1 |> Option.map (fun o -> o.Ontology) |> Option.flatten + let dateActual = testFile1 |> Option.map (fun o -> o.Date) |> Option.flatten + let savedByActual = testFile1 |> Option.map (fun o -> o.SavedBy) |> Option.flatten + let autoGeneratedByActual = testFile1 |> Option.map (fun o -> o.AutoGeneratedBy) |> Option.flatten + let subsetdefsActual = testFile1 |> Option.map (fun o -> o.Subsetdefs) + let importsActual = testFile1 |> Option.map (fun o -> o.Imports) + let synonymtypedefsActual = testFile1 |> Option.map (fun o -> o.Synonymtypedefs) + let idSpacesActual = testFile1 |> Option.map (fun o -> o.Idspaces) + let defaultRelationshipIdPrefixActual = testFile1 |> Option.map (fun o -> o.DefaultRelationshipIdPrefix) |> Option.flatten + let idMappingsActual = testFile1 |> Option.map (fun o -> o.IdMappings) + let remarksActual = testFile1 |> Option.map (fun o -> o.Remarks) + let treatXrefsAsEquivalentsActual = testFile1 |> Option.map (fun o -> o.TreatXrefsAsEquivalents) + let treatXrefsAsGenusDifferentiasActual = testFile1 |> Option.map (fun o -> o.TreatXrefsAsGenusDifferentias) + let treatXrefsAsRelationshipsActual = testFile1 |> Option.map (fun o -> o.TreatXrefsAsRelationships) + let treatXrefsAsIsAsActual = testFile1 |> Option.map (fun o -> o.TreatXrefsAsIsAs) + let relaxUniqueIdentifierAssumptionForNamespacesActual = testFile1 |> Option.map (fun o -> o.RelaxUniqueIdentifierAssumptionForNamespaces) + let relaxUniqueLabelAssumptionForNamespacesActual = testFile1 |> Option.map (fun o -> o.RelaxUniqueLabelAssumptionForNamespaces) let formatVersionExpected = "0.0.1" |> Some let dataVersionExpected = "0.0.1" |> Some let ontologyExpected = "CL" |> Some @@ -129,7 +129,7 @@ module OboOntologyTests = Expect.equal relaxUniqueIdentifierAssumptionForNamespacesActual relaxUniqueIdentifierAssumptionForNamespaceExpected "relax-unique-identifier-assumption-for-namespaces are not identical" Expect.equal relaxUniqueLabelAssumptionForNamespacesActual relaxUniqueLabelAssumptionForNamespaceExpected "relax-unique-label-assumption-for-namespaces are not identical" testCase "reads incorrect headers correctly" <| fun _ -> - Expect.isNone (Option.map (fun o -> o.Date) testFile2 |> Option.flatten) "Date should be missing but was still parsed" + Expect.isNone (testFile2 |> Option.map (fun o -> o.Date) |> Option.flatten) "Date should be missing but was still parsed" testCase "reads Terms correctly" <| fun _ -> let termsExpected = List.init 2 (fun i -> OboTerm.Create $"Test:000{i + 1}") |> Some Expect.equal (Option.map (fun o -> o.Terms) testFile1) termsExpected "Terms did not match" diff --git a/tests/Ontology.NET.Tests/OBO/OboTerm.Tests.fs b/tests/Ontology.NET.Tests/OBO/OboTerm.Tests.fs index 122631b..268d8e1 100644 --- a/tests/Ontology.NET.Tests/OBO/OboTerm.Tests.fs +++ b/tests/Ontology.NET.Tests/OBO/OboTerm.Tests.fs @@ -56,4 +56,14 @@ module OboTermTests = Expect.equal actual.Name expected.Name "Names are different" Expect.equal actual.Accession expected.Accession "TANs are different" ] + + testList "constructRelationship" [ + testCase "returns correct relationship string" <| fun _ -> + let actual = OboTerm.constructRelationship "part_of" "TGMA:0000002" + let expected = "part_of TGMA:0000002" + Expect.equal actual expected "relationship strings differ" + + testCase "throws when expected" <| fun _ -> + Expect.throws (fun _ -> OboTerm.constructRelationship "part of" "TGMA:0000002" |> ignore) "Did not throw though expected" + ] ] \ No newline at end of file From c50e4a56c0f6e55793da02815c23d933ed35638c Mon Sep 17 00:00:00 2001 From: Oliver Maus Date: Mon, 4 Aug 2025 19:24:54 +0200 Subject: [PATCH 08/14] Add functions & methods for RelationType --- src/Ontology.NET/Ontology.fs | 28 +++--- src/Ontology.NET/OntologyRelation.fs | 31 +++++++ tests/Ontology.NET.Tests/OBO/OboTerm.Tests.fs | 2 +- .../Ontology.NET.Tests.fsproj | 3 +- .../Ontology.NET.Tests/RelationType.Tests.fs | 88 +++++++++++++++++++ 5 files changed, 135 insertions(+), 17 deletions(-) create mode 100644 tests/Ontology.NET.Tests/RelationType.Tests.fs diff --git a/src/Ontology.NET/Ontology.fs b/src/Ontology.NET/Ontology.fs index e8b583b..f38b2d9 100644 --- a/src/Ontology.NET/Ontology.fs +++ b/src/Ontology.NET/Ontology.fs @@ -149,20 +149,20 @@ type Ontology() = //static member toOboOntology headerData (onto : Ontology) = - // let terms = - // onto.GetTerms() - // |> Seq.map snd - // |> Seq.map ( - // fun cvt -> - // let relations = onto.GetTargetTermRelations(cvt.Accession) - // OboTerm.Create( - // cvt.Accession, - // Name = cvt.Name - // Relationships = - // ) - // ) - - // OboOntology.Create + //let terms = + // onto.GetTerms() + // |> Seq.map snd + // |> Seq.map ( + // fun cvt -> + // let relations = onto.GetTargetTermRelations(cvt.Accession) + // OboTerm.Create( + // cvt.Accession, + // Name = cvt.Name + // Relationships = + // ) + // ) + + //OboOntology.Create // basic functionality: diff --git a/src/Ontology.NET/OntologyRelation.fs b/src/Ontology.NET/OntologyRelation.fs index dd16cd6..3bde583 100644 --- a/src/Ontology.NET/OntologyRelation.fs +++ b/src/Ontology.NET/OntologyRelation.fs @@ -11,6 +11,37 @@ type RelationType = | Term of CvTerm | Custom of string + with + /// + /// Creates a RelationType from a given string. + /// + /// The string that is used to create a RelationType. + static member fromString (str : string) = + match str.ToLower() with + | "is_a" + | "is a" + | "isa" -> IsA + | "xref" + | "x_ref" + | "x ref" -> Xref + | _ -> Custom str + + /// + /// Returns the RelationType as a string. + /// + override this.ToString() : string = + match this with + | IsA -> "is_a" + | Xref -> "xref" + | Custom r -> r + | Term cvt -> cvt.Name + + /// + /// Returns a RelationType as a string. + /// + static member toString (rt : RelationType) = + rt.ToString() + /// Model for a generic ontological relation. Consists of fields RelationType that inhabits the type of the relation, and Target for the targeted CvTerm of the relation. type OntologyRelation = { diff --git a/tests/Ontology.NET.Tests/OBO/OboTerm.Tests.fs b/tests/Ontology.NET.Tests/OBO/OboTerm.Tests.fs index 268d8e1..08cae25 100644 --- a/tests/Ontology.NET.Tests/OBO/OboTerm.Tests.fs +++ b/tests/Ontology.NET.Tests/OBO/OboTerm.Tests.fs @@ -64,6 +64,6 @@ module OboTermTests = Expect.equal actual expected "relationship strings differ" testCase "throws when expected" <| fun _ -> - Expect.throws (fun _ -> OboTerm.constructRelationship "part of" "TGMA:0000002" |> ignore) "Did not throw though expected" + Expect.throws (fun _ -> OboTerm.constructRelationship "TGMA:0000002" "part of" |> ignore) "Did not throw though expected" ] ] \ No newline at end of file diff --git a/tests/Ontology.NET.Tests/Ontology.NET.Tests.fsproj b/tests/Ontology.NET.Tests/Ontology.NET.Tests.fsproj index e16ac71..2b966db 100644 --- a/tests/Ontology.NET.Tests/Ontology.NET.Tests.fsproj +++ b/tests/Ontology.NET.Tests/Ontology.NET.Tests.fsproj @@ -34,10 +34,9 @@ + - - diff --git a/tests/Ontology.NET.Tests/RelationType.Tests.fs b/tests/Ontology.NET.Tests/RelationType.Tests.fs new file mode 100644 index 0000000..f9de5b1 --- /dev/null +++ b/tests/Ontology.NET.Tests/RelationType.Tests.fs @@ -0,0 +1,88 @@ +namespace Ontology.NET.Tests + + +open Expecto +open Graphoscope +open FSharpAux + +open ControlledVocabulary +open Ontology.NET + + +module RelationTypeTests = + + [] + let relationTypeTests = + testList "RelationType" [ + + let dummyTerm = CvTerm.create("DT:1", "dummyTerm", "DT") + + testList "fromString" [ + testCase "creates IsA from 'is_a'" <| fun _ -> + let actual = RelationType.fromString "is_a" + Expect.equal actual IsA "Expected RelationType.IsA" + + testCase "creates IsA from 'is a'" <| fun _ -> + let actual = RelationType.fromString "is a" + Expect.equal actual IsA "Expected RelationType.IsA" + + testCase "creates IsA from 'ISA'" <| fun _ -> + let actual = RelationType.fromString "ISA" + Expect.equal actual IsA "Expected RelationType.IsA" + + testCase "creates Xref from 'xref'" <| fun _ -> + let actual = RelationType.fromString "xref" + Expect.equal actual Xref "Expected RelationType.Xref" + + testCase "creates Xref from 'x_ref'" <| fun _ -> + let actual = RelationType.fromString "x_ref" + Expect.equal actual Xref "Expected RelationType.Xref" + + testCase "creates Xref from 'x ref'" <| fun _ -> + let actual = RelationType.fromString "x ref" + Expect.equal actual Xref "Expected RelationType.Xref" + + testCase "creates Custom from unknown string" <| fun _ -> + let input = "related_to" + let actual = RelationType.fromString input + let expected = Custom input + Expect.equal actual expected "Expected Custom relation type" + ] + + testList "ToString instance method" [ + testCase "returns 'is_a' for IsA" <| fun _ -> + let actual = IsA.ToString() + Expect.equal actual "is_a" "ToString failed for IsA" + + testCase "returns 'xref' for Xref" <| fun _ -> + let actual = Xref.ToString() + Expect.equal actual "xref" "ToString failed for Xref" + + testCase "returns original string for Custom" <| fun _ -> + let actual = (Custom "related_to").ToString() + Expect.equal actual "related_to" "ToString failed for Custom" + + testCase "returns CvTerm name for Term" <| fun _ -> + let actual = (Term dummyTerm).ToString() + Expect.equal actual "dummyTerm" "ToString failed for Term" + ] + + testList "toString static method" [ + testCase "returns 'is_a' for IsA" <| fun _ -> + let actual = RelationType.toString IsA + Expect.equal actual "is_a" "toString failed for IsA" + + testCase "returns 'xref' for Xref" <| fun _ -> + let actual = RelationType.toString Xref + Expect.equal actual "xref" "toString failed for Xref" + + testCase "returns string for Custom" <| fun _ -> + let actual = RelationType.toString (Custom "foobar") + Expect.equal actual "foobar" "toString failed for Custom" + + testCase "returns term name for Term" <| fun _ -> + let actual = RelationType.toString (Term dummyTerm) + Expect.equal actual "dummyTerm" "toString failed for Term" + ] + + ] \ No newline at end of file From 73ca21744c3ea157c67a93d1ae27cfdfef3ecdb2 Mon Sep 17 00:00:00 2001 From: Oliver Maus Date: Tue, 5 Aug 2025 17:05:27 +0200 Subject: [PATCH 09/14] Add functions to create OboOntologies from OboHeaderTags --- src/Ontology.NET/OBO/OboOntology.fs | 93 ++++++++++++++++++++++++++++- 1 file changed, 90 insertions(+), 3 deletions(-) diff --git a/src/Ontology.NET/OBO/OboOntology.fs b/src/Ontology.NET/OBO/OboOntology.fs index e1190fa..5c9b281 100644 --- a/src/Ontology.NET/OBO/OboOntology.fs +++ b/src/Ontology.NET/OBO/OboOntology.fs @@ -13,7 +13,7 @@ open System open System.IO -/// Ontology containing OBO Terms and OBO Type Defs (OBO 1.2). +/// Ontology containing OBO Terms and OBO Typedefs (OBO 1.2). type OboOntology = { @@ -616,7 +616,7 @@ module OboOntology = loop en 1 -type OboOntologyHeaderData = +type OboOntologyHeaderTags = { FormatVersion : string @@ -640,6 +640,29 @@ type OboOntologyHeaderData = RelaxUniqueLabelAssumptionForNamespaces : string list } + // Param descriptions taken and adapted from the OBO flat file format 1.4: https://owlcollab.github.io/oboformat/doc/GO.format.obo-1_4.html + /// + /// Creates the header tags for an OboOntology. + /// + /// Gives the OBO specification version that this OboOntology uses. This is useful if tag semantics change from one OBO specification version to the next. + /// Gives the version of the current OboOntology. This gets translated to versionIRI in OWL. + /// The ID space of this OboOntology. This should correspond to to the ID prefix of the terms that belong to that ontology, translated to lowercase. For GO, the value of this field will be "go". For the cell ontology (i.e. the ontology that contains CL:0000001), the value will be "cl". If the obo document contains some alternative cut or extension of the ontology (for example, a GO slim, or an ontology merged with another), then the ontology should be of form "X/Y", where X is the basic ontology name, and Y identifies the cut. For example go/gosubset_prok. A URI is also permitted in here. In the translation to OWL, the usual default prefix rules will apply, with the ".owl" suffix. E.g. "go" will be treated as "http://purl.obo-library.org/obo/go.owl". + /// The current date in dd:MM:yyyy HH:mm format (note: for historic reasons, this is NOT a ISO 8601 date, as is the case for the creation-date field). + /// The username of the person to last save this OboOntology. The meaning of "username" is entirely up to the application that generated the file. + /// The program that generated the OboOntology. + /// A description of a term subset. The value for this tag should contain a subset name, a space, and a quote enclosed subset description. + /// A url or ontology ID referencing another OBO document. + /// A description of a user-defined synonym type. The value for this tag should contain a synonym type name, a space, a quote enclosed description, and an optional scope specifier. + /// A mapping between a "local" ID space and a "global" ID space. The value for this tag should be a local idspace, a space, a URI, optionally followed by a quote-enclosed description. + /// Any relationship lacking an ID space will be prefixed with the value of this tag. + /// Maps a Term or Typedef ID to another Term or Typedef ID. The main reason for this tag is to increase interoperability between different OBO ontologies. + /// General comments for this file. This tag is differentiated from a ! comment in that the contents of a remark tag are guaranteed to be preserved by a parser. + /// The value for this tag should contain an ID Space. + /// The value for this tag should contain an ID Space followed by a relation and then a class filler. + /// The value for this tag should contain an ID Space followed by a relation ID. + /// The value for this tag should contain an ID Space. + /// The value for this tag should be a namespace. By default, an OBO namespace (note: not ID-space) partitions all the entities such that no two entities belonging to the same namespace may be equivalent. This header tag relaxes this assumption. + /// static member create formatVersion dataVersion ontology date savedBy autoGeneratedBy subsetdefs imports synonymtypedefs idspaces defaultRelationshipIdPrefix idMappings remarks treatXrefsAsEquivalents treatXrefsAsGenusDifferentias treatXrefsAsRelationships treatXrefsAsIsAs relaxUniqueIdentifierAssumptionForNamespaces relaxUniqueLabelAssumptionForNamespaces = { FormatVersion = formatVersion @@ -661,4 +684,68 @@ type OboOntologyHeaderData = TreatXrefsAsIsAs = treatXrefsAsIsAs RelaxUniqueIdentifierAssumptionForNamespaces = relaxUniqueIdentifierAssumptionForNamespaces RelaxUniqueLabelAssumptionForNamespaces = relaxUniqueLabelAssumptionForNamespaces - } \ No newline at end of file + } + + /// + /// Creates an empty OboOntologyHeaderTag object. + /// + static member createDefault () = + { + FormatVersion = "" + DataVersion = None + Ontology = None + Date = None + SavedBy = None + AutoGeneratedBy = None + Subsetdefs = [] + Imports = [] + Synonymtypedefs = [] + Idspaces = [] + DefaultRelationshipIdPrefix = None + IdMappings = [] + Remarks = [] + TreatXrefsAsEquivalents = [] + TreatXrefsAsGenusDifferentias = [] + TreatXrefsAsRelationships = [] + TreatXrefsAsIsAs = [] + RelaxUniqueIdentifierAssumptionForNamespaces = [] + RelaxUniqueLabelAssumptionForNamespaces = [] + } + + /// + /// Creates an OboOntology out of this header tags alongside the given terms and typedefs. + /// + /// The list of OboTerms the resulting OboOntology shall have. + /// The list of OboTypedefs the resulting OboOntology shall have. + member this.ToOboOntology(terms, typedefs) = + OboOntology.Create( + terms, + typedefs, + this.FormatVersion, + ?DataVersion = this.DataVersion, + ?Ontology = this.Ontology, + ?Date = this.Date, + ?SavedBy = this.SavedBy, + ?AutoGeneratedBy = this.AutoGeneratedBy, + Subsetdefs = this.Subsetdefs, + Imports = this.Imports, + Synonymtypedefs = this.Synonymtypedefs, + Idspaces = this.Idspaces, + ?DefaultRelationshipIdPrefix = this.DefaultRelationshipIdPrefix, + IdMappings = this.IdMappings, + Remarks = this.Remarks, + TreatXrefsAsEquivalents = this.TreatXrefsAsEquivalents, + TreatXrefsAsGenusDifferentias = this.TreatXrefsAsGenusDifferentias, + TreatXrefsAsRelationships = this.TreatXrefsAsRelationships, + TreatXrefsAsIsAs = this.TreatXrefsAsIsAs, + RelaxUniqueIdentifierAssumptionForNamespaces = this.RelaxUniqueIdentifierAssumptionForNamespaces, + RelaxUniqueLabelAssumptionForNamespaces = this.RelaxUniqueLabelAssumptionForNamespaces + ) + + /// + /// Creates an OboOntology out of the given header tags alongside the given terms and typedefs. + /// + /// The list of OboTerms the resulting OboOntology shall have. + /// The list of OboTypedefs the resulting OboOntology shall have. + static member toOboOntology terms typedefs (oboOntoloyHeaderTags : OboOntologyHeaderTags) = + oboOntoloyHeaderTags.ToOboOntology(terms, typedefs) \ No newline at end of file From aa7604f7efc62804713f5c3a49c6cdc32ed924ce Mon Sep 17 00:00:00 2001 From: Oliver Maus Date: Tue, 5 Aug 2025 17:22:55 +0200 Subject: [PATCH 10/14] Make OboOntologyHeaderTags a recursive class and give OboOntology a Create method using them --- src/Ontology.NET/OBO/OboOntology.fs | 115 +++++++++++++++------------- 1 file changed, 62 insertions(+), 53 deletions(-) diff --git a/src/Ontology.NET/OBO/OboOntology.fs b/src/Ontology.NET/OBO/OboOntology.fs index 5c9b281..7589f35 100644 --- a/src/Ontology.NET/OBO/OboOntology.fs +++ b/src/Ontology.NET/OBO/OboOntology.fs @@ -91,6 +91,15 @@ type OboOntology = RelaxUniqueLabelAssumptionForNamespaces = defaultArg RelaxUniqueLabelAssumptionForNamespaces [] } + /// + /// Creates an OboOntology out of the given header tags alongside the given terms and typedefs. + /// + /// The list of OboTerms the resulting OboOntology shall have. + /// The list of OboTypedefs the resulting OboOntology shall have. + /// The header tags the resulting OboOntology shall have. + static member Create(terms, typedefs, headerTags : OboOntologyHeaderTags) = + headerTags.ToOboOntology(terms, typedefs) + /// Reads an OBO Ontology containing document header tags, and term and type def stanzas from lines. static member fromLines verbose (input : seq) = @@ -565,58 +574,7 @@ type OboOntology = onto1.ReturnAllEquivalentTerms(onto2) -type OboTermDef = - { - Id : string - Name : string - IsTransitive : string - IsCyclic : string - } - - static member make id name isTransitive isCyclic = - {Id = id; Name = name; IsTransitive = isTransitive; IsCyclic = isCyclic} - - //parseTermDef - static member fromLines (en:Collections.Generic.IEnumerator) id name isTransitive isCyclic = - if en.MoveNext() then - let split = (en.Current |> trimComment).Split([|": "|], System.StringSplitOptions.None) - match split.[0] with - | "id" -> OboTermDef.fromLines en (split.[1..] |> String.concat ": ") name isTransitive isCyclic - | "name" -> OboTermDef.fromLines en id (split.[1..] |> String.concat ": ") isTransitive isCyclic - | "is_transitive" -> OboTermDef.fromLines en id name (split.[1..] |> String.concat ": ") isCyclic - | "is_cyclic" -> OboTermDef.fromLines en id name isTransitive (split.[1..] |> String.concat ": ") - | "" -> OboTermDef.make id name isTransitive isCyclic - | _ -> OboTermDef.fromLines en id name isTransitive isCyclic - else - // Maybe check if id is empty - OboTermDef.make id name isTransitive isCyclic - //failwithf "Unexcpected end of file." - - -/// Functions for parsing and querying an OBO ontology. -module OboOntology = - - /// Parses OBO Terms [Term] from seq. - [] - let parseOboTerms verbose (input:seq) = - - let en = input.GetEnumerator() - let rec loop (en:System.Collections.Generic.IEnumerator) lineNumber = - seq { - match en.MoveNext() with - | true -> - match (en.Current |> trimComment) with - | "[Term]" -> - let lineNumber,parsedTerm = (OboTerm.fromLines verbose en lineNumber "" "" false [] "" "" [] [] [] [] [] [] [] [] false [] [] [] false "" "") - yield parsedTerm - yield! loop en lineNumber - | _ -> yield! loop en (lineNumber + 1) - | false -> () - } - loop en 1 - - -type OboOntologyHeaderTags = +and OboOntologyHeaderTags = { FormatVersion : string @@ -748,4 +706,55 @@ type OboOntologyHeaderTags = /// The list of OboTerms the resulting OboOntology shall have. /// The list of OboTypedefs the resulting OboOntology shall have. static member toOboOntology terms typedefs (oboOntoloyHeaderTags : OboOntologyHeaderTags) = - oboOntoloyHeaderTags.ToOboOntology(terms, typedefs) \ No newline at end of file + oboOntoloyHeaderTags.ToOboOntology(terms, typedefs) + + +type OboTermDef = + { + Id : string + Name : string + IsTransitive : string + IsCyclic : string + } + + static member make id name isTransitive isCyclic = + {Id = id; Name = name; IsTransitive = isTransitive; IsCyclic = isCyclic} + + //parseTermDef + static member fromLines (en:Collections.Generic.IEnumerator) id name isTransitive isCyclic = + if en.MoveNext() then + let split = (en.Current |> trimComment).Split([|": "|], System.StringSplitOptions.None) + match split.[0] with + | "id" -> OboTermDef.fromLines en (split.[1..] |> String.concat ": ") name isTransitive isCyclic + | "name" -> OboTermDef.fromLines en id (split.[1..] |> String.concat ": ") isTransitive isCyclic + | "is_transitive" -> OboTermDef.fromLines en id name (split.[1..] |> String.concat ": ") isCyclic + | "is_cyclic" -> OboTermDef.fromLines en id name isTransitive (split.[1..] |> String.concat ": ") + | "" -> OboTermDef.make id name isTransitive isCyclic + | _ -> OboTermDef.fromLines en id name isTransitive isCyclic + else + // Maybe check if id is empty + OboTermDef.make id name isTransitive isCyclic + //failwithf "Unexcpected end of file." + + +/// Functions for parsing and querying an OBO ontology. +module OboOntology = + + /// Parses OBO Terms [Term] from seq. + [] + let parseOboTerms verbose (input:seq) = + + let en = input.GetEnumerator() + let rec loop (en:System.Collections.Generic.IEnumerator) lineNumber = + seq { + match en.MoveNext() with + | true -> + match (en.Current |> trimComment) with + | "[Term]" -> + let lineNumber,parsedTerm = (OboTerm.fromLines verbose en lineNumber "" "" false [] "" "" [] [] [] [] [] [] [] [] false [] [] [] false "" "") + yield parsedTerm + yield! loop en lineNumber + | _ -> yield! loop en (lineNumber + 1) + | false -> () + } + loop en 1 \ No newline at end of file From e2a4546ec51c2dad65d3d020a8b35559ed950eda Mon Sep 17 00:00:00 2001 From: Oliver Maus Date: Tue, 5 Aug 2025 20:12:49 +0200 Subject: [PATCH 11/14] (WIP) Add method to convert Ontology to OboOntology --- src/Ontology.NET/Ontology.fs | 69 ++++++++++++++++------ tests/Ontology.NET.Tests/Ontology.Tests.fs | 6 ++ 2 files changed, 58 insertions(+), 17 deletions(-) diff --git a/src/Ontology.NET/Ontology.fs b/src/Ontology.NET/Ontology.fs index f38b2d9..3a9772a 100644 --- a/src/Ontology.NET/Ontology.fs +++ b/src/Ontology.NET/Ontology.fs @@ -8,6 +8,7 @@ open Graphoscope open GraphoscopeAux open Ontology.NET.OBO +open type RelationType module internal OntologyGraphHelpers = @@ -130,6 +131,57 @@ type Ontology() = |> Seq.map Ontology.fromOboOntology |> Ontology.mergeAll + /// + /// Creates an OboOntology from the Ontology. Incorporates the given header tags if present. + /// + /// Optional. The header tags of the resulting OboOntology. Default is empty. + member this.ToOboOntology(?headerTags) = + + let ht = Option.defaultValue (OboOntologyHeaderTags.createDefault ()) headerTags + + let rec innerLoop (tt : string) (rts : RelationType list) rs ias xs = + match rts with + | h :: t -> + match h with + | IsA -> innerLoop tt t rs (tt :: ias) xs + | Xref -> innerLoop tt t rs ias (tt :: xs) + | Term cvt -> innerLoop tt t ((tt,cvt.Name) :: rs) ias xs + | Custom c -> innerLoop tt t ((tt, c) :: rs) ias xs + | [] -> rs, ias, xs + + let rec outerLoop (input : (string * RelationType Set) list) rs ias xs = + match input with + | (tt,rts) :: t -> innerLoop tt (Seq.toList rts) rs ias xs + | [] -> rs, ias, xs + + let terms = + this.GetTerms() + |> Seq.map snd + |> Seq.map ( + fun cvt -> + let relations = this.GetTargetTermRelations(cvt.Accession) + let relationshipsRaw, isAs, xrefsRaw = outerLoop (List.ofSeq relations) [] [] [] + let relationshipsProcessed = relationshipsRaw |> List.map (fun (tt,rsn) -> OboTerm.constructRelationship tt rsn) + let xrefsProcessed = xrefsRaw |> List.map DBXref.ofString + OboTerm.Create( + cvt.Accession, + Name = cvt.Name, + Relationships = relationshipsProcessed, + IsA = isAs, + Xrefs = xrefsProcessed + ) + ) + + OboOntology.Create(Seq.toList terms, [], ht) + + /// + /// Creates an OboOntology from the given Ontology. Incorporates the given header tags if present. + /// + /// Optional. The header tags of the resulting OboOntology. Default is empty. + /// The Ontology that shall be used as the basis of the resulting OboOntology. + static member toOboOntology headerTags (onto : Ontology) = + onto.ToOboOntology(headerTags) + /// /// Takes a sequence of triplets and returns the corresponding Ontology. /// @@ -147,23 +199,6 @@ type Ontology() = onto - //static member toOboOntology headerData (onto : Ontology) = - - //let terms = - // onto.GetTerms() - // |> Seq.map snd - // |> Seq.map ( - // fun cvt -> - // let relations = onto.GetTargetTermRelations(cvt.Accession) - // OboTerm.Create( - // cvt.Accession, - // Name = cvt.Name - // Relationships = - // ) - // ) - - //OboOntology.Create - // basic functionality: diff --git a/tests/Ontology.NET.Tests/Ontology.Tests.fs b/tests/Ontology.NET.Tests/Ontology.Tests.fs index 5154619..a6e6103 100644 --- a/tests/Ontology.NET.Tests/Ontology.Tests.fs +++ b/tests/Ontology.NET.Tests/Ontology.Tests.fs @@ -332,4 +332,10 @@ module OntologyTests = Expect.sequenceEqual actual expected "Ontologies differ but they shouldn't" ] + testList "toOboOntology" [ + testCase "returns correct OboOntology" <| fun _ -> + let oboOntoHeaderTags = OBO.OboOntologyHeaderTags.create + let actual = ReferenceObjects.testOnto1.ToOntology() + ] + ] \ No newline at end of file From d81d80afc31c537c240551ccdd336d8a31e3f46d Mon Sep 17 00:00:00 2001 From: Oliver Maus Date: Wed, 6 Aug 2025 16:32:42 +0200 Subject: [PATCH 12/14] Add Create method for OboOntologyHeaderTags --- src/Ontology.NET/OBO/OboOntology.fs | 47 ++++++++++++++++++++++++++++- 1 file changed, 46 insertions(+), 1 deletion(-) diff --git a/src/Ontology.NET/OBO/OboOntology.fs b/src/Ontology.NET/OBO/OboOntology.fs index 7589f35..caab132 100644 --- a/src/Ontology.NET/OBO/OboOntology.fs +++ b/src/Ontology.NET/OBO/OboOntology.fs @@ -13,7 +13,7 @@ open System open System.IO -/// Ontology containing OBO Terms and OBO Typedefs (OBO 1.2). +/// Ontology containing OBO Terms and OBO Typedefs (OBO 1.4). type OboOntology = { @@ -644,6 +644,51 @@ and OboOntologyHeaderTags = RelaxUniqueLabelAssumptionForNamespaces = relaxUniqueLabelAssumptionForNamespaces } + // Param descriptions taken and adapted from the OBO flat file format 1.4: https://owlcollab.github.io/oboformat/doc/GO.format.obo-1_4.html + /// + /// Creates the header tags for an OboOntology. + /// + /// Gives the OBO specification version that this OboOntology uses. This is useful if tag semantics change from one OBO specification version to the next. + /// Gives the version of the current OboOntology. This gets translated to versionIRI in OWL. + /// The ID space of this OboOntology. This should correspond to to the ID prefix of the terms that belong to that ontology, translated to lowercase. For GO, the value of this field will be "go". For the cell ontology (i.e. the ontology that contains CL:0000001), the value will be "cl". If the obo document contains some alternative cut or extension of the ontology (for example, a GO slim, or an ontology merged with another), then the ontology should be of form "X/Y", where X is the basic ontology name, and Y identifies the cut. For example go/gosubset_prok. A URI is also permitted in here. In the translation to OWL, the usual default prefix rules will apply, with the ".owl" suffix. E.g. "go" will be treated as "http://purl.obo-library.org/obo/go.owl". + /// The current date in dd:MM:yyyy HH:mm format (note: for historic reasons, this is NOT a ISO 8601 date, as is the case for the creation-date field). + /// The username of the person to last save this OboOntology. The meaning of "username" is entirely up to the application that generated the file. + /// The program that generated the OboOntology. + /// A description of a term subset. The value for this tag should contain a subset name, a space, and a quote enclosed subset description. + /// A url or ontology ID referencing another OBO document. + /// A description of a user-defined synonym type. The value for this tag should contain a synonym type name, a space, a quote enclosed description, and an optional scope specifier. + /// A mapping between a "local" ID space and a "global" ID space. The value for this tag should be a local idspace, a space, a URI, optionally followed by a quote-enclosed description. + /// Any relationship lacking an ID space will be prefixed with the value of this tag. + /// Maps a Term or Typedef ID to another Term or Typedef ID. The main reason for this tag is to increase interoperability between different OBO ontologies. + /// General comments for this file. This tag is differentiated from a ! comment in that the contents of a remark tag are guaranteed to be preserved by a parser. + /// The value for this tag should contain an ID Space. + /// The value for this tag should contain an ID Space followed by a relation and then a class filler. + /// The value for this tag should contain an ID Space followed by a relation ID. + /// The value for this tag should contain an ID Space. + /// The value for this tag should be a namespace. By default, an OBO namespace (note: not ID-space) partitions all the entities such that no two entities belonging to the same namespace may be equivalent. This header tag relaxes this assumption. + /// + static member Create(formatVersion, ?DataVersion, ?Ontology, ?Date, ?SavedBy, ?AutoGeneratedBy, ?Subsetdefs, ?Imports, ?Synonymtypedefs, ?Idspaces, ?DefaultRelationshipIdPrefix, ?IdMappings, ?Remarks, ?TreatXrefsAsEquivalents, ?TreatXrefsAsGenusDifferentias, ?TreatXrefsAsRelationships, ?TreatXrefsAsIsAs, ?RelaxUniqueIdentifierAssumptionForNamespaces, ?RelaxUniqueLabelAssumptionFornamespaces) = + OboOntologyHeaderTags.create + formatVersion + DataVersion + Ontology + Date + SavedBy + AutoGeneratedBy + (Option.defaultValue [] Subsetdefs) + (Option.defaultValue [] Imports) + (Option.defaultValue [] Synonymtypedefs) + (Option.defaultValue [] Idspaces) + DefaultRelationshipIdPrefix + (Option.defaultValue [] IdMappings) + (Option.defaultValue [] Remarks) + (Option.defaultValue [] TreatXrefsAsEquivalents) + (Option.defaultValue [] TreatXrefsAsGenusDifferentias) + (Option.defaultValue [] TreatXrefsAsRelationships) + (Option.defaultValue [] TreatXrefsAsIsAs) + (Option.defaultValue [] RelaxUniqueIdentifierAssumptionForNamespaces) + (Option.defaultValue [] RelaxUniqueLabelAssumptionFornamespaces) + /// /// Creates an empty OboOntologyHeaderTag object. /// From 32bec8afa7924a76606478699b36cf2b36bc23f7 Mon Sep 17 00:00:00 2001 From: Oliver Maus Date: Wed, 6 Aug 2025 18:27:55 +0200 Subject: [PATCH 13/14] (WIP) Testing unit tests --- tests/Ontology.NET.Tests/Ontology.Tests.fs | 62 ++++++++++++++-------- 1 file changed, 41 insertions(+), 21 deletions(-) diff --git a/tests/Ontology.NET.Tests/Ontology.Tests.fs b/tests/Ontology.NET.Tests/Ontology.Tests.fs index a6e6103..5e7fa0a 100644 --- a/tests/Ontology.NET.Tests/Ontology.Tests.fs +++ b/tests/Ontology.NET.Tests/Ontology.Tests.fs @@ -7,6 +7,10 @@ open FSharpAux open ControlledVocabulary open Ontology.NET +open Ontology.NET.OBO + + +open type Ontology.NET.RelationType module OntologyTests = @@ -124,25 +128,25 @@ module OntologyTests = ] testList "GetTargetTermsWithXrefsBy" [ - testCase "returns all target terms and their Xrefs correctly, Case: Hund is_a ..." <| fun _ -> - let actual = ReferenceObjects.testOnto2.GetTargetTermsWithXrefsBy("Hund", fun _ _ relations -> Set.contains IsA relations) |> Seq.toList - let expected = ["Räuber"; "Höhere Säugetiere"; "Höhere Säuger"; "Eutheria"; "Raubtiere"; "Carnivora"; "Laurasiatheria"] - Expect.sequenceEqual actual expected "Target terms and/or their Xrefs differ" - - testCase "returns all target terms and their Xrefs correctly, Case: Carnivora Sprache ..." <| fun _ -> - let actual = ReferenceObjects.testOnto2.GetTargetTermsWithXrefsBy("Carnivora", fun _ _ relations -> Set.contains (Custom "Sprache") relations) |> Seq.toList - let expected = ["Latein"; "Lateinisch"] - Expect.sequenceEqual actual expected "Target terms and/or their Xrefs differ" - - testCase "returns all target terms and their Xrefs correctly, Case: Lateinisch ist nicht ..." <| fun _ -> - let actual = ReferenceObjects.testOnto2.GetTargetTermsWithXrefsBy("Lateinisch", fun _ _ relations -> Set.contains (Custom "ist nicht") relations) |> Seq.toList - let expected = ["Deutsch"] - Expect.sequenceEqual actual expected "Target terms and/or their Xrefs differ" - - testCase "returns all target terms and their Xrefs correctly, Case: term ID has space(s)" <| fun _ -> - let actual = ReferenceObjects.testOnto2.GetTargetTermsWithXrefsBy("Eutheria", fun termID _ _ -> String.contains " " termID) |> Seq.toList - let expected = ["Höhere Säugetiere"; "Höhere Säuger"] - Expect.sequenceEqual actual expected "Target terms and/or their Xrefs differ" + testCase "returns all target terms and their Xrefs correctly, Case: Hund is_a ..." <| fun _ -> + let actual = ReferenceObjects.testOnto2.GetTargetTermsWithXrefsBy("Hund", fun _ _ relations -> Set.contains IsA relations) |> Seq.toList + let expected = ["Räuber"; "Höhere Säugetiere"; "Höhere Säuger"; "Eutheria"; "Raubtiere"; "Carnivora"; "Laurasiatheria"] + Expect.sequenceEqual actual expected "Target terms and/or their Xrefs differ" + + testCase "returns all target terms and their Xrefs correctly, Case: Carnivora Sprache ..." <| fun _ -> + let actual = ReferenceObjects.testOnto2.GetTargetTermsWithXrefsBy("Carnivora", fun _ _ relations -> Set.contains (Custom "Sprache") relations) |> Seq.toList + let expected = ["Latein"; "Lateinisch"] + Expect.sequenceEqual actual expected "Target terms and/or their Xrefs differ" + + testCase "returns all target terms and their Xrefs correctly, Case: Lateinisch ist nicht ..." <| fun _ -> + let actual = ReferenceObjects.testOnto2.GetTargetTermsWithXrefsBy("Lateinisch", fun _ _ relations -> Set.contains (Custom "ist nicht") relations) |> Seq.toList + let expected = ["Deutsch"] + Expect.sequenceEqual actual expected "Target terms and/or their Xrefs differ" + + testCase "returns all target terms and their Xrefs correctly, Case: term ID has space(s)" <| fun _ -> + let actual = ReferenceObjects.testOnto2.GetTargetTermsWithXrefsBy("Eutheria", fun termID _ _ -> String.contains " " termID) |> Seq.toList + let expected = ["Höhere Säugetiere"; "Höhere Säuger"] + Expect.sequenceEqual actual expected "Target terms and/or their Xrefs differ" ] testList "GetTargetTermsWithDepth" [ @@ -334,8 +338,24 @@ module OntologyTests = testList "toOboOntology" [ testCase "returns correct OboOntology" <| fun _ -> - let oboOntoHeaderTags = OBO.OboOntologyHeaderTags.create - let actual = ReferenceObjects.testOnto1.ToOntology() + + let oboOntoHeaderTags = OboOntologyHeaderTags.Create("1.4", Ontology = "test") + let terms = [ + OboTerm.Create("test:01", "Frosch", Xrefs = [{Name = "test:02"; Description = ""; Modifiers = ""}], IsA = ["test:04"]) + OboTerm.Create("test:02", "Kröte", IsA = ["test:04"]) + OboTerm.Create("test:03", "Quakendes Geschöpf", IsA = ["test:04"], Xrefs = [{Name = "test:02"; Description = ""; Modifiers = ""}]) + OboTerm.Create("test:04", "Tier") + ] + + let prepare terms = + terms + |> List.map (fun ot -> ot.Id, ot.Name, ot.IsA, ot.Relationships) + + let actual = ReferenceObjects.testOnto1.ToOboOntology(oboOntoHeaderTags) + let expected = OboOntology.Create(terms, [], oboOntoHeaderTags) + Expect.sequenceEqual (prepare actual.Terms) (prepare expected.Terms) "Terms differ" + Expect.equal actual.FormatVersion expected.FormatVersion "format-version differs" + Expect.equal actual.Ontology expected.Ontology "ontology (i.e., ontology name) differs" ] ] \ No newline at end of file From 0e4fe27aee745a9b6200335e034fe3b5232ca091 Mon Sep 17 00:00:00 2001 From: omaus Date: Thu, 7 Aug 2025 17:07:21 +0200 Subject: [PATCH 14/14] Fix bug in ToOboOntology method --- src/Ontology.NET/Ontology.fs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/Ontology.NET/Ontology.fs b/src/Ontology.NET/Ontology.fs index 3a9772a..0746744 100644 --- a/src/Ontology.NET/Ontology.fs +++ b/src/Ontology.NET/Ontology.fs @@ -151,7 +151,9 @@ type Ontology() = let rec outerLoop (input : (string * RelationType Set) list) rs ias xs = match input with - | (tt,rts) :: t -> innerLoop tt (Seq.toList rts) rs ias xs + | (tt,rts) :: t -> + let newRs, newIas, newXs = innerLoop tt (Seq.toList rts) rs ias xs + outerLoop t newRs newIas newXs | [] -> rs, ias, xs let terms =