kumi 0.0.16 → 0.0.18
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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +9 -0
- data/golden/cascade_logic/schema.kumi +3 -1
- data/lib/kumi/analyzer.rb +8 -11
- data/lib/kumi/core/analyzer/passes/broadcast_detector.rb +0 -81
- data/lib/kumi/core/analyzer/passes/lower_to_ir_pass.rb +0 -36
- data/lib/kumi/core/analyzer/passes/toposorter.rb +1 -36
- data/lib/kumi/core/analyzer/passes/unsat_detector.rb +8 -191
- data/lib/kumi/core/compiler/access_builder.rb +5 -8
- data/lib/kumi/version.rb +1 -1
- metadata +2 -25
- data/BACKLOG.md +0 -34
- data/config/functions.yaml +0 -352
- data/docs/functions/analyzer_integration.md +0 -199
- data/docs/functions/signatures.md +0 -171
- data/examples/hash_objects_demo.rb +0 -138
- data/lib/kumi/core/analyzer/passes/function_signature_pass.rb +0 -199
- data/lib/kumi/core/analyzer/passes/type_consistency_checker.rb +0 -48
- data/lib/kumi/core/functions/dimension.rb +0 -98
- data/lib/kumi/core/functions/dtypes.rb +0 -20
- data/lib/kumi/core/functions/errors.rb +0 -11
- data/lib/kumi/core/functions/kernel_adapter.rb +0 -45
- data/lib/kumi/core/functions/loader.rb +0 -119
- data/lib/kumi/core/functions/registry_v2.rb +0 -68
- data/lib/kumi/core/functions/shape.rb +0 -70
- data/lib/kumi/core/functions/signature.rb +0 -122
- data/lib/kumi/core/functions/signature_parser.rb +0 -86
- data/lib/kumi/core/functions/signature_resolver.rb +0 -272
- data/lib/kumi/kernels/ruby/aggregate_core.rb +0 -105
- data/lib/kumi/kernels/ruby/datetime_scalar.rb +0 -21
- data/lib/kumi/kernels/ruby/mask_scalar.rb +0 -15
- data/lib/kumi/kernels/ruby/scalar_core.rb +0 -63
- data/lib/kumi/kernels/ruby/string_scalar.rb +0 -19
- data/lib/kumi/kernels/ruby/vector_struct.rb +0 -39
@@ -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).
|
@@ -1,138 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
# Hash Objects Demo - Demonstrates Kumi's structured input syntax
|
4
|
-
# This example shows how to use hash objects for organizing related input fields
|
5
|
-
|
6
|
-
require_relative "../lib/kumi"
|
7
|
-
|
8
|
-
module HashObjectsDemo
|
9
|
-
extend Kumi::Schema
|
10
|
-
|
11
|
-
schema do
|
12
|
-
input do
|
13
|
-
# Employee information as hash object
|
14
|
-
hash :employee do
|
15
|
-
string :name
|
16
|
-
integer :age, domain: 18..65
|
17
|
-
float :base_salary, domain: 30_000.0..200_000.0
|
18
|
-
boolean :is_manager
|
19
|
-
integer :years_experience, domain: 0..40
|
20
|
-
end
|
21
|
-
|
22
|
-
# Company configuration as hash object
|
23
|
-
hash :company_config do
|
24
|
-
string :name
|
25
|
-
float :bonus_percentage, domain: 0.0..0.50
|
26
|
-
float :manager_multiplier, domain: 1.0..2.0
|
27
|
-
integer :current_year, domain: 2020..2030
|
28
|
-
end
|
29
|
-
|
30
|
-
# Benefits configuration as hash object
|
31
|
-
hash :benefits do
|
32
|
-
boolean :health_insurance
|
33
|
-
boolean :dental_coverage
|
34
|
-
float :retirement_match, domain: 0.0..0.10
|
35
|
-
integer :vacation_days, domain: 10..30
|
36
|
-
end
|
37
|
-
end
|
38
|
-
|
39
|
-
# Traits using hash object access
|
40
|
-
trait :is_senior, input.employee.years_experience >= 5
|
41
|
-
trait :eligible_for_bonus, is_senior & input.employee.is_manager
|
42
|
-
trait :has_full_benefits, input.benefits.health_insurance & input.benefits.dental_coverage
|
43
|
-
|
44
|
-
# Salary calculations using structured data
|
45
|
-
value :base_annual_salary, input.employee.base_salary
|
46
|
-
|
47
|
-
value :bonus_amount do
|
48
|
-
on eligible_for_bonus, base_annual_salary * input.company_config.bonus_percentage
|
49
|
-
base 0.0
|
50
|
-
end
|
51
|
-
|
52
|
-
trait :is_manager_trait, input.employee.is_manager == true
|
53
|
-
|
54
|
-
value :manager_adjustment do
|
55
|
-
on is_manager_trait, input.company_config.manager_multiplier
|
56
|
-
base 1.0
|
57
|
-
end
|
58
|
-
|
59
|
-
value :total_compensation, (base_annual_salary * manager_adjustment) + bonus_amount
|
60
|
-
|
61
|
-
# Benefits calculations
|
62
|
-
value :retirement_contribution, total_compensation * input.benefits.retirement_match
|
63
|
-
|
64
|
-
value :benefits_package_value do
|
65
|
-
on has_full_benefits, 5_000.0 + input.benefits.vacation_days * 150.0
|
66
|
-
base input.benefits.vacation_days * 100.0
|
67
|
-
end
|
68
|
-
|
69
|
-
# Final totals
|
70
|
-
value :total_package_value, total_compensation + retirement_contribution + benefits_package_value
|
71
|
-
|
72
|
-
# Summary calculations
|
73
|
-
value :years_to_retirement, 65 - input.employee.age
|
74
|
-
end
|
75
|
-
end
|
76
|
-
|
77
|
-
# Example usage
|
78
|
-
if __FILE__ == $0
|
79
|
-
puts "Hash Objects Demo - Employee Compensation Calculator"
|
80
|
-
puts "=" * 55
|
81
|
-
|
82
|
-
# Sample data demonstrating hash objects structure
|
83
|
-
employee_data = {
|
84
|
-
employee: {
|
85
|
-
name: "Alice Johnson",
|
86
|
-
age: 32,
|
87
|
-
base_salary: 85_000.0,
|
88
|
-
is_manager: true,
|
89
|
-
years_experience: 8
|
90
|
-
},
|
91
|
-
company_config: {
|
92
|
-
name: "Tech Solutions Inc",
|
93
|
-
bonus_percentage: 0.15,
|
94
|
-
manager_multiplier: 1.25,
|
95
|
-
current_year: 2024
|
96
|
-
},
|
97
|
-
benefits: {
|
98
|
-
health_insurance: true,
|
99
|
-
dental_coverage: true,
|
100
|
-
retirement_match: 0.06,
|
101
|
-
vacation_days: 25
|
102
|
-
}
|
103
|
-
}
|
104
|
-
|
105
|
-
# Calculate compensation
|
106
|
-
result = HashObjectsDemo.from(employee_data)
|
107
|
-
|
108
|
-
def format_currency(amount)
|
109
|
-
"$#{amount.round(0).to_s.gsub(/\B(?=(\d{3})+(?!\d))/, ',')}"
|
110
|
-
end
|
111
|
-
|
112
|
-
puts "\nEmployee Information:"
|
113
|
-
puts "- Name: #{employee_data[:employee][:name]}"
|
114
|
-
puts "- Age: #{employee_data[:employee][:age]}"
|
115
|
-
puts "- Experience: #{employee_data[:employee][:years_experience]} years"
|
116
|
-
puts "- Manager: #{employee_data[:employee][:is_manager] ? 'Yes' : 'No'}"
|
117
|
-
|
118
|
-
puts "\nCompany: #{employee_data[:company_config][:name]}"
|
119
|
-
puts "- Bonus Rate: #{(employee_data[:company_config][:bonus_percentage] * 100).round(1)}%"
|
120
|
-
puts "- Manager Multiplier: #{employee_data[:company_config][:manager_multiplier]}x"
|
121
|
-
|
122
|
-
puts "\nBenefits:"
|
123
|
-
puts "- Health Insurance: #{employee_data[:benefits][:health_insurance] ? 'Yes' : 'No'}"
|
124
|
-
puts "- Dental Coverage: #{employee_data[:benefits][:dental_coverage] ? 'Yes' : 'No'}"
|
125
|
-
puts "- Retirement Match: #{(employee_data[:benefits][:retirement_match] * 100).round(1)}%"
|
126
|
-
puts "- Vacation Days: #{employee_data[:benefits][:vacation_days]}"
|
127
|
-
|
128
|
-
puts "\nCompensation Breakdown:"
|
129
|
-
puts "- Base Salary: #{format_currency(result[:base_annual_salary])}"
|
130
|
-
puts "- Manager Adjustment: #{result[:manager_adjustment]}x"
|
131
|
-
puts "- Bonus: #{format_currency(result[:bonus_amount])}"
|
132
|
-
puts "- Total Compensation: #{format_currency(result[:total_compensation])}"
|
133
|
-
puts "- Retirement Contribution: #{format_currency(result[:retirement_contribution])}"
|
134
|
-
puts "- Benefits Package Value: #{format_currency(result[:benefits_package_value])}"
|
135
|
-
|
136
|
-
puts "\nTotal Package: #{format_currency(result[:total_package_value])}"
|
137
|
-
puts "Years to Retirement: #{result[:years_to_retirement]}"
|
138
|
-
end
|
@@ -1,199 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module Kumi
|
4
|
-
module Core
|
5
|
-
module Analyzer
|
6
|
-
module Passes
|
7
|
-
# RESPONSIBILITY: Apply NEP-20 signature resolution to function calls
|
8
|
-
# DEPENDENCIES: :node_index from Toposorter, :broadcast_metadata (optional)
|
9
|
-
# PRODUCES: Signature metadata in node_index for CallExpression nodes
|
10
|
-
# INTERFACE: new(schema, state).run(errors)
|
11
|
-
class FunctionSignaturePass < PassBase
|
12
|
-
def run(errors)
|
13
|
-
node_index = get_state(:node_index, required: true)
|
14
|
-
|
15
|
-
# Process all CallExpression nodes in the index
|
16
|
-
node_index.each do |object_id, entry|
|
17
|
-
next unless entry[:type] == "CallExpression"
|
18
|
-
|
19
|
-
resolve_function_signature(entry, object_id, errors)
|
20
|
-
end
|
21
|
-
|
22
|
-
state # Node index is modified in-place
|
23
|
-
end
|
24
|
-
|
25
|
-
private
|
26
|
-
|
27
|
-
def resolve_function_signature(entry, object_id, errors)
|
28
|
-
node = entry[:node]
|
29
|
-
|
30
|
-
# 1) Gather candidate signatures from current registry
|
31
|
-
sig_strings = get_function_signatures(node)
|
32
|
-
return if sig_strings.empty?
|
33
|
-
|
34
|
-
begin
|
35
|
-
sigs = parse_signatures(sig_strings)
|
36
|
-
rescue Kumi::Core::Functions::SignatureError => e
|
37
|
-
report_error(errors, "Invalid signature for function `#{node.fn_name}`: #{e.message}",
|
38
|
-
location: node.loc, type: :type)
|
39
|
-
return
|
40
|
-
end
|
41
|
-
|
42
|
-
# 2) Build arg_shapes from current node context
|
43
|
-
arg_shapes = build_argument_shapes(node, object_id)
|
44
|
-
|
45
|
-
# 3) Resolve signature
|
46
|
-
begin
|
47
|
-
plan = Kumi::Core::Functions::SignatureResolver.choose(signatures: sigs, arg_shapes: arg_shapes)
|
48
|
-
rescue Kumi::Core::Functions::SignatureMatchError => e
|
49
|
-
report_error(errors,
|
50
|
-
"Signature mismatch for `#{node.fn_name}` with args #{format_shapes(arg_shapes)}. Candidates: #{format_sigs(sig_strings)}. #{e.message}",
|
51
|
-
location: node.loc, type: :type)
|
52
|
-
return
|
53
|
-
end
|
54
|
-
|
55
|
-
# 4) Attach metadata to node index entry
|
56
|
-
attach_signature_metadata(entry, plan)
|
57
|
-
end
|
58
|
-
|
59
|
-
def get_function_signatures(node)
|
60
|
-
# Use RegistryV2 if enabled, otherwise fall back to legacy registry
|
61
|
-
if registry_v2_enabled?
|
62
|
-
registry_v2_signatures(node)
|
63
|
-
else
|
64
|
-
legacy_registry_signatures(node)
|
65
|
-
end
|
66
|
-
end
|
67
|
-
|
68
|
-
def registry_v2_signatures(node)
|
69
|
-
registry_v2.get_function_signatures(node.fn_name)
|
70
|
-
rescue => e
|
71
|
-
# If RegistryV2 fails, fall back to legacy
|
72
|
-
legacy_registry_signatures(node)
|
73
|
-
end
|
74
|
-
|
75
|
-
def legacy_registry_signatures(node)
|
76
|
-
# Try to get signatures from the current registry
|
77
|
-
# For now, we'll create basic signatures from the current registry format
|
78
|
-
|
79
|
-
meta = Kumi::Registry.signature(node.fn_name)
|
80
|
-
|
81
|
-
# Check if the function already has NEP-20 signatures
|
82
|
-
return meta[:signatures] if meta[:signatures] && meta[:signatures].is_a?(Array)
|
83
|
-
|
84
|
-
# Otherwise, create a basic signature from arity
|
85
|
-
# This is a bridge until we have full NEP-20 signatures in the registry
|
86
|
-
create_basic_signature(meta[:arity])
|
87
|
-
rescue Kumi::Errors::UnknownFunction
|
88
|
-
# For now, return empty array - function existence will be caught by TypeChecker
|
89
|
-
[]
|
90
|
-
end
|
91
|
-
|
92
|
-
def create_basic_signature(arity)
|
93
|
-
return [] if arity.nil? || arity < 0 # Variable arity - skip for now
|
94
|
-
|
95
|
-
case arity
|
96
|
-
when 0
|
97
|
-
["()->()"] # Scalar function
|
98
|
-
when 1
|
99
|
-
["()->()", "(i)->(i)"] # Scalar or element-wise
|
100
|
-
when 2
|
101
|
-
["(),()->()", "(i),(i)->(i)"] # Scalar or element-wise binary
|
102
|
-
else
|
103
|
-
# For higher arity, just provide scalar signature
|
104
|
-
args = (["()"] * arity).join(",")
|
105
|
-
["#{args}->()"]
|
106
|
-
end
|
107
|
-
end
|
108
|
-
|
109
|
-
def build_argument_shapes(node, object_id)
|
110
|
-
# Build argument shapes from current analysis context
|
111
|
-
node.args.map do |arg|
|
112
|
-
axes = get_broadcast_metadata(arg.object_id)
|
113
|
-
normalize_shape(axes)
|
114
|
-
end
|
115
|
-
end
|
116
|
-
|
117
|
-
def normalize_shape(axes)
|
118
|
-
case axes
|
119
|
-
when nil
|
120
|
-
[] # scalar
|
121
|
-
when Array
|
122
|
-
axes.map { |d| d.is_a?(Integer) ? d : d.to_sym }
|
123
|
-
else
|
124
|
-
[] # defensive fallback
|
125
|
-
end
|
126
|
-
end
|
127
|
-
|
128
|
-
def get_broadcast_metadata(arg_object_id)
|
129
|
-
# Try to get broadcast metadata from existing analysis state
|
130
|
-
broadcast_meta = get_state(:broadcast_metadata, required: false)
|
131
|
-
return nil unless broadcast_meta
|
132
|
-
|
133
|
-
# Look up by node object_id
|
134
|
-
broadcast_meta[arg_object_id]&.dig(:axes)
|
135
|
-
end
|
136
|
-
|
137
|
-
def parse_signatures(sig_strings)
|
138
|
-
@sig_cache ||= {}
|
139
|
-
sig_strings.map do |s|
|
140
|
-
@sig_cache[s] ||= Kumi::Core::Functions::SignatureParser.parse(s)
|
141
|
-
end
|
142
|
-
end
|
143
|
-
|
144
|
-
def format_shapes(shapes)
|
145
|
-
shapes.map { |ax| "(#{ax.join(',')})" }.join(', ')
|
146
|
-
end
|
147
|
-
|
148
|
-
def format_sigs(sig_strings)
|
149
|
-
sig_strings.join(" | ")
|
150
|
-
end
|
151
|
-
|
152
|
-
def attach_signature_metadata(entry, plan)
|
153
|
-
# Attach signature resolution results to the node index entry
|
154
|
-
# This way other passes can access the metadata via the node index
|
155
|
-
metadata = entry[:metadata]
|
156
|
-
|
157
|
-
attach_core_signature_data(metadata, plan)
|
158
|
-
attach_shape_contract(metadata, plan)
|
159
|
-
end
|
160
|
-
|
161
|
-
def attach_core_signature_data(metadata, plan)
|
162
|
-
metadata[:signature] = plan[:signature]
|
163
|
-
metadata[:result_axes] = plan[:result_axes] # e.g., [:i, :j]
|
164
|
-
metadata[:join_policy] = plan[:join_policy] # nil | :zip | :product
|
165
|
-
metadata[:dropped_axes] = plan[:dropped_axes] # e.g., [:j] for reductions
|
166
|
-
metadata[:effective_signature] = plan[:effective_signature] # Normalized for lowering
|
167
|
-
metadata[:dim_env] = plan[:env] # Dimension bindings (for matmul)
|
168
|
-
metadata[:signature_score] = plan[:score] # Match quality
|
169
|
-
end
|
170
|
-
|
171
|
-
def attach_shape_contract(metadata, plan)
|
172
|
-
# Attach shape contract for lowering convenience
|
173
|
-
metadata[:shape_contract] = {
|
174
|
-
in: plan[:effective_signature][:in_shapes],
|
175
|
-
out: plan[:effective_signature][:out_shape],
|
176
|
-
join: plan[:effective_signature][:join_policy]
|
177
|
-
}
|
178
|
-
end
|
179
|
-
|
180
|
-
def registry_v2_enabled?
|
181
|
-
ENV["KUMI_FN_REGISTRY_V2"] == "1"
|
182
|
-
end
|
183
|
-
|
184
|
-
def registry_v2
|
185
|
-
@registry_v2 ||= Kumi::Core::Functions::RegistryV2.load_from_file
|
186
|
-
end
|
187
|
-
|
188
|
-
def nep20_flex_enabled?
|
189
|
-
ENV["KUMI_ENABLE_FLEX"] == "1"
|
190
|
-
end
|
191
|
-
|
192
|
-
def nep20_bcast1_enabled?
|
193
|
-
ENV["KUMI_ENABLE_BCAST1"] == "1"
|
194
|
-
end
|
195
|
-
end
|
196
|
-
end
|
197
|
-
end
|
198
|
-
end
|
199
|
-
end
|
@@ -1,48 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module Kumi
|
4
|
-
module Core
|
5
|
-
module Analyzer
|
6
|
-
module Passes
|
7
|
-
# RESPONSIBILITY: Validate consistency between declared and inferred types
|
8
|
-
# DEPENDENCIES: :input_metadata from InputCollector, :inferred_types from TypeInferencerPass
|
9
|
-
# PRODUCES: None (validation only)
|
10
|
-
# INTERFACE: new(schema, state).run(errors)
|
11
|
-
class TypeConsistencyChecker < PassBase
|
12
|
-
def run(errors)
|
13
|
-
input_meta = get_state(:input_metadata, required: false) || {}
|
14
|
-
|
15
|
-
# First, validate that all declared types are valid
|
16
|
-
validate_declared_types(input_meta, errors)
|
17
|
-
|
18
|
-
# Then check basic consistency (placeholder for now)
|
19
|
-
# In a full implementation, this would do sophisticated usage analysis
|
20
|
-
state
|
21
|
-
end
|
22
|
-
|
23
|
-
private
|
24
|
-
|
25
|
-
def validate_declared_types(input_meta, errors)
|
26
|
-
input_meta.each do |field_name, meta|
|
27
|
-
declared_type = meta[:type]
|
28
|
-
next unless declared_type # Skip fields without declared types
|
29
|
-
next if Kumi::Core::Types.valid_type?(declared_type)
|
30
|
-
|
31
|
-
# Find the input field declaration for proper location information
|
32
|
-
field_decl = find_input_field_declaration(field_name)
|
33
|
-
location = field_decl&.loc
|
34
|
-
|
35
|
-
report_type_error(errors, "Invalid type declaration for field :#{field_name}: #{declared_type.inspect}", location: location)
|
36
|
-
end
|
37
|
-
end
|
38
|
-
|
39
|
-
def find_input_field_declaration(field_name)
|
40
|
-
return nil unless schema
|
41
|
-
|
42
|
-
schema.inputs.find { |input_decl| input_decl.name == field_name }
|
43
|
-
end
|
44
|
-
end
|
45
|
-
end
|
46
|
-
end
|
47
|
-
end
|
48
|
-
end
|
@@ -1,98 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require_relative "errors"
|
4
|
-
|
5
|
-
module Kumi
|
6
|
-
module Core
|
7
|
-
module Functions
|
8
|
-
# Represents a single dimension in a signature with NEP 20 support.
|
9
|
-
#
|
10
|
-
# A dimension can be:
|
11
|
-
# - Named dimension (symbol): :i, :j, :n
|
12
|
-
# - Fixed-size dimension (integer): 2, 3, 10
|
13
|
-
# - With modifiers:
|
14
|
-
# - flexible (?): can be omitted if not present in all operands
|
15
|
-
# - broadcastable (|1): can broadcast against size-1 dimensions
|
16
|
-
#
|
17
|
-
# Examples:
|
18
|
-
# Dimension.new(:i) # named dimension 'i'
|
19
|
-
# Dimension.new(3) # fixed-size dimension of size 3
|
20
|
-
# Dimension.new(:n, flexible: true) # dimension 'n' that can be omitted
|
21
|
-
# Dimension.new(:i, broadcastable: true) # dimension 'i' that can broadcast
|
22
|
-
class Dimension
|
23
|
-
attr_reader :name, :flexible, :broadcastable
|
24
|
-
|
25
|
-
def initialize(name, flexible: false, broadcastable: false)
|
26
|
-
@name = name
|
27
|
-
@flexible = flexible
|
28
|
-
@broadcastable = broadcastable
|
29
|
-
|
30
|
-
validate!
|
31
|
-
freeze
|
32
|
-
end
|
33
|
-
|
34
|
-
def fixed_size?
|
35
|
-
@name.is_a?(Integer)
|
36
|
-
end
|
37
|
-
|
38
|
-
def named?
|
39
|
-
@name.is_a?(Symbol)
|
40
|
-
end
|
41
|
-
|
42
|
-
def flexible?
|
43
|
-
@flexible
|
44
|
-
end
|
45
|
-
|
46
|
-
def broadcastable?
|
47
|
-
@broadcastable
|
48
|
-
end
|
49
|
-
|
50
|
-
def size
|
51
|
-
fixed_size? ? @name : nil
|
52
|
-
end
|
53
|
-
|
54
|
-
def ==(other)
|
55
|
-
other.is_a?(Dimension) &&
|
56
|
-
name == other.name &&
|
57
|
-
flexible == other.flexible &&
|
58
|
-
broadcastable == other.broadcastable
|
59
|
-
end
|
60
|
-
|
61
|
-
def eql?(other)
|
62
|
-
self == other
|
63
|
-
end
|
64
|
-
|
65
|
-
def hash
|
66
|
-
[name, flexible, broadcastable].hash
|
67
|
-
end
|
68
|
-
|
69
|
-
def to_s
|
70
|
-
str = name.to_s
|
71
|
-
str += "?" if flexible?
|
72
|
-
str += "|1" if broadcastable?
|
73
|
-
str
|
74
|
-
end
|
75
|
-
|
76
|
-
def inspect
|
77
|
-
"#<Dimension #{self}>"
|
78
|
-
end
|
79
|
-
|
80
|
-
private
|
81
|
-
|
82
|
-
def validate!
|
83
|
-
unless name.is_a?(Symbol) || name.is_a?(Integer)
|
84
|
-
raise SignatureError, "dimension name must be a symbol or integer, got: #{name.inspect}"
|
85
|
-
end
|
86
|
-
|
87
|
-
raise SignatureError, "fixed-size dimension must be positive, got: #{name}" if name.is_a?(Integer) && name <= 0
|
88
|
-
|
89
|
-
raise SignatureError, "dimension cannot be both flexible and broadcastable" if flexible? && broadcastable?
|
90
|
-
|
91
|
-
return unless fixed_size? && flexible?
|
92
|
-
|
93
|
-
raise SignatureError, "fixed-size dimension cannot be flexible"
|
94
|
-
end
|
95
|
-
end
|
96
|
-
end
|
97
|
-
end
|
98
|
-
end
|
@@ -1,20 +0,0 @@
|
|
1
|
-
module Kumi::Core::Functions
|
2
|
-
DType = Struct.new(:name, keyword_init: true)
|
3
|
-
module DTypes
|
4
|
-
BOOL = DType.new(name: :bool)
|
5
|
-
INT = DType.new(name: :int)
|
6
|
-
FLOAT = DType.new(name: :float)
|
7
|
-
STRING = DType.new(name: :string)
|
8
|
-
DATETIME = DType.new(name: :datetime)
|
9
|
-
ANY = DType.new(name: :any)
|
10
|
-
end
|
11
|
-
|
12
|
-
module Promotion
|
13
|
-
# super simple table; extend later
|
14
|
-
TABLE = {
|
15
|
-
%i[int int] => :int, %i[int float] => :float, %i[float int] => :float, %i[float float] => :float,
|
16
|
-
%i[bool int] => :int, %i[bool float] => :float, %i[bool bool] => :bool
|
17
|
-
}
|
18
|
-
def self.promote(a, b) = TABLE[[a, b]] || TABLE[[b, a]] || :any
|
19
|
-
end
|
20
|
-
end
|