pub struct Subgraph {
pub nodes: BTreeMap<String, NodeData>,
pub out_adj: BTreeMap<String, Vec<EdgeRef>>,
pub in_adj: BTreeMap<String, Vec<EdgeRef>>,
}Expand description
A transient, in-memory graph loaded from links_current.
The maps are BTreeMap, not HashMap, so iteration follows node id order.
Every algorithm in super::algorithms inherits its determinism from that
choice, and Louvain in particular returns a different partition under a
randomised iteration order.
§Closure
Every id appearing in out_adj or in_adj — as a key or as an
EdgeRef::node — is a key of nodes. Subgraph::drop_dangling_adjacency
establishes it and Subgraph::is_closed checks it; every algorithm in
super::algorithms is written assuming it and none of them re-checks.
It did not hold before Wave 1 (defect Z), and the way it failed is the reason
it is now stated on the type rather than left to the loader. Adjacency comes
from links_current, which carries edges to retired concepts; hydrate
filters retired = 0. So a retired neighbour left an EdgeRef pointing at
an id with no NodeData, and the five algorithms each met that differently:
louvain panicked on the missing map entry, scc emitted the absent node as
a phantom component of its own, k_core counted a degree of 2 where one edge
was in the graph, and dijkstra returned a finite distance to a node the
caller could not then look up. Four handlings of one violated invariant, none
of them chosen — and the panic was the least damaging, because the other
three answer.
Dangling entries are dropped rather than admitted with a tombstone node. A retired concept is not visible (§4.1), analytics over a graph is analytics over what is visible, and the alternative pushes a three-state node onto every present and future algorithm to preserve edges whose endpoint the caller is not entitled to read. Retirement is the supported path — concepts are never deleted (D-022) — so this is ordinary use, not a corner.
Fields§
§nodes: BTreeMap<String, NodeData>§out_adj: BTreeMap<String, Vec<EdgeRef>>§in_adj: BTreeMap<String, Vec<EdgeRef>>Implementations§
Source§impl Subgraph
impl Subgraph
Sourcepub fn out_edges(&self, node: &str) -> &[EdgeRef]
pub fn out_edges(&self, node: &str) -> &[EdgeRef]
Outgoing edges of node, empty when it has none or is absent.
Sourcepub fn in_edges(&self, node: &str) -> &[EdgeRef]
pub fn in_edges(&self, node: &str) -> &[EdgeRef]
Incoming edges of node, empty when it has none or is absent.
Sourcepub fn degree(&self, node: &str) -> usize
pub fn degree(&self, node: &str) -> usize
Undirected edge count incident to node, counting parallel edges once
each and a self-loop twice.
Sourcepub fn weighted_degree(&self, node: &str) -> f64
pub fn weighted_degree(&self, node: &str) -> f64
Undirected weight incident to node. Summed over both directions, so
summing this over all nodes gives 2 * total_weight.
Sourcepub fn total_weight(&self) -> f64
pub fn total_weight(&self) -> f64
Total edge weight, each edge counted once — the m of the modularity
formulas.
pub fn edge_count(&self) -> usize
Sourcepub fn is_closed(&self) -> bool
pub fn is_closed(&self) -> bool
Whether the closure invariant holds. Used by tests and debug_asserts.
Cheap enough to call in a test and O(V + E), so not on any hot path.
Sourcepub fn estimated_bytes(&self) -> usize
pub fn estimated_bytes(&self) -> usize
Estimated heap footprint (D-007).
Deliberately an estimate of the payload, not a precise size_of walk:
the budget exists to stop a dense neighbourhood exhausting memory, and a
figure that tracks string bytes and per-item overhead is accurate enough
for that.
O(V + E), and therefore not for use inside a loop over rows. The loader used to call this per row, which made loading O(E²): 500 edges in 26 ms, 1,000 in 76 ms, 2,000 in 231 ms — time tripling for each doubling. The byte budget is what bounds a load, and the budget check was the thing that did not scale (D-047).
Sourcepub async fn write_back_annotations(
&self,
db: &Database,
label: &str,
values: &BTreeMap<String, String>,
) -> Result<usize>
pub async fn write_back_annotations( &self, db: &Database, label: &str, values: &BTreeMap<String, String>, ) -> Result<usize>
Write one derived result per node under label (§5.4, D-041).
Goes through Database::write_analytics_annotations, which chunks at
crate::connection::chunk_rows::ANNOTATIONS and sends on the
low-priority channel,
so a community assignment over a large subgraph cannot starve interactive
writes.
Rows land in analytics_annotations, which carries no log trigger.
Before 0.5.4 this method built a ConceptUpsert per node and put the
value in content, so writing back a partition overwrote every
annotated concept’s document text — and, because the write went through
the ledger, recorded each rerun of the algorithm as a fresh version of a
world that had not changed. The old doc comment defended that as “a
normal bitemporal write,” which was true of the mechanism and false of
the intent: it is the right mechanism for a domain fact, and a community
label is not one.
values is keyed by node id; nodes absent from it are not annotated.
Trait Implementations§
Auto Trait Implementations§
impl Freeze for Subgraph
impl RefUnwindSafe for Subgraph
impl Send for Subgraph
impl Sync for Subgraph
impl Unpin for Subgraph
impl UnsafeUnpin for Subgraph
impl UnwindSafe for Subgraph
Blanket Implementations§
Source§impl<T> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
Source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
impl<ST, DT> CastableFrom<ST, Initialized, Initialized> for DT
impl<ST, DT> CastableFrom<ST, Uninit, Uninit> for DT
Source§impl<T> CloneToUninit for Twhere
T: Clone,
impl<T> CloneToUninit for Twhere
T: Clone,
Source§impl<T> Instrument for T
impl<T> Instrument for T
Source§fn instrument(self, span: Span) -> Instrumented<Self>
fn instrument(self, span: Span) -> Instrumented<Self>
Source§fn in_current_span(self) -> Instrumented<Self>
fn in_current_span(self) -> Instrumented<Self>
Source§impl<T> IntoRequest<T> for T
impl<T> IntoRequest<T> for T
Source§fn into_request(self) -> Request<T>
fn into_request(self) -> Request<T>
T in a tonic::Request