@@ -4,6 +4,7 @@ import caseapp._
44import org .apache .jena .graph .{Node , NodeFactory , Triple }
55import org .apache .jena .riot .RDFFormat
66import org .apache .jena .riot .system .{StreamRDF , StreamRDFWriter }
7+ import org .apache .jena .sys .JenaSystem
78import org .apache .jena .vocabulary .{OWL2 , RDF , RDFS }
89import org .geneontology .whelk .BuiltIn .{Bottom , Top }
910import org .geneontology .whelk ._
@@ -26,6 +27,8 @@ import scala.jdk.CollectionConverters._
2627
2728object Main extends ZCaseApp [Config ] {
2829
30+ JenaSystem .init()
31+
2932 private val RDFType = RDF .`type`.asNode
3033 private val RDFSSubClassOf = RDFS .subClassOf.asNode
3134 private val OWLEquivalentClass = OWL2 .equivalentClass.asNode
@@ -35,74 +38,69 @@ object Main extends ZCaseApp[Config] {
3538 private val OWLOntology = OWL2 .Ontology .asNode
3639
3740 override def run (config : Config , arg : RemainingArgs ): ZIO [ZEnv , Nothing , ExitCode ] = {
38- val ontologyFile = new File (config.ontologyFile)
39- val nonRedundantOutputFile = new File (config.nonRedundantOutputFile)
40- val redundantOutputFile = new File (config.redundantOutputFile)
41- val streamsManaged = for {
42- nonredundantOutputStream <- Managed .fromAutoCloseable(ZIO .effect(new FileOutputStream (nonRedundantOutputFile)))
43- redundantOutputStream <- Managed .fromAutoCloseable(ZIO .effect(new FileOutputStream (redundantOutputFile)))
44- nonredundantRDFWriter <- createStreamRDF(nonredundantOutputStream)
45- redundantRDFWriter <- createStreamRDF(redundantOutputStream)
46- } yield (nonredundantRDFWriter, redundantRDFWriter)
47- val program = streamsManaged.use {
48- case (nonredundantRDFWriter, redundantRDFWriter) =>
49- for {
50- fileProperties <- config.propertiesFile.map(readPropertiesFile).getOrElse(ZIO .succeed(Set .empty[AtomicConcept ]))
51- specifiedProperties = fileProperties ++ config.property.map(prop => AtomicConcept (prop)).to(Set )
52- manager <- ZIO .effect(OWLManager .createOWLOntologyManager())
53- ontology <- ZIO .effect(manager.loadOntologyFromOntologyDocument(ontologyFile))
54- whelkOntology = Bridge .ontologyToAxioms(ontology)
55- _ <- ZIO .effectTotal(scribe.info(" Running reasoner" ))
56- whelk = Reasoner .assert(whelkOntology)
57- _ <- ZIO .effectTotal(scribe.info(" Done running reasoner" ))
58- _ <- (effectBlockingIO(
59- nonredundantRDFWriter.triple(Triple .create(NodeFactory .createBlankNode(" nonredundant" ), RDFType , OWLOntology ))) *>
60- effectBlockingIO(redundantRDFWriter.triple(Triple .create(NodeFactory .createBlankNode(" redundant" ), RDFType , OWLOntology ))))
61- .when(config.mode == OWLMode )
62- start <- ZIO .effectTotal(System .currentTimeMillis())
63- processed <- computeRelations(ontology, whelk, specifiedProperties, config.reflexiveSubclasses.bool, config.equivalenceAsSubclass.bool, config.mode)
64- _ <- processed.foreach {
65- case TriplesGroup (nonredundant, redundant) =>
66- ZIO .effect {
67- nonredundant.foreach(nonredundantRDFWriter.triple)
68- redundant.foreach(redundantRDFWriter.triple)
69- }
70- }
71- stop <- ZIO .effectTotal(System .currentTimeMillis())
72- _ <- ZIO .effectTotal(scribe.info(s " Computed relations in ${(stop - start) / 1000.0 }s " ))
73- } yield ()
41+ val streamManaged = for {
42+ outputStream <- Managed .fromAutoCloseable(ZIO .effect(new FileOutputStream (new File (config.outputFile))))
43+ rdfWriter <- createStreamRDF(outputStream)
44+ } yield rdfWriter
45+ val program = streamManaged.use { rdfWriter =>
46+ for {
47+ fileProperties <- config.propertiesFile.map(readPropertiesFile).getOrElse(ZIO .succeed(Set .empty[AtomicConcept ]))
48+ specifiedProperties = fileProperties ++ config.property.map(prop => AtomicConcept (prop)).to(Set )
49+ ontology <- loadOntology(config.ontologyFile)
50+ whelkOntology = Bridge .ontologyToAxioms(ontology)
51+ _ <- ZIO .succeed(scribe.info(" Running reasoner" ))
52+ whelk = Reasoner .assert(whelkOntology)
53+ _ <- ZIO .succeed(scribe.info(" Done running reasoner" ))
54+ _ <- ZIO .fail(new Exception (" Ontology is incoherent; please correct unsatisfiable classes." )).when(isIncoherent(whelk))
55+ _ <- effectBlockingIO(rdfWriter.triple(Triple .create(NodeFactory .createBlankNode(" redundant" ), RDFType , OWLOntology )))
56+ .when(config.mode == OWLMode )
57+ start <- ZIO .succeed(System .currentTimeMillis())
58+ triplesStream = computeRelations(ontology, whelk, specifiedProperties, config.outputSubclasses.bool, config.reflexiveSubclasses.bool, config.equivalenceAsSubclass.bool, config.mode)
59+ _ <- triplesStream.foreach {
60+ case TriplesGroup (triples) => ZIO .effect(triples.foreach(rdfWriter.triple))
61+ }
62+ stop <- ZIO .succeed(System .currentTimeMillis())
63+ _ <- ZIO .succeed(scribe.info(s " Computed relations in ${(stop - start) / 1000.0 }s " ))
64+ } yield ()
7465 }
7566 program.exitCode
7667 }
7768
78- def computeRelations (ontology : OWLOntology , whelk : ReasonerState , specifiedProperties : Set [AtomicConcept ], reflexiveSubclasses : Boolean , equivalenceAsSubclass : Boolean , mode : Config .OutputMode ): UIO [ UStream [TriplesGroup ] ] = {
69+ def computeRelations (ontology : OWLOntology , whelk : ReasonerState , specifiedProperties : Set [AtomicConcept ], outputSubclasses : Boolean , reflexiveSubclasses : Boolean , equivalenceAsSubclass : Boolean , mode : Config .OutputMode ): UStream [TriplesGroup ] = {
7970 val classes = classHierarchy(whelk)
8071 val properties = propertyHierarchy(ontology)
81- val classesStream = allClasses(ontology)
82- val classesTasks = classesStream.map(c => ZIO .effectTotal(processSuperclasses(c, whelk, reflexiveSubclasses, equivalenceAsSubclass)))
83- for {
72+ val classesTasks = if (outputSubclasses) {
73+ allClasses(ontology).map(c => ZIO .succeed(processSuperclasses(c, whelk, reflexiveSubclasses, equivalenceAsSubclass)))
74+ } else Stream .empty
75+ val streamZ = for {
8476 queue <- Queue .unbounded[Restriction ]
8577 activeRestrictions <- Ref .make(0 )
8678 seenRef <- Ref .make(Map .empty[AtomicConcept , Set [AtomicConcept ]])
8779 _ <- traverse(specifiedProperties, properties, classes, queue, activeRestrictions, seenRef)
8880 restrictionsStream = Stream .fromQueue(queue).map(r => processRestrictionAndExtendQueue(r, properties, classes, whelk, mode, specifiedProperties.isEmpty, queue, activeRestrictions, seenRef))
8981 allTasks = classesTasks ++ restrictionsStream
9082 } yield allTasks.mapMParUnordered(JRuntime .getRuntime.availableProcessors)(identity)
83+ Stream .unwrap(streamZ)
9184 }
9285
9386 def readPropertiesFile (file : String ): ZIO [Blocking , Throwable , Set [AtomicConcept ]] =
9487 effectBlocking(Source .fromFile(file, " utf-8" )).bracketAuto { source =>
9588 effectBlocking(source.getLines().map(_.trim).filter(_.nonEmpty).map(line => AtomicConcept (line)).to(Set ))
9689 }
9790
91+ def loadOntology (path : String ): Task [OWLOntology ] = for {
92+ manager <- ZIO .effect(OWLManager .createOWLOntologyManager())
93+ ontology <- ZIO .effect(manager.loadOntologyFromOntologyDocument(new File (path)))
94+ } yield ontology
95+
9896 def createStreamRDF (output : OutputStream ): Managed [Throwable , StreamRDF ] =
9997 Managed .make {
10098 ZIO .effect {
10199 val stream = StreamRDFWriter .getWriterStream(output, RDFFormat .TURTLE_FLAT , null )
102100 stream.start()
103101 stream
104102 }
105- }(stream => ZIO .effectTotal (stream.finish()))
103+ }(stream => ZIO .succeed (stream.finish()))
106104
107105 def allClasses (ont : OWLOntology ): ZStream [Any , Nothing , OWLClass ] = Stream .fromIterable(ont.getClassesInSignature(Imports .INCLUDED ).asScala.to(Set ) - OWLThing - OWLNothing )
108106
@@ -128,9 +126,9 @@ object Main extends ZCaseApp[Config] {
128126 } yield ()
129127 }
130128
131- def processRestrictionAndExtendQueue (restriction : Restriction , properties : Hierarchy , classes : Hierarchy , whelk : ReasonerState , mode : Config .OutputMode , descendProperties : Boolean , queue : Queue [Restriction ], activeRestrictions : Ref [Int ], seenRef : Ref [Map [AtomicConcept , Set [AtomicConcept ]]]): UIO [TriplesGroup ] =
129+ def processRestrictionAndExtendQueue (restriction : Restriction , properties : Hierarchy , classes : Hierarchy , whelk : ReasonerState , mode : Config .OutputMode , descendProperties : Boolean , queue : Queue [Restriction ], activeRestrictions : Ref [Int ], seenRef : Ref [Map [AtomicConcept , Set [AtomicConcept ]]]): UIO [TriplesGroup ] = {
130+ val triples = processRestriction(restriction, whelk, mode)
132131 for {
133- triples <- ZIO .effectTotal(processRestriction(restriction, whelk, mode))
134132 directFillerSubclassesRestrictions <- if (triples.redundant.nonEmpty) seenRef.modify { seen =>
135133 val propertyConcept = AtomicConcept (restriction.property.id)
136134 val seenForThisProperty = seen.getOrElse(propertyConcept, Set .empty)
@@ -155,32 +153,29 @@ object Main extends ZCaseApp[Config] {
155153 active <- activeRestrictions.get
156154 _ <- queue.shutdown.when(active < 1 )
157155 } yield triples
156+ }
158157
159158 def processRestriction (restriction : Restriction , whelk : ReasonerState , mode : Config .OutputMode ): TriplesGroup = {
160- val queryConcept = AtomicConcept (s " ${restriction.property.id}${restriction.filler.id}" )
161- val er = ExistentialRestriction (restriction.property, restriction.filler)
162- val axioms = Set (ConceptInclusion (queryConcept, er), ConceptInclusion (er, queryConcept))
163- val updatedWhelk = Reasoner .assert(axioms, whelk)
164- // FIXME don't create these if result is empty
165- val predicate = NodeFactory .createURI(restriction.property.id)
166- val target = NodeFactory .createURI(restriction.filler.id)
167- val (equivalents, directSubclasses) = updatedWhelk.directlySubsumes(queryConcept)
168- val subclasses =
169- updatedWhelk.closureSubsBySuperclass(queryConcept).collect { case x : AtomicConcept => x } - queryConcept - Bottom
170- if (! equivalents(BuiltIn .Bottom )) {
171- val nonredundantTerms = directSubclasses - BuiltIn .Bottom ++ equivalents
172- val nonredundantTriples = mode match {
173- case RDFMode => nonredundantTerms.map(sc => Triple .create(NodeFactory .createURI(sc.id), predicate, target))
174- case OWLMode => nonredundantTerms.flatMap(sc => owlTriples(NodeFactory .createURI(sc.id), predicate, target))
175- }
176- val redundantTriples = mode match {
159+ val subclasses = queryExistentialSubclasses(restriction, whelk)
160+ if (subclasses.nonEmpty) {
161+ val predicate = NodeFactory .createURI(restriction.property.id)
162+ val target = NodeFactory .createURI(restriction.filler.id)
163+ val outputTriples = mode match {
177164 case RDFMode => subclasses.map(sc => Triple .create(NodeFactory .createURI(sc.id), predicate, target))
178165 case OWLMode => subclasses.flatMap(sc => owlTriples(NodeFactory .createURI(sc.id), predicate, target))
179166 }
180- TriplesGroup (nonredundantTriples, redundantTriples )
167+ TriplesGroup (outputTriples )
181168 } else TriplesGroup .empty
182169 }
183170
171+ def queryExistentialSubclasses (restriction : Restriction , whelk : ReasonerState ): Set [AtomicConcept ] = {
172+ val queryConcept = AtomicConcept (s " ${restriction.property.id}${restriction.filler.id}" )
173+ val er = ExistentialRestriction (restriction.property, restriction.filler)
174+ val axioms = Set (ConceptInclusion (queryConcept, er), ConceptInclusion (er, queryConcept))
175+ val updatedWhelk = Reasoner .assert(axioms, whelk)
176+ updatedWhelk.closureSubsBySuperclass(queryConcept).collect { case x : AtomicConcept => x } - queryConcept - Bottom
177+ }
178+
184179 def classHierarchy (reasoner : ReasonerState ): Hierarchy = {
185180 val taxonomy = reasoner.computeTaxonomy
186181 val subclassTaxonomy = taxonomy.foldLeft(Map .empty[AtomicConcept , Set [AtomicConcept ]]) { case (accum, (concept, (_, superclasses))) =>
@@ -215,23 +210,21 @@ object Main extends ZCaseApp[Config] {
215210 .collect { case ac @ AtomicConcept (_) => ac }
216211 if (allSuperclasses(BuiltIn .Bottom )) TriplesGroup .empty // unsatisfiable
217212 else {
218- val (equivs, directSuperclasses ) = whelk.directlySubsumedBy(concept)
213+ val (equivs, _ ) = whelk.directlySubsumedBy(concept)
219214 val adjustedEquivs = if (reflexiveSubclasses) equivs + concept else equivs - concept
220- val directSuperclassTriples = directSuperclasses.map(c => Triple .create(subject, RDFSSubClassOf , NodeFactory .createURI(c.id)))
221215 val equivalentClassTriples = if (equivalenceAsSubclass)
222216 adjustedEquivs.map(c => Triple .create(subject, RDFSSubClassOf , NodeFactory .createURI(c.id)))
223217 else
224218 adjustedEquivs.map(c => Triple .create(subject, OWLEquivalentClass , NodeFactory .createURI(c.id)))
225- val nonredundantTriples = directSuperclassTriples ++ equivalentClassTriples
226219 val adjustedSuperclasses = if (reflexiveSubclasses) allSuperclasses + concept else allSuperclasses - concept
227- val redundantTriples = if (equivalenceAsSubclass)
220+ val outputTriples = if (equivalenceAsSubclass)
228221 adjustedSuperclasses.map(c => Triple .create(subject, RDFSSubClassOf , NodeFactory .createURI(c.id)))
229222 else {
230223 val superclassesMinusEquiv = adjustedSuperclasses -- adjustedEquivs
231224 superclassesMinusEquiv.map(c => Triple .create(subject, RDFSSubClassOf , NodeFactory .createURI(c.id))) ++
232225 equivalentClassTriples
233226 }
234- TriplesGroup (nonredundantTriples, redundantTriples )
227+ TriplesGroup (outputTriples )
235228 }
236229 }
237230
@@ -247,15 +240,18 @@ object Main extends ZCaseApp[Config] {
247240 )
248241 }
249242
243+ private def isIncoherent (state : ReasonerState ): Boolean =
244+ state.closureSubsBySuperclass(Bottom ).exists(t => ! t.isAnonymous && t != Bottom )
245+
250246 final case class Hierarchy (equivs : Map [AtomicConcept , Set [AtomicConcept ]], subclasses : Map [AtomicConcept , Set [AtomicConcept ]])
251247
252248 final case class Restriction (property : Role , filler : AtomicConcept )
253249
254- final case class TriplesGroup (nonredundant : Set [ Triple ], redundant : Set [Triple ])
250+ final case class TriplesGroup (redundant : Set [Triple ])
255251
256252 object TriplesGroup {
257253
258- val empty : TriplesGroup = TriplesGroup (Set .empty, Set .empty )
254+ val empty : TriplesGroup = TriplesGroup (Set .empty)
259255
260256 }
261257
0 commit comments