@@ -5,15 +5,12 @@ import java.lang.{Runtime => JRuntime}
55import java .nio .charset .StandardCharsets
66import java .security .MessageDigest
77import java .util .Base64
8-
98import caseapp ._
10- import monix .eval .Task
11- import monix .execution .Scheduler .Implicits .global
12- import monix .reactive ._
139import org .apache .jena .graph .{Node , NodeFactory , Triple }
1410import org .apache .jena .riot .RDFFormat
1511import org .apache .jena .riot .system .{StreamRDF , StreamRDFWriter }
1612import org .apache .jena .vocabulary .{OWL2 , RDF , RDFS }
13+ import org .geneontology .whelk .BuiltIn .{Bottom , Top }
1714import org .geneontology .whelk ._
1815import org .renci .relationgraph .Config .{OWLMode , RDFMode }
1916import org .semanticweb .owlapi .apibinding .OWLFunctionalSyntaxFactory .{OWLNothing , OWLThing }
@@ -22,7 +19,7 @@ import org.semanticweb.owlapi.model._
2219import org .semanticweb .owlapi .model .parameters .Imports
2320import zio ._
2421import zio .blocking ._
25- import zio .interop . monix ._
22+ import zio .stream ._
2623
2724import scala .io .Source
2825import scala .jdk .CollectionConverters ._
@@ -37,7 +34,6 @@ object Main extends ZCaseApp[Config] {
3734 private val OWLSomeValuesFrom = OWL2 .someValuesFrom.asNode
3835 private val OWLOntology = OWL2 .Ontology .asNode
3936 private val df = OWLManager .getOWLDataFactory
40- private val OWLTopObjectProperty = df.getOWLTopObjectProperty
4137
4238 override def run (config : Config , arg : RemainingArgs ): ZIO [ZEnv , Nothing , ExitCode ] = {
4339 val ontologyFile = new File (config.ontologyFile)
@@ -65,18 +61,22 @@ object Main extends ZCaseApp[Config] {
6561 effectBlockingIO(redundantRDFWriter.triple(Triple .create(NodeFactory .createBlankNode(" redundant" ), RDFType , OWLOntology ))))
6662 .when(config.mode == OWLMode )
6763 start <- ZIO .effectTotal(System .currentTimeMillis())
68- classes = allClasses(ontology)
69- classesTasks = classes.map(c => Task (processSuperclasses(c, whelk, config)))
70- restrictions = extractAllRestrictions(ontology, specifiedProperties)
71- restrictionsTasks = restrictions.map(r => Task (processRestriction(r, whelk, config.mode)))
72- allTasks = classesTasks ++ restrictionsTasks
73- processed = allTasks.mapParallelUnordered(JRuntime .getRuntime.availableProcessors)(identity)
74- monixTask = processed.foreachL {
64+ classes = classHierarchy(whelk)
65+ properties = propertyHierarchy(ontology)
66+ classesStream = allClasses(ontology)
67+ classesTasks = classesStream.map(c => ZIO .effectTotal(processSuperclasses(c, whelk, config)))
68+ queue <- Queue .unbounded[Restriction ]
69+ _ <- traverse(properties, classes, whelk, config.mode, queue)
70+ restrictionsStream = Stream .fromQueue(queue).map(r => processRestrictionAndExtendQueue(r, classes, whelk, config.mode, queue))
71+ allTasks = classesTasks ++ restrictionsStream
72+ processed = allTasks.mapMParUnordered(JRuntime .getRuntime.availableProcessors)(identity)
73+ _ <- processed.foreach {
7574 case TriplesGroup (nonredundant, redundant) =>
76- nonredundant.foreach(nonredundantRDFWriter.triple)
77- redundant.foreach(redundantRDFWriter.triple)
75+ ZIO .effect {
76+ nonredundant.foreach(nonredundantRDFWriter.triple)
77+ redundant.foreach(redundantRDFWriter.triple)
78+ }
7879 }
79- _ <- IO .fromTask(monixTask)
8080 stop <- ZIO .effectTotal(System .currentTimeMillis())
8181 _ <- ZIO .effectTotal(scribe.info(s " Computed relations in ${(stop - start) / 1000.0 }s " ))
8282 } yield ()
@@ -98,7 +98,87 @@ object Main extends ZCaseApp[Config] {
9898 }
9999 }(stream => ZIO .effectTotal(stream.finish()))
100100
101- def allClasses (ont : OWLOntology ): Observable [OWLClass ] = Observable .fromIterable(ont.getClassesInSignature(Imports .INCLUDED ).asScala.to(Set ) - OWLThing - OWLNothing )
101+ def allClasses (ont : OWLOntology ): ZStream [Any , Nothing , OWLClass ] = Stream .fromIterable(ont.getClassesInSignature(Imports .INCLUDED ).asScala.to(Set ) - OWLThing - OWLNothing )
102+
103+ def traverse (properties : Hierarchy , classes : Hierarchy , reasoner : ReasonerState , mode : Config .OutputMode , queue : Queue [Restriction ]): UIO [Unit ] =
104+ ZIO .foreach_(properties.subclasses.getOrElse(Top , Set .empty)) { subprop =>
105+ traverseProperty(subprop, properties, classes, reasoner, mode, queue)
106+ }
107+
108+ def traverseProperty (property : AtomicConcept , properties : Hierarchy , classes : Hierarchy , whelk : ReasonerState , mode : Config .OutputMode , queue : Queue [Restriction ]): UIO [Unit ] = {
109+ val restrictions = if (hasSubClasses(property, whelk)) {
110+ (classes.subclasses.getOrElse(Top , Set .empty) - Bottom ).map(filler => Restriction (Role (property.id), filler))
111+ } else Set .empty
112+ queue.offerAll(restrictions).unit
113+ }
114+
115+ def hasSubClasses (property : AtomicConcept , whelk : ReasonerState ): Boolean = {
116+ val queryConcept = AtomicConcept (s " ${property.id}${Top .id}" )
117+ val restriction = ExistentialRestriction (Role (property.id), Top )
118+ val axioms = Set (ConceptInclusion (queryConcept, restriction), ConceptInclusion (restriction, queryConcept))
119+ val updatedWhelk = Reasoner .assert(axioms, whelk)
120+ (updatedWhelk.closureSubsBySuperclass.getOrElse(queryConcept, Set .empty) - Bottom ).nonEmpty
121+ }
122+
123+ def processRestrictionAndExtendQueue (restriction : Restriction , classes : Hierarchy , whelk : ReasonerState , mode : Config .OutputMode , queue : Queue [Restriction ]): UIO [TriplesGroup ] = {
124+ for {
125+ triples <- ZIO .effectTotal(processRestriction(restriction, whelk, mode))
126+ directFillerSubclassesRestrictions = if (triples.redundant.nonEmpty) (classes.subclasses.getOrElse(restriction.filler, Set .empty) - Bottom ).map(c => Restriction (restriction.property, c))
127+ else Set .empty
128+ _ <- queue.offerAll(directFillerSubclassesRestrictions)
129+ } yield triples
130+ }
131+
132+ def processRestriction (restriction : Restriction , whelk : ReasonerState , mode : Config .OutputMode ): TriplesGroup = {
133+ println(s " Querying: ${restriction.property.id} || ${restriction.filler.id}" )
134+ val queryConcept = AtomicConcept (s " ${restriction.property.id}${restriction.filler.id}" )
135+ val er = ExistentialRestriction (restriction.property, restriction.filler)
136+ val axioms = Set (ConceptInclusion (queryConcept, er), ConceptInclusion (er, queryConcept))
137+ val updatedWhelk = Reasoner .assert(axioms, whelk)
138+ // FIXME don't create these if result is empty
139+ val predicate = NodeFactory .createURI(restriction.property.id)
140+ val target = NodeFactory .createURI(restriction.filler.id)
141+ val (equivalents, directSubclasses) = updatedWhelk.directlySubsumes(queryConcept)
142+ val subclasses =
143+ updatedWhelk.closureSubsBySuperclass(queryConcept).collect { case x : AtomicConcept => x } - queryConcept - Bottom
144+ if (! equivalents(BuiltIn .Bottom )) {
145+ val nonredundantTerms = directSubclasses - BuiltIn .Bottom ++ equivalents
146+ val nonredundantTriples = mode match {
147+ case RDFMode => nonredundantTerms.map(sc => Triple .create(NodeFactory .createURI(sc.id), predicate, target))
148+ case OWLMode => nonredundantTerms.flatMap(sc => owlTriples(NodeFactory .createURI(sc.id), predicate, target))
149+ }
150+ val redundantTriples = mode match {
151+ case RDFMode => subclasses.map(sc => Triple .create(NodeFactory .createURI(sc.id), predicate, target))
152+ case OWLMode => subclasses.flatMap(sc => owlTriples(NodeFactory .createURI(sc.id), predicate, target))
153+ }
154+ TriplesGroup (nonredundantTriples, redundantTriples)
155+ } else TriplesGroup .empty
156+ }
157+
158+ def classHierarchy (reasoner : ReasonerState ): Hierarchy = {
159+ val taxonomy = reasoner.computeTaxonomy
160+ val subclassTaxonomy = taxonomy.foldLeft(Map .empty[AtomicConcept , Set [AtomicConcept ]]) { case (accum, (concept, (_, superclasses))) =>
161+ superclasses.foldLeft(accum) { case (inner, superclass) =>
162+ val updatedSubclasses = accum.getOrElse(superclass, Set .empty) + concept
163+ inner.updated(superclass, updatedSubclasses)
164+ }
165+ }
166+ val equivMap = taxonomy.map { case (concept, (equivs, _)) => concept -> equivs }
167+ Hierarchy (equivMap, subclassTaxonomy)
168+ }
169+
170+ def propertyHierarchy (ont : OWLOntology ): Hierarchy = {
171+ val subPropAxioms = ont.getAxioms(AxiomType .SUB_OBJECT_PROPERTY ).asScala.to(Set ).collect {
172+ case ax if ax.getSubProperty.isNamed && ax.getSuperProperty.isNamed => ConceptInclusion (
173+ AtomicConcept (ax.getSubProperty.asOWLObjectProperty.getIRI.toString),
174+ AtomicConcept (ax.getSuperProperty.asOWLObjectProperty.getIRI.toString))
175+ }
176+ val allProps = ont.getObjectPropertiesInSignature((Imports .INCLUDED )).asScala.to(Set ).map(prop =>
177+ ConceptInclusion (AtomicConcept (prop.getIRI.toString), AtomicConcept (prop.getIRI.toString)))
178+ val allAxioms = (subPropAxioms ++ allProps).toSet[Axiom ]
179+ val whelk = Reasoner .assert(allAxioms)
180+ classHierarchy(whelk)
181+ }
102182
103183 def processSuperclasses (cls : OWLClass , whelk : ReasonerState , config : Config ): TriplesGroup = {
104184 val subject = NodeFactory .createURI(cls.getIRI.toString)
@@ -127,45 +207,6 @@ object Main extends ZCaseApp[Config] {
127207 }
128208 }
129209
130- def extractAllRestrictions (ont : OWLOntology , specifiedProperties : Set [OWLObjectProperty ]): Observable [Restriction ] = {
131- val properties =
132- if (specifiedProperties.nonEmpty) specifiedProperties
133- else ont.getObjectPropertiesInSignature(Imports .INCLUDED ).asScala.to(Set ) - OWLTopObjectProperty
134- val propertiesStream = Observable .fromIterable(properties)
135- val classesStream = allClasses(ont)
136- for {
137- property <- propertiesStream
138- cls <- classesStream
139- } yield Restriction (property, cls)
140- }
141-
142- def processRestriction (combo : Restriction , whelk : ReasonerState , mode : Config .OutputMode ): TriplesGroup = {
143- val Restriction (property, cls) = combo
144- val propertyID = property.getIRI.toString
145- val clsID = cls.getIRI.toString
146- val queryConcept = AtomicConcept (s " $propertyID$clsID" )
147- val restriction = ExistentialRestriction (Role (propertyID), AtomicConcept (clsID))
148- val axioms = Set (ConceptInclusion (queryConcept, restriction), ConceptInclusion (restriction, queryConcept))
149- val updatedWhelk = Reasoner .assert(axioms, whelk)
150- val predicate = NodeFactory .createURI(property.getIRI.toString)
151- val target = NodeFactory .createURI(cls.getIRI.toString)
152- val (equivalents, directSubclasses) = updatedWhelk.directlySubsumes(queryConcept)
153- val subclasses =
154- updatedWhelk.closureSubsBySuperclass(queryConcept).collect { case x : AtomicConcept => x } - queryConcept - BuiltIn .Bottom
155- if (! equivalents(BuiltIn .Bottom )) {
156- val nonredundantTerms = directSubclasses - BuiltIn .Bottom ++ equivalents
157- val nonredundantTriples = mode match {
158- case RDFMode => nonredundantTerms.map(sc => Triple .create(NodeFactory .createURI(sc.id), predicate, target))
159- case OWLMode => nonredundantTerms.flatMap(sc => owlTriples(NodeFactory .createURI(sc.id), predicate, target))
160- }
161- val redundantTriples = mode match {
162- case RDFMode => subclasses.map(sc => Triple .create(NodeFactory .createURI(sc.id), predicate, target))
163- case OWLMode => subclasses.flatMap(sc => owlTriples(NodeFactory .createURI(sc.id), predicate, target))
164- }
165- TriplesGroup (nonredundantTriples, redundantTriples)
166- } else TriplesGroup .empty
167- }
168-
169210 def owlTriples (subj : Node , pred : Node , obj : Node ): Set [Triple ] = {
170211 val hash = MessageDigest .getInstance(" SHA-256" ).digest(s " $subj$pred$obj" .getBytes(StandardCharsets .UTF_8 ))
171212 val id = Base64 .getEncoder.encodeToString(hash)
@@ -178,7 +219,9 @@ object Main extends ZCaseApp[Config] {
178219 )
179220 }
180221
181- final case class Restriction (property : OWLObjectProperty , filler : OWLClass )
222+ final case class Hierarchy (equivs : Map [AtomicConcept , Set [AtomicConcept ]], subclasses : Map [AtomicConcept , Set [AtomicConcept ]])
223+
224+ final case class Restriction (property : Role , filler : AtomicConcept )
182225
183226 final case class TriplesGroup (nonredundant : Set [Triple ], redundant : Set [Triple ])
184227
0 commit comments