Skip to content

Math AccuracyFramework

Serge Stinckwich edited this page Jun 26, 2018 · 1 revision

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).