Skip to content
Open
374 changes: 374 additions & 0 deletions tests/stencil.apln
Original file line number Diff line number Diff line change
@@ -0,0 +1,374 @@
:Namespace stencil
Assert←#.unittest.Assert
classic←#.utils.isClassic

⍝ model of ⌺ to test against
_Stencil_←{
⎕IO←0
⎕ML←1
ERANK ELENGTH EDOMAIN←4 5 11
spec_rank←≢⍴⍵⍵
spec_rank>2:⎕SIGNAL ERANK ⍝ window spec has rank at most 2
(spec_rank=2)∧(2≠≢⍵⍵):⎕SIGNAL ELENGTH ⍝ if window spec is a matrix, it has two rows
rank←≢⍴⍵ ⍝ rank of ⍵
rank=0:⎕SIGNAL ERANK ⍝ can't window a scalar
axes←⊢/1,⍴⍵⍵ ⍝ number of axes specified
axes>rank:⎕SIGNAL ELENGTH ⍝ can't specifiy more axes than there are
0∊⍵⍵:⎕SIGNAL EDOMAIN ⍝ windows specs can't have 0 in size or movement
size←axes↑,⍵⍵ ⍝ size of the window
(size-size>2)∨.>(axes↑⍴⍵):⎕SIGNAL EDOMAIN ⍝ ⍵ axes must be long enough that we never need to pad on both sides
move←axes↑axes↓(,⍵⍵),axes⍴1 ⍝ movement of the window
padding←⌊0.5×size-1 ⍝ padding on each axis
padded←⍵ ⍝ ⍵ with padding
shape←axes↑⍴⍵ ⍝ leading shape of ⍵ that we care about
padded↑⍨←shape+padding ⍝ pad ends of axes
padded↑⍨←-shape+2×padding ⍝ pad starts of axes
jumps←move×⍳¨⌈move÷⍨shape-~2|size ⍝ jumps from the start corner along each axis
drop←0⌈padding-jumps ⍝ padding drop at the start of axes
drop-←0⌈jumps+padding+size-axes↑⍴padded ⍝ padding drop at the end of axes (negative)
drop←,¨⊃∘.,/drop ⍝ padding drops per window
corners←⊃∘.,/jumps ⍝ corners of windows
window←⍳¨size ⍝ window positioned at the start of the padded input
drop ⍺⍺{(⊃⍺)⍺⍺ padded⌷⍨window+⊃⍵}⍤0⊢corners ⍝ index windows and do ⍺⍺, use ⍤0 instead of ↑¨ to preserve shyness
}

⍝ check that the behaviour of ⌺ matches the behaviour of _Stencil_ for the given
⍝ operands and argument, varying ⎕IO, ⎕ML, and ⎕CT
⍝ F, spec, and data are passed through to the invocations of ⌺ and _Stencil_: (F⌺spec)data
⍝ config has one of the following forms:
⍝ ∘ test_comment_prefix
⍝ ∘ (test_comment_prefix nonzero_ct)
⍝ ∘ (test_comment_prefix nonzero_ct check_ct)
⍝ where
⍝ ∘ test_comment_prefix is a character vector,
⍝ ∘ nonzero_ct is a boolean
⍝ ∘ check_ct is a valid value for ⎕CT
⍝ test_comment_prefix is used as a prefix for the test comment, with a character
⍝ vector indicating the tested values of ⎕IO, ⎕ML, and ⎕CT catenated as a suffix
⍝ nonzero_ct indicates if this test is valid with ⎕CT≠0. when set to 0, only ⎕CT←0
⍝ will be tested. this exists because some special cases are only trigged with
⍝ ⎕CT←0
⍝ check_ct indicates the value of ⎕CT which the results of ⌺ and _Stencil_ will be
⍝ compared under. this exists to tolerate floating point non-associativity errors
⍝ the default values of these are (nonzero_ct check_ct)←1 0
⍝ test_id is used as a semi-global set by the calling code
∇ r←{config}(F _Check_ spec)data
;⎕CT;⎕IO;⎕ML
;expected;actual
;config;test_comment_prefix;test_comment;nonzero_ct;check_ct
;success

⍝ parse config
(nonzero_ct check_ct)←1 0
:If 326=⎕DR config
:If 2=≢config
(test_comment_prefix nonzero_ct)←config
:Else
(test_comment_prefix nonzero_ct check_ct)←config
:End
:Else
test_comment_prefix←config
:End

⍝ run tests with varying ⎕IO, ⎕ML, ⎕CT
r←⍬
:For ⎕IO ⎕ML ⎕CT :In ⊃∘.,/(0 1)(0 1 2 3)(1E¯14,⍣nonzero_ct⊢0)
test_comment←test_comment_prefix,' with (⎕IO ⎕ML ⎕CT)←',⍕⎕IO ⎕ML ⎕CT
:Trap 0 ⋄ expected←(1 ⋄ (F _Stencil_ spec)data) ⋄ :Else ⋄ expected←0 ⎕EN ⋄ :End
:Trap 0 ⋄ actual←(1 ⋄ (F⌺spec)data) ⋄ :Else ⋄ actual←0 ⎕EN ⋄ :End
⎕CT←check_ct
success←expected≡actual
Comment thread
sloorush marked this conversation as resolved.
:If 326=⎕DR expected ⋄ :AndIf 0∊⍴expected
success∧←expected≡⍥⊃actual ⍝ prototypes should also match
:End
r,←test_id test_comment Assert success
:End

⍝ test each case of ⌺ with randomly generated data
∇ {r}←test_stencil
;⎕IO
;data_rank;window_rank;bound;data_shape;window_shape;window_movement;data;spec
;i;j
;type;Atype
;test_id;test_comment
;A;E

⎕IO←0
r←⍬

⍝ GENERAL CASE
⍝ ─────── ────

⍝ we use ⍺⍺←{⍺⍵} as it is not optimised, and gives all the information we need to check it's working correctly
:For data_rank :In 1+⍳15
:For window_rank :In 1+⍳data_rank

⍝ try to generate a shape with a particular target bound
bound←1+?10000
data_shape←⌊0.5+*¯2-/(⍟bound)×0,1,⍨{⍵[⍋⍵]}?0⍴⍨data_rank-1
window_shape←1+?data_shape
window_movement←1+?data_shape

⍝ take a (possibly complete) prefix of the spec
i←?data_rank
window_shape window_movement↑⍨←i+1

⍝ optionally zero out an unspecified axis
⍝ (stencil allows you to implicitly, but not explicitly, have a window side length of 0)
:If (0.1>?0)∧(data_rank≠i+1) ⋄ data_shape[i+1]←0 ⋄ :End

spec←[window_shape ⋄ window_movement]
test_id←'Stencil general case'
test_comment←'{⍺⍵}⌺[,',(⍕window_shape),' ⋄ ,',(⍕window_movement),']⊢',(⍕data_shape),'⍴ ⍝ type '

⍝ numeric
:For type :In 11 83 163 323 645 1287 1289
data←data_shape #.random.BoundedNumeric #.utils.NumericMinMax type
r,←(test_comment,⍕type)({⍺ ⍵}_Check_ spec)data
:End

⍝ character
:If classic
data←data_shape #.random.Character 82
r,←(test_comment,'82')({⍺ ⍵}_Check_ spec)data
:Else
:For type :In 80 160 320
data←data_shape #.random.Character type
r,←(test_comment,⍕type)({⍺ ⍵}_Check_ spec)data
:End
:End

⍝ pointer
data←,¨data
r,←(test_comment,'326')({⍺ ⍵}_Check_ spec)data
:End
:End

⍝ SPECIAL CASES
⍝ ─────── ─────

⍝ stenvec_identity_etc
⍝ ────────────────────

data_shape←1+?1000
spec←1+?data_shape
test_id←'stenvec_identity_etc'
test_comment←'⌺',(⍕spec),'⊢',(⍕data_shape),'⍴ ⍝ type '

⍝ numeric
:For type :In 11 83 163 323 645 1287 1289
data←data_shape #.random.BoundedNumeric #.utils.NumericMinMax type
:If type=1287 ⋄ ⎕FR←1287 ⋄ :End ⍝ otherwise match might domain error
r,←(' {⍵}',test_comment,⍕type)({⍵}_Check_ spec)data
r,←('{⊂⍵}',test_comment,⍕type)({⊂⍵}_Check_ spec)data
r,←('{,⍵}',test_comment,⍕type)({,⍵}_Check_ spec)data
r,←('{⊢⍵}',test_comment,⍕type)({⊢⍵}_Check_ spec)data
:If type=1287 ⋄ ⎕FR←645 ⋄ :End
:End

⍝ character
:If classic
data←data_shape #.random.Character 82
r,←(' {⍵}',test_comment,'82')({⍵}_Check_ spec)data
r,←('{⊂⍵}',test_comment,'82')({⊂⍵}_Check_ spec)data
r,←('{,⍵}',test_comment,'82')({,⍵}_Check_ spec)data
r,←('{⊢⍵}',test_comment,'82')({⊢⍵}_Check_ spec)data
:Else
:For type :In 80 160 320
data←data_shape #.random.Character type
r,←(' {⍵}',test_comment,⍕type)({⍵}_Check_ spec)data
r,←('{⊂⍵}',test_comment,⍕type)({⊂⍵}_Check_ spec)data
r,←('{,⍵}',test_comment,⍕type)({,⍵}_Check_ spec)data
r,←('{⊢⍵}',test_comment,⍕type)({⊢⍵}_Check_ spec)data
:End
:End

⍝ pointer
data←,¨data
r,←(' {⍵}',test_comment,'326')({⍵}_Check_ spec)data
r,←('{⊂⍵}',test_comment,'326')({⊂⍵}_Check_ spec)data
r,←('{,⍵}',test_comment,'326')({,⍵}_Check_ spec)data
r,←('{⊢⍵}',test_comment,'326')({⊢⍵}_Check_ spec)data

⍝ stencil_identity_etc
⍝ ────────────────────

data_shape←1+?100 100
spec←(~2|⊢)⍛+?⌈0.5×data_shape
test_id←'stencil_identity_etc'
test_comment←'⌺',(⍕spec),'⊢',(⍕data_shape),'⍴ ⍝ type '

⍝ numeric
:For type :In 11 83 163 323 645 1287 1289
data←data_shape #.random.BoundedNumeric #.utils.NumericMinMax type
:If type=1287 ⋄ ⎕FR←1287 ⋄ :End ⍝ otherwise match might domain error
r,←(' {⍵}',test_comment,⍕type)({⍵}_Check_ spec)data
r,←('{⊂⍵}',test_comment,⍕type)({⊂⍵}_Check_ spec)data
r,←('{,⍵}',test_comment,⍕type)({,⍵}_Check_ spec)data
r,←('{⊢⍵}',test_comment,⍕type)({⊢⍵}_Check_ spec)data
:If type=1287 ⋄ ⎕FR←645 ⋄ :End
:End

⍝ character
:If classic
data←data_shape #.random.Character 82
r,←(' {⍵}',test_comment,'82')({⍵}_Check_ spec)data
r,←('{⊂⍵}',test_comment,'82')({⊂⍵}_Check_ spec)data
r,←('{,⍵}',test_comment,'82')({,⍵}_Check_ spec)data
r,←('{⊢⍵}',test_comment,'82')({⊢⍵}_Check_ spec)data
:Else
:For type :In 80 160 320
data←data_shape #.random.Character type
r,←(' {⍵}',test_comment,⍕type)({⍵}_Check_ spec)data
r,←('{⊂⍵}',test_comment,⍕type)({⊂⍵}_Check_ spec)data
r,←('{,⍵}',test_comment,⍕type)({,⍵}_Check_ spec)data
r,←('{⊢⍵}',test_comment,⍕type)({⊢⍵}_Check_ spec)data
:End
:End

⍝ pointer
data←,¨data
r,←(' {⍵}',test_comment,'326')({⍵}_Check_ spec)data
r,←('{⊂⍵}',test_comment,'326')({⊂⍵}_Check_ spec)data
r,←('{,⍵}',test_comment,'326')({,⍵}_Check_ spec)data
r,←('{⊢⍵}',test_comment,'326')({⊢⍵}_Check_ spec)data

⍝ stenvec_plus_slash
⍝ ──────────────────

data_shape←1+?1000
spec←1+?data_shape
test_id←'stenvec_plus_slash'
test_comment←'⌺',(⍕spec),'⊢',(⍕data_shape),'⍴ ⍝ type '

:For type :In 83 163 323 645 ⍝ only hits these types
data←data_shape #.random.BoundedNumeric #.utils.NumericMinMax type
r,←('{+/ ⍵}',test_comment,⍕type)({+/⍵}_Check_ spec)data
r,←('{+/,⍵}',test_comment,⍕type)({+/,⍵}_Check_ spec)data
:End

⍝ stencil_plus_slash_ravel
⍝ ────────────────────────

data_shape←1+?100 100
spec←(~2|⊢)⍛+?data_shape
test_id←'stencil_plus_slash_ravel'
test_comment←'⌺',(⍕spec),'⊢',(⍕data_shape),'⍴ ⍝ type '

:For type :In 11 83 163 323 645 ⍝ only hits these types
data←data_shape #.random.BoundedNumeric #.utils.NumericMinMax type
r,←('{+/,⍵}',test_comment,⍕type)({+/,⍵}_Check_ spec)data
:End

⍝ stencil_ip
⍝ ──────────

data_shape←1+?100 100
spec←(~2|⊢)⍛+?data_shape
test_id←'stencil_ip'
test_comment←'⌺',(⍕spec),'⊢',(⍕data_shape),'⍴ ⍝ type '

:For type :In 11 83 163 323 645
data←data_shape #.random.BoundedNumeric #.utils.NumericMinMax type
:For Atype :In 11 83 163 323 645
A←spec #.random.BoundedNumeric #.utils.NumericMinMax Atype
r,←('{+/,A× ⍵}',test_comment,(⍕type),' and A of type ',⍕Atype)({+/,A×⍵}_Check_ spec)data
A←((1+?10),spec)#.random.BoundedNumeric #.utils.NumericMinMax Atype
⍝ use ⎕CT←(quite big) since due to floating point operation ordering, the result will be a little different, and we need to pass the test anyway
r,←('{+/⍪A×⍤2⊢⍵}',test_comment,(⍕type),' and A of type ',⍕Atype)1(2*¯32)({+/⍪A×⍤2⊢⍵}_Check_ spec)data
:End ⋄ :End

⍝ stencil_ipcmp
⍝ ─────────────

E←0 ⍝ necessary to hit the optimisation (yes really)
data_shape←1+?100 100
spec←(~2|⊢)⍛+?data_shape
test_id←'stencil_ipcmp'
test_comment←'⌺',(⍕spec),'⊢',(⍕data_shape),'⍴ ⍝ type '
:For type :In 11 83 163 323 645
data←data_shape #.random.BoundedNumeric #.utils.NumericMinMax type
:For Atype :In 11 83 163 323 645
A←spec #.random.BoundedNumeric #.utils.NumericMinMax Atype
⍝ need ⎕CT←0 to hit the optimisation ──────────────────────────┐
r,←('{E<+/,A×⍵}',test_comment,(⍕type),' and A of type ',⍕Atype)0({E<+/,A×⍵}_Check_ spec)data
r,←('{E≤+/,A×⍵}',test_comment,(⍕type),' and A of type ',⍕Atype)0({E≤+/,A×⍵}_Check_ spec)data
r,←('{E=+/,A×⍵}',test_comment,(⍕type),' and A of type ',⍕Atype)0({E=+/,A×⍵}_Check_ spec)data
r,←('{E≥+/,A×⍵}',test_comment,(⍕type),' and A of type ',⍕Atype)0({E≥+/,A×⍵}_Check_ spec)data
r,←('{E>+/,A×⍵}',test_comment,(⍕type),' and A of type ',⍕Atype)0({E>+/,A×⍵}_Check_ spec)data
r,←('{E≠+/,A×⍵}',test_comment,(⍕type),' and A of type ',⍕Atype)0({E≠+/,A×⍵}_Check_ spec)data
r,←('{E=+/,A×⍵}',test_comment,(⍕type),' and A of type ',⍕Atype)0({E=+/,A×⍵}_Check_ spec)data
r,←('{0<+/,A×⍵}',test_comment,(⍕type),' and A of type ',⍕Atype)0({0<+/,A×⍵}_Check_ spec)data
r,←('{0≤+/,A×⍵}',test_comment,(⍕type),' and A of type ',⍕Atype)0({0≤+/,A×⍵}_Check_ spec)data
r,←('{0=+/,A×⍵}',test_comment,(⍕type),' and A of type ',⍕Atype)0({0=+/,A×⍵}_Check_ spec)data
r,←('{0≥+/,A×⍵}',test_comment,(⍕type),' and A of type ',⍕Atype)0({0≥+/,A×⍵}_Check_ spec)data
r,←('{0>+/,A×⍵}',test_comment,(⍕type),' and A of type ',⍕Atype)0({0>+/,A×⍵}_Check_ spec)data
r,←('{0≠+/,A×⍵}',test_comment,(⍕type),' and A of type ',⍕Atype)0({0≠+/,A×⍵}_Check_ spec)data
r,←('{0=+/,A×⍵}',test_comment,(⍕type),' and A of type ',⍕Atype)0({0=+/,A×⍵}_Check_ spec)data
A←((1+?10),spec)#.random.BoundedNumeric #.utils.NumericMinMax Atype
r,←('{E<+/⍪A×⍤2⊢⍵}',test_comment,(⍕type),' and A of type ',⍕Atype)0({E<+/⍪A×⍤2⊢⍵}_Check_ spec)data
r,←('{E≤+/⍪A×⍤2⊢⍵}',test_comment,(⍕type),' and A of type ',⍕Atype)0({E≤+/⍪A×⍤2⊢⍵}_Check_ spec)data
r,←('{E=+/⍪A×⍤2⊢⍵}',test_comment,(⍕type),' and A of type ',⍕Atype)0({E=+/⍪A×⍤2⊢⍵}_Check_ spec)data
r,←('{E≥+/⍪A×⍤2⊢⍵}',test_comment,(⍕type),' and A of type ',⍕Atype)0({E≥+/⍪A×⍤2⊢⍵}_Check_ spec)data
r,←('{E>+/⍪A×⍤2⊢⍵}',test_comment,(⍕type),' and A of type ',⍕Atype)0({E>+/⍪A×⍤2⊢⍵}_Check_ spec)data
r,←('{E≠+/⍪A×⍤2⊢⍵}',test_comment,(⍕type),' and A of type ',⍕Atype)0({E≠+/⍪A×⍤2⊢⍵}_Check_ spec)data
r,←('{E=+/⍪A×⍤2⊢⍵}',test_comment,(⍕type),' and A of type ',⍕Atype)0({E=+/⍪A×⍤2⊢⍵}_Check_ spec)data
r,←('{0<+/⍪A×⍤2⊢⍵}',test_comment,(⍕type),' and A of type ',⍕Atype)0({0<+/⍪A×⍤2⊢⍵}_Check_ spec)data
r,←('{0≤+/⍪A×⍤2⊢⍵}',test_comment,(⍕type),' and A of type ',⍕Atype)0({0≤+/⍪A×⍤2⊢⍵}_Check_ spec)data
r,←('{0=+/⍪A×⍤2⊢⍵}',test_comment,(⍕type),' and A of type ',⍕Atype)0({0=+/⍪A×⍤2⊢⍵}_Check_ spec)data
r,←('{0≥+/⍪A×⍤2⊢⍵}',test_comment,(⍕type),' and A of type ',⍕Atype)0({0≥+/⍪A×⍤2⊢⍵}_Check_ spec)data
r,←('{0>+/⍪A×⍤2⊢⍵}',test_comment,(⍕type),' and A of type ',⍕Atype)0({0>+/⍪A×⍤2⊢⍵}_Check_ spec)data
r,←('{0≠+/⍪A×⍤2⊢⍵}',test_comment,(⍕type),' and A of type ',⍕Atype)0({0≠+/⍪A×⍤2⊢⍵}_Check_ spec)data
r,←('{0=+/⍪A×⍤2⊢⍵}',test_comment,(⍕type),' and A of type ',⍕Atype)0({0=+/⍪A×⍤2⊢⍵}_Check_ spec)data
:End ⋄ :End

⍝ inline magic cases
⍝ ────── ───── ─────

test_id←'stencil inline magics'

⍝ vectors
data_shape←1+?100
spec←(~2|⊢)⍛+?data_shape
test_comment←'⌺(,',(⍕spec),')⊢',(⍕data_shape),'⍴ ⍝ type 11'
data←data_shape #.random.BoundedNumeric #.utils.NumericMinMax 11
r,←('{∧/ ⍵}',test_comment)({∧/⍵}_Check_ spec)data
r,←('{∨/ ⍵}',test_comment)({∨/⍵}_Check_ spec)data
r,←('{=/ ⍵}',test_comment)({=/⍵}_Check_ spec)data
r,←('{≠/ ⍵}',test_comment)({≠/⍵}_Check_ spec)data
r,←('{∧/,⍵}',test_comment)({∧/,⍵}_Check_ spec)data
r,←('{∨/,⍵}',test_comment)({∨/,⍵}_Check_ spec)data
r,←('{=/,⍵}',test_comment)({=/,⍵}_Check_ spec)data
r,←('{≠/,⍵}',test_comment)({≠/,⍵}_Check_ spec)data

⍝ matrices
data_shape←1+?100 100
spec←(~2|⊢)⍛+?data_shape
test_comment←'⌺',(⍕spec),'⊢',(⍕data_shape),'⍴ ⍝ type 11'
data←data_shape #.random.BoundedNumeric #.utils.NumericMinMax 11
r,←('{∧/,⍵}',test_comment)({∧/,⍵}_Check_ spec)data
r,←('{∨/,⍵}',test_comment)({∨/,⍵}_Check_ spec)data
r,←('{=/,⍵}',test_comment)({=/,⍵}_Check_ spec)data
r,←('{≠/,⍵}',test_comment)({≠/,⍵}_Check_ spec)data

⍝ BONUS TESTS
⍝ ───── ─────

⍝ execution order
⍝ ───────── ─────

i←⍬
{i,←1⊃⍵}_Stencil_ 3⊢3 1 4 1 5 9
j←⍬
{j,←1⊃⍵}⌺3⊢3 1 4 1 5 9
r,←('execution order' 'i←⍬ ⋄ {i,←1⊃⍵}⌺3⊢3 1 4 1 5 9 ⍝')Assert i≡j
:EndNamespace