diff --git a/graph/src/curie.rs b/graph/src/curie.rs index 89b2d6b..0bf0209 100644 --- a/graph/src/curie.rs +++ b/graph/src/curie.rs @@ -1,10 +1,8 @@ use std::collections::BTreeMap; use std::sync::LazyLock; use oxigraph::model::{GraphNameRef, NamedNodeRef}; -use tracing::debug; 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> = LazyLock::new(|| { BTreeMap::from_iter([ diff --git a/graph/src/inference.rs b/graph/src/inference.rs index dbfb32e..9dfa5a1 100644 --- a/graph/src/inference.rs +++ b/graph/src/inference.rs @@ -1,36 +1,55 @@ +use std::sync::Arc; use oxigraph::model::{Dataset, GraphNameRef, NamedNodeRef, Quad, QuadRef}; use oxigraph::sparql::{QueryResults, SparqlEvaluator}; -use oxigraph::store::{Store, Transaction}; -use tracing::{debug_span, field}; +use oxigraph::store::Store; +use tracing::{debug, 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")); - -#[derive(Clone)] -pub struct InferenceEngine { - ontology: Store, -} /// `prp-spo1` -fn sub_property_of(transaction: Transaction) -> crate::Result { - let span = debug_span!("rdfs:subPropertyOf", inferences = field::Empty).entered(); +const SUB_PROPERTY_OF_QUERY: &str = r#"CONSTRUCT { + ?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 { + let span = debug_span!("Query", name).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 . } -}"#)?; + .parse_query(query)?; 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) .map(|triple| Quad::new(triple.subject, triple.predicate, triple.object, INFERENCE_GRAPH)) .collect() @@ -38,23 +57,52 @@ fn sub_property_of(transaction: Transaction) -> crate::Result { Dataset::new() }; - span.record("inferences", inferences.len()); + Ok(inferences) } +fn run_recursively(mut dataset: Dataset) -> crate::Result { + 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 { 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 { - ontology: store, + ontology, } } pub fn run(&self, dataset: &Dataset) -> crate::Result { - 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, INPUT_GRAPH)); - transaction.extend(input); - sub_property_of(transaction) + let input = dataset.iter().map(|triple| QuadRef::new(triple.subject, triple.predicate, triple.object, INFERENCE_GRAPH)); + let mut ontology = self.ontology.clone(); + ontology.extend(input); + + let result: Dataset = run_recursively(ontology)? + .quads_for_graph_name(INFERENCE_GRAPH) + .collect(); + + span.record("inferences", result.len()); + Ok(result) } } \ No newline at end of file diff --git a/publish/src/main.rs b/publish/src/main.rs index 73fed60..1dfd098 100644 --- a/publish/src/main.rs +++ b/publish/src/main.rs @@ -49,22 +49,28 @@ fn main() -> color_eyre::Result<()> { let inference_engine = InferenceEngine::new(store.clone()); 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) .for_reader(File::open(&args.dataset_path)?) .filter_map(Result::ok) .collect::(); - let inferences = inference_engine.run(&dataset)?; - dataset.extend(&inferences); + let dataset_with_inferences = inference_engine.run(&dataset)?; let raw_query = String::from_utf8(std::fs::read(&args.query_path)?)?; let mut query = SparqlEvaluator::new() .parse_query(&raw_query)?; 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) => { 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 triple in graph.filter_map(Result::ok) { serializer.serialize_triple(triple.as_ref())?;