1 import Base 2 import ../shape/core.bend as Shape 3 import ../tensor/core.bend as Core 4 import ../tensor/elementwise.bend as Elementwise 5 import ../tensor/elementwise_totality.bend as Totality 6 import ../tensor/projection_LAWS.bend as Projection 7 import ./broadcast.bend as Broadcast 8 9 def owner(pair: Core.Dynamic & Result<&2, &1, Broadcast.Error, Core.Dynamic>) -> Core.Dynamic: 10 (input, result) = pair 11 input 12 13 def succeeded(pair: Core.Dynamic & Result<&2, &1, Broadcast.Error, Core.Dynamic>) -> Bool: 14 (input, result) = pair 15 Result.is_done(&2, &1, Broadcast.Error, Core.Dynamic, result) 16 17 # The input value that the specification assigns to one output position: the 18 # input offset of its right-aligned broadcast projection. 19 def source_value(input: Core.Dynamic, offsets: Maybe<&1, U32 & U32>) -> Maybe<&2, F32>: 20 match offsets: 21 case None{}: 22 None{} 23 case Some{pair}: 24 (input_offset, target_offset) = pair 25 Core.checked_observe_dynamic(input, input_offset) 26 27 def planned_source(input: Core.Dynamic, +index: U32, plan: Result<&2, &2, Shape.Error, Shape.BroadcastPlan>) -> Maybe<&2, F32>: 28 match plan: 29 case Fail{error}: 30 None{} 31 case Done{+plan}: 32 source_value(input, Projection.offset_spec(plan, U32.to_nat(index))) 33 34 def MappingWith(-input: Core.Dynamic, +target: Shape.Shape, plan: Result<&2, &2, Shape.Error, Shape.BroadcastPlan>, 35 pair: Core.Dynamic & Result<&2, &1, Broadcast.Error, Core.Dynamic>) -> Type: 36 (retained, result) = pair 37 match result: 38 case Fail{error}: 39 Unit 40 case Done{output}: 41 {Totality.dimensions(output) == Shape.dimensions(target) : List<&2, U32>} & 42 (@+index: U32 -> @inside: Shape.LT(U32.to_nat(index), Shape.logical_numel(Shape.dimensions(target))) -> 43 {Core.observe_dynamic(output, index) == planned_source(input, index, plan) : Maybe<&2, F32>}) 44 45 def Mapping(input: Core.Dynamic, +target: Shape.Shape, pair: Core.Dynamic & Result<&2, &1, Broadcast.Error, Core.Dynamic>) -> Type: 46 MappingWith(input, target, Shape.broadcast_checked(Totality.dynamic_shape(input), target), pair) 47 48 type Retained<-input: Core.Dynamic, -pair: Core.Dynamic & Result<&2, &1, Broadcast.Error, Core.Dynamic>> is Data: 49 Retained{-kept: Totality.SameSource(input, owner(pair))} 50 51 type MappingModel<-input: Core.Dynamic, -target: Shape.Shape, -pair: Core.Dynamic & Result<&2, &1, Broadcast.Error, Core.Dynamic>> is Data: 52 MappingModel{-mapping: Mapping(input, target, pair)} 53 54 type Success<-pair: Core.Dynamic & Result<&2, &1, Broadcast.Error, Core.Dynamic>> is Data: 55 Success{-successful: {succeeded(pair) == True{} : Bool}} 56 57 law keep_left_scalar: 58 for +left: F32 59 for +right: F32 60 {Elementwise.scalar(Elementwise.KeepLeft{}, left, right) == left : F32} 61 62 # The input owner comes back with the same dimensions and observations. 63 law broadcast_retains_input: 64 for input: Core.Dynamic 65 for +target: Shape.Shape 66 Retained 67 68 # On success the output has the target's dimensions, and each in-bounds output 69 # position observes the input at the offset that the independent broadcast 70 # projection specification assigns to it. 71 law broadcast_mapping: 72 for input: Core.Dynamic 73 for +target: Shape.Shape 74 MappingModel 75 76 # A request whose right-aligned broadcast with the input shape is the target 77 # itself always succeeds. 78 law broadcast_valid_success: 79 for input: Core.Dynamic 80 for +target: Shape.Shape 81 for +alignment: Shape.Broadcast 82 Success 83 84 # When the broadcast planner rejects the input and target shapes, broadcast_to 85 # fails with exactly that planner error and returns the input owner. For example [2] to 86 # [0] fails with IncompatibleBroadcast instead of returning an empty tensor. 87 law broadcast_shape_error: 88 for input: Core.Dynamic 89 for +target: Shape.Shape 90 for error: Shape.Error 91 for rejected: {Shape.broadcast_checked(Totality.dynamic_shape(input), target) == Fail{error} : Result<&2, &2, Shape.Error, Shape.BroadcastPlan>} 92 {Broadcast.broadcast_to(input, target) == (input, Fail{Broadcast.TensorError{Core.ShapeError{error}}}) : Core.Dynamic & Result<&2, &1, Broadcast.Error, Core.Dynamic>} 93 94 # When the planned broadcast is not the target itself (for example [3] to 95 # [1]), broadcast_to fails with NotBroadcastable and returns the input owner. 96 law broadcast_not_target: 97 for input: Core.Dynamic 98 for +target: Shape.Shape 99 for +plan: Shape.BroadcastPlan 100 for planned: {Shape.broadcast_checked(Totality.dynamic_shape(input), target) == Done{plan} : Result<&2, &2, Shape.Error, Shape.BroadcastPlan>} 101 for differ: {Shape.dimensions(Shape.output_shape(plan)) != Shape.dimensions(target) : List<&2, U32>} 102 {Broadcast.broadcast_to(input, target) == (input, Fail{Broadcast.NotBroadcastable{}}) : Core.Dynamic & Result<&2, &1, Broadcast.Error, Core.Dynamic>}