-
-
Notifications
You must be signed in to change notification settings - Fork 41
Math AccuracyFramework
by subclassing Accuracy
one can easily write tests for the numerical accuracy of methods. one makes checkXXXX
methods that return a number or a sequencableCollection of numbers and in initializeXXXX
one sets the correct result with self result:
. then one does a run
, and afterwards one can look at the results with report
or dataTree
. a little example, lets say you want to test sin
. first you make a subclass of Accuracy
:
Accuracy subclass: #AccuracySine
you make a test. the test has always to begin with check:
AccuracySine>>checkSine
^Float pi sin
for the expected result you have to make an initializeXXXX
method, where XXXX is exactly the same string as in checkXXXX
AccuracySine>>initializeSine
self result: 0
now you can run the test:
a:=AccuracySine new.
a run." -->'Report for: AccuracySine
test Sine
expected result: 0.0
result: 1.22e-16 error: -1.0e2% '"
now you probably want to test this function for different numbers, lets say for 0 to: Float pi by: Float pi /2
. you change initializeSine
:
AccuracySine>>initializeSine
self argument: (0 to: Float pi by: Float pi /2).
self result: #((0)(1)(0))
you see that i put every single result in its own Array. This is necessary since checkXXXX
may also return an Array of numbers instead of a single number and in this case the program would have expected that checkSine
returns #(0 1 0)
if i wouldnt have done that. similarly each argument should also be put in an array, since checkXXXX
can also get and use several arguments in an array. but in this simple case i omitted that, as it works also.
you have also to change checkSine
:
AccuracySine>>checkSine
^self argument sin
now you have to make a new AccuracySine
, since the initializeXXXX
methods are called by initialize
:
a:=AccuracySine new.
a run. "--> 'Report for: AccuracySine
test Sine
expected result: 0.0 arguments: 0.0
result: 0.0 error: 0.0%
expected result: 1.0 arguments: 1.57
result: 1.0 error: 0.0%
expected result: 0.0 arguments: 3.14
result: 1.22e-16 error: -1.0e2%'"
after you have run
AccuracySine
you can get the the stored results with dataTree
which returns a KeyedTree
:
a dataTree formattedText . "-->
'''data'' : ''names''
''iterations'' : 1
''names''
''Sine''
#(0)
''arguments'' : 0.0
''data'' : #(#(0.0))
''error'' : 0
''expected result'' : 0
''result'' : 0.0
''type'' : ''result''
#(0 ''another'')
''arguments'' : 3.141592653589793
''data'' : #(#(1.2246063538223773e-16))
''error'' : -100.0
''expected result'' : 0
''result'' : 1.2246063538223773e-16
''type'' : ''result''
#(1)
''arguments'' : 1.5707963267948966
''data'' : #(#(1.0))
''error'' : 0.0
''expected result'' : 1
''result'' : 1.0
''type'' : ''result''
''data'' : #(#(0) #(1) #(0))
''type'' : ''expected result''
''data'' : an OrderedCollection(''Sine'')
''type'' : ''names''
''type'' : #AccuracySine'"
that format doesnt look very simple, but otoh one can retrieve all the data.
you can also set parameters with self parameter:
and retrieve them in checkXXXX
with self parameter
. they are essentially the same as arguments, but one has not to change the results as the program simply iterates over all the arguments and results with different parameters.
one can also set parameters, arguments or results in initialize
(one has always to call super initialize
first) and this serves as default settings if they are not reset in a initializeXXXX
method.
for randomized algorithms one can also set self iterations:
but that should be done in initialize
, as this is not stored separately for each checkXXXX. then the mean result will be used.
for a further example one can look at AccuracyTestExample
and its methods: initialize
,initializeAaa
to initializeFff
and checkAaa
to checkFff
and see its output, when one prints a run
and a dataTree formattedText
:
a := AccuracyTestExample new.
"you could do this to see how this changes the output:"
a iterations:2.
"then you print this:"
a run.
"and then you can print that:"
a dataTree formattedText .
of course you can make several different checkXXXX
methods. you set results, arguments and parameters exactly as described and this works as the setters/getters automatically store/retrieve them under their XXXX name. i guess this is a somewhat bad smalltalk style since the setters/getters essentially behave differently depending on from where they are called. otoh this simplifies its use, when one gets used to it, and the persistence of these variables makes it simple to hook in optimizers and such things, but this means that you can use those setters/getters essentially only in the checkXXXX
and initializeXXXX
methods (or in initialize
).