Skip to content

Commit

Permalink
Develop (#2)
Browse files Browse the repository at this point in the history
* valid range of "Maximum RAM Percentage" parameter in Validate plugin

* edit CHANGELOG

* edit parameter validation

* edits

* move cleanup to utils.py, annotate inferred axioms not advanced parameter

* remove "annotate inferred axioms" parameter, edit README
  • Loading branch information
muddymudskipper committed Jul 1, 2024
1 parent ea38d17 commit 1426e10
Show file tree
Hide file tree
Showing 7 changed files with 156 additions and 136 deletions.
10 changes: 10 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,16 @@ All notable changes to this project will be documented in this file.

The format is based on [Keep a Changelog](http://keepachangelog.com/) and this project adheres to [Semantic Versioning](https://semver.org/)

## [Unreleased]

### Fixed

- valid range of "Maximum RAM Percentage" parameter in Validate plugin (1-100)

### Changed

- complete validation for IRI parameters
- Remove "Annnotate inferred subclass axioms" parameter

## [1.0.0alpha3] 2024-06-28

Expand Down
2 changes: 1 addition & 1 deletion README-public.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# cmem-plugin-reason

This [eccenca](https://eccenca.com) [Corporate Memory](https://documentation.eccenca.com) workflow plugin performs reasoning using [ROBOT](http://robot.obolibrary.org/). It takes an OWL ontology and a data graph as inputs and writes the reasoning result to a specified graph.
This [eccenca](https://eccenca.com) [Corporate Memory](https://documentation.eccenca.com) workflow plugin performs reasoning using [ROBOT](http://robot.obolibrary.org/).

ROBOT is published under the [BSD 3-Clause "New" or "Revised" License](https://choosealicense.com/licenses/bsd-3-clause/).
Copyright © 2015, the Authors
Expand Down
57 changes: 53 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,7 @@

# cmem-plugin-reason

Reasoning with ROBOT

This eccenca Corporate Memory workflow plugin performs reasoning using [ROBOT](http://robot.obolibrary.org/). It takes an OWL ontology and a data graph as inputs and writes the reasoning result to a specified graph.
This [eccenca](https://eccenca.com) [Corporate Memory](https://documentation.eccenca.com) workflow plugin bundle contains plugins performing reasoning (Reason) and ontology consistency checking (Validate) using [ROBOT](http://robot.obolibrary.org/).

[![eccenca Corporate Memory](https://img.shields.io/badge/eccenca-Corporate%20Memory-orange)](https://documentation.eccenca.com) [![workflow](https://github.com/eccenca/cmem-plugin-pyshacl/actions/workflows/check.yml/badge.svg)](https://github.com/eccenca/cmem-plugin-pyshacl/actions) [![pypi version](https://img.shields.io/pypi/v/cmem-plugin-reason)](https://pypi.org/project/cmem-plugin-reason/) [![license](https://img.shields.io/pypi/l/cmem-plugin-reason)](https://pypi.org/project/cmem-plugin-reasom)

Expand All @@ -29,6 +27,7 @@ Alternatively, the _build_ and _installation_ process can be initiated with the
➜ task deploy
```

# Reason
## Options

### Data graph IRI
Expand All @@ -46,7 +45,6 @@ The IRI of the output graph for the reasoning result.

:warning: Existing graphs will be overwritten.


### Reasoner

The following reasoner options are supported:
Expand Down Expand Up @@ -84,3 +82,54 @@ parameters to include inferred axiom generators:
- ObjectPropertyRange
- ObjectPropertyDomain

### Maximum RAM Percentage

Maximum heap size for the Java virtual machine in the DI container running the reasoning process.

:warning: Setting the percentage too high may result in an out of memory error.

# Validate

In case ontology inconsistencies are found, the plugin outputs the explanation as text in Markdown format using the path "text".

## Options

### Ontology graph IRI

The IRI of the input ontology graph. The graph IRI is selected from a list of graphs of type`owl:Ontology`.

### Reasoner

The following reasoner options are supported:
- [ELK](https://code.google.com/p/elk-reasoner/) (elk)
- [Expression Materializing Reasoner](http://static.javadoc.io/org.geneontology/expression-materializing-reasoner/0.1.3/org/geneontology/reasoner/ExpressionMaterializingReasoner.html) (emr)
- [HermiT](http://www.hermit-reasoner.com/) (hermit)
- [JFact](http://jfact.sourceforge.net/) (jfact)
- [Structural Reasoner](http://owlcs.github.io/owlapi/apidocs_4/org/semanticweb/owlapi/reasoner/structural/StructuralReasoner.html) (structural)
- [Whelk](https://github.com/balhoff/whelk) (whelk)

### Produce output graph

If enabled, an explanation graph is created.

### Output graph IRI

The IRI of the output graph for the reasoning result.

:warning: Existing graphs will be overwritten.

### Write markdown explanation file

If enabled, an explanation markdown file is written to the project.

:warning: Existing files will be overwritten.

### Stop at inconsistencies

Raise an error if inconsistencies are found. If enabled, the plugin does not output entities.

### Maximum RAM Percentage

Maximum heap size for the Java virtual machine in the DI container running the reasoning process.

:warning: Setting the percentage too high may result in an out of memory error.
59 changes: 11 additions & 48 deletions cmem_plugin_reason/plugin_reason.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
"""Reasoning workflow plugin module"""

import shlex
from collections.abc import Sequence
from datetime import UTC, datetime
from pathlib import Path
from subprocess import run
Expand All @@ -12,7 +11,6 @@
from cmem.cmempy.dp.proxy.graph import get
from cmem_plugin_base.dataintegration.context import ExecutionContext
from cmem_plugin_base.dataintegration.description import Icon, Plugin, PluginParameter
from cmem_plugin_base.dataintegration.entity import Entities
from cmem_plugin_base.dataintegration.parameter.graph import GraphParameterType
from cmem_plugin_base.dataintegration.plugins import WorkflowPlugin
from cmem_plugin_base.dataintegration.types import BoolParameterType, StringParameterType
Expand All @@ -27,6 +25,7 @@
ROBOT,
create_xml_catalog_file,
get_graphs_tree,
remove_temp,
send_result,
)

Expand Down Expand Up @@ -59,7 +58,7 @@
param_type=StringParameterType(),
name="result_graph_iri",
label="Result graph IRI",
description="The IRI of the output graph for the reasoning result. ⚠️ Existing graph "
description="The IRI of the output graph for the reasoning result. ⚠️ Existing graphs "
"will be overwritten.",
),
PluginParameter(
Expand Down Expand Up @@ -160,19 +159,10 @@
description="",
default_value=False,
),
PluginParameter(
param_type=BoolParameterType(),
name="annotate_inferred_axioms",
label="Annnotate inferred subclass axioms",
description="Annotate inferred subclass axioms. ⚠️ This parameter can only be enabled "
"if the only enabled axiom generator is SubClass.",
default_value=False,
advanced=True,
),
],
)
class ReasonPlugin(WorkflowPlugin):
"""Robot reasoning plugin"""
"""Reason plugin"""

def __init__( # noqa: PLR0913
self,
Expand All @@ -194,10 +184,8 @@ def __init__( # noqa: PLR0913
sub_class: bool = True,
sub_data_property: bool = False,
sub_object_property: bool = False,
annotate_inferred_axioms: bool = False,
max_ram_percentage: int = MAX_RAM_PERCENTAGE_DEFAULT,
) -> None:
"""Init"""
self.axioms = {
"SubClass": sub_class,
"EquivalentClass": equivalent_class,
Expand All @@ -214,16 +202,13 @@ def __init__( # noqa: PLR0913
"ObjectPropertyRange": object_property_range,
"ObjectPropertyDomain": object_property_domain,
}

errors = ""
iris = {
"Data graph IRI": data_graph_iri,
"Ontology graph IRI": ontology_graph_iri,
"Result graph IRI": result_graph_iri,
}
not_iri = sorted([k for k, v in iris.items() if not validators.url(v)])
if not_iri:
errors += f"Invalid IRI for parameters: {', '.join(not_iri)}. "
if not validators.url(data_graph_iri):
errors += 'Invalid IRI for parameter "Data graph IRI". '
if not validators.url(ontology_graph_iri):
errors += 'Invalid IRI for parameter "Ontology graph IRI". '
if not validators.url(result_graph_iri):
errors += 'Invalid IRI for parameter "Result graph IRI". '
if result_graph_iri and result_graph_iri == data_graph_iri:
errors += "Result graph IRI cannot be the same as the data graph IRI. "
if result_graph_iri and result_graph_iri == ontology_graph_iri:
Expand All @@ -232,21 +217,14 @@ def __init__( # noqa: PLR0913
errors += 'Invalid value for parameter "Reasoner". '
if True not in self.axioms.values():
errors += "No axiom generator selected. "
if annotate_inferred_axioms and [k for k, v in self.axioms.items() if v] != ["SubClass"]:
errors += (
'Parameter "Annnotate inferred subclass axioms" can only be enabled if the only '
"enabled axiom generator is SubClass. "
)
if max_ram_percentage not in range(1, 101):
errors += 'Invalid value for parameter "Maximum RAM Percentage". '
if errors:
raise ValueError(errors[:-1])

self.data_graph_iri = data_graph_iri
self.ontology_graph_iri = ontology_graph_iri
self.result_graph_iri = result_graph_iri
self.reasoner = reasoner
self.annotate_inferred_axioms = str(annotate_inferred_axioms).lower()
self.max_ram_percentage = max_ram_percentage
self.temp = f"reason_{uuid4().hex}"

Expand Down Expand Up @@ -275,7 +253,6 @@ def reason(self, graphs: dict) -> None:
"--collapse-import-closure false "
f"reason --reasoner {self.reasoner} "
f'--axiom-generators "{axioms}" '
f"--annotate-inferred-axioms {self.annotate_inferred_axioms} "
f"--include-indirect true "
f"--exclude-duplicate-axioms true "
f"--exclude-owl-thing true "
Expand Down Expand Up @@ -305,21 +282,7 @@ def reason(self, graphs: dict) -> None:
raise OSError(response.stderr.decode())
raise OSError("ROBOT error")

def clean_up(self, graphs: dict) -> None:
"""Remove temporary files"""
files = ["catalog-v001.xml", "result.ttl"]
files += list(graphs.values())
for file in files:
try:
(Path(self.temp) / file).unlink()
except (OSError, FileNotFoundError) as err:
self.log.warning(f"Cannot remove file {file} ({err})")
try:
Path(self.temp).rmdir()
except (OSError, FileNotFoundError) as err:
self.log.warning(f"Cannot remove directory {self.temp} ({err})")

def execute(self, inputs: Sequence[Entities], context: ExecutionContext) -> None: # noqa: ARG002
def execute(self, inputs: tuple, context: ExecutionContext) -> None: # noqa: ARG002
"""Execute plugin"""
setup_cmempy_user_access(context.user)
graphs = get_graphs_tree((self.data_graph_iri, self.ontology_graph_iri))
Expand All @@ -328,4 +291,4 @@ def execute(self, inputs: Sequence[Entities], context: ExecutionContext) -> None
self.reason(graphs)
setup_cmempy_user_access(context.user)
send_result(self.result_graph_iri, Path(self.temp) / "result.ttl")
self.clean_up(graphs)
remove_temp(self, ["catalog-v001.xml", "result.ttl", *graphs.values()])
62 changes: 23 additions & 39 deletions cmem_plugin_reason/plugin_validate.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
"""Ontology consistency validation workflow plugin module"""

import shlex
from collections.abc import Sequence
from datetime import UTC, datetime
from pathlib import Path
from subprocess import run
Expand All @@ -22,7 +21,7 @@
from cmem_plugin_base.dataintegration.plugins import WorkflowPlugin
from cmem_plugin_base.dataintegration.types import BoolParameterType, StringParameterType
from cmem_plugin_base.dataintegration.utils import setup_cmempy_user_access
from pathvalidate import validate_filename
from pathvalidate import is_valid_filename

from cmem_plugin_reason.utils import (
MAX_RAM_PERCENTAGE_DEFAULT,
Expand All @@ -33,6 +32,7 @@
ROBOT,
create_xml_catalog_file,
get_graphs_tree,
remove_temp,
send_result,
)

Expand All @@ -50,7 +50,8 @@
param_type=BoolParameterType(),
name="write_md",
label="Write Markdown explanation file",
description="Write Markdownn file with explanation to project.",
description="Write Markdown file with explanation to project. ⚠️ Existing files will "
"be overwritten.",
default_value=False,
),
PluginParameter(
Expand All @@ -65,7 +66,7 @@
name="output_graph_iri",
label="Output graph IRI",
description="The IRI of the output graph for the inconsistency validation. ⚠️ Existing "
"graph will be overwritten.",
"graphs will be overwritten.",
),
PluginParameter(
param_type=StringParameterType(),
Expand All @@ -85,7 +86,7 @@
],
)
class ValidatePlugin(WorkflowPlugin):
"""Example Workflow Plugin: Random Values"""
"""Validate plugin"""

def __init__( # noqa: PLR0913
self,
Expand All @@ -100,28 +101,26 @@ def __init__( # noqa: PLR0913
) -> None:
errors = ""
if not validators.url(ontology_graph_iri):
errors += "Invalid IRI for parameter Ontology graph IRI. "
if reasoner not in REASONERS:
errors += "Invalid value for parameter Reasoner. "
errors += 'Invalid IRI for parameter "Ontology graph IRI." '
if produce_graph and not validators.url(output_graph_iri):
errors += "Invalid IRI for parameter Output graph IRI. "
if write_md:
try:
validate_filename(md_filename)
except: # noqa: E722
errors += "Invalid filename for parameter Output filename. "
if max_ram_percentage not in range(1, 100):
errors += "Invalid value for parameter Maximum RAM Percentage. "
errors += 'Invalid IRI for parameter "Output graph IRI". '
if produce_graph and output_graph_iri == ontology_graph_iri:
errors += "Output graph IRI cannot be the same as the Ontology graph IRI. "
if reasoner not in REASONERS:
errors += 'Invalid value for parameter "Reasoner". '
if write_md and not is_valid_filename(md_filename):
errors += 'Invalid filename for parameter "Output filename". '
if max_ram_percentage not in range(1, 101):
errors += 'Invalid value for parameter "Maximum RAM Percentage". '
if errors:
raise ValueError(errors[:-1])

self.ontology_graph_iri = ontology_graph_iri
self.reasoner = reasoner
self.produce_graph = produce_graph
self.output_graph_iri = output_graph_iri
self.write_md = write_md
self.stop_at_inconsistencies = stop_at_inconsistencies
self.md_filename = md_filename if md_filename and write_md else "mdfile.md"
self.md_filename = md_filename if write_md else "mdfile.md"
self.max_ram_percentage = max_ram_percentage
self.temp = f"reason_{uuid4().hex}"

Expand Down Expand Up @@ -176,35 +175,20 @@ def make_resource(self, context: ExecutionContext) -> None:
replace=True,
)

def clean_up(self, graphs: dict) -> None:
"""Remove temporary files"""
files = ["catalog-v001.xml", "output.ttl", self.md_filename]
files += list(graphs.values())
for file in files:
try:
(Path(self.temp) / file).unlink()
except (OSError, FileNotFoundError) as err:
self.log.warning(f"Cannot remove file {file} ({err})")
try:
Path(self.temp).rmdir()
except (OSError, FileNotFoundError) as err:
self.log.warning(f"Cannot remove directory {self.temp} ({err})")

def execute(
self,
inputs: Sequence[Entities], # noqa: ARG002
context: ExecutionContext,
) -> Entities | None:
def execute(self, inputs: tuple, context: ExecutionContext) -> Entities | None: # noqa: ARG002
"""Run the workflow operator."""
setup_cmempy_user_access(context.user)
graphs = get_graphs_tree((self.ontology_graph_iri,))
self.get_graphs(graphs, context)
create_xml_catalog_file(self.temp, graphs)
self.validate(graphs)
files = ["catalog-v001.xml", self.md_filename, *graphs.values()]
if self.produce_graph:
files.append("output.ttl")

text = (Path(self.temp) / self.md_filename).read_text()
if text == "No explanations found.":
self.clean_up(graphs)
remove_temp(self, files)
return None

if self.produce_graph:
Expand All @@ -215,7 +199,7 @@ def execute(
setup_cmempy_user_access(context.user)
self.make_resource(context)

self.clean_up(graphs)
remove_temp(self, files)

if self.stop_at_inconsistencies:
raise RuntimeError("Inconsistencies found in Ontology.")
Expand Down
Loading

0 comments on commit 1426e10

Please sign in to comment.