core

package
v0.1.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 21, 2026 License: AGPL-3.0 Imports: 6 Imported by: 0

Documentation

Overview

Package core - deterministic, thread-safe in-memory graphs for serious work.

----------------------------------------------------------------------------- -- WHAT ---------------------------------------------------------------------

A single, composable Graph type G=(V,E) with predictable iteration order, strict sentinel errors, explicit configuration flags, and no ambient globals.

The package provides a stable, auditable contract suitable for algorithms that require reproducibility (tests, golden outputs, deterministic traversals) and a simple concurrency model for safe parallel reads and controlled mutations.

----------------------------------------------------------------------------- -- WHY ----------------------------------------------------------------------

  • Determinism First: Public enumeration order is documented and stable. This removes flakiness from order-sensitive tests and prevents reproducibility drift.

  • Concurrency Without Drama: Two RWMutexes (muVert, muEdgeAdj) separate vertex/config state from topology state (edges/adjacency) to reduce contention while remaining simple.

  • Mixed-Mode Mastery: Per-edge directedness overrides are explicit and legal only when mixed-mode is enabled. Violations return ErrMixedEdgesNotAllowed; there is no silent fallback.

  • Identity Discipline: Default Edge IDs are monotonic textual identifiers ("e1","e2",...). This is useful for stable logs, diffs, and external correlation.

  • Practical API Surface: One Graph with explicit flags (Directed/Weighted/Multi/Loops/Mixed) instead of multiple graph types that fragment algorithms.

  • No Magic, No Surprises: Sentinel errors only (errors.Is), explicit capability flags, and no hidden global state. If a capability is disabled, the package returns the relevant ErrX.

----------------------------------------------------------------------------- -- WHEN ---------------------------------------------------------------------

  • Need reproducible graph results (order-sensitive logic, golden tests).
  • Need to combine directed/undirected/loops/multi-edges within one instance.
  • Need safe concurrent reads with a clear lock model and controlled mutations.
  • Need stable Edge IDs across clones/views for debugging/analytics.

----------------------------------------------------------------------------- -- CONTRACT LAWS ------------------------------------------------------------

Determinism law:

  • Vertices() returns Vertex.ID values sorted lexicographically ascending.
  • Edges(), GetNamedEdges(), and Neighbors() return edges sorted by Edge.ID ascending.
  • NeighborIDs() returns unique adjacent vertex IDs sorted lexicographically ascending.
  • AdjacencyList() returns fresh per-vertex edge-ID slices sorted by Edge.ID ascending; map key iteration order remains Go-map order and is therefore not deterministic.

Construction / validation law:

  • NewGraph and NewMixedGraph validate public GraphOption inputs and return errors; constructor validation is part of the public contract.
  • AddEdge validates public EdgeOption inputs before lock acquisition, vertex auto-creation, or edge publication.

Ownership / aliasing law:

  • Detached container/value surfaces include Vertices(), NeighborIDs(), AdjacencyList(), Stats(), counts, and membership predicates.
  • Alias-based catalog accessors include VerticesMap(), GetEdge(), GetNamedEdges(), Edges(), and Neighbors().
  • Alias-based accessors may return freshly allocated containers, but the contained *Vertex/*Edge values alias live catalog records.
  • Structural records MUST be treated as immutable once published in a graph: Vertex.ID, Edge.ID, Edge.From, Edge.To, Edge.Weight, and Edge.Directed.
  • Vertex.Metadata is caller-managed payload. Clone/View operations shallow-copy the Metadata map pointer; core neither deep-copies nor synchronizes it.

Compatibility law:

  • InternalVertices() is retained only as a deprecated compatibility surface. New code must not treat it as an internal-storage escape hatch.

Concurrency law:

  • Public methods are safe for concurrent use according to their documented locks.
  • Retained aliased pointers do not extend lock scope, do not provide snapshot isolation, and do not remain membership proofs after later mutations.

----------------------------------------------------------------------------- -- DESIGN INVARIANTS --------------------------------------------------------

  1. Deterministic ordering (public contract): - Vertices() → IDs sorted lexicographically ascending. - Edges() → by Edge.ID ascending (string lex order). Note: lex order is over strings, so "e10" sorts between "e1" and "e2". Auto-generated IDs are monotonic in numeric suffix, but API ordering is defined by string sort order. - NeighborIDs(id) → unique IDs sorted lexicographically ascending. - Neighbors(id) → edges sorted by Edge.ID ascending.

  2. Sentinel errors only; compare with errors.Is. Exported operations return stable package-level sentinels. Callers must not rely on string matching.

  3. No mutation of caller-owned inputs. Views/clones are explicit and named; they never mutate the source graph.

  4. Concurrency model: two RWMutexes with fixed lock order. - muVert guards the vertex catalog and configuration flags. - muEdgeAdj guards the edge catalog and sparse adjacency index. - When both locks are needed, the required order is muVert -> muEdgeAdj. - Internal adjacency cleanup helpers never acquire muVert; callers either already hold the needed vertex lock or are performing edge-only mutations.

  5. Storage truth model: catalogs vs indexes. - g.vertices is the authoritative vertex-membership catalog. - g.edges is the authoritative edge-membership catalog. - g.adjacencyList is a private sparse edge index, not a second vertex catalog. - Empty adjacency buckets may be absent internally. - Therefore, absence of adjacencyList[id] does NOT imply absence of vertex id. - Public AdjacencyList() reconstructs a full graph-facing snapshot from g.vertices and includes isolated vertices with empty edge-ID slices. - Matrix-shaped representations belong outside this storage layer; use matrix.NewAdjacencyMatrix(graph, options) or matrix.BuildDenseAdjacency(vertices, edges, options) when a complete dense/sparse mathematical matrix is required.

----------------------------------------------------------------------------- -- CONFIGURATION (GraphOption) ----------------------------------------------

GraphOption values are applied only during construction (NewGraph/NewMixedGraph). After construction, flags are immutable.

  • WithDirected(defaultDirected bool) Sets the default edge orientation for newly created edges.

  • WithMixedEdges() Enables per-edge directedness overrides (WithEdgeDirected). Without it, AddEdge(..., WithEdgeDirected(...)) → ErrMixedEdgesNotAllowed.

  • WithWeighted() Permits non-zero edge weights. Without it, AddEdge(weight!=0) → ErrBadWeight.

  • WithMultiEdges() Permits parallel edges between identical endpoints. Without it, a second AddEdge(from,to,...) → ErrMultiEdgeNotAllowed.

  • WithLoops() Permits self-loops (from==to). Without it, AddEdge(v,v,...) → ErrLoopNotAllowed.

Helper constructor:

  • NewMixedGraph(opts ...GraphOption) = NewGraph(WithMixedEdges(), opts...) Ensures mixed-mode is enabled before any other options are applied.

----------------------------------------------------------------------------- -- EDGE OPTIONS (EdgeOption) ------------------------------------------------

EdgeOption values configure a single edge during AddEdge. Options are applied sequentially in call order. The first option returning an error aborts AddEdge without mutating the graph.

  • WithEdgeDirected(directed bool) Overrides Directed for this edge. Contract: allowed only when MixedEdges()==true; otherwise ErrMixedEdgesNotAllowed.

  • WithID(id string) Assigns a custom Edge.ID for the new edge. Contract:

  • id must be non-empty, else returns ErrEmptyEdgeID.

  • id must be globally unique within the graph, else returns ErrEdgeIDConflict. Note:

  • If id matches the canonical auto-ID form "eN", the graph advances its internal auto-ID counter to avoid future collisions.

----------------------------------------------------------------------------- -- ERROR SET (sentinels) ----------------------------------------------------

  • ErrEmptyVertexID - empty vertex ID is illegal.
  • ErrVertexNotFound - referenced vertex does not exist.
  • ErrEdgeNotFound - referenced edge does not exist.
  • ErrBadWeight - non-zero weight in an unweighted graph.
  • ErrLoopNotAllowed - self-loop when loops are disabled.
  • ErrMultiEdgeNotAllowed - parallel edge when multi-edges are disabled.
  • ErrMixedEdgesNotAllowed - per-edge directed override when mixed-mode is disabled.
  • ErrEmptyEdgeID - empty edge ID is illegal (WithID / SetEdgeID).
  • ErrEdgeIDConflict - edge ID collision (WithID / SetEdgeID).

----------------------------------------------------------------------------- -- LIFECYCLE MAPS -----------------------------------------------------------

Graph lifecycle (configuration → build → query → transform):

g := core.NewGraph(WithDirected(false), WithWeighted())
_ = g.AddVertex("A")
_ = g.AddVertex("B")
eid, err := g.AddEdge("A", "B", 10)  // undirected by default here
es  := g.Edges()                     // deterministic (Edge.ID asc, lex order)
in, out, und, _ := g.Degree("A")     // (in,out,undirected)
s   := g.Stats()                     // O(V+E) snapshot of flags & counts
g2  := g.Clone()                     // deep copy, preserves edge IDs
uv  := core.UnweightedView(g)        // same topology, weight=0, weighted=false

Vertex lifecycle:

  • Create → AddVertex(id)
  • Check → HasVertex(id)
  • Remove → RemoveVertex(id) // removes incident edges deterministically
  • Enumerate → Vertices() // sorted lex asc
  • Inspect → VerticesMap() // shallow copy (ID → *Vertex)

Edge lifecycle:

  • Create → AddEdge(from,to,weight, opts...) // policy enforced by sentinels
  • Check → HasEdge(from,to) // O(1) membership
  • Inspect → GetEdge(edgeID), Edges() // Edges() sorted by ID
  • Rename → SetEdgeID(oldID,newID) // updates catalog + adjacency atomically under lock
  • Remove → RemoveEdge(edgeID) // mirrors handled automatically
  • Filter → FilterEdges(pred) // O(E) + cleanup

Adjacency & Neighborhood:

  • Neighbors(id) → []*Edge // sorted by Edge.ID (lex asc)
  • NeighborIDs(id) → []string // unique, sorted lex asc
  • AdjacencyList() → map[id][]edgeID // per-vertex lists sorted by Edge.ID

Cloning & Views:

  • CloneEmpty() - copy flags + vertices, no edges, carry nextEdgeID.
  • Clone() - deep copy (flags + vertices + edges + adjacency), carry nextEdgeID.
  • UnweightedView(g) - same topology, weight=0, Weighted()=false. Preserves Edge.ID values and carries the edge-ID counter to prevent collisions if you later add edges to the derived graph.
  • InducedSubgraph(g, keep) - keep subset of vertices + incident edges. Preserves Edge.ID values and carries the edge-ID counter for the same reason.

----------------------------------------------------------------------------- -- COMPLEXITY SUMMARY -------------------------------------------------------

AddVertex / HasVertex / HasEdge              O(1) amortized
AddEdge                                      O(1) amortized (topologically atomic)
RemoveEdge                                   O(1) amortized + adjacency cleanup cost
RemoveVertex                                 O(E) (scans edge catalog to remove incidents)
Vertices / Edges                             O(V log V) / O(E log E) for ordering
Neighbors / NeighborIDs                      O(d log d)  (d = degree(id))
AdjacencyList                                O(V+E) assemble + per-vertex sort
Degree                                       O(E) (scans edges to ensure correct in-degree)
CloneEmpty / Clone                           O(V) / O(V+E)
Clear                                        O(1) (map reinit + counter reset)
Stats                                        O(V+E)

----------------------------------------------------------------------------- -- NON-GOALS ----------------------------------------------------------------

- No immutable persistent graph structure. - No automatic deep-copy policy for Metadata. - No silent policy fallback for weighted / mixed / loop / multigraph behavior. - No hidden mutation channel through accessor APIs. - No package-level snapshot isolation beyond documented method scope.

----------------------------------------------------------------------------- -- AI-HINT (LLM/Copilot/ChatGPT/Claude/Gemini/Qwen guidance) ----------------

  • Deterministic ordering is part of the public contract. Prefer Vertices()/Edges()/NeighborIDs() outputs directly (already sorted).

  • To use per-edge directedness, you MUST enable mixed-mode at construction time: g := core.NewMixedGraph(WithDirected(false)) _, err := g.AddEdge("A","B", 0, WithEdgeDirected(true))

  • Unweighted graphs must add edges with weight==0; otherwise AddEdge returns ErrBadWeight: g := core.NewGraph() // unweighted by default _, err := g.AddEdge("A","B", 5) // => ErrBadWeight

  • No silent fallbacks: loops/multi-edges/mixed-mode violations return explicit sentinels. Use errors.Is(err, core.ErrLoopNotAllowed) etc.

  • WithID provides stable external references: _, err := g.AddEdge("A","B", 0, WithID("stable-edge-1")) If you choose IDs of the form "eN", the graph advances its internal counter past N.

  • Cloning and views preserve textual Edge.ID sequence via nextEdgeID carry-over. If you Clone() then AddEdge(), the new edge ID continues monotonic growth.

  • Stats() is an O(V+E) snapshot suitable for diagnostics and tests. If the graph is mutated concurrently, treat Stats() as best-effort telemetry, not as a correctness-critical synchronization primitive.

  • Concurrency model: public methods manage locking internally. Avoid holding external locks around core methods to prevent lock-order coupling.

  • Use InducedSubgraph/UnweightedView to derive subproblems without mutating the input graph. Derived graphs preserve Edge.ID values and keep future AddEdge() IDs unique by carrying the internal edge-ID counter forward.

  • Do not treat g.adjacencyList as the vertex catalog. It is a sparse edge index.

  • Preserve isolated vertices in public AdjacencyList() by reading g.vertices, not by forcing empty internal adjacency buckets to exist.

----------------------------------------------------------------------------- -- See also: docs/CORE.md for algorithmic notes, proofs, and extended examples.

Package core defines deterministic, thread-safe in-memory graphs and the foundational types (Graph/Vertex/Edge) used across lvlath.

Design invariants (public contract, enforced across the codebase):

  1. Determinism: public enumeration order is stable and documented (Vertices by ID asc, Edges by Edge.ID asc, NeighborIDs by ID asc).
  2. Sentinel errors only: exported operations return package-level sentinels and are checked via errors.Is; no fmt-wrapping of those sentinels.
  3. No hidden global state: behavior is explicit via options; no ambient randomness.
  4. Concurrency: vertex catalog and edge/adjacency storage are protected by separate RWMutexes (muVert, muEdgeAdj) to reduce contention.

Notes:

  • Graph configuration flags are set only during construction (NewGraph + GraphOption). After construction they are immutable, so internal code may read them without locks to avoid lock inversion in hot paths.
Example (BetweennessCentrality)

Example_betweennessCentrality demonstrates the identification of a "critical artery" in a global logistics network using Betweenness Stress Centrality. Two densely connected communities (clusters) are linked by a single bridging edge. The bridging edge carries all shortest-path traffic between the clusters, making it the highest-betweenness edge. CONTEXT: "The Global Transit Bottleneck"

  • You are the lead architect of a global supply chain monitoring system. The graph represents two massive economic zones (Cluster A and Cluster B), each with high internal redundancy. However, they are connected by a single transit corridor (the "Suez-Link"). Your mission is to quantify the "Structural Stress" on this link. If this single edge fails, 100% of inter-cluster trade is paralyzed.

Scenario:

  • Vertices are hubs/warehouses, edges are direct transport corridors.
  • You have two dense regions (two cities / two warehouse clusters).
  • Exactly one corridor connects the regions (a bridge edge).

Why this matters (criticality):

  • If that corridor fails, inter-region delivery collapses immediately.
  • Even BEFORE failure, that corridor experiences maximal “load” because almost all cross-region shortest paths must traverse it.

MATHEMATICAL MODEL (Edge Stress):

  • For a graph partitioned into two disjoint sets V_A and V_B, where all paths between sets must traverse a single bridge edge (e_bridge), the "Load" (L) is:

    L(e_bridge) = |V_A| * |V_B|

  • This represents the total number of unique shortest-path pairs (s, t) such that s ∈ V_A and t ∈ V_B. In this topology, the bridge edge carries the maximum possible Betweenness Centrality.

Closed-form load (for this topology):

  • Every pair (a in A, b in B) must traverse the bridge.
  • Therefore bridgeLoad = |A| * |B|.

Implementation:

  • Stage 1: Construct two clusters of vertices with rich internal connections.
  • Stage 2: Link the clusters with a single edge and identify this edge.
  • Stage 3: Calculate the number of unique shortest-path pairs that traverse the bridge (betweenness load).

Behavior highlights:

  • The identified bridge edge is an articulation link between clusters (its removal would disconnect the graph).
  • The bridge's betweenness load equals the product of cluster sizes, as every inter-cluster pair of vertices must communicate via this edge.

Inputs:

  • None (graph structure is deterministic).

Returns:

  • None (prints the critical edge ID and its computed load).

Errors:

  • Any unexpected error is printed and the example returns early.

Complexity:

  • Graph construction: O(V^2) for dense cluster edges. Identifying the bridge and computing load: O(V + E).

CORE PACKAGE LEVERAGE:

  • Topology Verification: Uses GetEdge(id) for O(1) validation of critical links.
  • Connectivity Analysis: Leverages Incidence(v) to inspect the local "fan-out" of a hub vertex and identify the bridging edge among local connections.
  • Inventory Integrity: Uses the deterministic Vertices() sequence to partition and calculate global load factors without external state tracking.
package main

import (
	"fmt"
	"strings"

	"github.com/lvlath/go/core"
)

func main() {
	// Constants for simulation scale (4x4 clusters for the example output)
	const clusterSize = 4
	const bridgeID = "e13"

	// Stage 1: Infrastructure Construction
	// We initialize a non-directed graph representing physical transport corridors.
	g, _ := core.NewGraph(core.WithDirected(false))

	// Pre-allocate slices to avoid repeated allocations in loops.
	vertsA := make([]string, clusterSize)
	vertsB := make([]string, clusterSize)

	for i := 0; i < clusterSize; i++ {
		vertsA[i] = fmt.Sprintf("A%d", i)
		vertsB[i] = fmt.Sprintf("B%d", i)
	}

	// Build two Cliques (fully connected clusters).
	// This simulates high-density metropolitan or regional warehouse networks.
	for i := 0; i < clusterSize; i++ {
		for j := i + 1; j < clusterSize; j++ {
			_, _ = g.AddEdge(vertsA[i], vertsA[j], 0)
			_, _ = g.AddEdge(vertsB[i], vertsB[j], 0)
		}
	}

	// Stage 2: The Critical Integration (The Bottleneck)
	// We link the two clusters through a single point of failure.
	_, err := g.AddEdge(vertsA[0], vertsB[0], 0)
	if err != nil {
		fmt.Printf("Critical failure during bridge creation: %v\n", err)
		return
	}

	// Stage 3: Structural Analysis
	// Verify the bridge exists and analyze its impact.
	bridge, err := g.GetEdge(bridgeID)
	if err != nil {
		fmt.Printf("Link verification failed: %v\n", err)
		return
	}

	// Calculate Stress Load: L = |V_A| * |V_B|.
	// We use core.Vertices() to perform a census of the economic zones.
	var countA, countB int
	for _, v := range g.Vertices() {
		if strings.HasPrefix(v, "A") {
			countA++
		} else if strings.HasPrefix(v, "B") {
			countB++
		}
	}

	stressLoad := countA * countB

	// Stage 4: Reporting and Verification
	// Verify that hub A0 is indeed a proxy by analyzing its neighbors.
	// Use NeighborIDs for a quick inspection of local connections.
	neighbors, _ := g.NeighborIDs(vertsA[0])
	var isBottleneckFound bool
	for _, nID := range neighbors {
		if nID == vertsB[0] {
			isBottleneckFound = true
			break
		}
	}

	// Output results using stable identifiers for documentation.
	if isBottleneckFound {
		fmt.Printf("Analysis: Critical Link Identified: %s (%s)\n", bridgeID, bridge.From+"-"+bridge.To)
		fmt.Printf("Load: Betweenness Stress Factor = %d paths\n", stressLoad)
	}

}
Output:
Analysis: Critical Link Identified: e13 (A0-B0)
Load: Betweenness Stress Factor = 16 paths
Example (CascadingFailures)

Example_cascadingFailures demonstrates a cascading failure scenario in a power grid network. A highly connected hub node is removed to simulate a substation failure, and the impact on network connectivity is measured. Example_cascadingFailures demonstrates cascading-failure analysis in a power grid. CONTEXT:

  • You are a Resilience Architect for the 'Aethelgard' energy grid.
  • A critical infrastructure node (Hub) is targeted by a cyber-kinetic strike.
  • Objective: Predict the "Cascade Collapse Index" before the physical failure occurs.

Scenario:

  • You operate a smart-city grid graph: vertices are substations, edges are physical lines.
  • An incident (physical fault / cyberattack) disables a single high-degree hub substation.
  • Your job is to quantify whether the grid “degrades gracefully” or splits into islands.

Why this matters (criticality):

  • In real grids, the most dangerous failures are not “one line is down” but “a cut point is down”.
  • A single vertex can be a topological single point of failure (cut-vertex).
  • You need fast “what-if” evaluation without corrupting the production topology.

MATHEMATICAL MODEL:

  1. Survival Coefficient (Resilience Ratio) 'R': R = N'_LCC / (N_LCC - 1) Measures how much of the Giant Component (LCC) remains after the hub's evaporation.
  2. Fragility Index 'Φ': Φ = 1 - (Σ deg(v_adj) / deg(v_target)) Quantifies topological dependency. A high Φ indicates that neighbors are dangerously dependent on the target node for their connectivity.

Metric (resilience ratio):

  • Let N_LCC be the size of the Largest Connected Component (LCC) BEFORE the incident.
  • Let N'_LCC be the size of the LCC AFTER removing the incident vertex.
  • Resilience ratio: R = N'_LCC / (N_LCC - 1)
  • Interpretation:
  • R close to 1 -> removal barely hurts connectivity.
  • R close to 0 -> removal fractures the grid into small islands.

Implementation:

  • Stage 1: Build two dense clusters (districts) connected only via a single hub.
  • Stage 2: Clone() the topology and RemoveVertex(hub) in the clone (sandbox simulation).
  • Stage 3: Compute LCC size via BFS using NeighborIDs (deterministic neighbor ordering).

CORE PACKAGE LEVERAGE:

  • Snapshot Isolation: Uses core.Clone() to spawn a "shadow reality" for destructive testing without mutating the production graph.
  • Atomic Cleanup: core.RemoveVertex(id) ensures no orphaned edges remain, providing a clean state for the subsequent BFS traversal.
  • Structural Inspection: Uses core.Degree and core.AdjacentVertices to compute second-order topological metrics (Φ).

Inputs:

  • None (graph structure is hard-coded).

Returns:

  • None (prints the resilience ratio R).

Errors:

  • Any unexpected error is printed and the example returns early.

Complexity:

  • Building and scanning the graph: O(V + E). BFS for components: O(V + E).
package main

import (
	"fmt"

	"github.com/lvlath/go/core"
)

func main() {
	// ---- Stage 1: Infrastructure Synthesis ----
	const clusterSize = 4
	var (
		err       error
		neighbors []string
		hubID     = "Hub-Central"
		districtA = []string{"A1", "A2", "A3", "A4"}
		districtB = []string{"B1", "B2", "B3", "B4"}
	)

	g, _ := core.NewGraph(core.WithWeighted(), core.WithDirected(false))

	// Construct two dense districts (Cliques)
	for i := 0; i < clusterSize; i++ {
		for j := i + 1; j < clusterSize; j++ {
			if _, err = g.AddEdge(districtA[i], districtA[j], 1.0); err != nil {
				fmt.Println(err)
				return
			}
			if _, err = g.AddEdge(districtB[i], districtB[j], 1.0); err != nil {
				fmt.Println(err)
				return
			}
		}
	}

	// Link districts through a single strategic Hub (the single point of failure)
	for i := 0; i < clusterSize; i++ {
		_, _ = g.AddEdge(hubID, districtA[i], 1.0)
		_, _ = g.AddEdge(hubID, districtB[i], 1.0)
	}

	// ---- Stage 2: Pre-Collapse Fragility Analysis (Φ) ----
	_, _, hubDegree, _ := g.Degree(hubID)
	neighbors, _ = g.NeighborIDs(hubID)

	var neighborDegree, sumNeighborDegrees int
	for _, nID := range neighbors {
		_, _, neighborDegree, _ = g.Degree(nID)
		sumNeighborDegrees += neighborDegree
	}

	// Φ = 1 - (Average Neighbor Connectivity / Hub Connectivity)
	phi := 1.0 - (float64(sumNeighborDegrees) / float64(hubDegree))

	// ---- Stage 3: Sandbox Simulation (The Blackout) ----
	// core.Clone() creates a perfect isolated sandbox for destructive analysis
	sandbox := g.Clone()
	if err = sandbox.RemoveVertex(hubID); err != nil {
		fmt.Printf("Critical failure during simulation: %v\n", err)
		return
	}

	// ---- Stage 4: Topological Impact Assessment (BFS) ----
	// Expert-grade LCC (Largest Connected Component) calculation
	calcLCC := func(graph *core.Graph) int {
		var (
			maxSize     int
			allVertices = graph.Vertices()
			visited     = make(map[string]bool, len(allVertices))
			queue       = make([]string, 0, len(allVertices))
		)

		for _, root := range allVertices {
			if visited[root] {
				continue
			}

			// Component Discovery
			currentSize := 0
			queue = append(queue[:0], root) // Reset queue without re-allocating
			visited[root] = true

			for len(queue) > 0 {
				u := queue[0]
				queue = queue[1:]
				currentSize++

				adj, _ := graph.NeighborIDs(u)
				for _, v := range adj {
					if !visited[v] {
						visited[v] = true
						queue = append(queue, v)
					}
				}
			}

			if currentSize > maxSize {
				maxSize = currentSize
			}
		}

		return maxSize
	}

	nLCC := calcLCC(g)        // Giant component before attack
	npLCC := calcLCC(sandbox) // Giant component after hub removal

	// R = N'_LCC / (N_LCC - 1)
	resilience := float64(npLCC) / float64(nLCC-1)

	// ---- Stage 5: Executive Decision ----
	fmt.Printf("--- Aethelgard Grid Resilience Report ---\n")
	fmt.Printf("Target Hub Degree: %d\n", hubDegree)
	fmt.Printf("Fragility Index (Φ): %.2f\n", phi)
	fmt.Printf("Resilience Ratio (R): %.2f\n", resilience)

	if resilience < 0.6 {
		fmt.Println("STATUS: CRITICAL. System fragmentation imminent. Initiating bypass protocols.")
	} else {
		fmt.Println("STATUS: STABLE. Topology supports graceful degradation.")
	}

}
Output:
--- Aethelgard Grid Resilience Report ---
Target Hub Degree: 8
Fragility Index (Φ): -3.00
Resilience Ratio (R): 0.50
STATUS: CRITICAL. System fragmentation imminent. Initiating bypass protocols.
Example (NeuralEvolution)

Example_neuralEvolution simulates dynamic evolution of a neural network graph structure. It starts with a sparse, weighted graph (few connections), then adds a new neuron (vertex) with new connections, and finally removes an existing connection. The degree of a particular neuron is tracked through these modifications to illustrate network plasticity. CONTEXT: "Synapse-X" - The Structural Learning Engine

  • In traditional neural networks, "learning" is merely updating weights in a static matrix. In Project Synapse-X, we simulate biological neuroplasticity where the graph itself is a living organism. When associations weaken, synapses are physically destroyed (Pruning) to reclaim memory and reduce entropy. When new concepts emerge, the graph spawns new vertices and edges (Evolution).

Scenario:

  • Vertices are neurons (or concepts), edges are synapses (or associations).
  • Weights are connection strengths (requires Weighted graph).
  • Learning can create new neurons (AddVertex), strengthen/insert synapses (AddEdge), and prune unused synapses (RemoveEdge).

WHY THIS IS CRITICAL (The Engineering Edge):

  • Algorithmic Efficiency: In large-scale brains, "zeroing a weight" still keeps the connection in the adjacency list, forcing O(N^2) or O(E_total) scans. Using core.RemoveEdge(id) physically cleans the topology, ensuring neighborhood traversals (via core.AdjacentVertices) only visit active, meaningful synapses.
  • Topological Integrity: core.AddVertex(id) allows the network to expand its associative memory dynamically without re-initializing the system.

MATHEMATICAL MODEL (Structural Homeostasis):

  • Network Density (D): D = (2 * |E|) / (|V| * (|V| - 1)). The system monitors D to prevent a "connectivity explosion" (over-wiring).
  • Pruning Logic: When a synapse decays, the system identifies the topological link via NeighborIDs() and Edge verification, then executes core.RemoveEdge(id) to maintain energy efficiency.

Implementation:

  • Stage 1: Build a sparse weighted graph.
  • Stage 2: Add a new neuron and connect it.
  • Stage 3: Remove one existing edge (synaptic pruning).
  • Stage 4: Query Degree at each stage.

Inputs:

  • None (uses deterministic graph modifications).

Returns:

  • None (prints the tracked degree values).

Errors:

  • Any unexpected error is printed and the example returns early.

Complexity:

  • Graph updates (add/remove): O(1) each amortized. Degree queries: O(d) per query.

CORE PACKAGE LEVERAGE:

  • Targeted Retrieval: Using EdgeBetween(u, v) provides O(1) or O(d) access to specific synapses, avoiding expensive global Edge() scans.
  • Amortized O(1) Updates: Add/Remove operations leverage core's map-based architecture for high-frequency structural shifts.
package main

import (
	"fmt"

	"github.com/lvlath/go/core"
)

func main() {
	// ---- PHASE 1: Initial Cognitive Seed (Sparse Substrate) ----
	// Initialize an undirected, weighted graph representing the base neural cluster.
	g, _ := core.NewGraph(core.WithDirected(false), core.WithWeighted())

	// Primary synaptic pathways (Initial Knowledge)
	// AddEdge returns (ID, error). We use "_" as we track them by topology later.
	_, _ = g.AddEdge("0", "1", 0.5)
	_, _ = g.AddEdge("1", "2", 0.8)
	_, _ = g.AddEdge("3", "4", 1.2)

	// Capture baseline plasticity: connectivity of Neuron "2".
	// Degree returns (in, out, total, error).
	_, _, degInit, _ := g.Degree("2")

	// ---- PHASE 2: Evolutionary Expansion (Learning Spike) ----
	// A new concept "5" emerges, forging a strong bond with the existing hub (Neuron 2).
	if err := g.AddVertex("5"); err != nil {
		return
	}

	// Forging new synapses based on conceptual proximity.
	_, _ = g.AddEdge("5", "2", 0.7)
	_, _ = g.AddEdge("5", "4", 0.4)

	// Audit: Neuron "2" degree increases as it integrates the new concept.
	_, _, degAfterAdd, _ := g.Degree("2")

	// ---- PHASE 3: Synaptic Pruning (Homeostatic Optimization) ----
	// The system detects that the synapse between "1" and "2" has become "stale".
	// To prune it, we surgically identify its ID from the active Edges list.
	var targetID string
	for _, e := range g.Edges() {
		// In an undirected graph, we check both directions for the From/To pair.
		if (e.From == "1" && e.To == "2") || (e.From == "2" && e.To == "1") {
			targetID = e.ID
			break
		}
	}

	// Execute physical decommissioning of the connection.
	if targetID != "" {
		if err := g.RemoveEdge(targetID); err != nil {
			return
		}
	}

	// Final State: The network is optimized and ready for the next learning cycle.
	_, _, degAfterRem, _ := g.Degree("2")

	// ---- OUTPUT: Structural Pulse Monitoring ----
	// This confirms the successful growth and pruning cycles of the system.
	fmt.Printf("deg[2][0]=%d\n", degInit)
	fmt.Printf("deg[2][1]=%d\n", degAfterAdd)
	fmt.Printf("deg[2][2]=%d\n", degAfterRem)

}
Output:
deg[2][0]=1
deg[2][1]=2
deg[2][2]=1

Index

Examples

Constants

This section is empty.

Variables

View Source
var (
	// ErrEmptyVertexID signals that the provided vertex identifier is empty.
	//
	// Contract:
	//   - Any API that accepts a vertex ID MUST reject "" with this sentinel.
	ErrEmptyVertexID = errors.New("core: vertex ID is empty")

	// ErrVertexNotFound indicates that a referenced vertex does not exist.
	//
	// Contract:
	//   - Returned by query/mutation APIs that require a pre-existing vertex.
	ErrVertexNotFound = errors.New("core: vertex not found")

	// ErrEdgeNotFound indicates that a referenced edge (by Edge.ID) was not found.
	//
	// Contract:
	//   - Returned by edge-removal or lookup routines.
	ErrEdgeNotFound = errors.New("core: edge not found")

	// ErrBadWeight reports a non-zero weight on an unweighted graph.
	//
	// Contract:
	//   - On graphs without WithWeighted(), only weight == 0 is allowed.
	ErrBadWeight = errors.New("core: bad weight for unweighted graph")
	ErrNaNInf    = errors.New("core: weight is NaN or Inf")

	// ErrLoopNotAllowed reports a self-loop attempt when loops are disabled.
	//
	// Contract:
	//   - WithLoops() must be set to allow edges (v -> v).
	ErrLoopNotAllowed = errors.New("core: self-loop not allowed")

	// ErrEmptyEdgeID signals that an explicit edge ID was required but empty.
	//
	// Contract:
	//   - AddEdge(..., WithID("")) MUST return ErrEmptyEdgeID.
	//   - SetEdgeID(old,"") MUST return ErrEmptyEdgeID.
	//
	// Notes:
	//   - This sentinel is about edge identifiers (Edge.ID), not vertex IDs.
	ErrEmptyEdgeID = errors.New("core: empty edge ID")

	// ErrEdgeIDConflict signals that an explicit edge ID collides with an existing edge.
	//
	// Contract:
	//   - AddEdge(..., WithID(id)) MUST return ErrEdgeIDConflict if id is already present.
	//   - SetEdgeID(old,id) MUST return ErrEdgeIDConflict if id is already present.
	//
	// Determinism:
	//   - Collision checks are pure map membership checks; no iteration order dependence.
	ErrEdgeIDConflict = errors.New("core: edge ID already exists")

	// ErrMultiEdgeNotAllowed reports a parallel edge attempt when multi-edges are disabled.
	//
	// Contract:
	//   - WithMultiEdges() must be set to allow (u,v) duplication (or directional duplicates).
	ErrMultiEdgeNotAllowed = errors.New("core: multi-edges not allowed")

	// ErrMixedEdgesNotAllowed reports a per-edge directedness override on a non-mixed graph.
	//
	// Contract:
	//   - WithMixedEdges() (or NewMixedGraph) must be set before any WithEdgeDirected(...) override.
	ErrMixedEdgesNotAllowed = errors.New("core: mixed-mode per-edge overrides not allowed")

	// ErrNilGraphOption reports a nil GraphOption passed to NewGraph or NewMixedGraph.
	//
	// Contract:
	//   - NewGraph(nil) MUST return ErrNilGraphOption.
	//   - NewGraph(..., nil, ...) MUST return ErrNilGraphOption.
	//   - NewMixedGraph(..., nil, ...) MUST preserve this sentinel via delegation to NewGraph.
	//
	// Notes:
	//   - Nil constructor options are invalid public inputs, not no-ops and not panics.
	ErrNilGraphOption = errors.New("core: nil graph option")

	// ErrNilEdgeOption reports a nil EdgeOption passed to AddEdge.
	//
	// Contract:
	//   - AddEdge(..., nil) MUST return ErrNilEdgeOption.
	//   - AddEdge(..., opt1, nil, opt2) MUST return ErrNilEdgeOption.
	//
	// Notes:
	//   - Nil per-edge options are rejected during fail-fast validation before any lock
	//     acquisition, vertex auto-creation, or edge publication.
	ErrNilEdgeOption = errors.New("core: nil edge option")

	// ErrInvalidEdgeOption reports that an EdgeOption attempted to mutate
	// topology-owned edge fields after AddEdge had already validated the public input.
	//
	// Contract:
	//   - AddEdge owns Edge.From, Edge.To, and Edge.Weight after initial validation.
	//   - EdgeOption values may set Edge.ID via WithID.
	//   - EdgeOption values may set Edge.Directed only through an explicitly allowed
	//     mixed-mode policy.
	//   - Endpoint or weight mutation is rejected before ID assignment, catalog insertion,
	//     or adjacency mutation.
	//
	// Notes:
	//   - Directedness mutation on a non-mixed graph is classified separately as
	//     ErrMixedEdgesNotAllowed because it is a policy violation, not an endpoint/weight
	//     corruption attempt.
	ErrInvalidEdgeOption = errors.New("core: invalid edge option mutation")

	// ErrNilEdgePredicate reports a nil predicate passed to an edge-filtering API.
	//
	// Contract:
	//   - FilterEdges(nil) MUST return ErrNilEdgePredicate.
	//   - RemoveEdgesWhere(nil) MUST return ErrNilEdgePredicate.
	//   - Nil predicates are invalid public input, not no-ops and not panics.
	ErrNilEdgePredicate = errors.New("core: nil edge predicate")
)

Functions

This section is empty.

Types

type Edge

type Edge struct {
	// ID is a unique string identifier for the edge (auto-generated by default, or provided via WithID).
	// Once published in a Graph, it MUST be treated as immutable.
	ID string

	// From is the source vertex ID (for undirected edges this is one endpoint).
	// Once published in a Graph, it MUST be treated as immutable.
	From string

	// To is the destination vertex ID (for undirected edges this is the other endpoint).
	// Once published in a Graph, it MUST be treated as immutable.
	To string

	// Weight is the edge cost/capacity; must be 0 unless the graph is WithWeighted().
	// Once published in a Graph, it MUST be treated as immutable.
	Weight float64

	// Directed = true means the edge is asymmetric (From -> To only).
	// Directed = false means the edge is symmetric (From <-> To, adjacency mirrored).
	// Once published in a Graph, it MUST be treated as immutable.
	Directed bool
}

Edge is the canonical connection record used by Graph; the unique key is Edge.ID. Endpoints are stored as vertex IDs to keep Graph storage compact and deterministic.

Implementation:

  • Stage 1: AddEdge constructs a baseline Edge with graph defaults.
  • Stage 2: AddEdge applies EdgeOptions sequentially (first error aborts).
  • Stage 3: Edge is registered in the edge catalog and adjacency is updated.

Ownership / Mutation Law:

  • Graph query methods may return *Edge aliases to catalog records.
  • Once published in a Graph, Edge.ID, Edge.From, Edge.To, Edge.Weight, and Edge.Directed MUST be treated as immutable by callers.
  • If detached mutable ownership is required, callers must allocate their own copy.

Behavior highlights:

  • ID is unique within a graph for the graph lifetime.
  • For undirected edges (Directed=false), adjacency is mirrored.

Inputs:

  • ID: unique edge identifier; auto-generated unless WithID is used.
  • From/To: endpoint vertex IDs.
  • Weight: must be 0 unless the graph is WithWeighted().
  • Directed: effective directionality (may be overridden per-edge only in mixed mode).

Returns:

  • N/A (data type).

Errors:

  • N/A (data type).

Determinism:

  • Edge.ID is the stable ordering key for public enumeration.

Complexity:

  • N/A (data type).

Notes:

  • Auto-generated IDs are of the form "eN" where N is a monotonically increasing counter.

AI-Hints:

  • Treat returned *Edge pointers from getters as read-only to avoid data races.

func (*Edge) IsNil

func (e *Edge) IsNil() bool

IsNil reports whether the receiver should be treated as nil when stored inside interfaces.

Implementation:

  • Stage 1: Compare the receiver pointer to nil.
  • Stage 2: Return the result without dereferencing.

Behavior highlights:

  • Safe for typed-nil stored inside interfaces (no panic).
  • Reflect-free nil detection used by validators and test helpers via core.Nilable.

Returns:

  • bool: true iff receiver == nil.

Errors:

  • None.

Determinism:

  • Deterministic.

Complexity:

  • Time O(1), Space O(1).

Notes:

  • Keep this method trivial; do not add deep validation.

AI-Hints:

  • Use Nilable-aware helpers to correctly detect typed nils behind interfaces.

type EdgeOption

type EdgeOption func(g *Graph, e *Edge) error

EdgeOption configures a single edge during AddEdge.

Implementation:

  • Stage 1: AddEdge performs stateless validation, including nil EdgeOption rejection.
  • Stage 2: AddEdge builds a baseline Edge from the validated endpoints, weight, and graph default directedness.
  • Stage 3: AddEdge applies EdgeOptions sequentially; the first error aborts edge publication.
  • Stage 4: AddEdge validates that options did not mutate topology-owned fields before assigning an ID or publishing adjacency.

Behavior highlights:

  • Options MUST NOT panic as part of the public contract.
  • A nil EdgeOption is rejected with ErrNilEdgeOption before invocation.
  • Options MUST NOT mutate global graph state as a side effect.
  • Options may set only Edge.ID and, when mixed mode is enabled, Edge.Directed.
  • Options MUST NOT mutate Edge.From, Edge.To, or Edge.Weight.
  • Options should be O(1) and deterministic.

Inputs:

  • g: the owning graph; options may inspect immutable policy flags and the edge catalog only as documented by the specific option.
  • e: the unpublished edge being configured.

Returns:

  • error: nil on success; otherwise a stable sentinel error (ErrMixedEdgesNotAllowed, ErrEmptyEdgeID, ErrEdgeIDConflict, ErrInvalidEdgeOption, ...).

Errors:

  • Nil option values are rejected by AddEdge with ErrNilEdgeOption before invocation.
  • Endpoint or weight mutation is rejected by AddEdge with ErrInvalidEdgeOption.
  • Directedness override without mixed mode is rejected with ErrMixedEdgesNotAllowed.

Determinism:

  • Deterministic given deterministic inputs; option order is call-order stable.

Complexity:

  • Time O(1) per option, Space O(1).

Notes:

  • AddEdge applies non-nil options under the edge/adjacency write lock; options must not take additional graph locks or call graph methods.

AI-Hints:

  • Keep options local: set ID or allowed directedness only; never rewrite endpoints.

func WithEdgeDirected

func WithEdgeDirected(directed bool) EdgeOption

WithEdgeDirected overrides directedness for a single edge.

Implementation:

  • Stage 1: Validate that the graph was constructed with WithMixedEdges().
  • Stage 2: Assign e.Directed = directed.

Behavior highlights:

  • Requires WithMixedEdges mode; otherwise returns ErrMixedEdgesNotAllowed.

Inputs:

  • directed: desired directedness for this edge.

Returns:

  • EdgeOption: per-edge mutator.

Errors:

  • ErrMixedEdgesNotAllowed: if the graph does not allow per-edge directedness overrides.

Determinism:

  • Deterministic; constant-time flag set.

Complexity:

  • Time O(1), Space O(1).

Notes:

  • This option does not change endpoints; only directionality.

AI-Hints:

  • Use mixed mode only when you truly need both orientations in one graph.

func WithID

func WithID(id string) EdgeOption

WithID assigns a custom identifier to the edge created by AddEdge.

Implementation:

  • Stage 1: Validate id is non-empty (ErrEmptyEdgeID).
  • Stage 2: Under AddEdge's edge lock, check the edge catalog for collisions (ErrEdgeIDConflict).
  • Stage 3: Set e.ID = id.
  • Stage 4: If id matches the canonical auto-ID form "eN", bump the auto-ID counter so that future auto-generated IDs never collide with the explicit "eN".

Behavior highlights:

  • Works regardless of mixed mode.
  • Deterministic: collision is a strict membership check, not a scan.

Inputs:

  • id: desired edge identifier; must be non-empty and globally unique within the graph.

Returns:

  • EdgeOption: per-edge mutator.

Errors:

  • ErrEmptyEdgeID: if id == "".
  • ErrEdgeIDConflict: if id is already present in the edge catalog.

Determinism:

  • Deterministic; does not depend on map iteration order.

Complexity:

  • Time O(1), Space O(1).

Notes:

  • If you choose IDs of the canonical auto form "eN", the graph will advance its auto-ID counter past N.

AI-Hints:

  • Use WithID for stable external references (golden tests, trace correlation, interop).
  • Prefer non-auto-shaped IDs (e.g., "road_A_B") when you do not want to affect the auto-ID counter.

type Graph

type Graph struct {
	// contains filtered or unexported fields
}

Graph is a thread-safe, deterministic in-memory graph storage kernel.

Contract role:

  • Graph stores vertices and edges; it does not implement graph algorithms.
  • Algorithm packages must consume Graph through documented public methods (Vertices, Edges, Neighbors, NeighborIDs, AdjacencyList, Stats, ...), not by assuming internal map shapes.

Concurrency model:

  • muVert protects the vertex catalog and immutable configuration flags.
  • muEdgeAdj protects the edge catalog and the private sparse adjacency index.
  • If a method needs both locks, it must acquire muVert before muEdgeAdj.
  • Edge-only mutations do not acquire muVert because they do not change vertex membership.

Storage model:

  • vertices is the authoritative vertex catalog: if vertices[id] exists, the vertex exists.
  • edges is the authoritative edge catalog: if edges[eid] exists, the edge exists.
  • adjacencyList is a private sparse edge index: fromID -> toID -> edgeID -> unit.
  • adjacencyList is NOT a complete vertex mirror. Isolated vertices may have no top-level adjacency bucket internally.
  • Missing adjacencyList[id] means “no indexed outgoing/incident bucket currently stored”, not “vertex id is absent”. Vertex existence is resolved through vertices.

Public adjacency surfaces:

  • Use (*Graph).AdjacencyList() for a full graph-facing adjacency snapshot. It includes every current vertex from vertices, including isolated vertices with empty slices.
  • Use package matrix helpers for matrix-shaped representations, e.g. matrix.NewAdjacencyMatrix(graph, options) or matrix.BuildDenseAdjacency(vertices, edges, options).

Configuration flags:

  • directed/weighted/allowMulti/allowLoops/allowMixed are set during construction and treated as immutable afterwards.

ID generation:

  • Auto edge IDs are "eN" where N is a monotonically increasing counter.
  • WithID and SetEdgeID bump the counter when assigning canonical "eN" IDs.

Determinism:

  • Public enumeration order is stable and documented at package level.
  • Internal map iteration order is never part of the contract.

Notes:

  • Clone/view code must preserve edge IDs and nextEdgeID to prevent future collisions.
  • Do not add public APIs that expose adjacencyList directly; it is an implementation index.

AI-Hints:

  • Do not “fix” isolated vertices by storing fake adjacencyList[id][id] empty buckets.
  • Do not read or write adjacencyList without muEdgeAdj.
  • Do not read or write vertices without muVert.

func InducedSubgraph

func InducedSubgraph(g *Graph, keep map[string]bool) *Graph

InducedSubgraph creates a non-mutating induced subgraph containing only vertices selected by keep.

Implementation:

  • Stage 1: Acquire GLOBAL READ LOCK (Vert + EdgeAdj).
  • Stage 2: Filter and copy vertices.
  • Stage 3: Filter and copy edges (only if both endpoints exist in 'keep').

Concurrency snapshot:

  • Atomic: Prevents "phantom edges" where an endpoint might be deleted concurrently.

Behavior highlights:

  • Does not mutate the source graph.
  • Preserves Edge.ID, Directed, and Weight for retained edges.
  • Drops all edges that cross the cut (one endpoint not kept).

Inputs:

  • g: source graph (must be non-nil by caller convention).
  • keep: map of vertex IDs to retain; keep[id]==true means "retain id".

Returns:

  • *Graph: a new graph containing only the induced topology.

Errors:

  • None.

Determinism:

  • Deterministic for a fixed source snapshot; uses read locks for stable catalogs.

Complexity:

  • Time O(V+E), Space O(V'+E') where V'/E' are retained sizes.

Notes:

  • The order of iteration over keep is irrelevant; retention is membership-based.
  • Vertex.Metadata is shallow-copied (pointer copy).

AI-Hints:

  • Use InducedSubgraph to focus algorithms on a region of interest without changing the original graph.

func NewGraph

func NewGraph(opts ...GraphOption) (*Graph, error)

NewGraph constructs an empty graph and applies GraphOption values deterministically.

Implementation:

  • Stage 1: Validate the option list shape and reject nil GraphOption values.
  • Stage 2: Allocate empty vertex/edge catalogs and adjacency map.
  • Stage 3: Apply options in call order (left-to-right).
  • Stage 4: Publish the fully configured graph on success only.

Behavior highlights:

  • Construction-time options only; subsequent behavior is controlled by method contracts.
  • nextEdgeID starts at 0; the first auto-generated edge becomes "e1".
  • Constructor validation is part of the public contract; no panic-based option handling.

Inputs:

  • opts: zero or more GraphOption values applied in call order.

Returns:

  • *Graph: configured empty graph on success.
  • error: nil on success; otherwise a stable sentinel error.

Errors:

  • ErrNilGraphOption: if opts contains a nil GraphOption value.
  • Any stable sentinel returned by a provided GraphOption.

Determinism:

  • Deterministic option validation order and deterministic option application order.

Complexity:

  • Time O(len(opts)), Space O(1) excluding map growth.

Notes:

  • Nil GraphOption validation happens before graph allocation and publication.
  • On error, no partially configured graph is returned to the caller.

AI-Hints:

  • Keep option sets explicit to make tests reproducible.
  • Check the returned error with errors.Is; do not rely on panic recovery.

func NewMixedGraph

func NewMixedGraph(opts ...GraphOption) (*Graph, error)

NewMixedGraph creates a new Graph that allows per-edge directedness overrides via EdgeOption, while preserving deterministic option application order.

Implementation:

  • Stage 1: Prepend WithMixedEdges() to the caller-provided options.
  • Stage 2: Delegate to NewGraph(...) to validate and apply options deterministically.

Behavior highlights:

  • Enables WithEdgeDirected(...) on AddEdge; without mixed-mode this is rejected.
  • Does not mutate the caller's opts slice (no hidden side-effects).

Inputs:

  • opts: additional GraphOption values applied after enabling mixed-mode.

Returns:

  • *Graph: a fresh configured instance with allowMixed enabled.
  • error: nil on success; otherwise a stable sentinel error.

Errors:

  • ErrNilGraphOption: if opts contains a nil GraphOption value.
  • Any stable sentinel returned by a provided GraphOption via NewGraph.

Determinism:

  • Options are applied left-to-right, with WithMixedEdges() always first.

Complexity:

  • Time O(len(opts)), Space O(len(opts)) for the composed options slice.

Notes:

  • Prefer this constructor when you plan to mix directed and undirected edges in one graph.

AI-Hints:

  • Use NewMixedGraph(...) instead of remembering to prepend WithMixedEdges() manually.

func UnweightedView

func UnweightedView(g *Graph) *Graph

UnweightedView creates a non-mutating view of the input graph where weights are forced to 0 and the resulting graph reports Weighted()==false, while preserving topology and IDs.

Implementation:

  • Stage 1: Acquire GLOBAL READ LOCK (Vert + EdgeAdj) to ensure atomic snapshot.
  • Stage 2: Copy vertices (shallow Metadata).
  • Stage 3: Copy edges (forcing Weight=0).
  • Stage 4: Release locks.

Concurrency snapshot:

  • Atomic: The view represents a consistent state at the moment of creation.
  • No "gap" between vertex and edge copying.

Behavior highlights:

  • Does not mutate the source graph.
  • Preserves Edge.ID and Directed for every edge; only Weight is changed.
  • Preserves determinism rules of the core package (ordering is defined by public APIs).

Inputs:

  • g: source graph (must be non-nil by caller convention).

Returns:

  • *Graph: a new graph instance with identical topology and Weight forced to zero.

Errors:

  • None.

Determinism:

  • Deterministic for a fixed source snapshot; uses read locks for stable catalogs.

Complexity:

  • Time O(V+E), Space O(V+E) for the copied catalogs.

Notes:

  • This is a *view* implemented as a copy: the returned graph is independent and mutable.
  • Vertex.Metadata is shallow-copied (pointer copy); if deep-copy is required, callers must do it externally.

AI-Hints:

  • Use UnweightedView when an algorithm requires zero weights but you must preserve original weights elsewhere.

func (*Graph) AddEdge

func (g *Graph) AddEdge(from, to string, weight float64, opts ...EdgeOption) (string, error)

AddEdge creates a new edge from `from` to `to` with an optional weight and per-edge options. The method enforces graph capabilities (weighted, multi-edge, loop, and mixed-edge policy) and publishes catalog/adjoining adjacency state as one topology transaction.

Transactional Safety:

  • This operation is TOPOLOGICALLY ATOMIC for edge publication.
  • It acquires muVert.Lock() and muEdgeAdj.Lock() in that order.
  • While both locks are held, endpoints cannot be concurrently removed and the edge catalog/adjoining adjacency buckets cannot be observed half-published.
  • If endpoints are missing, they are created as part of the same transaction before edge publication.

Implementation:

  • Stage 1: Validate stateless public inputs: endpoint IDs, weight policy, loop policy, and nil EdgeOption values.
  • Stage 2: Acquire muVert.Lock() to start the topology transaction.
  • Stage 3: Ensure `from` and `to` exist in the vertex catalog.
  • Stage 4: Acquire muEdgeAdj.Lock() to protect edge catalog and adjacency mutation.
  • Stage 5: Reject forbidden parallel edges before allocation/publication.
  • Stage 6: Build a baseline unpublished Edge from validated endpoints, weight, and graph default directedness.
  • Stage 7: Apply EdgeOptions in call order.
  • Stage 8: Re-validate option effects: endpoints and weight are immutable; directedness override requires mixed mode; loop policy remains enforced.
  • Stage 9: Assign an explicit or generated Edge.ID and bump the auto-ID counter when needed.
  • Stage 10: Publish the edge in the catalog and update primary/mirrored adjacency buckets.
  • Stage 11: Release locks through deferred unlocks.

Behavior highlights:

  • Strict sentinel errors only (classify with errors.Is).
  • No panics as part of the public contract.
  • Nil EdgeOption values fail fast before lock acquisition, vertex auto-creation, or adjacency mutation.
  • Endpoint and weight mutation by custom EdgeOption values is rejected before edge publication.
  • Directedness mutation is allowed only when mixed mode is enabled.
  • No lock gap exists between endpoint creation and edge insertion.

Inputs:

  • from: source vertex ID; must be non-empty.
  • to: destination vertex ID; must be non-empty.
  • weight: edge weight; must be finite and must be 0 unless WithWeighted() was set.
  • opts: optional per-edge mutators; each value must be non-nil.

Returns:

  • string: assigned Edge.ID (auto-generated or explicit).
  • error: nil on success; otherwise a sentinel error.

Errors:

  • ErrEmptyVertexID: if from == "" or to == "".
  • ErrBadWeight: if weight != 0 on an unweighted graph.
  • ErrNaNInf: if weight is NaN/Inf.
  • ErrLoopNotAllowed: if from == to and loops are disabled.
  • ErrNilEdgeOption: if opts contains a nil EdgeOption value.
  • ErrMultiEdgeNotAllowed: if a parallel edge is attempted and multi-edges are disabled.
  • ErrInvalidEdgeOption: if an EdgeOption mutates Edge.From, Edge.To, or Edge.Weight.
  • ErrMixedEdgesNotAllowed: if directedness is changed without mixed mode.
  • ErrEmptyEdgeID / ErrEdgeIDConflict: from WithID / ValidateEdgeID rules.

Determinism:

  • Option validation order is stable (call order).
  • Option application order is stable (call order).
  • Auto-ID assignment uses a monotonic counter (no randomness, no time).

Complexity:

  • Time O(1) amortized.
  • Space O(1) amortized.

Notes:

  • AddEdge may auto-create missing endpoint vertices before a later EdgeOption error occurs. Such vertices are valid catalog state; the edge itself is not published on error.
  • Vertices are created while muVert is held to prevent concurrent RemoveVertex from deleting endpoints during edge publication.

AI-Hints:

  • Use WithID for stable cross-run edge references.
  • For undirected edges, adjacency is mirrored automatically.
  • Do not write custom EdgeOption values that rewrite endpoints or weights.

func (*Graph) AddVertex

func (g *Graph) AddVertex(id string) error

AddVertex inserts a vertex if missing.

Implementation:

  • Stage 1: Validate non-empty ID (ErrEmptyVertexID).
  • Stage 2: Acquire muVert write lock.
  • Stage 3: If the ID already exists, return nil without mutation.
  • Stage 4: Allocate and publish a Vertex record in the authoritative vertex catalog.

Behavior highlights:

  • Idempotent: adding an existing vertex is a no-op.
  • Initializes Metadata to a non-nil map for convenient caller use.
  • Does not create adjacency buckets. Internal adjacencyList is a sparse edge index; edge insertion creates buckets lazily.

Inputs:

  • id: vertex identifier; must be non-empty.

Returns:

  • error: nil on success; otherwise a sentinel error.

Errors:

  • ErrEmptyVertexID: if id == "".

Determinism:

  • Deterministic map membership/update logic; no iteration-order dependency.

Complexity:

  • Time O(1) amortized, Space O(1) amortized.

Notes:

  • Vertex existence is represented only by g.vertices.
  • Public AdjacencyList() will still include this vertex with an empty slice until incident edges are added.

AI-Hints:

  • Use AddVertex when an isolated vertex is semantically meaningful.
  • Prefer AddVertex in setup when you want explicit vertex presence before adding edges.

func (*Graph) AdjacencyList

func (g *Graph) AdjacencyList() map[string][]string

AdjacencyList returns a snapshot mapping each "from" vertex ID to the list of incident edge IDs. Each slice is sorted by Edge.ID ascending for deterministic per-vertex enumeration.

Storage distinction:

  • Internal g.adjacencyList is a sparse edge index and may omit isolated vertices.
  • This public method reconstructs the complete vertex-domain view from g.vertices.

Implementation:

  • Stage 1: Acquire muVert.RLock(), then muEdgeAdj.RLock() in package lock order.
  • Stage 2: Allocate a result map with one key for every current vertex.
  • Stage 3: Scan the sparse adjacency index and append existing edge IDs into the corresponding result[from] slice.
  • Stage 4: Sort each per-vertex edge-ID slice by Edge.ID ascending.
  • Stage 5: Return the detached map/slice snapshot.

Behavior highlights:

  • Includes isolated vertices with empty slices.
  • Returned map and slices are detached containers; callers may mutate them safely.
  • Edge IDs identify catalog edges; callers can use GetEdge if they need edge records.
  • Does not expose internal adjacency maps.

Returns:

  • map[string][]string: vertex ID -> sorted edge IDs for the current snapshot.

Errors:

  • None (pure query).

Determinism:

  • Per-vertex slices are sorted by Edge.ID ascending.
  • Map key iteration remains Go-map order; use Vertices() for deterministic key traversal.

Complexity:

  • Time O(V + A + Σ sort(d(v))), where A is indexed adjacency entries.
  • Space O(V + A) for detached containers.

Notes:

  • For matrix-shaped adjacency, use the matrix package rather than depending on this storage snapshot as a dense representation.
  • Use Vertices() to obtain deterministic key order if needed.

AI-Hints:

  • If you need stable iteration over keys, do: keys := g.Vertices(); then read result[key].
  • This is the right API when tests must prove isolated vertices are still present.
  • Do not “repair” internal adjacencyList to include isolated vertices; this method handles that boundary.

func (*Graph) Clear

func (g *Graph) Clear()

Clear resets the graph to an empty state while preserving configuration flags.

Implementation:

  • Stage 1: Acquire muVert and muEdgeAdj write locks to perform an atomic reset.
  • Stage 2: Reinitialize vertices, edges, and adjacencyList maps.
  • Stage 3: Reset nextEdgeID to 0 (future auto edge IDs resume from "e1").
  • Stage 4: Release locks.

Behavior highlights:

  • Preserves flags: Directed default, Weighted, MultiEdges, Loops, MixedMode.
  • Drops all vertices and edges.
  • Resets ID counter deterministically.

Inputs:

  • None.

Returns:

  • None.

Errors:

  • None.

Determinism:

  • Deterministic.

Complexity:

  • Time O(1) for map reallocation, Space O(1) new empty maps (old maps become GC-eligible).

Notes:

  • Not safe to call concurrently with readers/writers; it acquires both write locks.
  • After Clear(), the graph is equivalent to a newly constructed graph with the same options.

AI-Hints:

  • Prefer Clear() for reuse in benchmarks/tests to avoid repeated allocations from NewGraph.

func (*Graph) Clone

func (g *Graph) Clone() *Graph

Clone returns a deep topology copy of the Graph: configuration, vertices, edges, and adjacency.

Implementation:

  • Stage 1: Acquire BOTH muVert and muEdgeAdj Rlocks (atomic snapshot).
  • Stage 2: Create new Graph and copy configuration.
  • Stage 3: Copy vertices (shallow metadata).
  • Stage 4: Copy edges and rebuild adjacency.
  • Stage 5: Release locks.

Behavior highlights:

  • ATOMIC: The clone represents the graph state at a single instant.
  • Preserves Edge.ID, endpoints, weights, and directedness.
  • Vertex.Metadata is shallow-copied (shared pointer).

Inputs:

  • None.

Returns:

  • *Graph: cloned graph instance.

Errors:

  • None.

Determinism:

  • Deterministic for a fixed source graph state.

Complexity:

  • Time O(V + E), Space O(V + E).

Notes:

  • Edges are copied as new Edge structs; callers must still treat returned edges as immutable.
  • nextEdgeID carry-over ensures future AddEdge IDs remain monotonic on the clone.

AI-Hints:

  • Use Clone when algorithms need a sandbox graph to mutate without affecting the original.

func (*Graph) CloneEmpty

func (g *Graph) CloneEmpty() *Graph

CloneEmpty returns a new Graph with identical configuration and vertices, but no edges.

Implementation:

  • Stage 1: Acquire muVert and muEdgeAdj read locks to snapshot flags and vertex catalog safely.
  • Stage 2: Construct a new Graph with equivalent GraphOptions (flags only).
  • Stage 3: Carry over nextEdgeID to preserve the textual edge ID sequence on the clone.
  • Stage 4: Copy vertices (shallow metadata pointer copy) and initialize empty per-vertex adjacency maps.
  • Stage 5: Return the clone.

Behavior highlights:

  • Preserves configuration flags (Directed default, Weighted, MultiEdges, Loops, MixedMode).
  • Preserves vertex identities.
  • Drops all edges (edge catalog and adjacency remain empty).

Inputs:

  • None.

Returns:

  • *Graph: new graph instance with copied flags and vertices, no edges.

Errors:

  • None.

Determinism:

  • Deterministic for a fixed source graph state.

Complexity:

  • Time O(V), Space O(V).

Notes:

  • Vertex.Metadata is shallow-copied (the map pointer is reused). If deep copy is required, callers must implement it externally.
  • nextEdgeID is carried over to prevent future auto-generated IDs from "rewinding".

AI-Hints:

  • Use CloneEmpty when you need the same vertex universe but want to rebuild topology from scratch.

func (*Graph) Degree

func (g *Graph) Degree(id string) (in, out, undirected int, err error)

Degree returns the degree components of the given vertex ID.

Math Policy (Contract Anchor):

  • Directed edges contribute to in/out only.
  • Undirected edges contribute to undirected only.
  • Directed self-loop (id -> id): Contributes +1 to 'in' AND +1 to 'out'.
  • Undirected self-loop (id - id): Contributes +2 to 'undirected' (classic graph theory).

Implementation:

  • Stage 1: Validate id and vertex existence under locks.
  • Stage 2: Scan ALL graph edges (g.edges) to identify incident connections. This is an O(E) operation required to correctly calculate in-degree for directed edges without maintaining a separate expensive reverse index.

Inputs:

  • id: vertex identifier.

Returns:

  • in: number of incoming directed edges (e.To == id)
  • out: number of outgoing directed edges (e.From == id)
  • undirected: contribution from undirected edges
  • err: ErrEmptyVertexID or ErrVertexNotFound.

Errors:

  • ErrEmptyVertexID: if id is empty.
  • ErrVertexNotFound: if the vertex does not exist in the graph.

Determinism:

  • Deterministic result (counting is order-independent).

Complexity:

  • Time O(E), Space O(1), where E is the total number of edges in the graph. Note: This is an O(E) operation to ensure correct in-degree calculation without maintaining a separate reverse index.

Notes:

  • This method acquires global read locks on vertices and edges.

AI-Hints:

  • Directed self-loops increase the total degree sum by 2 (1 in + 1 out).
  • Undirected self-loops increase the total degree sum by 2 (2 undirected).
  • Use Degree() when you need loop-aware, policy-defined degree semantics.
  • Be aware of O(E) cost on very large graphs.

func (*Graph) Directed

func (g *Graph) Directed() bool

Directed reports the graph-wide default directedness applied to newly created edges. Per-edge overrides require mixed-mode (MixedEdges()==true).

Implementation:

  • Stage 1: Acquire muVert read lock to observe configuration consistently.
  • Stage 2: Return the immutable default directedness flag.

Behavior highlights:

  • Pure policy query: does not scan edges.

Returns:

  • bool: true if new edges default to directed orientation.

Errors:

  • None.

Determinism:

  • Deterministic for a fixed graph instance.

Complexity:

  • Time O(1), Space O(1).

Notes:

  • This does not indicate whether the graph currently contains directed edges (use HasDirectedEdges() or Stats().DirectedEdgeCount for that).

AI-Hints:

  • Use g.Directed() to decide default edge semantics when generating topology programmatically.

func (*Graph) EdgeCount

func (g *Graph) EdgeCount() int

EdgeCount returns the current number of edges in the graph.

Implementation:

  • Stage 1: Acquire muEdgeAdj read lock for a consistent catalog snapshot.
  • Stage 2: Return len(g.edges).

Behavior highlights:

  • O(1) fast-path.
  • No allocations.
  • Independent of map iteration order.

Inputs:

  • None.

Returns:

  • int: number of edges currently present.

Errors:

  • None (pure query).

Determinism:

  • Deterministic for a fixed graph state.

Complexity:

  • Time O(1), Space O(1).

Notes:

  • Counts the edge catalog entries; undirected edges still count as 1 edge.

AI-Hints:

  • Prefer EdgeCount() over len(Edges()) to avoid O(E log E) sorting cost.

func (*Graph) Edges

func (g *Graph) Edges() []*Edge

Edges returns all edges sorted by Edge.ID ascending (stable, deterministic order).

Implementation:

  • Stage 1: Acquire muEdgeAdj read lock for a stable catalog snapshot.
  • Stage 2: Copy all *Edge pointers into a pre-sized slice.
  • Stage 3: Sort the slice by Edge.ID ascending.
  • Stage 4: Return the sorted slice.

Behavior highlights:

  • Deterministic ordering independent of Go map iteration order.
  • Returns pointers to live catalog edges (read-only by convention).

Inputs:

  • None.

Returns:

  • []*Edge: all edges sorted by Edge.ID ascending.

Errors:

  • None (pure query).

Determinism:

  • Deterministic order by contract: Edge.ID ascending.

Complexity:

  • Time O(E log E), Space O(E).

Notes:

  • Prefer EdgeCount() when you need only counts (O(1) vs O(E log E)).
  • The returned slice is newly allocated; callers may retain and reorder it safely.
  • Do not mutate the returned *Edge objects.

AI-Hints:

  • Use Edges() for stable logs/golden outputs and deterministic diffing.
  • If you need only IDs, consider extracting IDs and comparing sorted slices in tests.

func (*Graph) FilterEdges

func (g *Graph) FilterEdges(pred func(Edge) bool) ([]Edge, error)

FilterEdges returns detached copies of all edges for which pred returns true.

Implementation:

  • Stage 1: Reject nil predicate with ErrNilEdgePredicate.
  • Stage 2: Acquire muEdgeAdj.RLock() for a stable edge-catalog snapshot.
  • Stage 3: Scan the edge catalog and pass detached Edge values to pred.
  • Stage 4: Append matching copies to the result slice.
  • Stage 5: Sort returned copies by Edge.ID ascending.

Behavior highlights:

  • Read-only query; does not mutate edge catalog or adjacency index.
  • Predicate receives detached Edge values, not live catalog pointers.
  • Returned slice and Edge values are caller-owned copies.

Inputs:

  • pred: non-nil pure predicate over an Edge value copy.

Returns:

  • []Edge: matching detached edge copies sorted by Edge.ID ascending.
  • error: nil on success; otherwise a sentinel error.

Errors:

  • ErrNilEdgePredicate: if pred == nil.

Determinism:

  • Output order is deterministic: Edge.ID ascending.
  • Predicate call order is not part of the contract.

Complexity:

  • Time O(E log E) due to final sorting, Space O(k) for k matches.

Notes:

  • Use RemoveEdgesWhere for mutating bulk deletion.

AI-Hints:

  • Do not reintroduce destructive filtering under this name; FilterEdges is a query surface.
  • If caller needs pointers, they can GetEdge(match.ID) explicitly after filtering.

func (*Graph) GetEdge

func (g *Graph) GetEdge(edgeID string) (*Edge, error)

GetEdge returns the edge with the given edgeID, or ErrEdgeNotFound if absent.

Implementation:

  • Stage 1: Acquire muEdgeAdj read lock for a consistent catalog snapshot.
  • Stage 2: Lookup g.edges[edgeID].
  • Stage 3: Return the pointer (read-only by convention) or ErrEdgeNotFound.

Behavior highlights:

  • O(1) average lookup time.
  • Does not allocate.
  • Does not mutate graph state.

Inputs:

  • edgeID: edge identifier (Edge.ID) to lookup.

Returns:

  • *Edge: pointer to the cataloged edge (treat as read-only).
  • error: nil on success; ErrEdgeNotFound if missing.

Errors:

  • ErrEdgeNotFound: if edgeID is not present in the edge catalog.

Determinism:

  • Deterministic for a fixed graph state (pure map membership check).

Complexity:

  • Time O(1) average, Space O(1).

Notes:

  • The returned pointer aliases the live catalog object.
  • Edge.ID, Edge.From, Edge.To, Edge.Weight, and Edge.Directed MUST be treated as immutable once the edge is published in the graph.
  • Retaining the pointer does not pin membership and does not extend graph locks.
  • If detached mutable ownership is required, copy the Edge value externally.

AI-Hints:

  • Use errors.Is(err, ErrEdgeNotFound) to branch without string matching.
  • Prefer GetEdge over scanning Edges() when you already have the ID.

func (*Graph) GetNamedEdges

func (g *Graph) GetNamedEdges() []*Edge

GetNamedEdges returns all edges whose ID is not in the canonical auto-generated “eN” form.

Implementation:

  • Stage 1: Acquire muEdgeAdj read lock.
  • Stage 2: Filter edges whose IDs do not match matchesAutoIDPattern.
  • Stage 3: Sort results by Edge.ID asc for deterministic order.

Behavior highlights: - Deterministic output order (Edge.ID lex asc). - The returned slice container is freshly allocated. - Slice elements alias live catalog edges. - Structural edge fields MUST be treated as immutable once published in a graph.

Returns:

  • []*Edge: edges with non-auto-shaped IDs, sorted by ID.

Determinism:

  • Deterministic output order (lexicographic ID sort).

Complexity:

  • Time O(E log E), Space O(E).

NOTES:

  • Reordering or truncating the returned slice does not mutate the graph.
  • Retaining a returned *Edge does not pin graph membership or extend graph locks.
  • Use external value copies if detached mutable edge ownership is required.

func (*Graph) HasDirectedEdges

func (g *Graph) HasDirectedEdges() bool

HasDirectedEdges reports whether at least one edge with Directed == true exists.

Implementation:

  • Stage 1: Acquire muEdgeAdj read lock for a stable catalog snapshot.
  • Stage 2: Scan edge catalog and return true on first directed edge.
  • Stage 3: If none found, return false.

Behavior highlights:

  • Early-exit scan (best-case O(1) if a directed edge is found early).
  • Does not allocate.

Inputs:

  • None.

Returns:

  • bool: true if any directed edge exists; otherwise false.

Errors:

  • None (pure query).

Determinism:

  • Deterministic for a fixed graph state (existence check).

Complexity:

  • Time O(E) worst-case, Space O(1).

Notes:

  • In mixed-mode graphs, this can be true even if the default orientation is undirected.

AI-Hints:

  • Use HasDirectedEdges() as a cheap gate for algorithms that need directed semantics.

func (*Graph) HasEdge

func (g *Graph) HasEdge(from, to string) bool

HasEdge reports whether at least one edge exists from 'from' to 'to'. It is safe to call with unknown vertex IDs; missing adjacency buckets return false. Implementation:

  • Stage 1: Reject empty IDs early (fast-path, no locks).
  • Stage 2: Acquire muEdgeAdj read lock for a stable adjacency snapshot.
  • Stage 3: Probe adjacency buckets defensively and return membership.

Behavior highlights:

  • Undirected edges are mirrored on insertion, so HasEdge works in both directions.
  • Missing vertices or missing adjacency buckets return false (never panic).

Inputs:

  • from: source vertex ID (non-empty for meaningful queries).
  • to: destination vertex ID (non-empty for meaningful queries).

Returns:

  • bool: true if at least one edge from->to exists, otherwise false.

Errors:

  • N/A (pure query; never returns sentinel errors).

Determinism:

  • Deterministic; relies only on map membership (no iteration order).

Complexity:

  • Time O(1), Space O(1).

Notes:

  • Uses adjacency buckets instead of scanning edges for performance.

AI-Hints:

  • Use HasEdge as an O(1) membership check; do not build temporary slices just to test existence.
  • For undirected graphs, checking (to,from) is equivalent due to mirrored adjacency.

func (*Graph) HasVertex

func (g *Graph) HasVertex(id string) bool

HasVertex reports whether the vertex ID exists (empty ID ⇒ false).

Implementation:

  • Stage 1: Reject empty ID (fast-path).
  • Stage 2: Acquire muVert read lock and check catalog membership.

Inputs:

  • id: vertex identifier.

Returns:

  • bool: true iff the vertex is present.

Errors:

  • None (pure query).

Determinism:

  • Deterministic; map membership only.

Complexity:

  • Time O(1), Space O(1).

AI-Hints:

  • Use HasVertex as a cheap admission check before operations that do not auto-create vertices.

func (*Graph) InternalVertices deprecated

func (g *Graph) InternalVertices() map[string]*Vertex

InternalVertices returns a compatibility copy of the vertex catalog.

Deprecated:

  • Historical versions exposed the live internal vertices map directly.
  • That behavior bypassed synchronization and allowed catalog-membership mutation outside graph methods.
  • The method is retained only as a compatibility alias and now delegates to VerticesMap().

Behavior highlights:

  • The returned map container is detached from the graph.
  • The contained *Vertex values still alias catalog records.

Notes:

  • Prefer VerticesMap() in new code.
  • This method is intentionally no longer an internal-storage escape hatch.

AI-Hints:

  • Do not write code that depends on InternalVertices mutating graph membership.

func (*Graph) Looped

func (g *Graph) Looped() bool

Looped reports whether self-loops (from==to) are permitted by policy. If false, AddEdge(v,v,...) rejects the operation with ErrLoopNotAllowed.

Implementation:

  • Stage 1: Acquire muVert read lock to observe configuration consistently.
  • Stage 2: Return the immutable loops policy flag.

Returns:

  • bool: true if self-loops are permitted.

Errors:

  • None.

Determinism:

  • Deterministic for a fixed graph instance.

Complexity:

  • Time O(1), Space O(1).

Notes:

  • This is a policy flag; existing self-loops can only exist if this was enabled at creation time.

AI-Hints:

  • Gate loop-sensitive algorithms by g.Looped() before assuming v->v edges may exist.

func (*Graph) MixedEdges

func (g *Graph) MixedEdges() bool

MixedEdges reports whether per-edge Directed overrides are permitted via EdgeOption (specifically WithEdgeDirected(...)) during AddEdge.

Implementation:

  • Stage 1: Acquire muVert read lock to observe configuration consistently.
  • Stage 2: Return the immutable mixed-mode policy flag.

Returns:

  • bool: true if per-edge Directed overrides are permitted.

Errors:

  • None.

Determinism:

  • Deterministic for a fixed graph instance.

Complexity:

  • Time O(1), Space O(1).

Notes:

  • If false, AddEdge(..., WithEdgeDirected(...)) returns ErrMixedEdgesNotAllowed.

AI-Hints:

  • Prefer NewMixedGraph(...) when you intend to mix directed and undirected edges in one instance.

func (*Graph) Multigraph

func (g *Graph) Multigraph() bool

Multigraph reports whether parallel edges between the same endpoints are permitted by policy. If false, AddEdge(from,to,...) rejects duplicates with ErrMultiEdgeNotAllowed.

Implementation:

  • Stage 1: Acquire muVert read lock to observe configuration consistently.
  • Stage 2: Return the immutable multi-edge policy flag.

Returns:

  • bool: true if parallel edges are permitted.

Errors:

  • None.

Determinism:

  • Deterministic for a fixed graph instance.

Complexity:

  • Time O(1), Space O(1).

Notes:

  • Multi-edge checks in AddEdge are membership checks over adjacency buckets.

AI-Hints:

  • If you need multi-edges, enable WithMultiEdges() at construction time; this is immutable later.

func (*Graph) NeighborIDs

func (g *Graph) NeighborIDs(id string) ([]string, error)

NeighborIDs returns the unique set of vertex IDs adjacent to id, sorted lexicographically ascending.

Adjacency policy:

  • For each edge returned by Neighbors(id):
  • If e.From == id, include e.To.
  • Else if !e.Directed and e.To == id, include e.From.

Implementation:

  • Stage 1: Call Neighbors(id) to obtain incident edges and enforce validation.
  • Stage 2: Build a set of adjacent vertex IDs.
  • Stage 3: Convert the set to a slice and sort lexicographically.
  • Stage 4: Return the sorted slice.

Behavior highlights:

  • Unique output: duplicates are removed.
  • Deterministic output order (lex asc).

Inputs:

  • id: vertex identifier.

Returns:

  • []string: unique adjacent vertex IDs, sorted lex asc.
  • error: propagated from Neighbors(id).

Errors:

  • Propagates ErrEmptyVertexID / ErrVertexNotFound from Neighbors(id).

Determinism:

  • Deterministic output order by contract (lex asc).

Complexity:

  • Time O(d + k log k), Space O(k), where d is incident edges and k is unique neighbors.

Notes:

  • For directed edges, only outgoing neighbors are included (consistent with Neighbors policy).

AI-Hints:

  • Use NeighborIDs when building traversal frontiers to avoid edge duplication.
  • If you need both in/out neighbors for directed graphs, define a dedicated API explicitly.

func (*Graph) Neighbors

func (g *Graph) Neighbors(id string) ([]*Edge, error)

Neighbors returns all edges incident to the given vertex id under the graph's neighborhood policy.

Neighborhood policy:

  • Directed edges: include only edges with e.From == id (outgoing edges).
  • Undirected edges: include incident edges (mirrored adjacency is used); self-loops appear once.

Implementation:

  • Stage 1: Validate id is non-empty (ErrEmptyVertexID).
  • Stage 2: Acquire muVert read lock and muEdgeAdj read lock (in that order) for a consistent snapshot.
  • Stage 3: Validate vertex existence (ErrVertexNotFound).
  • Stage 4: Collect incident edges by scanning adjacencyList[id] buckets and mapping edge IDs to *Edge.
  • Stage 5: Sort the result by Edge.ID ascending.
  • Stage 6: Return the sorted slice.

Behavior highlights:

  • Deterministic ordering by Edge.ID ascending.
  • The returned slice container is detached and safe to retain, re-slice, or reorder locally.
  • The slice elements alias live catalog edges.
  • Edge.ID, Edge.From, Edge.To, Edge.Weight, and Edge.Directed MUST be treated as immutable once an edge is published in a graph.
  • Safe against concurrent vertex removal due to consistent lock ordering.

Inputs:

  • id: vertex identifier.

Returns:

  • []*Edge: incident edges under the defined policy, sorted by Edge.ID asc.
  • error: nil on success; otherwise a sentinel error.

Errors:

  • ErrEmptyVertexID: if id == "".
  • ErrVertexNotFound: if the vertex does not exist.

Determinism:

  • Deterministic order by contract: Edge.ID ascending.

Complexity:

  • Time O(d log d), Space O(d), where d is the number of incident edges collected.

Notes:

  • This is a zero-copy inspection surface for expert callers.
  • Retaining a returned *Edge does not pin graph membership or extend graph locks.
  • If detached mutable edge state is required, copy the Edge value externally.

AI-Hints:

  • Use Neighbors(id) for deterministic iteration in algorithms.
  • Use NeighborIDs(id) when you need unique adjacent vertex IDs rather than edges.

func (*Graph) RemoveEdge

func (g *Graph) RemoveEdge(eid string) error

RemoveEdge deletes one edge by ID and unlinks its sparse adjacency references.

Implementation:

  • Stage 1: Validate non-empty edge ID (ErrEmptyEdgeID).
  • Stage 2: Acquire muEdgeAdj.Lock() because only edge catalog and adjacency index mutate.
  • Stage 3: Lookup the edge in the authoritative edge catalog.
  • Stage 4: Delete the edge catalog entry and remove its adjacency references.
  • Stage 5: Prune empty sparse adjacency buckets.

Behavior highlights:

  • Does not remove endpoint vertices, even if they become isolated.
  • Does not acquire muVert because vertex membership is unchanged.
  • Leaves public AdjacencyList() able to report isolated endpoints through g.vertices.

Inputs:

  • eid: edge identifier; must be non-empty.

Returns:

  • error: nil on success; otherwise a sentinel error.

Errors:

  • ErrEmptyEdgeID: if eid == "".
  • ErrEdgeNotFound: if no edge with eid exists.

Determinism:

  • Deterministic final graph state; no iteration-order dependency.

Complexity:

  • Time O(B) because cleanup may scan sparse buckets; O(1) average without cleanup cost.
  • Space O(1).

Notes:

  • Edge deletion changes edge topology, not vertex membership.

AI-Hints:

  • Do not add muVert locking here; it creates unnecessary contention and can invert lock order.
  • Removing the last incident edge of a vertex must not remove the vertex from g.vertices.

func (*Graph) RemoveEdgesWhere

func (g *Graph) RemoveEdgesWhere(pred func(Edge) bool) (int, error)

RemoveEdgesWhere removes every edge for which pred returns true.

Implementation:

  • Stage 1: Reject nil predicate with ErrNilEdgePredicate.
  • Stage 2: Acquire muEdgeAdj write lock for an atomic removal pass.
  • Stage 3: Scan the edge catalog once.
  • Stage 4: Pass each predicate a detached Edge value copy.
  • Stage 5: For matching edges, remove adjacency and delete from the catalog.
  • Stage 6: Cleanup empty adjacency buckets once after the bulk mutation.

Behavior highlights:

  • Removes matching edges; keeps non-matching edges.
  • The predicate receives a value copy, so it cannot mutate cataloged edge fields.
  • The graph remains catalog/adjacency-consistent after every successful call.
  • Predicate execution occurs while muEdgeAdj is held; predicates must be pure and must not call graph methods or try to mutate the graph.

Inputs:

  • pred: non-nil pure predicate over a detached Edge value.

Returns:

  • int: number of removed edges.
  • error: nil on success; otherwise a sentinel error.

Errors:

  • ErrNilEdgePredicate: if pred == nil.

Determinism:

  • Deterministic for a deterministic predicate; map scan order does not affect the final set.

Complexity:

  • Time O(E + B), where B is adjacency cleanup bucket count.
  • Space O(1) extra.

Notes:

  • This method is the preferred value-copy bulk-removal API.
  • Public AdjacencyList() still reports isolated vertices after their last edge is removed.

AI-Hints:

  • Use RemoveEdgesWhere for contract-safe bulk deletion.
  • Do not call g.Edges, g.Neighbors, AddEdge, RemoveEdge, or RemoveVertex from pred.

func (*Graph) RemoveVertex

func (g *Graph) RemoveVertex(id string) error

RemoveVertex deletes a vertex and all incident edges.

Implementation:

  • Stage 1: Validate non-empty ID (ErrEmptyVertexID).
  • Stage 2: Acquire muVert.Lock(), then muEdgeAdj.Lock() in the package lock order.
  • Stage 3: Verify vertex presence in the authoritative vertex catalog.
  • Stage 4: Scan the edge catalog once and remove every edge whose From or To is id.
  • Stage 5: For each removed edge, unlink adjacency buckets and delete the edge catalog entry.
  • Stage 6: Delete the vertex catalog entry.
  • Stage 7: Remove any remaining sparse adjacency index references for that vertex.

Behavior highlights:

  • This is a topology rewrite: vertex membership, edge catalog, and adjacency index change together.
  • Removes directed incoming, directed outgoing, undirected, loop, and parallel incident edges.
  • Leaves no adjacency references to the removed vertex.

Inputs:

  • id: vertex identifier to remove; must be non-empty.

Returns:

  • error: nil on success; otherwise a sentinel error.

Errors:

  • ErrEmptyVertexID: if id == "".
  • ErrVertexNotFound: if id is absent from the vertex catalog.

Determinism:

  • Deterministic final graph state; map scan order does not affect the result.

Complexity:

  • Time O(E + B), where E is edge count and B is adjacency bucket count touched/scanned.
  • Space O(1) extra.

Notes:

  • This method is intentionally heavier than RemoveEdge because it changes vertex membership.
  • It is the only operation that should call cleanupAdjacencyVertex.

AI-Hints:

  • Keep the lock order muVert -> muEdgeAdj. Reversing it can deadlock with AddEdge.
  • Do not call public graph methods while both locks are held.
  • Prefer building subgraphs/views when you need “logical removal” without mutating the original.

func (*Graph) SetEdgeID

func (g *Graph) SetEdgeID(oldID, newID string) error

SetEdgeID renames an existing edge’s identifier from oldID to newID.

Implementation:

  • Stage 1: Validate inputs (non-empty).
  • Stage 2: Acquire muEdgeAdj write lock.
  • Stage 3: Lookup oldID (ErrEdgeNotFound) and ensure newID is free (ErrEdgeIDConflict).
  • Stage 4: Update edge catalog key and edge.ID.
  • Stage 5: Rewrite adjacency buckets that store the edge ID (from->to and mirror if undirected).
  • Stage 6: Cleanup empty adjacency buckets.
  • Stage 7: If newID is canonical "eN", bump auto-ID counter to avoid future collisions.

Inputs:

  • oldID: existing edge identifier (must be non-empty).
  • newID: desired edge identifier (must be non-empty).

Returns:

  • error: nil on success; otherwise a sentinel error.

Errors:

  • ErrEmptyEdgeID: if oldID == "" or newID == "".
  • ErrEdgeNotFound: if oldID does not exist.
  • ErrEdgeIDConflict: if newID already exists.

Determinism:

  • Deterministic; map membership checks and deterministic updates.

Complexity:

  • Time O(1) average (map updates); Space O(1).

Notes:

  • This operation is atomic with respect to edge queries due to muEdgeAdj write lock.

AI-Hints:

  • Use SetEdgeID to migrate from auto-IDs to stable IDs after building a graph topology.

func (*Graph) Stats

func (g *Graph) Stats() *GraphStats

Stats produces a deterministic, read-only diagnostic summary of configuration flags, catalog sizes, and edge directedness counts.

Concurrency:

  • StrictSnapshot: Stats holds muVert.RLock and muEdgeAdj.RLock together in package lock order.
  • The returned GraphStats is detached and remains valid after unlock.
  • Stats is a diagnostic snapshot, not a long-lived synchronization primitive.

Implementation:

  • Stage 1: Acquire muVert.RLock(), snapshot immutable configuration flags and vertex count, then release muVert.RLock().
  • Stage 2: Acquire muEdgeAdj.RLock(), snapshot edge count and scan the edge catalog once, then release muEdgeAdj.RLock().
  • Stage 3: Return the populated GraphStats value object.

Behavior highlights:

  • Avoids holding both locks simultaneously, reducing contention for diagnostics.
  • The returned *GraphStats is detached from the graph and immutable by convention.
  • Under concurrent mutation, vertex/config fields and edge counters may come from different read phases; callers must not treat it as a linearizable topology snapshot.

Returns:

  • *GraphStats: detached summary of policy flags and catalog counters.

Errors:

  • None.

Determinism:

  • Deterministic for a fixed graph state.
  • Under concurrent mutation, each phase is internally consistent, but the whole result is best-effort telemetry rather than a strict transaction snapshot.

Complexity:

  • Time O(E), Space O(1) plus the returned struct.

Notes:

  • DirectedDefault reports the default policy for new edges, not whether directed edges exist.
  • Use HasDirectedEdges or DirectedEdgeCount when gating algorithms by current topology.

AI-Hints:

  • Use Stats() to gate algorithms quickly (e.g., ensure Weighted==true before reading weights).
  • Do not use Stats() as a strict concurrent admission primitive unless the caller owns external synchronization.

func (*Graph) ValidateEdgeID

func (g *Graph) ValidateEdgeID(ID string) error

ValidateEdgeID checks whether ID is non-empty and not already in use.

Implementation:

  • Stage 1: Reject empty IDs (ErrEmptyEdgeID).
  • Stage 2: Under muEdgeAdj read lock, check edge catalog membership (ErrEdgeIDConflict).

Inputs:

  • ID: desired edge identifier.

Returns:

  • error: nil if valid; otherwise a sentinel.

Errors:

  • ErrEmptyEdgeID: if ID == "".
  • ErrEdgeIDConflict: if ID already exists.

Complexity:

  • Time O(1), Space O(1).

func (*Graph) VertexCount

func (g *Graph) VertexCount() int

VertexCount returns the current number of vertices in the graph.

Implementation:

  • Stage 1: Acquire muVert read lock.
  • Stage 2: Return len(g.vertices).

Returns:

  • int: number of vertices.

Errors:

  • None (pure query).

Determinism:

  • Deterministic for a fixed graph state.

Complexity:

  • Time O(1), Space O(1).

AI-Hints:

  • Prefer VertexCount() over len(Vertices()) to avoid O(V log V) sorting costs.

func (*Graph) Vertices

func (g *Graph) Vertices() []string

Vertices returns all vertex IDs in lexicographic ascending order.

Implementation:

  • Stage 1: Acquire muVert read lock and copy vertex IDs into a slice.
  • Stage 2: Sort the slice ascending.
  • Stage 3: Return the sorted slice.

Behavior highlights:

  • Stable enumeration surface used for determinism in higher-level algorithms.

Inputs:

  • None.

Returns:

  • []string: sorted vertex IDs.

Errors:

  • None (pure query).

Determinism:

  • Deterministic output order (lex asc).

Complexity:

  • Time O(V log V), Space O(V).

AI-Hints:

  • Use Vertices() for reproducible traversal seeds and stable test assertions.

func (*Graph) VerticesMap

func (g *Graph) VerticesMap() map[string]*Vertex

VerticesMap returns a fresh map copy of the vertex catalog (ID -> *Vertex).

Implementation:

  • Stage 1: Acquire muVert read lock.
  • Stage 2: Allocate a new map sized to the catalog.
  • Stage 3: Copy ID -> *Vertex entries into the new map.

Behavior highlights:

  • The returned map container is detached from the graph and may be retained, re-keyed, or discarded by the caller without holding graph locks.
  • The contained *Vertex values alias catalog records.
  • Vertex.ID MUST be treated as immutable once the vertex is published in a Graph.
  • Vertex.Metadata remains caller-managed payload; core neither deep-copies nor synchronizes metadata contents.

Returns:

  • map[string]*Vertex: fresh map container whose values alias catalog vertices.

Errors:

  • None (pure query).

Determinism:

  • Deterministic for membership; returned map iteration order is not deterministic (Go map rule).

Complexity:

  • Time O(V), Space O(V).

Notes:

  • Use Vertices() when you need deterministic ordering.
  • Retaining a returned *Vertex does not pin graph membership or extend graph locks.

AI-Hints:

  • Prefer VerticesMap() for detached catalog membership snapshots without sorting.
  • Treat Vertex.ID as immutable; if you need mutable detached state, allocate your own copies.

func (*Graph) Weighted

func (g *Graph) Weighted() bool

Weighted reports the construction-time "weighted" capability flag. If false, AddEdge rejects non-zero weights with ErrBadWeight.

Implementation:

  • Stage 1: Acquire muVert read lock to observe configuration consistently.
  • Stage 2: Return the immutable flag value.

Behavior highlights:

  • Pure query: no mutation, no iteration, no allocations.

Returns:

  • bool: true if non-zero weights are permitted.

Errors:

  • None.

Determinism:

  • Deterministic for a fixed graph instance (flags are immutable after construction).

Complexity:

  • Time O(1), Space O(1).

Notes:

  • This reports a policy flag, not whether any stored edge currently has Weight != 0.

AI-Hints:

  • Gate weighted algorithms by g.Weighted() before reading edge.Weight.

type GraphOption

type GraphOption func(g *Graph) error

GraphOption mutates only a newly constructed Graph inside NewGraph.

Implementation:

  • Stage 1: NewGraph validates the option list shape and rejects nil option values.
  • Stage 2: NewGraph allocates empty catalogs.
  • Stage 3: Options are applied deterministically in call order.
  • Stage 4: The configured graph is published only if every option succeeds.

Behavior highlights:

  • Options are construction-time only.
  • Options MUST NOT panic as part of the public contract.
  • A nil GraphOption is rejected with ErrNilGraphOption before invocation.
  • Option implementations may mutate only the graph being constructed.

Inputs:

  • g: the graph instance being constructed.

Returns:

  • error: nil on success; otherwise a stable sentinel error.

Errors:

  • Nil option values are rejected by NewGraph/NewMixedGraph with ErrNilGraphOption.
  • Option implementations may return stable sentinels for invalid configuration.

Determinism:

  • Deterministic application order (left-to-right).

Complexity:

  • Time O(1) per option, Space O(1).

Notes:

  • Core avoids hidden global state; behavior changes must be explicit via options.
  • On constructor error, no partially configured graph is published to the caller.

AI-Hints:

  • Prefer explicit options over implicit defaults when writing reproducible tests.
  • Return sentinels; do not panic or mutate external state from a GraphOption.

func WithDirected

func WithDirected(defaultDirected bool) GraphOption

WithDirected sets the default directedness for all future edges created in this graph. Per-edge overrides are only allowed in mixed mode (WithMixedEdges + WithEdgeDirected).

Implementation:

  • Stage 1: Store defaultDirected into g.directed during construction.

Behavior highlights:

  • Affects only the default for future edges (not existing edges).

Inputs:

  • defaultDirected: true for directed-by-default graphs; false for undirected-by-default.

Returns:

  • GraphOption: construction-time mutator.

Errors:

  • None.

Determinism:

  • Deterministic; constant-time flag set.

Complexity:

  • Time O(1), Space O(1).

Notes:

  • Mixed mode controls whether individual edges may override Directed.

AI-Hints:

  • Use WithMixedEdges() if your workload needs both directed and undirected edges in one graph.
  • Sets only the default; per-edge override requires WithMixedEdges().

func WithLoops

func WithLoops() GraphOption

WithLoops permits self-loops (edges from a vertex to itself).

Implementation:

  • Stage 1: Set g.allowLoops = true during construction.

Behavior highlights:

  • Without this option, AddEdge(v,v,...) returns ErrLoopNotAllowed.

Returns:

  • GraphOption: construction-time mutator.

Errors:

  • None.

Determinism:

  • Deterministic; constant-time flag set.

Complexity:

  • Time O(1), Space O(1).

AI-Hints:

  • Keep loops disabled unless your math model explicitly includes self transitions.
  • Without this, AddEdge(v,v,...) returns ErrLoopNotAllowed.

func WithMixedEdges

func WithMixedEdges() GraphOption

WithMixedEdges enables per-edge directedness overrides (mixed mode). In mixed mode, individual edges may specify Directed=true/false via EdgeOption.

Implementation:

  • Stage 1: Set g.allowMixed = true during construction.

Behavior highlights:

  • Without this option, WithEdgeDirected(...) returns ErrMixedEdgesNotAllowed.

Returns:

  • GraphOption: construction-time mutator.

Errors:

  • None.

Determinism:

  • Deterministic; constant-time flag set.

Complexity:

  • Time O(1), Space O(1).

AI-Hints:

  • Mixed mode is a capability flag; keep it off if all edges share the same orientation.

func WithMultiEdges

func WithMultiEdges() GraphOption

WithMultiEdges permits parallel edges between the same endpoints.

Implementation:

  • Stage 1: Set g.allowMulti = true during construction.

Behavior highlights:

  • Without this option, AddEdge on an existing (from,to) bucket returns ErrMultiEdgeNotAllowed.

Returns:

  • GraphOption: construction-time mutator.

Errors:

  • None.

Determinism:

  • Deterministic; constant-time flag set.

Complexity:

  • Time O(1), Space O(1).

AI-Hints:

  • Enable multi-edges for multigraph models (e.g., multiple relations between entities).
  • Without this, a second AddEdge(from,to,...) returns ErrMultiEdgeNotAllowed.

func WithWeighted

func WithWeighted() GraphOption

WithWeighted enables non-zero edge weights in this graph.

Implementation:

  • Stage 1: Set g.weighted = true during construction.

Behavior highlights:

  • Without this option, AddEdge rejects weight != 0 with ErrBadWeight.

Returns:

  • GraphOption: construction-time mutator.

Errors:

  • None.

Determinism:

  • Deterministic; constant-time flag set.

Complexity:

  • Time O(1), Space O(1).

AI-Hints:

  • Keep graphs unweighted if you only need topology; it simplifies inputs and tests.
  • Without this, AddEdge(weight!=0) returns ErrBadWeight.

type GraphStats

type GraphStats struct {
	// DirectedDefault is the graph-wide default orientation (true=directed).
	DirectedDefault bool

	// Weighted is true when non-zero edge weights are permitted.
	Weighted bool

	// AllowsMulti is true when parallel edges are permitted.
	AllowsMulti bool

	// AllowsLoops is true when self-loops are permitted.
	AllowsLoops bool

	// MixedMode is true when per-edge Directed overrides are permitted.
	MixedMode bool

	// VertexCount is the number of vertices present in the graph.
	VertexCount int

	// EdgeCount is the number of edges present in the graph.
	EdgeCount int

	// DirectedEdgeCount is the number of edges with Directed == true.
	DirectedEdgeCount int

	// UndirectedEdgeCount is the number of edges with Directed == false.
	UndirectedEdgeCount int
}

GraphStats is a read-only result object summarizing graph state.

Implementation:

  • Filled by (*Graph).Stats() by scanning catalogs (implementation elsewhere).

Behavior highlights:

  • Pure data type: no methods, no side effects.

Determinism:

  • Field meanings are stable and part of the public contract.

Complexity:

  • N/A (data type).

AI-Hints:

  • Use Stats() in tests as a deterministic admission check (counts, feature flags).

type Nilable

type Nilable interface {
	// IsNil reports whether the receiver should be treated as nil.
	// It MUST be side-effect free, deterministic, non-allocating, and MUST NOT panic.
	IsNil() bool
}

Nilable provides an explicit, reflect-free mechanism to treat typed-nil receivers stored inside interfaces as nil during validation and testing.

Implementation:

  • Stage 1: Callers accept an interface value that may hold a typed nil pointer.
  • Stage 2: If the dynamic value implements Nilable, callers invoke IsNil().
  • Stage 3: If IsNil reports true, the value is treated as nil by the caller.

Behavior highlights:

  • Avoids reflect in foundational validators and test helpers.
  • Keeps nil-detection O(1) and deterministic.
  • Optional: types that do not implement Nilable are checked with regular `== nil`.

Returns:

  • bool: true if the receiver should be treated as nil.

Errors:

  • None. IsNil MUST NOT panic and MUST NOT allocate.

Determinism:

  • Deterministic and side-effect free (required).

Complexity:

  • Time O(1), Space O(1).

Notes:

  • In Go, an interface can be non-nil while holding a typed nil pointer.

AI-Hints:

  • Implement IsNil() on pointer-backed types that are commonly stored behind interfaces.

type Vertex

type Vertex struct {
	// ID uniquely identifies a vertex within a single Graph instance.
	ID string

	// Metadata holds arbitrary caller-managed payload.
	// Clone/View operations shallow-copy this map pointer.
	// core does not synchronize metadata contents.
	Metadata map[string]interface{}
}

Vertex is the canonical node record used by Graph; the unique key is Vertex.ID. Metadata is an opaque, caller-managed payload that the core does not interpret.

Memory Policy (Aliasing & Ownership): - Graph query methods may return *Vertex aliases to catalog records. - Vertex.ID MUST be treated as immutable once the vertex is published in a Graph. - Metadata is a REFERENCE TYPE (map pointer). - Clone and view operations perform a SHALLOW COPY of this pointer. - Result: the source graph and its clones/views SHARE the same underlying Metadata maps. - The caller is responsible for deep-copying Metadata if total isolation is required.

Implementation:

  • Stage 1: Graph stores vertices in a map keyed by Vertex.ID.
  • Stage 2: Algorithms treat Metadata as an opaque pointer; Clone is shallow.

Behavior highlights:

  • Vertex identity is stable for the lifetime of the graph.
  • Metadata ownership belongs to the caller; core never mutates it.

Inputs:

  • ID: unique identifier within a Graph; must be non-empty.
  • Metadata: arbitrary user payload; may be nil.

Returns:

  • N/A (data type).

Errors:

  • N/A (data type).

Determinism:

  • Vertex.ID is the stable ordering key for public enumeration.

Complexity:

  • N/A (data type).

Notes:

  • Graph.Clone copies the Metadata map pointer; callers must deep-copy if required.

AI-Hints:

  • Prefer short, stable IDs for deterministic logs and golden tests.
  • Keep Metadata small and immutable if used concurrently.

func (*Vertex) IsNil

func (v *Vertex) IsNil() bool

IsNil reports whether the receiver should be treated as nil when stored inside interfaces.

Implementation:

  • Stage 1: Compare the receiver pointer to nil.
  • Stage 2: Return the result without dereferencing.

Behavior highlights:

  • Safe for typed-nil stored inside interfaces (no panic).
  • Reflect-free nil detection used by validators and test helpers via core.Nilable.

Returns:

  • bool: true iff receiver == nil.

Errors:

  • None.

Determinism:

  • Deterministic.

Complexity:

  • Time O(1), Space O(1).

Notes:

  • Keep this method trivial; do not add deep validation.

AI-Hints:

  • Implement the same pattern for other pointer-backed core types that appear behind interfaces.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL