Skip to main content

ELK-compatible OWL 2 EL reasoning in pure Python with optional Rust acceleration

Project description

pyELK

PyPI Python License

pyELK is a Java-free implementation of the core reasoning behaviour of ELK Reasoner 0.6.0. It provides one typed Python API with a portable pure-Python backend and an optional Rust/PyO3 accelerator. Both backends consume the same deterministic compiled ontology and return the same canonical public values.

The distribution is pyelk-reasoner; the import package is pyelk:

python -m pip install pyelk-reasoner

The separate pyelk distribution on PyPI is an unrelated graph-layout project. A normal install selects a compatible native wheel when one is published and otherwise installs the universal Python wheel. Python 3.10 and 3.12 run the complete release suites; the native module uses the CPython 3.10 limited ABI and is additionally exercised on every supported CPython minor through 3.14.

Start with the documentation index or the getting-started guide. The distribution is pyelk-reasoner; do not install the unrelated pyelk graph-layout package.

First classification

pyELK delegates OWL syntax, structural values, imports, and immutable snapshots to the independent pyowl-core package. A path, bytes-like value, or caller-owned stream can be passed directly. Streams require a stable document_iri; pyELK never closes them.

from io import BytesIO

import pyowl_core as owl
from pyelk import Reasoner

document = (
    b"Prefix(:=<urn:readme#>) Ontology(<urn:readme> "
    b"Declaration(Class(:A)) Declaration(Class(:B)) SubClassOf(:A :B))"
)
options = owl.LoadOptions(
    format=owl.DocumentFormat.FUNCTIONAL,
    imports=owl.ImportPolicy.IGNORE,
    backend=owl.BackendPreference.PYTHON,
)

with Reasoner(document, load_options=options) as reasoner:
    byte_result = reasoner.classify()
    taxonomy = byte_result.require_complete()
    assert reasoner.backend.name in {"python", "rust"}
    assert taxonomy.node(owl.Class(owl.IRI("urn:readme#A"))) is not None

stream = BytesIO(document)
with Reasoner(
    stream,
    document_iri="urn:readme:stream",
    load_options=options,
) as reasoner:
    assert reasoner.classify() == byte_result
assert not stream.closed

Reasoner also accepts str/PathLike paths, OntologyDocument, every immutable OntologyView, and SnapshotProvider. Import resolution is explicit through LoadOptions and an optional pyowl-core ImportResolver; unresolved or intentionally ignored imports are never silently presented as complete reasoning.

Completeness is part of every answer

Each operation returns ReasoningResult[T], containing value, complete, and canonical reasons. complete means complete for the pinned ELK 0.6 procedure, not for arbitrary OWL. Inspect the metadata when a partial value is useful, or call require_complete() before using a value that must be authoritative.

import pyowl_core as owl
from pyelk import Reasoner
from pyelk.exceptions import IncompleteReasoningError

document = (
    b"Prefix(:=<urn:readme#>) Ontology(<urn:readme> "
    b"SubClassOf(ObjectAllValuesFrom(:p :A) :B))"
)
options = owl.LoadOptions(
    format=owl.DocumentFormat.FUNCTIONAL,
    imports=owl.ImportPolicy.IGNORE,
    backend=owl.BackendPreference.PYTHON,
)
with Reasoner(document, load_options=options) as reasoner:
    result = reasoner.classify()
    assert not result.complete
    assert any("OBJECT_ALL_VALUES_FROM" in issue.features for issue in result.reasons)
    try:
        result.require_complete()
    except IncompleteReasoningError as error:
        assert error.reasons == result.reasons
    else:
        raise AssertionError("an incomplete result was accepted")

Set ReasonerConfig(unsupported="error") when unsupported ontology constructs should fail during construction instead of producing an explicitly incomplete result. The exact construct, polarity, combination, per-operation monitor, and entailment-query boundary are listed in specs/compatibility.md.

Exact-OM and shared snapshots

When a caller already owns a pyowl-core view, pass it directly. pyELK retains that exact object, does not parse it again, and never converts public entities into a second OWL model. An Exact-OM-style owner can expose only owl_snapshot(); it is called once. Source, target, and bridge views can be combined as an identity-preserving OntologyComposite.

import pyowl_core as owl
from pyelk import Reasoner

options = owl.LoadOptions(
    format=owl.DocumentFormat.FUNCTIONAL,
    imports=owl.ImportPolicy.IGNORE,
    backend=owl.BackendPreference.PYTHON,
)
source = owl.load_snapshot(
    b"Prefix(:=<urn:shared#>) Ontology(SubClassOf(:A :B))",
    options=options,
)
target = owl.load_snapshot(
    b"Prefix(:=<urn:shared#>) Ontology(SubClassOf(:B :C))",
    options=options,
)
shared = owl.compose_views(source, target, roles=("source", "target"))


class ExactModel:
    def __init__(self, view: owl.OntologyView) -> None:
        self.view = view
        self.calls = 0

    def owl_snapshot(self) -> owl.OntologyView:
        self.calls += 1
        return self.view


model = ExactModel(shared)
with Reasoner(model) as reasoner:
    assert reasoner.ontology is shared
    taxonomy = reasoner.classify().require_complete()
    public_a = next(entity for entity in shared.signature() if entity.iri.value.endswith("#A"))
    assert taxonomy.node(public_a).members[0] == public_a
assert model.calls == 1

pyELK imports neither Exact-OM nor any consumer-private record type. The interoperability boundary is only pyowl-core's OntologyView/SnapshotProvider contract.

OAEI and process boundaries

For a worker process, persist the shared snapshot with pyowl-core's versioned wire format. The receiving process should verify and memory-map it before constructing the reasoner. No OWL parser runs on this path.

from pathlib import Path
from tempfile import TemporaryDirectory

import pyowl_core as owl
from pyelk import Reasoner

options = owl.LoadOptions(
    format=owl.DocumentFormat.FUNCTIONAL,
    imports=owl.ImportPolicy.IGNORE,
    backend=owl.BackendPreference.PYTHON,
)
document = b"Prefix(:=<urn:wire#>) Ontology(SubClassOf(:A :B))"
original = owl.load_snapshot(document, options=options)

with TemporaryDirectory() as directory:
    path = Path(directory) / "ontology.pyocore"
    path.write_bytes(owl.encode_snapshot(original))
    mapped = owl.open_snapshot(path, mmap=True, verify=True)
    try:
        with Reasoner(mapped) as reasoner:
            assert reasoner.ontology is mapped
            assert reasoner.classify().require_complete().nodes
    finally:
        mapped.close()

This is the supported OAEI-Bio-ML-eval handoff for timeout-isolated workers. Do not serialize private pyELK indexes or consumer records as an interchange format.

Backend selection and diagnostics

The default auto mode uses a compatible native extension when its version/ABI/IR handshake passes, and otherwise selects Python while retaining the fallback reason. Use ReasonerConfig(backend="python" | "rust") for a session-specific choice, or these process controls:

  • PYELK_BACKEND=auto|python|rust selects the default request;
  • PYELK_PURE_PYTHON=1 forces Python and prevents even probing _native;
  • an explicit Rust request fails with BackendUnavailableError rather than falling back.
import pyowl_core as owl
from pyelk import backend_report

report = backend_report()
assert report.selection_error is None
assert report.selected in {"python", "rust"}
assert report.core_package_version == owl.__version__
assert report.core_api_version == owl.API_VERSION
assert report.core_model_schema_version == owl.MODEL_SCHEMA_VERSION
assert report.core_wire_format_version == owl.WIRE_FORMAT_VERSION
assert report.core_adapter_protocol_version == owl.ADAPTER_PROTOCOL_VERSION

if report.selected == "python" and report.rust.reason:
    print(f"native backend not selected: {report.rust.reason}")

reasoner.backend reports the immutable choice, effective worker count, native availability, and fallback reason for a live session. reasoner.diagnostics() returns an immutable scalar mapping. Every session reports its canonical compiler_digest, compiler-cache and private-IR schema versions, implementation version, total consumer compile time, scalar-row materialization count, and an exact encoded buffer/copy/segment ledger. ingestion_path is scalar-python, scalar-wire, or encoded-native; scalar sessions report contractual zero/false encoded counters. Encoded sessions additionally report view-publication time, native validation, compilation, session-build, and total native-boundary durations. Recursive composite sessions count each temporary resolved posting byte and anonymous-scope mapping pair in encoded_staging_copy_bytes; they never label those bounded metadata copies as zero-copy. pyelk.core.require_core_compatibility() performs the full pyowl-core package/API/model/wire/ adapter guard explicitly.

Supported reasoning surface

The public facade provides consistency, class and object-property classification, realization, class-expression satisfiability, equivalent/sub/super-class queries, instances/types, object-property views, entity enumeration, and the pinned supported entailment families. It follows OWL 2 Direct Semantics and ELK's quiet inconsistent-ontology values.

The complete fragment is deliberately “ELK 0.6 compatible,” not “all OWL 2 EL.” Named classes/properties, intersections, existential restrictions, the core class axioms, individual equality/difference, supported assertions, domains/ranges, and named property hierarchies/chains are handled according to the pinned converter. Several data constructs, cardinalities, universals, inverses, keys, and other features are ignored or partial with exact completeness reasons. Annotation axioms remain in the shared snapshot but are non-logical to pyELK.

Runtime and current limits

Installed pyELK never launches or embeds Java. Java is used only by opt-in development tools that regenerate or compare frozen ELK 0.6 oracle data; JARs and class files are rejected from release artifacts. The Python fallback also needs no Rust/C/C++ compiler at installation or runtime.

Version 0.1 sessions are immutable. Incremental reasoning, proofs/explanations/tracing, method-for-method OWL API compatibility, datatype reasoning, a CLI, and a Protégé plugin are outside scope. Input syntaxes and import retrieval are exactly those supplied by the installed compatible pyowl-core. Native availability is platform-wheel dependent; Python remains the semantic reference and fallback.

Development verification

The default suite is Java-free:

PYELK_BUILD_PURE=1 PYELK_PURE_PYTHON=1 python -m pytest
ruff format --check .
ruff check .
mypy
lint-imports

The unified frozen-corpus runner covers all 124 upstream ontologies and 138 goldens:

python tests/parity/runner.py --backend python --workers 1
python tests/parity/runner.py --backend rust --workers 1 --workers 0

Integrated benchmark metadata and commands live in benchmarks/manifest.toml and tools/benchmark.py. Java oracle and performance gates are opt-in and pinned; ordinary tests and installed artifacts have no Java dependency. See specs/verification.md for the release gates and specs/traceability.md for the implemented source-to-test map.

pyELK is an independent Apache-2.0 reimplementation informed by the pinned ELK source and tests. Attribution and modification details are in NOTICE.pyelk.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

pyelk_reasoner-0.1.1.tar.gz (238.6 kB view details)

Uploaded Source

Built Distributions

If you're not sure about the file name format, learn more about wheel file names.

pyelk_reasoner-0.1.1-py3-none-any.whl (145.7 kB view details)

Uploaded Python 3

pyelk_reasoner-0.1.1-cp310-abi3-win_amd64.whl (950.7 kB view details)

Uploaded CPython 3.10+Windows x86-64

pyelk_reasoner-0.1.1-cp310-abi3-musllinux_1_2_x86_64.whl (1.3 MB view details)

Uploaded CPython 3.10+musllinux: musl 1.2+ x86-64

pyelk_reasoner-0.1.1-cp310-abi3-musllinux_1_2_aarch64.whl (1.2 MB view details)

Uploaded CPython 3.10+musllinux: musl 1.2+ ARM64

pyelk_reasoner-0.1.1-cp310-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl (1.2 MB view details)

Uploaded CPython 3.10+manylinux: glibc 2.17+ x86-64

pyelk_reasoner-0.1.1-cp310-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl (1.1 MB view details)

Uploaded CPython 3.10+manylinux: glibc 2.17+ ARM64

pyelk_reasoner-0.1.1-cp310-abi3-macosx_11_0_arm64.whl (1.0 MB view details)

Uploaded CPython 3.10+macOS 11.0+ ARM64

pyelk_reasoner-0.1.1-cp310-abi3-macosx_10_12_x86_64.whl (1.1 MB view details)

Uploaded CPython 3.10+macOS 10.12+ x86-64

File details

Details for the file pyelk_reasoner-0.1.1.tar.gz.

File metadata

  • Download URL: pyelk_reasoner-0.1.1.tar.gz
  • Upload date:
  • Size: 238.6 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.12.3

File hashes

Hashes for pyelk_reasoner-0.1.1.tar.gz
Algorithm Hash digest
SHA256 ad451438bd5b04d340532005d13184c4da47b4ed7473dde44edece7146992f55
MD5 b32f09490cd1084e69db49a4e4ac3fc2
BLAKE2b-256 88ea8e920d5dd5357b35c6ffe631ca2e10dfb0772a7b7075e35e37490b91e86c

See more details on using hashes here.

File details

Details for the file pyelk_reasoner-0.1.1-py3-none-any.whl.

File metadata

  • Download URL: pyelk_reasoner-0.1.1-py3-none-any.whl
  • Upload date:
  • Size: 145.7 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.12.3

File hashes

Hashes for pyelk_reasoner-0.1.1-py3-none-any.whl
Algorithm Hash digest
SHA256 06d20ff599141e4a1cc40af67da0ce9a1752ad4143fcc63db336e1ce22e6f723
MD5 cc2d12245532109b2c7783684a04a3eb
BLAKE2b-256 e65c1857027bf6fed0a9d9d9d1b32fa79b745acba300e458384f1f8563759c47

See more details on using hashes here.

File details

Details for the file pyelk_reasoner-0.1.1-cp310-abi3-win_amd64.whl.

File metadata

File hashes

Hashes for pyelk_reasoner-0.1.1-cp310-abi3-win_amd64.whl
Algorithm Hash digest
SHA256 04df0567ca094cb60315e24ec4c796c0b47db21704a9f4c3c13e8a503a9fca25
MD5 1da76e106e30d9eafa4d11ef96a47d68
BLAKE2b-256 6a0f495aa0048d5353384226eb9bb175222aa356c9eb7fc4aed6a4ca50cb0a3e

See more details on using hashes here.

File details

Details for the file pyelk_reasoner-0.1.1-cp310-abi3-musllinux_1_2_x86_64.whl.

File metadata

File hashes

Hashes for pyelk_reasoner-0.1.1-cp310-abi3-musllinux_1_2_x86_64.whl
Algorithm Hash digest
SHA256 cc060082757a190dec9b36f39e474f3cbccfbb86f9502321e069171b4b713e13
MD5 634f3fc96c2488509533023a3a417ca0
BLAKE2b-256 c8c83c698efec391be3f8c3fed9e4a62a7b46a82c3a643b9b2bf5da7e0ca2b68

See more details on using hashes here.

File details

Details for the file pyelk_reasoner-0.1.1-cp310-abi3-musllinux_1_2_aarch64.whl.

File metadata

File hashes

Hashes for pyelk_reasoner-0.1.1-cp310-abi3-musllinux_1_2_aarch64.whl
Algorithm Hash digest
SHA256 f9eb07e44c5732c6bd0bd6016d6cdcbf795da181809360d8bb2ca6cd7e561cf2
MD5 72347848060dc49c06b5df4e7b1c6505
BLAKE2b-256 9838d33a20f2ae124dddd1abee24fbefcf37bf59255759ef9114bfe188ead2eb

See more details on using hashes here.

File details

Details for the file pyelk_reasoner-0.1.1-cp310-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl.

File metadata

File hashes

Hashes for pyelk_reasoner-0.1.1-cp310-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl
Algorithm Hash digest
SHA256 476fe6a9d59334dbdd5fd1bb930de58503bcbafabae29b00fac14ba6aeb82bd2
MD5 1d887082fd7306ad42c69b2b7aae017f
BLAKE2b-256 6e2854b6abceacceae0328475986c541f52452c8d55fd043a607bbd0879b9fd5

See more details on using hashes here.

File details

Details for the file pyelk_reasoner-0.1.1-cp310-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl.

File metadata

File hashes

Hashes for pyelk_reasoner-0.1.1-cp310-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl
Algorithm Hash digest
SHA256 ea4f4ab64d2233f61db3754ff7a59ea5c35786d98e5d7976b44db0b6554afe0d
MD5 360b5d83a931981919cfba6f7e7f58e1
BLAKE2b-256 21d6adea0e92b9c30fc9258e0e74a74e67a593c817371f664196dacea7bc162f

See more details on using hashes here.

File details

Details for the file pyelk_reasoner-0.1.1-cp310-abi3-macosx_11_0_arm64.whl.

File metadata

File hashes

Hashes for pyelk_reasoner-0.1.1-cp310-abi3-macosx_11_0_arm64.whl
Algorithm Hash digest
SHA256 887c8ac3965e008737dbb291a9aaba6212140cde1cf54e1c12c95643454f3c7a
MD5 f87015b010e0a3e2f11c6696e8e06ad7
BLAKE2b-256 b4677541857394bbede5070702591ae86a50e9dfffa31a4c353f7a44d4367133

See more details on using hashes here.

File details

Details for the file pyelk_reasoner-0.1.1-cp310-abi3-macosx_10_12_x86_64.whl.

File metadata

File hashes

Hashes for pyelk_reasoner-0.1.1-cp310-abi3-macosx_10_12_x86_64.whl
Algorithm Hash digest
SHA256 10598d23c4a16ece5ff71c4b95c256428ef5b0c1c80654033cdabdcf74f62f40
MD5 0119704498d87377444670446f6d4aff
BLAKE2b-256 b951f23210f9c6b92a7b8f81c60c497b72dacc724bcd76fd6099c8620ac602ba

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page