[ English | Nederlands | Deutsch | Español | Français | 日本語 ]
Kolibrie es un motor de consultas SPARQL de alto rendimiento, concurrente y con muchas funcionalidades, implementado en Rust. Diseñado para la escalabilidad y eficiencia, aprovecha el robusto modelo de concurrencia de Rust y optimizaciones avanzadas, incluyendo SIMD (Single Instruction, Multiple Data) y procesamiento paralelo con Rayon, para manejar conjuntos de datos RDF (Resource Description Framework) a gran escala de manera fluida.
Con una API integral, Kolibrie facilita el análisis, almacenamiento y consulta de datos RDF utilizando formatos SPARQL, Turtle y N3. Sus avanzados filtros, agregaciones, operaciones de unión y sofisticadas estrategias de optimización lo convierten en una opción adecuada para aplicaciones que requieren un procesamiento de datos semánticos complejo. Además, la integración del Volcano Optimizer y las capacidades de Reasoner permiten a los usuarios realizar una planificación de consultas rentable y aprovechar la inferencia basada en reglas para obtener conocimientos de datos mejorados.
Kolibrie se desarrolla dentro del Stream Intelligence Lab en la KU Leuven, bajo la supervisión del Prof. Pieter Bonte. El Stream Intelligence Lab se enfoca en Stream Reasoning, un campo de investigación emergente que integra técnicas basadas en lógica de la inteligencia artificial con enfoques de aprendizaje automático basados en datos para derivar conocimientos oportunos y accionables de flujos de datos continuos. Nuestra investigación enfatiza aplicaciones en el Internet de las Cosas (IoT) y procesamiento en el Edge, permitiendo la toma de decisiones en tiempo real en entornos dinámicos como vehículos autónomos, robótica y análisis web.
Para más información sobre nuestra investigación y proyectos en curso, visita el sitio web del Stream Intelligence Lab.
- Análisis RDF Eficiente: Soporta el análisis de formatos RDF/XML, Turtle y N3 con manejo robusto de errores y gestión de prefijos.
- Procesamiento Concurrente: Utiliza Rayon y Crossbeam para el procesamiento de datos en paralelo, asegurando un rendimiento óptimo en sistemas multi-core.
- Optimización SIMD: Implementa instrucciones SIMD para acelerar el filtrado y la agregación de consultas.
- Consultas Flexibles: Soporta consultas SPARQL complejas, incluyendo cláusulas SELECT, INSERT, FILTER, GROUP BY y VALUES.
- Volcano Optimizer: Incorpora un optimizador de consultas basado en costos según el modelo Volcano para determinar los planes de ejecución más eficientes.
- Reasoner: Proporciona soporte robusto para construir y consultar grafos de conocimiento, incluyendo afirmaciones ABox (nivel de instancia) y TBox (nivel de esquema), inferencia dinámica basada en reglas y backward chaining.
- Streaming y Ventanas Deslizantes (Sliding Windows): Maneja triples con marca de tiempo y operaciones de ventanas deslizantes para análisis de datos basados en tiempo.
- Codificación de Diccionario Extensible: Codifica y decodifica términos RDF de manera eficiente usando un diccionario personalizable.
- API Completa: Ofrece un conjunto rico de métodos para la manipulación de datos, consultas y procesamiento de resultados.
Warning
el uso de CUDA es experimental y está en desarrollo
Asegúrate de tener Rust instalado (versión 1.60 o superior).
Clona el repositorio:
git clone https://github.com/StreamIntelligenceLab/Kolibrie.git
cd KolibrieCompila el proyecto:
cargo build --releaseLuego, inclúyelo en tu proyecto:
use kolibrie::SparqlDatabase;Kolibrie proporciona soporte para Docker con múltiples configuraciones para diferentes casos de uso. La configuración de Docker maneja automáticamente todas las dependencias incluyendo Rust, CUDA (para builds GPU) y frameworks de Python ML.
- Docker instalado
- Docker Compose instalado
- Para soporte GPU: NVIDIA Docker runtime instalado
- Build solo CPU (recomendado para la mayoría de usuarios):
docker compose --profile cpu up --build- Build con GPU habilitado (requiere GPU NVIDIA y nvidia-docker):
docker compose --profile gpu up --build- Build de desarrollo (detecta automáticamente disponibilidad de GPU):
docker compose --profile dev up --buildCrea una nueva instancia de SparqlDatabase:
use kolibrie::SparqlDatabase;
fn main() {
let mut db = SparqlDatabase::new();
// Tu código aquí
}Kolibrie soporta el análisis de datos RDF desde archivos o cadenas en varios formatos.
db.parse_rdf_from_file("data.rdf");let rdf_data = r#"
<?xml version="1.0" encoding="UTF-8"?>
<rdf:RDF
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:foaf="http://xmlns.com/foaf/0. 1/">
<rdf:Description rdf:about="http://example.org/alice">
<foaf:name>Alice</foaf:name>
<foaf:age>30</foaf:age>
</rdf:Description>
</rdf:RDF>
"#;
db.parse_rdf(rdf_data);let turtle_data = r#"
@prefix ex: <http://example.org/> .
ex:Alice ex:knows ex:Bob .
ex:Bob ex:knows ex:Charlie .
"#;
db.parse_turtle(turtle_data);let n3_data = r#"
@prefix ex: <http://example.org/> .
ex:Alice ex:knows ex:Bob .
ex:Bob ex:knows ex:Charlie .
"#;
db.parse_n3(n3_data);let ntriples_data = r#"
<http://example.org/john> <http://example.org/hasFriend> <http://example.org/jane> .
<http://example.org/jane> <http://example.org/name> "Jane Doe" .
<http://example.org/john> <http://example.org/age> "30"^^<http://www.w3.org/2001/XMLSchema#integer> .
"#;
db.parse_ntriples_and_add(ntriples_data);Añade triples individuales directamente a la base de datos:
db.add_triple_parts(
"http://example.org/alice",
"http://xmlns.com/foaf/0.1/name",
"Alice"
);
db.add_triple_parts(
"http://example.org/alice",
"http://xmlns.com/foaf/0.1/age",
"30"
);Ejecuta consultas SPARQL para recuperar y manipular datos.
use kolibrie::execute_query::execute_query;
let sparql_query = r#"
PREFIX ex: <http://example.org/>
SELECT ?s ?o
WHERE {
?s ex:knows ?o .
}
"#;
let results = execute_query(sparql_query, &mut db);
for row in results {
println!("Subject: {}, Object: {}", row[0], row[1]);
}let sparql = r#"
PREFIX ex: <http://example.org/vocab#>
SELECT ?name ?attendees
WHERE {
?event ex:name ?name .
?event ex:attendees ?attendees .
FILTER (?attendees > 50)
}
"#;
let results = execute_query(sparql, &mut db);
for row in results {
println! ("Event: {}, Attendees: {}", row[0], row[1]);
}let sparql = r#"
PREFIX ex: <http://example.org/vocab#>
SELECT ?name ?type ?attendees
WHERE {
?event ex:name ?name .
?event ex:type ?type .
?event ex:attendees ?attendees .
FILTER (?type = "Technical" || ?type = "Academic")
}
"#;
let results = execute_query(sparql, &mut db);
for row in results {
if let [name, type_, attendees] = &row[.. ] {
println!("Name: {}, Type: {}, Attendees: {}", name, type_, attendees);
}
}let sparql = r#"
PREFIX ex: <http://example.org/vocab#>
SELECT ?name ?type
WHERE {
?event ex:name ?name .
?event ex:type ?type .
FILTER (?type = "Technical" || ?type = "Academic")
}
LIMIT 2
"#;
let results = execute_query(sparql, &mut db);
for row in results {
println!("Name: {}, Type: {}", row[0], row[1]);
}let sparql = r#"
PREFIX ds: <https://data.cityofchicago.org/resource/xzkq-xp2w/>
SELECT AVG(?salary) AS ?average_salary
WHERE {
?employee ds:annual_salary ?salary
}
GROUPBY ?average_salary
"#;
let results = execute_query(sparql, &mut db);
for row in results {
if let [avg_salary] = &row[.. ] {
println!("Average Salary: {}", avg_salary);
}
}Agregaciones Soportadas:
AVG(? var)- Calcular promedioCOUNT(?var)- Contar ocurrenciasSUM(?var)- Sumar valoresMIN(?var)- Encontrar mínimoMAX(? var)- Encontrar máximo
let sparql = r#"
PREFIX foaf: <http://xmlns.com/foaf/0.1/>
SELECT ?name
WHERE {
?person foaf:givenName ?first .
?person foaf:surname ?last
BIND(CONCAT(?first, " ", ?last) AS ?name)
}
"#;
let results = execute_query(sparql, &mut db);
for row in results {
println!("Full Name: {}", row[0]);
}let sparql = r#"
PREFIX ex: <http://example.org/>
SELECT ?friendName
WHERE {
?person ex:name "Alice" .
?person ex:knows ?friend
{
SELECT ?friend ?friendName
WHERE {
?friend ex:name ?friendName .
}
}
}"#;
let results = execute_query(sparql, &mut db);
for row in results {
println!("Alice's Friend: {}", row[0]);
}El Query Builder proporciona una interfaz fluida para construir consultas de forma programática.
// Get all objects for a specific predicate
let results = db.query()
.with_predicate("http://xmlns.com/foaf/0.1/name")
.get_objects();
for object in results {
println!("Name: {}", object);
}let results = db.query()
.with_predicate("http://xmlns.com/foaf/0.1/age")
.filter(|triple| {
// Custom filter logic
db.dictionary.decode(triple.object)
.and_then(|age| age.parse::<i32>().ok())
.map(|age| age > 25)
.unwrap_or(false)
})
.get_decoded_triples();
for (subject, predicate, object) in results {
println!("{} is {} years old", subject, object);
}let other_db = SparqlDatabase::new();
// ... populate other_db ...
let results = db.query()
.join(&other_db)
.join_on_subject()
.get_triples();let results = db.query()
.with_predicate("http://xmlns.com/foaf/0.1/name")
.order_by(|triple| {
db.dictionary.decode(triple.object).unwrap().to_string()
})
.distinct()
.limit(10)
.offset(5)
.get_decoded_triples();
for (subject, predicate, object) in results {
println!("{} - {} - {}", subject, predicate, object);
}El Volcano Optimizer está integrado dentro de Kolibrie para optimizar los planes de ejecución de consultas basados en la estimación de costos. Transforma planes lógicos en planes físicos eficientes utilizando diversas estrategias de join y toma decisiones basadas en costos para seleccionar la ruta más performante.
use kolibrie::execute_query::*;
use kolibrie::sparql_database::*;
fn main() {
let mut db = SparqlDatabase::new();
// Parse N-Triples data
let ntriples_data = r#"
<http://example.org/john> <http://example.org/hasFriend> <http://example.org/jane> .
<http://example.org/jane> <http://example.org/name> "Jane Doe" .
<http://example.org/john> <http://example.org/name> "John Smith" .
<http://example.org/jane> <http://example.org/age> "25"^^<http://www.w3.org/2001/XMLSchema#integer> .
<http://example.org/john> <http://example.org/age> "30"^^<http://www. w3.org/2001/XMLSchema#integer> .
"#;
db.parse_ntriples_and_add(ntriples_data);
// Build statistics for the optimizer
db.get_or_build_stats();
// Define the SPARQL query
let sparql_query = r#"
PREFIX ex: <http://example.org/>
SELECT ?person ?friend ?friendName
WHERE {
?person ex:hasFriend ?friend .
?friend ex:name ?friendName .
}
"#;
// Execute the query with optimized plan
let results = execute_query(sparql_query, &mut db);
for row in results {
println!("Person: {}, Friend: {}, Friend's Name: {}", row[0], row[1], row[2]);
}
}El componente Reasoner permite construir y gestionar redes semánticas con información a nivel de instancia (ABox). Soporta inferencia dinámica basada en reglas usando forward chaining, backward chaining y evaluación semi-naive para derivar nuevo conocimiento.
use datalog::knowledge_graph::Reasoner;
use shared::terms::Term;
use shared::rule::Rule;
fn main() {
let mut kg = Reasoner::new();
// Add ABox triples (instance-level data)
kg.add_abox_triple("Alice", "parentOf", "Bob");
kg.add_abox_triple("Bob", "parentOf", "Charlie");
// Define a transitivity rule for ancestorOf relationship
// Rule: parentOf(X, Y) ∧ parentOf(Y, Z) → ancestorOf(X, Z)
let ancestor_rule = Rule {
premise: vec![
(
Term::Variable("X".to_string()),
Term::Constant(kg.dictionary.encode("parentOf")),
Term::Variable("Y".to_string()),
),
(
Term::Variable("Y". to_string()),
Term::Constant(kg.dictionary.encode("parentOf")),
Term::Variable("Z".to_string()),
),
],
conclusion: vec![
(
Term::Variable("X".to_string()),
Term::Constant(kg.dictionary.encode("ancestorOf")),
Term::Variable("Z".to_string()),
)
],
filters: vec![],
};
kg.add_rule(ancestor_rule);
// Infer new facts using forward chaining
let inferred_facts = kg.infer_new_facts();
println!("Inferred {} new facts", inferred_facts.len());
// Query the Knowledge Graph for ancestorOf relationships
let results = kg.query_abox(
Some("Alice"),
Some("ancestorOf"),
None,
);
for triple in results {
println!(
"{} is ancestor of {}",
kg.dictionary.decode(triple.subject).unwrap(),
kg. dictionary.decode(triple.object). unwrap()
);
}
}Salida:
Inferred 1 new facts
Alice is ancestor of CharlieLa estructura SparqlDatabase es el componente central que representa el almacén RDF y proporciona métodos para la manipulación de datos y consultas.
pub struct SparqlDatabase {
pub triples: BTreeSet<Triple>,
pub streams: Vec<TimestampedTriple>,
pub sliding_window: Option<SlidingWindow>,
pub dictionary: Dictionary,
pub prefixes: HashMap<String, String>,
pub udfs: HashMap<String, ClonableFn>,
pub index_manager: UnifiedIndex,
pub rule_map: HashMap<String, String>,
pub cached_stats: Option<Arc<DatabaseStats>>,
}- triples: Almacena triples RDF en un conjunto ordenado para consultas eficientes.
- streams: Contiene triples con marca de tiempo para consultas de streaming y temporales.
- sliding_window: Ventana deslizante opcional para análisis de datos basados en tiempo.
- dictionary: Codifica y decodifica términos RDF para eficiencia de almacenamiento.
- prefixes: Gestiona prefijos de espacios de nombres para resolver términos prefijados.
- udfs: Registro de funciones definidas por el usuario para operaciones personalizadas.
- index_manager: Sistema de indexación unificado para rendimiento optimizado.
- rule_map: Mapea nombres de reglas a sus definiciones.
- cached_stats: Estadísticas cacheadas de la base de datos para optimización de consultas.
El Streamertail implementa un optimizador de consultas basado en costos según el modelo Volcano. Transforma planes lógicos en planes físicos eficientes evaluando diferentes operadores físicos y seleccionando el de menor costo estimado.
pub struct Streamertail<'a> {
pub stats: Arc<DatabaseStats>,
pub memo: HashMap<String, (PhysicalOperator, f64)>,
pub selected_variables: Vec<String>,
database: &'a SparqlDatabase,
}- stats: Información estadística compartida de la base de datos para ayudar en la estimación de costos.
- memo: Caches de operadores físicos optimizados junto con sus costos para evitar cálculos redundantes.
- selected_variables: Rastrea las variables seleccionadas en la consulta.
- database: Referencia a la base de datos SPARQL para la ejecución.
La estructura Reasoner gestiona afirmaciones a nivel de instancia (ABox), soporta inferencia dinámica basada en reglas y proporciona capacidades de consulta con forward chaining, backward chaining y evaluación semi-naive.
pub struct Reasoner {
pub dictionary: Dictionary,
pub rules: Vec<Rule>,
pub index_manager: UnifiedIndex,
pub rule_index: RuleIndex,
pub constraints: Vec<Rule>,
}- dictionary: Codifica y decodifica términos RDF para eficiencia de almacenamiento.
- rules: Contiene reglas dinámicas para inferencia de nuevo conocimiento.
- index_manager: Sistema de indexación unificado para almacenar y consultar triples.
- rule_index: Índice especializado para emparejamiento eficiente de reglas.
- constraints: Restricciones de integridad para detección y reparación de inconsistencias.
Crea una nueva SparqlDatabase vacía.
let mut db = SparqlDatabase::new();Analiza datos RDF/XML desde un archivo especificado y llena la base de datos.
db.parse_rdf_from_file("data. rdf");Analiza datos RDF/XML desde una cadena.
let rdf_xml = r#"<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">... </rdf:RDF>"#;
db.parse_rdf(rdf_xml);Analiza datos RDF en formato Turtle desde una cadena.
let turtle_data = r#"
@prefix ex: <http://example.org/> .
ex:Alice ex:knows ex:Bob .
"#;
db.parse_turtle(turtle_data);Analiza datos RDF en formato N3 desde una cadena.
let n3_data = r#"
@prefix ex: <http://example.org/> .
ex:Alice ex:knows ex:Bob .
"#;
db.parse_n3(n3_data);Analiza N-Triples y los añade a la base de datos.
let ntriples_data = r#"
<http://example.org/john> <http://example.org/hasFriend> <http://example.org/jane> .
<http://example.org/jane> <http://example.org/name> "Jane Doe" .
"#;
db.parse_ntriples_and_add(ntriples_data);Añade un triple a la base de datos codificando sus partes.
db.add_triple_parts(
"http://example.org/alice",
"http://xmlns.com/foaf/0. 1/name",
"Alice"
);Elimina un triple y retorna si se removió exitosamente.
let deleted = db.delete_triple_parts(
"http://example.org/alice",
"http://xmlns.com/foaf/0.1/age",
"30"
);Construye todos los índices a partir de los triples actuales.
db.build_all_indexes();Obtiene estadísticas cacheadas o construye nuevas estadísticas.
let stats = db.get_or_build_stats();Invalida el caché de estadísticas tras modificaciones de datos.
db.invalidate_stats_cache();Devuelve un QueryBuilder para construir consultas programáticamente.
let results = db.query()
.with_predicate("http://xmlns.com/foaf/0. 1/name")
.get_objects();Registra una función definida por el usuario para usarla en consultas.
db.register_udf("toUpperCase", |args: Vec<&str>| {
args[0].to_uppercase()
});Genera una representación RDF/XML de la base de datos.
let rdf_xml = db.generate_rdf_xml();Decodifica un triple a su representación en string.
if let Some((s, p, o)) = db. decode_triple(&triple) {
println!("{} - {} - {}", s, p, o);
}Crea una nueva instancia del Streamertail con estadísticas de la base de datos.
let optimizer = Streamertail::new(&db);Crea un optimizador con estadísticas precomputadas.
let stats = db.get_or_build_stats();
let optimizer = Streamertail::with_cached_stats(stats);Determina el plan físico más eficiente para un plan lógico dado.
let best_plan = optimizer.find_best_plan(&logical_plan);execute_plan(&mut self, plan: &PhysicalOperator, database: &mut SparqlDatabase) -> Vec<BTreeMap<String, String>>
Ejecuta un plan físico optimizado y retorna los resultados.
let results = optimizer.execute_plan(&physical_plan, &mut db);Crea un Reasoner vacío.
let mut kg = Reasoner::new();Añade un triple ABox (información a nivel de instancia).
kg.add_abox_triple("Alice", "knows", "Bob");query_abox(&mut self, subject: Option<&str>, predicate: Option<&str>, object: Option<&str>) -> Vec<Triple>
Consulta la ABox con filtros opcionales de sujeto, predicado y objeto.
let results = kg.query_abox(Some("Alice"), Some("knows"), None);Añade una regla dinámica para inferencia.
let rule = Rule {
premise: vec![... ],
conclusion: vec![... ],
filters: vec![],
};
kg.add_rule(rule);Realiza naive forward chaining.
let inferred = kg.infer_new_facts();
println!("Inferred {} new facts", inferred.len());Realiza evaluación semi-naive para inferencia más eficiente.
let inferred = kg.infer_new_facts_semi_naive();Realiza evaluación semi-naive en paralelo para inferencia a gran escala.
let inferred = kg.infer_new_facts_semi_naive_parallel();Realiza backward chaining para responder consultas a partir de reglas.
let query_pattern = (
Term::Variable("X".to_string()),
Term::Constant(kg.dictionary.encode("knows")),
Term::Variable("Y".to_string())
);
let results = kg.backward_chaining(&query_pattern);Añade una restricción de integridad.
kg.add_constraint(constraint);Realiza inferencia manejando inconsistencias mediante reparación automática.
let inferred = kg.infer_new_facts_semi_naive_with_repairs();Consulta usando semánticas tolerantes a inconsistencias (IAR).
let results = kg.query_with_repairs(&query_pattern);Kolibrie está optimizado para alto rendimiento mediante:
- Análisis y Procesamiento Paralelo: Utiliza Rayon y Crossbeam para el análisis de datos multi-threaded y la ejecución de consultas.
- Instrucciones SIMD: Implementa operaciones SIMD para acelerar tareas de filtrado y agregación.
- Volcano Optimizer: Emplea un optimizador de consultas basado en costos para generar planes de ejecución física eficientes, minimizando el tiempo de ejecución de consultas.
- Inferencia del Grafo de Conocimiento: Aprovecha la inferencia basada en reglas y backward chaining para derivar nuevos conocimientos sin una sobrecarga significativa de rendimiento.
- Estructuras de Datos Eficientes: Emplea
BTreeSetpara almacenamiento ordenado yHashMappara la gestión de prefijos, asegurando una rápida recuperación y manipulación de datos. - Optimización de Memoria: Utiliza codificación de diccionario para minimizar la huella de memoria reutilizando términos repetidos.
Nuestros benchmarks demuestran el rendimiento superior de Kolibrie frente a otros motores RDF populares. Las siguientes pruebas se realizaron usando:
- Dataset: benchmark WatDiv 10M triples
- Configuración de Oxigraph: backend RocksDB para rendimiento óptimo
- Razonamiento de Taxonomía Profunda: pruebas de profundidad jerárquica hasta 10K niveles
Figura 1: Tiempos de ejecución de consultas entre diferentes motores SPARQL usando el dataset WatDiv 10M
Hallazgos Clave:
- Kolibrie supera consistentemente a sus competidores en todos los tipos de consulta (L1-L5, S1-S7, F1-F3, C1-C3)
- Tiempo promedio de ejecución: rango sub-milisegundo a pocos milisegundos
- Blazegraph y QLever muestran rendimiento competitivo en patrones específicos
- Oxigraph (con RocksDB) demuestra estabilidad en todas las consultas
Figura 2: Rendimiento de razonamiento a través de diferentes profundidades jerárquicas (10, 100, 1K, 10K niveles)
Hallazgos Clave:
- Kolibrie muestra escalado logarítmico con la profundidad de la jerarquía
- En 10K niveles, Kolibrie mantiene tiempos de respuesta sub-segundo
- Rendimiento superior frente a Apache Jena y el razonador EYE
- Manejo eficiente de estructuras taxonómicas complejas
Utiliza el Issue Tracker para enviar reportes de errores y solicitudes de nuevas funcionalidades/mejoras. Antes de enviar un nuevo problema, asegúrate de que no exista un issue similar abierto.
¡Se agradece enormemente que cualquier persona que pruebe el código manualmente y reporte errores o sugerencias de mejoras en el Issue Tracker contribuya!
Se aceptan parches/correcciones en forma de pull requests (PRs). Asegúrate de que el issue que el pull request aborda esté abierto en el Issue Tracker.
El pull request enviado se considera que ha aceptado publicarse bajo la Licencia Pública de Mozilla, versión 2.0.
Únete a nuestra comunidad de Discord para discutir sobre Kolibrie, hacer preguntas y compartir experiencias.
Kolibrie está licenciado bajo la Licencia MPL-2.0.

