Skip to content

Commit c173eda

Browse files
authored
Merge pull request #3 from balhoff/add-subclass
option to output subclass relationships
2 parents 4e1a30b + de02a01 commit c173eda

4 files changed

Lines changed: 94 additions & 16 deletions

File tree

build.sbt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,9 @@ libraryDependencies ++= {
2424
"dev.zio" %% "zio-streams" % zioVersion,
2525
"dev.zio" %% "zio-interop-monix" % "3.2.2.0-RC2",
2626
"io.monix" %% "monix" % "3.2.2",
27-
"org.geneontology" %% "whelk-owlapi" % "1.0.2",
27+
"org.geneontology" %% "whelk-owlapi" % "1.0.3",
2828
"com.outr" %% "scribe-slf4j" % "2.7.12",
29-
"com.github.alexarchambault" %% "case-app" % "2.0.3",
29+
"com.github.alexarchambault" %% "case-app" % "2.0.4",
3030
"org.apache.jena" % "apache-jena-libs" % "3.16.0" exclude ("org.slf4j", "slf4j-log4j12"),
3131
"dev.zio" %% "zio-test" % zioVersion % Test,
3232
"dev.zio" %% "zio-test-sbt" % zioVersion % Test

src/main/scala/org/renci/relationgraph/Config.scala

Lines changed: 39 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,19 @@ package org.renci.relationgraph
22

33
import caseapp.core.Error.MalformedValue
44
import caseapp.core.argparser.{ArgParser, SimpleArgParser}
5+
import org.renci.relationgraph.Config.{BoolValue, FalseValue, TrueValue}
56

67
final case class Config(ontologyFile: String,
78
nonRedundantOutputFile: String,
89
redundantOutputFile: String,
910
mode: Config.OutputMode = Config.RDFMode,
1011
property: List[String] = Nil,
11-
propertiesFile: Option[String])
12+
propertiesFile: Option[String],
13+
outputSubclasses: BoolValue = FalseValue,
14+
reflexiveSubclasses: BoolValue = TrueValue,
15+
equivalenceAsSubclass: BoolValue = TrueValue) {
16+
17+
}
1218

1319
object Config {
1420

@@ -24,10 +30,41 @@ object Config {
2430
arg.toLowerCase match {
2531
case "rdf" => Right(RDFMode)
2632
case "owl" => Right(OWLMode)
27-
case _ => Left(MalformedValue("output mode", arg))
33+
case _ => Left(MalformedValue("output mode", arg))
2834
}
2935
}
3036

3137
}
3238

39+
/**
40+
* This works around some confusing behavior in case-app boolean parsing
41+
*/
42+
sealed trait BoolValue {
43+
44+
def bool: Boolean
45+
46+
}
47+
48+
case object TrueValue extends BoolValue {
49+
50+
def bool = true
51+
52+
}
53+
54+
case object FalseValue extends BoolValue {
55+
56+
def bool = false
57+
58+
}
59+
60+
implicit val argParser: ArgParser[BoolValue] = SimpleArgParser.from[BoolValue]("boolean value") { arg =>
61+
arg.toLowerCase match {
62+
case "true" => Right(TrueValue)
63+
case "false" => Right(FalseValue)
64+
case "1" => Right(TrueValue)
65+
case "0" => Right(FalseValue)
66+
case _ => Left(MalformedValue("boolean value", arg))
67+
}
68+
}
69+
3370
}

src/main/scala/org/renci/relationgraph/Main.scala

Lines changed: 50 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ object Main extends ZCaseApp[Config] {
3131

3232
private val RDFType = RDF.`type`.asNode
3333
private val RDFSSubClassOf = RDFS.subClassOf.asNode
34+
private val OWLEquivalentClass = OWL2.equivalentClass.asNode
3435
private val OWLRestriction = OWL2.Restriction.asNode
3536
private val OWLOnProperty = OWL2.onProperty.asNode
3637
private val OWLSomeValuesFrom = OWL2.someValuesFrom.asNode
@@ -60,15 +61,18 @@ object Main extends ZCaseApp[Config] {
6061
whelk = Reasoner.assert(whelkOntology)
6162
_ <- ZIO.effectTotal(scribe.info("Done running reasoner"))
6263
_ <- (effectBlockingIO(
63-
nonredundantRDFWriter.triple(Triple.create(NodeFactory.createBlankNode("nonredundant"), RDFType, OWLOntology))) *>
64-
effectBlockingIO(redundantRDFWriter.triple(Triple.create(NodeFactory.createBlankNode("redundant"), RDFType, OWLOntology))))
64+
nonredundantRDFWriter.triple(Triple.create(NodeFactory.createBlankNode("nonredundant"), RDFType, OWLOntology))) *>
65+
effectBlockingIO(redundantRDFWriter.triple(Triple.create(NodeFactory.createBlankNode("redundant"), RDFType, OWLOntology))))
6566
.when(config.mode == OWLMode)
6667
start <- ZIO.effectTotal(System.currentTimeMillis())
68+
classes = allClasses(ontology)
69+
classesTasks = classes.map(c => Task(processSuperclasses(c, whelk, config)))
6770
restrictions = extractAllRestrictions(ontology, specifiedProperties)
68-
processed =
69-
restrictions.mapParallelUnordered(JRuntime.getRuntime.availableProcessors)(r => Task(processRestriction(r, whelk, config.mode)))
71+
restrictionsTasks = restrictions.map(r => Task(processRestriction(r, whelk, config.mode)))
72+
allTasks = classesTasks ++ restrictionsTasks
73+
processed = allTasks.mapParallelUnordered(JRuntime.getRuntime.availableProcessors)(identity)
7074
monixTask = processed.foreachL {
71-
case (nonredundant, redundant) =>
75+
case TriplesGroup(nonredundant, redundant) =>
7276
nonredundant.foreach(nonredundantRDFWriter.triple)
7377
redundant.foreach(redundantRDFWriter.triple)
7478
}
@@ -94,20 +98,48 @@ object Main extends ZCaseApp[Config] {
9498
}
9599
}(stream => ZIO.effectTotal(stream.finish()))
96100

101+
def allClasses(ont: OWLOntology): Observable[OWLClass] = Observable.fromIterable(ont.getClassesInSignature(Imports.INCLUDED).asScala.to(Set) - OWLThing - OWLNothing)
102+
103+
def processSuperclasses(cls: OWLClass, whelk: ReasonerState, config: Config): TriplesGroup = {
104+
val subject = NodeFactory.createURI(cls.getIRI.toString)
105+
val concept = AtomicConcept(cls.getIRI.toString)
106+
val allSuperclasses = (whelk.closureSubsBySubclass.getOrElse(concept, Set.empty) - BuiltIn.Top)
107+
.collect { case ac @ AtomicConcept(_) => ac }
108+
if (allSuperclasses(BuiltIn.Bottom)) TriplesGroup.empty //unsatisfiable
109+
else {
110+
val (equivs, directSuperclasses) = whelk.directlySubsumedBy(concept)
111+
val adjustedEquivs = if (config.reflexiveSubclasses.bool) equivs + concept else equivs - concept
112+
val directSuperclassTriples = directSuperclasses.map(c => Triple.create(subject, RDFSSubClassOf, NodeFactory.createURI(c.id)))
113+
val equivalentClassTriples = if (config.equivalenceAsSubclass.bool)
114+
adjustedEquivs.map(c => Triple.create(subject, RDFSSubClassOf, NodeFactory.createURI(c.id)))
115+
else
116+
adjustedEquivs.map(c => Triple.create(subject, OWLEquivalentClass, NodeFactory.createURI(c.id)))
117+
val nonredundantTriples = directSuperclassTriples ++ equivalentClassTriples
118+
val adjustedSuperclasses = if (config.reflexiveSubclasses.bool) allSuperclasses + concept else allSuperclasses - concept
119+
val redundantTriples = if (config.equivalenceAsSubclass.bool)
120+
adjustedSuperclasses.map(c => Triple.create(subject, RDFSSubClassOf, NodeFactory.createURI(c.id)))
121+
else {
122+
val superclassesMinusEquiv = adjustedSuperclasses -- adjustedEquivs
123+
superclassesMinusEquiv.map(c => Triple.create(subject, RDFSSubClassOf, NodeFactory.createURI(c.id))) ++
124+
equivalentClassTriples
125+
}
126+
TriplesGroup(nonredundantTriples, redundantTriples)
127+
}
128+
}
129+
97130
def extractAllRestrictions(ont: OWLOntology, specifiedProperties: Set[OWLObjectProperty]): Observable[Restriction] = {
98131
val properties =
99132
if (specifiedProperties.nonEmpty) specifiedProperties
100133
else ont.getObjectPropertiesInSignature(Imports.INCLUDED).asScala.to(Set) - OWLTopObjectProperty
101-
val classes = ont.getClassesInSignature(Imports.INCLUDED).asScala.to(Set) - OWLThing - OWLNothing
102134
val propertiesStream = Observable.fromIterable(properties)
103-
val classesStream = Observable.fromIterable(classes)
135+
val classesStream = allClasses(ont)
104136
for {
105137
property <- propertiesStream
106138
cls <- classesStream
107139
} yield Restriction(property, cls)
108140
}
109141

110-
def processRestriction(combo: Restriction, whelk: ReasonerState, mode: Config.OutputMode): (Set[Triple], Set[Triple]) = {
142+
def processRestriction(combo: Restriction, whelk: ReasonerState, mode: Config.OutputMode): TriplesGroup = {
111143
val Restriction(property, cls) = combo
112144
val propertyID = property.getIRI.toString
113145
val clsID = cls.getIRI.toString
@@ -130,8 +162,8 @@ object Main extends ZCaseApp[Config] {
130162
case RDFMode => subclasses.map(sc => Triple.create(NodeFactory.createURI(sc.id), predicate, target))
131163
case OWLMode => subclasses.flatMap(sc => owlTriples(NodeFactory.createURI(sc.id), predicate, target))
132164
}
133-
(nonredundantTriples, redundantTriples)
134-
} else (Set.empty[Triple], Set.empty[Triple])
165+
TriplesGroup(nonredundantTriples, redundantTriples)
166+
} else TriplesGroup.empty
135167
}
136168

137169
def owlTriples(subj: Node, pred: Node, obj: Node): Set[Triple] = {
@@ -148,4 +180,12 @@ object Main extends ZCaseApp[Config] {
148180

149181
final case class Restriction(property: OWLObjectProperty, filler: OWLClass)
150182

183+
final case class TriplesGroup(nonredundant: Set[Triple], redundant: Set[Triple])
184+
185+
object TriplesGroup {
186+
187+
val empty: TriplesGroup = TriplesGroup(Set.empty, Set.empty)
188+
189+
}
190+
151191
}

src/test/scala/org/renci/relationgraph/TestRelationGraph.scala

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package org.renci.relationgraph
33
import monix.execution.Scheduler.Implicits.global
44
import org.apache.jena.graph.{Node, NodeFactory, Triple}
55
import org.geneontology.whelk.{Bridge, Reasoner}
6+
import org.renci.relationgraph.Main.TriplesGroup
67
import org.semanticweb.owlapi.apibinding.OWLManager
78
import zio._
89
import zio.interop.monix._
@@ -28,9 +29,9 @@ object TestRelationGraph extends DefaultRunnableSpec {
2829
triples <- IO.fromTask(
2930
restrictions
3031
.map(Main.processRestriction(_, whelk, Config.RDFMode))
31-
.reduce((left, right) => (left._1 ++ right._1, left._2 ++ right._2))
32+
.reduce((left, right) => TriplesGroup(left.nonredundant ++ right.nonredundant, left.redundant ++ right.redundant))
3233
.headL)
33-
(nonredundant, redundant) = triples
34+
TriplesGroup(nonredundant, redundant) = triples
3435
} yield assert(nonredundant)(contains(Triple.create(n(s"$Prefix#A"), P, n(s"$Prefix#D")))) &&
3536
assert(redundant)(contains(Triple.create(n(s"$Prefix#A"), P, n(s"$Prefix#D")))) &&
3637
assert(nonredundant)(not(contains(Triple.create(n(s"$Prefix#C"), P, n(s"$Prefix#D"))))) &&

0 commit comments

Comments
 (0)