Repository navigation
Releases: Trivyn/howl
Release list
v0.7.0
Each omission now says why its axiom was dropped, and an irregular RBox's omissions name the axioms
that make it irregular (issue #125). A host that wants to refuse an edit breaking an OWL 2 DL global
restriction no longer has to diff omission sets, which also catch harmless profile gaps, or parse an
omission's text, and it can point at the edit to undo. Every report golden is byte-identical, and
the slop-std-sys 0.5 / slop-rdf-sys 0.6 ranges are unchanged.
Added
Omission::reason(Rust) andhowl_run_omission_reason/howl_run_omission_reason_role
(C), with probe-run equivalents. The reasons:OutOfProfile: the construct is not in the profile;IrregularRbox { witness }: an RBox axiom of an RBox that is not regular.witnesslists the
axioms that make it so (SPEC §5.2's policy, the union over every failure), the same on every
such omission. In C:howl_run_omission_witness_len/howl_run_omission_witness;NonSimpleRole { role }: a simple-role-only axiom or restriction over a non-simple role, naming
the canonically least such role;RangeCondition: anel/el++chain that breaks the range condition;ResourceGuard: asriqnumber restriction above 16, or a chain set aside by chain
elimination's size guard;Undeclared,UnresolvedImportandUnrecognized.
- When an axiom fails several conditions, the first in the gate's order decides (SPEC §6.2).
- The reason comes from checks the gate already runs, so classification costs the same.
Changed
Omissionis#[non_exhaustive], and gains a field. Code that built anOmissionwith a
struct literal no longer compiles; reading its fields is unchanged. A later witness for an
irregular RBox will then not need another breaking change.
v0.6.1
The answer run (Reasoner::answer, howl_run_answer, howl ask) no longer classifies the whole
input over an omission the whole input shares. Before, any omission inside the queries' module sent
it to the whole input, which carries the same omission and so could only give the same unknown,
after a full classification. A class with a datatype restriction, such as rdfs:label exactly 1,
was enough. On CCO with one such class, howl ask --profile auto --ask-unsat took 7.1 s and now
takes 0.02 s, with the same answers. Nothing changes shape. Every report golden is byte-identical,
and the slop-std-sys 0.5 / slop-rdf-sys 0.6 ranges are unchanged.
Changed
- The module's run answers when every axiom it omits is read by no profile the whole input's run
could use (SPEC §8.4 L5). That set is the profile asked for, or el, el++ and sriq underauto.- Such omissions include a datatype restriction, a data axiom, SWRL and a key, and the decode-level
omissions (an undeclared property, an unresolved import). - The run must reach its fixpoint, without a step-down.
- Its
yeses stand, and its omissions rule out ano, as the whole input's would.
- Such omissions include a datatype restriction, a data axiom, SWRL and a key, and the decode-level
- The fallback to the whole input stays for:
- a round cap or the clause budget;
auto's step-down;- an RBox, simple-role or S1-guard omission;
- an omission another profile could read. Under
auto, the whole input may choose a profile that
reads it: for example a union, whichsriqreads andel++omits.
- A module run with omissions gets a tenth of
sriq's clause budget. When it would not finish,
the whole input is classified anyway, so the tenth bounds the wasted run. On OBI'ssriqmodule
that wasted run took 25 s of a 30 s answer run; it now takes about 0.7 s more than 0.6.0's 5.5 s. - An answer run's omissions are its own run's. When the module answers,
Answers::omissions
andomission_details(and the C accessors) list the module's omissions, each with its kind and
document, rather than the whole input's. - Constraint (SPEC §8.4): the answer run may now give a
yeswhere the two-run mode says
unknown, from a run that is not complete. Every suchyesis sound, since the module is part of
the input. It still never contradicts a two-runyesorno, and givesnoonly from a complete
run.
v0.6.0
Each omission now says where it came from: its kind and the document that introduced it (issue #67).
A host that loads a background ontology (BFO and CCO, say) before its own document can tell its own
coverage gaps from the background's in one run, instead of classifying the background alone and
diffing omission strings. The C API and the Rust crate only gain: nothing existing changes shape,
the report lines are byte-identical, and the slop-std-sys 0.5 / slop-rdf-sys 0.6 ranges are
unchanged. An owl:imports that names an attested ontology's version IRI now resolves (issue
#71).
Added
- Omission provenance (SPEC §8.4, issue #67). Every omission of a classify run, of its probe run
and of an answer run has three things:- its kind, the head token of its rendering (
MissingDeclaration,UndeclaredProperty,
UnresolvedImport,SubClassOf,DataAxiom,HasKey,Unrecognized, ...); - a
MissingDeclaration's entity kind; - its document, the index of the document that introduced it in
addorder (from 0), or none
when no single document did, for example aMissingDeclarationcited from two documents. The
probe run's omissions are placed among the input's documents or the probe documents. - An axiom stated in two documents is omitted once per copy under
elandel++, and each copy
names its own document.sriqkeeps one copy, which names the first document. - The document is provenance beside the coverage, never part of an omission's identity. Omissions
are compared, ordered, deduplicated and hashed exactly as before. - C API:
howl_run_omission_kind,howl_run_omission_entity_kind,
howl_run_omission_document;howl_run_probe_omission_kind,_entity_kind,_document,
_source; the enumstypes_EntityKindandDocumentSource. Each is total: out of range it
gives"",entity_classor -1. - Rust:
Report::omission_details,Report::probe_omission_details,
Answers::omission_details, each&[Omission]in the order of the matchingomissionslist;
the structOmission { kind, entity_kind, document, text }and the#[non_exhaustive]enums
EntityKindandDocument { Input(usize), Probe(usize) }. All additive.
- its kind, the head token of its rendering (
Fixed
- An import by version IRI resolves (issue #71). OWL 2 lets
owl:importsname the imported
ontology's IRI or its version IRI. An import naming the version stayed anUnresolvedImport, and
the run inconclusive, even with the document supplied and attested. Now an attested ontology's
owl:versionIRI, as the supplied documents declare it, counts as attested too, so neither-I
nor a library host has to know which form each importer used. A version IRI declared by an
ontology nobody attested still resolves nothing. Pinned by theby-versionimport golden.
v0.5.0
Probe queries can be answered without classifying the whole ontology first: howl ask,
howl_run_answer and Reasoner::answer classify only the queried classes' module. On Core (BFO and
CCO merged), 30 queries take about 1 s, where the two-run mode takes 14 s. HOWL is now generated
with slop 0.5.1, which renames the enum and struct types in howl.h, so C callers must update those
names (see Changed). The existing Rust API and the report grammar are unchanged.
Added
- Answering probe queries without classifying first (SPEC §8.4). The answer run is the probe
run alone, with no main run: usually one classification, of the queries' module, instead of two
of the whole input. It is additive: nothing existing changes.- It classifies the queries' module first. This is the syntactic ⊥-locality module (Cuenca
Grau, Horrocks, Kazakov and Sattler, JAIR 2008), which entails exactly what the ontology entails
about the queried classes whenever HOWL can read every axiom in it. It is usually far smaller,
andautopicks a profile for the module alone. - It falls back to the whole input when the module's run is incomplete: an omission inside
the module, a round cap, or the clause budget. That makes two classifications in that case. - It gives every
yesandnothe two-run mode gives. It also answers where an omission
outside the module made that mode sayunknown. - C API:
howl_run_answer(input, options), read with the existinghowl_run_probe_*
accessors. The run has no report, its verdict isinconclusive, andhowl_run_profilenames
the profile that answered. - Rust:
Reasoner::answerandanswer_cancellable, returningAnswers(the profile,
probes,inconsistent,omissionsandlines()). - CLI:
howl ask FILE [--probe F]... --ask-unsat C | --ask-sub A B ...printsprofile Pand
the probe lines. It exits 0 when it answered, whatever the answers; 2 if cancelled; 3 on an
error.
- It classifies the queries' module first. This is the syntactic ⊥-locality module (Cuenca
Changed
- The Rust crate compiles the engine with the release CLI's flags:
-O3and 65,536 intern
buckets, where it used-O2and 4,096. The crate pins these whatever the host's Cargo profile.
It is 0–3% faster on the corpus, and every report is identical. howl_run_okmeans the run has a result, now including an answer run's answers. It is
unchanged for everyhowl_run_classifyrun: it is false exactly when the run faulted.- The macOS release binary and library are built with Apple clang, where they used Homebrew
gcc-14 (a choice inherited from GROWL's CI, never measured).- On the corpus, a clang build of the engine runs 14–21% faster than a gcc build (gcc-16 locally,
release flags), with identical reports. - CI's macOS job builds and tests with clang too.
- Linux and Windows are unchanged (gcc, MinGW gcc).
- On the corpus, a clang build of the engine runs 14–21% faster than a gcc build (gcc-16 locally,
- Generated with slop 0.5.1 and slop-rdf 0.6.0. The crate depends on
slop-std-sys0.5 and
slop-rdf-sys0.6.- Breaking for C callers: slop 0.5 adds the package to generated C names (slop #360), so
the enum and struct types inhowl.hare renamed.types_Verdict_*,types_Profile_*,
types_Termination*,probe_QueryKind_*andprobe_ProbeAnswer_*gain ahowl_prefix
(howl_types_Verdict_verdict_coherent), andrdf_TermKind_*becomes
slop_rdf_rdf_TermKind_*. Values and layouts are unchanged, as are thehowl_*functions and
the Rust API. - The crate's
-sysdependencies are caret ranges again, where they were pinned exactly. Its
build no longer compares the runtime header byte for byte. Every generated header checks
SLOP_RUNTIME_ABIinstead, so C generated for another runtime ABI fails to compile, and a
header that differs only in comments builds.
- Breaking for C callers: slop 0.5 adds the package to generated C names (slop #360), so
Fixed
- Turtle: a dot inside a prefix name or blank-node label (slop-rdf #13).
@prefix j.0:, as
rdflib writes it, failed to parse._:b.1was cut to_:band the leftover read as the next
statement's subject, a silently wrong triple. Both now read as Turtle defines them. A literal in
subject position is now a parse error (exit 3); it used to be read as a triple. sriq: aDisjointUnionheaded byowl:Thingorowl:Nothingno longer faults. The head
reached the clause boundary as a plain class name, which has no id for a built-in, so a valid
ontology was refused as an input error. It now lowers to⊤or⊥, as decode reads those names
everywhere else:DisjointUnion(owl:Thing, P, Q)with P and Q empty is inconsistent, and
DisjointUnion(owl:Nothing, P, Q)makes P and Q unsatisfiable.
HOWL 0.4.0: sriq
HOWL 0.4.0 adds sriq, a third profile. It reasons over inverses, unions, ∀, full negation and qualified cardinalities, which el and el++ omit.
Highlights
--profile sriq: the SRIQ object fragment with an ABox. It is available from the CLI, the C API and the Rust crate (Profile::Sriq).- BFO-core and CCO are fully covered and match HermiT on every class pair.
autopicks amongel,el++andsriq: the cheapest profile that covers the ontology, or else the one that omits least.- Bounded runs:
sriqstops at a fixed budget of 500,000 clauses. A run that hits the budget is inconclusive (exit 2) unless it has already found an inconsistency or an unsatisfiable class. Whenautopickedsriqand the budget is hit, it falls back toelorel++and notes this on stderr.
What changes if you're upgrading from 0.3.0
- The default profile is still
el. Nothing changes unless you ask forsriqorauto. - Under
auto, ontologies with inverses or unions now run undersriq. The report'sprofileline says which profile answered. - The report format, exit codes, C ABI and Rust API are unchanged.
- Every profile: OWL 1-style
owl:intersectionOf/unionOf/oneOfon a named class is now reported as an omission instead of silently ignored. A report that used to say coherent may now say inconclusive.
Fixes
- The named-class
owl:intersectionOfcase above. - A literal quoted in an omission is now escaped, so a multi-line literal can no longer break the report format.
Known limits
sriqruns single-threaded.--workersis accepted and the report is identical at any worker count.- RO hits the budget under
sriq, andautofalls back toel++, which omits RO's inverses. Both runs are inconclusive. - OBI is out of
sriq's reach.autofalls back toel++. - Performance against HermiT and Sequoia has not been measured yet.
Get it
- CLI and C library: the binaries and
libhowlzips below, for macOS arm64, Linux x86_64 and Windows x86_64. - Rust:
howl-reasoner = "0.4.0"on crates.io.
The full list of changes is in CHANGELOG.md.
v0.3.0
Probe queries ask HOWL whether a class is unsatisfiable, or one class subsumed by another, without
touching the report. An undeclared property is now an omission instead of failing the whole run.
The C API and the Rust crate only gain; nothing existing changes shape.
Added
- Probe queries (SPEC §8.4, issue #68). A caller adds probe documents, which define classes to
ask about, and queries: isCunsatisfiable, isA ⊑ B. A second classification answers them,
over the input, the probe documents and a declaration of every class a query names. The main
report, verdict and exit code are exactly those of a run without probes: a probe class never
reachesunsatisfiable, the taxonomy, the omissions or the verdict.- Answers are
yes(entailed, whatever the probe run's coverage),no(not entailed, by a
complete probe run) orunknown. An inconsistent probe run answersyesto everything and
says so. - Each query is answered once, in canonical order. The probe lines (
probes N,
probe-inconsistent,probe-omitted,probe unsatisfiable C yes,probe subsumed A B no)
follow the report only when a query was asked, so no existing report changes. - C API:
howl_input_add_probe_turtle,howl_input_begin_probe_document,
howl_input_add_probe_triple,howl_input_ask_unsatisfiable,howl_input_ask_subsumed;
howl_run_probe_len,_kind,_sub,_sup,_answer,_inconsistent,_omission_len,
_omission; the enumsprobe_QueryKindandprobe_ProbeAnswer. - Rust:
Input::add_probe_turtle,add_probe_triples,ask_unsatisfiable,ask_subsumed;
Report::probes,probe_inconsistent,probe_omissions; the typesProbeAnswerand the
#[non_exhaustive]enumsQueryandAnswer. All additive. - CLI:
--probe FILE,--ask-unsat C,--ask-sub A B, each repeatable.
- Answers are
Changed
- An undeclared property is an omission, not an input error (SPEC §5.2, issue #70). In
:B :editorNote "x"or:B :seeAlsoClass :A, with the predicate never declared, HOWL cannot
tell an annotation from a data or object property assertion. Such a triple used to fail the whole
run (exit 3). It is now anUndeclaredProperty(<iri>)omission, one per property: the rest of the
document is read, and the run is inconclusive (exit 2). HOWL still does not guess; declaring the
property clears it. Conflicting declarations are still an input error.
v0.2.1
A toolchain update. HOWL's answers do not change.
- Generated with slop 0.4.3 and slop-rdf 0.5.2, and pinned exactly to
slop-std-sys = "=0.4.3"
andslop-rdf-sys = "=0.5.2". slop 0.4.3's runtime keeps gcc from inlining the list-growth slow
path at every push site, which made the stack frame of a recursive function with many pushes grow
several times over (a stack overflow in a sibling project's evaluator). clang builds are unchanged.
v0.2.0
Data restrictions are read as data: a qualified data cardinality (owl:onDataRange) no longer fails
the whole run, and data axioms no longer pass for object ones. Idle data properties' axioms are set
aside. One breaking change, in the Rust crate's Profile.
Specified
sriq, the SRIQ object fragment with an ABox, is specified in SPEC §5.5. It uses Simančík's
chain elimination, then Tena Cucala, Cuenca Grau and Horrocks's consequence-based calculus. It is
not built yet;--profile sriqexits 3.
Changed
- The data lemma (SPEC §5.2): the domain, range, functionality, sub-property, equivalence and
disjointness axioms of a data property that nothing uses are set aside in every profile. "Used"
means used in a class expression, an assertion or a key, by the property or by any data property
below it. These axioms provably change no answer, so they are no longer omissions.- On CCO, 16 omissions go: 220 → 204 under
el, 218 → 202 underel++. - On RO, 5 go, including the 4 lines its range's datatype restriction used to leak.
- On OBI, 2 go.
- The RO and OBI projections keep those axioms, and their pins were re-certified against HermiT.
- On CCO, 16 omissions go: 220 → 204 under
horn-sriqis dropped from the profile ladder. Onsriq's calculus it would run at the same
cost, so it would only be a stricter gate.- Its value keeps its place in the C
types_Profileenum and in Rust'sProfile, so neither is
renumbered. - Asking for it is always refused (
unavailable, exit 3), and the message namessriq.
- Its value keeps its place in the C
- The C API's
howl_run_fault_messagenow says why anunavailablerun was refused: "was
dropped", or "is not implemented yet". It used to be empty. - Rust (breaking):
Profileis#[non_exhaustive].Profile::HornSriqis deprecated.Fault::ProfileUnavailable's message for it says it was dropped.
Fixed
- Data restrictions and data property axioms on a property that is used are now data.
- Accepted as object axioms before.
DataSomeValuesFrom, andowl:equivalentProperty
between data properties, were accepted byelandel++: the first as an existential over a
"role" whose datatype was taken for a class, the second as role inclusions. Both are now
omissions. On OBI, 5 suchDataSomeValuesFromaxioms move from accepted to omitted: 506 → 508
omissions, because the 3 MissingDeclaration lines they used to cause go away. - Mislabelled before.
FunctionalDataProperty,DisjointDataPropertiesand the data
cardinalities were labelled as their object twins. - Faulted before.
owl:onDataRangeandowl:onPropertiesfaulted the whole document
(exit 3). They are now omissions.
- Accepted as object axioms before.
- An ambiguous
owl:inverseOfnode is no longer dropped silently._:x owl:inverseOf :p , :q
makes no ObjectInverseOf, and no axiom could read it. Both triples vanished, with no axiom and no
omission, and the document came out coherent. Each is now an omission.
Changed (reports)
ObjectInverseOfis represented:RoleIdhas an inverse arm (SPEC §6.2), forsriq. Every
rung built so far still omits it, but the omission now reads as the axiom it is. Before, it was an
N-Triples fragment with blank-node labels, and every restriction on an inverse rendered as the
same constant string. RO's 11 inverse omissions are re-rendered; their count is unchanged.DisjointUnionis read whole and renderedDisjointUnion(C D₁ … Dₙ). It is still an omission.
v0.1.1
A build fix. HOWL's answers do not change.
- 0.1.0 no longer builds from a fresh resolve. It asked for
slop-std-sysandslop-rdf-syswith
a caret, so once slop-std-sys 0.4.2 was published a new build resolved it, andbuild.rs, which
requires the runtime header to match HOWL's generated C byte for byte, panicked. 0.1.1 is generated
with slop 0.4.2 and slop-rdf 0.5.1, and pinsslop-std-sys = "=0.4.2"and
slop-rdf-sys = "=0.5.1"exactly. - Windows (#64). slop 0.4.2's runtime threads through Win32 on Windows. The crate links pthread
only on other targets, and compiles its C with clang-cl on MSVC targets (cl.execannot compile the
generated C), as the -sys crates do. CI builds and tests the crate on Windows, with MSVC and with
MinGW.
v0.1.0
The first release. HOWL classifies OWL ontologies: the complete subsumption hierarchy, every
unsatisfiable class, and whether the ontology is coherent, including the subsumptions that follow
from defined classes, which OWL 2 RL materialization cannot see.
What's in it
- Two profiles, both polynomial.
el(the default): ELH⊥R+ with domains, ranges, ABox assertions, and¬in a superclass,
domain or range.el++: the OWL 2 EL object fragment, adding nominals,hasValue,hasSelf, reflexive roles,
individual equality and inequality, and negative assertions.autopicks the cheapest profile that covers the ontology.
- The
howlCLI.howl validateexits 0 (coherent), 1 (incoherent), 2 (inconclusive — not a pass) or 3 (error).- Flags:
-Ifor attested imports,--profile,--strict,--report(a canonical, hashable
report),--workers,--max-iterations,--timingsand--version.
- A C library.
libhowl.awithinclude/howl.h. The API is written in SLOP and its header is generated by
slop.- Inputs and runs are opaque handles. Turtle and triple input, attested imports and cancellation
are supported. Seeexamples/c/classify.c.
- A Rust crate in
rust/, a safe wrapper over the C API. It is not on crates.io yet.
What it guarantees
- Nothing is silently dropped. Every axiom HOWL does not reason over is listed as an omission,
and any omission makes an otherwise clean run inconclusive. - A finding beats incomplete coverage. An incoherence found under partial coverage is still
reported as incoherent. - Reports are input-determined. For a given round cap, the canonical report is byte-identical
at every worker count, and it never contains a timing, path or worker count.
Evidence
- Differential testing:
- every fixture against HermiT, and against ELK where ELK is probed capable of the construct.
The one exception isowl:Thing ⊑ owl:Nothing, which HermiT cannot load; it is checked against
ELK alone; - the in-profile part of RO, OBI, the Gene Ontology and EL-GALEN against its routed oracle, over
every pair of named classes.
- every fixture against HermiT, and against ELK where ELK is probed capable of the construct.
- Conformance: the W3C OWL 2 and ELK conformance suites, wherever HOWL reasons completely.
- Fuzzing: 400 generated ontologies with ABoxes per profile, matched against HermiT.
- Performance:
elclassifies within 5× of ELK, the SPEC §12 target. On an Apple M3 Ultra,
the Gene Ontology runs at 0.84× ELK's time and EL-GALEN at 2.9×.el++has no target. It runs
at 0.95× and 5.25×. - Contracts: 89 functions carry Z3-checked contracts, among them the profile selection, the
verdict's safety property and eachel++rule's faithfulness.
Known limitations
- Unbuilt profiles.
horn-sriqandsriqare not built; asking for one exits 3. - Omitted constructs.
HasKey, datatypes and data properties, and constructs outside OWL 2 EL
(unions, universal restrictions, cardinalities, inverse properties) are reported as omissions. - No
howl classifyyet.howl classify(--emit,--reduce) is refused until a later
release. - MOOSE port amendments. The
TBoxReasonerport amendments (SPEC §8.5) are on the consumer
side and not delivered here. - Contract checking.
make verifyneeds a slop toolchain from before 0.4.0 until
slop-lang/slop#325 is fixed; the committed C is generated by slop 0.4.0 and slop-rdf 0.5.0. - Prebuilt binaries are for linux-x86_64 and macos-arm64 only. Elsewhere,
makebuilds from the
committed C with any C compiler.