This commit is contained in:
2026-08-07 18:16:03 -04:00
parent cd47d868c1
commit 36a105edfc
14 changed files with 175 additions and 257 deletions
+1
View File
@@ -5,4 +5,5 @@ edition = "2024"
[dependencies]
oxigraph.workspace = true
thiserror.workspace = true
tracing.workspace = true
+21
View File
@@ -0,0 +1,21 @@
use thiserror::Error;
pub type Result<R> = std::result::Result<R, Error>;
#[derive(Error, Debug)]
pub enum Error {
#[error(transparent)]
IriParse(#[from] oxigraph::model::IriParseError),
#[error(transparent)]
Storage(#[from] oxigraph::store::StorageError),
#[error(transparent)]
SparqlSyntax(#[from] oxigraph::sparql::SparqlSyntaxError),
#[error(transparent)]
QueryEvaluation(#[from] oxigraph::sparql::QueryEvaluationError),
#[error(transparent)]
UpdateEvaluation(#[from] oxigraph::sparql::UpdateEvaluationError)
}
+59
View File
@@ -0,0 +1,59 @@
use oxigraph::model::{Dataset, GraphNameRef, NamedNodeRef, Quad, QuadRef};
use oxigraph::sparql::{QueryResults, SparqlEvaluator};
use oxigraph::store::{Store, Transaction};
use tracing::{debug_span, field};
const RDF_SCHEMA_PREFIX: &str = "http://www.w3.org/2000/01/rdf-schema#";
const GL_PREFIX: &str = "https://graphofliberty.org/";
const INFERENCE_GRAPH: GraphNameRef = GraphNameRef::NamedNode(NamedNodeRef::new_unchecked("https://graphofliberty.org/inference"));
const INPUT_GRAPH: GraphNameRef = GraphNameRef::NamedNode(NamedNodeRef::new_unchecked("https://graphofliberty.org/input"));
pub struct InferenceEngine {
ontology: Store,
}
/// `prp-spo1`
fn sub_property_of(transaction: Transaction) -> crate::Result<Dataset> {
let span = debug_span!("rdfs:subPropertyOf", inferences = field::Empty).entered();
let mut query = SparqlEvaluator::new()
.with_prefix("rdfs", RDF_SCHEMA_PREFIX)?
.with_prefix("gl", GL_PREFIX)?
.parse_query(r#"CONSTRUCT {
?x ?p2 ?y .
} WHERE {
?p1 rdfs:subPropertyOf+ ?p2 .
GRAPH gl:input { ?x ?p1 ?y . }
}"#)?;
query.dataset_mut().set_default_graph_as_union();
let inferences = if let QueryResults::Graph(result) = query.on_transaction(&transaction).execute()? {
result.filter_map(Result::ok)
.map(|triple| Quad::new(triple.subject, triple.predicate, triple.object, INFERENCE_GRAPH))
.collect()
} else {
Dataset::new()
};
span.record("inferences", inferences.len());
Ok(inferences)
}
impl InferenceEngine {
pub fn new(store: Store) -> Self {
Self {
ontology: store,
}
}
pub fn run(&self, dataset: &Dataset) -> crate::Result<Dataset> {
let _span = debug_span!("Inference").entered();
let mut transaction = self.ontology.start_transaction()?;
let input = dataset.iter().map(|triple| QuadRef::new(triple.subject, triple.predicate, triple.object, INPUT_GRAPH));
transaction.extend(input);
sub_property_of(transaction)
}
}
+7 -2
View File
@@ -1,4 +1,9 @@
mod curie;
pub mod vocab;
//mod materialize;
mod error;
pub use curie::{PREFIXES, CurieHelper};
pub mod vocab;
pub mod inference;
pub use curie::{PREFIXES, CurieHelper};
pub use error::{Error, Result};
+114
View File
@@ -0,0 +1,114 @@
use crate::vocab;
use oxigraph::model::{Dataset, GraphName, NamedNodeRef, NamedOrBlankNode, NamedOrBlankNodeRef, Term};
use oxigraph::sparql::SparqlEvaluator;
use oxigraph::store::Store;
use tracing::{debug, debug_span};
const RDF_SCHEMA: NamedNodeRef = NamedNodeRef::new_unchecked("http://www.w3.org/2000/01/rdf-schema#");
pub fn same_as(store: &mut Store) -> crate::Result<()> {
let _span = debug_span!("Materialize owl:sameAs").entered();
let additional_quads = store
.quads_for_pattern(None, Some(vocab::owl::SAME_AS), None, None)
.filter_map(Result::ok)
.fold(Dataset::new(), |mut new_dataset, alias| {
if let NamedOrBlankNode::NamedNode(x) = alias.subject
&& let Term::NamedNode(y) = alias.object
{
for mut quad in store.quads_for_pattern(
Some(NamedOrBlankNodeRef::NamedNode(x.as_ref())),
None,
None,
None,
).filter_map(Result::ok) {
quad.subject = NamedOrBlankNode::NamedNode(y.clone());
quad.graph_name = GraphName::NamedNode(INFERENCE_GRAPH.into_owned());
new_dataset.insert(quad.as_ref());
}
for mut quad in store.quads_for_pattern(
Some(NamedOrBlankNodeRef::NamedNode(y.as_ref())),
None,
None,
None,
).filter_map(Result::ok) {
quad.subject = NamedOrBlankNode::NamedNode(x.clone());
quad.graph_name = GraphName::NamedNode(INFERENCE_GRAPH.into_owned());
new_dataset.insert(quad.as_ref());
}
}
new_dataset
});
let old_size = store.len()?;
store.extend(&additional_quads)?;
let new_size = store.len()?;
if new_size > old_size {
let difference = new_size - old_size;
debug!(?old_size, ?new_size, ?difference, "same_as");
same_as(store)?;
}
Ok(())
}
pub fn super_properties(store: &mut Store) -> crate::Result<()> {
let _span = debug_span!("Materialize rdfs:subPropertyOf").entered();
let update = SparqlEvaluator::new()
.with_prefix("rdfs", RDF_SCHEMA.as_str())?
.with_prefix("inference", INFERENCE_GRAPH.as_str())?
.parse_update(r#"INSERT {
GRAPH inference: {
?property rdfs:subPropertyOf ?parent .
?subject ?parent ?object .
}
} WHERE {
GRAPH ?g1 { ?property rdfs:subPropertyOf+ ?parent }
OPTIONAL { GRAPH ?g2 { ?subject ?property ?object } }
}"#)?;
let old_size = store.len()?;
update.on_store(&store).execute()?;
let new_size = store.len()?;
if new_size > old_size {
let difference = new_size - old_size;
debug!(?old_size, ?new_size, ?difference, "super_properties");
super_properties(store)?;
}
Ok(())
}
pub fn super_classes(store: &mut Store) -> crate::Result<()> {
let _span = debug_span!("Materialize rdfs:subClassOf").entered();
let update = SparqlEvaluator::new()
.with_prefix("rdfs", RDF_SCHEMA.as_str())?
.with_prefix("inference", INFERENCE_GRAPH.as_str())?
.parse_update(r#"INSERT {
GRAPH inference: {
?class rdfs:subClassOf ?parent .
?item a ?parent .
}
} WHERE {
GRAPH ?g1 { ?class rdfs:subClassOf+ ?parent }
OPTIONAL { GRAPH ?g2 { ?item a ?class } }
}"#)?;
let old_size = store.len()?;
update.on_store(&store).execute()?;
let new_size = store.len()?;
if new_size > old_size {
let difference = new_size - old_size;
debug!(?old_size, ?new_size, ?difference, "super_classes");
super_classes(store)?;
}
Ok(())
}