#![cfg_attr(
not(test),
expect(
clippy::missing_docs_in_private_items,
reason = "hypergraph builder internals are documented at the public type and trait capability boundaries"
)
)]
use alloc::{boxed::Box, vec, vec::Vec};
use core::{error::Error, fmt, marker::PhantomData};
use oxgraph_hyper::{
CanonicalElementIdentity, CanonicalIncidenceIdentity, CanonicalRelationIdentity,
ContainsElement, ContainsIncidence, ContainsRelation, DenseElementIndex, DenseIncidenceIndex,
DenseRelationIndex, DirectedHyperedgeIncidences, DirectedHyperedgeParticipants,
DirectedVertexHyperedges, ElementIncidenceCount, ElementIncidences, ElementPredecessors,
ElementSuccessors, ElementWeight, HyperedgeParticipants, IncidenceBase, IncidenceCounts,
IncidenceElement, IncidenceRelation, IncidenceRole, IncidenceWeight, IncidentHyperedges,
LocalElementIdentity, LocalIncidenceIdentity, LocalRelationIdentity, RelationIncidenceCount,
RelationIncidences, RelationWeight, TopologyBase, TopologyCounts,
};
use oxgraph_layout_util::{
LayoutIndex, LayoutWord,
build::{
IdOutOfBounds, OffsetOverflow, build_offset_index, id_to_slot, index_from_usize,
slot_or_max,
},
crc32c_append,
};
#[cfg(feature = "build-property-arrow")]
use oxgraph_property::{
EncodedPropertySnapshot, HyperPropertyLayers, IdFamily, IdentityModeRecord, PropertyError,
PropertyLayer, PropertySnapshotMetaWord, encode_hyper_property_snapshot, rekey_layer_to_local,
};
use oxgraph_snapshot::{PlanError, SnapshotWriter};
use crate::{
BcsrChainedHyperedges, BcsrChainedParticipants, BcsrChainedRelationIncidences,
BcsrElementIncidences, BcsrError, BcsrHyperedgeSlice, BcsrHypergraph, BcsrNativeHypergraph,
BcsrParticipantSlice, BcsrPredecessorVertices, BcsrRole, BcsrSections, BcsrSnapshotIndex,
BcsrSuccessorVertices, BcsrValidation, BcsrVertexSlice,
};
type FrozenHypergraphResult<VertexIndex, RelationIndex, IncidenceIndex> = Result<
FrozenHypergraph<VertexIndex, RelationIndex, IncidenceIndex>,
HyperBuildError<VertexIndex, RelationIndex, IncidenceIndex>,
>;
type FrozenWeightedHypergraphResult<VertexIndex, RelationIndex, IncidenceIndex, EW, RW, IW> =
Result<
FrozenWeightedHypergraph<VertexIndex, RelationIndex, IncidenceIndex, EW, RW, IW>,
HyperBuildError<VertexIndex, RelationIndex, IncidenceIndex>,
>;
type BuiltTopology<VertexIndex, RelationIndex, IncidenceIndex, IW> = (
FrozenTopology<VertexIndex, RelationIndex, IncidenceIndex>,
Vec<IW>,
);
type BuiltTopologyResult<VertexIndex, RelationIndex, IncidenceIndex, IW> = Result<
BuiltTopology<VertexIndex, RelationIndex, IncidenceIndex, IW>,
HyperBuildError<VertexIndex, RelationIndex, IncidenceIndex>,
>;
type VertexRelationIndex<RelationIndex, IncidenceIndex> = (Vec<IncidenceIndex>, Vec<RelationIndex>);
type VertexRelationIndexResult<VertexIndex, RelationIndex, IncidenceIndex> = Result<
VertexRelationIndex<RelationIndex, IncidenceIndex>,
HyperBuildError<VertexIndex, RelationIndex, IncidenceIndex>,
>;
pub type HyperVertexId<VertexIndex> = crate::BcsrVertexId<VertexIndex>;
pub type HyperedgeId<RelationIndex> = crate::BcsrHyperedgeId<RelationIndex>;
pub type HyperParticipantId<IncidenceIndex> = crate::BcsrParticipantId<IncidenceIndex>;
#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
#[non_exhaustive]
pub enum HyperParticipantRole {
Source,
Target,
}
#[derive(Debug)]
#[non_exhaustive]
pub enum HyperBuildError<VertexIndex, RelationIndex, IncidenceIndex> {
InvalidVertex {
vertex: HyperVertexId<VertexIndex>,
},
InvalidHyperedge {
hyperedge: HyperedgeId<RelationIndex>,
},
InvalidParticipant {
participant: HyperParticipantId<IncidenceIndex>,
},
ParticipantPositionOutOfBounds {
hyperedge: HyperedgeId<RelationIndex>,
position: usize,
},
DuplicateParticipant {
vertex: HyperVertexId<VertexIndex>,
role: HyperParticipantRole,
},
IdOverflow {
value: usize,
},
InvalidTopology {
source: BcsrError,
},
#[cfg(feature = "build-property-arrow")]
UnsupportedPropertyFamily {
id_family: IdFamily,
},
#[cfg(feature = "build-property-arrow")]
PropertyLayerTooShort {
id_family: IdFamily,
required: usize,
actual: usize,
},
#[cfg(feature = "build-property-arrow")]
Property {
source: PropertyError,
},
SnapshotPlan {
source: PlanError,
},
}
impl<VertexIndex, RelationIndex, IncidenceIndex> fmt::Display
for HyperBuildError<VertexIndex, RelationIndex, IncidenceIndex>
where
VertexIndex: fmt::Debug,
RelationIndex: fmt::Debug,
IncidenceIndex: fmt::Debug,
{
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::InvalidVertex { vertex } => {
write!(formatter, "invalid hypergraph vertex ID {vertex:?}")
}
Self::InvalidHyperedge { hyperedge } => {
write!(formatter, "invalid hyperedge ID {hyperedge:?}")
}
Self::InvalidParticipant { participant } => {
write!(formatter, "invalid participant ID {participant:?}")
}
Self::ParticipantPositionOutOfBounds {
hyperedge,
position,
} => write!(
formatter,
"participant position {position} is outside {hyperedge:?}"
),
Self::DuplicateParticipant { vertex, role } => write!(
formatter,
"duplicate {role:?} participant vertex {vertex:?} in hyperedge"
),
Self::IdOverflow { value } => {
write!(formatter, "hypergraph builder ID overflow at {value}")
}
Self::InvalidTopology { source } => {
write!(
formatter,
"frozen topology failed strict validation: {source}"
)
}
#[cfg(feature = "build-property-arrow")]
Self::UnsupportedPropertyFamily { id_family } => write!(
formatter,
"unsupported hypergraph property family {id_family:?}"
),
#[cfg(feature = "build-property-arrow")]
Self::PropertyLayerTooShort {
id_family,
required,
actual,
} => write!(
formatter,
"hypergraph property layer for {id_family:?} too short: required {required}, got {actual}"
),
#[cfg(feature = "build-property-arrow")]
Self::Property { source } => {
write!(formatter, "property snapshot export failed: {source}")
}
Self::SnapshotPlan { source } => write!(formatter, "snapshot export failed: {source}"),
}
}
}
impl<VertexIndex, RelationIndex, IncidenceIndex> Error
for HyperBuildError<VertexIndex, RelationIndex, IncidenceIndex>
where
VertexIndex: fmt::Debug,
RelationIndex: fmt::Debug,
IncidenceIndex: fmt::Debug,
{
fn source(&self) -> Option<&(dyn Error + 'static)> {
match self {
#[cfg(feature = "build-property-arrow")]
Self::Property { source } => Some(source),
Self::SnapshotPlan { source } => Some(source),
Self::InvalidTopology { source } => Some(source),
Self::InvalidVertex { .. }
| Self::InvalidHyperedge { .. }
| Self::InvalidParticipant { .. }
| Self::ParticipantPositionOutOfBounds { .. }
| Self::DuplicateParticipant { .. }
| Self::IdOverflow { .. } => None,
#[cfg(feature = "build-property-arrow")]
Self::UnsupportedPropertyFamily { .. } | Self::PropertyLayerTooShort { .. } => None,
}
}
}
impl<VertexIndex, RelationIndex, IncidenceIndex> From<PlanError>
for HyperBuildError<VertexIndex, RelationIndex, IncidenceIndex>
{
fn from(source: PlanError) -> Self {
Self::SnapshotPlan { source }
}
}
#[cfg(feature = "build-property-arrow")]
impl<VertexIndex, RelationIndex, IncidenceIndex> From<PropertyError>
for HyperBuildError<VertexIndex, RelationIndex, IncidenceIndex>
{
fn from(source: PropertyError) -> Self {
Self::Property { source }
}
}
#[derive(Clone, Debug)]
#[must_use]
pub struct HypergraphBuilder<VertexIndex, RelationIndex, IncidenceIndex>
where
VertexIndex: LayoutIndex,
RelationIndex: LayoutIndex,
IncidenceIndex: LayoutIndex,
{
vertex_count: usize,
hyperedges: Vec<HyperedgeRecord<VertexIndex>>,
index_width: PhantomData<(RelationIndex, IncidenceIndex)>,
}
impl<VertexIndex, RelationIndex, IncidenceIndex>
HypergraphBuilder<VertexIndex, RelationIndex, IncidenceIndex>
where
VertexIndex: LayoutIndex,
RelationIndex: LayoutIndex,
IncidenceIndex: LayoutIndex,
{
pub const fn new() -> Self {
Self {
vertex_count: 0,
hyperedges: Vec::new(),
index_width: PhantomData,
}
}
pub fn add_vertex(
&mut self,
) -> Result<
HyperVertexId<VertexIndex>,
HyperBuildError<VertexIndex, RelationIndex, IncidenceIndex>,
> {
let id = VertexIndex::from_usize(self.vertex_count).ok_or(HyperBuildError::IdOverflow {
value: self.vertex_count,
})?;
self.vertex_count = self
.vertex_count
.checked_add(1)
.ok_or(HyperBuildError::IdOverflow { value: usize::MAX })?;
Ok(HyperVertexId::new(id))
}
pub fn add_hyperedge(
&mut self,
sources: &[HyperVertexId<VertexIndex>],
targets: &[HyperVertexId<VertexIndex>],
) -> Result<
HyperedgeId<RelationIndex>,
HyperBuildError<VertexIndex, RelationIndex, IncidenceIndex>,
> {
ensure_vertices(self.vertex_count, sources.iter().copied())?;
ensure_vertices(self.vertex_count, targets.iter().copied())?;
ensure_unique_participants(sources.iter().copied(), HyperParticipantRole::Source)?;
ensure_unique_participants(targets.iter().copied(), HyperParticipantRole::Target)?;
let hyperedge = RelationIndex::from_usize(self.hyperedges.len()).ok_or(
HyperBuildError::IdOverflow {
value: self.hyperedges.len(),
},
)?;
self.hyperedges.push(HyperedgeRecord {
sources: sources.iter().map(|vertex| vertex.get()).collect(),
targets: targets.iter().map(|vertex| vertex.get()).collect(),
});
Ok(HyperedgeId::new(hyperedge))
}
#[must_use]
pub const fn vertex_count(&self) -> usize {
self.vertex_count
}
#[must_use]
pub const fn hyperedge_count(&self) -> usize {
self.hyperedges.len()
}
pub fn freeze(&self) -> FrozenHypergraphResult<VertexIndex, RelationIndex, IncidenceIndex>
where
VertexIndex: LayoutWord<Index = VertexIndex>,
RelationIndex: LayoutWord<Index = RelationIndex>,
IncidenceIndex: LayoutWord<Index = IncidenceIndex>,
{
let normalized = self.normalized_hyperedges();
let (topology, _weights) = build_topology(self.vertex_count, &normalized)?;
validate_frozen_topology(&topology)?;
Ok(FrozenHypergraph { topology })
}
fn normalized_hyperedges(&self) -> Vec<NormalizedHyperedgeRecord<VertexIndex, ()>> {
self.hyperedges
.iter()
.map(|record| {
let mut sources: Vec<(VertexIndex, ())> = record
.sources
.iter()
.copied()
.map(|vertex| (vertex, ()))
.collect();
let mut targets: Vec<(VertexIndex, ())> = record
.targets
.iter()
.copied()
.map(|vertex| (vertex, ()))
.collect();
sources.sort_by_key(|(vertex, _weight)| *vertex);
targets.sort_by_key(|(vertex, _weight)| *vertex);
NormalizedHyperedgeRecord { sources, targets }
})
.collect()
}
}
impl<VertexIndex, RelationIndex, IncidenceIndex> Default
for HypergraphBuilder<VertexIndex, RelationIndex, IncidenceIndex>
where
VertexIndex: LayoutIndex,
RelationIndex: LayoutIndex,
IncidenceIndex: LayoutIndex,
{
fn default() -> Self {
Self::new()
}
}
#[derive(Clone, Debug)]
#[must_use]
pub struct WeightedHypergraphBuilder<VertexIndex, RelationIndex, IncidenceIndex, EW, RW, IW>
where
VertexIndex: LayoutIndex,
RelationIndex: LayoutIndex,
IncidenceIndex: LayoutIndex,
{
vertex_count: usize,
element_weights: Vec<EW>,
hyperedges: Vec<NormalizedHyperedgeRecord<VertexIndex, IW>>,
relation_weights: Vec<RW>,
index_width: PhantomData<(RelationIndex, IncidenceIndex)>,
}
impl<VertexIndex, RelationIndex, IncidenceIndex, EW, RW, IW>
WeightedHypergraphBuilder<VertexIndex, RelationIndex, IncidenceIndex, EW, RW, IW>
where
VertexIndex: LayoutIndex,
RelationIndex: LayoutIndex,
IncidenceIndex: LayoutIndex,
{
pub const fn new() -> Self {
Self {
vertex_count: 0,
element_weights: Vec::new(),
hyperedges: Vec::new(),
relation_weights: Vec::new(),
index_width: PhantomData,
}
}
pub fn add_vertex(
&mut self,
weight: EW,
) -> Result<
HyperVertexId<VertexIndex>,
HyperBuildError<VertexIndex, RelationIndex, IncidenceIndex>,
> {
let id = VertexIndex::from_usize(self.vertex_count).ok_or(HyperBuildError::IdOverflow {
value: self.vertex_count,
})?;
self.vertex_count = self
.vertex_count
.checked_add(1)
.ok_or(HyperBuildError::IdOverflow { value: usize::MAX })?;
self.element_weights.push(weight);
Ok(HyperVertexId::new(id))
}
pub fn add_hyperedge(
&mut self,
sources: &[(HyperVertexId<VertexIndex>, IW)],
targets: &[(HyperVertexId<VertexIndex>, IW)],
relation_weight: RW,
) -> Result<
HyperedgeId<RelationIndex>,
HyperBuildError<VertexIndex, RelationIndex, IncidenceIndex>,
>
where
IW: Clone,
{
ensure_vertices(self.vertex_count, sources.iter().map(|(v, _)| *v))?;
ensure_vertices(self.vertex_count, targets.iter().map(|(v, _)| *v))?;
ensure_unique_participants(
sources.iter().map(|(v, _)| *v),
HyperParticipantRole::Source,
)?;
ensure_unique_participants(
targets.iter().map(|(v, _)| *v),
HyperParticipantRole::Target,
)?;
let hyperedge = RelationIndex::from_usize(self.hyperedges.len()).ok_or(
HyperBuildError::IdOverflow {
value: self.hyperedges.len(),
},
)?;
self.hyperedges.push(NormalizedHyperedgeRecord {
sources: sources
.iter()
.map(|(vertex, weight)| (vertex.get(), weight.clone()))
.collect(),
targets: targets
.iter()
.map(|(vertex, weight)| (vertex.get(), weight.clone()))
.collect(),
});
self.relation_weights.push(relation_weight);
Ok(HyperedgeId::new(hyperedge))
}
pub fn set_element_weight(
&mut self,
vertex: HyperVertexId<VertexIndex>,
weight: EW,
) -> Result<(), HyperBuildError<VertexIndex, RelationIndex, IncidenceIndex>> {
let index = vertex_index_checked(self.vertex_count, vertex)?;
self.element_weights[index] = weight;
Ok(())
}
pub fn set_relation_weight(
&mut self,
hyperedge: HyperedgeId<RelationIndex>,
weight: RW,
) -> Result<(), HyperBuildError<VertexIndex, RelationIndex, IncidenceIndex>> {
let index = hyperedge_index_checked(self.hyperedges.len(), hyperedge)?;
self.relation_weights[index] = weight;
Ok(())
}
pub fn set_source_incidence_weight(
&mut self,
hyperedge: HyperedgeId<RelationIndex>,
position: usize,
weight: IW,
) -> Result<(), HyperBuildError<VertexIndex, RelationIndex, IncidenceIndex>> {
let index = hyperedge_index_checked(self.hyperedges.len(), hyperedge)?;
let Some((_vertex, slot)) = self.hyperedges[index].sources.get_mut(position) else {
return Err(HyperBuildError::ParticipantPositionOutOfBounds {
hyperedge,
position,
});
};
*slot = weight;
Ok(())
}
pub fn set_target_incidence_weight(
&mut self,
hyperedge: HyperedgeId<RelationIndex>,
position: usize,
weight: IW,
) -> Result<(), HyperBuildError<VertexIndex, RelationIndex, IncidenceIndex>> {
let index = hyperedge_index_checked(self.hyperedges.len(), hyperedge)?;
let Some((_vertex, slot)) = self.hyperedges[index].targets.get_mut(position) else {
return Err(HyperBuildError::ParticipantPositionOutOfBounds {
hyperedge,
position,
});
};
*slot = weight;
Ok(())
}
#[must_use]
pub const fn vertex_count(&self) -> usize {
self.vertex_count
}
#[must_use]
pub const fn hyperedge_count(&self) -> usize {
self.hyperedges.len()
}
pub fn freeze(
&self,
) -> FrozenWeightedHypergraphResult<VertexIndex, RelationIndex, IncidenceIndex, EW, RW, IW>
where
VertexIndex: LayoutWord<Index = VertexIndex>,
RelationIndex: LayoutWord<Index = RelationIndex>,
IncidenceIndex: LayoutWord<Index = IncidenceIndex>,
EW: Clone,
RW: Clone,
IW: Clone,
{
let normalized = self.normalized_hyperedges();
let (topology, incidence_weights) = build_topology(self.vertex_count, &normalized)?;
validate_frozen_topology(&topology)?;
Ok(FrozenWeightedHypergraph {
topology,
element_weights: self.element_weights.clone().into_boxed_slice(),
relation_weights: self.relation_weights.clone().into_boxed_slice(),
incidence_weights: incidence_weights.into_boxed_slice(),
})
}
fn normalized_hyperedges(&self) -> Vec<NormalizedHyperedgeRecord<VertexIndex, IW>>
where
IW: Clone,
{
self.hyperedges
.iter()
.map(|record| {
let mut sources = record.sources.clone();
let mut targets = record.targets.clone();
sources.sort_by_key(|(vertex, _weight)| *vertex);
targets.sort_by_key(|(vertex, _weight)| *vertex);
NormalizedHyperedgeRecord { sources, targets }
})
.collect()
}
}
impl<VertexIndex, RelationIndex, IncidenceIndex, EW, RW, IW> Default
for WeightedHypergraphBuilder<VertexIndex, RelationIndex, IncidenceIndex, EW, RW, IW>
where
VertexIndex: LayoutIndex,
RelationIndex: LayoutIndex,
IncidenceIndex: LayoutIndex,
{
fn default() -> Self {
Self::new()
}
}
#[derive(Clone, Debug)]
#[must_use]
pub struct FrozenHypergraph<VertexIndex, RelationIndex, IncidenceIndex>
where
VertexIndex: LayoutIndex,
RelationIndex: LayoutIndex,
IncidenceIndex: LayoutIndex,
{
topology: FrozenTopology<VertexIndex, RelationIndex, IncidenceIndex>,
}
impl<VertexIndex, RelationIndex, IncidenceIndex>
FrozenHypergraph<VertexIndex, RelationIndex, IncidenceIndex>
where
VertexIndex: LayoutIndex,
RelationIndex: LayoutIndex,
IncidenceIndex: LayoutIndex,
{
#[must_use]
pub fn as_view(&self) -> BcsrNativeHypergraph<'_, VertexIndex, RelationIndex, IncidenceIndex>
where
VertexIndex: LayoutWord<Index = VertexIndex>,
RelationIndex: LayoutWord<Index = RelationIndex>,
IncidenceIndex: LayoutWord<Index = IncidenceIndex>,
{
self.topology.as_view()
}
}
#[derive(Clone, Debug)]
#[must_use]
pub struct FrozenWeightedHypergraph<VertexIndex, RelationIndex, IncidenceIndex, EW, RW, IW>
where
VertexIndex: LayoutIndex,
RelationIndex: LayoutIndex,
IncidenceIndex: LayoutIndex,
{
topology: FrozenTopology<VertexIndex, RelationIndex, IncidenceIndex>,
element_weights: Box<[EW]>,
relation_weights: Box<[RW]>,
incidence_weights: Box<[IW]>,
}
impl<VertexIndex, RelationIndex, IncidenceIndex, EW, RW, IW>
FrozenWeightedHypergraph<VertexIndex, RelationIndex, IncidenceIndex, EW, RW, IW>
where
VertexIndex: LayoutIndex,
RelationIndex: LayoutIndex,
IncidenceIndex: LayoutIndex,
{
#[must_use]
pub fn as_view(&self) -> BcsrNativeHypergraph<'_, VertexIndex, RelationIndex, IncidenceIndex>
where
VertexIndex: LayoutWord<Index = VertexIndex>,
RelationIndex: LayoutWord<Index = RelationIndex>,
IncidenceIndex: LayoutWord<Index = IncidenceIndex>,
{
self.topology.as_view()
}
}
#[derive(Clone, Debug)]
struct FrozenTopology<VertexIndex, RelationIndex, IncidenceIndex>
where
VertexIndex: LayoutIndex,
RelationIndex: LayoutIndex,
IncidenceIndex: LayoutIndex,
{
vertex_count: usize,
head_offsets: Box<[IncidenceIndex]>,
head_participants: Box<[VertexIndex]>,
tail_offsets: Box<[IncidenceIndex]>,
tail_participants: Box<[VertexIndex]>,
vertex_outgoing_offsets: Box<[IncidenceIndex]>,
vertex_outgoing_hyperedges: Box<[RelationIndex]>,
vertex_incoming_offsets: Box<[IncidenceIndex]>,
vertex_incoming_hyperedges: Box<[RelationIndex]>,
}
impl<VertexIndex, RelationIndex, IncidenceIndex>
FrozenTopology<VertexIndex, RelationIndex, IncidenceIndex>
where
VertexIndex: LayoutIndex,
RelationIndex: LayoutIndex,
IncidenceIndex: LayoutIndex,
{
fn relation_count(&self) -> usize {
self.head_offsets.len().saturating_sub(1)
}
fn total_participants(&self) -> usize {
self.head_participants
.len()
.saturating_add(self.tail_participants.len())
}
fn sections(&self) -> BcsrSections<'_, IncidenceIndex, VertexIndex, RelationIndex>
where
VertexIndex: LayoutWord<Index = VertexIndex>,
RelationIndex: LayoutWord<Index = RelationIndex>,
IncidenceIndex: LayoutWord<Index = IncidenceIndex>,
{
BcsrSections {
head_offsets: &self.head_offsets,
head_participants: &self.head_participants,
tail_offsets: &self.tail_offsets,
tail_participants: &self.tail_participants,
vertex_outgoing_offsets: &self.vertex_outgoing_offsets,
vertex_outgoing_hyperedges: &self.vertex_outgoing_hyperedges,
vertex_incoming_offsets: &self.vertex_incoming_offsets,
vertex_incoming_hyperedges: &self.vertex_incoming_hyperedges,
}
}
fn as_view(&self) -> BcsrNativeHypergraph<'_, VertexIndex, RelationIndex, IncidenceIndex>
where
VertexIndex: LayoutWord<Index = VertexIndex>,
RelationIndex: LayoutWord<Index = RelationIndex>,
IncidenceIndex: LayoutWord<Index = IncidenceIndex>,
{
BcsrHypergraph::from_validated_sections(self.sections())
}
}
fn validate_frozen_topology<VertexIndex, RelationIndex, IncidenceIndex>(
topology: &FrozenTopology<VertexIndex, RelationIndex, IncidenceIndex>,
) -> Result<(), HyperBuildError<VertexIndex, RelationIndex, IncidenceIndex>>
where
VertexIndex: LayoutIndex + LayoutWord<Index = VertexIndex>,
RelationIndex: LayoutIndex + LayoutWord<Index = RelationIndex>,
IncidenceIndex: LayoutIndex + LayoutWord<Index = IncidenceIndex>,
{
BcsrNativeHypergraph::<VertexIndex, RelationIndex, IncidenceIndex>::open_with(
topology.sections(),
BcsrValidation::Strict,
)
.map(|_view| ())
.map_err(|source| HyperBuildError::InvalidTopology { source })
}
fn vertex_slot<VertexIndex: LayoutIndex>(vertex: HyperVertexId<VertexIndex>) -> usize {
slot_or_max::<VertexIndex>(vertex.get())
}
fn hyperedge_slot<RelationIndex: LayoutIndex>(hyperedge: HyperedgeId<RelationIndex>) -> usize {
slot_or_max::<RelationIndex>(hyperedge.get())
}
fn participant_slot<IncidenceIndex: LayoutIndex>(
participant: HyperParticipantId<IncidenceIndex>,
) -> usize {
slot_or_max::<IncidenceIndex>(participant.get())
}
const fn map_offset_overflow<VertexIndex, RelationIndex, IncidenceIndex>(
error: OffsetOverflow,
) -> HyperBuildError<VertexIndex, RelationIndex, IncidenceIndex> {
match error {
OffsetOverflow::IndexOverflow { value } => HyperBuildError::IdOverflow { value },
_ => HyperBuildError::IdOverflow { value: usize::MAX },
}
}
macro_rules! impl_topology_for {
($name:ident, [$($extra:ident),*], $topology:ident) => {
impl<VertexIndex, RelationIndex, IncidenceIndex$(, $extra)*> TopologyBase
for $name<VertexIndex, RelationIndex, IncidenceIndex$(, $extra)*>
where
VertexIndex: LayoutIndex + LayoutWord<Index = VertexIndex>,
RelationIndex: LayoutIndex + LayoutWord<Index = RelationIndex>,
IncidenceIndex: LayoutIndex + LayoutWord<Index = IncidenceIndex>,
{
type ElementId = HyperVertexId<VertexIndex>;
type RelationId = HyperedgeId<RelationIndex>;
}
impl<VertexIndex, RelationIndex, IncidenceIndex$(, $extra)*> IncidenceBase
for $name<VertexIndex, RelationIndex, IncidenceIndex$(, $extra)*>
where
VertexIndex: LayoutIndex + LayoutWord<Index = VertexIndex>,
RelationIndex: LayoutIndex + LayoutWord<Index = RelationIndex>,
IncidenceIndex: LayoutIndex + LayoutWord<Index = IncidenceIndex>,
{
type IncidenceId = HyperParticipantId<IncidenceIndex>;
type Role = HyperParticipantRole;
}
impl<VertexIndex, RelationIndex, IncidenceIndex$(, $extra)*> TopologyCounts
for $name<VertexIndex, RelationIndex, IncidenceIndex$(, $extra)*>
where
VertexIndex: LayoutIndex + LayoutWord<Index = VertexIndex>,
RelationIndex: LayoutIndex + LayoutWord<Index = RelationIndex>,
IncidenceIndex: LayoutIndex + LayoutWord<Index = IncidenceIndex>,
{
fn element_count(&self) -> usize {
self.$topology.vertex_count
}
fn relation_count(&self) -> usize {
self.$topology.relation_count()
}
}
impl<VertexIndex, RelationIndex, IncidenceIndex$(, $extra)*> IncidenceCounts
for $name<VertexIndex, RelationIndex, IncidenceIndex$(, $extra)*>
where
VertexIndex: LayoutIndex + LayoutWord<Index = VertexIndex>,
RelationIndex: LayoutIndex + LayoutWord<Index = RelationIndex>,
IncidenceIndex: LayoutIndex + LayoutWord<Index = IncidenceIndex>,
{
fn incidence_count(&self) -> usize {
self.$topology.total_participants()
}
}
impl<VertexIndex, RelationIndex, IncidenceIndex$(, $extra)*> DenseElementIndex
for $name<VertexIndex, RelationIndex, IncidenceIndex$(, $extra)*>
where
VertexIndex: LayoutIndex + LayoutWord<Index = VertexIndex>,
RelationIndex: LayoutIndex + LayoutWord<Index = RelationIndex>,
IncidenceIndex: LayoutIndex + LayoutWord<Index = IncidenceIndex>,
{
fn element_bound(&self) -> usize {
self.$topology.vertex_count
}
fn element_index(&self, element: HyperVertexId<VertexIndex>) -> usize {
element.get().to_usize().unwrap_or(usize::MAX)
}
}
impl<VertexIndex, RelationIndex, IncidenceIndex$(, $extra)*> DenseRelationIndex
for $name<VertexIndex, RelationIndex, IncidenceIndex$(, $extra)*>
where
VertexIndex: LayoutIndex + LayoutWord<Index = VertexIndex>,
RelationIndex: LayoutIndex + LayoutWord<Index = RelationIndex>,
IncidenceIndex: LayoutIndex + LayoutWord<Index = IncidenceIndex>,
{
fn relation_bound(&self) -> usize {
self.$topology.relation_count()
}
fn relation_index(&self, relation: HyperedgeId<RelationIndex>) -> usize {
relation.get().to_usize().unwrap_or(usize::MAX)
}
}
impl<VertexIndex, RelationIndex, IncidenceIndex$(, $extra)*> DenseIncidenceIndex
for $name<VertexIndex, RelationIndex, IncidenceIndex$(, $extra)*>
where
VertexIndex: LayoutIndex + LayoutWord<Index = VertexIndex>,
RelationIndex: LayoutIndex + LayoutWord<Index = RelationIndex>,
IncidenceIndex: LayoutIndex + LayoutWord<Index = IncidenceIndex>,
{
fn incidence_bound(&self) -> usize {
self.$topology.total_participants()
}
fn incidence_index(&self, incidence: HyperParticipantId<IncidenceIndex>) -> usize {
incidence.get().to_usize().unwrap_or(usize::MAX)
}
}
impl<VertexIndex, RelationIndex, IncidenceIndex$(, $extra)*> ContainsElement
for $name<VertexIndex, RelationIndex, IncidenceIndex$(, $extra)*>
where
VertexIndex: LayoutIndex + LayoutWord<Index = VertexIndex>,
RelationIndex: LayoutIndex + LayoutWord<Index = RelationIndex>,
IncidenceIndex: LayoutIndex + LayoutWord<Index = IncidenceIndex>,
{
fn contains_element(&self, element: HyperVertexId<VertexIndex>) -> bool {
element
.get()
.to_usize()
.is_some_and(|slot| slot < self.$topology.vertex_count)
}
}
impl<VertexIndex, RelationIndex, IncidenceIndex$(, $extra)*> ContainsRelation
for $name<VertexIndex, RelationIndex, IncidenceIndex$(, $extra)*>
where
VertexIndex: LayoutIndex + LayoutWord<Index = VertexIndex>,
RelationIndex: LayoutIndex + LayoutWord<Index = RelationIndex>,
IncidenceIndex: LayoutIndex + LayoutWord<Index = IncidenceIndex>,
{
fn contains_relation(&self, relation: HyperedgeId<RelationIndex>) -> bool {
relation
.get()
.to_usize()
.is_some_and(|slot| slot < self.$topology.relation_count())
}
}
impl<VertexIndex, RelationIndex, IncidenceIndex$(, $extra)*> ContainsIncidence
for $name<VertexIndex, RelationIndex, IncidenceIndex$(, $extra)*>
where
VertexIndex: LayoutIndex + LayoutWord<Index = VertexIndex>,
RelationIndex: LayoutIndex + LayoutWord<Index = RelationIndex>,
IncidenceIndex: LayoutIndex + LayoutWord<Index = IncidenceIndex>,
{
fn contains_incidence(&self, incidence: HyperParticipantId<IncidenceIndex>) -> bool {
incidence
.get()
.to_usize()
.is_some_and(|slot| slot < self.$topology.total_participants())
}
}
impl<VertexIndex, RelationIndex, IncidenceIndex$(, $extra)*> IncidenceElement
for $name<VertexIndex, RelationIndex, IncidenceIndex$(, $extra)*>
where
VertexIndex: LayoutIndex + LayoutWord<Index = VertexIndex>,
RelationIndex: LayoutIndex + LayoutWord<Index = RelationIndex>,
IncidenceIndex: LayoutIndex + LayoutWord<Index = IncidenceIndex>,
{
fn incidence_element(
&self,
incidence: HyperParticipantId<IncidenceIndex>,
) -> HyperVertexId<VertexIndex> {
self.$topology.as_view().incidence_element(incidence)
}
}
impl<VertexIndex, RelationIndex, IncidenceIndex$(, $extra)*> IncidenceRelation
for $name<VertexIndex, RelationIndex, IncidenceIndex$(, $extra)*>
where
VertexIndex: LayoutIndex + LayoutWord<Index = VertexIndex>,
RelationIndex: LayoutIndex + LayoutWord<Index = RelationIndex>,
IncidenceIndex: LayoutIndex + LayoutWord<Index = IncidenceIndex>,
{
fn incidence_relation(
&self,
incidence: HyperParticipantId<IncidenceIndex>,
) -> HyperedgeId<RelationIndex> {
self.$topology.as_view().incidence_relation(incidence)
}
}
impl<VertexIndex, RelationIndex, IncidenceIndex$(, $extra)*> IncidenceRole
for $name<VertexIndex, RelationIndex, IncidenceIndex$(, $extra)*>
where
VertexIndex: LayoutIndex + LayoutWord<Index = VertexIndex>,
RelationIndex: LayoutIndex + LayoutWord<Index = RelationIndex>,
IncidenceIndex: LayoutIndex + LayoutWord<Index = IncidenceIndex>,
{
fn incidence_role(&self, incidence: HyperParticipantId<IncidenceIndex>) -> HyperParticipantRole {
match self.$topology.as_view().incidence_role(incidence) {
BcsrRole::Head => HyperParticipantRole::Source,
BcsrRole::Tail => HyperParticipantRole::Target,
}
}
}
impl<VertexIndex, RelationIndex, IncidenceIndex$(, $extra)*> RelationIncidences
for $name<VertexIndex, RelationIndex, IncidenceIndex$(, $extra)*>
where
VertexIndex: LayoutIndex + LayoutWord<Index = VertexIndex>,
RelationIndex: LayoutIndex + LayoutWord<Index = RelationIndex>,
IncidenceIndex: LayoutIndex + LayoutWord<Index = IncidenceIndex>,
{
type Incidences<'view>
= BcsrChainedRelationIncidences<IncidenceIndex>
where
Self: 'view;
fn relation_incidences(&self, relation: HyperedgeId<RelationIndex>) -> Self::Incidences<'_> {
self.$topology.as_view().relation_incidences(relation)
}
}
impl<VertexIndex, RelationIndex, IncidenceIndex$(, $extra)*> ElementIncidences
for $name<VertexIndex, RelationIndex, IncidenceIndex$(, $extra)*>
where
VertexIndex: LayoutIndex + LayoutWord<Index = VertexIndex>,
RelationIndex: LayoutIndex + LayoutWord<Index = RelationIndex>,
IncidenceIndex: LayoutIndex + LayoutWord<Index = IncidenceIndex>,
{
type Incidences<'view>
= BcsrElementIncidences<'view, IncidenceIndex, VertexIndex, RelationIndex>
where
Self: 'view;
fn element_incidences(&self, element: HyperVertexId<VertexIndex>) -> Self::Incidences<'_> {
self.$topology.as_view().detached_element_incidences(element)
}
}
impl<VertexIndex, RelationIndex, IncidenceIndex$(, $extra)*> RelationIncidenceCount
for $name<VertexIndex, RelationIndex, IncidenceIndex$(, $extra)*>
where
VertexIndex: LayoutIndex + LayoutWord<Index = VertexIndex>,
RelationIndex: LayoutIndex + LayoutWord<Index = RelationIndex>,
IncidenceIndex: LayoutIndex + LayoutWord<Index = IncidenceIndex>,
{
fn relation_incidence_count(&self, relation: HyperedgeId<RelationIndex>) -> usize {
self.$topology.as_view().relation_incidence_count(relation)
}
}
impl<VertexIndex, RelationIndex, IncidenceIndex$(, $extra)*> ElementIncidenceCount
for $name<VertexIndex, RelationIndex, IncidenceIndex$(, $extra)*>
where
VertexIndex: LayoutIndex + LayoutWord<Index = VertexIndex>,
RelationIndex: LayoutIndex + LayoutWord<Index = RelationIndex>,
IncidenceIndex: LayoutIndex + LayoutWord<Index = IncidenceIndex>,
{
fn element_incidence_count(&self, element: HyperVertexId<VertexIndex>) -> usize {
self.$topology.as_view().element_incidence_count(element)
}
}
impl<VertexIndex, RelationIndex, IncidenceIndex$(, $extra)*> HyperedgeParticipants
for $name<VertexIndex, RelationIndex, IncidenceIndex$(, $extra)*>
where
VertexIndex: LayoutIndex + LayoutWord<Index = VertexIndex>,
RelationIndex: LayoutIndex + LayoutWord<Index = RelationIndex>,
IncidenceIndex: LayoutIndex + LayoutWord<Index = IncidenceIndex>,
{
type Participants<'view>
= BcsrChainedParticipants<'view, VertexIndex>
where
Self: 'view;
fn hyperedge_participants(&self, hyperedge: HyperedgeId<RelationIndex>) -> Self::Participants<'_> {
self.$topology.as_view().detached_hyperedge_participants(hyperedge)
}
}
impl<VertexIndex, RelationIndex, IncidenceIndex$(, $extra)*> IncidentHyperedges
for $name<VertexIndex, RelationIndex, IncidenceIndex$(, $extra)*>
where
VertexIndex: LayoutIndex + LayoutWord<Index = VertexIndex>,
RelationIndex: LayoutIndex + LayoutWord<Index = RelationIndex>,
IncidenceIndex: LayoutIndex + LayoutWord<Index = IncidenceIndex>,
{
type IncidentHyperedges<'view>
= BcsrChainedHyperedges<'view, RelationIndex>
where
Self: 'view;
fn incident_hyperedges(&self, vertex: HyperVertexId<VertexIndex>) -> Self::IncidentHyperedges<'_> {
self.$topology.as_view().detached_incident_hyperedges(vertex)
}
}
impl<VertexIndex, RelationIndex, IncidenceIndex$(, $extra)*> DirectedHyperedgeParticipants
for $name<VertexIndex, RelationIndex, IncidenceIndex$(, $extra)*>
where
VertexIndex: LayoutIndex + LayoutWord<Index = VertexIndex>,
RelationIndex: LayoutIndex + LayoutWord<Index = RelationIndex>,
IncidenceIndex: LayoutIndex + LayoutWord<Index = IncidenceIndex>,
{
type SourceParticipants<'view>
= BcsrVertexSlice<'view, VertexIndex>
where
Self: 'view;
type TargetParticipants<'view>
= BcsrVertexSlice<'view, VertexIndex>
where
Self: 'view;
fn source_participants(&self, hyperedge: HyperedgeId<RelationIndex>) -> Self::SourceParticipants<'_> {
self.$topology.as_view().detached_source_participants(hyperedge)
}
fn target_participants(&self, hyperedge: HyperedgeId<RelationIndex>) -> Self::TargetParticipants<'_> {
self.$topology.as_view().detached_target_participants(hyperedge)
}
}
impl<VertexIndex, RelationIndex, IncidenceIndex$(, $extra)*> DirectedHyperedgeIncidences
for $name<VertexIndex, RelationIndex, IncidenceIndex$(, $extra)*>
where
VertexIndex: LayoutIndex + LayoutWord<Index = VertexIndex>,
RelationIndex: LayoutIndex + LayoutWord<Index = RelationIndex>,
IncidenceIndex: LayoutIndex + LayoutWord<Index = IncidenceIndex>,
{
type SourceIncidences<'view>
= BcsrParticipantSlice<IncidenceIndex>
where
Self: 'view;
type TargetIncidences<'view>
= BcsrParticipantSlice<IncidenceIndex>
where
Self: 'view;
fn source_incidences(&self, hyperedge: HyperedgeId<RelationIndex>) -> Self::SourceIncidences<'_> {
self.$topology.as_view().source_incidences(hyperedge)
}
fn target_incidences(&self, hyperedge: HyperedgeId<RelationIndex>) -> Self::TargetIncidences<'_> {
self.$topology.as_view().target_incidences(hyperedge)
}
}
impl<VertexIndex, RelationIndex, IncidenceIndex$(, $extra)*> DirectedVertexHyperedges
for $name<VertexIndex, RelationIndex, IncidenceIndex$(, $extra)*>
where
VertexIndex: LayoutIndex + LayoutWord<Index = VertexIndex>,
RelationIndex: LayoutIndex + LayoutWord<Index = RelationIndex>,
IncidenceIndex: LayoutIndex + LayoutWord<Index = IncidenceIndex>,
{
type OutgoingHyperedges<'view>
= BcsrHyperedgeSlice<'view, RelationIndex>
where
Self: 'view;
type IncomingHyperedges<'view>
= BcsrHyperedgeSlice<'view, RelationIndex>
where
Self: 'view;
fn outgoing_hyperedges(&self, vertex: HyperVertexId<VertexIndex>) -> Self::OutgoingHyperedges<'_> {
self.$topology.as_view().detached_outgoing_hyperedges(vertex)
}
fn incoming_hyperedges(&self, vertex: HyperVertexId<VertexIndex>) -> Self::IncomingHyperedges<'_> {
self.$topology.as_view().detached_incoming_hyperedges(vertex)
}
}
impl<VertexIndex, RelationIndex, IncidenceIndex$(, $extra)*> ElementSuccessors
for $name<VertexIndex, RelationIndex, IncidenceIndex$(, $extra)*>
where
VertexIndex: LayoutIndex + LayoutWord<Index = VertexIndex>,
RelationIndex: LayoutIndex + LayoutWord<Index = RelationIndex>,
IncidenceIndex: LayoutIndex + LayoutWord<Index = IncidenceIndex>,
{
type Successors<'view>
= BcsrSuccessorVertices<'view, RelationIndex, IncidenceIndex, VertexIndex>
where
Self: 'view;
fn element_successors(&self, element: HyperVertexId<VertexIndex>) -> Self::Successors<'_> {
self.$topology.as_view().detached_element_successors(element)
}
}
impl<VertexIndex, RelationIndex, IncidenceIndex$(, $extra)*> ElementPredecessors
for $name<VertexIndex, RelationIndex, IncidenceIndex$(, $extra)*>
where
VertexIndex: LayoutIndex + LayoutWord<Index = VertexIndex>,
RelationIndex: LayoutIndex + LayoutWord<Index = RelationIndex>,
IncidenceIndex: LayoutIndex + LayoutWord<Index = IncidenceIndex>,
{
type Predecessors<'view>
= BcsrPredecessorVertices<'view, RelationIndex, IncidenceIndex, VertexIndex>
where
Self: 'view;
fn element_predecessors(&self, element: HyperVertexId<VertexIndex>) -> Self::Predecessors<'_> {
self.$topology.as_view().detached_element_predecessors(element)
}
}
impl<VertexIndex, RelationIndex, IncidenceIndex$(, $extra)*> CanonicalElementIdentity
for $name<VertexIndex, RelationIndex, IncidenceIndex$(, $extra)*>
where
VertexIndex: LayoutIndex + LayoutWord<Index = VertexIndex>,
RelationIndex: LayoutIndex + LayoutWord<Index = RelationIndex>,
IncidenceIndex: LayoutIndex + LayoutWord<Index = IncidenceIndex>,
{
type CanonicalElementId = HyperVertexId<VertexIndex>;
fn canonical_element_id(&self, element: HyperVertexId<VertexIndex>) -> Self::CanonicalElementId {
element
}
}
impl<VertexIndex, RelationIndex, IncidenceIndex$(, $extra)*> LocalElementIdentity
for $name<VertexIndex, RelationIndex, IncidenceIndex$(, $extra)*>
where
VertexIndex: LayoutIndex + LayoutWord<Index = VertexIndex>,
RelationIndex: LayoutIndex + LayoutWord<Index = RelationIndex>,
IncidenceIndex: LayoutIndex + LayoutWord<Index = IncidenceIndex>,
{
fn local_element_id(&self, canonical: Self::CanonicalElementId) -> Option<Self::ElementId> {
self.contains_element(canonical).then_some(canonical)
}
}
impl<VertexIndex, RelationIndex, IncidenceIndex$(, $extra)*> CanonicalRelationIdentity
for $name<VertexIndex, RelationIndex, IncidenceIndex$(, $extra)*>
where
VertexIndex: LayoutIndex + LayoutWord<Index = VertexIndex>,
RelationIndex: LayoutIndex + LayoutWord<Index = RelationIndex>,
IncidenceIndex: LayoutIndex + LayoutWord<Index = IncidenceIndex>,
{
type CanonicalRelationId = HyperedgeId<RelationIndex>;
fn canonical_relation_id(&self, relation: HyperedgeId<RelationIndex>) -> Self::CanonicalRelationId {
relation
}
}
impl<VertexIndex, RelationIndex, IncidenceIndex$(, $extra)*> LocalRelationIdentity
for $name<VertexIndex, RelationIndex, IncidenceIndex$(, $extra)*>
where
VertexIndex: LayoutIndex + LayoutWord<Index = VertexIndex>,
RelationIndex: LayoutIndex + LayoutWord<Index = RelationIndex>,
IncidenceIndex: LayoutIndex + LayoutWord<Index = IncidenceIndex>,
{
fn local_relation_id(&self, canonical: Self::CanonicalRelationId) -> Option<Self::RelationId> {
self.contains_relation(canonical).then_some(canonical)
}
}
impl<VertexIndex, RelationIndex, IncidenceIndex$(, $extra)*> CanonicalIncidenceIdentity
for $name<VertexIndex, RelationIndex, IncidenceIndex$(, $extra)*>
where
VertexIndex: LayoutIndex + LayoutWord<Index = VertexIndex>,
RelationIndex: LayoutIndex + LayoutWord<Index = RelationIndex>,
IncidenceIndex: LayoutIndex + LayoutWord<Index = IncidenceIndex>,
{
type CanonicalIncidenceId = HyperParticipantId<IncidenceIndex>;
fn canonical_incidence_id(
&self,
incidence: HyperParticipantId<IncidenceIndex>,
) -> Self::CanonicalIncidenceId {
incidence
}
}
impl<VertexIndex, RelationIndex, IncidenceIndex$(, $extra)*> LocalIncidenceIdentity
for $name<VertexIndex, RelationIndex, IncidenceIndex$(, $extra)*>
where
VertexIndex: LayoutIndex + LayoutWord<Index = VertexIndex>,
RelationIndex: LayoutIndex + LayoutWord<Index = RelationIndex>,
IncidenceIndex: LayoutIndex + LayoutWord<Index = IncidenceIndex>,
{
fn local_incidence_id(
&self,
canonical: Self::CanonicalIncidenceId,
) -> Option<Self::IncidenceId> {
self.contains_incidence(canonical).then_some(canonical)
}
}
};
}
impl_topology_for!(FrozenHypergraph, [], topology);
impl_topology_for!(FrozenWeightedHypergraph, [EW, RW, IW], topology);
impl<VertexIndex, RelationIndex, IncidenceIndex, EW: Copy, RW, IW> ElementWeight
for FrozenWeightedHypergraph<VertexIndex, RelationIndex, IncidenceIndex, EW, RW, IW>
where
VertexIndex: LayoutIndex + LayoutWord<Index = VertexIndex>,
RelationIndex: LayoutIndex + LayoutWord<Index = RelationIndex>,
IncidenceIndex: LayoutIndex + LayoutWord<Index = IncidenceIndex>,
{
type Weight = EW;
fn element_weight(&self, element: HyperVertexId<VertexIndex>) -> Self::Weight {
self.element_weights[vertex_slot(element)]
}
}
impl<VertexIndex, RelationIndex, IncidenceIndex, EW, RW: Copy, IW> RelationWeight
for FrozenWeightedHypergraph<VertexIndex, RelationIndex, IncidenceIndex, EW, RW, IW>
where
VertexIndex: LayoutIndex + LayoutWord<Index = VertexIndex>,
RelationIndex: LayoutIndex + LayoutWord<Index = RelationIndex>,
IncidenceIndex: LayoutIndex + LayoutWord<Index = IncidenceIndex>,
{
type Weight = RW;
fn relation_weight(&self, relation: HyperedgeId<RelationIndex>) -> Self::Weight {
self.relation_weights[hyperedge_slot(relation)]
}
}
impl<VertexIndex, RelationIndex, IncidenceIndex, EW, RW, IW: Copy> IncidenceWeight
for FrozenWeightedHypergraph<VertexIndex, RelationIndex, IncidenceIndex, EW, RW, IW>
where
VertexIndex: LayoutIndex + LayoutWord<Index = VertexIndex>,
RelationIndex: LayoutIndex + LayoutWord<Index = RelationIndex>,
IncidenceIndex: LayoutIndex + LayoutWord<Index = IncidenceIndex>,
{
type Weight = IW;
fn incidence_weight(&self, incidence: HyperParticipantId<IncidenceIndex>) -> Self::Weight {
self.incidence_weights[participant_slot(incidence)]
}
}
pub fn export_bcsr_snapshot<VertexIndex, RelationIndex, IncidenceIndex>(
graph: &FrozenHypergraph<VertexIndex, RelationIndex, IncidenceIndex>,
) -> Result<Vec<u8>, HyperBuildError<VertexIndex, RelationIndex, IncidenceIndex>>
where
VertexIndex: LayoutIndex + BcsrSnapshotIndex,
RelationIndex: LayoutIndex + BcsrSnapshotIndex,
IncidenceIndex: LayoutIndex + BcsrSnapshotIndex,
{
export_topology_snapshot(&graph.topology)
}
pub fn export_weighted_bcsr_snapshot<VertexIndex, RelationIndex, IncidenceIndex, EW, RW, IW>(
graph: &FrozenWeightedHypergraph<VertexIndex, RelationIndex, IncidenceIndex, EW, RW, IW>,
) -> Result<Vec<u8>, HyperBuildError<VertexIndex, RelationIndex, IncidenceIndex>>
where
VertexIndex: LayoutIndex + BcsrSnapshotIndex,
RelationIndex: LayoutIndex + BcsrSnapshotIndex,
IncidenceIndex: LayoutIndex + BcsrSnapshotIndex,
{
export_topology_snapshot(&graph.topology)
}
#[cfg(feature = "build-property-arrow")]
pub fn export_bcsr_snapshot_with_properties<VertexIndex, RelationIndex, IncidenceIndex, Id>(
graph: &FrozenHypergraph<VertexIndex, RelationIndex, IncidenceIndex>,
layers: HyperPropertyLayers<'_, Id, VertexIndex, RelationIndex, IncidenceIndex>,
) -> Result<Vec<u8>, HyperBuildError<VertexIndex, RelationIndex, IncidenceIndex>>
where
VertexIndex: LayoutIndex + BcsrSnapshotIndex + oxgraph_property::PropertyIndex,
RelationIndex: LayoutIndex + BcsrSnapshotIndex + oxgraph_property::PropertyIndex,
IncidenceIndex: LayoutIndex + BcsrSnapshotIndex + PropertySnapshotMetaWord,
Id: Clone + Copy + Into<u64> + Ord + TryInto<IncidenceIndex>,
{
let property = encode_hyper_properties(&graph.topology, layers)?;
export_topology_property_snapshot(&graph.topology, &property)
}
#[cfg(feature = "build-property-arrow")]
pub fn export_weighted_bcsr_snapshot_with_properties<
VertexIndex,
RelationIndex,
IncidenceIndex,
EW,
RW,
IW,
Id,
>(
graph: &FrozenWeightedHypergraph<VertexIndex, RelationIndex, IncidenceIndex, EW, RW, IW>,
layers: HyperPropertyLayers<'_, Id, VertexIndex, RelationIndex, IncidenceIndex>,
) -> Result<Vec<u8>, HyperBuildError<VertexIndex, RelationIndex, IncidenceIndex>>
where
VertexIndex: LayoutIndex + BcsrSnapshotIndex + oxgraph_property::PropertyIndex,
RelationIndex: LayoutIndex + BcsrSnapshotIndex + oxgraph_property::PropertyIndex,
IncidenceIndex: LayoutIndex + BcsrSnapshotIndex + PropertySnapshotMetaWord,
Id: Clone + Copy + Into<u64> + Ord + TryInto<IncidenceIndex>,
{
let property = encode_hyper_properties(&graph.topology, layers)?;
export_topology_property_snapshot(&graph.topology, &property)
}
#[derive(Clone, Debug, Default)]
struct HyperedgeRecord<VertexIndex> {
sources: Vec<VertexIndex>,
targets: Vec<VertexIndex>,
}
#[derive(Clone, Debug, Default)]
struct NormalizedHyperedgeRecord<VertexIndex, IW> {
sources: Vec<(VertexIndex, IW)>,
targets: Vec<(VertexIndex, IW)>,
}
fn build_topology<VertexIndex, RelationIndex, IncidenceIndex, IW>(
vertex_count: usize,
records: &[NormalizedHyperedgeRecord<VertexIndex, IW>],
) -> BuiltTopologyResult<VertexIndex, RelationIndex, IncidenceIndex, IW>
where
VertexIndex: LayoutIndex,
RelationIndex: LayoutIndex,
IncidenceIndex: LayoutIndex,
IW: Clone,
{
let participant_count = records
.iter()
.map(|record| record.sources.len() + record.targets.len())
.sum();
let mut head_offsets = Vec::with_capacity(records.len() + 1);
let mut tail_offsets = Vec::with_capacity(records.len() + 1);
let mut head_participants = Vec::new();
let mut tail_participants = Vec::new();
let mut incidence_weights = Vec::with_capacity(participant_count);
head_offsets.push(index_from_usize(0).map_err(map_offset_overflow)?);
tail_offsets.push(index_from_usize(0).map_err(map_offset_overflow)?);
for record in records {
for (vertex, weight) in &record.sources {
head_participants.push(*vertex);
incidence_weights.push(weight.clone());
}
head_offsets.push(index_from_usize(head_participants.len()).map_err(map_offset_overflow)?);
}
for record in records {
for (vertex, weight) in &record.targets {
tail_participants.push(*vertex);
incidence_weights.push(weight.clone());
}
tail_offsets.push(index_from_usize(tail_participants.len()).map_err(map_offset_overflow)?);
}
let (vertex_outgoing_offsets, vertex_outgoing_hyperedges) =
build_vertex_relation_index(vertex_count, records, HyperParticipantRole::Source)?;
let (vertex_incoming_offsets, vertex_incoming_hyperedges) =
build_vertex_relation_index(vertex_count, records, HyperParticipantRole::Target)?;
Ok((
FrozenTopology {
vertex_count,
head_offsets: head_offsets.into_boxed_slice(),
head_participants: head_participants.into_boxed_slice(),
tail_offsets: tail_offsets.into_boxed_slice(),
tail_participants: tail_participants.into_boxed_slice(),
vertex_outgoing_offsets: vertex_outgoing_offsets.into_boxed_slice(),
vertex_outgoing_hyperedges: vertex_outgoing_hyperedges.into_boxed_slice(),
vertex_incoming_offsets: vertex_incoming_offsets.into_boxed_slice(),
vertex_incoming_hyperedges: vertex_incoming_hyperedges.into_boxed_slice(),
},
incidence_weights,
))
}
fn build_vertex_relation_index<VertexIndex, RelationIndex, IncidenceIndex, IW>(
vertex_count: usize,
records: &[NormalizedHyperedgeRecord<VertexIndex, IW>],
role: HyperParticipantRole,
) -> VertexRelationIndexResult<VertexIndex, RelationIndex, IncidenceIndex>
where
VertexIndex: LayoutIndex,
RelationIndex: LayoutIndex,
IncidenceIndex: LayoutIndex,
{
let mut buckets = vec![Vec::<RelationIndex>::new(); vertex_count];
for (relation, record) in records.iter().enumerate() {
let relation_id = index_from_usize(relation).map_err(map_offset_overflow)?;
let participants = match role {
HyperParticipantRole::Source => &record.sources,
HyperParticipantRole::Target => &record.targets,
};
for (vertex, _weight) in participants {
let slot = vertex
.to_usize()
.ok_or(HyperBuildError::IdOverflow { value: usize::MAX })?;
buckets[slot].push(relation_id);
}
}
build_offset_index::<IncidenceIndex, RelationIndex>(buckets).map_err(map_offset_overflow)
}
fn ensure_vertices<VertexIndex, RelationIndex, IncidenceIndex, I>(
vertex_count: usize,
participants: I,
) -> Result<(), HyperBuildError<VertexIndex, RelationIndex, IncidenceIndex>>
where
VertexIndex: LayoutIndex,
RelationIndex: LayoutIndex,
IncidenceIndex: LayoutIndex,
I: IntoIterator<Item = HyperVertexId<VertexIndex>>,
{
for vertex in participants {
vertex_index_checked(vertex_count, vertex)?;
}
Ok(())
}
fn ensure_unique_participants<VertexIndex, RelationIndex, IncidenceIndex, I>(
participants: I,
role: HyperParticipantRole,
) -> Result<(), HyperBuildError<VertexIndex, RelationIndex, IncidenceIndex>>
where
VertexIndex: LayoutIndex,
RelationIndex: LayoutIndex,
IncidenceIndex: LayoutIndex,
I: IntoIterator<Item = HyperVertexId<VertexIndex>>,
{
let mut sorted: Vec<VertexIndex> = participants.into_iter().map(HyperVertexId::get).collect();
sorted.sort_unstable();
for pair in sorted.windows(2) {
if pair[0] == pair[1] {
return Err(HyperBuildError::DuplicateParticipant {
vertex: HyperVertexId::new(pair[0]),
role,
});
}
}
Ok(())
}
fn vertex_index_checked<VertexIndex, RelationIndex, IncidenceIndex>(
vertex_count: usize,
vertex: HyperVertexId<VertexIndex>,
) -> Result<usize, HyperBuildError<VertexIndex, RelationIndex, IncidenceIndex>>
where
VertexIndex: LayoutIndex,
RelationIndex: LayoutIndex,
IncidenceIndex: LayoutIndex,
{
id_to_slot::<VertexIndex>(vertex.get(), vertex_count)
.map_err(|_: IdOutOfBounds| HyperBuildError::InvalidVertex { vertex })
}
fn hyperedge_index_checked<VertexIndex, RelationIndex, IncidenceIndex>(
hyperedge_count: usize,
hyperedge: HyperedgeId<RelationIndex>,
) -> Result<usize, HyperBuildError<VertexIndex, RelationIndex, IncidenceIndex>>
where
VertexIndex: LayoutIndex,
RelationIndex: LayoutIndex,
IncidenceIndex: LayoutIndex,
{
id_to_slot::<RelationIndex>(hyperedge.get(), hyperedge_count)
.map_err(|_: IdOutOfBounds| HyperBuildError::InvalidHyperedge { hyperedge })
}
fn export_topology_snapshot<VertexIndex, RelationIndex, IncidenceIndex>(
topology: &FrozenTopology<VertexIndex, RelationIndex, IncidenceIndex>,
) -> Result<Vec<u8>, HyperBuildError<VertexIndex, RelationIndex, IncidenceIndex>>
where
VertexIndex: LayoutIndex + BcsrSnapshotIndex,
RelationIndex: LayoutIndex + BcsrSnapshotIndex,
IncidenceIndex: LayoutIndex + BcsrSnapshotIndex,
{
let mut writer = SnapshotWriter::new(8, crc32c_append)?;
add_topology_sections::<VertexIndex, RelationIndex, IncidenceIndex>(&mut writer, topology)?;
writer.finish().map_err(HyperBuildError::from)
}
fn add_topology_sections<VertexIndex, RelationIndex, IncidenceIndex>(
writer: &mut SnapshotWriter,
topology: &FrozenTopology<VertexIndex, RelationIndex, IncidenceIndex>,
) -> Result<(), HyperBuildError<VertexIndex, RelationIndex, IncidenceIndex>>
where
VertexIndex: LayoutIndex + BcsrSnapshotIndex,
RelationIndex: LayoutIndex + BcsrSnapshotIndex,
IncidenceIndex: LayoutIndex + BcsrSnapshotIndex,
{
writer.section_widths(
IncidenceIndex::HEAD_OFFSETS_KIND,
IncidenceIndex::SECTION_VERSION,
&topology.head_offsets,
)?;
writer.section_widths(
VertexIndex::HEAD_PARTICIPANTS_KIND,
VertexIndex::SECTION_VERSION,
&topology.head_participants,
)?;
writer.section_widths(
IncidenceIndex::TAIL_OFFSETS_KIND,
IncidenceIndex::SECTION_VERSION,
&topology.tail_offsets,
)?;
writer.section_widths(
VertexIndex::TAIL_PARTICIPANTS_KIND,
VertexIndex::SECTION_VERSION,
&topology.tail_participants,
)?;
writer.section_widths(
IncidenceIndex::VERTEX_OUTGOING_OFFSETS_KIND,
IncidenceIndex::SECTION_VERSION,
&topology.vertex_outgoing_offsets,
)?;
writer.section_widths(
RelationIndex::VERTEX_OUTGOING_HYPEREDGES_KIND,
RelationIndex::SECTION_VERSION,
&topology.vertex_outgoing_hyperedges,
)?;
writer.section_widths(
IncidenceIndex::VERTEX_INCOMING_OFFSETS_KIND,
IncidenceIndex::SECTION_VERSION,
&topology.vertex_incoming_offsets,
)?;
writer.section_widths(
RelationIndex::VERTEX_INCOMING_HYPEREDGES_KIND,
RelationIndex::SECTION_VERSION,
&topology.vertex_incoming_hyperedges,
)?;
Ok(())
}
#[cfg(feature = "build-property-arrow")]
fn encode_hyper_properties<VertexIndex, RelationIndex, IncidenceIndex, Id>(
topology: &FrozenTopology<VertexIndex, RelationIndex, IncidenceIndex>,
layers: HyperPropertyLayers<'_, Id, VertexIndex, RelationIndex, IncidenceIndex>,
) -> Result<EncodedPropertySnapshot, HyperBuildError<VertexIndex, RelationIndex, IncidenceIndex>>
where
VertexIndex: LayoutIndex + BcsrSnapshotIndex + oxgraph_property::PropertyIndex,
RelationIndex: LayoutIndex + BcsrSnapshotIndex + oxgraph_property::PropertyIndex,
IncidenceIndex: LayoutIndex + BcsrSnapshotIndex + PropertySnapshotMetaWord,
Id: Clone + Copy + Into<u64> + Ord + TryInto<IncidenceIndex>,
{
validate_property_lengths(topology, layers.element, IdFamily::Element)?;
validate_property_lengths(topology, layers.relation, IdFamily::Relation)?;
validate_property_lengths(topology, layers.incidence, IdFamily::Incidence)?;
let incidence_map = bcsr_local_incidence_to_canonical(topology)?;
let incidence = layers
.incidence
.iter()
.map(|layer| rekey_layer_to_local(layer, &incidence_map).map_err(HyperBuildError::from))
.collect::<Result<Vec<PropertyLayer<Id, IncidenceIndex>>, _>>()?;
Ok(encode_hyper_property_snapshot::<
IncidenceIndex,
Id,
VertexIndex,
RelationIndex,
IncidenceIndex,
>(HyperPropertyLayers {
element: layers.element,
relation: layers.relation,
incidence: &incidence,
})?)
}
#[cfg(feature = "build-property-arrow")]
fn validate_property_lengths<Id, I, VertexIndex, RelationIndex, IncidenceIndex>(
topology: &FrozenTopology<VertexIndex, RelationIndex, IncidenceIndex>,
layers: &[PropertyLayer<Id, I>],
id_family: IdFamily,
) -> Result<(), HyperBuildError<VertexIndex, RelationIndex, IncidenceIndex>>
where
I: oxgraph_property::PropertyIndex,
VertexIndex: LayoutIndex,
RelationIndex: LayoutIndex,
IncidenceIndex: LayoutIndex,
{
let required = match id_family {
IdFamily::Element => topology.vertex_count,
IdFamily::Relation => topology.relation_count(),
IdFamily::Incidence => topology.total_participants(),
unsupported => {
return Err(HyperBuildError::UnsupportedPropertyFamily {
id_family: unsupported,
});
}
};
oxgraph_property::export::validate_layer_lengths(layers, id_family, required).map_err(|error| {
HyperBuildError::PropertyLayerTooShort {
id_family: error.id_family,
required: error.required,
actual: error.actual,
}
})
}
#[cfg(feature = "build-property-arrow")]
fn export_topology_property_snapshot<VertexIndex, RelationIndex, IncidenceIndex>(
topology: &FrozenTopology<VertexIndex, RelationIndex, IncidenceIndex>,
property: &EncodedPropertySnapshot,
) -> Result<Vec<u8>, HyperBuildError<VertexIndex, RelationIndex, IncidenceIndex>>
where
VertexIndex: LayoutIndex + BcsrSnapshotIndex,
RelationIndex: LayoutIndex + BcsrSnapshotIndex,
IncidenceIndex: LayoutIndex + BcsrSnapshotIndex + PropertySnapshotMetaWord,
{
let incidence_map = bcsr_local_incidence_to_canonical(topology)?;
let identity_modes = [
IdentityModeRecord::<IncidenceIndex>::local_equals_canonical(
IdFamily::Element,
topology.vertex_count,
)?,
IdentityModeRecord::<IncidenceIndex>::local_equals_canonical(
IdFamily::Relation,
topology.relation_count(),
)?,
IdentityModeRecord::<IncidenceIndex>::explicit_map(
IdFamily::Incidence,
incidence_map.len(),
)?,
];
let mut writer = SnapshotWriter::new(12, crc32c_append)?;
add_topology_sections::<VertexIndex, RelationIndex, IncidenceIndex>(&mut writer, topology)?;
oxgraph_property::export::append_identity_and_property_sections(
&mut writer,
&identity_modes,
IncidenceIndex::INCIDENCE_IDENTITY_MAP_KIND,
&incidence_map,
property,
)?;
writer.finish().map_err(HyperBuildError::from)
}
#[cfg(feature = "build-property-arrow")]
fn bcsr_local_incidence_to_canonical<VertexIndex, RelationIndex, IncidenceIndex>(
topology: &FrozenTopology<VertexIndex, RelationIndex, IncidenceIndex>,
) -> Result<Vec<IncidenceIndex>, HyperBuildError<VertexIndex, RelationIndex, IncidenceIndex>>
where
VertexIndex: LayoutIndex,
RelationIndex: LayoutIndex,
IncidenceIndex: LayoutIndex,
{
let total = topology.total_participants();
let mut map = Vec::with_capacity(total);
for incidence in 0..total {
map.push(index_from_usize(incidence).map_err(map_offset_overflow)?);
}
Ok(map)
}
#[cfg(test)]
mod tests {
#[cfg(feature = "build-property-arrow")]
use alloc::sync::Arc;
use alloc::vec;
#[cfg(feature = "build-property-arrow")]
use core::error::Error;
#[cfg(feature = "build-property-arrow")]
use arrow_array::Int32Array;
#[cfg(feature = "build-property-arrow")]
use arrow_schema::{DataType, Field};
#[cfg(feature = "build-property-arrow")]
use oxgraph_property::{
HyperPropertyLayers, IdFamily, LayerId, LayerRole, PropertyLayer, PropertyLayerDescriptor,
StorageMode, validate_identity_snapshot, validate_property_snapshot,
};
#[cfg(feature = "build-property-arrow")]
use oxgraph_snapshot::Snapshot;
use super::*;
#[cfg(feature = "build-property-arrow")]
use crate::{BcsrSnapshotHypergraph, BcsrValidation};
#[test]
fn freeze_preserves_generic_weights() -> Result<(), HyperBuildError<u32, u32, u32>> {
let mut builder = WeightedHypergraphBuilder::<u32, u32, u32, i32, u16, i8>::new();
let a = builder.add_vertex(1_i32)?;
let b = builder.add_vertex(2_i32)?;
let edge = builder.add_hyperedge(&[(a, 3_i8)], &[(b, 4_i8)], 2_u16)?;
builder.set_element_weight(a, 9_i32)?;
builder.set_relation_weight(edge, 7_u16)?;
builder.set_source_incidence_weight(edge, 0, 5_i8)?;
let frozen = builder.freeze()?;
assert_eq!(frozen.element_weight(a), 9_i32);
assert_eq!(frozen.relation_weight(edge), 7_u16);
assert_eq!(frozen.incidence_weight(HyperParticipantId::new(0)), 5_i8);
assert_eq!(frozen.element_successors(a).collect::<Vec<_>>(), vec![b]);
Ok(())
}
#[test]
fn mixed_width_builder_freezes() -> Result<(), HyperBuildError<u32, u32, u64>> {
let mut builder = HypergraphBuilder::<u32, u32, u64>::new();
let a = builder.add_vertex()?;
let b = builder.add_vertex()?;
let edge = builder.add_hyperedge(&[a], &[b])?;
let frozen = builder.freeze()?;
assert_eq!(frozen.relation_index(edge), 0);
assert_eq!(frozen.element_successors(a).collect::<Vec<_>>(), vec![b]);
Ok(())
}
#[cfg(feature = "build-property-arrow")]
#[test]
fn snapshot_includes_identity_and_property_sections() -> Result<(), Box<dyn Error>> {
let mut builder = HypergraphBuilder::<u32, u32, u32>::new();
let a = builder.add_vertex()?;
let b = builder.add_vertex()?;
builder.add_hyperedge(&[a], &[b])?;
let descriptor = PropertyLayerDescriptor::<u32, u32>::try_new(
LayerId(1_u32),
"vertex_marker",
IdFamily::Element,
LayerRole::Property,
StorageMode::Dense,
Field::new("vertex_marker", DataType::Int32, false),
)?;
let element_layers = [PropertyLayer::try_new_dense(
descriptor,
Arc::new(Int32Array::from(vec![1_i32, 2_i32])),
)?];
let frozen = builder.freeze()?;
let bytes = export_bcsr_snapshot_with_properties(
&frozen,
HyperPropertyLayers {
element: &element_layers,
relation: &[],
incidence: &[],
},
)?;
let snapshot = Snapshot::open(&bytes)?;
let _opened = BcsrSnapshotHypergraph::<u32, u32, u32>::from_snapshot_with(
&snapshot,
BcsrValidation::Strict,
)?;
assert_eq!(
validate_identity_snapshot::<u32>(&snapshot)?.records.len(),
3
);
assert_eq!(validate_property_snapshot::<u32>(&snapshot)?.layer_count, 1);
Ok(())
}
#[test]
fn build_and_view_incidence_numbering_match()
-> Result<(), alloc::boxed::Box<dyn core::error::Error>> {
use oxgraph_hyper::{IncidenceElement, IncidenceRelation, RelationIncidences};
use oxgraph_snapshot::Snapshot;
use crate::{BcsrParticipantId, BcsrSnapshotHypergraph};
let mut builder = HypergraphBuilder::<u32, u32, u32>::new();
let a = builder.add_vertex()?;
let b = builder.add_vertex()?;
let c = builder.add_vertex()?;
let e0 = builder.add_hyperedge(&[a, b], &[c])?;
let e1 = builder.add_hyperedge(&[a], &[b, c])?;
let frozen = builder.freeze()?;
let bytes = export_bcsr_snapshot(&frozen)?;
let snapshot = Snapshot::open(&bytes)?;
let view = BcsrSnapshotHypergraph::<u32, u32, u32>::from_snapshot(&snapshot)?;
for hyperedge in [e0, e1] {
let build_ids: Vec<BcsrParticipantId<u32>> =
RelationIncidences::relation_incidences(&frozen, hyperedge).collect();
let view_ids: Vec<BcsrParticipantId<u32>> =
RelationIncidences::relation_incidences(&view, hyperedge).collect();
assert_eq!(
build_ids, view_ids,
"incidence ID sequence diverged for {hyperedge:?}"
);
for incidence in build_ids {
assert_eq!(
IncidenceElement::incidence_element(&frozen, incidence),
IncidenceElement::incidence_element(&view, incidence)
);
assert_eq!(
IncidenceRelation::incidence_relation(&frozen, incidence),
IncidenceRelation::incidence_relation(&view, incidence)
);
}
}
Ok(())
}
}