From 80556601e4035738a6b2196be2836098941b2c47 Mon Sep 17 00:00:00 2001 From: Jim Balhoff Date: Wed, 20 Apr 2022 13:46:21 -0400 Subject: [PATCH 1/2] Split project into core and cli modules. Add static method for easy access from Java. --- build.sbt | 107 +++++++++++------ .../org/renci/relationgraph/Config.scala | 39 +++--- .../scala/org/renci/relationgraph/Main.scala | 70 +++++++++++ .../org/renci/relationgraph/ZCaseApp.scala | 29 +++-- .../renci/relationgraph/RelationGraph.scala | 113 ++++++------------ .../relationgraph/RelationGraphUtil.scala | 47 ++++++++ .../resources/org/renci/relationgraph/apo.owl | 0 .../renci/relationgraph/materialize_test.ofn | 0 .../relationgraph/TestRelationGraph.scala | 27 +++-- project/plugins.sbt | 1 + 10 files changed, 278 insertions(+), 155 deletions(-) rename {src => cli/src}/main/scala/org/renci/relationgraph/Config.scala (80%) create mode 100644 cli/src/main/scala/org/renci/relationgraph/Main.scala rename {src => cli/src}/main/scala/org/renci/relationgraph/ZCaseApp.scala (62%) rename src/main/scala/org/renci/relationgraph/Main.scala => core/src/main/scala/org/renci/relationgraph/RelationGraph.scala (73%) create mode 100644 core/src/main/scala/org/renci/relationgraph/RelationGraphUtil.scala rename {src => core/src}/test/resources/org/renci/relationgraph/apo.owl (100%) rename {src => core/src}/test/resources/org/renci/relationgraph/materialize_test.ofn (100%) rename {src => core/src}/test/scala/org/renci/relationgraph/TestRelationGraph.scala (73%) diff --git a/build.sbt b/build.sbt index 761d3f2..422a6c4 100644 --- a/build.sbt +++ b/build.sbt @@ -1,42 +1,77 @@ -enablePlugins(JavaAppPackaging) -enablePlugins(BuildInfoPlugin) -enablePlugins(GitVersioning) +lazy val zioVersion = "2.0.0-RC5" +lazy val gitCommitString = SettingKey[String]("gitCommit") -organization := "org.renci" +lazy val commonSettings = Seq( + organization := "org.geneontology", + version := "2.2-SNAPSHOT", + licenses := Seq("MIT license" -> url("https://opensource.org/licenses/MIT")), + scalaVersion := "2.13.8", + scalacOptions := Seq("-unchecked", "-deprecation", "-encoding", "utf8"), + javaOptions += "-Xmx8G" +) -name := "relation-graph" +lazy val publishSettings = Seq( + Test / publishArtifact := false, + publishMavenStyle := true, + publishTo := { + val nexus = "https://oss.sonatype.org/" + if (isSnapshot.value) Some("snapshots" at nexus + "content/repositories/snapshots") + else Some("releases" at nexus + "service/local/staging/deploy/maven2") + }, + pomExtra := + git@github.com:balhoff/relation-graph.git + scm:git:git@github.com:balhoff/relation-graph.git + + + + balhoff + Jim Balhoff + balhoff@renci.org + + +) -version := "2.1.0" +lazy val parentProject = project + .in(file(".")) + .settings(commonSettings) + .settings(name := "relation-graph-project", publish / skip := true) + .aggregate(core, cli) -licenses := Seq("MIT license" -> url("https://opensource.org/licenses/MIT")) - -scalaVersion := "2.13.8" - -scalacOptions := Seq("-unchecked", "-deprecation", "-encoding", "utf8") - -javaOptions += "-Xmx8G" - -testFrameworks += new TestFramework("zio.test.sbt.ZTestFramework") - -val gitCommitString = SettingKey[String]("gitCommit") - -gitCommitString := git.gitHeadCommit.value.getOrElse("Not Set") - -buildInfoKeys := Seq[BuildInfoKey](name, version, scalaVersion, sbtVersion, gitCommitString) - -buildInfoPackage := "org.renci.relationgraph" - -val zioVersion = "2.0.0-RC3" +lazy val core = project + .in(file("core")) + .settings(commonSettings) + .settings( + name := "relation-graph", + description := "relation-graph core", + testFrameworks += new TestFramework("zio.test.sbt.ZTestFramework"), + libraryDependencies ++= Seq( + "dev.zio" %% "zio" % zioVersion, + "dev.zio" %% "zio-streams" % zioVersion, + "org.geneontology" %% "whelk-owlapi" % "1.1.1", + "org.apache.jena" % "apache-jena-libs" % "4.4.0" exclude("org.slf4j", "slf4j-log4j12"), + "com.typesafe.scala-logging" %% "scala-logging" % "3.9.4", + "dev.zio" %% "zio-test" % zioVersion % Test, + "dev.zio" %% "zio-test-sbt" % zioVersion % Test + ) + ) + .settings(publishSettings) -libraryDependencies ++= { - Seq( - "dev.zio" %% "zio" % zioVersion, - "dev.zio" %% "zio-streams" % zioVersion, - "org.geneontology" %% "whelk-owlapi" % "1.1.1", - "com.outr" %% "scribe-slf4j" % "3.8.2", - "com.github.alexarchambault" %% "case-app" % "2.0.6", - "org.apache.jena" % "apache-jena-libs" % "4.4.0" exclude ("org.slf4j", "slf4j-log4j12"), - "dev.zio" %% "zio-test" % zioVersion % Test, - "dev.zio" %% "zio-test-sbt" % zioVersion % Test +lazy val cli = project + .in(file("cli")) + .enablePlugins(JavaAppPackaging) + .enablePlugins(BuildInfoPlugin) + .enablePlugins(GitVersioning) + .settings(commonSettings) + .dependsOn(core) + .settings( + name := "relation-graph-cli", + executableScriptName := "relation-graph", + publish / skip := true, + libraryDependencies ++= Seq( + "com.outr" %% "scribe-slf4j" % "3.8.2", + "com.github.alexarchambault" %% "case-app" % "2.0.6" + ), + gitCommitString := git.gitHeadCommit.value.getOrElse("Not Set"), + buildInfoKeys := Seq[BuildInfoKey](name, version, scalaVersion, sbtVersion, gitCommitString), + buildInfoPackage := "org.renci.relationgraph" ) -} diff --git a/src/main/scala/org/renci/relationgraph/Config.scala b/cli/src/main/scala/org/renci/relationgraph/Config.scala similarity index 80% rename from src/main/scala/org/renci/relationgraph/Config.scala rename to cli/src/main/scala/org/renci/relationgraph/Config.scala index 9efc80f..10af006 100644 --- a/src/main/scala/org/renci/relationgraph/Config.scala +++ b/cli/src/main/scala/org/renci/relationgraph/Config.scala @@ -4,6 +4,7 @@ import caseapp._ import caseapp.core.Error.MalformedValue import caseapp.core.argparser.{ArgParser, SimpleArgParser} import org.renci.relationgraph.Config.{BoolValue, FalseValue, TrueValue} +import org.renci.relationgraph.RelationGraph.Config.{OWLMode, OutputMode, RDFMode} @AppName("relation-graph") @ProgName("relation-graph") @@ -16,7 +17,7 @@ final case class Config( outputFile: String, @HelpMessage("Configure style of triples to be output. RDF mode is the default; each existential relation is collapsed to a single direct triple.") @ValueDescription("RDF|OWL") - mode: Config.OutputMode = Config.RDFMode, + mode: OutputMode = RDFMode, @HelpMessage("Property to restrict output relations to. Provide option multiple times for multiple properties.") @ValueDescription("IRI") property: List[String] = Nil, @@ -43,28 +44,32 @@ final case class Config( disableOwlNothing: BoolValue = FalseValue, @HelpMessage("Set log level to INFO") @ValueDescription("bool") - verbose: Boolean = false) + verbose: Boolean = false) { + + def toRelationGraphConfig: RelationGraph.Config = + RelationGraph.Config( + mode = this.mode, + outputSubclasses = this.outputSubclasses.bool, + reflexiveSubclasses = this.reflexiveSubclasses.bool, + equivalenceAsSubclass = this.equivalenceAsSubclass.bool, + outputClasses = this.outputClasses.bool, + outputIndividuals = this.outputIndividuals.bool, + disableOwlNothing = this.disableOwlNothing.bool, + ) -object Config { - - sealed trait OutputMode - - case object RDFMode extends OutputMode - - case object OWLMode extends OutputMode +} - object OutputMode { +object Config { - implicit val argParser: ArgParser[OutputMode] = SimpleArgParser.from[OutputMode]("output mode") { arg => - arg.toLowerCase match { - case "rdf" => Right(RDFMode) - case "owl" => Right(OWLMode) - case _ => Left(MalformedValue("output mode", arg)) - } + implicit val rdfModeParser: ArgParser[OutputMode] = SimpleArgParser.from[OutputMode]("output mode") { arg => + arg.toLowerCase match { + case "rdf" => Right(RDFMode) + case "owl" => Right(OWLMode) + case _ => Left(MalformedValue("output mode", arg)) } - } + /** * This works around some confusing behavior in case-app boolean parsing */ diff --git a/cli/src/main/scala/org/renci/relationgraph/Main.scala b/cli/src/main/scala/org/renci/relationgraph/Main.scala new file mode 100644 index 0000000..9d4f3a9 --- /dev/null +++ b/cli/src/main/scala/org/renci/relationgraph/Main.scala @@ -0,0 +1,70 @@ +package org.renci.relationgraph + +import caseapp._ +import org.apache.jena.riot.RDFFormat +import org.apache.jena.riot.system.{StreamRDF, StreamRDFWriter} +import org.geneontology.whelk._ +import org.renci.relationgraph.RelationGraph.TriplesGroup +import org.semanticweb.owlapi.apibinding.OWLManager +import org.semanticweb.owlapi.model._ +import scribe.Level +import scribe.filter.{packageName, select} +import zio._ +import Config._ + +import java.io.{File, FileOutputStream} +import scala.io.Source + +object Main extends ZCaseApp[Config] { + + override def run(config: Config, arg: RemainingArgs): ZIO[Environment, Nothing, ExitCode] = { + val configureLogging = ZIO.succeed { + scribe.Logger.root + .clearHandlers() + .clearModifiers() + .withModifier(select(packageName("org.renci.relationgraph")).boosted(Level.Info, Level.Warn)) + .withHandler(minimumLevel = Some(if (config.verbose) Level.Info else Level.Warn)) + .replace() + } + val program = ZIO.scoped { + createStreamRDF(config.outputFile).flatMap { rdfWriter => + for { + fileProperties <- config.propertiesFile.map(readPropertiesFile).getOrElse(ZIO.succeed(Set.empty[AtomicConcept])) + specifiedProperties = fileProperties ++ config.property.map(prop => AtomicConcept(prop)).to(Set) + ontology <- loadOntology(config.ontologyFile) + _ <- RelationGraph.computeRelations(ontology, specifiedProperties, config.toRelationGraphConfig) + .foreach { + case TriplesGroup(triples) => ZIO.attempt(triples.foreach(rdfWriter.triple)) + } + _ <- ZIO.succeed(scribe.info("Done computing relations")) + } yield () + } + } + configureLogging *> + program.tapError { e => + if (config.verbose) ZIO.succeed(e.printStackTrace()) + else ZIO.succeed(scribe.error(e.getMessage)) + }.exitCode + } + + def createStreamRDF(path: String): ZIO[Scope, Throwable, StreamRDF] = { + ZIO.acquireRelease(ZIO.attempt(new FileOutputStream(new File(path))))(stream => ZIO.succeed(stream.close())).flatMap { outputStream => + ZIO.acquireRelease(ZIO.attempt { + val stream = StreamRDFWriter.getWriterStream(outputStream, RDFFormat.TURTLE_FLAT, null) + stream.start() + stream + })(stream => ZIO.succeed(stream.finish())) + } + } + + def loadOntology(path: String): Task[OWLOntology] = for { + manager <- ZIO.attempt(OWLManager.createOWLOntologyManager()) + ontology <- ZIO.attemptBlocking(manager.loadOntologyFromOntologyDocument(new File(path))) + } yield ontology + + def readPropertiesFile(file: String): ZIO[Any, Throwable, Set[AtomicConcept]] = + ZIO.attemptBlocking(Source.fromFile(file, "utf-8")).acquireReleaseWithAuto { source => + ZIO.attemptBlocking(source.getLines().map(_.trim).filter(_.nonEmpty).map(line => AtomicConcept(line)).to(Set)) + } + +} diff --git a/src/main/scala/org/renci/relationgraph/ZCaseApp.scala b/cli/src/main/scala/org/renci/relationgraph/ZCaseApp.scala similarity index 62% rename from src/main/scala/org/renci/relationgraph/ZCaseApp.scala rename to cli/src/main/scala/org/renci/relationgraph/ZCaseApp.scala index 322c146..34125dd 100644 --- a/src/main/scala/org/renci/relationgraph/ZCaseApp.scala +++ b/cli/src/main/scala/org/renci/relationgraph/ZCaseApp.scala @@ -13,7 +13,7 @@ import java.io.IOException /** * Adapted from caseapp.cats.IOCaseApp */ -abstract class ZCaseApp[T](implicit val parser0: Parser[T], val messages: Help[T]) extends App { +abstract class ZCaseApp[T](implicit val parser0: Parser[T], val messages: Help[T]) extends ZIOAppDefault { private[this] def parser: Parser[T] = { val p = parser0.nameFormatter(nameFormatter) @@ -23,15 +23,15 @@ abstract class ZCaseApp[T](implicit val parser0: Parser[T], val messages: Help[T p } - def run(options: T, remainingArgs: RemainingArgs): ZIO[ZEnv, Nothing, ExitCode] + def run(options: T, remainingArgs: RemainingArgs): ZIO[Environment, Nothing, ExitCode] - private[this] def error(message: Error): ZIO[Console, IOException, ExitCode] = + private[this] def error(message: Error): ZIO[Any, IOException, ExitCode] = printLine(message.message).as(ExitCode.failure) - private[this] def helpAsked: ZIO[Console, IOException, ExitCode] = + private[this] def helpAsked: ZIO[Any, IOException, ExitCode] = printLine(messages.withHelp.help).as(ExitCode.success) - private[this] def usageAsked: ZIO[Console, IOException, ExitCode] = + private[this] def usageAsked: ZIO[Any, IOException, ExitCode] = printLine(messages.withHelp.usage).as(ExitCode.success) /** @@ -64,14 +64,17 @@ abstract class ZCaseApp[T](implicit val parser0: Parser[T], val messages: Help[T private[this] def nameFormatter: Formatter[Name] = Formatter.DefaultNameFormatter - override def run(args: List[String]): ZIO[ZEnv, Nothing, ExitCode] = { - if (args == List("--version")) ZIO.succeed(println(org.renci.relationgraph.BuildInfo.toString)).exitCode - else parser.withHelp.detailedParse(expandArgs(args), stopAtFirstUnrecognized) match { - case Left(err) => error(err).orDie - case Right((WithHelp(_, true, _), _)) => helpAsked.orDie - case Right((WithHelp(true, _, _), _)) => usageAsked.orDie - case Right((WithHelp(_, _, Left(err)), _)) => error(err).orDie - case Right((WithHelp(_, _, Right(t)), remainingArgs)) => run(t, remainingArgs) + override def run: ZIO[Environment with ZIOAppArgs with Scope, Any, Any] = { + ZIO.service[ZIOAppArgs].flatMap { appArgs => + val args = appArgs.getArgs.toList + if (args == List("--version")) ZIO.succeed(println(org.renci.relationgraph.BuildInfo.toString)).exitCode + else parser.withHelp.detailedParse(expandArgs(args), stopAtFirstUnrecognized) match { + case Left(err) => error(err).orDie + case Right((WithHelp(_, true, _), _)) => helpAsked.orDie + case Right((WithHelp(true, _, _), _)) => usageAsked.orDie + case Right((WithHelp(_, _, Left(err)), _)) => error(err).orDie + case Right((WithHelp(_, _, Right(t)), remainingArgs)) => run(t, remainingArgs) + } } } diff --git a/src/main/scala/org/renci/relationgraph/Main.scala b/core/src/main/scala/org/renci/relationgraph/RelationGraph.scala similarity index 73% rename from src/main/scala/org/renci/relationgraph/Main.scala rename to core/src/main/scala/org/renci/relationgraph/RelationGraph.scala index f33d227..a6740a2 100644 --- a/src/main/scala/org/renci/relationgraph/Main.scala +++ b/core/src/main/scala/org/renci/relationgraph/RelationGraph.scala @@ -1,34 +1,25 @@ package org.renci.relationgraph -import caseapp._ +import com.typesafe.scalalogging.StrictLogging import org.apache.jena.graph.{Node, NodeFactory, Triple} -import org.apache.jena.riot.RDFFormat -import org.apache.jena.riot.system.{StreamRDF, StreamRDFWriter} import org.apache.jena.sys.JenaSystem import org.apache.jena.vocabulary.{OWL2, RDF, RDFS} import org.geneontology.whelk.BuiltIn.{Bottom, Top} import org.geneontology.whelk._ -import org.renci.relationgraph.Config.{OWLMode, RDFMode} +import org.renci.relationgraph.RelationGraph.Config.{OWLMode, OutputMode, RDFMode} import org.semanticweb.owlapi.apibinding.OWLFunctionalSyntaxFactory.{OWLNothing, OWLThing} -import org.semanticweb.owlapi.apibinding.OWLManager -import org.semanticweb.owlapi.model._ import org.semanticweb.owlapi.model.parameters.Imports -import scribe.Level -import scribe.filter.{packageName, select} -import zio.ZIO.attemptBlockingIO +import org.semanticweb.owlapi.model._ import zio._ import zio.stream._ -import java.io.{File, FileOutputStream} import java.lang.{Runtime => JRuntime} import java.nio.charset.StandardCharsets import java.security.MessageDigest import java.util.Base64 -import java.util.concurrent.TimeUnit -import scala.io.Source import scala.jdk.CollectionConverters._ -object Main extends ZCaseApp[Config] { +object RelationGraph extends StrictLogging { JenaSystem.init() @@ -40,86 +31,51 @@ object Main extends ZCaseApp[Config] { private val OWLSomeValuesFrom = OWL2.someValuesFrom.asNode private val OWLOntology = OWL2.Ontology.asNode - override def run(config: Config, arg: RemainingArgs): ZIO[ZEnv, Nothing, ExitCode] = { - val configureLogging = ZIO.succeed { - scribe.Logger.root - .clearHandlers() - .clearModifiers() - .withModifier(select(packageName("org.renci.relationgraph")).boosted(Level.Info, Level.Warn)) - .withHandler(minimumLevel = Some(if (config.verbose) Level.Info else Level.Warn)) - .replace() - } + final case class Config( + mode: OutputMode = RDFMode, + outputSubclasses: Boolean = false, + reflexiveSubclasses: Boolean = true, + equivalenceAsSubclass: Boolean = true, + outputClasses: Boolean = true, + outputIndividuals: Boolean = false, + disableOwlNothing: Boolean = false, + ) + + object Config { + + sealed trait OutputMode + + case object RDFMode extends OutputMode + + case object OWLMode extends OutputMode - val program = ZIO.scoped { - createStreamRDF(config.outputFile).flatMap { rdfWriter => - for { - fileProperties <- config.propertiesFile.map(readPropertiesFile).getOrElse(ZIO.succeed(Set.empty[AtomicConcept])) - specifiedProperties = fileProperties ++ config.property.map(prop => AtomicConcept(prop)).to(Set) - ontology <- loadOntology(config.ontologyFile) - whelkOntology = Bridge.ontologyToAxioms(ontology) - _ <- ZIO.succeed(scribe.info("Running reasoner")) - whelk = Reasoner.assert(whelkOntology, disableBottom = config.disableOwlNothing.bool) - indexedWhelk = IndexedReasonerState(whelk) - _ <- ZIO.succeed(scribe.info("Done running reasoner")) - _ <- ZIO.fail(new Exception("Ontology is incoherent; please correct unsatisfiable classes.")).when(isIncoherent(whelk)) - _ <- attemptBlockingIO(rdfWriter.triple(Triple.create(NodeFactory.createBlankNode("redundant"), RDFType, OWLOntology))) - .when(config.mode == OWLMode) - start <- Clock.currentTime(TimeUnit.MILLISECONDS) - triplesStream = computeRelations(ontology, indexedWhelk, specifiedProperties, config.outputSubclasses.bool, config.reflexiveSubclasses.bool, config.equivalenceAsSubclass.bool, config.outputClasses.bool, config.outputIndividuals.bool, config.mode) - _ <- triplesStream.foreach { - case TriplesGroup(triples) => ZIO.attempt(triples.foreach(rdfWriter.triple)) - } - stop <- Clock.currentTime(TimeUnit.MILLISECONDS) - _ <- ZIO.succeed(scribe.info(s"Computed relations in ${(stop - start) / 1000.0}s")) - } yield () - } - } - configureLogging *> - program.as(ExitCode.success) - .catchAll { e => - if (config.verbose) ZIO.succeed(e.printStackTrace()).as(ExitCode.failure) - else ZIO.succeed(scribe.error(e.getMessage)).as(ExitCode.failure) - } } - def computeRelations(ontology: OWLOntology, whelk: IndexedReasonerState, specifiedProperties: Set[AtomicConcept], outputSubclasses: Boolean, reflexiveSubclasses: Boolean, equivalenceAsSubclass: Boolean, outputClasses: Boolean, outputIndividuals: Boolean, mode: Config.OutputMode): UStream[TriplesGroup] = { - val classes = classHierarchy(whelk.state) + def computeRelations(ontology: OWLOntology, specifiedProperties: Set[AtomicConcept], outputConfig: Config): UStream[TriplesGroup] = { + val whelkOntology = Bridge.ontologyToAxioms(ontology) + logger.info("Running reasoner") + val whelk = Reasoner.assert(whelkOntology, disableBottom = outputConfig.disableOwlNothing) + val indexedWhelk = IndexedReasonerState(whelk) + logger.info("Done running reasoner") + val classes = classHierarchy(indexedWhelk.state) val properties = propertyHierarchy(ontology) val allProperties = properties.subclasses.keySet.map(c => Role(c.id)) - val classesTasks = if (outputSubclasses) { - allClasses(ontology).map(c => ZIO.succeed(processSubclasses(c, whelk.state, reflexiveSubclasses, equivalenceAsSubclass, outputClasses, outputIndividuals))) + val ontologyDeclarationStream = ZStream.succeed(ZIO.succeed(TriplesGroup(Set(Triple.create(NodeFactory.createBlankNode("redundant"), RDFType, OWLOntology))))) + .when(outputConfig.mode == OWLMode) + val classesTasks = if (outputConfig.outputSubclasses) { + allClasses(ontology).map(c => ZIO.succeed(processSubclasses(c, indexedWhelk.state, outputConfig.reflexiveSubclasses, outputConfig.equivalenceAsSubclass, outputConfig.outputClasses, outputConfig.outputIndividuals))) } else Stream.empty val streamZ = for { queue <- Queue.unbounded[Restriction] activeRestrictions <- Ref.make(0) seenRefs <- ZIO.foreach(allProperties)(p => Ref.make(Set.empty[AtomicConcept]).map(p -> _)).map(_.toMap) _ <- traverse(specifiedProperties, properties, classes, queue, activeRestrictions, seenRefs) - restrictionsStream = Stream.fromQueue(queue).map(r => processRestrictionAndExtendQueue(r, properties, classes, whelk, mode, specifiedProperties.isEmpty, outputClasses, outputIndividuals, queue, activeRestrictions, seenRefs)) - allTasks = classesTasks ++ restrictionsStream + restrictionsStream = Stream.fromQueue(queue).map(r => processRestrictionAndExtendQueue(r, properties, classes, indexedWhelk, outputConfig.mode, specifiedProperties.isEmpty, outputConfig.outputClasses, outputConfig.outputIndividuals, queue, activeRestrictions, seenRefs)) + allTasks = ontologyDeclarationStream ++ classesTasks ++ restrictionsStream } yield allTasks.mapZIOParUnordered(JRuntime.getRuntime.availableProcessors)(identity) Stream.unwrap(streamZ) } - def readPropertiesFile(file: String): ZIO[Any, Throwable, Set[AtomicConcept]] = - ZIO.attemptBlocking(Source.fromFile(file, "utf-8")).acquireReleaseWithAuto { source => - ZIO.attemptBlocking(source.getLines().map(_.trim).filter(_.nonEmpty).map(line => AtomicConcept(line)).to(Set)) - } - - def loadOntology(path: String): Task[OWLOntology] = for { - manager <- ZIO.attempt(OWLManager.createOWLOntologyManager()) - ontology <- ZIO.attemptBlocking(manager.loadOntologyFromOntologyDocument(new File(path))) - } yield ontology - - def createStreamRDF(path: String): ZIO[Scope, Throwable, StreamRDF] = { - ZIO.acquireRelease(ZIO.attempt(new FileOutputStream(new File(path))))(stream => ZIO.succeed(stream.close())).flatMap { outputStream => - ZIO.acquireRelease(ZIO.attempt { - val stream = StreamRDFWriter.getWriterStream(outputStream, RDFFormat.TURTLE_FLAT, null) - stream.start() - stream - })(stream => ZIO.succeed(stream.finish())) - } - } - def allClasses(ont: OWLOntology): ZStream[Any, Nothing, OWLClass] = Stream.fromIterable(ont.getClassesInSignature(Imports.INCLUDED).asScala.to(Set) - OWLThing - OWLNothing) def traverse(specifiedProperties: Set[AtomicConcept], properties: Hierarchy, classes: Hierarchy, queue: Queue[Restriction], activeRestrictions: Ref[Int], seenRefs: Map[Role, Ref[Set[AtomicConcept]]]): UIO[Unit] = { @@ -318,4 +274,5 @@ object Main extends ZCaseApp[Config] { } + } diff --git a/core/src/main/scala/org/renci/relationgraph/RelationGraphUtil.scala b/core/src/main/scala/org/renci/relationgraph/RelationGraphUtil.scala new file mode 100644 index 0000000..cb5720b --- /dev/null +++ b/core/src/main/scala/org/renci/relationgraph/RelationGraphUtil.scala @@ -0,0 +1,47 @@ +package org.renci.relationgraph + +import org.apache.jena.vocabulary.{OWL, RDFS} +import org.geneontology.whelk.AtomicConcept +import org.phenoscape.scowl._ +import org.renci.relationgraph.RelationGraph.Config +import org.renci.relationgraph.RelationGraph.Config.RDFMode +import org.semanticweb.owlapi.model.{IRI, OWLClassAxiom, OWLOntology} +import zio._ + +import java.util +import scala.jdk.CollectionConverters._ + +/** + * Static methods supporting easier, synchronous, access from Java + */ +object RelationGraphUtil { + + private val RDFSSubClassOf = RDFS.subClassOf.getURI + private val OWLEquivalentClass = OWL.equivalentClass.getURI + + /** + * @param ontology + * @param specifiedProperties properties for which to compute relations; an empty collection signifies all + * @param outputConfig configuration for RelationGraph; `mode` is ignored, since results are converted to OWL axioms + * @return + */ + def computeRelationGraph(ontology: OWLOntology, specifiedProperties: util.Collection[IRI], outputConfig: Config): util.Set[OWLClassAxiom] = { + val properties = specifiedProperties.asScala.to(Set).map(iri => AtomicConcept(iri.toString)) + val owlZ = RelationGraph.computeRelations(ontology, properties, outputConfig.copy(mode = RDFMode)) + .map { triplesGroup => + val triples = triplesGroup.redundant + triples.map { triple => + triple.getPredicate.getURI match { + case RDFSSubClassOf => SubClassOf(Class(triple.getSubject.getURI), Class(triple.getObject.getURI)) + case OWLEquivalentClass => EquivalentClasses(Class(triple.getSubject.getURI), Class(triple.getObject.getURI)) + case property => SubClassOf(Class(triple.getSubject.getURI), ObjectSomeValuesFrom(ObjectProperty(property), Class(triple.getObject.getURI))) + } + } + } + .runCollect + .map(_.toSet.flatten) + .map(_.asJava) + Runtime.default.unsafeRun(owlZ) + } + +} diff --git a/src/test/resources/org/renci/relationgraph/apo.owl b/core/src/test/resources/org/renci/relationgraph/apo.owl similarity index 100% rename from src/test/resources/org/renci/relationgraph/apo.owl rename to core/src/test/resources/org/renci/relationgraph/apo.owl diff --git a/src/test/resources/org/renci/relationgraph/materialize_test.ofn b/core/src/test/resources/org/renci/relationgraph/materialize_test.ofn similarity index 100% rename from src/test/resources/org/renci/relationgraph/materialize_test.ofn rename to core/src/test/resources/org/renci/relationgraph/materialize_test.ofn diff --git a/src/test/scala/org/renci/relationgraph/TestRelationGraph.scala b/core/src/test/scala/org/renci/relationgraph/TestRelationGraph.scala similarity index 73% rename from src/test/scala/org/renci/relationgraph/TestRelationGraph.scala rename to core/src/test/scala/org/renci/relationgraph/TestRelationGraph.scala index 59f4dcc..4e860dd 100644 --- a/src/test/scala/org/renci/relationgraph/TestRelationGraph.scala +++ b/core/src/test/scala/org/renci/relationgraph/TestRelationGraph.scala @@ -1,30 +1,38 @@ package org.renci.relationgraph import org.apache.jena.graph.{Node, NodeFactory, Triple} -import org.geneontology.whelk.{Bridge, Reasoner} -import org.renci.relationgraph.Main.{IndexedReasonerState, TriplesGroup} +import org.renci.relationgraph.RelationGraph.Config.RDFMode +import org.renci.relationgraph.RelationGraph.{Config, TriplesGroup} import org.semanticweb.owlapi.apibinding.OWLManager import zio._ import zio.test.TestAspect.timeout import zio.test._ -object TestRelationGraph extends DefaultRunnableSpec { + +object TestRelationGraph extends ZIOSpecDefault { private val Prefix = "http://example.org/test" private val P = NodeFactory.createURI(s"$Prefix#p") private def n: String => Node = NodeFactory.createURI + private val testConfig = Config( + mode = RDFMode, + outputSubclasses = true, + reflexiveSubclasses = false, + equivalenceAsSubclass = false, + outputClasses = true, + outputIndividuals = false, + disableOwlNothing = false + ) + def spec = suite("RelationGraphSpec")( test("testMaterializedRelations") { for { manager <- ZIO.attempt(OWLManager.createOWLOntologyManager()) ontology <- ZIO.attempt(manager.loadOntologyFromOntologyDocument(this.getClass.getResourceAsStream("materialize_test.ofn"))) - whelkOntology = Bridge.ontologyToAxioms(ontology) - whelk = Reasoner.assert(whelkOntology) - indexedWhelk = IndexedReasonerState(whelk) - resultsStream = Main.computeRelations(ontology, indexedWhelk, Set.empty, true, false, false, true, false, Config.RDFMode) + resultsStream = RelationGraph.computeRelations(ontology, Set.empty, testConfig) results <- resultsStream.runCollect triples <- ZIO.from(results.reduceOption((left, right) => TriplesGroup(left.redundant ++ right.redundant))) TriplesGroup(redundant) = triples @@ -40,10 +48,7 @@ object TestRelationGraph extends DefaultRunnableSpec { for { manager <- ZIO.attempt(OWLManager.createOWLOntologyManager()) ontology <- ZIO.attempt(manager.loadOntologyFromOntologyDocument(this.getClass.getResourceAsStream("apo.owl"))) - whelkOntology = Bridge.ontologyToAxioms(ontology) - whelk = Reasoner.assert(whelkOntology) - indexedWhelk = IndexedReasonerState(whelk) - resultsStream = Main.computeRelations(ontology, indexedWhelk, Set.empty, true, false, false, true, false, Config.RDFMode) + resultsStream = RelationGraph.computeRelations(ontology, Set.empty, testConfig) results <- resultsStream.runCollect triples <- ZIO.from(results.reduceOption((left, right) => TriplesGroup(left.redundant ++ right.redundant))) TriplesGroup(redundant) = triples diff --git a/project/plugins.sbt b/project/plugins.sbt index 7b6d52a..f0f0082 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -1,3 +1,4 @@ addSbtPlugin("com.github.sbt" % "sbt-native-packager" % "1.9.9") addSbtPlugin("com.eed3si9n" % "sbt-buildinfo" % "0.11.0") addSbtPlugin("com.typesafe.sbt" % "sbt-git" % "1.0.2") +addSbtPlugin("com.github.sbt" % "sbt-pgp" % "2.1.2") From 37372f45a84773a10c3e489a8e30900769c0abf4 Mon Sep 17 00:00:00 2001 From: Jim Balhoff Date: Wed, 20 Apr 2022 13:51:41 -0400 Subject: [PATCH 2/2] Fix type error. --- .../main/scala/org/renci/relationgraph/RelationGraphUtil.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/main/scala/org/renci/relationgraph/RelationGraphUtil.scala b/core/src/main/scala/org/renci/relationgraph/RelationGraphUtil.scala index cb5720b..42515ab 100644 --- a/core/src/main/scala/org/renci/relationgraph/RelationGraphUtil.scala +++ b/core/src/main/scala/org/renci/relationgraph/RelationGraphUtil.scala @@ -39,7 +39,7 @@ object RelationGraphUtil { } } .runCollect - .map(_.toSet.flatten) + .map(_.toSet.flatten[OWLClassAxiom]) .map(_.asJava) Runtime.default.unsafeRun(owlZ) }