diff options
| author | s-ol <s+removethis@s-ol.nu> | 2022-02-02 16:37:58 +0000 |
|---|---|---|
| committer | s-ol <s+removethis@s-ol.nu> | 2025-03-02 14:24:49 +0000 |
| commit | 8baadfc47832bbc3820e7d55adbbe5ddfaffedf3 (patch) | |
| tree | e97b316c29ed3fdce52a061fd8c205edc2613b9a | |
| parent | allow ^ and % in symbols (diff) | |
| download | alive-8baadfc47832bbc3820e7d55adbbe5ddfaffedf3.tar.gz alive-8baadfc47832bbc3820e7d55adbbe5ddfaffedf3.zip | |
lib: rename math → math-simple, add vectorized math
| -rw-r--r-- | alv-lib/math-simple.moon | 215 | ||||
| -rw-r--r-- | alv-lib/math.moon | 225 | ||||
| -rw-r--r-- | alv/type.moon | 13 | ||||
| -rw-r--r-- | docs/gen/layout.moon | 2 | ||||
| -rw-r--r-- | docs/reference/04-2_pure-operators.md | 8 | ||||
| -rw-r--r-- | spec/lang/math_spec.moon | 267 |
6 files changed, 681 insertions, 49 deletions
diff --git a/alv-lib/math-simple.moon b/alv-lib/math-simple.moon new file mode 100644 index 0000000..b79f065 --- /dev/null +++ b/alv-lib/math-simple.moon @@ -0,0 +1,215 @@ +import PureOp, Input, Constant, T, any from require 'alv.base' +unpack or= table.unpack + +num = any.num + +class ReduceOp extends PureOp + pattern: num\rep 2, nil + type: T.num + + tick: => + args = @unwrap_all! + accum = args[1] + for val in *args[2,] + accum = @.fn accum, val + @out\set accum + +func_op = (func, pattern) -> + class extends PureOp + pattern: pattern + type: T.num + + tick: => @out\set func unpack @unwrap_all! + +func_def = (name, args, func, summary, pattern) -> + Constant.meta + meta: + :name + :summary + examples: { "(#{name} #{args})" } + value: func_op func, pattern or num\rep 1, 1 + +evenodd_op = (remainder) -> + class extends PureOp + pattern: num + -num + type: T.bool + + tick: => + { val, div } = @unwrap_all! + @out\set (val % div) == remainder + +add = Constant.meta + meta: + name: 'add' + summary: "Add values." + examples: { '(+ a b [c…])', '(add a b [c…])' } + description: "Sum all arguments." + value: class extends ReduceOp + fn: (a, b) -> a + b + +sub = Constant.meta + meta: + name: 'sub' + summary: "Subtract values." + examples: { '(- a b [c…])', '(- b)', '(sub a b [c…])' } + description: "Subtract all other arguments from `a`. + +If only `b` is given, `a` is assumed to be `0`." + value: class extends ReduceOp + setup: (...) => + super ... + if #@inputs == 1 + table.insert @inputs, 1, Input.cold Constant.num 0 + + pattern: num*0 + fn: (a, b) -> a - b + +mul = Constant.meta + meta: + name: 'mul' + summary: "Multiply values." + examples: { '(* a b [c…])', '(mul a b [c…])' } + value: class extends ReduceOp + fn: (a, b) -> a * b + +div = Constant.meta + meta: + name: 'div' + summary: "Divide values." + examples: { '(/ a b [c…])', '(div a b [c…])' } + description: "Divide `a` by all other arguments." + value: class extends ReduceOp + fn: (a, b) -> a / b + +pow = Constant.meta + meta: + name: 'pow' + summary: "Raise to a power." + examples: { '(^ base exp)', '(pow base exp' } + description: "Raise `base` to the power `exp`." + value: class extends ReduceOp + fn: (a, b) -> a ^ b + +mod = Constant.meta + meta: + name: 'mod' + summary: 'Modulo operator.' + examples: { '(% num div)', '(mod num div)' } + description: "Calculate remainder of division by `div`." + value: func_op ((a, b) -> a % b), num + num + +even = Constant.meta + meta: + name: 'even' + summary: 'Check whether val is even.' + examples: { '(even val [div])' } + description: "`true` if dividing `val` by `div` has remainder zero. +`div` defaults to 2." + value: evenodd_op 0 + +odd = Constant.meta + meta: + name: 'odd' + summary: 'Check whether val is odd.' + examples: { '(odd val [div])' } + description: "`true` if dividing `val` by `div` has remainder one. +`div` defaults to 2." + value: evenodd_op 1 + +mix = Constant.meta + meta: + name: 'mix' + summary: 'Linearly interpolate.' + examples: { '(mix a b i)' } + description: "Interpolate between `a` and `b` using `i` in range 0-1." + value: func_op ((a, b, i) -> i*b + (1-i)*a), num + num + num + +min = Constant.meta + meta: + name: 'min' + summary: "Find the minimum." + examples: { '(min a b [c…])' } + description: "Return the lowest of arguments." + value: func_op math.min, num*0 + +max = Constant.meta + meta: + name: 'max' + summary: "Find the maximum." + examples: { '(max a b [c…])' } + description: "Return the highest of arguments." + value: func_op math.max, num*0 + +clamp = Constant.meta + meta: + name: 'clamp' + summary: "Clamp a value to a range." + examples: { '(clamp min max val)' } + description: "Returns `min` if `val < min`; `max` if `val > max`; and `val` otherwise." + value: func_op ((min, max, val) -> math.min max, math.max min, val), num*3 + +inc = func_def 'inc', 'i', ((i) -> i + 1), "Increment by 1." +dec = func_def 'dec', 'i', ((i) -> i - 1), "Decrement by 1." + +cos = func_def 'cos', 'alpha', math.cos, "Cosine function (radians)." +sin = func_def 'sin', 'alpha', math.sin, "Sine function (radians)." +tan = func_def 'tan', 'alpha', math.tan, "Tangent function (radians)." +acos = func_def 'acos', 'cos', math.acos, "Inverse cosine function (radians)." +asin = func_def 'asin', 'sin', math.asin, "Inverse sine function (radians)." +atan = func_def 'atan', 'tan', math.atan, "Inverse tangent function (radians)." +atan2 = func_def 'atan2', 'y x', math.atan2, "Inverse tangent function (two argument version).", num + num +cosh = func_def 'cosh', 'alpha', math.cosh, "Hyperbolic cosine function (radians)." +sinh = func_def 'sinh', 'alpha', math.sinh, "Hyperbolic sine function (radians)." +tanh = func_def 'tanh', 'alpha', math.tanh, "Hyperbolic tangent function (radians)." + +floor = func_def 'floor', 'val', math.floor, "Round towards negative infinity." +ceil = func_def 'ceil', 'val', math.ceil, "Round towards positive infinity." +abs = func_def 'abs', 'val', math.abs, "Get the absolute value." + +exp = func_def 'exp', 'exp', math.floor, "*e* number raised to a power." +log = func_def 'log', 'val [base]', math.log, "Logarithm with optional base.", num + -num +log10 = func_def 'log10', 'val', math.log10, "Logarithm with base 10." +sqrt = func_def 'sqrt', 'val', math.sqrt, "Square root function." + +Constant.meta + meta: + name: 'math-simple' + summary: "Mathematical functions for scalars." + description: " +All operators are PureOps. +They accept only scalar numbers. +For vectorized operators and matrix multiplication, use [math/][]." + + value: + :add, '+': add + :sub, '-': sub + :mul, '*': mul + :div, '/': div + :pow, '^': pow + :mod, '%': mod + + :even, :odd + + :mix + :min, :max, :clamp + + :inc, :dec + + pi: Constant.meta + value: math.pi + meta: summary: 'The pi constant.' + + tau: Constant.meta + value: math.pi*2 + meta: summary: 'The tau constant.' + + huge: Constant.meta + value: math.huge + meta: summary: 'Positive infinity constant.' + + :sin, :cos, :tan + :asin, :acos, :atan, :atan2 + :sinh, :cosh, :tanh + + :floor, :ceil, :abs + :exp, :log, :log10, :sqrt diff --git a/alv-lib/math.moon b/alv-lib/math.moon index 7f76ce9..4baf4be 100644 --- a/alv-lib/math.moon +++ b/alv-lib/math.moon @@ -1,25 +1,133 @@ -import PureOp, Constant, T, sig, evt from require 'alv.base' +import PureOp, RTNode, Constant, Error, T, Array, any from require 'alv.base' unpack or= table.unpack -num = sig.num / evt.num +--- (recursively) wrap/repeat a scalar value to match a (nested) array type. +-- +-- For example `expand_to (Array 3, Array 4, T.num), 2` will return +-- `[[2 2 2 2] [2 2 2 2] [2 2 2 2]]`. +expand_to = (want, have, val) -> + return val if want == have + + return for key, inner in want\iter_keys! + expand_to inner, have, val + +--- apply fn componentwise +deep_apply = (fn, type, args) -> + return fn args unless type.iter_keys + + return for key, inner in type\iter_keys! + deep_apply fn, inner, [arg[key] for arg in *args] + +is_vec = (type) -> type.__class == Array and type.type == T.num +is_mat = (type) -> type.__class == Array and type.type.__class == Array + +--- return a function that runs `expand_to` on all arguments +-- +-- @treturn function +deep_apply_fn = (_func, types) -> + func = (args) -> _func unpack args + + result_type = nil + for type in *types + continue if type == T.num + + result_type or= type + assert type == result_type + + -- all scalars, don't expand + if not result_type + return T.num, func + + -- at least one non-scalar + result_type, (args) -> + -- expand all arguments + expanded = for i, arg in ipairs args + if types[i] != result_type + expand_to result_type, types[i], arg + else + arg + + deep_apply func, result_type, expanded + + +multiply_mat = (ltype, rtype, lval, rval) -> + return for i = 1, ltype.size + for j = 1, rtype.type.size + accum = 0 + for k = 1, rtype.size + accum += lval[i][k] * rval[k][j] + accum + +apply_fn_linalg = (types) -> + result = types[1] + fns = {} + + for i=2, #types + local nextres + ltype = result + rtype = types[i] + + lvec, rvec = (is_vec ltype), (is_vec rtype) + + fn = if (lvec and rvec) or ltype == T.num or rtype == T.num + -- componentwise mult + nextres = if lvec or (is_mat ltype) then ltype else rtype + (lval, rval) -> + lval = expand_to nextres, ltype, lval + rval = expand_to nextres, rtype, rval + deep_apply ((args) -> args[1] * args[2]), nextres, { lval, rval } + else + -- matrix/vector multiplication or matrix/matrix multiplication + if lvec + nextres = ltype + ltype = Array 1, ltype + else if rvec + nextres = rtype + rtype = Array rtype.size, Array 1, rtype.type if rvec + else + nextres = Array ltype.size, Array rtype.type.size, T.num + + assert ltype.type.size == rtype.size + + (lval, rval) -> + lval = {lval} if lvec + rval = [{ v } for v in *rval] if rvec + res = multiply_mat ltype, rtype, lval, rval + if rvec + [v[1] for v in *res] + else if lvec + res[1] + else + res + + result = nextres + table.insert fns, fn + + result, (args) -> + accum = args[1] + for i, fn in ipairs fns + accum = fn accum, args[i + 1] + accum -class ReduceOp extends PureOp - pattern: num\rep 2, nil - type: T.num +num = any.num / any!! - tick: => - args = @unwrap_all! - accum = args[1] - for val in *args[2,] - accum = @.fn accum, val - @out\set accum +reduce_fn = (fn) -> + (accum, ...) -> + for i=1, select '#', ... + accum = fn accum, select i, ... + accum func_op = (func, pattern) -> class extends PureOp pattern: pattern - type: T.num + type: (inputs) => + types = [input\type! for input in *inputs] + result, @func = deep_apply_fn func, types + assert (is_vec result) or (is_mat result) or (result == T.num), + Error 'argument', "expected matrices, vectors or numbers" + result - tick: => @out\set func unpack @unwrap_all! + tick: => @out\set @.func @unwrap_all! func_def = (name, args, func, summary, pattern) -> Constant.meta @@ -31,7 +139,7 @@ func_def = (name, args, func, summary, pattern) -> evenodd_op = (remainder) -> class extends PureOp - pattern: num + -num + pattern: T.num + -T.num type: T.bool tick: => @@ -44,8 +152,7 @@ add = Constant.meta summary: "Add values." examples: { '(+ a b [c…])', '(add a b [c…])' } description: "Sum all arguments." - value: class extends ReduceOp - fn: (a, b) -> a + b + value: func_op (reduce_fn (a, b) -> a + b), num\rep 2, nil sub = Constant.meta meta: @@ -53,16 +160,38 @@ sub = Constant.meta summary: "Subtract values." examples: { '(- a b [c…])', '(sub a b [c…])' } description: "Subtract all other arguments from `a`." - value: class extends ReduceOp - fn: (a, b) -> a - b + value: class extends func_op (reduce_fn (a, b) -> a - b), num*0 + setup: (inputs, ...) => + if #inputs == 1 + table.insert inputs, 1, RTNode result: Constant.num 0 + super inputs, ... mul = Constant.meta meta: name: 'mul' - summary: "Multiply values." + summary: "Multiply scalars, vectors and matrices." examples: { '(* a b [c…])', '(mul a b [c…])' } - value: class extends ReduceOp - fn: (a, b) -> a * b + description: "Multiplies all arguments. + +For every pair of arguments, from left to right: + +- If either argument is a scalar, or both are vectors, multiply componentwise. +- If either argument is a matrix and the other is a vector, apply the matrix transformation. + - `(* num[L][M] num[M]) → num[L]` (forward transform) + - `(* num[M] num[M][N]) → num[N]` (reverse transform) +- If both arguments are matrices, multiply them using matrix multiplication. + - `(* num[L][M] num[M][N]) → num[M][N]`" + value: class extends PureOp + pattern: any!\rep 2, nil + + type: (inputs) => + types = [input\type! for input in *inputs] + result, @func = apply_fn_linalg types + assert (is_vec result) or (is_mat result) or (result == T.num), + Error 'argument', "expected matrices, vectors or numbers" + result + + tick: => @out\set @.func @unwrap_all! div = Constant.meta meta: @@ -70,8 +199,8 @@ div = Constant.meta summary: "Divide values." examples: { '(/ a b [c…])', '(div a b [c…])' } description: "Divide `a` by all other arguments." - value: class extends ReduceOp - fn: (a, b) -> a / b + value: func_op (reduce_fn (a, b) -> a / b), num\rep 2, nil + -- @TODO: block matrix-matrix division pow = Constant.meta meta: @@ -79,8 +208,7 @@ pow = Constant.meta summary: "Raise to a power." examples: { '(^ base exp)', '(pow base exp' } description: "Raise `base` to the power `exp`." - value: class extends ReduceOp - fn: (a, b) -> a ^ b + value: func_op (reduce_fn (a, b) -> a ^ b), num\rep 2, nil mod = Constant.meta meta: @@ -90,24 +218,6 @@ mod = Constant.meta description: "Calculate remainder of division by `div`." value: func_op ((a, b) -> a % b), num + num -even = Constant.meta - meta: - name: 'even' - summary: 'Check whether val is even.' - examples: { '(even val [div])' } - description: "`true` if dividing `val` by `div` has remainder zero. -`div` defaults to 2." - value: evenodd_op 0 - -odd = Constant.meta - meta: - name: 'odd' - summary: 'Check whether val is odd.' - examples: { '(odd val [div])' } - description: "`true` if dividing `val` by `div` has remainder one. -`div` defaults to 2." - value: evenodd_op 1 - mix = Constant.meta meta: name: 'mix' @@ -132,6 +242,14 @@ max = Constant.meta description: "Return the highest of arguments." value: func_op math.max, num*0 +clamp = Constant.meta + meta: + name: 'clamp' + summary: "Clamp a value to a range." + examples: { '(clamp min max val)' } + description: "Returns `min` if `val < min`; `max` if `val > max`; and `val` otherwise." + value: func_op ((min, max, val) -> math.min max, math.max min, val), num*3 + inc = func_def 'inc', 'i', ((i) -> i + 1), "Increment by 1." dec = func_def 'dec', 'i', ((i) -> i - 1), "Decrement by 1." @@ -159,6 +277,25 @@ Constant.meta meta: name: 'math' summary: "Mathematical functions." + description: " +This module is exactly like [math-simple/][], except that the operators +also work componentwise with vectors (`num[X]`) and matrices (`num[X][Y]`). +All operators are PureOps. + + (+ 1 2 3) #(<num= 6>) + (+ (array 1 2) (array 3 4)) #(<num[2]= [4 6]>) + +The arguments for an operator generally have to be of the same type. +However it is also okay to pass in scalar numbers together with a different type. +The scalars will be repeated as necessary to fit the shape of other arguments: + + (* (array (array 1 2) (array 3 4)) + 2) + #(<num[2][2]= [[2 4] [6 8]]>) + +The [mul][:math/mul:] (`*`) operator is the only exception to this, +as it handles matrix-matrix and matrix-vector multiplication according to linear algebra. +" value: :add, '+': add @@ -171,7 +308,7 @@ Constant.meta :even, :odd :mix - :min, :max + :min, :max, :clamp :inc, :dec diff --git a/alv/type.moon b/alv/type.moon index 4f38e02..765cab2 100644 --- a/alv/type.moon +++ b/alv/type.moon @@ -119,6 +119,12 @@ class Struct extends Type get: (key) => assert @types[key], Error 'index', "#{@} has no '#{key}' key" + --- iterate over contained keys and types + -- each iteration, returns a key and associated type + -- @treturn string key + -- @treturn Type associated type + iter_keys: => next, @types, nil + --- create a new struct type with a subset of keys. project: (keys) => types = {} @@ -161,6 +167,13 @@ class Array extends Type assert key >= 0 and key < @size, Error 'index', "index '#{key}' out of range!" @type + array_next = (i) => if i < @size then i + 1, @type + --- iterate over contained types + -- each iteration, returns a key and associated type + -- @treturn number key + -- @treturn Type associated type + iter_keys: => array_next, @, 0 + __eq: (other) => other.__class == Array and @size == other.size and @type == other.type __tostring: => "#{@type}[#{@size}]" diff --git a/docs/gen/layout.moon b/docs/gen/layout.moon index 7d3c4e1..4c75749 100644 --- a/docs/gen/layout.moon +++ b/docs/gen/layout.moon @@ -86,7 +86,7 @@ r = (text, ref) -> -- substitute markdown-style reference links autoref = (str) -> str = str\gsub '%[([^%]]-)%]%[%]', r - str = str\gsub '%[([^%]]-)%]%[:(.-):%]', r + str = str\gsub '%[([^%]]-)%]%[:([^%]]-):%]', r str subnav = do diff --git a/docs/reference/04-2_pure-operators.md b/docs/reference/04-2_pure-operators.md index 4d07920..b3bdf42 100644 --- a/docs/reference/04-2_pure-operators.md +++ b/docs/reference/04-2_pure-operators.md @@ -25,18 +25,18 @@ As an example, let's consider [math/+][]: (trace (+ 1 2 (every 1 3))) (trace (+ 1 (lfo 2) (every 1 3))) ```output -(+ num= num= num=) -> num= +(+ num= num= num=) → num= trace (+ 1 2 3): <num= 6> -(+ num= num= num~) -> num~ +(+ num= num= num~) → num~ trace (+ 1 2 (lfo 2)): <num~ 4.0> trace (+ 1 2 (lfo 2)): <num~ 3.9882585630406> -(+ num= num= num!) -> num! +(+ num= num= num!) → num! trace (+ 1 2 (every 2 3)): <num! 6> trace (+ 1 2 (every 2 3)): <num! 6> -(+ num= num~ num!) -> num! +(+ num= num~ num!) → num! trace (+ 1 (lfo 2) (every 2 3)): <num! 4.9950529446967> trace (+ 1 (lfo 2) (every 2 3)): <num! 4.9950529446967> ``` diff --git a/spec/lang/math_spec.moon b/spec/lang/math_spec.moon new file mode 100644 index 0000000..23a353b --- /dev/null +++ b/spec/lang/math_spec.moon @@ -0,0 +1,267 @@ +import TestPilot from require 'spec.test_setup' +import T, Array, Constant from require 'alv' + +describe_both = (fn) -> + describe "math", -> + test = TestPilot '', '(import* testing math)\n' + fn! + + describe "math-simple", -> + test = TestPilot '', '(import* testing math-simple)\n' + fn! + +describe_both -> + describe "add, sub, mul, div, pow, mod", -> + it "are aliased as +-*/^%", -> + COPILOT\eval_once ' + (expect= + add) + (expect= - sub) + (expect= * mul) + (expect= / div) + (expect= ^ pow) + (expect= % mod) + ' + + it "are sane", -> + COPILOT\eval_once ' + (expect= 2 (+ 1 1)) + (expect= 6 (+ 2 3 0 1)) + + (expect= -5 (- 2 7)) + (expect= 0 (- 10 4 3 2 1 0)) + (expect= -10 (- 10)) + + (expect= 1 (* 1 1)) + (expect= -2 (* -1 2)) + (expect= 14 (* 4 0.5 7)) + + (expect= 1 (/ 4 4)) + (expect= -2 (/ -10 5)) + (expect= 3 (/ -30 -10)) + + (expect= 1024 (^ 2 10)) + (expect= 0.25 (^ 4 -1)) + (expect= 1 (^ 999 0)) + + (expect= 4 (% 10 6)) + (expect= 3 (% -2 5)) + (expect= -2 (% -2 -5)) + ' + + describe "trigonometric functions and constants", -> + COPILOT\eval_once ' + (expect= tau (* pi 2)) + + #(darn you fp accuracy! + (expect= 0.5 (asin (sin 0.5))) + (expect= 0.5 (acos (cos 0.5))) + ...) + ' + + it "min, max, clamp, huge", -> + COPILOT\eval_once ' + (expect= 0 (min 0 1 2)) + (expect= 2 (max 0 1 2)) + + (expect= -2 (clamp -2 3.5 -4)) + (expect= -1 (clamp -2 3.5 -1)) + (expect= 0 (clamp -2 3.5 0)) + (expect= 1 (clamp -2 3.5 1)) + (expect= 3.5 (clamp -2 3.5 4)) + + (expect= (- huge) (min 0 1 -123456789 (- huge))) + (expect= huge (max 0 1 123456789 huge)) + ' + + it "inc, dec", -> + COPILOT\eval_once ' + (expect= 1 (inc 0)) + (expect= -1 (dec 0)) + (expect= 3 (inc (inc 1))) + (expect= -1 (dec (dec 1))) + (expect= 0 (inc (dec 0)) (dec (inc 0))) + ' + +describe "math", -> + test = TestPilot '', '(import* testing math)\n' + + describe "add, sub, mul, div, pow, mod", -> + it "handle scalar/vector", -> + COPILOT\eval_once ' + (expect= (array 3 4 5) + (+ 1 (array 1 2 3) 1)) + + (expect= (array 0 1 2) + (- (array 1 2 3) 1)) + + (expect= (array 3 6 9) + (* 3 (array 1 2 3))) + (expect= (array 3 6 9) + (* (array 1 2 3) 3)) + + (expect= (array 12 9 4) + (/ 36 (array 3 4 9))) + + (expect= (array 9 16 25) + (^ (array 3 4 5) 2)) + (expect= (array 1 2 4 8) + (^ 2 (array 0 1 2 3))) + + (expect= (array 3 0 1) + (% (array 3 4 5) 4)) + ' + + it "handle vector/vector and matrix/matrix", -> + COPILOT\eval_once ' + (expect= (array 5 7 9) + (+ (array 1 2 3) + (array 4 5 6))) + + (expect= (array (array 11 12) + (array 13 14)) + (+ + (array (array 1 2) + (array 3 4)) + 5 + 5)) + + (expect= (array 2 0 -2) + (- (array 3 2 1) + (array 1 2 3))) + + (expect= (array 1 -2 -3) + (- (array -1 2 3))) + ' + + err = assert.has.error -> + COPILOT\eval_once ' + (+ (array 1 2 3) + (array 1 2))' + + err = assert.has.error -> + COPILOT\eval_once ' + (+ (array (array 1 2) (array 1 2)) + (array 1 2))' + + err = assert.has.error -> + COPILOT\eval_once ' + (- (array 1 2 3) + (array 1 2))' + + err = assert.has.error -> + COPILOT\eval_once ' + (- (array (array 1 2) (array 1 2)) + (array 1 2))' + + describe "mul", -> + it "handles scalars and matrices", -> + with COPILOT\eval_once ' + (* 3 + (array + (array 1 2) + (array 4 5)))' + assert.is.true \is_const! + assert.is.equal '<num[2][2]= [[3 6] [12 15]]>', tostring .result + + with COPILOT\eval_once ' + (* (array + (array 1 2) + (array 4 5)) + 3)' + assert.is.true \is_const! + assert.is.equal '<num[2][2]= [[3 6] [12 15]]>', tostring .result + + it "handles vectors and matrices", -> + with COPILOT\eval_once ' + (* + (array (array 1 0 0) + (array 0 1 0) + (array 0 0 1)) + (array 4 5 6))' + assert.is.true \is_const! + assert.is.equal '<num[3]= [4 5 6]>', tostring .result + + with COPILOT\eval_once ' + (* + (array (array 1 0 0) + (array 0 1 0) + (array 3 2 1)) + (array 4 5 1))' + assert.is.true \is_const! + assert.is.equal '<num[3]= [4 5 23]>', tostring .result + + it "handles matrices", -> + with COPILOT\eval_once ' + (* + (array (array 1 2 3) + (array 4 5 6)) + (array (array 10 11) + (array 20 21) + (array 30 31)))' + assert.is.true \is_const! + assert.is.equal '<num[2][2]= [[140 146] [320 335]]>', tostring .result + + it "handles everything mixed", -> + with COPILOT\eval_once ' + (* + (array (array 1 2 3) + (array 4 5 6)) + 2 + (array (array 10 11) + (array 20 21) + (array 30 31)))' + assert.is.true \is_const! + assert.is.equal '<num[2][2]= [[280 292] [640 670]]>', tostring .result + + with COPILOT\eval_once ' + (* + (array (array 1 2 3) + (array 4 5 6)) + 2 + (array (array 10 11) + (array 20 21) + (array 30 31)) + (array 4 7))' + assert.is.true \is_const! + assert.is.equal '<num[2]= [3164 7250]>', tostring .result + + it "errors with wrong sizes (matrix and vector)", -> + err = assert.has.error -> COPILOT\eval_once ' + (* + (array (array 1 2 3) + (array 4 5 6)) + (array 1 2))' + -- assert.matches "", err + + err = assert.has.error -> COPILOT\eval_once ' + (* + (array 1 2 3) + (array (array 1 2 3) + (array 4 5 6)))' + -- assert.matches "", err + + it "errors with wrong sizes (matrix)", -> + err = assert.has.error -> COPILOT\eval_once ' + (* + (array (array 1 2 3) + (array 4 5 6)) + (array (array 1 2) + (array 4 5)))' + -- assert.matches "", err + + it "min, max, clamp, huge", -> + COPILOT\eval_once ' + (expect= (array 3 2 1) + (min (array 3 4 1) (array 5 2 huge))) + (expect= (array 5 huge 4) + (max (array 3 huge 4) (array 5 999 2))) + + (expect= (array -2 -1 0 1 3.5) + (clamp -2 3.5 (array -4 -1 0 1 4))) + + (expect= 1 (inc 0)) + (expect= -1 (dec 0)) + (expect= 3 (inc (inc 1))) + (expect= -1 (dec (dec 1))) + (expect= 0 (inc (dec 0)) (dec (inc 0))) + ' |
