kumi 0.0.13 → 0.0.15

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 (73) hide show
  1. checksums.yaml +4 -4
  2. data/.rspec +0 -1
  3. data/BACKLOG.md +34 -0
  4. data/CHANGELOG.md +33 -0
  5. data/CLAUDE.md +4 -6
  6. data/README.md +0 -45
  7. data/config/functions.yaml +352 -0
  8. data/docs/dev/analyzer-debug.md +52 -0
  9. data/docs/dev/parse-command.md +64 -0
  10. data/docs/dev/vm-profiling.md +95 -0
  11. data/docs/features/README.md +0 -7
  12. data/docs/functions/analyzer_integration.md +199 -0
  13. data/docs/functions/signatures.md +171 -0
  14. data/examples/hash_objects_demo.rb +138 -0
  15. data/golden/array_operations/schema.kumi +17 -0
  16. data/golden/cascade_logic/schema.kumi +16 -0
  17. data/golden/mixed_nesting/schema.kumi +42 -0
  18. data/golden/simple_math/schema.kumi +10 -0
  19. data/lib/kumi/analyzer.rb +76 -22
  20. data/lib/kumi/compiler.rb +6 -5
  21. data/lib/kumi/core/analyzer/checkpoint.rb +72 -0
  22. data/lib/kumi/core/analyzer/debug.rb +167 -0
  23. data/lib/kumi/core/analyzer/passes/broadcast_detector.rb +1 -3
  24. data/lib/kumi/core/analyzer/passes/function_signature_pass.rb +199 -0
  25. data/lib/kumi/core/analyzer/passes/ir_dependency_pass.rb +67 -0
  26. data/lib/kumi/core/analyzer/passes/load_input_cse.rb +120 -0
  27. data/lib/kumi/core/analyzer/passes/lower_to_ir_pass.rb +72 -157
  28. data/lib/kumi/core/analyzer/passes/toposorter.rb +40 -36
  29. data/lib/kumi/core/analyzer/state_serde.rb +64 -0
  30. data/lib/kumi/core/analyzer/structs/access_plan.rb +12 -10
  31. data/lib/kumi/core/compiler/access_planner.rb +3 -2
  32. data/lib/kumi/core/function_registry/collection_functions.rb +3 -1
  33. data/lib/kumi/core/functions/dimension.rb +98 -0
  34. data/lib/kumi/core/functions/dtypes.rb +20 -0
  35. data/lib/kumi/core/functions/errors.rb +11 -0
  36. data/lib/kumi/core/functions/kernel_adapter.rb +45 -0
  37. data/lib/kumi/core/functions/loader.rb +119 -0
  38. data/lib/kumi/core/functions/registry_v2.rb +68 -0
  39. data/lib/kumi/core/functions/shape.rb +70 -0
  40. data/lib/kumi/core/functions/signature.rb +122 -0
  41. data/lib/kumi/core/functions/signature_parser.rb +86 -0
  42. data/lib/kumi/core/functions/signature_resolver.rb +272 -0
  43. data/lib/kumi/core/ir/execution_engine/interpreter.rb +110 -7
  44. data/lib/kumi/core/ir/execution_engine/profiler.rb +330 -0
  45. data/lib/kumi/core/ir/execution_engine.rb +6 -15
  46. data/lib/kumi/dev/ir.rb +75 -0
  47. data/lib/kumi/dev/parse.rb +105 -0
  48. data/lib/kumi/dev/profile_aggregator.rb +301 -0
  49. data/lib/kumi/dev/profile_runner.rb +199 -0
  50. data/lib/kumi/dev/runner.rb +85 -0
  51. data/lib/kumi/dev.rb +14 -0
  52. data/lib/kumi/frontends/ruby.rb +28 -0
  53. data/lib/kumi/frontends/text.rb +46 -0
  54. data/lib/kumi/frontends.rb +29 -0
  55. data/lib/kumi/kernels/ruby/aggregate_core.rb +105 -0
  56. data/lib/kumi/kernels/ruby/datetime_scalar.rb +21 -0
  57. data/lib/kumi/kernels/ruby/mask_scalar.rb +15 -0
  58. data/lib/kumi/kernels/ruby/scalar_core.rb +63 -0
  59. data/lib/kumi/kernels/ruby/string_scalar.rb +19 -0
  60. data/lib/kumi/kernels/ruby/vector_struct.rb +39 -0
  61. data/lib/kumi/runtime/executable.rb +108 -45
  62. data/lib/kumi/schema.rb +12 -6
  63. data/lib/kumi/support/diff.rb +22 -0
  64. data/lib/kumi/support/ir_render.rb +61 -0
  65. data/lib/kumi/version.rb +1 -1
  66. data/lib/kumi.rb +3 -0
  67. data/performance_results.txt +63 -0
  68. data/scripts/test_mixed_nesting_performance.rb +206 -0
  69. metadata +50 -6
  70. data/docs/features/analysis-cascade-mutual-exclusion.md +0 -89
  71. data/docs/features/javascript-transpiler.md +0 -148
  72. data/lib/kumi/js.rb +0 -23
  73. data/lib/kumi/support/ir_dump.rb +0 -491
@@ -0,0 +1,95 @@
1
+ # VM Profiling with Schema Differentiation
2
+
3
+ ## Overview
4
+
5
+ Profiles VM operation execution with schema-level differentiation. Tracks operations by schema type for multi-schema performance analysis.
6
+
7
+ ## Core Components
8
+
9
+ **Profiler**: `lib/kumi/core/ir/execution_engine/profiler.rb`
10
+ - Streams VM operation events with schema identification
11
+ - Supports persistent mode for cross-run analysis
12
+ - JSONL event format with operation metadata
13
+
14
+ **Profile Aggregator**: `lib/kumi/dev/profile_aggregator.rb`
15
+ - Analyzes profiling data by schema type
16
+ - Generates summary and detailed performance reports
17
+ - Schema breakdown showing operations and timing per schema
18
+
19
+ **CLI Integration**: `bin/kumi profile`
20
+ - Processes JSONL profiling data files
21
+ - Multiple output formats: summary, detailed, raw
22
+
23
+ ## Usage
24
+
25
+ ### Basic Profiling
26
+
27
+ ```bash
28
+ # Single schema with operations
29
+ KUMI_PROFILE=1 KUMI_PROFILE_OPS=1 KUMI_PROFILE_FILE=profile.jsonl ruby script.rb
30
+
31
+ # Persistent mode across multiple runs
32
+ KUMI_PROFILE=1 KUMI_PROFILE_PERSISTENT=1 KUMI_PROFILE_OPS=1 KUMI_PROFILE_FILE=profile.jsonl ruby script.rb
33
+
34
+ # Streaming mode for real-time analysis
35
+ KUMI_PROFILE=1 KUMI_PROFILE_STREAM=1 KUMI_PROFILE_OPS=1 KUMI_PROFILE_FILE=profile.jsonl ruby script.rb
36
+ ```
37
+
38
+ ### CLI Analysis
39
+
40
+ ```bash
41
+ # Summary report with schema breakdown
42
+ kumi profile profile.jsonl --summary
43
+
44
+ # Detailed per-operation analysis
45
+ kumi profile profile.jsonl --detailed
46
+
47
+ # Raw event stream
48
+ kumi profile profile.jsonl --raw
49
+ ```
50
+
51
+ ## Environment Variables
52
+
53
+ **Core**:
54
+ - `KUMI_PROFILE=1` - Enable profiling
55
+ - `KUMI_PROFILE_FILE=path` - Output file (required)
56
+ - `KUMI_PROFILE_OPS=1` - Enable VM operation profiling
57
+
58
+ **Modes**:
59
+ - `KUMI_PROFILE_PERSISTENT=1` - Append to existing files across runs
60
+ - `KUMI_PROFILE_STREAM=1` - Stream individual events vs batch
61
+ - `KUMI_PROFILE_TRUNCATE=1` - Truncate existing files
62
+
63
+ ## Event Format
64
+
65
+ JSONL with operation metadata:
66
+
67
+ ```json
68
+ {"event":"vm_operation","schema":"TestSchema","operation":"LoadInput","duration_ms":0.001,"timestamp":"2025-01-20T10:30:45.123Z"}
69
+ {"event":"vm_operation","schema":"TestSchema","operation":"Map","duration_ms":0.002,"timestamp":"2025-01-20T10:30:45.125Z"}
70
+ ```
71
+
72
+ ## Schema Differentiation
73
+
74
+ Tracks operations by schema class name for multi-schema analysis:
75
+
76
+ **Implementation**:
77
+ - Schema name propagated through compilation pipeline
78
+ - Profiler tags each VM operation with schema identifier
79
+ - Aggregator groups operations by schema type
80
+
81
+ **Output Example**:
82
+ ```
83
+ Total operations: 24 (0.8746ms)
84
+ Schemas analyzed: SchemaA, SchemaB
85
+ SchemaA: 12 operations, 0.3242ms
86
+ SchemaB: 12 operations, 0.0504ms
87
+ ```
88
+
89
+ ## Performance Analysis
90
+
91
+ **Reference Operations**: Typically dominate execution time in complex schemas
92
+ **Map Operations**: Element-wise computations on arrays
93
+ **LoadInput Operations**: Data access operations
94
+
95
+ Use schema breakdown to identify performance differences between schema types.
@@ -9,13 +9,6 @@ Analyzes rule combinations to detect logical impossibilities across dependency c
9
9
  - Validates domain constraints
10
10
  - Reports multiple errors
11
11
 
12
- ### [Cascade Mutual Exclusion](analysis-cascade-mutual-exclusion.md)
13
- Enables safe mutual recursion when cascade conditions are mutually exclusive.
14
-
15
- - Allows mathematically sound recursive patterns
16
- - Detects mutually exclusive conditions
17
- - Prevents unsafe cycles while enabling safe ones
18
-
19
12
  ### [Type Inference](analysis-type-inference.md)
20
13
  Determines types from expressions and propagates them through dependencies.
21
14
 
@@ -0,0 +1,199 @@
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.
@@ -0,0 +1,171 @@
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).
@@ -0,0 +1,138 @@
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
@@ -0,0 +1,17 @@
1
+ schema do
2
+ input do
3
+ array :items do
4
+ float :price
5
+ integer :quantity
6
+ string :category
7
+ end
8
+ float :tax_rate
9
+ end
10
+
11
+ value :subtotals, input.items.price * input.items.quantity
12
+ trait :expensive_items, input.items.price > 100.0
13
+ trait :electronics, input.items.category == "electronics"
14
+
15
+ value :discounted_price, input.items.price * 0.9
16
+ value :is_valid_quantity, input.items.quantity > 0
17
+ end
@@ -0,0 +1,16 @@
1
+ schema do
2
+ input do
3
+ integer :x
4
+ integer :y
5
+ end
6
+
7
+ trait :x_positive, input.x > 0
8
+ trait :y_positive, input.y > 0
9
+
10
+ value :status do
11
+ on x_positive, y_positive, "both positive"
12
+ on x_positive, "x positive"
13
+ on y_positive, "y positive"
14
+ base "neither positive"
15
+ end
16
+ end
@@ -0,0 +1,42 @@
1
+ schema do
2
+ input do
3
+ hash :organization do
4
+ string :name
5
+ array :regions do
6
+ string :region_name
7
+ hash :headquarters do
8
+ string :city
9
+ array :buildings do
10
+ string :building_name
11
+ hash :facilities do
12
+ string :facility_type
13
+ integer :capacity
14
+ float :utilization_rate
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
21
+
22
+ # Deep access across 5 levels
23
+ value :org_name, input.organization.name
24
+ value :region_names, input.organization.regions.region_name
25
+ value :hq_cities, input.organization.regions.headquarters.city
26
+ value :building_names, input.organization.regions.headquarters.buildings.building_name
27
+ value :facility_types, input.organization.regions.headquarters.buildings.facilities.facility_type
28
+ value :capacities, input.organization.regions.headquarters.buildings.facilities.capacity
29
+ value :utilization_rates, input.organization.regions.headquarters.buildings.facilities.utilization_rate
30
+
31
+ # Traits using deep nesting - avoiding cross-scope issues
32
+ trait :large_organization, fn(:size, input.organization.regions) > 1
33
+
34
+ # Simple cascade using traits that work within same scope
35
+ value :org_classification do
36
+ on large_organization, "Enterprise"
37
+ base "Standard"
38
+ end
39
+
40
+ # Aggregations that work properly
41
+ value :total_capacity, fn(:sum, input.organization.regions.headquarters.buildings.facilities.capacity)
42
+ end