├── .github └── workflows │ ├── ci.yml │ └── docs.yml ├── .gitignore ├── LICENSE ├── README.md ├── build.zig ├── build.zig.zon └── src ├── bench.zig └── hypergraphz.zig /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: ci 2 | 3 | on: 4 | pull_request: 5 | push: 6 | branches: 7 | - main 8 | 9 | jobs: 10 | test: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@v4 14 | - uses: mlugg/setup-zig@v1 15 | with: 16 | version: 0.14.0 17 | - run: zig build test --summary all 18 | -------------------------------------------------------------------------------- /.github/workflows/docs.yml: -------------------------------------------------------------------------------- 1 | name: docs 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | 8 | jobs: 9 | upload-artifacts: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: actions/checkout@v4 13 | - uses: mlugg/setup-zig@v1 14 | with: 15 | version: 0.14.0 16 | - run: zig build docs 17 | - uses: actions/upload-pages-artifact@v3 18 | with: 19 | path: zig-out/docs/ 20 | 21 | deploy-pages: 22 | needs: upload-artifacts 23 | runs-on: ubuntu-latest 24 | permissions: 25 | pages: write 26 | id-token: write 27 | environment: 28 | name: github-pages 29 | url: ${{ steps.deployment.outputs.page_url }} 30 | steps: 31 | - id: deployment 32 | uses: actions/deploy-pages@v4 33 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /zig-* 2 | /.zig-* 3 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) Davy Duperron 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # HypergraphZ - A Hypergraph Implementation in Zig 2 | 3 | ![GitHub Actions Workflow Status](https://img.shields.io/github/actions/workflow/status/yamafaktory/hypergraphz/ci.yml?branch=main&style=flat-square) 4 | 5 | HypergraphZ is a directed hypergraph implementation in Zig (https://en.wikipedia.org/wiki/Hypergraph): 6 | 7 | - Each hyperedge can contain zero, one (unary) or multiple vertices. 8 | - Each hyperedge can contain vertices directed to themselves one or more times. 9 | 10 | ## Usage 11 | 12 | Add `hypergraphz` as a dependency to your `build.zig.zon`: 13 | 14 | ```sh 15 | zig fetch --save https://github.com/yamafaktory/hypergraphz/archive/.tar.gz 16 | ``` 17 | 18 | Add `hypergraphz` as a dependency to your `build.zig`: 19 | 20 | ```zig 21 | const hypergraphz = b.dependency("hypergraphz", .{ 22 | .target = target, 23 | .optimize = optimize, 24 | }); 25 | exe.root_module.addImport("hypergraphz", hypergraphz.module("hypergraphz")); 26 | ``` 27 | 28 | ## Documentation 29 | 30 | The latest online documentation can be found [here](https://yamafaktory.github.io/hypergraphz/). 31 | -------------------------------------------------------------------------------- /build.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const HypergraphZPath = "src/hypergraphz.zig"; 3 | 4 | // Although this function looks imperative, note that its job is to 5 | // declaratively construct a build graph that will be executed by an external 6 | // runner. 7 | pub fn build(b: *std.Build) void { 8 | // Standard target options allows the person running `zig build` to choose 9 | // what target to build for. Here we do not override the defaults, which 10 | // means any target is allowed, and the default is native. Other options 11 | // for restricting supported target set are available. 12 | const target = b.standardTargetOptions(.{}); 13 | 14 | // Standard optimization options allow the person running `zig build` to select 15 | // between Debug, ReleaseSafe, ReleaseFast, and ReleaseSmall. Here we do not 16 | // set a preferred release mode, allowing the user to decide how to optimize. 17 | const optimize = b.standardOptimizeOption(.{}); 18 | 19 | const root_source_file = b.path(HypergraphZPath); 20 | 21 | // Export as module to be available via @import("hypergraphz"). 22 | _ = b.addModule("hypergraphz", .{ 23 | .root_source_file = root_source_file, 24 | .target = target, 25 | .optimize = optimize, 26 | }); 27 | 28 | // Creates a step for unit testing. This only builds the test executable 29 | // but does not run it. 30 | const unit_tests = b.addTest(.{ 31 | .root_source_file = root_source_file, 32 | .target = target, 33 | .optimize = optimize, 34 | .filter = b.option([]const u8, "filter", "Filter strings for tests"), 35 | }); 36 | 37 | const run_lib_unit_tests = b.addRunArtifact(unit_tests); 38 | 39 | // Disable cache for unit tests to get logs. 40 | // https://ziggit.dev/t/how-to-enable-more-logging-and-disable-caching-with-zig-build-test/2654/11 41 | run_lib_unit_tests.has_side_effects = true; 42 | 43 | // Similar to creating the run step earlier, this exposes a `test` step to 44 | // the `zig build --help` menu, providing a way for the user to request 45 | // running the unit tests. 46 | // Notes: 47 | // - `zig build test -Doptimize=ReleaseFast` to run the unit tests in release-fast mode. 48 | const test_step = b.step("test", "Run unit tests"); 49 | test_step.dependOn(&run_lib_unit_tests.step); 50 | 51 | // Generate docs step. 52 | const lib = b.addStaticLibrary(.{ 53 | .name = "hypergraphz", 54 | .target = target, 55 | .optimize = optimize, 56 | .root_source_file = root_source_file, 57 | }); 58 | const docs_step = b.step("docs", "Emit docs"); 59 | const docs_install = b.addInstallDirectory(.{ 60 | .install_dir = .prefix, 61 | .install_subdir = "docs", 62 | .source_dir = lib.getEmittedDocs(), 63 | }); 64 | docs_step.dependOn(&docs_install.step); 65 | b.default_step.dependOn(docs_step); 66 | 67 | // Check step used by the zls configuration. 68 | const check_step = b.step("check", "Check if HypergraphZ compiles"); 69 | const check = b.addTest(.{ 70 | .root_source_file = root_source_file, 71 | .target = target, 72 | .optimize = .Debug, 73 | }); 74 | check_step.dependOn(&check.step); 75 | 76 | // Bench step. 77 | const bench_step = b.step("bench", "Run benchmarks"); 78 | const bench_exe = b.addExecutable(.{ 79 | .name = "bench", 80 | .root_source_file = b.path("src/bench.zig"), 81 | .target = target, 82 | .optimize = .ReleaseFast, 83 | }); 84 | const bench_run = b.addRunArtifact(bench_exe); 85 | if (b.args) |args| { 86 | bench_run.addArgs(args); 87 | } 88 | bench_step.dependOn(&bench_run.step); 89 | b.default_step.dependOn(bench_step); 90 | } 91 | -------------------------------------------------------------------------------- /build.zig.zon: -------------------------------------------------------------------------------- 1 | .{ 2 | .name = .hypergraphz, 3 | .fingerprint = 0xc4efc93d2a1d228a, 4 | .version = "0.0.1", 5 | .minimum_zig_version = "0.14.0", 6 | .paths = .{ 7 | "LICENSE", 8 | "README.md", 9 | "build.zig", 10 | "build.zig.zon", 11 | "src", 12 | }, 13 | } 14 | -------------------------------------------------------------------------------- /src/bench.zig: -------------------------------------------------------------------------------- 1 | //! Benchmarks for HypergraphZ. 2 | 3 | const std = @import("std"); 4 | const hypergraphz = @import("hypergraphz.zig"); 5 | 6 | const Allocator = std.mem.Allocator; 7 | const ArrayList = std.ArrayList; 8 | const GeneralPurposeAllocator = std.heap.GeneralPurposeAllocator; 9 | const HypergraphZ = hypergraphz.HypergraphZ; 10 | const HypergraphZId = hypergraphz.HypergraphZId; 11 | const Timer = std.time.Timer; 12 | const comptimePrint = std.fmt.comptimePrint; 13 | const fmtDuration = std.fmt.fmtDuration; 14 | const getStdOut = std.io.getStdOut; 15 | const Writer = std.fs.File.Writer; 16 | 17 | const Hyperedge = struct { weight: usize = 1 }; 18 | const Vertex = struct {}; 19 | 20 | const Bench = struct { 21 | const Self = @This(); 22 | 23 | graph: HypergraphZ(Hyperedge, Vertex), 24 | start: u64, 25 | stdout: Writer, 26 | timer: Timer, 27 | 28 | pub fn init(allocator: Allocator, stdout: Writer, comptime name: []const u8) !Self { 29 | const graph = try HypergraphZ( 30 | Hyperedge, 31 | Vertex, 32 | ).init(allocator, .{ .vertices_capacity = 1_000_000, .hyperedges_capacity = 1_000 }); 33 | const msg = comptime comptimePrint("{s}...\n", .{name}); 34 | try stdout.print(msg, .{}); 35 | var timer = try Timer.start(); 36 | 37 | return .{ .timer = timer, .start = timer.lap(), .stdout = stdout, .graph = graph }; 38 | } 39 | 40 | pub fn end(self: *Self) !void { 41 | try self.stdout.print("Total duration: {}\n", .{fmtDuration(self.timer.read() - self.start)}); 42 | } 43 | 44 | pub fn deinit(self: *Self) void { 45 | self.graph.deinit(); 46 | } 47 | }; 48 | 49 | pub fn main() !void { 50 | var gpa = GeneralPurposeAllocator(.{}){}; 51 | const allocator = gpa.allocator(); 52 | const stdout = getStdOut().writer(); 53 | 54 | { 55 | var bench = try Bench.init(allocator, stdout, "generate 1_000 hyperedges with 1_000 vertices each atomically"); 56 | for (0..1_000) |_| { 57 | const h = try bench.graph.createHyperedge(.{}); 58 | for (0..1_000) |_| { 59 | const v = try bench.graph.createVertex(.{}); 60 | try bench.graph.appendVertexToHyperedge(h, v); 61 | } 62 | } 63 | try bench.end(); 64 | defer bench.deinit(); 65 | } 66 | 67 | { 68 | var vertices = try ArrayList(HypergraphZId).initCapacity(allocator, 1_000); 69 | defer vertices.deinit(); 70 | var bench = try Bench.init(allocator, stdout, "generate 1_000 hyperedges with 1_000 vertices each in batches"); 71 | for (0..1_000) |_| { 72 | vertices.clearRetainingCapacity(); 73 | const h = try bench.graph.createHyperedgeAssumeCapacity(.{}); 74 | for (0..1_000) |_| { 75 | const id = try bench.graph.createVertexAssumeCapacity(.{}); 76 | try vertices.append(id); 77 | } 78 | try bench.graph.appendVerticesToHyperedge(h, vertices.items); 79 | } 80 | try bench.end(); 81 | defer bench.deinit(); 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /src/hypergraphz.zig: -------------------------------------------------------------------------------- 1 | //! HypergraphZ is a directed hypergraph implementation in Zig. 2 | //! https://en.wikipedia.org/wiki/Hypergraph 3 | //! Each hyperedge can contain zero, one (unary) or multiple vertices. 4 | //! Each hyperedge can contain vertices directed to themselves one or more times. 5 | 6 | const std = @import("std"); 7 | 8 | const Allocator = std.mem.Allocator; 9 | const ArenaAllocator = std.heap.ArenaAllocator; 10 | const ArrayListUnmanaged = std.ArrayListUnmanaged; 11 | const AutoHashMapUnmanaged = std.AutoHashMapUnmanaged; 12 | const AutoArrayHashMapUnmanaged = std.array_hash_map.AutoArrayHashMapUnmanaged; 13 | const MemoryPool = std.heap.MemoryPool; 14 | const MultiArrayList = std.MultiArrayList; 15 | const PriorityQueue = std.PriorityQueue; 16 | const assert = std.debug.assert; 17 | const debug = std.log.debug; 18 | const window = std.mem.window; 19 | 20 | pub const HypergraphZId = u32; 21 | 22 | /// HypergraphZ errors. 23 | pub const HypergraphZError = (error{ 24 | HyperedgeNotFound, 25 | IndexOutOfBounds, 26 | NoVerticesToInsert, 27 | NotEnoughHyperedgesProvided, 28 | VertexNotFound, 29 | } || Allocator.Error); 30 | 31 | /// Create a hypergraph with hyperedges and vertices as comptime types. 32 | /// Both vertex and hyperedge must be struct types. 33 | /// Every hyperedge must have a `weight` field of type `.Int`. 34 | pub fn HypergraphZ(comptime H: type, comptime V: type) type { 35 | return struct { 36 | const Self = @This(); 37 | 38 | /// The allocator used by the HypergraphZ instance. 39 | allocator: Allocator, 40 | /// A hashmap of hyperedges data and relations. 41 | hyperedges: AutoArrayHashMapUnmanaged(HypergraphZId, HyperedgeDataRelations), 42 | /// A memory pool for hyperedges data. 43 | hyperedges_pool: MemoryPool(H), 44 | /// A hashmap of vertices data and relations. 45 | vertices: AutoArrayHashMapUnmanaged(HypergraphZId, VertexDataRelations), 46 | /// A memory pool for vertices data. 47 | vertices_pool: MemoryPool(V), 48 | /// Internal counter for both the hyperedges and vertices ids. 49 | id_counter: HypergraphZId = 0, 50 | 51 | comptime { 52 | assert(@typeInfo(H) == .@"struct"); 53 | var weightFieldType: ?type = null; 54 | for (@typeInfo(H).@"struct".fields) |f| { 55 | if (std.mem.eql(u8, f.name, "weight")) { 56 | weightFieldType = f.type; 57 | } 58 | } 59 | const isWeightInt = if (weightFieldType) |w| @typeInfo(w) == .int else false; 60 | assert(isWeightInt); 61 | assert(@typeInfo(V) == .@"struct"); 62 | } 63 | 64 | /// Vertex representation with data and relations as an array hashmap. 65 | const VertexDataRelations = struct { 66 | data: *V, 67 | relations: AutoArrayHashMapUnmanaged(HypergraphZId, void), 68 | }; 69 | 70 | /// Hyperedge representation with data and relations as an array list. 71 | const HyperedgeDataRelations = struct { 72 | data: *H, 73 | relations: ArrayListUnmanaged(HypergraphZId), 74 | }; 75 | 76 | /// Configuration struct for the HypergraphZ instance. 77 | pub const HypergraphZConfig = struct { 78 | /// The initial capacity of the hyperedges array hashmap. 79 | hyperedges_capacity: ?usize = null, 80 | /// The initial capacity of the vertices array hashmap. 81 | vertices_capacity: ?usize = null, 82 | }; 83 | 84 | /// Create a new HypergraphZ instance. 85 | pub fn init(allocator: Allocator, config: HypergraphZConfig) HypergraphZError!Self { 86 | // We use an array list for hyperedges and an array hashmap for vertices. 87 | // The hyperedges can't be a hashmap since a hyperedge can contain the same vertex multiple times. 88 | var h: AutoArrayHashMapUnmanaged(HypergraphZId, HyperedgeDataRelations) = .empty; 89 | var v: AutoArrayHashMapUnmanaged(HypergraphZId, VertexDataRelations) = .empty; 90 | 91 | // Memory pools for hyperedges and vertices. 92 | var h_pool = MemoryPool(H).init(allocator); 93 | var v_pool = MemoryPool(V).init(allocator); 94 | 95 | if (config.hyperedges_capacity) |c| { 96 | try h.ensureTotalCapacity(allocator, c); 97 | assert(h.capacity() >= c); 98 | h_pool = try MemoryPool(H).initPreheated(allocator, c); 99 | } 100 | 101 | if (config.vertices_capacity) |c| { 102 | try v.ensureTotalCapacity(allocator, c); 103 | assert(v.capacity() >= c); 104 | v_pool = try MemoryPool(V).initPreheated(allocator, c); 105 | } 106 | 107 | return .{ .allocator = allocator, .hyperedges = h, .vertices = v, .hyperedges_pool = h_pool, .vertices_pool = v_pool }; 108 | } 109 | 110 | /// Deinit the HypergraphZ instance. 111 | pub fn deinit(self: *Self) void { 112 | // Deinit hyperedge relations. 113 | var h_it = self.hyperedges.iterator(); 114 | while (h_it.next()) |*kv| { 115 | kv.value_ptr.relations.deinit(self.allocator); 116 | } 117 | 118 | // Deinit vertex relations. 119 | var v_it = self.vertices.iterator(); 120 | while (v_it.next()) |*kv| { 121 | kv.value_ptr.relations.deinit(self.allocator); 122 | } 123 | 124 | // Finally deinit all entities and the struct itself. 125 | self.hyperedges.deinit(self.allocator); 126 | self.vertices.deinit(self.allocator); 127 | self.hyperedges_pool.deinit(); 128 | self.vertices_pool.deinit(); 129 | self.* = undefined; 130 | } 131 | 132 | /// Internal method to get an id. 133 | fn _getId(self: *Self) HypergraphZId { 134 | self.id_counter += 1; 135 | return self.id_counter; 136 | } 137 | 138 | /// Create a new hyperedge. 139 | pub fn createHyperedge(self: *Self, hyperedge: H) HypergraphZError!HypergraphZId { 140 | const id = self._getId(); 141 | const h = try self.hyperedges_pool.create(); 142 | h.* = hyperedge; 143 | try self.hyperedges.put(self.allocator, id, .{ .relations = .empty, .data = h }); 144 | 145 | return id; 146 | } 147 | 148 | /// Create a new hyperedge assuming there is enough capacity. 149 | pub fn createHyperedgeAssumeCapacity(self: *Self, hyperedge: H) HypergraphZError!HypergraphZId { 150 | const id = self._getId(); 151 | const h = try self.hyperedges_pool.create(); 152 | h.* = hyperedge; 153 | self.hyperedges.putAssumeCapacity(id, .{ .relations = .empty, .data = h }); 154 | 155 | return id; 156 | } 157 | 158 | /// Reserve capacity for the insertion of new hyperedges. 159 | pub fn reserveHyperedges(self: *Self, additional_capacity: usize) HypergraphZError!void { 160 | try self.hyperedges.ensureUnusedCapacity(self.allocator, additional_capacity); 161 | } 162 | 163 | /// Create a new vertex. 164 | pub fn createVertex(self: *Self, vertex: V) HypergraphZError!HypergraphZId { 165 | const id = self._getId(); 166 | const v = try self.vertices_pool.create(); 167 | v.* = vertex; 168 | try self.vertices.put(self.allocator, id, .{ .relations = .empty, .data = v }); 169 | 170 | return id; 171 | } 172 | 173 | /// Create a new vertex assuming there is enough capacity. 174 | pub fn createVertexAssumeCapacity(self: *Self, vertex: V) HypergraphZError!HypergraphZId { 175 | const id = self._getId(); 176 | const v = try self.vertices_pool.create(); 177 | v.* = vertex; 178 | self.vertices.putAssumeCapacity(id, .{ .relations = .empty, .data = v }); 179 | 180 | return id; 181 | } 182 | 183 | /// Reserve capacity for the insertion of new vertices. 184 | pub fn reserveVertices(self: *Self, additional_capacity: usize) HypergraphZError!void { 185 | try self.vertices.ensureUnusedCapacity(self.allocator, additional_capacity); 186 | } 187 | 188 | /// Count the number of hyperedges. 189 | pub fn countHyperedges(self: *Self) usize { 190 | return self.hyperedges.count(); 191 | } 192 | 193 | /// Count the number of vertices. 194 | pub fn countVertices(self: *Self) usize { 195 | return self.vertices.count(); 196 | } 197 | 198 | /// Check if an hyperedge exists. 199 | pub fn checkIfHyperedgeExists(self: *Self, id: HypergraphZId) HypergraphZError!void { 200 | if (!self.hyperedges.contains(id)) { 201 | debug("hyperedge {} not found", .{id}); 202 | 203 | return HypergraphZError.HyperedgeNotFound; 204 | } 205 | } 206 | 207 | /// Check if a vertex exists. 208 | pub fn checkIfVertexExists(self: *Self, id: HypergraphZId) HypergraphZError!void { 209 | if (!self.vertices.contains(id)) { 210 | debug("vertex {} not found", .{id}); 211 | 212 | return HypergraphZError.VertexNotFound; 213 | } 214 | } 215 | 216 | /// Get a hyperedge. 217 | pub fn getHyperedge(self: *Self, id: HypergraphZId) HypergraphZError!H { 218 | try self.checkIfHyperedgeExists(id); 219 | 220 | const hyperedge = self.hyperedges.get(id).?; 221 | 222 | return hyperedge.data.*; 223 | } 224 | 225 | /// Get a vertex. 226 | pub fn getVertex(self: *Self, id: HypergraphZId) HypergraphZError!V { 227 | try self.checkIfVertexExists(id); 228 | 229 | const hyperedge = self.vertices.get(id).?; 230 | 231 | return hyperedge.data.*; 232 | } 233 | 234 | /// Get all the hyperedges. 235 | pub fn getAllHyperedges(self: *Self) []HypergraphZId { 236 | return self.hyperedges.keys(); 237 | } 238 | 239 | /// Get all the vertices. 240 | pub fn getAllVertices(self: *Self) []HypergraphZId { 241 | return self.vertices.keys(); 242 | } 243 | 244 | /// Update a hyperedge. 245 | pub fn updateHyperedge(self: *Self, id: HypergraphZId, hyperedge: H) HypergraphZError!void { 246 | try self.checkIfHyperedgeExists(id); 247 | 248 | self.hyperedges.getPtr(id).?.data.* = hyperedge; 249 | } 250 | 251 | /// Update a vertex. 252 | pub fn updateVertex(self: *Self, id: HypergraphZId, vertex: V) HypergraphZError!void { 253 | try self.checkIfVertexExists(id); 254 | 255 | self.vertices.getPtr(id).?.data.* = vertex; 256 | } 257 | 258 | /// Get the indegree of a vertex. 259 | /// Note that a vertex can be directed to itself multiple times. 260 | /// https://en.wikipedia.org/wiki/Directed_graph#Indegree_and_outdegree 261 | pub fn getVertexIndegree(self: *Self, id: HypergraphZId) HypergraphZError!usize { 262 | try self.checkIfVertexExists(id); 263 | 264 | const vertex = self.vertices.get(id).?; 265 | var indegree: usize = 0; 266 | var it = vertex.relations.iterator(); 267 | while (it.next()) |*kv| { 268 | const hyperedge = self.hyperedges.get(kv.key_ptr.*).?; 269 | if (hyperedge.relations.items.len > 0) { 270 | // Use a window iterator over the hyperedge relations. 271 | var wIt = window(HypergraphZId, hyperedge.relations.items, 2, 1); 272 | while (wIt.next()) |v| { 273 | if (v[0] == id) { 274 | indegree += 1; 275 | } 276 | } 277 | } 278 | } 279 | 280 | return indegree; 281 | } 282 | 283 | /// Get the indegree of a vertex. 284 | /// Note that a vertex can be directed to itself multiple times. 285 | /// https://en.wikipedia.org/wiki/Directed_graph#Indegree_and_outdegree 286 | pub fn getVertexOutdegree(self: *Self, id: HypergraphZId) HypergraphZError!usize { 287 | try self.checkIfVertexExists(id); 288 | 289 | const vertex = self.vertices.get(id).?; 290 | var outdegree: usize = 0; 291 | var it = vertex.relations.iterator(); 292 | while (it.next()) |*kv| { 293 | const hyperedge = self.hyperedges.get(kv.key_ptr.*).?; 294 | if (hyperedge.relations.items.len > 0) { 295 | // Use a window iterator over the hyperedge relations. 296 | var wIt = window(HypergraphZId, hyperedge.relations.items, 2, 1); 297 | while (wIt.next()) |v| { 298 | if (v[1] == id) { 299 | outdegree += 1; 300 | } 301 | } 302 | } 303 | } 304 | 305 | return outdegree; 306 | } 307 | 308 | /// Struct containing the adjacents vertices as a hashmap whose keys are 309 | /// hyperedge ids and values are an array of adjacent vertices. 310 | /// The caller is responsible for freeing the memory with `deinit`. 311 | pub const AdjacencyResult = struct { 312 | data: AutoArrayHashMapUnmanaged(HypergraphZId, ArrayListUnmanaged(HypergraphZId)), 313 | 314 | fn deinit(self: *AdjacencyResult, allocator: Allocator) void { 315 | // Deinit the array lists. 316 | var it = self.data.iterator(); 317 | while (it.next()) |*kv| { 318 | kv.value_ptr.deinit(allocator); 319 | } 320 | 321 | self.data.deinit(allocator); 322 | self.* = undefined; 323 | } 324 | }; 325 | /// Get the adjacents vertices connected to a vertex. 326 | /// The caller is responsible for freeing the result memory with `denit`. 327 | pub fn getVertexAdjacencyTo(self: *Self, id: HypergraphZId) HypergraphZError!AdjacencyResult { 328 | try self.checkIfVertexExists(id); 329 | 330 | // We don't need to release the memory here since the caller will do it. 331 | var adjacents: AutoArrayHashMapUnmanaged(HypergraphZId, ArrayListUnmanaged(HypergraphZId)) = .empty; 332 | const vertex = self.vertices.get(id).?; 333 | var it = vertex.relations.iterator(); 334 | while (it.next()) |*kv| { 335 | const hyperedge_id = kv.key_ptr.*; 336 | const hyperedge = self.hyperedges.get(hyperedge_id).?; 337 | if (hyperedge.relations.items.len > 0) { 338 | // Use a window iterator over the hyperedge relations. 339 | var wIt = window(HypergraphZId, hyperedge.relations.items, 2, 1); 340 | while (wIt.next()) |v| { 341 | if (v[1] == id) { 342 | const adjacent = v[0]; 343 | const result = try adjacents.getOrPut(self.allocator, hyperedge_id); 344 | // Initialize if not found. 345 | if (!result.found_existing) { 346 | result.value_ptr.* = .empty; 347 | } 348 | try result.value_ptr.*.append(self.allocator, adjacent); 349 | debug("adjacent vertex {} to vertex {} found in hyperedge {}", .{ adjacent, id, hyperedge_id }); 350 | } 351 | } 352 | } 353 | } 354 | 355 | return .{ .data = adjacents }; 356 | } 357 | 358 | /// Get the adjacents vertices connected from a vertex. 359 | /// The caller is responsible for freeing the result memory with `denit`. 360 | pub fn getVertexAdjacencyFrom(self: *Self, id: HypergraphZId) HypergraphZError!AdjacencyResult { 361 | try self.checkIfVertexExists(id); 362 | 363 | // We don't need to release the memory here since the caller will do it. 364 | var adjacents: AutoArrayHashMapUnmanaged(HypergraphZId, ArrayListUnmanaged(HypergraphZId)) = .empty; 365 | const vertex = self.vertices.get(id).?; 366 | var it = vertex.relations.iterator(); 367 | while (it.next()) |*kv| { 368 | const hyperedge_id = kv.key_ptr.*; 369 | const hyperedge = self.hyperedges.get(hyperedge_id).?; 370 | if (hyperedge.relations.items.len > 0) { 371 | // Use a window iterator over the hyperedge relations. 372 | var wIt = window(HypergraphZId, hyperedge.relations.items, 2, 1); 373 | while (wIt.next()) |v| { 374 | if (v[0] == id) { 375 | const adjacent = v[1]; 376 | const result = try adjacents.getOrPut(self.allocator, hyperedge_id); 377 | // Initialize if not found. 378 | if (!result.found_existing) { 379 | result.value_ptr.* = .empty; 380 | } 381 | try result.value_ptr.*.append(self.allocator, adjacent); 382 | debug("adjacent vertex {} from vertex {} found in hyperedge {}", .{ adjacent, id, hyperedge_id }); 383 | } 384 | } 385 | } 386 | } 387 | 388 | return .{ .data = adjacents }; 389 | } 390 | 391 | /// Delete a hyperedge. 392 | pub fn deleteHyperedge(self: *Self, id: HypergraphZId, drop_vertices: bool) HypergraphZError!void { 393 | try self.checkIfHyperedgeExists(id); 394 | 395 | const hyperedge = self.hyperedges.getPtr(id).?; 396 | const vertices = hyperedge.relations.items; 397 | 398 | if (drop_vertices) { 399 | // Delete vertices. 400 | for (vertices) |v| { 401 | const vertex = self.vertices.getPtr(v); 402 | // A vertex can appear multiple times within a hyperedge and thus might already be deleted. 403 | if (vertex) |ptr| { 404 | // Remove from the vertices pool. 405 | self.vertices_pool.destroy(@alignCast(ptr.data)); 406 | 407 | // Release memory. 408 | ptr.relations.deinit(self.allocator); 409 | const removed = self.vertices.orderedRemove(v); 410 | assert(removed); 411 | } 412 | } 413 | } else { 414 | // Delete the hyperedge from the vertex relations. 415 | for (vertices) |v| { 416 | const vertex = self.vertices.getPtr(v); 417 | // A vertex can appear multiple times within a hyperedge and thus might already be deleted. 418 | if (vertex) |ptr| { 419 | _ = ptr.relations.orderedRemove(id); 420 | } 421 | } 422 | } 423 | 424 | // Remove from the hyperedges pool. 425 | self.hyperedges_pool.destroy(hyperedge.data); 426 | 427 | // Release memory. 428 | hyperedge.relations.deinit(self.allocator); 429 | 430 | // Delete the hyperedge itself. 431 | const removed = self.hyperedges.orderedRemove(id); 432 | assert(removed); 433 | 434 | debug("hyperedge {} deleted", .{id}); 435 | } 436 | 437 | /// Delete a vertex. 438 | pub fn deleteVertex(self: *Self, id: HypergraphZId) HypergraphZError!void { 439 | try self.checkIfVertexExists(id); 440 | 441 | const vertex = self.vertices.getPtr(id).?; 442 | const hyperedges = vertex.relations.keys(); 443 | for (hyperedges) |h| { 444 | const hyperedge = self.hyperedges.getPtr(h).?; 445 | // Delete the vertex from the hyperedge relations. 446 | // The same vertex can appear multiple times within a hyperedge. 447 | // Create a temporary list to store the relations without the vertex. 448 | var tmp: ArrayListUnmanaged(HypergraphZId) = .empty; 449 | defer tmp.deinit(self.allocator); 450 | for (hyperedge.relations.items) |v| { 451 | if (v != id) { 452 | try tmp.append(self.allocator, v); 453 | } 454 | } 455 | // Swap the temporary list with the hyperedge relations. 456 | std.mem.swap(ArrayListUnmanaged(HypergraphZId), &hyperedge.relations, &tmp); 457 | } 458 | 459 | // Remove from the vertices pool. 460 | self.vertices_pool.destroy(@alignCast(vertex.data)); 461 | 462 | // Release memory. 463 | vertex.relations.deinit(self.allocator); 464 | 465 | // Delete the hyperedge itself. 466 | const removed = self.vertices.orderedRemove(id); 467 | assert(removed); 468 | 469 | debug("vertex {} deleted", .{id}); 470 | } 471 | 472 | /// Get all vertices of a hyperedge as a slice. 473 | pub fn getHyperedgeVertices(self: *Self, hyperedge_id: HypergraphZId) HypergraphZError![]HypergraphZId { 474 | try self.checkIfHyperedgeExists(hyperedge_id); 475 | 476 | const hyperedge = self.hyperedges.getPtr(hyperedge_id).?; 477 | 478 | return hyperedge.relations.items; 479 | } 480 | 481 | /// Get all hyperedges of a vertex as a slice. 482 | pub fn getVertexHyperedges(self: *Self, vertex_id: HypergraphZId) HypergraphZError![]HypergraphZId { 483 | try self.checkIfVertexExists(vertex_id); 484 | 485 | const vertex = self.vertices.getPtr(vertex_id).?; 486 | 487 | return vertex.relations.keys(); 488 | } 489 | 490 | /// Append a vertex to a hyperedge. 491 | pub fn appendVertexToHyperedge(self: *Self, hyperedge_id: HypergraphZId, vertex_id: HypergraphZId) HypergraphZError!void { 492 | try self.checkIfHyperedgeExists(hyperedge_id); 493 | try self.checkIfVertexExists(vertex_id); 494 | 495 | // Append vertex to hyperedge relations. 496 | const hyperedge = self.hyperedges.getPtr(hyperedge_id).?; 497 | try hyperedge.relations.append(self.allocator, vertex_id); 498 | 499 | // Add hyperedge to vertex relations. 500 | const vertex = self.vertices.getPtr(vertex_id).?; 501 | try vertex.relations.put(self.allocator, hyperedge_id, {}); 502 | 503 | debug("vertex {} appended to hyperedge {}", .{ 504 | vertex_id, 505 | hyperedge_id, 506 | }); 507 | } 508 | 509 | /// Prepend a vertex to a hyperedge. 510 | pub fn prependVertexToHyperedge(self: *Self, hyperedge_id: HypergraphZId, vertex_id: HypergraphZId) HypergraphZError!void { 511 | try self.checkIfHyperedgeExists(hyperedge_id); 512 | try self.checkIfVertexExists(vertex_id); 513 | 514 | // Prepend vertex to hyperedge relations. 515 | const hyperedge = self.hyperedges.getPtr(hyperedge_id).?; 516 | try hyperedge.relations.insertSlice(self.allocator, 0, &.{vertex_id}); 517 | 518 | // Add hyperedge to vertex relations. 519 | const vertex = self.vertices.getPtr(vertex_id).?; 520 | try vertex.relations.put(self.allocator, hyperedge_id, {}); 521 | 522 | debug("vertex {} prepended to hyperedge {}", .{ 523 | vertex_id, 524 | hyperedge_id, 525 | }); 526 | } 527 | 528 | /// Insert a vertex into a hyperedge at a given index. 529 | pub fn insertVertexIntoHyperedge(self: *Self, hyperedge_id: HypergraphZId, vertex_id: HypergraphZId, index: usize) HypergraphZError!void { 530 | try self.checkIfHyperedgeExists(hyperedge_id); 531 | try self.checkIfVertexExists(vertex_id); 532 | 533 | const hyperedge = self.hyperedges.getPtr(hyperedge_id).?; 534 | if (index > hyperedge.relations.items.len) { 535 | return HypergraphZError.IndexOutOfBounds; 536 | } 537 | 538 | // Insert vertex into hyperedge relations at given index. 539 | try hyperedge.relations.insert(self.allocator, index, vertex_id); 540 | 541 | const vertex = self.vertices.getPtr(vertex_id).?; 542 | try vertex.relations.put(self.allocator, hyperedge_id, {}); 543 | 544 | debug("vertex {} inserted into hyperedge {} at index {}", .{ 545 | vertex_id, 546 | hyperedge_id, 547 | index, 548 | }); 549 | } 550 | 551 | /// Append vertices to a hyperedge. 552 | pub fn appendVerticesToHyperedge(self: *Self, hyperedge_id: HypergraphZId, vertex_ids: []const HypergraphZId) HypergraphZError!void { 553 | if (vertex_ids.len == 0) { 554 | debug("no vertices to append to hyperedge {}, skipping", .{hyperedge_id}); 555 | return; 556 | } 557 | 558 | try self.checkIfHyperedgeExists(hyperedge_id); 559 | for (vertex_ids) |v| { 560 | try self.checkIfVertexExists(v); 561 | } 562 | 563 | // Append vertices to hyperedge relations. 564 | const hyperedge = self.hyperedges.getPtr(hyperedge_id).?; 565 | try hyperedge.relations.appendSlice(self.allocator, vertex_ids); 566 | 567 | for (vertex_ids) |id| { 568 | const vertex = self.vertices.getPtr(id).?; 569 | try vertex.relations.put(self.allocator, hyperedge_id, {}); 570 | } 571 | 572 | debug("vertices appended to hyperedge {}", .{hyperedge_id}); 573 | } 574 | 575 | /// Prepend vertices to a hyperedge. 576 | pub fn prependVerticesToHyperedge(self: *Self, hyperedge_id: HypergraphZId, vertices_ids: []const HypergraphZId) HypergraphZError!void { 577 | if (vertices_ids.len == 0) { 578 | debug("no vertices to prepend to hyperedge {}, skipping", .{hyperedge_id}); 579 | return; 580 | } 581 | 582 | try self.checkIfHyperedgeExists(hyperedge_id); 583 | for (vertices_ids) |v| { 584 | try self.checkIfVertexExists(v); 585 | } 586 | 587 | // Prepend vertices to hyperedge relations. 588 | const hyperedge = self.hyperedges.getPtr(hyperedge_id).?; 589 | try hyperedge.relations.insertSlice(std.testing.allocator, 0, vertices_ids); 590 | 591 | for (vertices_ids) |id| { 592 | const vertex = self.vertices.getPtr(id).?; 593 | try vertex.relations.put(self.allocator, hyperedge_id, {}); 594 | } 595 | 596 | debug("vertices prepended to hyperedge {}", .{hyperedge_id}); 597 | } 598 | 599 | /// Insert vertices into a hyperedge at a given index. 600 | pub fn insertVerticesIntoHyperedge(self: *Self, hyperedge_id: HypergraphZId, vertices_ids: []const HypergraphZId, index: usize) HypergraphZError!void { 601 | if (vertices_ids.len == 0) { 602 | debug("no vertices to insert into hyperedge {}, skipping", .{hyperedge_id}); 603 | return HypergraphZError.NoVerticesToInsert; 604 | } 605 | 606 | try self.checkIfHyperedgeExists(hyperedge_id); 607 | for (vertices_ids) |v| { 608 | try self.checkIfVertexExists(v); 609 | } 610 | 611 | const hyperedge = self.hyperedges.getPtr(hyperedge_id).?; 612 | if (index > hyperedge.relations.items.len) { 613 | return HypergraphZError.IndexOutOfBounds; 614 | } 615 | 616 | // Prepend vertices to hyperedge relations. 617 | try hyperedge.relations.insertSlice(std.testing.allocator, index, vertices_ids); 618 | 619 | for (vertices_ids) |id| { 620 | const vertex = self.vertices.getPtr(id).?; 621 | try vertex.relations.put(self.allocator, hyperedge_id, {}); 622 | } 623 | 624 | debug("vertices inserted into hyperedge {} at index {}", .{ hyperedge_id, index }); 625 | } 626 | 627 | /// Delete a vertex from a hyperedge. 628 | pub fn deleteVertexFromHyperedge(self: *Self, hyperedge_id: HypergraphZId, vertex_id: HypergraphZId) HypergraphZError!void { 629 | try self.checkIfHyperedgeExists(hyperedge_id); 630 | try self.checkIfVertexExists(vertex_id); 631 | 632 | const hyperedge = self.hyperedges.getPtr(hyperedge_id).?; 633 | 634 | // The same vertex can appear multiple times within a hyperedge. 635 | // Create a temporary list to store the relations without the vertex. 636 | var tmp: ArrayListUnmanaged(HypergraphZId) = .empty; 637 | defer tmp.deinit(self.allocator); 638 | for (hyperedge.relations.items) |v| { 639 | if (v != vertex_id) { 640 | try tmp.append(self.allocator, v); 641 | } 642 | } 643 | // Swap the temporary list with the hyperedge relations. 644 | std.mem.swap(ArrayListUnmanaged(HypergraphZId), &hyperedge.relations, &tmp); 645 | 646 | const vertex = self.vertices.getPtr(vertex_id).?; 647 | const removed = vertex.relations.orderedRemove(hyperedge_id); 648 | assert(removed); 649 | debug("vertice {} deleted from hyperedge {}", .{ vertex_id, hyperedge_id }); 650 | } 651 | 652 | /// Delete a vertex from a hyperedge at a given index. 653 | pub fn deleteVertexByIndexFromHyperedge(self: *Self, hyperedge_id: HypergraphZId, index: usize) HypergraphZError!void { 654 | try self.checkIfHyperedgeExists(hyperedge_id); 655 | 656 | const hyperedge = self.hyperedges.getPtr(hyperedge_id).?; 657 | if (index > hyperedge.relations.items.len) { 658 | return HypergraphZError.IndexOutOfBounds; 659 | } 660 | 661 | const vertex_id = hyperedge.relations.orderedRemove(index); 662 | const vertex = self.vertices.getPtr(vertex_id).?; 663 | 664 | // Check if the same vertex appears again in this hyperedge. 665 | // If not, we can remove the hyperedge from the vertex relations. 666 | for (hyperedge.relations.items) |v| { 667 | if (v == vertex_id) { 668 | break; 669 | } 670 | } else { 671 | const removed = vertex.relations.orderedRemove(hyperedge_id); 672 | assert(removed); 673 | } 674 | 675 | debug("vertice {} at index {} deleted from hyperedge {}", .{ vertex_id, index, hyperedge_id }); 676 | } 677 | 678 | /// Get the intersections between multiple hyperedges. 679 | /// This method returns an owned slice which must be freed by the caller. 680 | pub fn getIntersections(self: *Self, hyperedges_ids: []const HypergraphZId) HypergraphZError![]const HypergraphZId { 681 | if (hyperedges_ids.len < 2) { 682 | debug("at least two hyperedges must be provided, skipping", .{}); 683 | return HypergraphZError.NotEnoughHyperedgesProvided; 684 | } 685 | 686 | for (hyperedges_ids) |id| { 687 | try self.checkIfHyperedgeExists(id); 688 | } 689 | 690 | // We don't need to release the memory here since the caller will do it. 691 | var intersections: ArrayListUnmanaged(HypergraphZId) = .empty; 692 | var matches: AutoArrayHashMapUnmanaged(HypergraphZId, usize) = .empty; 693 | defer matches.deinit(self.allocator); 694 | 695 | for (hyperedges_ids) |id| { 696 | const hyperedge = self.hyperedges.getPtr(id).?; 697 | 698 | // Keep track of visited vertices since the same vertex can appear multiple times within a hyperedge. 699 | var visited: AutoArrayHashMapUnmanaged(HypergraphZId, void) = .empty; 700 | defer visited.deinit(self.allocator); 701 | 702 | for (hyperedge.relations.items) |v| { 703 | if (visited.get(v) != null) { 704 | continue; 705 | } 706 | const result = try matches.getOrPut(self.allocator, v); 707 | try visited.put(self.allocator, v, {}); 708 | if (result.found_existing) { 709 | result.value_ptr.* += 1; 710 | if (result.value_ptr.* == hyperedges_ids.len) { 711 | debug("intersection found at vertex {}", .{v}); 712 | try intersections.append(self.allocator, v); 713 | } 714 | } else { 715 | // Initialize. 716 | result.value_ptr.* = 1; 717 | } 718 | } 719 | } 720 | 721 | return try intersections.toOwnedSlice(self.allocator); 722 | } 723 | 724 | const Node = struct { 725 | from: HypergraphZId, 726 | weight: usize, 727 | }; 728 | const CameFrom = AutoHashMapUnmanaged(HypergraphZId, ?Node); 729 | const Queue = PriorityQueue(HypergraphZId, *const CameFrom, compareNode); 730 | fn compareNode(map: *const CameFrom, n1: HypergraphZId, n2: HypergraphZId) std.math.Order { 731 | const node1 = map.get(n1).?; 732 | const node2 = map.get(n2).?; 733 | 734 | return std.math.order(node1.?.weight, node2.?.weight); 735 | } 736 | /// Struct containing the shortest path as a list of vertices. 737 | /// The caller is responsible for freeing the memory with `deinit`. 738 | pub const ShortestPathResult = struct { 739 | data: ?ArrayListUnmanaged(HypergraphZId), 740 | 741 | fn deinit(self: *ShortestPathResult, allocator: Allocator) void { 742 | if (self.data) |*d| d.deinit(allocator); 743 | self.* = undefined; 744 | } 745 | }; 746 | /// Find the shortest path between two vertices using the A* algorithm. 747 | /// The caller is responsible for freeing the result memory with `deinit`. 748 | pub fn findShortestPath(self: *Self, from: HypergraphZId, to: HypergraphZId) HypergraphZError!ShortestPathResult { 749 | try self.checkIfVertexExists(from); 750 | try self.checkIfVertexExists(to); 751 | 752 | var arena = ArenaAllocator.init(self.allocator); 753 | defer arena.deinit(); 754 | const arena_allocator = arena.allocator(); 755 | 756 | var came_from: CameFrom = .empty; 757 | var cost_so_far: AutoHashMapUnmanaged(HypergraphZId, usize) = .empty; 758 | var frontier = Queue.init(arena.allocator(), &came_from); 759 | 760 | try came_from.put(arena_allocator, from, null); 761 | try cost_so_far.put(arena_allocator, from, 0); 762 | try frontier.add(from); 763 | 764 | while (frontier.count() != 0) { 765 | const current = frontier.remove(); 766 | 767 | if (current == to) break; 768 | 769 | // Get adjacent vertices and their weights from the current hyperedge. 770 | var result = try self.getVertexAdjacencyFrom(current); 771 | defer result.deinit(self.allocator); 772 | var adjacentsWithWeight: AutoArrayHashMapUnmanaged(HypergraphZId, usize) = .empty; 773 | defer adjacentsWithWeight.deinit(self.allocator); 774 | var it = result.data.iterator(); 775 | while (it.next()) |*kv| { 776 | const hyperedge = self.hyperedges.get(kv.key_ptr.*).?; 777 | const hWeight = hyperedge.data.weight; 778 | for (kv.value_ptr.*.items) |v| { 779 | try adjacentsWithWeight.put(self.allocator, v, hWeight); 780 | } 781 | } 782 | 783 | // Apply A* on the adjacent vertices. 784 | var weighted_it = adjacentsWithWeight.iterator(); 785 | while (weighted_it.next()) |*kv| { 786 | const next = kv.key_ptr.*; 787 | const new_cost = (cost_so_far.get(current) orelse 0) + kv.value_ptr.*; 788 | if (!cost_so_far.contains(next) or new_cost < cost_so_far.get(next).?) { 789 | try cost_so_far.put(arena_allocator, next, new_cost); 790 | try came_from.put(arena_allocator, next, .{ .weight = kv.value_ptr.*, .from = current }); 791 | try frontier.add(next); 792 | } 793 | } 794 | } 795 | 796 | var it = came_from.iterator(); 797 | var visited: AutoArrayHashMapUnmanaged(HypergraphZId, HypergraphZId) = .empty; 798 | defer visited.deinit(self.allocator); 799 | while (it.next()) |*kv| { 800 | const node = kv.value_ptr.*; 801 | const origin = kv.key_ptr.*; 802 | const dest = if (node) |n| n.from else 0; 803 | try visited.put(self.allocator, origin, dest); 804 | } 805 | 806 | var last = visited.get(to); 807 | 808 | if (last == null) { 809 | debug("no path found between {} and {}", .{ from, to }); 810 | return .{ .data = null }; 811 | } 812 | 813 | // We iterate in reverse order. 814 | var path: ArrayListUnmanaged(HypergraphZId) = .empty; 815 | try path.append(self.allocator, to); 816 | while (true) { 817 | if (last == 0) break; 818 | try path.append(self.allocator, last.?); 819 | const next = visited.get(last.?); 820 | if (next == null or next == 0) break; 821 | last = next; 822 | } 823 | std.mem.reverse(HypergraphZId, path.items); 824 | 825 | debug("path found between {} and {}", .{ from, to }); 826 | return .{ .data = path }; 827 | } 828 | 829 | /// Reverse a hyperedge. 830 | pub fn reverseHyperedge(self: *Self, hyperedge_id: HypergraphZId) HypergraphZError!void { 831 | try self.checkIfHyperedgeExists(hyperedge_id); 832 | 833 | const hyperedge = self.hyperedges.getPtr(hyperedge_id).?; 834 | const tmp = try hyperedge.relations.toOwnedSlice(self.allocator); 835 | std.mem.reverse(HypergraphZId, tmp); 836 | hyperedge.relations = ArrayListUnmanaged(HypergraphZId).fromOwnedSlice(tmp); 837 | debug("hyperedge {} reversed", .{hyperedge_id}); 838 | } 839 | 840 | /// Join two or more hyperedges into one. 841 | /// All the vertices are moved to the first hyperedge. 842 | pub fn joinHyperedges(self: *Self, hyperedges_ids: []const HypergraphZId) HypergraphZError!void { 843 | if (hyperedges_ids.len < 2) { 844 | debug("at least two hyperedges must be provided, skipping", .{}); 845 | return HypergraphZError.NotEnoughHyperedgesProvided; 846 | } 847 | 848 | for (hyperedges_ids) |h| { 849 | try self.checkIfHyperedgeExists(h); 850 | } 851 | 852 | var first = self.hyperedges.getPtr(hyperedges_ids[0]).?; 853 | for (hyperedges_ids[1..]) |h| { 854 | const hyperedge = self.hyperedges.getPtr(h).?; 855 | const items = hyperedge.relations.items; 856 | 857 | // Move the vertices to the first hyperedge. 858 | try first.relations.appendSlice(self.allocator, items); 859 | 860 | // Delete the hyperedge from the vertex relations. 861 | const vertices = hyperedge.relations.items; 862 | for (vertices) |v| { 863 | // We can't assert that the removal is truthy since a vertex can appear multiple times within a hyperedge. 864 | const vertex = self.vertices.getPtr(v); 865 | _ = vertex.?.relations.orderedRemove(h); 866 | } 867 | 868 | // Release memory. 869 | hyperedge.relations.deinit(self.allocator); 870 | 871 | // Delete the hyperedge itself. 872 | const removed = self.hyperedges.orderedRemove(h); 873 | assert(removed); 874 | } 875 | 876 | debug("hyperedges {any} joined into hyperedge {}", .{ hyperedges_ids, hyperedges_ids[0] }); 877 | } 878 | 879 | /// Contract a hyperedge by merging its vertices into one. 880 | /// The resulting vertex will be the last vertex in the hyperedge. 881 | /// https://en.wikipedia.org/wiki/Edge_contraction 882 | pub fn contractHyperedge(self: *Self, id: HypergraphZId) HypergraphZError!void { 883 | try self.checkIfHyperedgeExists(id); 884 | 885 | // Get the deduped vertices of the hyperedge. 886 | const hyperedge = self.hyperedges.getPtr(id).?; 887 | var arena = ArenaAllocator.init(self.allocator); 888 | defer arena.deinit(); 889 | const arena_allocator = arena.allocator(); 890 | var deduped: AutoHashMapUnmanaged(HypergraphZId, void) = .empty; 891 | const vertices = hyperedge.relations.items; 892 | for (vertices) |v| { 893 | try deduped.put(arena_allocator, v, {}); 894 | } 895 | 896 | const last = vertices[vertices.len - 1]; 897 | 898 | // Get all vertices connecting to the ones from this hyperedge except the last one. 899 | var it = deduped.keyIterator(); 900 | while (it.next()) |d| { 901 | var result = try self.getVertexAdjacencyTo(d.*); 902 | defer result.deinit(self.allocator); 903 | var it_h = result.data.iterator(); 904 | while (it_h.next()) |*kv| { 905 | var h = self.hyperedges.getPtr(kv.key_ptr.*).?; 906 | for (h.relations.items, 0..) |v, i| { 907 | // In each hyperedge, replace the current vertex with the last one. 908 | if (v == d.*) { 909 | h.relations.items[i] = last; 910 | // If the next vertex is also the last one, remove it. 911 | if (i + 1 < h.relations.items.len and h.relations.items[i + 1] == last) { 912 | _ = h.relations.orderedRemove(i + 1); 913 | } 914 | } 915 | } 916 | } 917 | 918 | // Delete the hyperedge from the vertex relations. 919 | const vertex = self.vertices.getPtr(d.*).?; 920 | const removed = vertex.relations.orderedRemove(id); 921 | assert(removed); 922 | } 923 | 924 | // Delete the hyperedge itself. 925 | hyperedge.relations.deinit(self.allocator); 926 | const removed = self.hyperedges.orderedRemove(id); 927 | assert(removed); 928 | debug("hyperedge {} contracted", .{id}); 929 | } 930 | 931 | /// Clear the hypergraph. 932 | pub fn clear(self: *Self) void { 933 | self.hyperedges.clearAndFree(self.allocator); 934 | self.vertices.clearAndFree(self.allocator); 935 | } 936 | 937 | /// Struct containing the hyperedges as a hashset whose keys are 938 | /// hyperedge ids. 939 | /// The caller is responsible for freeing the memory with `deinit`. 940 | pub const HyperedgesResult = struct { 941 | data: AutoArrayHashMapUnmanaged(HypergraphZId, void), 942 | 943 | fn deinit(self: *HyperedgesResult, allocator: Allocator) void { 944 | self.data.deinit(allocator); 945 | self.* = undefined; 946 | } 947 | }; 948 | /// Get all the hyperedges connecting two vertices. 949 | /// This method returns an owned slice which must be freed by the caller. 950 | pub fn getHyperedgesConnecting(self: *Self, first_vertex_id: HypergraphZId, second_vertex_id: HypergraphZId) HypergraphZError!HyperedgesResult { 951 | try self.checkIfVertexExists(first_vertex_id); 952 | try self.checkIfVertexExists(second_vertex_id); 953 | 954 | const eq = first_vertex_id == second_vertex_id; 955 | const first_vertex = self.vertices.get(first_vertex_id).?; 956 | var it = first_vertex.relations.iterator(); 957 | var deduped: AutoArrayHashMapUnmanaged(HypergraphZId, void) = .empty; 958 | while (it.next()) |*kv| { 959 | const hyperedge = self.hyperedges.get(kv.key_ptr.*).?; 960 | var found_occurences: usize = 0; 961 | for (hyperedge.relations.items) |v| { 962 | if (v == second_vertex_id) { 963 | found_occurences += 1; 964 | } 965 | } 966 | // We need to take care of potential self-loops. 967 | if ((eq and found_occurences > 1) or (!eq and found_occurences > 0)) { 968 | try deduped.put(self.allocator, kv.key_ptr.*, {}); 969 | } 970 | } 971 | 972 | return .{ .data = deduped }; 973 | } 974 | 975 | /// Tuple struct containing a vertex id and its hyperedge id as an endpoint. 976 | pub const EndpointTuple = struct { 977 | hyperedge_id: HypergraphZId, 978 | vertex_id: HypergraphZId, 979 | }; 980 | /// Struct containing the endpoints - initial and terminal - as two 981 | /// multi array lists of `EndpointTuple`. 982 | /// The caller is responsible for freeing the memory with `deinit`. 983 | pub const EndpointsResult = struct { 984 | allocator: Allocator, 985 | initial: MultiArrayList(EndpointTuple), 986 | terminal: MultiArrayList(EndpointTuple), 987 | 988 | fn init(allocator: Allocator) EndpointsResult { 989 | const initial = (MultiArrayList(EndpointTuple)){}; 990 | const terminal = (MultiArrayList(EndpointTuple)){}; 991 | 992 | return .{ 993 | .allocator = allocator, 994 | .initial = initial, 995 | .terminal = terminal, 996 | }; 997 | } 998 | 999 | fn deinit(self: *EndpointsResult) void { 1000 | self.initial.deinit(self.allocator); 1001 | self.terminal.deinit(self.allocator); 1002 | self.* = undefined; 1003 | } 1004 | }; 1005 | /// Get all the initial and terminal endpoints of all the hyperedges. 1006 | pub fn getEndpoints(self: *Self) HypergraphZError!EndpointsResult { 1007 | var result = EndpointsResult.init(self.allocator); 1008 | var it = self.hyperedges.iterator(); 1009 | while (it.next()) |*kv| { 1010 | const hyperedge = kv.value_ptr; 1011 | if (hyperedge.relations.items.len == 0) continue; 1012 | const hyperedge_id = kv.key_ptr.*; 1013 | const vertices = hyperedge.relations.items; 1014 | try result.initial.append(self.allocator, .{ .hyperedge_id = hyperedge_id, .vertex_id = vertices[0] }); 1015 | try result.terminal.append(self.allocator, .{ .hyperedge_id = hyperedge_id, .vertex_id = vertices[vertices.len - 1] }); 1016 | } 1017 | 1018 | debug("{} initial and {} terminal endpoints found", .{ result.initial.len, result.terminal.len }); 1019 | return result; 1020 | } 1021 | 1022 | /// Get the orphan hyperedges. 1023 | /// The caller is responsible for freeing the memory with `deinit`. 1024 | pub fn getOrphanHyperedges(self: *Self) HypergraphZError![]const HypergraphZId { 1025 | var orphans: ArrayListUnmanaged(HypergraphZId) = .empty; 1026 | var it = self.hyperedges.iterator(); 1027 | while (it.next()) |*kv| { 1028 | const vertices = kv.value_ptr.relations; 1029 | if (vertices.items.len == 0) { 1030 | try orphans.append(self.allocator, kv.key_ptr.*); 1031 | } 1032 | } 1033 | 1034 | debug("{} orphan hyperedges found", .{orphans.items.len}); 1035 | return orphans.toOwnedSlice(self.allocator); 1036 | } 1037 | 1038 | /// Get the orphan vertices. 1039 | /// The caller is responsible for freeing the memory with `deinit`. 1040 | pub fn getOrphanVertices(self: *Self) HypergraphZError![]const HypergraphZId { 1041 | var orphans: ArrayListUnmanaged(HypergraphZId) = .empty; 1042 | var it = self.vertices.iterator(); 1043 | while (it.next()) |*kv| { 1044 | const hyperedges = kv.value_ptr.relations; 1045 | if (hyperedges.count() == 0) { 1046 | try orphans.append(self.allocator, kv.key_ptr.*); 1047 | } 1048 | } 1049 | 1050 | debug("{} orphan vertices found", .{orphans.items.len}); 1051 | return orphans.toOwnedSlice(self.allocator); 1052 | } 1053 | }; 1054 | } 1055 | 1056 | const expect = std.testing.expect; 1057 | const expectEqualSlices = std.testing.expectEqualSlices; 1058 | const expectError = std.testing.expectError; 1059 | const maxInt = std.math.maxInt; 1060 | 1061 | const Hyperedge = struct { meow: bool = false, weight: usize = 1 }; 1062 | const Vertex = struct { purr: bool = false }; 1063 | 1064 | fn scaffold() HypergraphZError!HypergraphZ(Hyperedge, Vertex) { 1065 | std.testing.log_level = .debug; 1066 | 1067 | const graph = try HypergraphZ( 1068 | Hyperedge, 1069 | Vertex, 1070 | ).init(std.testing.allocator, .{ .vertices_capacity = 5, .hyperedges_capacity = 3 }); 1071 | 1072 | return graph; 1073 | } 1074 | 1075 | const max_id = maxInt(HypergraphZId); 1076 | 1077 | const Data = struct { 1078 | v_a: HypergraphZId, 1079 | v_b: HypergraphZId, 1080 | v_c: HypergraphZId, 1081 | v_d: HypergraphZId, 1082 | v_e: HypergraphZId, 1083 | h_a: HypergraphZId, 1084 | h_b: HypergraphZId, 1085 | h_c: HypergraphZId, 1086 | }; 1087 | fn generateTestData(graph: *HypergraphZ(Hyperedge, Vertex)) !Data { 1088 | const v_a = try graph.createVertexAssumeCapacity(.{}); 1089 | const v_b = try graph.createVertexAssumeCapacity(.{}); 1090 | const v_c = try graph.createVertexAssumeCapacity(.{}); 1091 | const v_d = try graph.createVertexAssumeCapacity(.{}); 1092 | const v_e = try graph.createVertexAssumeCapacity(.{}); 1093 | 1094 | const h_a = try graph.createHyperedgeAssumeCapacity(.{}); 1095 | try graph.appendVerticesToHyperedge(h_a, &.{ v_a, v_b, v_c, v_d, v_e }); 1096 | const h_b = try graph.createHyperedgeAssumeCapacity(.{}); 1097 | try graph.appendVerticesToHyperedge(h_b, &.{ v_e, v_e, v_a }); 1098 | const h_c = try graph.createHyperedgeAssumeCapacity(.{}); 1099 | try graph.appendVerticesToHyperedge(h_c, &.{ v_b, v_c, v_c, v_e, v_a, v_d, v_b }); 1100 | 1101 | return .{ 1102 | .v_a = v_a, 1103 | .v_b = v_b, 1104 | .v_c = v_c, 1105 | .v_d = v_d, 1106 | .v_e = v_e, 1107 | .h_a = h_a, 1108 | .h_b = h_b, 1109 | .h_c = h_c, 1110 | }; 1111 | } 1112 | 1113 | test "allocation failure" { 1114 | var failingAllocator = std.testing.FailingAllocator.init(std.testing.allocator, .{ .fail_index = 1 }); 1115 | var graph = try HypergraphZ( 1116 | Hyperedge, 1117 | Vertex, 1118 | ).init(failingAllocator.allocator(), .{}); 1119 | defer graph.deinit(); 1120 | 1121 | // The following fails since two allocations are made. 1122 | try expectError(HypergraphZError.OutOfMemory, graph.createHyperedge(.{})); 1123 | } 1124 | 1125 | test "create and get hyperedge" { 1126 | var graph = try scaffold(); 1127 | defer graph.deinit(); 1128 | 1129 | const hyperedge_id = try graph.createHyperedge(.{}); 1130 | 1131 | try expectError(HypergraphZError.HyperedgeNotFound, graph.getHyperedge(max_id)); 1132 | 1133 | const hyperedge = try graph.getHyperedge(hyperedge_id); 1134 | try expect(@TypeOf(hyperedge) == Hyperedge); 1135 | } 1136 | 1137 | test "create and get vertex" { 1138 | var graph = try scaffold(); 1139 | defer graph.deinit(); 1140 | 1141 | const vertex_id = try graph.createVertex(.{}); 1142 | 1143 | try expectError(HypergraphZError.VertexNotFound, graph.getVertex(max_id)); 1144 | 1145 | const vertex = try graph.getVertex(vertex_id); 1146 | try expect(@TypeOf(vertex) == Vertex); 1147 | } 1148 | 1149 | test "get all hyperedges" { 1150 | var graph = try scaffold(); 1151 | defer graph.deinit(); 1152 | 1153 | const data = try generateTestData(&graph); 1154 | 1155 | const hyperedges = graph.getAllHyperedges(); 1156 | try expectEqualSlices(HypergraphZId, &[_]HypergraphZId{ data.h_a, data.h_b, data.h_c }, hyperedges); 1157 | } 1158 | 1159 | test "get all vertices" { 1160 | var graph = try scaffold(); 1161 | defer graph.deinit(); 1162 | 1163 | const data = try generateTestData(&graph); 1164 | 1165 | const vertices = graph.getAllVertices(); 1166 | try expectEqualSlices(HypergraphZId, &[_]HypergraphZId{ data.v_a, data.v_b, data.v_c, data.v_d, data.v_e }, vertices); 1167 | } 1168 | 1169 | test "append vertex to hyperedge" { 1170 | var graph = try scaffold(); 1171 | defer graph.deinit(); 1172 | 1173 | const hyperedge_id = try graph.createHyperedge(.{}); 1174 | try expect(hyperedge_id != 0); 1175 | 1176 | const first_vertex_id = try graph.createVertex(.{}); 1177 | const second_vertex_id = try graph.createVertex(.{}); 1178 | try expect(first_vertex_id != 0); 1179 | 1180 | try expectError(HypergraphZError.HyperedgeNotFound, graph.appendVertexToHyperedge(max_id, max_id)); 1181 | 1182 | try expectError(HypergraphZError.VertexNotFound, graph.appendVertexToHyperedge(hyperedge_id, max_id)); 1183 | 1184 | try graph.appendVertexToHyperedge(hyperedge_id, first_vertex_id); 1185 | try graph.appendVertexToHyperedge(hyperedge_id, second_vertex_id); 1186 | const vertices = try graph.getHyperedgeVertices(hyperedge_id); 1187 | try expect(vertices.len == 2); 1188 | try expect(vertices[0] == first_vertex_id); 1189 | try expect(vertices[1] == second_vertex_id); 1190 | } 1191 | 1192 | test "prepend vertex to hyperedge" { 1193 | var graph = try scaffold(); 1194 | defer graph.deinit(); 1195 | 1196 | const hyperedge_id = try graph.createHyperedge(.{}); 1197 | try expect(hyperedge_id != 0); 1198 | 1199 | const first_vertex_id = try graph.createVertex(.{}); 1200 | const second_vertex_id = try graph.createVertex(.{}); 1201 | try expect(first_vertex_id != 0); 1202 | 1203 | try expectError(HypergraphZError.HyperedgeNotFound, graph.prependVertexToHyperedge(max_id, max_id)); 1204 | 1205 | try expectError(HypergraphZError.VertexNotFound, graph.prependVertexToHyperedge(hyperedge_id, max_id)); 1206 | 1207 | try graph.prependVertexToHyperedge(hyperedge_id, first_vertex_id); 1208 | try graph.prependVertexToHyperedge(hyperedge_id, second_vertex_id); 1209 | const vertices = try graph.getHyperedgeVertices(hyperedge_id); 1210 | try expect(vertices.len == 2); 1211 | try expect(vertices[0] == second_vertex_id); 1212 | try expect(vertices[1] == first_vertex_id); 1213 | } 1214 | 1215 | test "insert vertex into hyperedge" { 1216 | var graph = try scaffold(); 1217 | defer graph.deinit(); 1218 | 1219 | const hyperedge_id = try graph.createHyperedge(.{}); 1220 | try expect(hyperedge_id != 0); 1221 | 1222 | const first_vertex_id = try graph.createVertex(.{}); 1223 | const second_vertex_id = try graph.createVertex(.{}); 1224 | try expect(first_vertex_id != 0); 1225 | 1226 | try expectError(HypergraphZError.HyperedgeNotFound, graph.insertVertexIntoHyperedge(max_id, max_id, 0)); 1227 | 1228 | try expectError(HypergraphZError.VertexNotFound, graph.insertVertexIntoHyperedge(hyperedge_id, max_id, 0)); 1229 | 1230 | try expectError(HypergraphZError.IndexOutOfBounds, graph.insertVertexIntoHyperedge(hyperedge_id, first_vertex_id, 10)); 1231 | 1232 | try graph.insertVertexIntoHyperedge(hyperedge_id, first_vertex_id, 0); 1233 | try graph.insertVertexIntoHyperedge(hyperedge_id, second_vertex_id, 0); 1234 | const vertices = try graph.getHyperedgeVertices(hyperedge_id); 1235 | try expect(vertices.len == 2); 1236 | try expect(vertices[0] == second_vertex_id); 1237 | try expect(vertices[1] == first_vertex_id); 1238 | } 1239 | 1240 | test "get hyperedge vertices" { 1241 | var graph = try scaffold(); 1242 | defer graph.deinit(); 1243 | 1244 | const hyperedge_id = try graph.createHyperedge(.{}); 1245 | 1246 | const nb_vertices = 10; 1247 | for (0..nb_vertices) |_| { 1248 | const vertex_id = try graph.createVertex(.{}); 1249 | try graph.appendVertexToHyperedge(hyperedge_id, vertex_id); 1250 | } 1251 | 1252 | try expectError(HypergraphZError.HyperedgeNotFound, graph.getHyperedgeVertices(max_id)); 1253 | 1254 | const vertices = try graph.getHyperedgeVertices(hyperedge_id); 1255 | try expect(vertices.len == nb_vertices); 1256 | } 1257 | 1258 | test "append vertices to hyperedge" { 1259 | var graph = try scaffold(); 1260 | defer graph.deinit(); 1261 | 1262 | const hyperedge_id = try graph.createHyperedge(.{}); 1263 | try expect(hyperedge_id != 0); 1264 | 1265 | // Create 10 vertices and store their ids. 1266 | const nb_vertices = 10; 1267 | var arr: ArrayListUnmanaged(HypergraphZId) = .empty; 1268 | defer arr.deinit(std.testing.allocator); 1269 | for (0..nb_vertices) |_| { 1270 | const id = try graph.createVertex(.{}); 1271 | try arr.append(std.testing.allocator, id); 1272 | } 1273 | const ids = arr.items; 1274 | 1275 | try expectError(HypergraphZError.HyperedgeNotFound, graph.appendVerticesToHyperedge(max_id, ids)); 1276 | 1277 | try expect(try graph.appendVerticesToHyperedge(hyperedge_id, &.{}) == undefined); 1278 | 1279 | // Append first vertex, then the rest and check that appending works. 1280 | try graph.appendVertexToHyperedge(hyperedge_id, ids[0]); 1281 | try graph.appendVerticesToHyperedge(hyperedge_id, ids[1..nb_vertices]); 1282 | const vertices = try graph.getHyperedgeVertices(hyperedge_id); 1283 | try expect(vertices.len == nb_vertices); 1284 | for (ids, 0..) |id, i| { 1285 | try expect(vertices[i] == id); 1286 | const hyperedges = try graph.getVertexHyperedges(id); 1287 | try expect(hyperedges.len == 1); 1288 | try expect(hyperedges[0] == hyperedge_id); 1289 | } 1290 | } 1291 | 1292 | test "prepend vertices to hyperedge" { 1293 | var graph = try scaffold(); 1294 | defer graph.deinit(); 1295 | 1296 | const hyperedge_id = try graph.createHyperedge(.{}); 1297 | try expect(hyperedge_id != 0); 1298 | 1299 | // Create 10 vertices and store their ids. 1300 | const nb_vertices = 10; 1301 | var arr: ArrayListUnmanaged(HypergraphZId) = .empty; 1302 | defer arr.deinit(std.testing.allocator); 1303 | for (0..nb_vertices) |_| { 1304 | const id = try graph.createVertex(.{}); 1305 | try arr.append(std.testing.allocator, id); 1306 | } 1307 | const ids = arr.items; 1308 | 1309 | try expectError(HypergraphZError.HyperedgeNotFound, graph.prependVerticesToHyperedge(max_id, ids)); 1310 | 1311 | try expect(try graph.prependVerticesToHyperedge(hyperedge_id, &.{}) == undefined); 1312 | 1313 | // Prepend the last vertex, then the rest and check that prepending works. 1314 | try graph.prependVertexToHyperedge(hyperedge_id, ids[nb_vertices - 1]); 1315 | try graph.prependVerticesToHyperedge(hyperedge_id, ids[0 .. nb_vertices - 1]); 1316 | const vertices = try graph.getHyperedgeVertices(hyperedge_id); 1317 | try expect(vertices.len == nb_vertices); 1318 | for (ids, 0..) |id, i| { 1319 | try expect(vertices[i] == id); 1320 | const hyperedges = try graph.getVertexHyperedges(id); 1321 | try expect(hyperedges.len == 1); 1322 | try expect(hyperedges[0] == hyperedge_id); 1323 | } 1324 | } 1325 | 1326 | test "insert vertices into hyperedge" { 1327 | var graph = try scaffold(); 1328 | defer graph.deinit(); 1329 | 1330 | const hyperedge_id = try graph.createHyperedge(.{}); 1331 | try expect(hyperedge_id != 0); 1332 | 1333 | // Create 10 vertices and store their ids. 1334 | const nb_vertices = 10; 1335 | var arr: ArrayListUnmanaged(HypergraphZId) = .empty; 1336 | defer arr.deinit(std.testing.allocator); 1337 | for (0..nb_vertices) |_| { 1338 | const id = try graph.createVertex(.{}); 1339 | try arr.append(std.testing.allocator, id); 1340 | } 1341 | const ids = arr.items; 1342 | 1343 | try expectError(HypergraphZError.HyperedgeNotFound, graph.insertVerticesIntoHyperedge(max_id, ids, 0)); 1344 | 1345 | try expectError(HypergraphZError.NoVerticesToInsert, graph.insertVerticesIntoHyperedge(hyperedge_id, &.{}, 0)); 1346 | 1347 | try expectError(HypergraphZError.IndexOutOfBounds, graph.insertVerticesIntoHyperedge(hyperedge_id, ids, 10)); 1348 | 1349 | // Insert the first vertex, then the rest and check that inserting works. 1350 | try graph.insertVertexIntoHyperedge(hyperedge_id, ids[0], 0); 1351 | try graph.insertVerticesIntoHyperedge(hyperedge_id, ids[1..nb_vertices], 1); 1352 | const vertices = try graph.getHyperedgeVertices(hyperedge_id); 1353 | try expect(vertices.len == nb_vertices); 1354 | for (ids, 0..) |id, i| { 1355 | try expect(vertices[i] == id); 1356 | const hyperedges = try graph.getVertexHyperedges(id); 1357 | try expect(hyperedges.len == 1); 1358 | try expect(hyperedges[0] == hyperedge_id); 1359 | } 1360 | } 1361 | 1362 | test "get vertex hyperedges" { 1363 | var graph = try scaffold(); 1364 | defer graph.deinit(); 1365 | 1366 | const hyperedge_id = try graph.createHyperedge(.{}); 1367 | 1368 | const vertex_id = try graph.createVertex(.{}); 1369 | try graph.appendVertexToHyperedge(hyperedge_id, vertex_id); 1370 | 1371 | try expectError(HypergraphZError.VertexNotFound, graph.getVertexHyperedges(max_id)); 1372 | 1373 | const hyperedges = try graph.getVertexHyperedges(vertex_id); 1374 | try expect(hyperedges.len == 1); 1375 | } 1376 | 1377 | test "count hyperedges" { 1378 | var graph = try scaffold(); 1379 | defer graph.deinit(); 1380 | 1381 | const hyperedge_id = try graph.createHyperedge(.{}); 1382 | try expect(graph.countHyperedges() == 1); 1383 | try graph.deleteHyperedge(hyperedge_id, false); 1384 | try expect(graph.countHyperedges() == 0); 1385 | } 1386 | 1387 | test "count vertices" { 1388 | var graph = try scaffold(); 1389 | defer graph.deinit(); 1390 | 1391 | const vertex_id = try graph.createVertex(.{}); 1392 | try expect(graph.countVertices() == 1); 1393 | try graph.deleteVertex(vertex_id); 1394 | try expect(graph.countVertices() == 0); 1395 | } 1396 | 1397 | test "delete vertex from hyperedge" { 1398 | var graph = try scaffold(); 1399 | defer graph.deinit(); 1400 | 1401 | const hyperedge_id = try graph.createHyperedge(.{}); 1402 | 1403 | const vertex_id = try graph.createVertex(.{}); 1404 | 1405 | // Insert the vertex twice. 1406 | try graph.appendVertexToHyperedge(hyperedge_id, vertex_id); 1407 | try graph.appendVertexToHyperedge(hyperedge_id, vertex_id); 1408 | 1409 | try expectError(HypergraphZError.HyperedgeNotFound, graph.deleteVertexFromHyperedge(max_id, vertex_id)); 1410 | 1411 | try expectError(HypergraphZError.VertexNotFound, graph.deleteVertexFromHyperedge(hyperedge_id, max_id)); 1412 | 1413 | try graph.deleteVertexFromHyperedge(hyperedge_id, vertex_id); 1414 | const vertices = try graph.getHyperedgeVertices(hyperedge_id); 1415 | try expect(vertices.len == 0); 1416 | 1417 | const hyperedges = try graph.getVertexHyperedges(vertex_id); 1418 | try expect(hyperedges.len == 0); 1419 | } 1420 | 1421 | test "delete hyperedge only" { 1422 | var graph = try scaffold(); 1423 | defer graph.deinit(); 1424 | 1425 | const hyperedge_id = try graph.createHyperedge(.{}); 1426 | 1427 | // Create 10 vertices and store their ids. 1428 | const nb_vertices = 10; 1429 | var arr: ArrayListUnmanaged(HypergraphZId) = .empty; 1430 | defer arr.deinit(std.testing.allocator); 1431 | for (0..nb_vertices) |_| { 1432 | const id = try graph.createVertex(.{}); 1433 | try arr.append(std.testing.allocator, id); 1434 | } 1435 | // Add the same vertex twice. 1436 | try arr.append(std.testing.allocator, arr.items[arr.items.len - 1]); 1437 | const ids = arr.items; 1438 | 1439 | try graph.appendVerticesToHyperedge(hyperedge_id, ids); 1440 | 1441 | try graph.deleteHyperedge(hyperedge_id, false); 1442 | for (ids) |id| { 1443 | const hyperedges = try graph.getVertexHyperedges(id); 1444 | try expect(hyperedges.len == 0); 1445 | } 1446 | 1447 | try expectError(HypergraphZError.HyperedgeNotFound, graph.getHyperedge(hyperedge_id)); 1448 | } 1449 | 1450 | test "delete hyperedge and vertices" { 1451 | var graph = try scaffold(); 1452 | defer graph.deinit(); 1453 | 1454 | const hyperedge_id = try graph.createHyperedge(.{}); 1455 | 1456 | // Create 10 vertices and store their ids. 1457 | const nb_vertices = 10; 1458 | var arr: ArrayListUnmanaged(HypergraphZId) = .empty; 1459 | defer arr.deinit(std.testing.allocator); 1460 | for (0..nb_vertices) |_| { 1461 | const id = try graph.createVertex(.{}); 1462 | try arr.append(std.testing.allocator, id); 1463 | } 1464 | // Add the same vertex twice. 1465 | try arr.append(std.testing.allocator, arr.items[arr.items.len - 1]); 1466 | const ids = arr.items; 1467 | 1468 | try graph.appendVerticesToHyperedge(hyperedge_id, ids); 1469 | 1470 | try graph.deleteHyperedge(hyperedge_id, true); 1471 | for (ids) |id| { 1472 | try expectError(HypergraphZError.VertexNotFound, graph.getVertex(id)); 1473 | } 1474 | 1475 | try expectError(HypergraphZError.HyperedgeNotFound, graph.getHyperedge(hyperedge_id)); 1476 | } 1477 | 1478 | test "delete vertex" { 1479 | var graph = try scaffold(); 1480 | defer graph.deinit(); 1481 | 1482 | const hyperedge_id = try graph.createHyperedge(.{}); 1483 | const vertex_id = try graph.createVertex(.{}); 1484 | 1485 | // Insert the vertex twice. 1486 | try graph.appendVertexToHyperedge(hyperedge_id, vertex_id); 1487 | try graph.appendVertexToHyperedge(hyperedge_id, vertex_id); 1488 | 1489 | try expectError(HypergraphZError.VertexNotFound, graph.deleteVertex(max_id)); 1490 | 1491 | try graph.deleteVertex(vertex_id); 1492 | const vertices = try graph.getHyperedgeVertices(hyperedge_id); 1493 | try expect(vertices.len == 0); 1494 | try expectError(HypergraphZError.VertexNotFound, graph.getVertex(vertex_id)); 1495 | } 1496 | 1497 | test "delete vertex by index from hyperedge" { 1498 | var graph = try scaffold(); 1499 | defer graph.deinit(); 1500 | 1501 | const hyperedge_id = try graph.createHyperedge(.{}); 1502 | 1503 | // Create 10 vertices and store their ids. 1504 | // Last two vertices are duplicated. 1505 | const nb_vertices = 10; 1506 | var arr: ArrayListUnmanaged(HypergraphZId) = .empty; 1507 | defer arr.deinit(std.testing.allocator); 1508 | for (0..nb_vertices, 0..) |_, i| { 1509 | if (i == nb_vertices - 1) { 1510 | try arr.append(std.testing.allocator, arr.items[arr.items.len - 1]); 1511 | continue; 1512 | } 1513 | const id = try graph.createVertex(.{}); 1514 | try arr.append(std.testing.allocator, id); 1515 | } 1516 | const ids = arr.items; 1517 | 1518 | // Append vertices to the hyperedge. 1519 | try graph.appendVerticesToHyperedge(hyperedge_id, ids); 1520 | 1521 | try expectError(HypergraphZError.HyperedgeNotFound, graph.deleteVertexByIndexFromHyperedge(max_id, 0)); 1522 | 1523 | // Delete the first vertex. 1524 | // The hyperedge should be dropped from the relations. 1525 | try graph.deleteVertexByIndexFromHyperedge(hyperedge_id, 0); 1526 | const vertices = try graph.getHyperedgeVertices(hyperedge_id); 1527 | try expect(vertices.len == nb_vertices - 1); 1528 | for (ids[1..], 0..) |id, i| { 1529 | try expect(vertices[i] == id); 1530 | } 1531 | const first_vertex_hyperedges = try graph.getVertexHyperedges(ids[0]); 1532 | try expect(first_vertex_hyperedges.len == 0); 1533 | 1534 | // Delete the last vertex. 1535 | // The hyperedge should not be dropped from the relations. 1536 | try graph.deleteVertexByIndexFromHyperedge(hyperedge_id, nb_vertices - 2); 1537 | const last_vertex_hyperedges = try graph.getVertexHyperedges(ids[nb_vertices - 3]); 1538 | try expect(last_vertex_hyperedges.len == 1); 1539 | } 1540 | 1541 | test "get vertex indegree" { 1542 | var graph = try scaffold(); 1543 | defer graph.deinit(); 1544 | 1545 | const data = try generateTestData(&graph); 1546 | 1547 | try expectError(HypergraphZError.VertexNotFound, graph.getVertexIndegree(max_id)); 1548 | 1549 | try expect(try graph.getVertexIndegree(data.v_a) == 2); 1550 | try expect(try graph.getVertexIndegree(data.v_b) == 2); 1551 | try expect(try graph.getVertexIndegree(data.v_c) == 3); 1552 | try expect(try graph.getVertexIndegree(data.v_d) == 2); 1553 | try expect(try graph.getVertexIndegree(data.v_e) == 3); 1554 | } 1555 | 1556 | test "get vertex outdegree" { 1557 | var graph = try scaffold(); 1558 | defer graph.deinit(); 1559 | 1560 | const data = try generateTestData(&graph); 1561 | 1562 | try expectError(HypergraphZError.VertexNotFound, graph.getVertexOutdegree(max_id)); 1563 | 1564 | try expect(try graph.getVertexOutdegree(data.v_a) == 2); 1565 | try expect(try graph.getVertexOutdegree(data.v_b) == 2); 1566 | try expect(try graph.getVertexOutdegree(data.v_c) == 3); 1567 | try expect(try graph.getVertexOutdegree(data.v_d) == 2); 1568 | try expect(try graph.getVertexOutdegree(data.v_e) == 3); 1569 | } 1570 | 1571 | test "update hyperedge" { 1572 | var graph = try scaffold(); 1573 | defer graph.deinit(); 1574 | 1575 | const hyperedge_id = try graph.createHyperedge(.{}); 1576 | 1577 | try expectError(HypergraphZError.HyperedgeNotFound, graph.updateHyperedge(max_id, .{})); 1578 | 1579 | try graph.updateHyperedge(hyperedge_id, .{ .meow = true }); 1580 | const hyperedge = try graph.getHyperedge(hyperedge_id); 1581 | try expect(@TypeOf(hyperedge) == Hyperedge); 1582 | try expect(hyperedge.meow); 1583 | } 1584 | 1585 | test "update vertex" { 1586 | var graph = try scaffold(); 1587 | defer graph.deinit(); 1588 | 1589 | const vertex_id = try graph.createVertex(.{}); 1590 | 1591 | try expectError(HypergraphZError.VertexNotFound, graph.updateVertex(max_id, .{})); 1592 | 1593 | try graph.updateVertex(vertex_id, .{ .purr = true }); 1594 | const vertex = try graph.getVertex(vertex_id); 1595 | try expect(@TypeOf(vertex) == Vertex); 1596 | try expect(vertex.purr); 1597 | } 1598 | 1599 | test "get intersections" { 1600 | var graph = try scaffold(); 1601 | defer graph.deinit(); 1602 | 1603 | const data = try generateTestData(&graph); 1604 | 1605 | try expectError(HypergraphZError.NotEnoughHyperedgesProvided, graph.getIntersections(&[_]HypergraphZId{1})); 1606 | 1607 | const hyperedges = [_]HypergraphZId{ data.h_a, data.h_b, data.h_c }; 1608 | const expected = [_]HypergraphZId{ data.v_e, data.v_a }; 1609 | const intersections = try graph.getIntersections(&hyperedges); 1610 | defer graph.allocator.free(intersections); 1611 | try std.testing.expectEqualSlices(HypergraphZId, &expected, intersections); 1612 | } 1613 | 1614 | test "get vertex adjacency to" { 1615 | var graph = try scaffold(); 1616 | defer graph.deinit(); 1617 | 1618 | const data = try generateTestData(&graph); 1619 | 1620 | try expectError(HypergraphZError.VertexNotFound, graph.getVertexAdjacencyTo(max_id)); 1621 | 1622 | { 1623 | var result = try graph.getVertexAdjacencyTo(data.v_a); 1624 | defer result.deinit(std.testing.allocator); 1625 | try expect(result.data.count() == 2); 1626 | var it = result.data.iterator(); 1627 | var i: usize = 0; 1628 | while (it.next()) |*kv| { 1629 | if (i == 0) { 1630 | try expect(kv.key_ptr.* == data.h_b); 1631 | try expect(kv.value_ptr.*.items.len == 1); 1632 | try expect(kv.value_ptr.*.items[0] == data.v_e); 1633 | } else if (i == 1) { 1634 | try expect(kv.key_ptr.* == data.h_c); 1635 | try expect(kv.value_ptr.*.items.len == 1); 1636 | try expect(kv.value_ptr.*.items[0] == data.v_e); 1637 | } 1638 | i += 1; 1639 | } 1640 | } 1641 | 1642 | { 1643 | var result = try graph.getVertexAdjacencyTo(data.v_b); 1644 | defer result.deinit(std.testing.allocator); 1645 | try expect(result.data.count() == 2); 1646 | var it = result.data.iterator(); 1647 | var i: usize = 0; 1648 | while (it.next()) |*kv| { 1649 | if (i == 0) { 1650 | try expect(kv.key_ptr.* == data.h_a); 1651 | try expect(kv.value_ptr.*.items.len == 1); 1652 | try expect(kv.value_ptr.*.items[0] == data.v_a); 1653 | } else if (i == 1) { 1654 | try expect(kv.key_ptr.* == data.h_c); 1655 | try expect(kv.value_ptr.*.items.len == 1); 1656 | try expect(kv.value_ptr.*.items[0] == data.v_d); 1657 | } 1658 | i += 1; 1659 | } 1660 | } 1661 | 1662 | { 1663 | var result = try graph.getVertexAdjacencyTo(data.v_c); 1664 | defer result.deinit(std.testing.allocator); 1665 | try expect(result.data.count() == 2); 1666 | var it = result.data.iterator(); 1667 | var i: usize = 0; 1668 | while (it.next()) |*kv| { 1669 | if (i == 0) { 1670 | try expect(kv.key_ptr.* == data.h_a); 1671 | try expect(kv.value_ptr.*.items.len == 1); 1672 | try expect(kv.value_ptr.*.items[0] == data.v_b); 1673 | } else if (i == 1) { 1674 | try expect(kv.key_ptr.* == data.h_c); 1675 | try expect(kv.value_ptr.*.items.len == 2); 1676 | try expect(kv.value_ptr.*.items[0] == data.v_b); 1677 | try expect(kv.value_ptr.*.items[1] == data.v_c); 1678 | } 1679 | i += 1; 1680 | } 1681 | } 1682 | 1683 | { 1684 | var result = try graph.getVertexAdjacencyTo(data.v_d); 1685 | defer result.deinit(std.testing.allocator); 1686 | try expect(result.data.count() == 2); 1687 | var it = result.data.iterator(); 1688 | var i: usize = 0; 1689 | while (it.next()) |*kv| { 1690 | if (i == 0) { 1691 | try expect(kv.key_ptr.* == data.h_a); 1692 | try expect(kv.value_ptr.*.items.len == 1); 1693 | try expect(kv.value_ptr.*.items[0] == data.v_c); 1694 | } else if (i == 1) { 1695 | try expect(kv.key_ptr.* == data.h_c); 1696 | try expect(kv.value_ptr.*.items.len == 1); 1697 | try expect(kv.value_ptr.*.items[0] == data.v_a); 1698 | } 1699 | i += 1; 1700 | } 1701 | } 1702 | 1703 | { 1704 | var result = try graph.getVertexAdjacencyTo(data.v_e); 1705 | defer result.deinit(std.testing.allocator); 1706 | try expect(result.data.count() == 3); 1707 | var it = result.data.iterator(); 1708 | var i: usize = 0; 1709 | while (it.next()) |*kv| { 1710 | if (i == 0) { 1711 | try expect(kv.key_ptr.* == data.h_a); 1712 | try expect(kv.value_ptr.*.items.len == 1); 1713 | try expect(kv.value_ptr.*.items[0] == data.v_d); 1714 | } else if (i == 1) { 1715 | try expect(kv.key_ptr.* == data.h_b); 1716 | try expect(kv.value_ptr.*.items.len == 1); 1717 | try expect(kv.value_ptr.*.items[0] == data.v_e); 1718 | } else if (i == 2) { 1719 | try expect(kv.key_ptr.* == data.h_c); 1720 | try expect(kv.value_ptr.*.items.len == 1); 1721 | try expect(kv.value_ptr.*.items[0] == data.v_c); 1722 | } 1723 | i += 1; 1724 | } 1725 | } 1726 | } 1727 | 1728 | test "get vertex adjacency from" { 1729 | var graph = try scaffold(); 1730 | defer graph.deinit(); 1731 | 1732 | const data = try generateTestData(&graph); 1733 | 1734 | try expectError(HypergraphZError.VertexNotFound, graph.getVertexAdjacencyFrom(max_id)); 1735 | 1736 | { 1737 | var result = try graph.getVertexAdjacencyFrom(data.v_a); 1738 | defer result.deinit(std.testing.allocator); 1739 | try expect(result.data.count() == 2); 1740 | var it = result.data.iterator(); 1741 | var i: usize = 0; 1742 | while (it.next()) |*kv| { 1743 | if (i == 0) { 1744 | try expect(kv.key_ptr.* == data.h_a); 1745 | try expect(kv.value_ptr.*.items.len == 1); 1746 | try expect(kv.value_ptr.*.items[0] == data.v_b); 1747 | } else if (i == 1) { 1748 | try expect(kv.key_ptr.* == data.h_c); 1749 | try expect(kv.value_ptr.*.items.len == 1); 1750 | try expect(kv.value_ptr.*.items[0] == data.v_d); 1751 | } 1752 | i += 1; 1753 | } 1754 | } 1755 | 1756 | { 1757 | var result = try graph.getVertexAdjacencyFrom(data.v_b); 1758 | defer result.deinit(std.testing.allocator); 1759 | try expect(result.data.count() == 2); 1760 | var it = result.data.iterator(); 1761 | var i: usize = 0; 1762 | while (it.next()) |*kv| { 1763 | if (i == 0) { 1764 | try expect(kv.key_ptr.* == data.h_a); 1765 | try expect(kv.value_ptr.*.items.len == 1); 1766 | try expect(kv.value_ptr.*.items[0] == data.v_c); 1767 | } else if (i == 1) { 1768 | try expect(kv.key_ptr.* == data.h_c); 1769 | try expect(kv.value_ptr.*.items.len == 1); 1770 | try expect(kv.value_ptr.*.items[0] == data.v_c); 1771 | } 1772 | i += 1; 1773 | } 1774 | } 1775 | 1776 | { 1777 | var result = try graph.getVertexAdjacencyFrom(data.v_c); 1778 | defer result.deinit(std.testing.allocator); 1779 | try expect(result.data.count() == 2); 1780 | var it = result.data.iterator(); 1781 | var i: usize = 0; 1782 | while (it.next()) |*kv| { 1783 | if (i == 0) { 1784 | try expect(kv.key_ptr.* == data.h_a); 1785 | try expect(kv.value_ptr.*.items.len == 1); 1786 | try expect(kv.value_ptr.*.items[0] == data.v_d); 1787 | } else if (i == 1) { 1788 | try expect(kv.key_ptr.* == data.h_c); 1789 | try expect(kv.value_ptr.*.items.len == 2); 1790 | try expect(kv.value_ptr.*.items[0] == data.v_c); 1791 | try expect(kv.value_ptr.*.items[1] == data.v_e); 1792 | } 1793 | i += 1; 1794 | } 1795 | } 1796 | 1797 | { 1798 | var result = try graph.getVertexAdjacencyFrom(data.v_d); 1799 | defer result.deinit(std.testing.allocator); 1800 | try expect(result.data.count() == 2); 1801 | var it = result.data.iterator(); 1802 | var i: usize = 0; 1803 | while (it.next()) |*kv| { 1804 | if (i == 0) { 1805 | try expect(kv.key_ptr.* == data.h_a); 1806 | try expect(kv.value_ptr.*.items.len == 1); 1807 | try expect(kv.value_ptr.*.items[0] == data.v_e); 1808 | } else if (i == 1) { 1809 | try expect(kv.key_ptr.* == data.h_c); 1810 | try expect(kv.value_ptr.*.items.len == 1); 1811 | try expect(kv.value_ptr.*.items[0] == data.v_b); 1812 | } 1813 | i += 1; 1814 | } 1815 | } 1816 | 1817 | { 1818 | var result = try graph.getVertexAdjacencyFrom(data.v_e); 1819 | defer result.deinit(std.testing.allocator); 1820 | try expect(result.data.count() == 2); 1821 | var it = result.data.iterator(); 1822 | var i: usize = 0; 1823 | while (it.next()) |*kv| { 1824 | if (i == 0) { 1825 | try expect(kv.key_ptr.* == data.h_b); 1826 | try expect(kv.value_ptr.*.items.len == 2); 1827 | try expect(kv.value_ptr.*.items[0] == data.v_e); 1828 | try expect(kv.value_ptr.*.items[1] == data.v_a); 1829 | } else if (i == 1) { 1830 | try expect(kv.key_ptr.* == data.h_c); 1831 | try expect(kv.value_ptr.*.items.len == 1); 1832 | try expect(kv.value_ptr.*.items[0] == data.v_a); 1833 | } 1834 | i += 1; 1835 | } 1836 | } 1837 | } 1838 | 1839 | test "find shortest path" { 1840 | var graph = try scaffold(); 1841 | defer graph.deinit(); 1842 | 1843 | const data = try generateTestData(&graph); 1844 | 1845 | try expectError(HypergraphZError.VertexNotFound, graph.findShortestPath(max_id, data.v_a)); 1846 | try expectError(HypergraphZError.VertexNotFound, graph.findShortestPath(data.v_a, max_id)); 1847 | 1848 | { 1849 | var result = try graph.findShortestPath(data.v_a, data.v_e); 1850 | defer result.deinit(std.testing.allocator); 1851 | 1852 | try expectEqualSlices(HypergraphZId, &[_]HypergraphZId{ data.v_a, data.v_d, data.v_e }, result.data.?.items); 1853 | } 1854 | 1855 | { 1856 | var result = try graph.findShortestPath(data.v_b, data.v_e); 1857 | defer result.deinit(std.testing.allocator); 1858 | 1859 | try expectEqualSlices(HypergraphZId, &[_]HypergraphZId{ data.v_b, data.v_c, data.v_e }, result.data.?.items); 1860 | } 1861 | 1862 | { 1863 | var result = try graph.findShortestPath(data.v_d, data.v_a); 1864 | defer result.deinit(std.testing.allocator); 1865 | 1866 | try expectEqualSlices(HypergraphZId, &[_]HypergraphZId{ data.v_d, data.v_e, data.v_a }, result.data.?.items); 1867 | } 1868 | 1869 | { 1870 | var result = try graph.findShortestPath(data.v_c, data.v_b); 1871 | defer result.deinit(std.testing.allocator); 1872 | 1873 | try expectEqualSlices(HypergraphZId, &[_]HypergraphZId{ data.v_c, data.v_d, data.v_b }, result.data.?.items); 1874 | } 1875 | 1876 | { 1877 | var result = try graph.findShortestPath(data.v_d, data.v_b); 1878 | defer result.deinit(std.testing.allocator); 1879 | try expectEqualSlices(HypergraphZId, &[_]HypergraphZId{ data.v_d, data.v_b }, result.data.?.items); 1880 | } 1881 | 1882 | { 1883 | var result = try graph.findShortestPath(data.v_c, data.v_c); 1884 | defer result.deinit(std.testing.allocator); 1885 | try expectEqualSlices(HypergraphZId, &[_]HypergraphZId{data.v_c}, result.data.?.items); 1886 | } 1887 | 1888 | { 1889 | var result = try graph.findShortestPath(data.v_e, data.v_e); 1890 | defer result.deinit(std.testing.allocator); 1891 | try expectEqualSlices(HypergraphZId, &[_]HypergraphZId{data.v_e}, result.data.?.items); 1892 | } 1893 | 1894 | { 1895 | const disconnected = try graph.createVertex(Vertex{}); 1896 | var result = try graph.findShortestPath(data.v_a, disconnected); 1897 | defer result.deinit(std.testing.allocator); 1898 | try expect(result.data == null); 1899 | } 1900 | } 1901 | 1902 | test "reverse hyperedge" { 1903 | var graph = try scaffold(); 1904 | defer graph.deinit(); 1905 | 1906 | const data = try generateTestData(&graph); 1907 | 1908 | try expectError(HypergraphZError.HyperedgeNotFound, graph.reverseHyperedge(max_id)); 1909 | 1910 | try graph.reverseHyperedge(data.h_a); 1911 | const vertices = try graph.getHyperedgeVertices(data.h_a); 1912 | try expect(vertices.len == 5); 1913 | try expect(vertices[0] == data.v_e); 1914 | try expect(vertices[4] == data.v_a); 1915 | } 1916 | 1917 | test "join hyperedges" { 1918 | var graph = try scaffold(); 1919 | defer graph.deinit(); 1920 | 1921 | const data = try generateTestData(&graph); 1922 | 1923 | try expectError(HypergraphZError.HyperedgeNotFound, graph.joinHyperedges(&[_]HypergraphZId{ max_id - 1, max_id })); 1924 | try expectError(HypergraphZError.NotEnoughHyperedgesProvided, graph.joinHyperedges(&[_]HypergraphZId{data.h_a})); 1925 | 1926 | try graph.joinHyperedges(&[_]HypergraphZId{ data.h_a, data.h_c }); 1927 | const vertices = try graph.getHyperedgeVertices(data.h_a); 1928 | try expectEqualSlices(HypergraphZId, &[_]HypergraphZId{ 1929 | data.v_a, data.v_b, data.v_c, data.v_d, data.v_e, 1930 | data.v_b, data.v_c, data.v_c, data.v_e, data.v_a, 1931 | data.v_d, data.v_b, 1932 | }, vertices); 1933 | try expectError(HypergraphZError.HyperedgeNotFound, graph.getHyperedge(data.h_c)); 1934 | } 1935 | 1936 | test "contract hyperedge" { 1937 | var graph = try scaffold(); 1938 | defer graph.deinit(); 1939 | 1940 | const data = try generateTestData(&graph); 1941 | 1942 | try graph.contractHyperedge(data.h_b); 1943 | 1944 | const h_a = try graph.getHyperedgeVertices(data.h_a); 1945 | try expectEqualSlices(HypergraphZId, &[_]HypergraphZId{ data.v_a, data.v_b, data.v_c, data.v_d, data.v_a }, h_a); 1946 | 1947 | try expectError(HypergraphZError.HyperedgeNotFound, graph.getHyperedgeVertices(data.h_b)); 1948 | 1949 | const h_c = try graph.getHyperedgeVertices(data.h_c); 1950 | try expectEqualSlices(HypergraphZId, &[_]HypergraphZId{ data.v_b, data.v_c, data.v_c, data.v_a, data.v_d, data.v_b }, h_c); 1951 | } 1952 | 1953 | test "clear hypergraph" { 1954 | var graph = try scaffold(); 1955 | defer graph.deinit(); 1956 | 1957 | graph.clear(); 1958 | const hyperedges = graph.getAllHyperedges(); 1959 | const vertices = graph.getAllVertices(); 1960 | try expect(hyperedges.len == 0); 1961 | try expect(vertices.len == 0); 1962 | } 1963 | 1964 | test "get hyperedges connecting vertices" { 1965 | var graph = try scaffold(); 1966 | defer graph.deinit(); 1967 | 1968 | const data = try generateTestData(&graph); 1969 | 1970 | try expectError(HypergraphZError.VertexNotFound, graph.getHyperedgesConnecting(max_id, data.v_b)); 1971 | try expectError(HypergraphZError.VertexNotFound, graph.getHyperedgesConnecting(data.v_a, max_id)); 1972 | 1973 | { 1974 | var result = try graph.getHyperedgesConnecting(data.v_a, data.v_b); 1975 | defer result.deinit(std.testing.allocator); 1976 | var i: usize = 0; 1977 | var it = result.data.iterator(); 1978 | while (it.next()) |*kv| { 1979 | if (i == 0) { 1980 | try expect(kv.key_ptr.* == data.h_a); 1981 | } else if (i == 1) { 1982 | try expect(kv.key_ptr.* == data.h_c); 1983 | } 1984 | i += 1; 1985 | } 1986 | try expect(i == 2); 1987 | } 1988 | 1989 | { 1990 | var result = try graph.getHyperedgesConnecting(data.v_b, data.v_b); 1991 | defer result.deinit(std.testing.allocator); 1992 | var i: usize = 0; 1993 | var it = result.data.iterator(); 1994 | 1995 | while (it.next()) |*kv| { 1996 | if (i == 0) { 1997 | try expect(kv.key_ptr.* == data.h_c); 1998 | } 1999 | i += 1; 2000 | } 2001 | try expect(i == 1); 2002 | } 2003 | 2004 | { 2005 | var result = try graph.getHyperedgesConnecting(data.v_b, data.v_c); 2006 | defer result.deinit(std.testing.allocator); 2007 | var i: usize = 0; 2008 | var it = result.data.iterator(); 2009 | 2010 | while (it.next()) |*kv| { 2011 | if (i == 0) { 2012 | try expect(kv.key_ptr.* == data.h_a); 2013 | } else if (i == 1) { 2014 | try expect(kv.key_ptr.* == data.h_c); 2015 | } 2016 | i += 1; 2017 | } 2018 | try expect(i == 2); 2019 | } 2020 | 2021 | { 2022 | var result = try graph.getHyperedgesConnecting(data.v_c, data.v_c); 2023 | defer result.deinit(std.testing.allocator); 2024 | var i: usize = 0; 2025 | var it = result.data.iterator(); 2026 | 2027 | while (it.next()) |*kv| { 2028 | if (i == 0) { 2029 | try expect(kv.key_ptr.* == data.h_c); 2030 | } 2031 | i += 1; 2032 | } 2033 | try expect(i == 1); 2034 | } 2035 | 2036 | { 2037 | var result = try graph.getHyperedgesConnecting(data.v_e, data.v_e); 2038 | defer result.deinit(std.testing.allocator); 2039 | var i: usize = 0; 2040 | var it = result.data.iterator(); 2041 | 2042 | while (it.next()) |*kv| { 2043 | if (i == 0) { 2044 | try expect(kv.key_ptr.* == data.h_b); 2045 | } 2046 | i += 1; 2047 | } 2048 | try expect(i == 1); 2049 | } 2050 | } 2051 | 2052 | test "get endpoints" { 2053 | var graph = try scaffold(); 2054 | defer graph.deinit(); 2055 | 2056 | const data = try generateTestData(&graph); 2057 | 2058 | var result = try graph.getEndpoints(); 2059 | defer result.deinit(); 2060 | 2061 | const initial = result.initial.slice(); 2062 | try expect(initial.len == 3); 2063 | for (initial.items(.vertex_id), initial.items(.hyperedge_id), 0..) |v, h, i| { 2064 | if (i == 0) { 2065 | try expect(data.v_a == v); 2066 | try expect(data.h_a == h); 2067 | } else if (i == 1) { 2068 | try expect(data.v_e == v); 2069 | try expect(data.h_b == h); 2070 | } else if (i == 2) { 2071 | try expect(data.v_b == v); 2072 | try expect(data.h_c == h); 2073 | } 2074 | } 2075 | 2076 | const terminal = result.terminal.slice(); 2077 | try expect(terminal.len == 3); 2078 | for (terminal.items(.vertex_id), terminal.items(.hyperedge_id), 0..) |v, h, i| { 2079 | if (i == 0) { 2080 | try expect(data.v_e == v); 2081 | try expect(data.h_a == h); 2082 | } else if (i == 1) { 2083 | try expect(data.v_a == v); 2084 | try expect(data.h_b == h); 2085 | } else if (i == 2) { 2086 | try expect(data.v_b == v); 2087 | try expect(data.h_c == h); 2088 | } 2089 | } 2090 | } 2091 | 2092 | test "get orphan hyperedges" { 2093 | var graph = try scaffold(); 2094 | defer graph.deinit(); 2095 | 2096 | _ = try generateTestData(&graph); 2097 | 2098 | const orphan = try graph.createHyperedge(.{}); 2099 | const orphans = try graph.getOrphanHyperedges(); 2100 | defer graph.allocator.free(orphans); 2101 | try expectEqualSlices(HypergraphZId, &[_]HypergraphZId{orphan}, orphans); 2102 | } 2103 | 2104 | test "get orphan vertices" { 2105 | var graph = try scaffold(); 2106 | defer graph.deinit(); 2107 | 2108 | _ = try generateTestData(&graph); 2109 | 2110 | const orphan = try graph.createVertex(.{}); 2111 | const orphans = try graph.getOrphanVertices(); 2112 | defer graph.allocator.free(orphans); 2113 | try expectEqualSlices(HypergraphZId, &[_]HypergraphZId{orphan}, orphans); 2114 | } 2115 | 2116 | test "reserve hyperedges" { 2117 | var graph = try HypergraphZ( 2118 | Hyperedge, 2119 | Vertex, 2120 | ).init(std.testing.allocator, .{}); 2121 | defer graph.deinit(); 2122 | 2123 | try expect(graph.countHyperedges() == 0); 2124 | try expect(graph.hyperedges.capacity() == 0); 2125 | // Put more than `linear_scan_max`. 2126 | try graph.reserveHyperedges(20); 2127 | for (0..20) |_| { 2128 | _ = try graph.createHyperedgeAssumeCapacity(.{}); 2129 | } 2130 | try expect(graph.hyperedges.capacity() > 20); 2131 | // Calling `createHyperedgeAssumeCapacity` will panic but we can't test 2132 | // it, see: https://github.com/ziglang/zig/issues/1356. 2133 | } 2134 | 2135 | test "reserve vertices" { 2136 | var graph = try HypergraphZ( 2137 | Hyperedge, 2138 | Vertex, 2139 | ).init(std.testing.allocator, .{}); 2140 | defer graph.deinit(); 2141 | 2142 | try expect(graph.countVertices() == 0); 2143 | try expect(graph.vertices.capacity() == 0); 2144 | // Put more than `linear_scan_max`. 2145 | try graph.reserveVertices(20); 2146 | for (0..20) |_| { 2147 | _ = try graph.createVertexAssumeCapacity(.{}); 2148 | } 2149 | try expect(graph.vertices.capacity() > 20); 2150 | // Calling `createVertexAssumeCapacity` will panic but we can't test 2151 | // it, see: https://github.com/ziglang/zig/issues/1356. 2152 | } 2153 | --------------------------------------------------------------------------------