.
This commit is contained in:
@@ -1,10 +1,8 @@
|
|||||||
use std::collections::BTreeMap;
|
use std::collections::BTreeMap;
|
||||||
use std::sync::LazyLock;
|
use std::sync::LazyLock;
|
||||||
use oxigraph::model::{GraphNameRef, NamedNodeRef};
|
use oxigraph::model::{GraphNameRef, NamedNodeRef};
|
||||||
use tracing::debug;
|
|
||||||
|
|
||||||
const ONTOLOGY_PREFIX: &str = "https://graphofliberty.org/2026/04/ont/";
|
const ONTOLOGY_PREFIX: &str = "https://graphofliberty.org/2026/04/ont/";
|
||||||
const ONTOLOGY_GRAPH_NAME: GraphNameRef = GraphNameRef::NamedNode(NamedNodeRef::new_unchecked("https://graphofliberty.org/2026/04/ont"));
|
|
||||||
|
|
||||||
pub static PREFIXES: LazyLock<BTreeMap<String, String>> = LazyLock::new(|| {
|
pub static PREFIXES: LazyLock<BTreeMap<String, String>> = LazyLock::new(|| {
|
||||||
BTreeMap::from_iter([
|
BTreeMap::from_iter([
|
||||||
|
|||||||
+72
-24
@@ -1,36 +1,55 @@
|
|||||||
|
use std::sync::Arc;
|
||||||
use oxigraph::model::{Dataset, GraphNameRef, NamedNodeRef, Quad, QuadRef};
|
use oxigraph::model::{Dataset, GraphNameRef, NamedNodeRef, Quad, QuadRef};
|
||||||
use oxigraph::sparql::{QueryResults, SparqlEvaluator};
|
use oxigraph::sparql::{QueryResults, SparqlEvaluator};
|
||||||
use oxigraph::store::{Store, Transaction};
|
use oxigraph::store::Store;
|
||||||
use tracing::{debug_span, field};
|
use tracing::{debug, debug_span, field};
|
||||||
|
|
||||||
const RDF_SCHEMA_PREFIX: &str = "http://www.w3.org/2000/01/rdf-schema#";
|
const RDF_SCHEMA_PREFIX: &str = "http://www.w3.org/2000/01/rdf-schema#";
|
||||||
const GL_PREFIX: &str = "https://graphofliberty.org/";
|
const GL_PREFIX: &str = "https://graphofliberty.org/";
|
||||||
|
|
||||||
const INFERENCE_GRAPH: GraphNameRef = GraphNameRef::NamedNode(NamedNodeRef::new_unchecked("https://graphofliberty.org/inference"));
|
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"));
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
|
||||||
pub struct InferenceEngine {
|
|
||||||
ontology: Store,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// `prp-spo1`
|
/// `prp-spo1`
|
||||||
fn sub_property_of(transaction: Transaction) -> crate::Result<Dataset> {
|
const SUB_PROPERTY_OF_QUERY: &str = r#"CONSTRUCT {
|
||||||
let span = debug_span!("rdfs:subPropertyOf", inferences = field::Empty).entered();
|
?x ?p2 ?y .
|
||||||
|
} WHERE {
|
||||||
|
?p1 rdfs:subPropertyOf+ ?p2 .
|
||||||
|
GRAPH gl:inference { ?x ?p1 ?y . }
|
||||||
|
}"#;
|
||||||
|
|
||||||
|
/// `cax-sco`
|
||||||
|
const SUB_CLASS_OF_QUERY: &str = r#"CONSTRUCT {
|
||||||
|
?x a ?c2 .
|
||||||
|
} WHERE {
|
||||||
|
?c1 rdfs:subClassOf+ ?c2 .
|
||||||
|
GRAPH gl:inference { ?x a ?c1 . }
|
||||||
|
}"#;
|
||||||
|
|
||||||
|
/// `prp-dom`
|
||||||
|
const DOMAIN_QUERY: &str = r#"CONSTRUCT {
|
||||||
|
?x a ?c .
|
||||||
|
} WHERE {
|
||||||
|
?p rdfs:domain ?c .
|
||||||
|
GRAPH gl:inference { ?x ?p ?y . }
|
||||||
|
}"#;
|
||||||
|
|
||||||
|
/// https://www.w3.org/TR/owl2-profiles/#Reasoning_in_OWL_2_RL_and_RDF_Graphs_using_Rules
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct InferenceEngine {
|
||||||
|
ontology: Dataset,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run_query(name: &str, query: &str, dataset: &Dataset) -> crate::Result<Dataset> {
|
||||||
|
let span = debug_span!("Query", name).entered();
|
||||||
|
|
||||||
let mut query = SparqlEvaluator::new()
|
let mut query = SparqlEvaluator::new()
|
||||||
.with_prefix("rdfs", RDF_SCHEMA_PREFIX)?
|
.with_prefix("rdfs", RDF_SCHEMA_PREFIX)?
|
||||||
.with_prefix("gl", GL_PREFIX)?
|
.with_prefix("gl", GL_PREFIX)?
|
||||||
.parse_query(r#"CONSTRUCT {
|
.parse_query(query)?;
|
||||||
?x ?p2 ?y .
|
|
||||||
} WHERE {
|
|
||||||
?p1 rdfs:subPropertyOf+ ?p2 .
|
|
||||||
GRAPH gl:input { ?x ?p1 ?y . }
|
|
||||||
}"#)?;
|
|
||||||
|
|
||||||
query.dataset_mut().set_default_graph_as_union();
|
query.dataset_mut().set_default_graph_as_union();
|
||||||
|
|
||||||
let inferences = if let QueryResults::Graph(result) = query.on_transaction(&transaction).execute()? {
|
let inferences = if let QueryResults::Graph(result) = query.on_queryable_dataset(dataset).execute()? {
|
||||||
result.filter_map(Result::ok)
|
result.filter_map(Result::ok)
|
||||||
.map(|triple| Quad::new(triple.subject, triple.predicate, triple.object, INFERENCE_GRAPH))
|
.map(|triple| Quad::new(triple.subject, triple.predicate, triple.object, INFERENCE_GRAPH))
|
||||||
.collect()
|
.collect()
|
||||||
@@ -38,23 +57,52 @@ fn sub_property_of(transaction: Transaction) -> crate::Result<Dataset> {
|
|||||||
Dataset::new()
|
Dataset::new()
|
||||||
};
|
};
|
||||||
|
|
||||||
span.record("inferences", inferences.len());
|
|
||||||
Ok(inferences)
|
Ok(inferences)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn run_recursively(mut dataset: Dataset) -> crate::Result<Dataset> {
|
||||||
|
let sub_property_of_result = run_query("rdfs:subPropertyOf", SUB_PROPERTY_OF_QUERY, &dataset)?;
|
||||||
|
let sub_class_of_result = run_query("rdfs:subClassOf", SUB_CLASS_OF_QUERY, &dataset)?;
|
||||||
|
let domain_result = run_query("rdfs:domain", DOMAIN_QUERY, &dataset)?;
|
||||||
|
|
||||||
|
let old_count = dataset.len();
|
||||||
|
dataset.extend(&sub_property_of_result);
|
||||||
|
dataset.extend(&sub_class_of_result);
|
||||||
|
dataset.extend(&domain_result);
|
||||||
|
let new_count = dataset.len();
|
||||||
|
|
||||||
|
if new_count > old_count {
|
||||||
|
run_recursively(dataset)
|
||||||
|
} else {
|
||||||
|
Ok(dataset)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl InferenceEngine {
|
impl InferenceEngine {
|
||||||
pub fn new(store: Store) -> Self {
|
pub fn new(store: Store) -> Self {
|
||||||
|
let mut ontology = Dataset::new();
|
||||||
|
debug_span!("Load Ontology").in_scope(|| {
|
||||||
|
ontology.extend(store.iter().filter_map(Result::ok));
|
||||||
|
});
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
ontology: store,
|
ontology,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn run(&self, dataset: &Dataset) -> crate::Result<Dataset> {
|
pub fn run(&self, dataset: &Dataset) -> crate::Result<Dataset> {
|
||||||
let _span = debug_span!("Inference").entered();
|
let span = debug_span!("Inference", inferences = field::Empty).entered();
|
||||||
|
|
||||||
let mut transaction = self.ontology.start_transaction()?;
|
let input = dataset.iter().map(|triple| QuadRef::new(triple.subject, triple.predicate, triple.object, INFERENCE_GRAPH));
|
||||||
let input = dataset.iter().map(|triple| QuadRef::new(triple.subject, triple.predicate, triple.object, INPUT_GRAPH));
|
let mut ontology = self.ontology.clone();
|
||||||
transaction.extend(input);
|
ontology.extend(input);
|
||||||
sub_property_of(transaction)
|
|
||||||
|
let result: Dataset = run_recursively(ontology)?
|
||||||
|
.quads_for_graph_name(INFERENCE_GRAPH)
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
span.record("inferences", result.len());
|
||||||
|
Ok(result)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+10
-4
@@ -49,22 +49,28 @@ fn main() -> color_eyre::Result<()> {
|
|||||||
let inference_engine = InferenceEngine::new(store.clone());
|
let inference_engine = InferenceEngine::new(store.clone());
|
||||||
|
|
||||||
let graph_name = GraphName::NamedNode(NamedNode::new_unchecked(format!("file://{}", args.dataset_path.to_string_lossy())));
|
let graph_name = GraphName::NamedNode(NamedNode::new_unchecked(format!("file://{}", args.dataset_path.to_string_lossy())));
|
||||||
let mut dataset = RdfParser::from_format(RdfFormat::Turtle)
|
let dataset = RdfParser::from_format(RdfFormat::Turtle)
|
||||||
.with_default_graph(graph_name)
|
.with_default_graph(graph_name)
|
||||||
.for_reader(File::open(&args.dataset_path)?)
|
.for_reader(File::open(&args.dataset_path)?)
|
||||||
.filter_map(Result::ok)
|
.filter_map(Result::ok)
|
||||||
.collect::<Dataset>();
|
.collect::<Dataset>();
|
||||||
let inferences = inference_engine.run(&dataset)?;
|
let dataset_with_inferences = inference_engine.run(&dataset)?;
|
||||||
dataset.extend(&inferences);
|
|
||||||
|
|
||||||
let raw_query = String::from_utf8(std::fs::read(&args.query_path)?)?;
|
let raw_query = String::from_utf8(std::fs::read(&args.query_path)?)?;
|
||||||
let mut query = SparqlEvaluator::new()
|
let mut query = SparqlEvaluator::new()
|
||||||
.parse_query(&raw_query)?;
|
.parse_query(&raw_query)?;
|
||||||
query.dataset_mut().set_default_graph_as_union();
|
query.dataset_mut().set_default_graph_as_union();
|
||||||
|
|
||||||
match query.on_queryable_dataset(&dataset).execute()? {
|
match query.on_queryable_dataset(&dataset_with_inferences).execute()? {
|
||||||
QueryResults::Graph(graph) => {
|
QueryResults::Graph(graph) => {
|
||||||
let mut serializer = RdfSerializer::from_format(RdfFormat::Turtle)
|
let mut serializer = RdfSerializer::from_format(RdfFormat::Turtle)
|
||||||
|
.with_prefix("rdfs", "http://www.w3.org/2000/01/rdf-schema#")?
|
||||||
|
.with_prefix("rdac", "http://rdaregistry.info/Elements/c/")?
|
||||||
|
.with_prefix("ldp", "http://www.w3.org/ns/ldp#")?
|
||||||
|
.with_prefix("fedora", "http://fedora.info/definitions/v4/repository#")?
|
||||||
|
.with_prefix("lrmer", "http://iflastandards.info/ns/lrm/lrmer/")?
|
||||||
|
.with_prefix("owl", "http://www.w3.org/2002/07/owl#")?
|
||||||
|
.with_base_iri("http://fedora.quill.lan/rest/")?
|
||||||
.for_writer(std::io::stdout());
|
.for_writer(std::io::stdout());
|
||||||
for triple in graph.filter_map(Result::ok) {
|
for triple in graph.filter_map(Result::ok) {
|
||||||
serializer.serialize_triple(triple.as_ref())?;
|
serializer.serialize_triple(triple.as_ref())?;
|
||||||
|
|||||||
Reference in New Issue
Block a user