Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

### Added

- `AnnotationComponentType::apply_update_graph_index` makes sure all edges
from named dominance components are also contained in the default
dominance component `Dominance/annis`.

## [4.2.0] - 2026-08-11

### Added
Expand Down
36 changes: 36 additions & 0 deletions graphannis/benches/graphannis.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,10 @@ use fake::locales::*;
use graphannis::CorpusStorage;
use graphannis::corpusstorage::ResultOrder;
use graphannis::corpusstorage::{QueryLanguage, SearchQuery};
use graphannis::model::AnnotationComponentType;
use graphannis::update::{GraphUpdate, UpdateEvent};
use std::collections::HashSet;
use std::io::BufReader;
use std::path::Path;
use std::path::PathBuf;

Expand Down Expand Up @@ -429,6 +431,39 @@ fn apply_update_ondisk(bench: &mut Criterion) {
});
}

/// Benchmark a more complex graph with dominance and pointing relations.
fn apply_update_complex_graph(bench: &mut Criterion) {
if CORPUS_STORAGE.is_none() {
return;
}

let cs = CORPUS_STORAGE.as_ref().unwrap();

bench.bench_function("apply_update_complex_graph", move |b| {
cs.create_empty_corpus("apply_update_test_corpus", false)
.unwrap();

let mut input =
BufReader::new(&include_bytes!("../../core/tests/single_sentence.graphml")[..]);
let mut node_updates = GraphUpdate::new();
let mut edge_updates = GraphUpdate::new();
let _config_str = graphannis_core::graph::serialization::graphml::read_graphml::<
AnnotationComponentType,
_,
_,
>(&mut input, &mut node_updates, &mut edge_updates, &|_| {})
.unwrap();

b.iter(|| {
cs.apply_update("apply_update_test_corpus", &mut node_updates)
.unwrap();
cs.apply_update("apply_update_test_corpus", &mut edge_updates)
.unwrap();
});
cs.delete("apply_update_test_corpus").unwrap();
});
}

criterion_group!(name=default; config= Criterion::default().sample_size(50); targets =
deserialize_gum,
find_first_ten_token_gum,
Expand All @@ -441,5 +476,6 @@ criterion_group!(name=default; config= Criterion::default().sample_size(50); tar
criterion_group!(name=apply_update; config= Criterion::default().sample_size(20); targets =
apply_update_inmemory,
apply_update_ondisk,
apply_update_complex_graph,
);
criterion_main!(default, apply_update);
60 changes: 58 additions & 2 deletions graphannis/src/annis/db/aql/model.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,13 @@ use facet::Facet;
use graphannis_core::{
annostorage::ValueSearch,
dfs::CycleSafeDFS,
errors::ComponentTypeError,
errors::{ComponentTypeError, Result},
graph::{ANNIS_NS, NODE_TYPE_KEY, storage::union::UnionEdgeContainer},
types::ComponentType,
util::disk_collections::{DEFAULT_BLOCK_CACHE_CAPACITY, DiskMap, EvictionStrategy},
};
use std::{
collections::{BTreeMap, HashMap},
collections::{BTreeMap, HashMap, HashSet},
fmt,
};
use transient_btree_index::BtreeConfig;
Expand Down Expand Up @@ -393,6 +393,60 @@ impl AQLUpdateGraphIndex {
Ok(None)
}
}

/// Make sure all edges in named dominance components are in an unnamed dominance component.
fn add_empty_dominance_edges(
&mut self,
graph: &mut AnnotationGraph,
) -> std::result::Result<(), ComponentTypeError> {
let default_dominance_component = AnnotationComponent::new(
AnnotationComponentType::Dominance,
ANNIS_NS.into(),
"".into(),
);

let existing_dominance_components: Vec<_> =
graph.get_all_components(Some(AnnotationComponentType::Dominance), None);

let (unnamed_dominance_components, named_dominance_componts): (Vec<_>, Vec<_>) =
existing_dominance_components
.into_iter()
.filter(|c| c != &default_dominance_component)
.partition(|c| c.name.is_empty());

let named_dominance_gs: Vec<_> = named_dominance_componts
.into_iter()
.filter_map(|c| graph.get_graphstorage(&c))
.collect();

let unnamed_dominance_gs: Vec<_> = unnamed_dominance_components
.into_iter()
.filter_map(|c| graph.get_graphstorage(&c))
.collect();

let default_dominance_gs = graph.get_or_create_writable(&default_dominance_component)?;
for other_dominance_gs in named_dominance_gs {
for source_node in other_dominance_gs.source_nodes() {
let source = source_node?;
// The ">" operator will search all components that have an empty name.
// We don't need to add an edge if its already part of such a component.
let existing_unnamed_outgoing: Result<Vec<NodeID>> = unnamed_dominance_gs
.iter()
.flat_map(|gs| gs.get_outgoing_edges(source))
.collect();
let existing_unnamed_outgoing =
HashSet::<u64>::from_iter(existing_unnamed_outgoing?);
for target in other_dominance_gs.get_outgoing_edges(source) {
let target = target?;
if !existing_unnamed_outgoing.contains(&target) {
default_dominance_gs.add_edge(Edge { source, target })?;
}
}
}
}

Ok(())
}
}

impl ComponentType for AnnotationComponentType {
Expand Down Expand Up @@ -578,6 +632,8 @@ impl ComponentType for AnnotationComponentType {
}
index.invalid_nodes.compact()?;

index.add_empty_dominance_edges(graph)?;

// Re-index the inherited coverage component.
// To make this operation fast, we need to optimize the order component first
let order_component = AnnotationComponent::new(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,198 @@
---
source: graphannis/src/annis/db/aql/model/tests.rs
expression: "String::from_utf8_lossy(&output)"
---
<?xml version="1.0" encoding="UTF-8"?>
<graphml>
<key id="k0" for="node" attr.name="syntax::cat" attr.type="string"/>
<key id="k1" for="node" attr.name="annis::doc" attr.type="string"/>
<key id="k2" for="node" attr.name="annis::layer" attr.type="string"/>
<key id="k3" for="node" attr.name="annis::node_type" attr.type="string"/>
<key id="k4" for="node" attr.name="default_ns::pos" attr.type="string"/>
<key id="k5" for="node" attr.name="annis::tok" attr.type="string"/>
<key id="k6" for="node" attr.name="annis::tok-whitespace-before" attr.type="string"/>
<graph edgedefault="directed" parse.order="nodesfirst" parse.nodeids="free" parse.edgeids="canonical">
<node id="single_sentence">
<data key="k3">corpus</data>
</node>
<node id="single_sentence/zossen">
<data key="k1">zossen</data>
<data key="k3">corpus</data>
</node>
<node id="single_sentence/zossen#text">
<data key="k3">datasource</data>
</node>
<node id="single_sentence/zossen#n1">
<data key="k0">ROOT</data>
<data key="k2">syntax</data>
<data key="k3">node</data>
</node>
<node id="single_sentence/zossen#n2">
<data key="k0">S</data>
<data key="k2">syntax</data>
<data key="k3">node</data>
</node>
<node id="single_sentence/zossen#n3">
<data key="k0">NP</data>
<data key="k2">syntax</data>
<data key="k3">node</data>
</node>
<node id="single_sentence/zossen#t1">
<data key="k2">default_layer</data>
<data key="k3">node</data>
<data key="k4">ART</data>
<data key="k5">Die</data>
</node>
<node id="single_sentence/zossen#t2">
<data key="k2">default_layer</data>
<data key="k3">node</data>
<data key="k4">NN</data>
<data key="k5">Jugendlichen</data>
<data key="k6"> </data>
</node>
<node id="single_sentence/zossen#n4">
<data key="k0">PP</data>
<data key="k2">syntax</data>
<data key="k3">node</data>
</node>
<node id="single_sentence/zossen#t3">
<data key="k2">default_layer</data>
<data key="k3">node</data>
<data key="k4">APPR</data>
<data key="k5">in</data>
<data key="k6"> </data>
</node>
<node id="single_sentence/zossen#t4">
<data key="k2">default_layer</data>
<data key="k3">node</data>
<data key="k4">NE</data>
<data key="k5">Zossen</data>
<data key="k6"> </data>
</node>
<node id="single_sentence/zossen#t5">
<data key="k2">default_layer</data>
<data key="k3">node</data>
<data key="k4">VMFIN</data>
<data key="k5">wollen</data>
<data key="k6"> </data>
</node>
<node id="single_sentence/zossen#n5">
<data key="k0">NP</data>
<data key="k2">syntax</data>
<data key="k3">node</data>
</node>
<node id="single_sentence/zossen#t6">
<data key="k2">default_layer</data>
<data key="k3">node</data>
<data key="k4">ART</data>
<data key="k5">ein</data>
<data key="k6"> </data>
</node>
<node id="single_sentence/zossen#t7">
<data key="k2">default_layer</data>
<data key="k3">node</data>
<data key="k4">NN</data>
<data key="k5">Musikcafé</data>
<data key="k6"> </data>
</node>
<node id="single_sentence/zossen#t8">
<data key="k2">default_layer</data>
<data key="k3">node</data>
<data key="k4">$.</data>
<data key="k5">.</data>
<data key="k6"> </data>
</node>
<edge id="e0" source="single_sentence/zossen#n1" target="single_sentence/zossen#n2" label="Dominance/annis/">
</edge>
<edge id="e1" source="single_sentence/zossen#n1" target="single_sentence/zossen#t8" label="Dominance/annis/">
</edge>
<edge id="e2" source="single_sentence/zossen#n2" target="single_sentence/zossen#n3" label="Dominance/annis/">
</edge>
<edge id="e3" source="single_sentence/zossen#n2" target="single_sentence/zossen#t5" label="Dominance/annis/">
</edge>
<edge id="e4" source="single_sentence/zossen#n2" target="single_sentence/zossen#n5" label="Dominance/annis/">
</edge>
<edge id="e5" source="single_sentence/zossen#n3" target="single_sentence/zossen#t1" label="Dominance/annis/">
</edge>
<edge id="e6" source="single_sentence/zossen#n3" target="single_sentence/zossen#t2" label="Dominance/annis/">
</edge>
<edge id="e7" source="single_sentence/zossen#n3" target="single_sentence/zossen#n4" label="Dominance/annis/">
</edge>
<edge id="e8" source="single_sentence/zossen#n4" target="single_sentence/zossen#t3" label="Dominance/annis/">
</edge>
<edge id="e9" source="single_sentence/zossen#n4" target="single_sentence/zossen#t4" label="Dominance/annis/">
</edge>
<edge id="e10" source="single_sentence/zossen#n5" target="single_sentence/zossen#t6" label="Dominance/annis/">
</edge>
<edge id="e11" source="single_sentence/zossen#n5" target="single_sentence/zossen#t7" label="Dominance/annis/">
</edge>
<edge id="e12" source="single_sentence/zossen#n1" target="single_sentence/zossen#n2" label="Dominance/syntax/edge">
</edge>
<edge id="e13" source="single_sentence/zossen#n1" target="single_sentence/zossen#t8" label="Dominance/syntax/edge">
</edge>
<edge id="e14" source="single_sentence/zossen#n2" target="single_sentence/zossen#n3" label="Dominance/syntax/edge">
</edge>
<edge id="e15" source="single_sentence/zossen#n2" target="single_sentence/zossen#t5" label="Dominance/syntax/edge">
</edge>
<edge id="e16" source="single_sentence/zossen#n2" target="single_sentence/zossen#n5" label="Dominance/syntax/edge">
</edge>
<edge id="e17" source="single_sentence/zossen#n3" target="single_sentence/zossen#t1" label="Dominance/syntax/edge">
</edge>
<edge id="e18" source="single_sentence/zossen#n3" target="single_sentence/zossen#t2" label="Dominance/syntax/edge">
</edge>
<edge id="e19" source="single_sentence/zossen#n3" target="single_sentence/zossen#n4" label="Dominance/syntax/edge">
</edge>
<edge id="e20" source="single_sentence/zossen#n4" target="single_sentence/zossen#t3" label="Dominance/syntax/edge">
</edge>
<edge id="e21" source="single_sentence/zossen#n4" target="single_sentence/zossen#t4" label="Dominance/syntax/edge">
</edge>
<edge id="e22" source="single_sentence/zossen#n5" target="single_sentence/zossen#t6" label="Dominance/syntax/edge">
</edge>
<edge id="e23" source="single_sentence/zossen#n5" target="single_sentence/zossen#t7" label="Dominance/syntax/edge">
</edge>
<edge id="e24" source="single_sentence/zossen#t1" target="single_sentence/zossen#t2" label="Ordering/annis/">
</edge>
<edge id="e25" source="single_sentence/zossen#t2" target="single_sentence/zossen#t3" label="Ordering/annis/">
</edge>
<edge id="e26" source="single_sentence/zossen#t3" target="single_sentence/zossen#t4" label="Ordering/annis/">
</edge>
<edge id="e27" source="single_sentence/zossen#t4" target="single_sentence/zossen#t5" label="Ordering/annis/">
</edge>
<edge id="e28" source="single_sentence/zossen#t5" target="single_sentence/zossen#t6" label="Ordering/annis/">
</edge>
<edge id="e29" source="single_sentence/zossen#t6" target="single_sentence/zossen#t7" label="Ordering/annis/">
</edge>
<edge id="e30" source="single_sentence/zossen#t7" target="single_sentence/zossen#t8" label="Ordering/annis/">
</edge>
<edge id="e31" source="single_sentence/zossen" target="single_sentence" label="PartOf/annis/">
</edge>
<edge id="e32" source="single_sentence/zossen#text" target="single_sentence/zossen" label="PartOf/annis/">
</edge>
<edge id="e33" source="single_sentence/zossen#n1" target="single_sentence/zossen#text" label="PartOf/annis/">
</edge>
<edge id="e34" source="single_sentence/zossen#n2" target="single_sentence/zossen#text" label="PartOf/annis/">
</edge>
<edge id="e35" source="single_sentence/zossen#n3" target="single_sentence/zossen#text" label="PartOf/annis/">
</edge>
<edge id="e36" source="single_sentence/zossen#t1" target="single_sentence/zossen#text" label="PartOf/annis/">
</edge>
<edge id="e37" source="single_sentence/zossen#t2" target="single_sentence/zossen#text" label="PartOf/annis/">
</edge>
<edge id="e38" source="single_sentence/zossen#n4" target="single_sentence/zossen#text" label="PartOf/annis/">
</edge>
<edge id="e39" source="single_sentence/zossen#t3" target="single_sentence/zossen#text" label="PartOf/annis/">
</edge>
<edge id="e40" source="single_sentence/zossen#t4" target="single_sentence/zossen#text" label="PartOf/annis/">
</edge>
<edge id="e41" source="single_sentence/zossen#t5" target="single_sentence/zossen#text" label="PartOf/annis/">
</edge>
<edge id="e42" source="single_sentence/zossen#n5" target="single_sentence/zossen#text" label="PartOf/annis/">
</edge>
<edge id="e43" source="single_sentence/zossen#t6" target="single_sentence/zossen#text" label="PartOf/annis/">
</edge>
<edge id="e44" source="single_sentence/zossen#t7" target="single_sentence/zossen#text" label="PartOf/annis/">
</edge>
<edge id="e45" source="single_sentence/zossen#t8" target="single_sentence/zossen#text" label="PartOf/annis/">
</edge>
</graph>
</graphml>
25 changes: 23 additions & 2 deletions graphannis/src/annis/db/aql/model/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,11 @@ fn global_stats_token_count() {
assert_eq!(true, graph.global_statistics.is_some());
let global_stats = graph.global_statistics.unwrap();
assert_eq!(true, global_stats.all_token_in_order_component);
assert_matches!(global_stats.corpus_size,
assert_matches!(global_stats.corpus_size,
CorpusSize::Token { base_token_count, segmentation_count }
if base_token_count == 16
&& segmentation_count.len() == 2
&& *segmentation_count.get("diplomatic").unwrap() == 11
&& *segmentation_count.get("diplomatic").unwrap() == 11
&& *segmentation_count.get("norm").unwrap() == 13);
}

Expand Down Expand Up @@ -345,3 +345,24 @@ fn add_token_to_single_sentence() {
.unwrap();
assert_snapshot!(String::from_utf8_lossy(&output));
}

#[test]
fn add_dominance_edges_with_empty_name() {
let content =
&include_bytes!("../../../../../tests/single_sentence_only_named_edges.graphml")[..];
let (graph, _config) =
graphml::import::<AnnotationComponentType, _, _>(content, false, |_| {}).unwrap();

let mut output = Vec::<u8>::default();
graphannis_core::graph::serialization::graphml::export_stable_order(
&graph,
None,
&mut output,
|_| {},
)
.unwrap();

// The output has changed, because new edges have been added.
assert_ne!(content, output);
assert_snapshot!(String::from_utf8_lossy(&output));
}
Loading
Loading