kumi 0.0.9 → 0.0.11
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 +18 -0
- data/CLAUDE.md +18 -258
- data/README.md +188 -121
- data/docs/AST.md +1 -1
- data/docs/FUNCTIONS.md +52 -8
- data/docs/VECTOR_SEMANTICS.md +286 -0
- data/docs/compiler_design_principles.md +86 -0
- data/docs/features/README.md +15 -2
- data/docs/features/hierarchical-broadcasting.md +349 -0
- data/docs/features/javascript-transpiler.md +148 -0
- data/docs/features/performance.md +1 -3
- data/docs/features/s-expression-printer.md +2 -2
- data/docs/schema_metadata.md +7 -7
- data/examples/deep_schema_compilation_and_evaluation_benchmark.rb +21 -15
- data/examples/game_of_life.rb +2 -4
- data/lib/kumi/analyzer.rb +34 -14
- data/lib/kumi/compiler.rb +4 -283
- data/lib/kumi/core/analyzer/passes/broadcast_detector.rb +717 -66
- data/lib/kumi/core/analyzer/passes/dependency_resolver.rb +1 -1
- data/lib/kumi/core/analyzer/passes/input_access_planner_pass.rb +47 -0
- data/lib/kumi/core/analyzer/passes/input_collector.rb +118 -99
- data/lib/kumi/core/analyzer/passes/join_reduce_planning_pass.rb +293 -0
- data/lib/kumi/core/analyzer/passes/lower_to_ir_pass.rb +993 -0
- data/lib/kumi/core/analyzer/passes/pass_base.rb +2 -2
- data/lib/kumi/core/analyzer/passes/scope_resolution_pass.rb +346 -0
- data/lib/kumi/core/analyzer/passes/semantic_constraint_validator.rb +28 -0
- data/lib/kumi/core/analyzer/passes/toposorter.rb +9 -3
- data/lib/kumi/core/analyzer/passes/type_checker.rb +9 -5
- data/lib/kumi/core/analyzer/passes/type_consistency_checker.rb +2 -2
- data/lib/kumi/core/analyzer/passes/{type_inferencer.rb → type_inferencer_pass.rb} +4 -4
- data/lib/kumi/core/analyzer/passes/unsat_detector.rb +92 -48
- data/lib/kumi/core/analyzer/plans.rb +52 -0
- data/lib/kumi/core/analyzer/structs/access_plan.rb +20 -0
- data/lib/kumi/core/analyzer/structs/input_meta.rb +29 -0
- data/lib/kumi/core/compiler/access_builder.rb +36 -0
- data/lib/kumi/core/compiler/access_planner.rb +219 -0
- data/lib/kumi/core/compiler/accessors/base.rb +69 -0
- data/lib/kumi/core/compiler/accessors/each_indexed_accessor.rb +84 -0
- data/lib/kumi/core/compiler/accessors/materialize_accessor.rb +55 -0
- data/lib/kumi/core/compiler/accessors/ravel_accessor.rb +73 -0
- data/lib/kumi/core/compiler/accessors/read_accessor.rb +41 -0
- data/lib/kumi/core/compiler_base.rb +137 -0
- data/lib/kumi/core/error_reporter.rb +6 -5
- data/lib/kumi/core/errors.rb +4 -0
- data/lib/kumi/core/explain.rb +157 -205
- data/lib/kumi/core/export/node_builders.rb +2 -2
- data/lib/kumi/core/export/node_serializers.rb +1 -1
- data/lib/kumi/core/function_registry/collection_functions.rb +100 -6
- data/lib/kumi/core/function_registry/conditional_functions.rb +14 -4
- data/lib/kumi/core/function_registry/function_builder.rb +142 -53
- data/lib/kumi/core/function_registry/logical_functions.rb +173 -3
- data/lib/kumi/core/function_registry/stat_functions.rb +156 -0
- data/lib/kumi/core/function_registry.rb +138 -98
- data/lib/kumi/core/ir/execution_engine/combinators.rb +117 -0
- data/lib/kumi/core/ir/execution_engine/interpreter.rb +336 -0
- data/lib/kumi/core/ir/execution_engine/values.rb +46 -0
- data/lib/kumi/core/ir/execution_engine.rb +50 -0
- data/lib/kumi/core/ir.rb +58 -0
- data/lib/kumi/core/ruby_parser/build_context.rb +2 -2
- data/lib/kumi/core/ruby_parser/declaration_reference_proxy.rb +0 -12
- data/lib/kumi/core/ruby_parser/dsl_cascade_builder.rb +37 -16
- data/lib/kumi/core/ruby_parser/input_builder.rb +61 -8
- data/lib/kumi/core/ruby_parser/parser.rb +1 -1
- data/lib/kumi/core/ruby_parser/schema_builder.rb +2 -2
- data/lib/kumi/core/ruby_parser/sugar.rb +7 -0
- data/lib/kumi/errors.rb +2 -0
- data/lib/kumi/js.rb +23 -0
- data/lib/kumi/registry.rb +17 -22
- data/lib/kumi/runtime/executable.rb +213 -0
- data/lib/kumi/schema.rb +15 -4
- data/lib/kumi/schema_metadata.rb +2 -2
- data/lib/kumi/support/ir_dump.rb +491 -0
- data/lib/kumi/support/s_expression_printer.rb +17 -16
- data/lib/kumi/syntax/array_expression.rb +6 -6
- data/lib/kumi/syntax/call_expression.rb +4 -4
- data/lib/kumi/syntax/cascade_expression.rb +4 -4
- data/lib/kumi/syntax/case_expression.rb +4 -4
- data/lib/kumi/syntax/declaration_reference.rb +4 -4
- data/lib/kumi/syntax/hash_expression.rb +4 -4
- data/lib/kumi/syntax/input_declaration.rb +6 -5
- data/lib/kumi/syntax/input_element_reference.rb +5 -5
- data/lib/kumi/syntax/input_reference.rb +5 -5
- data/lib/kumi/syntax/literal.rb +4 -4
- data/lib/kumi/syntax/location.rb +5 -0
- data/lib/kumi/syntax/node.rb +33 -34
- data/lib/kumi/syntax/root.rb +6 -6
- data/lib/kumi/syntax/trait_declaration.rb +4 -4
- data/lib/kumi/syntax/value_declaration.rb +4 -4
- data/lib/kumi/version.rb +1 -1
- data/lib/kumi.rb +6 -15
- data/scripts/analyze_broadcast_methods.rb +68 -0
- data/scripts/analyze_cascade_methods.rb +74 -0
- data/scripts/check_broadcasting_coverage.rb +51 -0
- data/scripts/find_dead_code.rb +114 -0
- metadata +36 -9
- data/docs/features/array-broadcasting.md +0 -170
- data/lib/kumi/cli.rb +0 -449
- data/lib/kumi/core/compiled_schema.rb +0 -43
- data/lib/kumi/core/evaluation_wrapper.rb +0 -40
- data/lib/kumi/core/schema_instance.rb +0 -111
- data/lib/kumi/core/vectorization_metadata.rb +0 -110
- data/migrate_to_core_iterative.rb +0 -938
@@ -3,90 +3,179 @@
|
|
3
3
|
module Kumi
|
4
4
|
module Core
|
5
5
|
module FunctionRegistry
|
6
|
-
# Utility class to reduce repetition in function definitions
|
7
6
|
class FunctionBuilder
|
8
|
-
|
7
|
+
# Rich, defaulted function entry
|
8
|
+
class Entry
|
9
|
+
# NOTE: Keep ctor args minimal; everything else has sensible defaults.
|
10
|
+
attr_reader :fn, :arity, :param_types, :return_type, :description,
|
11
|
+
:reducer, :structure_function, :param_modes, :param_info
|
9
12
|
|
10
|
-
|
13
|
+
# param_modes: nil | ->(argc){[:elem,:scalar,...]} | {fixed: [...], variadic: :elem|:scalar}
|
14
|
+
# param_info: nil | ->(argc){[specs]} | {fixed: [...], variadic: {…}}
|
15
|
+
# where a spec is: { name:, type:, mode:, required:, default:, doc: }
|
16
|
+
def initialize(
|
17
|
+
fn:,
|
18
|
+
arity: nil, # Integer (>=0) or -1 / nil for variadic
|
19
|
+
param_types: nil, # defaults to [:any] * arity (when fixed)
|
20
|
+
return_type: :any,
|
21
|
+
description: "",
|
22
|
+
reducer: false,
|
23
|
+
structure_function: false,
|
24
|
+
param_modes: nil,
|
25
|
+
param_info: nil
|
26
|
+
)
|
27
|
+
@fn = fn
|
28
|
+
@arity = arity
|
29
|
+
@param_types = param_types || default_param_types(arity)
|
30
|
+
@return_type = return_type
|
31
|
+
@description = description
|
32
|
+
@reducer = !!reducer
|
33
|
+
@structure_function = !!structure_function
|
34
|
+
@param_modes = normalize_param_modes(param_modes, arity)
|
35
|
+
@param_info = normalize_param_info(param_info, arity, @param_types)
|
36
|
+
end
|
37
|
+
|
38
|
+
# Concrete modes for a call site
|
39
|
+
def param_modes_for(argc)
|
40
|
+
pm = @param_modes
|
41
|
+
return pm.call(argc) if pm.respond_to?(:call)
|
42
|
+
|
43
|
+
fixed = Array(pm[:fixed] || [])
|
44
|
+
return fixed.first(argc) if argc <= fixed.size
|
45
|
+
|
46
|
+
fixed + Array.new(argc - fixed.size, pm.fetch(:variadic, :elem))
|
47
|
+
end
|
48
|
+
|
49
|
+
# Concrete param specs for a call site
|
50
|
+
# → [{name:, type:, mode:, required:, default:, doc:}, ...]
|
51
|
+
def param_specs_for(argc)
|
52
|
+
base = if @param_info.respond_to?(:call)
|
53
|
+
@param_info.call(argc)
|
54
|
+
else
|
55
|
+
fixed = Array(@param_info[:fixed] || [])
|
56
|
+
if argc <= fixed.size
|
57
|
+
fixed.first(argc)
|
58
|
+
else
|
59
|
+
fixed + Array.new(argc - fixed.size, @param_info.fetch(:variadic, {}))
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
modes = param_modes_for(argc)
|
64
|
+
types = expand_types_for(argc)
|
65
|
+
|
66
|
+
base.each_with_index.map do |spec, i|
|
67
|
+
{
|
68
|
+
name: spec[:name] || auto_name(i),
|
69
|
+
type: spec[:type] || types[i] || :any,
|
70
|
+
mode: spec[:mode] || modes[i] || :elem,
|
71
|
+
required: spec.key?(:required) ? spec[:required] : true,
|
72
|
+
default: spec[:default],
|
73
|
+
doc: spec[:doc] || ""
|
74
|
+
}
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
private
|
79
|
+
|
80
|
+
def default_param_types(arity)
|
81
|
+
if arity.is_a?(Integer) && arity >= 0
|
82
|
+
Array.new(arity, :any)
|
83
|
+
else
|
84
|
+
[] # variadic → types resolved per call
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
def expand_types_for(argc)
|
89
|
+
if @param_types.nil? || @param_types.empty?
|
90
|
+
Array.new(argc, :any)
|
91
|
+
elsif @param_types.length >= argc
|
92
|
+
@param_types.first(argc)
|
93
|
+
else
|
94
|
+
@param_types + Array.new(argc - @param_types.length, @param_types.last || :any)
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
def normalize_param_modes(pm, arity)
|
99
|
+
return pm if pm
|
100
|
+
|
101
|
+
# Default: everything element-wise/broadcastable
|
102
|
+
->(argc) { Array.new(argc, :elem) }
|
103
|
+
end
|
104
|
+
|
105
|
+
def normalize_param_info(info, arity, types)
|
106
|
+
return info if info
|
107
|
+
|
108
|
+
# Default: synthesize from types/modes at call time
|
109
|
+
->(argc) { Array.new(argc) { {} } }
|
110
|
+
end
|
111
|
+
|
112
|
+
def auto_name(i) = :"arg#{i + 1}"
|
113
|
+
end
|
114
|
+
|
115
|
+
# ===== Helper constructors (unchanged usage; now benefit from defaults) =====
|
116
|
+
|
117
|
+
def self.comparison(_name, description, op)
|
11
118
|
Entry.new(
|
12
|
-
fn: ->(a, b) { a.public_send(
|
13
|
-
arity: 2,
|
14
|
-
|
15
|
-
return_type: :boolean,
|
16
|
-
description: description
|
119
|
+
fn: ->(a, b) { a.public_send(op, b) },
|
120
|
+
arity: 2, param_types: %i[float float],
|
121
|
+
return_type: :boolean, description: description
|
17
122
|
)
|
18
123
|
end
|
19
124
|
|
20
|
-
def self.equality(_name, description,
|
125
|
+
def self.equality(_name, description, op)
|
21
126
|
Entry.new(
|
22
|
-
fn: ->(a, b) { a.public_send(
|
23
|
-
arity: 2,
|
24
|
-
|
25
|
-
return_type: :boolean,
|
26
|
-
description: description
|
127
|
+
fn: ->(a, b) { a.public_send(op, b) },
|
128
|
+
arity: 2, param_types: %i[any any],
|
129
|
+
return_type: :boolean, description: description
|
27
130
|
)
|
28
131
|
end
|
29
132
|
|
30
|
-
def self.math_binary(_name, description,
|
133
|
+
def self.math_binary(_name, description, op, return_type: :float)
|
31
134
|
Entry.new(
|
32
|
-
fn:
|
33
|
-
|
34
|
-
|
35
|
-
arity: 2,
|
36
|
-
param_types: %i[float float],
|
37
|
-
return_type: return_type,
|
38
|
-
description: description
|
135
|
+
fn: ->(a, b) { a.public_send(op, b) },
|
136
|
+
arity: 2, param_types: %i[float float],
|
137
|
+
return_type: return_type, description: description
|
39
138
|
)
|
40
139
|
end
|
41
140
|
|
42
|
-
def self.math_unary(_name, description,
|
141
|
+
def self.math_unary(_name, description, op, return_type: :float)
|
43
142
|
Entry.new(
|
44
|
-
fn: proc(&
|
45
|
-
arity: 1,
|
46
|
-
|
47
|
-
return_type: return_type,
|
48
|
-
description: description
|
143
|
+
fn: proc(&op),
|
144
|
+
arity: 1, param_types: [:float],
|
145
|
+
return_type: return_type, description: description
|
49
146
|
)
|
50
147
|
end
|
51
148
|
|
52
|
-
def self.string_unary(_name, description,
|
149
|
+
def self.string_unary(_name, description, op)
|
53
150
|
Entry.new(
|
54
|
-
fn: ->(
|
55
|
-
arity: 1,
|
56
|
-
|
57
|
-
return_type: :string,
|
58
|
-
description: description
|
151
|
+
fn: ->(s) { s.to_s.public_send(op) },
|
152
|
+
arity: 1, param_types: [:string],
|
153
|
+
return_type: :string, description: description
|
59
154
|
)
|
60
155
|
end
|
61
156
|
|
62
|
-
def self.string_binary(_name, description,
|
157
|
+
def self.string_binary(_name, description, op, return_type: :string)
|
63
158
|
Entry.new(
|
64
|
-
fn: ->(
|
65
|
-
arity: 2,
|
66
|
-
|
67
|
-
return_type: return_type,
|
68
|
-
description: description
|
159
|
+
fn: ->(s, x) { s.to_s.public_send(op, x.to_s) },
|
160
|
+
arity: 2, param_types: %i[string string],
|
161
|
+
return_type: return_type, description: description
|
69
162
|
)
|
70
163
|
end
|
71
164
|
|
72
|
-
def self.logical_variadic(_name, description,
|
165
|
+
def self.logical_variadic(_name, description, op)
|
73
166
|
Entry.new(
|
74
|
-
fn: ->(
|
75
|
-
arity: -1,
|
76
|
-
|
77
|
-
return_type: :boolean,
|
78
|
-
description: description
|
167
|
+
fn: ->(*conds) { conds.flatten.public_send(op) },
|
168
|
+
arity: -1, param_types: [:boolean],
|
169
|
+
return_type: :boolean, description: description
|
79
170
|
)
|
80
171
|
end
|
81
172
|
|
82
|
-
def self.collection_unary(_name, description,
|
173
|
+
def self.collection_unary(_name, description, op, return_type: :boolean, reducer: false, structure_function: false)
|
83
174
|
Entry.new(
|
84
|
-
fn: proc(&
|
85
|
-
arity: 1,
|
86
|
-
|
87
|
-
|
88
|
-
description: description,
|
89
|
-
reducer: reducer
|
175
|
+
fn: proc(&op),
|
176
|
+
arity: 1, param_types: [Kumi::Core::Types.array(:any)],
|
177
|
+
return_type: return_type, description: description,
|
178
|
+
reducer: reducer, structure_function: structure_function
|
90
179
|
)
|
91
180
|
end
|
92
181
|
end
|
@@ -5,6 +5,146 @@ module Kumi
|
|
5
5
|
module FunctionRegistry
|
6
6
|
# Logical operations and boolean functions
|
7
7
|
module LogicalFunctions
|
8
|
+
def self.element_wise_and(a, b)
|
9
|
+
if ENV["DEBUG_CASCADE"]
|
10
|
+
puts "DEBUG element_wise_and called with:"
|
11
|
+
puts " a: #{a.inspect} (depth: #{array_depth(a)})"
|
12
|
+
puts " b: #{b.inspect} (depth: #{array_depth(b)})"
|
13
|
+
end
|
14
|
+
|
15
|
+
case [a.class, b.class]
|
16
|
+
when [Array, Array]
|
17
|
+
# Both are arrays - handle hierarchical broadcasting
|
18
|
+
if hierarchical_broadcasting_needed?(a, b)
|
19
|
+
puts " -> Using hierarchical broadcasting" if ENV["DEBUG_CASCADE"]
|
20
|
+
result = perform_hierarchical_and(a, b)
|
21
|
+
puts " -> Hierarchical result: #{result.inspect}" if ENV["DEBUG_CASCADE"]
|
22
|
+
else
|
23
|
+
# Same structure - use zip for element-wise operations
|
24
|
+
puts " -> Using same-structure zip" if ENV["DEBUG_CASCADE"]
|
25
|
+
result = a.zip(b).map { |elem_a, elem_b| element_wise_and(elem_a, elem_b) }
|
26
|
+
puts " -> Zip result: #{result.inspect}" if ENV["DEBUG_CASCADE"]
|
27
|
+
end
|
28
|
+
result
|
29
|
+
when [Array, Object], [Object, Array]
|
30
|
+
# One is array, one is scalar - broadcast scalar
|
31
|
+
puts " -> Broadcasting scalar to array" if ENV["DEBUG_CASCADE"]
|
32
|
+
result = if a.is_a?(Array)
|
33
|
+
a.map { |elem| element_wise_and(elem, b) }
|
34
|
+
else
|
35
|
+
b.map { |elem| element_wise_and(a, elem) }
|
36
|
+
end
|
37
|
+
puts " -> Broadcast result: #{result.inspect}" if ENV["DEBUG_CASCADE"]
|
38
|
+
result
|
39
|
+
else
|
40
|
+
# Both are scalars - simple AND
|
41
|
+
puts " -> Simple scalar AND: #{a} && #{b} = #{a && b}" if ENV["DEBUG_CASCADE"]
|
42
|
+
a && b
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def self.hierarchical_broadcasting_needed?(a, b)
|
47
|
+
# Check if arrays have different nesting depths (hierarchical broadcasting)
|
48
|
+
depth_a = array_depth(a)
|
49
|
+
depth_b = array_depth(b)
|
50
|
+
depth_a != depth_b
|
51
|
+
end
|
52
|
+
|
53
|
+
def self.array_depth(arr)
|
54
|
+
return 0 unless arr.is_a?(Array)
|
55
|
+
return 1 if arr.empty? || !arr.first.is_a?(Array)
|
56
|
+
|
57
|
+
1 + array_depth(arr.first)
|
58
|
+
end
|
59
|
+
|
60
|
+
def self.perform_hierarchical_and(a, b)
|
61
|
+
# Determine which is the higher dimension and which is lower
|
62
|
+
depth_a = array_depth(a)
|
63
|
+
depth_b = array_depth(b)
|
64
|
+
|
65
|
+
puts " perform_hierarchical_and: depth_a=#{depth_a}, depth_b=#{depth_b}" if ENV["DEBUG_CASCADE"]
|
66
|
+
|
67
|
+
if depth_a > depth_b
|
68
|
+
# a is deeper (child level), b is shallower (parent level)
|
69
|
+
# Broadcast b values to match a's structure - PRESERVE a's structure
|
70
|
+
puts " -> Broadcasting b (parent) to match a (child) structure" if ENV["DEBUG_CASCADE"]
|
71
|
+
broadcast_parent_to_child_structure(a, b)
|
72
|
+
else
|
73
|
+
# b is deeper (child level), a is shallower (parent level)
|
74
|
+
# Broadcast a values to match b's structure - PRESERVE b's structure
|
75
|
+
puts " -> Broadcasting a (parent) to match b (child) structure" if ENV["DEBUG_CASCADE"]
|
76
|
+
broadcast_parent_to_child_structure(b, a)
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
def self.broadcast_parent_to_child_structure(child_array, parent_array)
|
81
|
+
# Broadcast parent array values to match child array structure, preserving child structure
|
82
|
+
if ENV["DEBUG_CASCADE"]
|
83
|
+
puts " broadcast_parent_to_child_structure:"
|
84
|
+
puts " child_array: #{child_array.inspect}"
|
85
|
+
puts " parent_array: #{parent_array.inspect}"
|
86
|
+
puts " child depth: #{array_depth(child_array)}, parent depth: #{array_depth(parent_array)}"
|
87
|
+
end
|
88
|
+
|
89
|
+
# Use child array structure as template and broadcast parent values
|
90
|
+
map_with_parent_broadcasting(child_array, parent_array, [])
|
91
|
+
end
|
92
|
+
|
93
|
+
def self.map_with_parent_broadcasting(child_structure, parent_structure, indices)
|
94
|
+
if child_structure.is_a?(Array)
|
95
|
+
child_structure.map.with_index do |child_elem, index|
|
96
|
+
new_indices = indices + [index]
|
97
|
+
|
98
|
+
# Navigate parent structure with fewer indices (broadcasting)
|
99
|
+
parent_depth = array_depth(parent_structure)
|
100
|
+
parent_indices = new_indices[0, parent_depth]
|
101
|
+
parent_value = navigate_indices(parent_structure, parent_indices)
|
102
|
+
|
103
|
+
if child_elem.is_a?(Array)
|
104
|
+
# Recurse deeper into child structure
|
105
|
+
map_with_parent_broadcasting(child_elem, parent_structure, new_indices)
|
106
|
+
else
|
107
|
+
# Leaf level - apply AND operation
|
108
|
+
result = child_elem && parent_value
|
109
|
+
if ENV["DEBUG_CASCADE"]
|
110
|
+
puts " Leaf: child=#{child_elem}, parent=#{parent_value} (indices #{new_indices.inspect}) -> #{result}"
|
111
|
+
end
|
112
|
+
result
|
113
|
+
end
|
114
|
+
end
|
115
|
+
else
|
116
|
+
# Non-array child - just AND with parent
|
117
|
+
child_structure && parent_structure
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
def self.navigate_indices(structure, indices)
|
122
|
+
return structure if indices.empty?
|
123
|
+
return structure unless structure.is_a?(Array)
|
124
|
+
return nil if indices.first >= structure.length
|
125
|
+
|
126
|
+
navigate_indices(structure[indices.first], indices[1..])
|
127
|
+
end
|
128
|
+
|
129
|
+
def self.broadcast_to_match_structure(child_array, parent_array)
|
130
|
+
# Legacy method - keeping for backward compatibility
|
131
|
+
if ENV["DEBUG_CASCADE"]
|
132
|
+
puts " broadcast_to_match_structure (LEGACY):"
|
133
|
+
puts " child_array: #{child_array.inspect}"
|
134
|
+
puts " parent_array: #{parent_array.inspect}"
|
135
|
+
puts " child_array.length: #{child_array.length}"
|
136
|
+
puts " parent_array.length: #{parent_array.length}"
|
137
|
+
end
|
138
|
+
|
139
|
+
result = child_array.zip(parent_array).map do |child_elem, parent_elem|
|
140
|
+
puts " Combining child_elem: #{child_elem.inspect} with parent_elem: #{parent_elem.inspect}" if ENV["DEBUG_CASCADE"]
|
141
|
+
element_wise_and(child_elem, parent_elem)
|
142
|
+
end
|
143
|
+
|
144
|
+
puts " broadcast result: #{result.inspect}" if ENV["DEBUG_CASCADE"]
|
145
|
+
result
|
146
|
+
end
|
147
|
+
|
8
148
|
def self.definitions
|
9
149
|
{
|
10
150
|
# Basic logical operations
|
@@ -33,9 +173,39 @@ module Kumi
|
|
33
173
|
),
|
34
174
|
|
35
175
|
# Collection logical operations
|
36
|
-
all?: FunctionBuilder.collection_unary(:all?, "Check if all elements in collection are truthy", :all
|
37
|
-
any?: FunctionBuilder.collection_unary(:any?, "Check if any element in collection is truthy", :any
|
38
|
-
none?: FunctionBuilder.collection_unary(:none?, "Check if no elements in collection are truthy", :none
|
176
|
+
all?: FunctionBuilder.collection_unary(:all?, "Check if all elements in collection are truthy", :all?, reducer: true),
|
177
|
+
any?: FunctionBuilder.collection_unary(:any?, "Check if any element in collection is truthy", :any?, reducer: true),
|
178
|
+
none?: FunctionBuilder.collection_unary(:none?, "Check if no elements in collection are truthy", :none?, reducer: true),
|
179
|
+
|
180
|
+
# Element-wise AND for cascades - works on arrays with same structure
|
181
|
+
cascade_and: FunctionBuilder::Entry.new(
|
182
|
+
fn: lambda do |*conditions|
|
183
|
+
if ENV["DEBUG_CASCADE"]
|
184
|
+
puts "DEBUG cascade_and called with #{conditions.length} conditions:"
|
185
|
+
conditions.each_with_index do |cond, i|
|
186
|
+
puts " condition[#{i}]: #{cond.inspect}"
|
187
|
+
end
|
188
|
+
end
|
189
|
+
|
190
|
+
return false if conditions.empty?
|
191
|
+
|
192
|
+
# Always process uniformly, even for single conditions
|
193
|
+
# This ensures DeclarationReferences are evaluated properly
|
194
|
+
result = conditions.first
|
195
|
+
conditions[1..].each_with_index do |condition, i|
|
196
|
+
puts " Combining result with condition[#{i + 1}]" if ENV["DEBUG_CASCADE"]
|
197
|
+
result = LogicalFunctions.element_wise_and(result, condition)
|
198
|
+
puts " Result after combining: #{result.inspect}" if ENV["DEBUG_CASCADE"]
|
199
|
+
end
|
200
|
+
|
201
|
+
puts " Final cascade_and result: #{result.inspect}" if ENV["DEBUG_CASCADE"]
|
202
|
+
result
|
203
|
+
end,
|
204
|
+
arity: -1,
|
205
|
+
param_types: [:boolean],
|
206
|
+
return_type: :boolean,
|
207
|
+
description: "Element-wise AND for arrays with same nested structure"
|
208
|
+
)
|
39
209
|
}
|
40
210
|
end
|
41
211
|
end
|
@@ -0,0 +1,156 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Kumi
|
4
|
+
module Core
|
5
|
+
module FunctionRegistry
|
6
|
+
module StatFunctions
|
7
|
+
def self.definitions
|
8
|
+
{
|
9
|
+
# Statistical Functions
|
10
|
+
avg: FunctionBuilder::Entry.new(
|
11
|
+
fn: ->(array) { array.empty? ? nil : array.sum.to_f / array.size },
|
12
|
+
arity: 1,
|
13
|
+
param_types: [Kumi::Core::Types.array(:float)],
|
14
|
+
return_type: :float,
|
15
|
+
description: "Calculate arithmetic mean (average) of numeric collection",
|
16
|
+
reducer: true
|
17
|
+
),
|
18
|
+
|
19
|
+
mean: FunctionBuilder::Entry.new(
|
20
|
+
fn: ->(array) { array.empty? ? nil : array.sum.to_f / array.size },
|
21
|
+
arity: 1,
|
22
|
+
param_types: [Kumi::Core::Types.array(:float)],
|
23
|
+
return_type: :float,
|
24
|
+
description: "Calculate arithmetic mean (average) of numeric collection (alias for avg)",
|
25
|
+
reducer: true
|
26
|
+
),
|
27
|
+
|
28
|
+
median: FunctionBuilder::Entry.new(
|
29
|
+
fn: lambda do |array|
|
30
|
+
sorted = array.sort
|
31
|
+
len = sorted.length
|
32
|
+
if len.odd?
|
33
|
+
sorted[len / 2]
|
34
|
+
else
|
35
|
+
(sorted[(len / 2) - 1] + sorted[len / 2]) / 2.0
|
36
|
+
end
|
37
|
+
end,
|
38
|
+
arity: 1,
|
39
|
+
param_types: [Kumi::Core::Types.array(:float)],
|
40
|
+
return_type: :float,
|
41
|
+
description: "Calculate median (middle value) of numeric collection",
|
42
|
+
reducer: true
|
43
|
+
),
|
44
|
+
|
45
|
+
variance: FunctionBuilder::Entry.new(
|
46
|
+
fn: lambda do |array|
|
47
|
+
mean = array.sum.to_f / array.size
|
48
|
+
sum_of_squares = array.sum { |x| (x - mean)**2 }
|
49
|
+
sum_of_squares / array.size
|
50
|
+
end,
|
51
|
+
arity: 1,
|
52
|
+
param_types: [Kumi::Core::Types.array(:float)],
|
53
|
+
return_type: :float,
|
54
|
+
description: "Calculate population variance of numeric collection",
|
55
|
+
reducer: true
|
56
|
+
),
|
57
|
+
|
58
|
+
stdev: FunctionBuilder::Entry.new(
|
59
|
+
fn: lambda do |array|
|
60
|
+
mean = array.sum.to_f / array.size
|
61
|
+
sum_of_squares = array.sum { |x| (x - mean)**2 }
|
62
|
+
variance = sum_of_squares / array.size
|
63
|
+
Math.sqrt(variance)
|
64
|
+
end,
|
65
|
+
arity: 1,
|
66
|
+
param_types: [Kumi::Core::Types.array(:float)],
|
67
|
+
return_type: :float,
|
68
|
+
description: "Calculate population standard deviation of numeric collection",
|
69
|
+
reducer: true
|
70
|
+
),
|
71
|
+
|
72
|
+
sample_variance: FunctionBuilder::Entry.new(
|
73
|
+
fn: lambda do |array|
|
74
|
+
return 0.0 if array.size <= 1
|
75
|
+
|
76
|
+
mean = array.sum.to_f / array.size
|
77
|
+
sum_of_squares = array.sum { |x| (x - mean)**2 }
|
78
|
+
sum_of_squares / (array.size - 1)
|
79
|
+
end,
|
80
|
+
arity: 1,
|
81
|
+
param_types: [Kumi::Core::Types.array(:float)],
|
82
|
+
return_type: :float,
|
83
|
+
description: "Calculate sample variance of numeric collection",
|
84
|
+
reducer: true
|
85
|
+
),
|
86
|
+
|
87
|
+
sample_stdev: FunctionBuilder::Entry.new(
|
88
|
+
fn: lambda do |array|
|
89
|
+
return 0.0 if array.size <= 1
|
90
|
+
|
91
|
+
mean = array.sum.to_f / array.size
|
92
|
+
sum_of_squares = array.sum { |x| (x - mean)**2 }
|
93
|
+
variance = sum_of_squares / (array.size - 1)
|
94
|
+
Math.sqrt(variance)
|
95
|
+
end,
|
96
|
+
arity: 1,
|
97
|
+
param_types: [Kumi::Core::Types.array(:float)],
|
98
|
+
return_type: :float,
|
99
|
+
description: "Calculate sample standard deviation of numeric collection",
|
100
|
+
reducer: true
|
101
|
+
),
|
102
|
+
|
103
|
+
# Convenience functions for flattened statistics
|
104
|
+
flat_size: FunctionBuilder::Entry.new(
|
105
|
+
fn: ->(nested_array) { nested_array.flatten.size },
|
106
|
+
arity: 1,
|
107
|
+
param_types: [Kumi::Core::Types.array(:any)],
|
108
|
+
return_type: :integer,
|
109
|
+
description: "Count total elements across all nested levels",
|
110
|
+
reducer: true
|
111
|
+
),
|
112
|
+
|
113
|
+
flat_sum: FunctionBuilder::Entry.new(
|
114
|
+
fn: ->(nested_array) { nested_array.flatten.sum },
|
115
|
+
arity: 1,
|
116
|
+
param_types: [Kumi::Core::Types.array(:float)],
|
117
|
+
return_type: :float,
|
118
|
+
description: "Sum all numeric elements across all nested levels",
|
119
|
+
reducer: true
|
120
|
+
),
|
121
|
+
|
122
|
+
flat_avg: FunctionBuilder::Entry.new(
|
123
|
+
fn: lambda do |nested_array|
|
124
|
+
flattened = nested_array.flatten
|
125
|
+
flattened.sum.to_f / flattened.size
|
126
|
+
end,
|
127
|
+
arity: 1,
|
128
|
+
param_types: [Kumi::Core::Types.array(:float)],
|
129
|
+
return_type: :float,
|
130
|
+
description: "Calculate average across all nested levels",
|
131
|
+
reducer: true
|
132
|
+
),
|
133
|
+
|
134
|
+
flat_max: FunctionBuilder::Entry.new(
|
135
|
+
fn: ->(nested_array) { nested_array.flatten.max },
|
136
|
+
arity: 1,
|
137
|
+
param_types: [Kumi::Core::Types.array(:float)],
|
138
|
+
return_type: :float,
|
139
|
+
description: "Find maximum value across all nested levels",
|
140
|
+
reducer: true
|
141
|
+
),
|
142
|
+
|
143
|
+
flat_min: FunctionBuilder::Entry.new(
|
144
|
+
fn: ->(nested_array) { nested_array.flatten.min },
|
145
|
+
arity: 1,
|
146
|
+
param_types: [Kumi::Core::Types.array(:float)],
|
147
|
+
return_type: :float,
|
148
|
+
description: "Find minimum value across all nested levels",
|
149
|
+
reducer: true
|
150
|
+
)
|
151
|
+
}
|
152
|
+
end
|
153
|
+
end
|
154
|
+
end
|
155
|
+
end
|
156
|
+
end
|