Skip to main content

oxgraph_db/
projection.rs

1//! Materialized graph and hypergraph projection views.
2
3use std::collections::{BTreeMap, BTreeSet};
4
5use oxgraph_graph::{
6    EdgeSourceGraph, EdgeTargetGraph, ElementIndex, ElementPredecessors, ElementSuccessors,
7    GraphCounts, IncomingEdgeCount, IncomingGraph, LocalElementIdentity, LocalRelationIdentity,
8    OutgoingEdgeCount, OutgoingGraph, RelationIndex, TopologyBase, TopologyCounts,
9};
10use oxgraph_hyper::{
11    DirectedHyperedgeIncidences, DirectedHyperedgeParticipants, DirectedVertexHyperedges,
12    ElementIncidenceCount, HyperedgeParticipantCount, HypergraphCounts, IncidenceBase,
13    IncidenceCounts, IncidenceElement, IncidenceIndex, IncidenceRelation, IncidenceRole,
14    IncidentHyperedgeCount, IncidentHyperedges, RelationIncidenceCount, RelationIncidences,
15};
16use serde::{Deserialize, Serialize};
17
18use crate::{
19    DbError, ElementId, IncidenceId, RelationId, RelationTypeId, RoleId,
20    catalog::{GraphProjectionDefinition, HypergraphProjectionDefinition},
21    state::{DatabaseState, IncidenceRecord, PropertySubject, RelationRecord},
22};
23
24/// Dense projection-local element identifier.
25///
26/// # Performance
27///
28/// Copying, comparing, ordering, and hashing are `O(1)`.
29#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)]
30pub struct ProjectionElementId(u32);
31
32impl ProjectionElementId {
33    /// Creates a local element ID.
34    ///
35    /// # Performance
36    ///
37    /// This function is `O(1)`.
38    #[must_use]
39    pub const fn new(value: u32) -> Self {
40        Self(value)
41    }
42
43    /// Returns the raw local index.
44    ///
45    /// # Performance
46    ///
47    /// This function is `O(1)`.
48    #[must_use]
49    pub const fn get(self) -> u32 {
50        self.0
51    }
52}
53
54/// Dense projection-local relation identifier.
55///
56/// # Performance
57///
58/// Copying, comparing, ordering, and hashing are `O(1)`.
59#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)]
60pub struct ProjectionRelationId(u32);
61
62impl ProjectionRelationId {
63    /// Creates a local relation ID.
64    ///
65    /// # Performance
66    ///
67    /// This function is `O(1)`.
68    #[must_use]
69    pub const fn new(value: u32) -> Self {
70        Self(value)
71    }
72
73    /// Returns the raw local index.
74    ///
75    /// # Performance
76    ///
77    /// This function is `O(1)`.
78    #[must_use]
79    pub const fn get(self) -> u32 {
80        self.0
81    }
82}
83
84/// Dense projection-local incidence identifier.
85///
86/// # Performance
87///
88/// Copying, comparing, ordering, and hashing are `O(1)`.
89#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)]
90pub struct ProjectionIncidenceId(u32);
91
92impl ProjectionIncidenceId {
93    /// Creates a local incidence ID.
94    ///
95    /// # Performance
96    ///
97    /// This function is `O(1)`.
98    #[must_use]
99    pub const fn new(value: u32) -> Self {
100        Self(value)
101    }
102
103    /// Returns the raw local index.
104    ///
105    /// # Performance
106    ///
107    /// This function is `O(1)`.
108    #[must_use]
109    pub const fn get(self) -> u32 {
110        self.0
111    }
112}
113
114/// Materialized binary graph projection.
115///
116/// This view exposes `OxGraph` canonical topology through `oxgraph-graph`
117/// traits. Outgoing arrays are CSR-shaped; incoming arrays are CSC-shaped.
118///
119/// # Performance
120///
121/// Cloning is `O(n + m)` for visible nodes and edges. Traversal is `O(k)` for
122/// each yielded adjacency list.
123#[derive(Clone, Debug, Eq, PartialEq)]
124pub struct GraphProjection {
125    /// Projection definition used to build this view.
126    definition: GraphProjectionDefinition,
127    /// Local-to-canonical elements.
128    elements: Vec<ElementId>,
129    /// Canonical-to-local elements.
130    element_index: BTreeMap<ElementId, ProjectionElementId>,
131    /// Local-to-canonical relations.
132    relations: Vec<RelationId>,
133    /// Canonical-to-local relations.
134    relation_index: BTreeMap<RelationId, ProjectionRelationId>,
135    /// Source and target local IDs by local relation.
136    endpoints: Vec<(ProjectionElementId, ProjectionElementId)>,
137    /// CSR outgoing edge IDs by source local element.
138    out_edges: Vec<Vec<ProjectionRelationId>>,
139    /// CSC incoming edge IDs by target local element.
140    in_edges: Vec<Vec<ProjectionRelationId>>,
141    /// CSR outgoing neighbor IDs by source local element.
142    successors: Vec<Vec<ProjectionElementId>>,
143    /// CSC incoming neighbor IDs by target local element.
144    predecessors: Vec<Vec<ProjectionElementId>>,
145}
146
147impl GraphProjection {
148    /// Builds a graph projection from canonical state.
149    ///
150    /// # Errors
151    ///
152    /// Returns [`DbError::InvalidProjection`] when a selected relation is not a
153    /// binary relation for the configured source and target roles.
154    ///
155    /// # Performance
156    ///
157    /// This function is `O(r * i + e log n)` for relation count `r`,
158    /// incidence count `i`, and selected edge count `e`.
159    pub(crate) fn from_state(
160        state: &DatabaseState,
161        definition: GraphProjectionDefinition,
162    ) -> Result<Self, DbError> {
163        let mut builder = GraphProjectionBuilder::new(definition);
164        for relation in state.relations() {
165            if relation_selected(relation, &builder.definition.relation_types) {
166                builder.push_relation(state, relation)?;
167            }
168        }
169        builder.finish()
170    }
171
172    /// Returns the definition used for this projection.
173    ///
174    /// # Performance
175    ///
176    /// This method is `O(1)`.
177    #[must_use]
178    pub const fn definition(&self) -> &GraphProjectionDefinition {
179        &self.definition
180    }
181
182    /// Returns deterministic canonical subjects materialized by this projection.
183    pub(crate) fn subjects(&self) -> Vec<PropertySubject> {
184        let mut subjects = Vec::with_capacity(self.elements.len() + self.relations.len());
185        subjects.extend(self.elements.iter().copied().map(PropertySubject::Element));
186        subjects.extend(
187            self.relations
188                .iter()
189                .copied()
190                .map(PropertySubject::Relation),
191        );
192        subjects.sort_unstable();
193        subjects
194    }
195}
196
197/// Builder used to keep graph projection construction small.
198struct GraphProjectionBuilder {
199    /// Projection definition being built.
200    definition: GraphProjectionDefinition,
201    /// Local-to-canonical elements.
202    elements: Vec<ElementId>,
203    /// Canonical-to-local elements.
204    element_index: BTreeMap<ElementId, ProjectionElementId>,
205    /// Local-to-canonical relations.
206    relations: Vec<RelationId>,
207    /// Canonical-to-local relations.
208    relation_index: BTreeMap<RelationId, ProjectionRelationId>,
209    /// Edge endpoints.
210    endpoints: Vec<(ProjectionElementId, ProjectionElementId)>,
211}
212
213impl GraphProjectionBuilder {
214    /// Creates an empty graph projection builder.
215    const fn new(definition: GraphProjectionDefinition) -> Self {
216        Self {
217            definition,
218            elements: Vec::new(),
219            element_index: BTreeMap::new(),
220            relations: Vec::new(),
221            relation_index: BTreeMap::new(),
222            endpoints: Vec::new(),
223        }
224    }
225
226    /// Adds one selected canonical relation.
227    fn push_relation(
228        &mut self,
229        state: &DatabaseState,
230        relation: &RelationRecord,
231    ) -> Result<(), DbError> {
232        let (source, target) = graph_endpoints(
233            state,
234            relation.id,
235            self.definition.source_role,
236            self.definition.target_role,
237        )?;
238        let source = intern_element(&mut self.element_index, &mut self.elements, source)?;
239        let target = intern_element(&mut self.element_index, &mut self.elements, target)?;
240        let edge = projection_relation_id(self.relations.len())?;
241        self.relations.push(relation.id);
242        self.relation_index.insert(relation.id, edge);
243        self.endpoints.push((source, target));
244        Ok(())
245    }
246
247    /// Finishes the graph projection.
248    fn finish(self) -> Result<GraphProjection, DbError> {
249        let mut out_edges = vec![Vec::new(); self.elements.len()];
250        let mut in_edges = vec![Vec::new(); self.elements.len()];
251        let mut successors = vec![Vec::new(); self.elements.len()];
252        let mut predecessors = vec![Vec::new(); self.elements.len()];
253        for (index, (source, target)) in self.endpoints.iter().copied().enumerate() {
254            let edge = projection_relation_id(index)?;
255            out_edges[local_index(source)].push(edge);
256            in_edges[local_index(target)].push(edge);
257            successors[local_index(source)].push(target);
258            predecessors[local_index(target)].push(source);
259        }
260        Ok(GraphProjection {
261            definition: self.definition,
262            elements: self.elements,
263            element_index: self.element_index,
264            relations: self.relations,
265            relation_index: self.relation_index,
266            endpoints: self.endpoints,
267            out_edges,
268            in_edges,
269            successors,
270            predecessors,
271        })
272    }
273}
274
275impl TopologyBase for GraphProjection {
276    type ElementId = ProjectionElementId;
277    type RelationId = ProjectionRelationId;
278}
279
280impl TopologyCounts for GraphProjection {
281    fn element_count(&self) -> usize {
282        self.elements.len()
283    }
284
285    fn relation_count(&self) -> usize {
286        self.relations.len()
287    }
288}
289
290impl GraphCounts for GraphProjection {}
291
292impl ElementIndex for GraphProjection {
293    fn element_bound(&self) -> usize {
294        self.elements.len()
295    }
296
297    fn element_index(&self, element: Self::ElementId) -> usize {
298        local_index(element)
299    }
300}
301
302impl RelationIndex for GraphProjection {
303    fn relation_bound(&self) -> usize {
304        self.relations.len()
305    }
306
307    fn relation_index(&self, relation: Self::RelationId) -> usize {
308        local_relation_index(relation)
309    }
310}
311
312impl oxgraph_graph::ContainsElement for GraphProjection {
313    fn contains_element(&self, element: Self::ElementId) -> bool {
314        local_index(element) < self.elements.len()
315    }
316}
317
318impl oxgraph_graph::ContainsRelation for GraphProjection {
319    fn contains_relation(&self, relation: Self::RelationId) -> bool {
320        local_relation_index(relation) < self.relations.len()
321    }
322}
323
324impl oxgraph_graph::CanonicalElementIdentity for GraphProjection {
325    type CanonicalElementId = ElementId;
326
327    fn canonical_element_id(&self, element: Self::ElementId) -> Self::CanonicalElementId {
328        self.elements[local_index(element)]
329    }
330}
331
332impl LocalElementIdentity for GraphProjection {
333    fn local_element_id(&self, canonical: Self::CanonicalElementId) -> Option<Self::ElementId> {
334        self.element_index.get(&canonical).copied()
335    }
336}
337
338impl oxgraph_graph::CanonicalRelationIdentity for GraphProjection {
339    type CanonicalRelationId = RelationId;
340
341    fn canonical_relation_id(&self, relation: Self::RelationId) -> Self::CanonicalRelationId {
342        self.relations[local_relation_index(relation)]
343    }
344}
345
346impl LocalRelationIdentity for GraphProjection {
347    fn local_relation_id(&self, canonical: Self::CanonicalRelationId) -> Option<Self::RelationId> {
348        self.relation_index.get(&canonical).copied()
349    }
350}
351
352impl EdgeSourceGraph for GraphProjection {
353    fn source(&self, edge: Self::RelationId) -> Self::ElementId {
354        self.endpoints[local_relation_index(edge)].0
355    }
356}
357
358impl EdgeTargetGraph for GraphProjection {
359    fn target(&self, edge: Self::RelationId) -> Self::ElementId {
360        self.endpoints[local_relation_index(edge)].1
361    }
362}
363
364impl OutgoingGraph for GraphProjection {
365    type OutEdges<'view>
366        = std::iter::Copied<std::slice::Iter<'view, ProjectionRelationId>>
367    where
368        Self: 'view;
369
370    fn outgoing_edges(&self, node: Self::ElementId) -> Self::OutEdges<'_> {
371        self.out_edges[local_index(node)].iter().copied()
372    }
373}
374
375impl IncomingGraph for GraphProjection {
376    type InEdges<'view>
377        = std::iter::Copied<std::slice::Iter<'view, ProjectionRelationId>>
378    where
379        Self: 'view;
380
381    fn incoming_edges(&self, node: Self::ElementId) -> Self::InEdges<'_> {
382        self.in_edges[local_index(node)].iter().copied()
383    }
384}
385
386impl ElementSuccessors for GraphProjection {
387    type Successors<'view>
388        = std::iter::Copied<std::slice::Iter<'view, ProjectionElementId>>
389    where
390        Self: 'view;
391
392    fn element_successors(&self, element: Self::ElementId) -> Self::Successors<'_> {
393        self.successors[local_index(element)].iter().copied()
394    }
395}
396
397impl ElementPredecessors for GraphProjection {
398    type Predecessors<'view>
399        = std::iter::Copied<std::slice::Iter<'view, ProjectionElementId>>
400    where
401        Self: 'view;
402
403    fn element_predecessors(&self, element: Self::ElementId) -> Self::Predecessors<'_> {
404        self.predecessors[local_index(element)].iter().copied()
405    }
406}
407
408impl OutgoingEdgeCount for GraphProjection {
409    fn out_degree(&self, node: Self::ElementId) -> usize {
410        self.out_edges[local_index(node)].len()
411    }
412}
413
414impl IncomingEdgeCount for GraphProjection {
415    fn in_degree(&self, node: Self::ElementId) -> usize {
416        self.in_edges[local_index(node)].len()
417    }
418}
419
420/// Materialized directed hypergraph projection.
421///
422/// This view exposes `OxGraph` canonical topology through `oxgraph-hyper`
423/// traits. Its physical arrays are BCSR-shaped: relation-major participant
424/// arrays plus vertex-major incoming/outgoing hyperedge arrays.
425///
426/// # Performance
427///
428/// Cloning is `O(v + h + p)` for vertices, hyperedges, and participants.
429#[derive(Clone, Debug, Eq, PartialEq)]
430pub struct HypergraphProjection {
431    /// Projection definition used to build this view.
432    definition: HypergraphProjectionDefinition,
433    /// Local-to-canonical elements.
434    elements: Vec<ElementId>,
435    /// Canonical-to-local elements.
436    element_index: BTreeMap<ElementId, ProjectionElementId>,
437    /// Local-to-canonical relations.
438    relations: Vec<RelationId>,
439    /// Canonical-to-local relations.
440    relation_index: BTreeMap<RelationId, ProjectionRelationId>,
441    /// Local-to-canonical incidences.
442    incidences: Vec<IncidenceRecord>,
443    /// Canonical-to-local incidences.
444    incidence_index: BTreeMap<IncidenceId, ProjectionIncidenceId>,
445    /// Relation-major incidence IDs.
446    relation_incidences: Vec<Vec<ProjectionIncidenceId>>,
447    /// Element-major incidence IDs.
448    element_incidences: Vec<Vec<ProjectionIncidenceId>>,
449    /// Source-side incidence IDs by relation.
450    source_incidences: Vec<Vec<ProjectionIncidenceId>>,
451    /// Target-side incidence IDs by relation.
452    target_incidences: Vec<Vec<ProjectionIncidenceId>>,
453    /// Source-side element IDs by relation.
454    source_vertices: Vec<Vec<ProjectionElementId>>,
455    /// Target-side element IDs by relation.
456    target_vertices: Vec<Vec<ProjectionElementId>>,
457    /// Outgoing hyperedges by source-side element.
458    outgoing_hyperedges: Vec<Vec<ProjectionRelationId>>,
459    /// Incoming hyperedges by target-side element.
460    incoming_hyperedges: Vec<Vec<ProjectionRelationId>>,
461    /// Directed successor elements by source-side element.
462    successors: Vec<Vec<ProjectionElementId>>,
463    /// Directed predecessor elements by target-side element.
464    predecessors: Vec<Vec<ProjectionElementId>>,
465}
466
467impl HypergraphProjection {
468    /// Builds a hypergraph projection from canonical state.
469    ///
470    /// # Errors
471    ///
472    /// Returns [`DbError::InvalidProjection`] when source/target role sets do
473    /// not produce directed participant sets for selected relations.
474    ///
475    /// # Performance
476    ///
477    /// This function is `O(r * i + p log v)` for relation count `r`,
478    /// incidence count `i`, and selected participant count `p`.
479    pub(crate) fn from_state(
480        state: &DatabaseState,
481        definition: HypergraphProjectionDefinition,
482    ) -> Result<Self, DbError> {
483        if definition.source_roles.is_empty() || definition.target_roles.is_empty() {
484            return Err(DbError::invalid_projection(
485                "hypergraph projection requires non-empty source and target roles",
486            ));
487        }
488        let mut builder = HypergraphProjectionBuilder::new(definition);
489        for relation in state.relations() {
490            if relation_selected(relation, &builder.definition.relation_types) {
491                builder.push_relation(state, relation)?;
492            }
493        }
494        builder.finish()
495    }
496
497    /// Returns the definition used for this projection.
498    ///
499    /// # Performance
500    ///
501    /// This method is `O(1)`.
502    #[must_use]
503    pub const fn definition(&self) -> &HypergraphProjectionDefinition {
504        &self.definition
505    }
506
507    /// Returns deterministic canonical subjects materialized by this projection.
508    pub(crate) fn subjects(&self) -> Vec<PropertySubject> {
509        let mut subjects =
510            Vec::with_capacity(self.elements.len() + self.relations.len() + self.incidences.len());
511        subjects.extend(self.elements.iter().copied().map(PropertySubject::Element));
512        subjects.extend(
513            self.relations
514                .iter()
515                .copied()
516                .map(PropertySubject::Relation),
517        );
518        subjects.extend(
519            self.incidences
520                .iter()
521                .map(|record| PropertySubject::Incidence(record.id)),
522        );
523        subjects.sort_unstable();
524        subjects
525    }
526}
527
528/// Builder used to keep hypergraph projection construction small.
529struct HypergraphProjectionBuilder {
530    /// Projection definition being built.
531    definition: HypergraphProjectionDefinition,
532    /// Local-to-canonical elements.
533    elements: Vec<ElementId>,
534    /// Canonical-to-local elements.
535    element_index: BTreeMap<ElementId, ProjectionElementId>,
536    /// Local-to-canonical relations.
537    relations: Vec<RelationId>,
538    /// Canonical-to-local relations.
539    relation_index: BTreeMap<RelationId, ProjectionRelationId>,
540    /// Local incidence records.
541    incidences: Vec<IncidenceRecord>,
542    /// Canonical-to-local incidences.
543    incidence_index: BTreeMap<IncidenceId, ProjectionIncidenceId>,
544    /// Relation incidence arrays.
545    relation_incidences: Vec<Vec<ProjectionIncidenceId>>,
546    /// Source incidence arrays.
547    source_incidences: Vec<Vec<ProjectionIncidenceId>>,
548    /// Target incidence arrays.
549    target_incidences: Vec<Vec<ProjectionIncidenceId>>,
550    /// Source vertex arrays.
551    source_vertices: Vec<Vec<ProjectionElementId>>,
552    /// Target vertex arrays.
553    target_vertices: Vec<Vec<ProjectionElementId>>,
554}
555
556impl HypergraphProjectionBuilder {
557    /// Creates an empty hypergraph projection builder.
558    const fn new(definition: HypergraphProjectionDefinition) -> Self {
559        Self {
560            definition,
561            elements: Vec::new(),
562            element_index: BTreeMap::new(),
563            relations: Vec::new(),
564            relation_index: BTreeMap::new(),
565            incidences: Vec::new(),
566            incidence_index: BTreeMap::new(),
567            relation_incidences: Vec::new(),
568            source_incidences: Vec::new(),
569            target_incidences: Vec::new(),
570            source_vertices: Vec::new(),
571            target_vertices: Vec::new(),
572        }
573    }
574
575    /// Adds one selected canonical hyperedge relation.
576    fn push_relation(
577        &mut self,
578        state: &DatabaseState,
579        relation: &RelationRecord,
580    ) -> Result<(), DbError> {
581        let hyperedge = projection_relation_id(self.relations.len())?;
582        self.relations.push(relation.id);
583        self.relation_index.insert(relation.id, hyperedge);
584        let mut relation_ids = Vec::new();
585        let mut source_ids = Vec::new();
586        let mut target_ids = Vec::new();
587        let mut sources = Vec::new();
588        let mut targets = Vec::new();
589        for incidence in state.relation_incidences(relation.id) {
590            let local = self.push_incidence(incidence)?;
591            let vertex = intern_element(
592                &mut self.element_index,
593                &mut self.elements,
594                incidence.element,
595            )?;
596            relation_ids.push(local);
597            if self.definition.source_roles.contains(&incidence.role) {
598                source_ids.push(local);
599                sources.push(vertex);
600            }
601            if self.definition.target_roles.contains(&incidence.role) {
602                target_ids.push(local);
603                targets.push(vertex);
604            }
605        }
606        if sources.is_empty() || targets.is_empty() {
607            return Err(DbError::invalid_projection(
608                "selected hyperedge lacks source or target participants",
609            ));
610        }
611        self.relation_incidences.push(relation_ids);
612        self.source_incidences.push(source_ids);
613        self.target_incidences.push(target_ids);
614        self.source_vertices.push(sources);
615        self.target_vertices.push(targets);
616        Ok(())
617    }
618
619    /// Adds one canonical incidence to local storage.
620    fn push_incidence(
621        &mut self,
622        incidence: &IncidenceRecord,
623    ) -> Result<ProjectionIncidenceId, DbError> {
624        if let Some(id) = self.incidence_index.get(&incidence.id) {
625            return Ok(*id);
626        }
627        let local = projection_incidence_id(self.incidences.len())?;
628        self.incidences.push(*incidence);
629        self.incidence_index.insert(incidence.id, local);
630        Ok(local)
631    }
632
633    /// Finishes the hypergraph projection.
634    fn finish(self) -> Result<HypergraphProjection, DbError> {
635        let element_incidences =
636            build_element_incidences(self.elements.len(), &self.incidences, &self.element_index)?;
637        let (outgoing_hyperedges, incoming_hyperedges, successors, predecessors) =
638            build_directed_vertex_arrays(
639                self.elements.len(),
640                &self.source_vertices,
641                &self.target_vertices,
642            )?;
643        Ok(HypergraphProjection {
644            definition: self.definition,
645            elements: self.elements,
646            element_index: self.element_index,
647            relations: self.relations,
648            relation_index: self.relation_index,
649            incidences: self.incidences,
650            incidence_index: self.incidence_index,
651            relation_incidences: self.relation_incidences,
652            element_incidences,
653            source_incidences: self.source_incidences,
654            target_incidences: self.target_incidences,
655            source_vertices: self.source_vertices,
656            target_vertices: self.target_vertices,
657            outgoing_hyperedges,
658            incoming_hyperedges,
659            successors,
660            predecessors,
661        })
662    }
663}
664
665impl TopologyBase for HypergraphProjection {
666    type ElementId = ProjectionElementId;
667    type RelationId = ProjectionRelationId;
668}
669
670impl IncidenceBase for HypergraphProjection {
671    type IncidenceId = ProjectionIncidenceId;
672    type Role = RoleId;
673}
674
675impl TopologyCounts for HypergraphProjection {
676    fn element_count(&self) -> usize {
677        self.elements.len()
678    }
679
680    fn relation_count(&self) -> usize {
681        self.relations.len()
682    }
683}
684
685impl HypergraphCounts for HypergraphProjection {}
686
687impl IncidenceCounts for HypergraphProjection {
688    fn incidence_count(&self) -> usize {
689        self.incidences.len()
690    }
691}
692
693impl ElementIndex for HypergraphProjection {
694    fn element_bound(&self) -> usize {
695        self.elements.len()
696    }
697
698    fn element_index(&self, element: Self::ElementId) -> usize {
699        local_index(element)
700    }
701}
702
703impl RelationIndex for HypergraphProjection {
704    fn relation_bound(&self) -> usize {
705        self.relations.len()
706    }
707
708    fn relation_index(&self, relation: Self::RelationId) -> usize {
709        local_relation_index(relation)
710    }
711}
712
713impl IncidenceIndex for HypergraphProjection {
714    fn incidence_bound(&self) -> usize {
715        self.incidences.len()
716    }
717
718    fn incidence_index(&self, incidence: Self::IncidenceId) -> usize {
719        local_incidence_index(incidence)
720    }
721}
722
723impl oxgraph_hyper::ContainsElement for HypergraphProjection {
724    fn contains_element(&self, element: Self::ElementId) -> bool {
725        local_index(element) < self.elements.len()
726    }
727}
728
729impl oxgraph_hyper::ContainsRelation for HypergraphProjection {
730    fn contains_relation(&self, relation: Self::RelationId) -> bool {
731        local_relation_index(relation) < self.relations.len()
732    }
733}
734
735impl oxgraph_hyper::ContainsIncidence for HypergraphProjection {
736    fn contains_incidence(&self, incidence: Self::IncidenceId) -> bool {
737        local_incidence_index(incidence) < self.incidences.len()
738    }
739}
740
741impl oxgraph_hyper::CanonicalElementIdentity for HypergraphProjection {
742    type CanonicalElementId = ElementId;
743
744    fn canonical_element_id(&self, element: Self::ElementId) -> Self::CanonicalElementId {
745        self.elements[local_index(element)]
746    }
747}
748
749impl oxgraph_hyper::LocalElementIdentity for HypergraphProjection {
750    fn local_element_id(&self, canonical: Self::CanonicalElementId) -> Option<Self::ElementId> {
751        self.element_index.get(&canonical).copied()
752    }
753}
754
755impl oxgraph_hyper::CanonicalRelationIdentity for HypergraphProjection {
756    type CanonicalRelationId = RelationId;
757
758    fn canonical_relation_id(&self, relation: Self::RelationId) -> Self::CanonicalRelationId {
759        self.relations[local_relation_index(relation)]
760    }
761}
762
763impl oxgraph_hyper::LocalRelationIdentity for HypergraphProjection {
764    fn local_relation_id(&self, canonical: Self::CanonicalRelationId) -> Option<Self::RelationId> {
765        self.relation_index.get(&canonical).copied()
766    }
767}
768
769impl oxgraph_hyper::CanonicalIncidenceIdentity for HypergraphProjection {
770    type CanonicalIncidenceId = IncidenceId;
771
772    fn canonical_incidence_id(&self, incidence: Self::IncidenceId) -> Self::CanonicalIncidenceId {
773        self.incidences[local_incidence_index(incidence)].id
774    }
775}
776
777impl oxgraph_hyper::LocalIncidenceIdentity for HypergraphProjection {
778    fn local_incidence_id(
779        &self,
780        canonical: Self::CanonicalIncidenceId,
781    ) -> Option<Self::IncidenceId> {
782        self.incidence_index.get(&canonical).copied()
783    }
784}
785
786impl RelationIncidences for HypergraphProjection {
787    type Incidences<'view>
788        = std::iter::Copied<std::slice::Iter<'view, ProjectionIncidenceId>>
789    where
790        Self: 'view;
791
792    fn relation_incidences(&self, relation: Self::RelationId) -> Self::Incidences<'_> {
793        self.relation_incidences[local_relation_index(relation)]
794            .iter()
795            .copied()
796    }
797}
798
799impl oxgraph_hyper::ElementIncidences for HypergraphProjection {
800    type Incidences<'view>
801        = std::iter::Copied<std::slice::Iter<'view, ProjectionIncidenceId>>
802    where
803        Self: 'view;
804
805    fn element_incidences(&self, element: Self::ElementId) -> Self::Incidences<'_> {
806        self.element_incidences[local_index(element)]
807            .iter()
808            .copied()
809    }
810}
811
812impl IncidenceElement for HypergraphProjection {
813    fn incidence_element(&self, incidence: Self::IncidenceId) -> Self::ElementId {
814        let canonical = self.incidences[local_incidence_index(incidence)].element;
815        self.element_index[&canonical]
816    }
817}
818
819impl IncidenceRelation for HypergraphProjection {
820    fn incidence_relation(&self, incidence: Self::IncidenceId) -> Self::RelationId {
821        let canonical = self.incidences[local_incidence_index(incidence)].relation;
822        self.relation_index[&canonical]
823    }
824}
825
826impl IncidenceRole for HypergraphProjection {
827    fn incidence_role(&self, incidence: Self::IncidenceId) -> Self::Role {
828        self.incidences[local_incidence_index(incidence)].role
829    }
830}
831
832impl RelationIncidenceCount for HypergraphProjection {
833    fn relation_incidence_count(&self, relation: Self::RelationId) -> usize {
834        self.relation_incidences[local_relation_index(relation)].len()
835    }
836}
837
838impl ElementIncidenceCount for HypergraphProjection {
839    fn element_incidence_count(&self, element: Self::ElementId) -> usize {
840        self.element_incidences[local_index(element)].len()
841    }
842}
843
844impl oxgraph_hyper::HyperedgeParticipants for HypergraphProjection {
845    type Participants<'view>
846        = HyperedgeParticipants<'view>
847    where
848        Self: 'view;
849
850    fn hyperedge_participants(&self, hyperedge: Self::RelationId) -> Self::Participants<'_> {
851        HyperedgeParticipants {
852            view: self,
853            inner: self.relation_incidences[local_relation_index(hyperedge)].iter(),
854        }
855    }
856}
857
858impl IncidentHyperedges for HypergraphProjection {
859    type IncidentHyperedges<'view>
860        = IncidentHyperedgesIter<'view>
861    where
862        Self: 'view;
863
864    fn incident_hyperedges(&self, vertex: Self::ElementId) -> Self::IncidentHyperedges<'_> {
865        IncidentHyperedgesIter {
866            view: self,
867            inner: self.element_incidences[local_index(vertex)].iter(),
868        }
869    }
870}
871
872impl HyperedgeParticipantCount for HypergraphProjection {
873    fn hyperedge_participant_count(&self, hyperedge: Self::RelationId) -> usize {
874        self.relation_incidences[local_relation_index(hyperedge)].len()
875    }
876}
877
878impl IncidentHyperedgeCount for HypergraphProjection {
879    fn incident_hyperedge_count(&self, vertex: Self::ElementId) -> usize {
880        self.element_incidences[local_index(vertex)].len()
881    }
882}
883
884impl DirectedHyperedgeParticipants for HypergraphProjection {
885    type SourceParticipants<'view>
886        = std::iter::Copied<std::slice::Iter<'view, ProjectionElementId>>
887    where
888        Self: 'view;
889
890    type TargetParticipants<'view>
891        = std::iter::Copied<std::slice::Iter<'view, ProjectionElementId>>
892    where
893        Self: 'view;
894
895    fn source_participants(&self, hyperedge: Self::RelationId) -> Self::SourceParticipants<'_> {
896        self.source_vertices[local_relation_index(hyperedge)]
897            .iter()
898            .copied()
899    }
900
901    fn target_participants(&self, hyperedge: Self::RelationId) -> Self::TargetParticipants<'_> {
902        self.target_vertices[local_relation_index(hyperedge)]
903            .iter()
904            .copied()
905    }
906}
907
908impl DirectedHyperedgeIncidences for HypergraphProjection {
909    type SourceIncidences<'view>
910        = std::iter::Copied<std::slice::Iter<'view, ProjectionIncidenceId>>
911    where
912        Self: 'view;
913
914    type TargetIncidences<'view>
915        = std::iter::Copied<std::slice::Iter<'view, ProjectionIncidenceId>>
916    where
917        Self: 'view;
918
919    fn source_incidences(&self, hyperedge: Self::RelationId) -> Self::SourceIncidences<'_> {
920        self.source_incidences[local_relation_index(hyperedge)]
921            .iter()
922            .copied()
923    }
924
925    fn target_incidences(&self, hyperedge: Self::RelationId) -> Self::TargetIncidences<'_> {
926        self.target_incidences[local_relation_index(hyperedge)]
927            .iter()
928            .copied()
929    }
930}
931
932impl DirectedVertexHyperedges for HypergraphProjection {
933    type OutgoingHyperedges<'view>
934        = std::iter::Copied<std::slice::Iter<'view, ProjectionRelationId>>
935    where
936        Self: 'view;
937
938    type IncomingHyperedges<'view>
939        = std::iter::Copied<std::slice::Iter<'view, ProjectionRelationId>>
940    where
941        Self: 'view;
942
943    fn outgoing_hyperedges(&self, vertex: Self::ElementId) -> Self::OutgoingHyperedges<'_> {
944        self.outgoing_hyperedges[local_index(vertex)]
945            .iter()
946            .copied()
947    }
948
949    fn incoming_hyperedges(&self, vertex: Self::ElementId) -> Self::IncomingHyperedges<'_> {
950        self.incoming_hyperedges[local_index(vertex)]
951            .iter()
952            .copied()
953    }
954}
955
956impl ElementSuccessors for HypergraphProjection {
957    type Successors<'view>
958        = std::iter::Copied<std::slice::Iter<'view, ProjectionElementId>>
959    where
960        Self: 'view;
961
962    fn element_successors(&self, element: Self::ElementId) -> Self::Successors<'_> {
963        self.successors[local_index(element)].iter().copied()
964    }
965}
966
967impl ElementPredecessors for HypergraphProjection {
968    type Predecessors<'view>
969        = std::iter::Copied<std::slice::Iter<'view, ProjectionElementId>>
970    where
971        Self: 'view;
972
973    fn element_predecessors(&self, element: Self::ElementId) -> Self::Predecessors<'_> {
974        self.predecessors[local_index(element)].iter().copied()
975    }
976}
977
978/// Iterator mapping hyperedge incidences to participant vertices.
979pub struct HyperedgeParticipants<'view> {
980    /// Hypergraph projection view.
981    view: &'view HypergraphProjection,
982    /// Underlying incidence iterator.
983    inner: std::slice::Iter<'view, ProjectionIncidenceId>,
984}
985
986impl Iterator for HyperedgeParticipants<'_> {
987    type Item = ProjectionElementId;
988
989    fn next(&mut self) -> Option<Self::Item> {
990        self.inner
991            .next()
992            .map(|incidence| self.view.incidence_element(*incidence))
993    }
994}
995
996/// Iterator mapping vertex incidences to incident hyperedges.
997pub struct IncidentHyperedgesIter<'view> {
998    /// Hypergraph projection view.
999    view: &'view HypergraphProjection,
1000    /// Underlying incidence iterator.
1001    inner: std::slice::Iter<'view, ProjectionIncidenceId>,
1002}
1003
1004impl Iterator for IncidentHyperedgesIter<'_> {
1005    type Item = ProjectionRelationId;
1006
1007    fn next(&mut self) -> Option<Self::Item> {
1008        self.inner
1009            .next()
1010            .map(|incidence| self.view.incidence_relation(*incidence))
1011    }
1012}
1013
1014/// Returns whether a relation belongs to a relation-type filter.
1015fn relation_selected(record: &RelationRecord, selected: &BTreeSet<RelationTypeId>) -> bool {
1016    record
1017        .relation_type
1018        .map_or(selected.is_empty(), |relation_type| {
1019            selected.is_empty() || selected.contains(&relation_type)
1020        })
1021}
1022
1023/// Finds graph source and target endpoints for one canonical relation.
1024fn graph_endpoints(
1025    state: &DatabaseState,
1026    relation: RelationId,
1027    source_role: RoleId,
1028    target_role: RoleId,
1029) -> Result<(ElementId, ElementId), DbError> {
1030    let mut source = None;
1031    let mut target = None;
1032    for incidence in state.relation_incidences(relation) {
1033        update_endpoint(&mut source, incidence, source_role, "source")?;
1034        update_endpoint(&mut target, incidence, target_role, "target")?;
1035    }
1036    match (source, target) {
1037        (Some(source), Some(target)) => Ok((source, target)),
1038        _missing => Err(DbError::invalid_projection(
1039            "selected graph relation lacks source or target endpoint",
1040        )),
1041    }
1042}
1043
1044/// Updates one graph endpoint slot.
1045fn update_endpoint(
1046    slot: &mut Option<ElementId>,
1047    incidence: &IncidenceRecord,
1048    role: RoleId,
1049    name: &'static str,
1050) -> Result<(), DbError> {
1051    if incidence.role != role {
1052        return Ok(());
1053    }
1054    if slot.replace(incidence.element).is_some() {
1055        return Err(DbError::invalid_projection(format!(
1056            "selected graph relation has duplicate {name} endpoint"
1057        )));
1058    }
1059    Ok(())
1060}
1061
1062/// Interns a canonical element into projection-local storage.
1063fn intern_element(
1064    index: &mut BTreeMap<ElementId, ProjectionElementId>,
1065    elements: &mut Vec<ElementId>,
1066    canonical: ElementId,
1067) -> Result<ProjectionElementId, DbError> {
1068    if let Some(local) = index.get(&canonical) {
1069        return Ok(*local);
1070    }
1071    let local = projection_element_id(elements.len())?;
1072    elements.push(canonical);
1073    index.insert(canonical, local);
1074    Ok(local)
1075}
1076
1077/// Builds element-major incidence arrays.
1078fn build_element_incidences(
1079    element_count: usize,
1080    incidences: &[IncidenceRecord],
1081    element_index: &BTreeMap<ElementId, ProjectionElementId>,
1082) -> Result<Vec<Vec<ProjectionIncidenceId>>, DbError> {
1083    let mut rows = vec![Vec::new(); element_count];
1084    for (index, incidence) in incidences.iter().enumerate() {
1085        let local = projection_incidence_id(index)?;
1086        let element = element_index
1087            .get(&incidence.element)
1088            .ok_or_else(|| DbError::invalid_projection("incidence element not projected"))?;
1089        rows[local_index(*element)].push(local);
1090    }
1091    Ok(rows)
1092}
1093
1094/// Builds directed vertex-major hypergraph arrays.
1095fn build_directed_vertex_arrays(
1096    element_count: usize,
1097    source_vertices: &[Vec<ProjectionElementId>],
1098    target_vertices: &[Vec<ProjectionElementId>],
1099) -> Result<DirectedVertexArrays, DbError> {
1100    let mut outgoing = vec![Vec::new(); element_count];
1101    let mut incoming = vec![Vec::new(); element_count];
1102    let mut successor_sets = vec![BTreeSet::new(); element_count];
1103    let mut predecessor_sets = vec![BTreeSet::new(); element_count];
1104    for (relation_index, (sources, targets)) in
1105        source_vertices.iter().zip(target_vertices).enumerate()
1106    {
1107        let relation = projection_relation_id(relation_index)?;
1108        for source in sources {
1109            outgoing[local_index(*source)].push(relation);
1110            for target in targets {
1111                successor_sets[local_index(*source)].insert(*target);
1112            }
1113        }
1114        for target in targets {
1115            incoming[local_index(*target)].push(relation);
1116            for source in sources {
1117                predecessor_sets[local_index(*target)].insert(*source);
1118            }
1119        }
1120    }
1121    let successors = successor_sets
1122        .into_iter()
1123        .map(|set| set.into_iter().collect())
1124        .collect();
1125    let predecessors = predecessor_sets
1126        .into_iter()
1127        .map(|set| set.into_iter().collect())
1128        .collect();
1129    Ok((outgoing, incoming, successors, predecessors))
1130}
1131
1132/// Directed vertex-array build result.
1133type DirectedVertexArrays = (
1134    Vec<Vec<ProjectionRelationId>>,
1135    Vec<Vec<ProjectionRelationId>>,
1136    Vec<Vec<ProjectionElementId>>,
1137    Vec<Vec<ProjectionElementId>>,
1138);
1139
1140/// Converts a local element count into an ID.
1141fn projection_element_id(index: usize) -> Result<ProjectionElementId, DbError> {
1142    u32::try_from(index)
1143        .map(ProjectionElementId)
1144        .map_err(|_error| DbError::IdOverflow)
1145}
1146
1147/// Converts a local relation count into an ID.
1148fn projection_relation_id(index: usize) -> Result<ProjectionRelationId, DbError> {
1149    u32::try_from(index)
1150        .map(ProjectionRelationId)
1151        .map_err(|_error| DbError::IdOverflow)
1152}
1153
1154/// Converts a local incidence count into an ID.
1155fn projection_incidence_id(index: usize) -> Result<ProjectionIncidenceId, DbError> {
1156    u32::try_from(index)
1157        .map(ProjectionIncidenceId)
1158        .map_err(|_error| DbError::IdOverflow)
1159}
1160
1161/// Returns a local element index.
1162fn local_index(id: ProjectionElementId) -> usize {
1163    usize::try_from(id.0).unwrap_or(usize::MAX)
1164}
1165
1166/// Returns a local relation index.
1167fn local_relation_index(id: ProjectionRelationId) -> usize {
1168    usize::try_from(id.0).unwrap_or(usize::MAX)
1169}
1170
1171/// Returns a local incidence index.
1172fn local_incidence_index(id: ProjectionIncidenceId) -> usize {
1173    usize::try_from(id.0).unwrap_or(usize::MAX)
1174}