1 import Base 2 import ../shape/core.bend as Shape 3 import ../tensor/core.bend as Core 4 import ../tensor/elementwise_totality.bend as Totality 5 import ../tensor/projection_LAWS.bend as Projection 6 import ./reduce.bend as Reduce 7 8 def owner(pair: Core.Dynamic & Result<&2, &1, Reduce.Error, Core.Dynamic>) -> Core.Dynamic: 9 (input, result) = pair 10 input 11 12 def succeeded(pair: Core.Dynamic & Result<&2, &1, Reduce.Error, Core.Dynamic>) -> Bool: 13 (input, result) = pair 14 Result.is_done(&2, &1, Reduce.Error, Core.Dynamic, result) 15 16 def word_at(values: List<&2, F32>, +position: Nat) -> Maybe<&2, F32>: 17 match values position: 18 case Nil{} _: 19 None{} 20 case value <> rest 0n: 21 Some{value} 22 case value <> rest 1n+next: 23 word_at(rest, next) 24 25 def add_word(total: Maybe<&2, F32>, value: Maybe<&2, F32>) -> Maybe<&2, F32>: 26 match total value: 27 case None{} _: 28 None{} 29 case Some{sum} None{}: 30 None{} 31 case Some{sum} Some{word}: 32 Some{F32.add(sum, word)} 33 34 def matched(total: Maybe<&2, F32>, value: Maybe<&2, F32>, same: Bool) -> Maybe<&2, F32>: 35 match same: 36 case False{}: 37 total 38 case True{}: 39 add_word(total, value) 40 41 def contribute(+values: List<&2, F32>, +target_index: U32, total: Maybe<&2, F32>, offsets: Maybe<&1, U32 & U32>) -> Maybe<&2, F32>: 42 match offsets: 43 case None{}: 44 None{} 45 case Some{pair}: 46 (target_offset, source_offset) = pair 47 matched(total, word_at(values, U32.to_nat(source_offset)), Nat.is_eq(U32.to_nat(target_offset), U32.to_nat(target_index))) 48 49 # The specified accumulation: start at +0.0 and visit contribution positions 50 # 0, 1, ..., count - 1 in ascending order; each position whose independent 51 # broadcast projection, Projection.offset_spec, has target_offset equal to 52 # target_index adds the word at its source_offset with one F32 add. 53 def fiber(+plan: Shape.BroadcastPlan, +values: List<&2, F32>, +target_index: U32, +count: Nat) -> Maybe<&2, F32>: 54 match count: 55 case 0n: 56 Some{0.0} 57 case 1n+position: 58 contribute(values, target_index, fiber(plan, values, target_index, position), Projection.offset_spec(plan, position)) 59 60 def planned_fiber(+values: List<&2, F32>, +target_index: U32, +count: Nat, 61 plan: Result<&2, &2, Shape.Error, Shape.BroadcastPlan>) -> Maybe<&2, F32>: 62 match plan: 63 case Fail{error}: 64 None{} 65 case Done{+plan}: 66 fiber(plan, values, target_index, count) 67 68 # values lists the contribution's words: the checked observation at every 69 # U32 position is the word at that position. 70 def Listed(-input: Core.Dynamic, +values: List<&2, F32>) -> Type: 71 @+source: U32 -> {Core.checked_observe_dynamic(input, source) == word_at(values, U32.to_nat(source)) : Maybe<&2, F32>} 72 73 def MappingWith(-input: Core.Dynamic, +target: Shape.Shape, plan: Result<&2, &2, Shape.Error, Shape.BroadcastPlan>, 74 pair: Core.Dynamic & Result<&2, &1, Reduce.Error, Core.Dynamic>) -> Type: 75 (retained, result) = pair 76 match result: 77 case Fail{error}: 78 Unit 79 case Done{output}: 80 {Totality.dimensions(output) == Shape.dimensions(target) : List<&2, U32>} & 81 (@+values: List<&2, F32> -> @-listed: Listed(input, values) -> 82 @+index: U32 -> @inside: {Nat.is_lt(U32.to_nat(index), Shape.logical_numel(Shape.dimensions(target))) == True{} : Bool} -> 83 {Core.checked_observe_dynamic(output, index) == 84 planned_fiber(values, index, Shape.logical_numel(Totality.dimensions(input)), plan) : Maybe<&2, F32>}) 85 86 def Mapping(input: Core.Dynamic, +target: Shape.Shape, pair: Core.Dynamic & Result<&2, &1, Reduce.Error, Core.Dynamic>) -> Type: 87 MappingWith(input, target, Shape.broadcast_checked(target, Totality.dynamic_shape(input)), pair) 88 89 def ZeroValues(+target: Shape.Shape, pair: Core.Dynamic & Result<&2, &1, Reduce.Error, Core.Dynamic>) -> Type: 90 (retained, result) = pair 91 match result: 92 case Fail{error}: 93 Unit 94 case Done{output}: 95 @+index: U32 -> @inside: {Nat.is_lt(U32.to_nat(index), Shape.logical_numel(Shape.dimensions(target))) == True{} : Bool} -> 96 {Core.checked_observe_dynamic(output, index) == Some{0.0} : Maybe<&2, F32>} 97 98 type Retained<-input: Core.Dynamic, -pair: Core.Dynamic & Result<&2, &1, Reduce.Error, Core.Dynamic>> is Data: 99 Retained{-kept: Totality.SameSource(input, owner(pair))} 100 101 type MappingModel<-input: Core.Dynamic, -target: Shape.Shape, 102 -pair: Core.Dynamic & Result<&2, &1, Reduce.Error, Core.Dynamic>> is Data: 103 MappingModel{-mapping: Mapping(input, target, pair)} 104 105 type Success<-pair: Core.Dynamic & Result<&2, &1, Reduce.Error, Core.Dynamic>> is Data: 106 Success{-successful: {succeeded(pair) == True{} : Bool}} 107 108 type Length<-input: Core.Dynamic, -values: List<&2, F32>> is Data: 109 Length{-exact: {List.length(&2, F32, values) == Shape.logical_numel(Totality.dimensions(input)) : Nat}} 110 111 type Zeros<-target: Shape.Shape, -pair: Core.Dynamic & Result<&2, &1, Reduce.Error, Core.Dynamic>> is Data: 112 Zeros{-zeros: ZeroValues(target, pair)} 113 114 # The contribution owner comes back with the same dimensions and observations. 115 law reduce_retains_input: 116 for input: Core.Dynamic 117 for +target: Shape.Shape 118 Retained 119 120 # On success the output has the target's dimensions, and for every list of 121 # words that lists the contribution, each in-bounds target position holds the 122 # ordered fiber sum of those words. 123 law reduce_mapping: 124 for input: Core.Dynamic 125 for +target: Shape.Shape 126 MappingModel 127 128 # A list of words that lists the contribution has exactly the contribution's 129 # logical length. 130 law listed_length: 131 for input: Core.Dynamic 132 for +values: List<&2, F32> 133 for listed: Listed(input, values) 134 Length 135 136 # A target whose right-aligned broadcast with the contribution shape is the 137 # contribution shape itself always succeeds. 138 law reduce_valid_success: 139 for input: Core.Dynamic 140 for +target: Shape.Shape 141 for +alignment: Shape.Broadcast 142 Success 143 144 # Reducing an empty contribution gives zeros at every target position, not an 145 # empty tensor: for example [0, 3] reduced to [1, 3] is three zeros. 146 law reduce_empty_zeros: 147 for input: Core.Dynamic 148 for +target: Shape.Shape 149 for empty: {Shape.logical_numel(Totality.dimensions(input)) == 0n : Nat} 150 Zeros 151 152 # When the broadcast planner rejects the target and contribution shapes, 153 # reduce_to fails with exactly that planner error and returns the contribution 154 # owner. For example [2] to [0] fails with IncompatibleBroadcast instead of 155 # returning an empty tensor. 156 law reduce_shape_error: 157 for input: Core.Dynamic 158 for +target: Shape.Shape 159 for error: Shape.Error 160 for rejected: {Shape.broadcast_checked(target, Totality.dynamic_shape(input)) == Fail{error} : Result<&2, &2, Shape.Error, Shape.BroadcastPlan>} 161 {Reduce.reduce_to(input, target) == (input, Fail{Reduce.TensorError{Core.ShapeError{error}}}) : Core.Dynamic & Result<&2, &1, Reduce.Error, Core.Dynamic>} 162 163 # When the planned broadcast of the target is not the contribution's shape 164 # (for example a [2, 3] target for a [3] contribution), reduce_to fails with 165 # NotReducible and returns the contribution owner. 166 law reduce_not_reducible: 167 for input: Core.Dynamic 168 for +target: Shape.Shape 169 for +plan: Shape.BroadcastPlan 170 for planned: {Shape.broadcast_checked(target, Totality.dynamic_shape(input)) == Done{plan} : Result<&2, &2, Shape.Error, Shape.BroadcastPlan>} 171 for differ: {Shape.dimensions(Shape.output_shape(plan)) != Totality.dimensions(input) : List<&2, U32>} 172 {Reduce.reduce_to(input, target) == (input, Fail{Reduce.NotReducible{}}) : Core.Dynamic & Result<&2, &1, Reduce.Error, Core.Dynamic>}