VarHandle API¶
VarHandle is the core abstraction in the Groggy Algorithm Builder. It represents a variable in the algorithm's intermediate representation (IR) and provides operator overloading for natural mathematical syntax.
Overview¶
Every operation in the builder creates and returns a VarHandle:
@algorithm
def example(sG):
x = sG.nodes(1.0) # VarHandle
y = x * 2.0 # VarHandle
z = y + 1.0 # VarHandle
result = z.normalize() # VarHandle
return result
Constructor¶
Direct construction not recommended. VarHandles are created by the builder automatically.
# DON'T DO THIS:
# var = VarHandle("my_var", builder)
# DO THIS:
var = sG.nodes(1.0) # Builder creates VarHandle internally
Attributes¶
name: str¶
The internal variable name in the IR (e.g., "node_0", "add_3").
builder: AlgorithmBuilder¶
Reference to the parent builder (used internally for chaining operations).
Arithmetic Operators¶
All arithmetic operators create new VarHandles representing the operation.
Addition: +¶
result = x + y # Add two variables
result = x + 5.0 # Add scalar to variable
result = 5.0 + x # Scalar + variable (commutative)
IR Generated:
Subtraction: -¶
result = x - y # Subtract variables
result = x - 5.0 # Subtract scalar
result = 5.0 - x # Reverse subtraction
Multiplication: *¶
Common pattern:
Division: /¶
Division by zero handling:
Negation: - (unary)¶
Example:
Comparison Operators¶
Comparison operators return VarHandles containing boolean masks (0.0 = false, 1.0 = true).
Equality: ==¶
Example:
Inequality: !=¶
Greater Than: >¶
Less Than: <¶
Greater or Equal: >=¶
Less or Equal: <=¶
Fluent Methods¶
Fluent methods enable method chaining for readable code.
.where(if_true, if_false)¶
Conditional selection based on boolean mask.
Example:
is_sink = (degrees == 0.0)
contrib = is_sink.where(0.0, ranks / degrees)
# If sink: contrib = 0.0
# Else: contrib = ranks / degrees
IR Generated:
{
"type": "core.where",
"condition": "is_sink",
"if_true": "0.0",
"if_false": "ranks_div_degrees",
"output": "where_0"
}
.reduce(op: str)¶
Aggregate all values to a single scalar.
Supported operations:
- "sum" - Sum all values
- "mean" - Average of all values
- "min" - Minimum value
- "max" - Maximum value
total = values.reduce("sum")
average = values.reduce("mean")
min_val = values.reduce("min")
max_val = values.reduce("max")
Example - PageRank sink handling:
Returns: VarHandle representing a scalar (broadcasted when used).
.normalize()¶
Normalize values to sum to 1.0.
Example:
@algorithm("pagerank")
def pagerank(sG, ...):
# ... compute ranks ...
return ranks.normalize() # Final normalized PageRank scores
.degrees()¶
Get the out-degree for each node.
Note: Must be called on a VarHandle representing node values.
Example:
IR Generated:
Matrix Notation¶
Neighbor Aggregation: sG @ values¶
The @ operator performs neighbor aggregation (sum by default).
Equivalent to:
Example - PageRank:
How it works:
IR Generated:
Operator Precedence¶
Python's standard operator precedence applies:
# Precedence (highest to lowest):
# 1. - (unary negation)
# 2. *, /
# 3. +, -
# 4. ==, !=, <, >, <=, >=
# Example:
result = x * 2.0 + y / 3.0 # Parsed as: (x * 2.0) + (y / 3.0)
Use parentheses for clarity:
Common Patterns¶
Safe Division¶
Weighted Average¶
Clamping¶
# Clamp to [0, 1]
clamped = values.where(values > 1.0, 1.0, values)
clamped = clamped.where(clamped < 0.0, 0.0, clamped)
Conditional Computation¶
Normalization Patterns¶
# L1 normalization (sum to 1)
normalized = values / values.reduce("sum")
# Or use helper:
normalized = values.normalize()
# L2 normalization (unit length)
# (future: when pow() is available)
Type Coercion¶
Scalars are automatically converted to VarHandles when needed:
result = x + 5.0 # 5.0 → scalar VarHandle internally
result = x * 0.85 # 0.85 → scalar VarHandle internally
Supported scalar types:
- float (preferred)
- int (converted to float)
Not supported:
- str, list, dict, etc.
Error Handling¶
Invalid Operations¶
# These will raise errors:
result = x + "string" # TypeError: unsupported operand type
result = x @ y # Only sG can use @
Undefined Variables¶
The builder validates variable dependencies:
@algorithm
def bad_algo(sG):
result = undefined_var + 1.0 # NameError: undefined_var not defined
return result
Performance Considerations¶
No Immediate Execution¶
Operators build IR, they don't execute immediately:
x = sG.nodes(1.0)
y = x * 2.0 # No computation yet
z = y + 1.0 # Still no computation
algo = builder.build() # Still just building IR
result = sg.apply(algo) # NOW execution happens in Rust
Chaining Efficiency¶
Long chains are fine - they're compiled, not interpreted:
# This is efficient:
result = ((x * 2.0 + 1.0) / y).normalize()
# Single FFI call, fused execution in Rust
Advanced Usage¶
Manual Variable Naming¶
# Normally variables are auto-named (add_0, mul_1, etc.)
# For debugging, you can create named variables:
ranks = sG.builder.var("ranks", initial_ranks)
# "ranks" will appear in IR, easier to trace
Accessing IR¶
Migration from Old API¶
Before (explicit builder calls):¶
result = builder.core.add(builder.core.mul(x, 2.0), 1.0)
mask = builder.core.compare(values, "gt", 0.5)
output = builder.core.where(mask, a, b)
After (VarHandle operators):¶
Reduction: 75-80% less code, 100% more readable.
Examples¶
PageRank Iteration¶
with sG.builder.iter.loop(max_iter):
# Compute contribution from each node
contrib = ranks / (degrees + 1e-9)
# Aggregate from neighbors
neighbor_sum = sG @ contrib
# Update ranks
ranks = sG.builder.var("ranks",
damping * neighbor_sum + (1 - damping) / sG.N
)
Label Propagation Mode¶
# Collect neighbor labels
neighbor_labels = sG.builder.graph_ops.collect_neighbor_values(labels)
# Find most common
most_common = sG.builder.core.mode(neighbor_labels)
# Update
labels = sG.builder.var("labels", most_common)
Custom Centrality¶
@algorithm
def custom_centrality(sG, alpha=0.5):
degrees = sG.builder.graph_ops.degree()
neighbors = sG.nodes(1.0)
neighbor_count = sG @ neighbors
# Blend degree and neighbor count
centrality = alpha * degrees + (1 - alpha) * neighbor_count
return centrality.normalize()
See Also¶
- CoreOps API - Arithmetic operations
- GraphOps API - Topology operations
- Operator overloading is covered in the main builder guide and examples.
- Algorithm Examples