Skip to content

Commit cac5907

Browse files
committed
wip: blasgraph semiring algebra — types, descriptor, mod (agent writing remaining files)
https://claude.ai/code/session_01Mcj8GxEtzmVba6RmuT7AjD
1 parent 730754a commit cac5907

3 files changed

Lines changed: 898 additions & 0 deletions

File tree

Lines changed: 252 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,252 @@
1+
// SPDX-License-Identifier: Apache-2.0
2+
// SPDX-FileCopyrightText: Copyright The Lance Authors
3+
4+
//! # GraphBLAS Descriptors
5+
//!
6+
//! Descriptors control the behaviour of GraphBLAS operations, such as whether
7+
//! to transpose inputs, complement masks, or replace outputs.
8+
9+
use std::fmt;
10+
11+
/// Descriptor field identifiers.
12+
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
13+
pub enum DescField {
14+
/// Controls the output container.
15+
Output,
16+
/// Controls the mask.
17+
Mask,
18+
/// Controls the first input.
19+
Input0,
20+
/// Controls the second input.
21+
Input1,
22+
}
23+
24+
/// Descriptor value for a given field.
25+
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
26+
pub enum DescValue {
27+
/// Default behaviour (no modification).
28+
Default,
29+
/// Transpose the matrix operand.
30+
Transpose,
31+
/// Complement the mask (use structural complement).
32+
Complement,
33+
/// Replace mode: clear the output before writing results.
34+
Replace,
35+
/// Structure-only mask: ignore values, use pattern only.
36+
Structure,
37+
}
38+
39+
/// A descriptor that controls GraphBLAS operation behaviour.
40+
///
41+
/// Each field can be set independently. Unset fields default to [`DescValue::Default`].
42+
#[derive(Clone, PartialEq, Eq)]
43+
pub struct Descriptor {
44+
/// Output field setting.
45+
pub output: DescValue,
46+
/// Mask field setting.
47+
pub mask: DescValue,
48+
/// First input field setting.
49+
pub inp0: DescValue,
50+
/// Second input field setting.
51+
pub inp1: DescValue,
52+
}
53+
54+
impl Default for Descriptor {
55+
fn default() -> Self {
56+
Self {
57+
output: DescValue::Default,
58+
mask: DescValue::Default,
59+
inp0: DescValue::Default,
60+
inp1: DescValue::Default,
61+
}
62+
}
63+
}
64+
65+
impl fmt::Debug for Descriptor {
66+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
67+
f.debug_struct("Descriptor")
68+
.field("output", &self.output)
69+
.field("mask", &self.mask)
70+
.field("inp0", &self.inp0)
71+
.field("inp1", &self.inp1)
72+
.finish()
73+
}
74+
}
75+
76+
impl Descriptor {
77+
/// Create a new default descriptor.
78+
pub fn new() -> Self {
79+
Self::default()
80+
}
81+
82+
/// Set a descriptor field.
83+
pub fn set(&mut self, field: DescField, value: DescValue) -> &mut Self {
84+
match field {
85+
DescField::Output => self.output = value,
86+
DescField::Mask => self.mask = value,
87+
DescField::Input0 => self.inp0 = value,
88+
DescField::Input1 => self.inp1 = value,
89+
}
90+
self
91+
}
92+
93+
/// Get the value of a descriptor field.
94+
pub fn get(&self, field: DescField) -> DescValue {
95+
match field {
96+
DescField::Output => self.output,
97+
DescField::Mask => self.mask,
98+
DescField::Input0 => self.inp0,
99+
DescField::Input1 => self.inp1,
100+
}
101+
}
102+
103+
/// Whether the first input should be transposed.
104+
pub fn transpose_input0(&self) -> bool {
105+
self.inp0 == DescValue::Transpose
106+
}
107+
108+
/// Whether the second input should be transposed.
109+
pub fn transpose_input1(&self) -> bool {
110+
self.inp1 == DescValue::Transpose
111+
}
112+
113+
/// Whether the mask should be complemented.
114+
pub fn complement_mask(&self) -> bool {
115+
self.mask == DescValue::Complement
116+
}
117+
118+
/// Whether the output should be replaced (cleared before writing).
119+
pub fn replace_output(&self) -> bool {
120+
self.output == DescValue::Replace
121+
}
122+
123+
/// Whether the mask is structure-only.
124+
pub fn structure_mask(&self) -> bool {
125+
self.mask == DescValue::Structure
126+
}
127+
}
128+
129+
/// Pre-built descriptor constants.
130+
pub mod GrBDesc {
131+
use super::*;
132+
133+
/// Default descriptor — no modifications.
134+
pub fn default() -> Descriptor {
135+
Descriptor::default()
136+
}
137+
138+
/// Transpose the first input.
139+
pub fn t0() -> Descriptor {
140+
let mut d = Descriptor::default();
141+
d.set(DescField::Input0, DescValue::Transpose);
142+
d
143+
}
144+
145+
/// Transpose the second input.
146+
pub fn t1() -> Descriptor {
147+
let mut d = Descriptor::default();
148+
d.set(DescField::Input1, DescValue::Transpose);
149+
d
150+
}
151+
152+
/// Transpose both inputs.
153+
pub fn t0t1() -> Descriptor {
154+
let mut d = Descriptor::default();
155+
d.set(DescField::Input0, DescValue::Transpose);
156+
d.set(DescField::Input1, DescValue::Transpose);
157+
d
158+
}
159+
160+
/// Complement the mask.
161+
pub fn comp() -> Descriptor {
162+
let mut d = Descriptor::default();
163+
d.set(DescField::Mask, DescValue::Complement);
164+
d
165+
}
166+
167+
/// Replace the output.
168+
pub fn replace() -> Descriptor {
169+
let mut d = Descriptor::default();
170+
d.set(DescField::Output, DescValue::Replace);
171+
d
172+
}
173+
174+
/// Replace the output and complement the mask.
175+
pub fn replace_comp() -> Descriptor {
176+
let mut d = Descriptor::default();
177+
d.set(DescField::Output, DescValue::Replace);
178+
d.set(DescField::Mask, DescValue::Complement);
179+
d
180+
}
181+
182+
/// Structure-only mask.
183+
pub fn structure() -> Descriptor {
184+
let mut d = Descriptor::default();
185+
d.set(DescField::Mask, DescValue::Structure);
186+
d
187+
}
188+
}
189+
190+
#[cfg(test)]
191+
mod tests {
192+
use super::*;
193+
194+
#[test]
195+
fn test_default_descriptor() {
196+
let d = Descriptor::default();
197+
assert_eq!(d.get(DescField::Output), DescValue::Default);
198+
assert_eq!(d.get(DescField::Mask), DescValue::Default);
199+
assert_eq!(d.get(DescField::Input0), DescValue::Default);
200+
assert_eq!(d.get(DescField::Input1), DescValue::Default);
201+
assert!(!d.transpose_input0());
202+
assert!(!d.transpose_input1());
203+
assert!(!d.complement_mask());
204+
assert!(!d.replace_output());
205+
}
206+
207+
#[test]
208+
fn test_set_fields() {
209+
let mut d = Descriptor::new();
210+
d.set(DescField::Input0, DescValue::Transpose);
211+
assert!(d.transpose_input0());
212+
assert!(!d.transpose_input1());
213+
214+
d.set(DescField::Mask, DescValue::Complement);
215+
assert!(d.complement_mask());
216+
}
217+
218+
#[test]
219+
fn test_presets() {
220+
let t0 = GrBDesc::t0();
221+
assert!(t0.transpose_input0());
222+
assert!(!t0.transpose_input1());
223+
224+
let t1 = GrBDesc::t1();
225+
assert!(!t1.transpose_input0());
226+
assert!(t1.transpose_input1());
227+
228+
let t0t1 = GrBDesc::t0t1();
229+
assert!(t0t1.transpose_input0());
230+
assert!(t0t1.transpose_input1());
231+
232+
let comp = GrBDesc::comp();
233+
assert!(comp.complement_mask());
234+
235+
let repl = GrBDesc::replace();
236+
assert!(repl.replace_output());
237+
238+
let rc = GrBDesc::replace_comp();
239+
assert!(rc.replace_output());
240+
assert!(rc.complement_mask());
241+
242+
let s = GrBDesc::structure();
243+
assert!(s.structure_mask());
244+
}
245+
246+
#[test]
247+
fn test_debug_format() {
248+
let d = GrBDesc::t0();
249+
let s = format!("{:?}", d);
250+
assert!(s.contains("Transpose"));
251+
}
252+
}
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
// SPDX-License-Identifier: Apache-2.0
2+
// SPDX-FileCopyrightText: Copyright The Lance Authors
3+
4+
//! # BlasGraph — Sparse Matrix Algebra with Semiring Operations
5+
//!
6+
//! A GraphBLAS-inspired sparse matrix algebra for graph computations.
7+
//! Uses hyperdimensional binary vectors (16384-bit) as the fundamental
8+
//! data type, enabling algebraic graph algorithms via semiring operations.
9+
//!
10+
//! ## Semirings
11+
//!
12+
//! | Name | Multiply | Add | Use Case |
13+
//! |------|----------|-----|----------|
14+
//! | XOR_BUNDLE | XOR | Bundle (majority) | Path composition |
15+
//! | BIND_FIRST | XOR | First non-empty | BFS traversal |
16+
//! | HAMMING_MIN | Hamming dist | Min | Shortest path |
17+
//! | SIMILARITY_MAX | Similarity | Max | Best match |
18+
//! | RESONANCE | XOR bind | Best density | Query expansion |
19+
//! | BOOLEAN | AND | OR | Reachability |
20+
//! | XOR_FIELD | XOR | XOR | GF(2) algebra |
21+
22+
pub mod types;
23+
pub mod semiring;
24+
pub mod matrix;
25+
pub mod vector;
26+
pub mod ops;
27+
pub mod sparse;
28+
pub mod descriptor;
29+
30+
pub use types::*;
31+
pub use matrix::GrBMatrix;
32+
pub use vector::GrBVector;
33+
pub use semiring::{Semiring, HdrSemiring};
34+
pub use sparse::{SparseFormat, CsrStorage, CooStorage};
35+
pub use descriptor::{Descriptor, GrBDesc};
36+
pub use ops::*;
37+
38+
/// GraphBLAS status codes.
39+
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
40+
#[repr(C)]
41+
pub enum GrBInfo {
42+
/// Operation completed successfully.
43+
Success = 0,
44+
/// No value at specified position.
45+
NoValue = 1,
46+
/// Invalid value provided.
47+
InvalidValue = 2,
48+
/// Index out of bounds.
49+
InvalidIndex = 3,
50+
/// Type domain mismatch.
51+
DomainMismatch = 4,
52+
/// Dimension mismatch between operands.
53+
DimensionMismatch = 5,
54+
/// Output container not empty.
55+
OutputNotEmpty = 6,
56+
/// Memory allocation failed.
57+
OutOfMemory = 7,
58+
/// Invalid object state.
59+
InvalidObject = 8,
60+
/// Null pointer.
61+
NullPointer = 9,
62+
}
63+
64+
/// Initialize the GraphBLAS context (no-op in Rust).
65+
pub fn grb_init() -> GrBInfo {
66+
GrBInfo::Success
67+
}
68+
69+
/// Finalize the GraphBLAS context (no-op in Rust).
70+
pub fn grb_finalize() -> GrBInfo {
71+
GrBInfo::Success
72+
}
73+
74+
/// Get library version (GraphBLAS 2.0 compatible).
75+
pub fn grb_version() -> (u32, u32, u32) {
76+
(2, 0, 0)
77+
}
78+
79+
#[cfg(test)]
80+
mod tests {
81+
use super::*;
82+
83+
#[test]
84+
fn test_grb_info() {
85+
assert_eq!(grb_init(), GrBInfo::Success);
86+
assert_eq!(grb_finalize(), GrBInfo::Success);
87+
assert_eq!(grb_version(), (2, 0, 0));
88+
}
89+
}

0 commit comments

Comments
 (0)