kumi 0.0.15 → 0.0.17

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +13 -0
  3. data/golden/cascade_logic/schema.kumi +3 -1
  4. data/lib/kumi/analyzer.rb +11 -9
  5. data/lib/kumi/core/analyzer/passes/broadcast_detector.rb +0 -81
  6. data/lib/kumi/core/analyzer/passes/ir_dependency_pass.rb +18 -20
  7. data/lib/kumi/core/analyzer/passes/ir_execution_schedule_pass.rb +67 -0
  8. data/lib/kumi/core/analyzer/passes/lower_to_ir_pass.rb +0 -36
  9. data/lib/kumi/core/analyzer/passes/toposorter.rb +1 -39
  10. data/lib/kumi/core/analyzer/passes/unsat_detector.rb +8 -191
  11. data/lib/kumi/core/compiler/access_builder.rb +20 -10
  12. data/lib/kumi/core/compiler/access_codegen.rb +61 -0
  13. data/lib/kumi/core/compiler/access_emit/base.rb +173 -0
  14. data/lib/kumi/core/compiler/access_emit/each_indexed.rb +56 -0
  15. data/lib/kumi/core/compiler/access_emit/materialize.rb +45 -0
  16. data/lib/kumi/core/compiler/access_emit/ravel.rb +50 -0
  17. data/lib/kumi/core/compiler/access_emit/read.rb +32 -0
  18. data/lib/kumi/core/ir/execution_engine/interpreter.rb +36 -181
  19. data/lib/kumi/core/ir/execution_engine/values.rb +8 -8
  20. data/lib/kumi/core/ir/execution_engine.rb +3 -19
  21. data/lib/kumi/dev/parse.rb +12 -12
  22. data/lib/kumi/runtime/executable.rb +22 -175
  23. data/lib/kumi/runtime/run.rb +105 -0
  24. data/lib/kumi/schema.rb +8 -13
  25. data/lib/kumi/version.rb +1 -1
  26. data/lib/kumi.rb +3 -2
  27. metadata +10 -25
  28. data/BACKLOG.md +0 -34
  29. data/config/functions.yaml +0 -352
  30. data/docs/functions/analyzer_integration.md +0 -199
  31. data/docs/functions/signatures.md +0 -171
  32. data/examples/hash_objects_demo.rb +0 -138
  33. data/lib/kumi/core/analyzer/passes/function_signature_pass.rb +0 -199
  34. data/lib/kumi/core/analyzer/passes/type_consistency_checker.rb +0 -48
  35. data/lib/kumi/core/functions/dimension.rb +0 -98
  36. data/lib/kumi/core/functions/dtypes.rb +0 -20
  37. data/lib/kumi/core/functions/errors.rb +0 -11
  38. data/lib/kumi/core/functions/kernel_adapter.rb +0 -45
  39. data/lib/kumi/core/functions/loader.rb +0 -119
  40. data/lib/kumi/core/functions/registry_v2.rb +0 -68
  41. data/lib/kumi/core/functions/shape.rb +0 -70
  42. data/lib/kumi/core/functions/signature.rb +0 -122
  43. data/lib/kumi/core/functions/signature_parser.rb +0 -86
  44. data/lib/kumi/core/functions/signature_resolver.rb +0 -272
  45. data/lib/kumi/kernels/ruby/aggregate_core.rb +0 -105
  46. data/lib/kumi/kernels/ruby/datetime_scalar.rb +0 -21
  47. data/lib/kumi/kernels/ruby/mask_scalar.rb +0 -15
  48. data/lib/kumi/kernels/ruby/scalar_core.rb +0 -63
  49. data/lib/kumi/kernels/ruby/string_scalar.rb +0 -19
  50. data/lib/kumi/kernels/ruby/vector_struct.rb +0 -39
@@ -1,352 +0,0 @@
1
- # ==================== CORE - SCALAR (elementwise) ====================
2
-
3
- - name: core.add
4
- domain: core
5
- opset: 1
6
- class: scalar
7
- summary: Numeric addition with broadcasting
8
- signature: ["(),()->()", "(i),(i)->(i)", "(i,j),(i,j)->(i,j)"]
9
- type_vars: {T: Numeric, U: Numeric}
10
- dtypes: {result: "promote(T,U)", casting: "same_kind"}
11
- null_policy: propagate
12
- algebra: {associative: true, commutative: true, identity: 0}
13
- vectorization: {mask_aware: true}
14
- kernels:
15
- - backend: ruby
16
- impl: kumi_add
17
- priority: 10
18
-
19
- - name: core.sub
20
- domain: core
21
- opset: 1
22
- class: scalar
23
- signature: ["(),()->()", "(i),(i)->(i)", "(i,j),(i,j)->(i,j)"]
24
- type_vars: {T: Numeric, U: Numeric}
25
- dtypes: {result: "promote(T,U)", casting: "same_kind"}
26
- null_policy: propagate
27
- kernels:
28
- - backend: ruby
29
- impl: kumi_sub
30
- priority: 10
31
-
32
- - name: core.mul
33
- domain: core
34
- opset: 1
35
- class: scalar
36
- signature: ["(),()->()", "(i),(i)->(i)", "(i,j),(i,j)->(i,j)"]
37
- type_vars: {T: Numeric, U: Numeric}
38
- dtypes: {result: "promote(T,U)", casting: "same_kind"}
39
- null_policy: propagate
40
- algebra: {associative: true, commutative: true, identity: 1, annihilator: 0}
41
- kernels:
42
- - backend: ruby
43
- impl: kumi_mul
44
- priority: 10
45
-
46
- - name: core.div
47
- domain: core
48
- opset: 1
49
- class: scalar
50
- signature: ["(),()->()", "(i),(i)->(i)", "(i,j),(i,j)->(i,j)"]
51
- type_vars: {T: Numeric, U: Numeric}
52
- dtypes: {result: "promote_float(T,U)", casting: "same_kind"}
53
- null_policy: propagate
54
- kernels:
55
- - backend: ruby
56
- impl: kumi_div
57
- priority: 10
58
-
59
- - name: core.eq
60
- domain: core
61
- opset: 1
62
- class: scalar
63
- signature: ["(),()->()", "(i),(i)->(i)", "(i,j),(i,j)->(i,j)"]
64
- type_vars: {T: Any, U: Any}
65
- dtypes: {result: "bool"}
66
- null_policy: propagate
67
- algebra: {idempotent: true}
68
- kernels:
69
- - backend: ruby
70
- impl: kumi_eq
71
- priority: 10
72
-
73
- - name: core.gt
74
- domain: core
75
- opset: 1
76
- class: scalar
77
- signature: ["(),()->()", "(i),(i)->(i)"]
78
- type_vars: {T: Orderable, U: Orderable}
79
- dtypes: {result: "bool"}
80
- null_policy: propagate
81
- kernels:
82
- - backend: ruby
83
- impl: kumi_gt
84
- priority: 10
85
-
86
- - name: core.gte
87
- domain: core
88
- opset: 1
89
- class: scalar
90
- signature: ["(),()->()", "(i),(i)->(i)"]
91
- type_vars: {T: Orderable, U: Orderable}
92
- dtypes: {result: "bool"}
93
- null_policy: propagate
94
- kernels:
95
- - backend: ruby
96
- impl: kumi_gte
97
- priority: 10
98
-
99
- - name: core.and
100
- domain: core
101
- opset: 1
102
- class: scalar
103
- signature: ["(),()->()", "(i),(i)->(i)"]
104
- type_vars: {B1: Bool, B2: Bool}
105
- dtypes: {result: "bool"}
106
- null_policy: propagate
107
- algebra: {associative: true, commutative: true, identity: true, annihilator: false}
108
- vectorization: {short_circuit: left_to_right, mask_aware: true}
109
- kernels:
110
- - backend: ruby
111
- impl: kumi_and
112
- priority: 10
113
-
114
- - name: core.or
115
- domain: core
116
- opset: 1
117
- class: scalar
118
- signature: ["(),()->()", "(i),(i)->(i)"]
119
- type_vars: {B1: Bool, B2: Bool}
120
- dtypes: {result: "bool"}
121
- null_policy: propagate
122
- algebra: {associative: true, commutative: true, identity: false, annihilator: true}
123
- vectorization: {short_circuit: left_to_right, mask_aware: true}
124
- kernels:
125
- - backend: ruby
126
- impl: kumi_or
127
- priority: 10
128
-
129
- - name: core.not
130
- domain: core
131
- opset: 1
132
- class: scalar
133
- signature: ["()->()", "(i)->(i)"]
134
- type_vars: {B: Bool}
135
- dtypes: {result: "bool"}
136
- null_policy: propagate
137
- kernels:
138
- - backend: ruby
139
- impl: kumi_not
140
- priority: 10
141
-
142
- # ==================== AGGREGATES (reduce last axis in signature) ==============
143
-
144
- - name: agg.sum
145
- domain: core
146
- opset: 1
147
- class: aggregate
148
- signature: ["(i)->()", "(i,j)->(i)"]
149
- type_vars: {T: Numeric}
150
- dtypes: {result: "promote(T)"}
151
- null_policy: skip
152
- options: {min_count: 0}
153
- algebra: {associative: true, commutative: true, identity: 0}
154
- kernels:
155
- - backend: ruby
156
- impl: kumi_sum
157
- priority: 10
158
-
159
- - name: agg.min
160
- domain: core
161
- opset: 1
162
- class: aggregate
163
- signature: ["(i)->()", "(i,j)->(i)"]
164
- type_vars: {T: Orderable}
165
- dtypes: {result: "T"}
166
- null_policy: skip
167
- kernels:
168
- - backend: ruby
169
- impl: kumi_min
170
- priority: 10
171
-
172
- - name: agg.max
173
- domain: core
174
- opset: 1
175
- class: aggregate
176
- signature: ["(i)->()", "(i,j)->(i)"]
177
- type_vars: {T: Orderable}
178
- dtypes: {result: "T"}
179
- null_policy: skip
180
- kernels:
181
- - backend: ruby
182
- impl: kumi_max
183
- priority: 10
184
-
185
- - name: agg.mean
186
- domain: core
187
- opset: 1
188
- class: aggregate
189
- signature: ["(i)->()", "(i,j)->(i)"]
190
- type_vars: {T: Numeric}
191
- dtypes: {result: "float"}
192
- null_policy: skip
193
- options: {min_count: 0}
194
- kernels:
195
- - backend: ruby
196
- impl: kumi_mean
197
- priority: 10
198
-
199
- # ==================== STRUCTURE / SHAPE / JOIN ================================
200
-
201
- - name: struct.join
202
- domain: struct
203
- opset: 1
204
- class: structure
205
- summary: Align inputs by axes using zip or product
206
- signature: ["(i),(i)->(i)@zip", "(i),(j)->(i,j)@product"]
207
- null_policy: error_on_missing
208
- kernels:
209
- - backend: ruby
210
- impl: join_zip
211
- when: {policy: zip}
212
- priority: 10
213
- - backend: ruby
214
- impl: join_product
215
- when: {policy: product}
216
- priority: 10
217
-
218
- - name: struct.align_to
219
- domain: struct
220
- opset: 1
221
- class: structure
222
- signature: ["(i)->(i)", "(i,j)->(i,j)"]
223
- traits: {reorders: false}
224
- null_policy: error_on_missing
225
- kernels:
226
- - backend: ruby
227
- impl: align_to
228
- priority: 10
229
-
230
- - name: struct.lift
231
- domain: struct
232
- opset: 1
233
- class: structure
234
- signature: ["(i)->(i)", "(i,j)->(i,j)"]
235
- summary: Group flat rows back to nested collections based on indices
236
- kernels:
237
- - backend: ruby
238
- impl: lift
239
- priority: 10
240
-
241
- - name: struct.flatten
242
- domain: struct
243
- opset: 1
244
- class: structure
245
- signature: ["(i,j)->(k)"]
246
- shape_fn: "flatten_axes(i,j)->k"
247
- kernels:
248
- - backend: ruby
249
- impl: flatten
250
- priority: 10
251
-
252
- - name: struct.take
253
- domain: struct
254
- opset: 1
255
- class: structure
256
- signature: ["(i),(i)->(i)"] # take(values, indices) elemwise
257
- type_vars: {T: Any}
258
- dtypes: {result: "T"}
259
- null_policy: error
260
- kernels:
261
- - backend: ruby
262
- impl: take
263
- priority: 10
264
-
265
- - name: struct.size
266
- domain: struct
267
- opset: 1
268
- class: vector
269
- signature: ["(i)->()", "(i,j)->()"]
270
- dtypes: {result: "int"}
271
- null_policy: error
272
- kernels:
273
- - backend: ruby
274
- impl: size
275
- priority: 10
276
-
277
- # ==================== MASK / CONDITIONAL ======================================
278
-
279
- - name: mask.where
280
- domain: mask
281
- opset: 1
282
- class: scalar
283
- summary: Ternary select with broadcasting of then/else
284
- signature:
285
- - "(i),(i),(i)->(i)"
286
- - "(i),(),()->(i)"
287
- - "(),(i),()->(i)"
288
- - "(),(),()->()"
289
- type_vars: {B: Bool, T: Any, U: Any}
290
- dtypes: {result: "unify(T,U)"}
291
- null_policy: propagate
292
- vectorization: {mask_aware: true, short_circuit: left_to_right}
293
- kernels:
294
- - backend: ruby
295
- impl: where
296
- priority: 10
297
-
298
- # ==================== STRING (minimal) ========================================
299
-
300
- - name: string.concat
301
- domain: string
302
- opset: 1
303
- class: scalar
304
- signature: ["(),()->()", "(i),(i)->(i)"]
305
- type_vars: {S1: String, S2: String}
306
- dtypes: {result: "string"}
307
- null_policy: propagate
308
- kernels:
309
- - backend: ruby
310
- impl: str_concat
311
- priority: 10
312
-
313
- - name: string.length
314
- domain: string
315
- opset: 1
316
- class: vector
317
- signature: ["(i)->(i)", "()->()"]
318
- type_vars: {S: String}
319
- dtypes: {result: "int"}
320
- null_policy: propagate
321
- kernels:
322
- - backend: ruby
323
- impl: str_length
324
- priority: 10
325
-
326
- # ==================== DATETIME (minimal) ======================================
327
-
328
- - name: datetime.add_days
329
- domain: datetime
330
- opset: 1
331
- class: scalar
332
- signature: ["(),()->()", "(i),(i)->(i)"]
333
- type_vars: {D: DateLike, N: IntLike}
334
- dtypes: {result: "datetime"}
335
- null_policy: propagate
336
- kernels:
337
- - backend: ruby
338
- impl: dt_add_days
339
- priority: 10
340
-
341
- - name: datetime.diff_days
342
- domain: datetime
343
- opset: 1
344
- class: scalar
345
- signature: ["(),()->()", "(i),(i)->(i)"]
346
- type_vars: {D1: DateLike, D2: DateLike}
347
- dtypes: {result: "int"}
348
- null_policy: propagate
349
- kernels:
350
- - backend: ruby
351
- impl: dt_diff_days
352
- priority: 10
@@ -1,199 +0,0 @@
1
- # Function Signature Analysis Integration
2
-
3
- This document describes how NEP-20 function signatures integrate with Kumi's multi-pass analyzer architecture.
4
-
5
- ## Architecture Overview
6
-
7
- Function signature resolution is integrated into the analyzer pipeline through a node index system that allows passes to share metadata efficiently.
8
-
9
- ```
10
- ┌─────────────────┐ ┌──────────────────────┐ ┌────────────────────┐
11
- │ Toposorter │───▶│ FunctionSignaturePass │───▶│ LowerToIRPass │
12
- │ Creates node │ │ Resolves signatures │ │ Validates metadata │
13
- │ index by ID │ │ Attaches metadata │ │ Emits IR ops │
14
- └─────────────────┘ └──────────────────────┘ └────────────────────┘
15
- ```
16
-
17
- ## Node Index System
18
-
19
- ### Creation (Toposorter)
20
-
21
- The `Toposorter` pass creates a comprehensive index of all nodes in the AST:
22
-
23
- ```ruby
24
- # State structure after Toposorter
25
- state[:node_index] = {
26
- object_id_1 => {
27
- node: CallExpression_instance,
28
- type: "CallExpression",
29
- metadata: {}
30
- },
31
- # ... more nodes
32
- }
33
- ```
34
-
35
- ### Population (FunctionSignaturePass)
36
-
37
- The `FunctionSignaturePass` populates metadata for `CallExpression` nodes:
38
-
39
- ```ruby
40
- # After FunctionSignaturePass
41
- entry[:metadata] = {
42
- signature: Signature_object, # Full signature object
43
- result_axes: [:i, :j], # Output dimension names
44
- join_policy: :zip, # Cross-dimensional policy
45
- dropped_axes: [:k], # Dimensions eliminated by reductions
46
- effective_signature: { # Normalized for lowering
47
- in_shapes: [[:i, :k], [:k, :j]],
48
- out_shape: [:i, :j],
49
- join_policy: :zip
50
- },
51
- dim_env: { i: :i, j: :j, k: :k }, # Dimension variable bindings
52
- shape_contract: { # Simplified for lowering
53
- in: [[:i, :k], [:k, :j]],
54
- out: [:i, :j],
55
- join: :zip
56
- },
57
- signature_score: 0 # Match quality (0 = exact)
58
- }
59
- ```
60
-
61
- ### Consumption (LowerToIRPass)
62
-
63
- The lowering pass validates and uses the signature metadata:
64
-
65
- ```ruby
66
- def validate_signature_metadata(expr, entry)
67
- node_index = get_state(:node_index)
68
- metadata = node_index[expr.object_id][:metadata]
69
-
70
- # Validate dropped axes for reductions
71
- if entry&.reducer && metadata[:dropped_axes]
72
- # Assert axis exists in scope
73
- end
74
-
75
- # Warn about join policies not yet implemented
76
- if metadata[:join_policy]
77
- # Log warning or feature flag check
78
- end
79
- end
80
- ```
81
-
82
- ## Pass Integration Points
83
-
84
- ### 1. Signature Resolution
85
-
86
- **Location**: After `BroadcastDetector`, before `TypeChecker`
87
-
88
- **Purpose**: Resolve NEP-20 signatures for all function calls
89
-
90
- **Input**:
91
- - Node index from Toposorter
92
- - Broadcast metadata (optional)
93
- - Current function registry
94
-
95
- **Output**: Rich signature metadata attached to call nodes
96
-
97
- ### 2. Type Checking Enhancement
98
-
99
- **Integration**: `TypeChecker` can now use signature metadata for enhanced validation:
100
-
101
- ```ruby
102
- def validate_function_call(node, errors)
103
- # Get resolved signature metadata
104
- node_entry = node_index[node.object_id]
105
- if node_entry && node_entry[:metadata][:signature]
106
- # Use NEP-20 signature for validation
107
- validate_nep20_signature(node, node_entry[:metadata], errors)
108
- else
109
- # Fall back to legacy registry-based validation
110
- validate_legacy_signature(node, errors)
111
- end
112
- end
113
- ```
114
-
115
- ### 3. Lowering Integration
116
-
117
- **Integration**: `LowerToIRPass` validates signature consistency and emits appropriate IR:
118
-
119
- ```ruby
120
- when Syntax::CallExpression
121
- entry = Kumi::Registry.entry(expr.fn_name)
122
- validate_signature_metadata(expr, entry) # Read-only validation
123
-
124
- # Use shape contract for IR generation
125
- if node_entry = node_index[expr.object_id]
126
- contract = node_entry[:metadata][:shape_contract]
127
- # Use contract to emit appropriate reduce/join ops
128
- end
129
- ```
130
-
131
- ## Metadata Contract
132
-
133
- All passes can rely on this metadata structure for `CallExpression` nodes:
134
-
135
- | Field | Type | Description |
136
- |-------|------|-------------|
137
- | `:signature` | `Signature` | Full signature object with dimensions |
138
- | `:result_axes` | `Array<Symbol>` | Output dimension names |
139
- | `:join_policy` | `Symbol?` | `:zip`, `:product`, or `nil` |
140
- | `:dropped_axes` | `Array<Symbol>` | Dimensions eliminated (reductions) |
141
- | `:effective_signature` | `Hash` | Normalized signature for lowering |
142
- | `:dim_env` | `Hash` | Dimension variable bindings |
143
- | `:shape_contract` | `Hash` | Simplified contract for IR generation |
144
- | `:signature_score` | `Integer` | Match quality (0 = exact) |
145
-
146
- ## Error Handling
147
-
148
- Enhanced error messages include signature information:
149
-
150
- ```ruby
151
- # Before
152
- "operator `multiply` expects 2 args, got 3"
153
-
154
- # After
155
- "Signature mismatch for `multiply` with args (i,j), (k).
156
- Candidates: (),()->() | (i),(i)->(i).
157
- no matching signature for shapes (i,j), (k)"
158
- ```
159
-
160
- ## Feature Flags
161
-
162
- Control NEP-20 behavior at runtime:
163
-
164
- ```bash
165
- export KUMI_ENABLE_FLEX=1 # Enable flexible dimension matching (?)
166
- export KUMI_ENABLE_BCAST1=1 # Enable broadcastable matching (|1)
167
- ```
168
-
169
- ## Future Extensions
170
-
171
- The node index architecture supports future enhancements:
172
-
173
- ### Registry V2 Integration
174
- ```ruby
175
- # Read signatures from YAML registry
176
- signatures = RegistryV2.get_function_signatures(node.fn_name)
177
- ```
178
-
179
- ### Type System Integration
180
- ```ruby
181
- # Add dtype metadata alongside shape metadata
182
- metadata[:result_dtype] = infer_result_dtype(args, signature)
183
- ```
184
-
185
- ### Optimization Metadata
186
- ```ruby
187
- # Add optimization hints
188
- metadata[:can_vectorize] = true
189
- metadata[:memory_access_pattern] = :sequential
190
- ```
191
-
192
- ## Performance Considerations
193
-
194
- - **Node index creation**: O(n) where n = total AST nodes
195
- - **Signature resolution**: O(k*m) where k = signatures, m = arguments
196
- - **Memory overhead**: ~100 bytes per CallExpression node
197
- - **Pass integration**: Zero performance impact on existing passes
198
-
199
- The architecture is designed for extensibility while maintaining backward compatibility with the existing analyzer pipeline.
@@ -1,171 +0,0 @@
1
- # Function Signatures (NEP 20)
2
-
3
- Kumi implements **NEP 20 conformant function signatures** for describing generalized universal functions. This system provides precise control over multidimensional operations with support for fixed-size, flexible, and broadcastable dimensions.
4
-
5
- ## Basic Syntax
6
-
7
- Function signatures use the format: `<inputs> -> <outputs>[@policy]`
8
-
9
- ```ruby
10
- "(i),(i)->(i)" # Element-wise operation on vectors
11
- "(m,n),(n,p)->(m,p)" # Matrix multiplication
12
- "(i,j)->(i)" # Reduction along j axis
13
- "(),()->()" # Scalar operation
14
- ```
15
-
16
- ## NEP 20 Extensions
17
-
18
- ### Fixed-Size Dimensions
19
-
20
- Use integers to specify exact dimension sizes:
21
-
22
- ```ruby
23
- "(3),(3)->(3)" # Cross product for 3-vectors
24
- "()->(2)" # Function returning 2D vector
25
- "(),()->(3)" # Two scalars to 3D vector
26
- ```
27
-
28
- Fixed-size dimensions must match exactly - no broadcasting allowed.
29
-
30
- ### Flexible Dimensions (?)
31
-
32
- The `?` modifier indicates dimensions that can be omitted if not present in all operands:
33
-
34
- ```ruby
35
- "(m?,n),(n,p?)->(m?,p?)" # matmul signature - handles:
36
- # - (m,n),(n,p) -> (m,p) matrix * matrix
37
- # - (n),(n,p) -> (p) vector * matrix
38
- # - (m,n),(n) -> (m) matrix * vector
39
- # - (n),(n) -> () vector * vector
40
- ```
41
-
42
- ### Broadcastable Dimensions (|1)
43
-
44
- The `|1` modifier allows dimensions to broadcast against scalar or size-1 dimensions:
45
-
46
- ```ruby
47
- "(n|1),(n|1)->()" # all_equal - compares vectors or scalars
48
- "(i|1),(j|1)->(i,j)" # Outer product with broadcasting
49
- ```
50
-
51
- **Constraints:**
52
- - Only input dimensions can be broadcastable
53
- - Output dimensions cannot have `|1` modifier
54
- - Cannot combine `?` and `|1` on same dimension
55
-
56
- ## Join Policies
57
-
58
- Control how different dimension names are combined:
59
-
60
- ### Default (nil policy)
61
- All non-scalar arguments must have compatible dimension names:
62
- ```ruby
63
- "(i),(i)->(i)" # ✓ Compatible - same dimensions
64
- "(i),(j)->(i,j)" # ✗ Incompatible without policy
65
- ```
66
-
67
- ### @product Policy
68
- Allows different dimensions, creates Cartesian product:
69
- ```ruby
70
- "(i),(j)->(i,j)@product" # Outer product
71
- ```
72
-
73
- ### @zip Policy
74
- Allows different dimensions, pairs them up:
75
- ```ruby
76
- "(i),(j)->(i)@zip" # Paired operation
77
- ```
78
-
79
- ## Examples from NEP 20
80
-
81
- | Signature | Use Case | Description |
82
- |-----------|----------|-------------|
83
- | `(),()->()` | Addition | Scalar operations |
84
- | `(i)->()` | Sum | Reduction over last axis |
85
- | `(i\|1),(i\|1)->()` | all_equal | Equality test with broadcasting |
86
- | `(i),(i)->()` | dot product | Inner vector product |
87
- | `(m,n),(n,p)->(m,p)` | matmul | Matrix multiplication |
88
- | `(3),(3)->(3)` | cross | Cross product for 3-vectors |
89
- | `(m?,n),(n,p?)->(m?,p?)` | matmul | Universal matrix operations |
90
-
91
- ## Signature Validation Rules
92
-
93
- 1. **Arity matching**: Number of arguments must match signature
94
- 2. **Fixed-size consistency**: Integer dimensions must match exactly
95
- 3. **Broadcasting rules**: `|1` dimensions can broadcast to any size
96
- 4. **Flexible resolution**: `?` dimensions resolved based on presence
97
- 5. **Output constraints**: No broadcastable (`|1`) dimensions in outputs
98
- 6. **Policy requirements**: Different dimension names need explicit policy
99
-
100
- ## Matching Priority
101
-
102
- When multiple signatures are available, resolution prefers:
103
-
104
- 1. **Exact matches** (score: 0) - All dimensions match perfectly
105
- 2. **Fixed-size matches** (score: 0-2) - Integer dimensions match
106
- 3. **Broadcast matches** (score: 1-3) - Scalar broadcasting
107
- 4. **Flexible matches** (score: 10+) - Dimension omission/addition
108
-
109
- Lower scores indicate better matches.
110
-
111
- ## Implementation Notes
112
-
113
- Signatures are parsed into `Dimension` objects that track:
114
- - Name (Symbol or Integer)
115
- - Flexible flag (`?`)
116
- - Broadcastable flag (`|1`)
117
-
118
- The resolver handles NEP 20 semantics including:
119
- - Flexible dimension resolution
120
- - Broadcastable matching
121
- - Fixed-size validation
122
- - Join policy enforcement
123
-
124
- ## Constraint Solver
125
-
126
- Kumi implements a **dimension constraint solver** that validates cross-argument dimension consistency for operations like matrix multiplication:
127
-
128
- ```ruby
129
- signature = "(m,n),(n,p)->(m,p)"
130
- arg_shapes = [[:m, :n], [:n, :p]]
131
-
132
- # The solver validates that 'n' dimensions are consistent across arguments
133
- plan = resolver.choose(signatures: [sig], arg_shapes: arg_shapes)
134
- # Returns: { env: { m: :m, n: :n, p: :p }, result_axes: [:m, :p] }
135
- ```
136
-
137
- The constraint solver:
138
- - Links repeated dimension names across arguments
139
- - Validates dimension consistency (same `n` in both positions)
140
- - Returns dimension environment bindings
141
- - Enables complex operations without explicit join policies
142
-
143
- ## Integration with Analyzer
144
-
145
- Function signatures integrate with Kumi's analyzer pipeline through:
146
-
147
- ### FunctionSignaturePass
148
- Resolves NEP-20 signatures for all function calls and attaches metadata:
149
- - `:result_axes` - Output dimension names
150
- - `:join_policy` - Cross-dimensional operation policy
151
- - `:dropped_axes` - Dimensions eliminated by reductions
152
- - `:effective_signature` - Normalized signature for lowering
153
- - `:dim_env` - Dimension variable bindings
154
- - `:shape_contract` - Simplified contract for lowering
155
-
156
- ### Node Index Architecture
157
- The analyzer creates a node index mapping `object_id → metadata` that passes can use to:
158
- - Attach signature resolution results
159
- - Share metadata across analysis passes
160
- - Validate consistency in lowering
161
-
162
- ## Feature Flags
163
-
164
- Control NEP-20 extensions with environment variables:
165
-
166
- ```bash
167
- KUMI_ENABLE_FLEX=1 # Enable flexible dimension matching (?)
168
- KUMI_ENABLE_BCAST1=1 # Enable broadcastable dimension matching (|1)
169
- ```
170
-
171
- For more details see [NEP 20 specification](https://numpy.org/neps/nep-0020-expansion-of-generalized-ufunc-signatures.html).