#!/usr/bin/env python # tests the standard library of math functions import unittest import math import cmath import string import commands import types import testbase import absyn import codegen import fractparser import fractlexer import fsymbol import translate class Test(testbase.TestBase): def setUp(self): self.fakeNode = absyn.Empty(0) self.codegen = codegen.T(fsymbol.T()) self.parser = fractparser.parser def compileAndRun(self,c_code): cFileName = self.codegen.writeToTempFile(c_code,".cpp") oFileName = self.codegen.writeToTempFile("") #print c_code cmd = "g++ -Wall %s -o %s -Ic -lm" % (cFileName, oFileName) #print cmd (status,output) = commands.getstatusoutput(cmd) self.assertEqual(status,0,"C error:\n%s\nProgram:\n%s\n" % \ ( output,c_code)) #print "status: %s\noutput:\n%s" % (status, output) cmd = oFileName (status,output) = commands.getstatusoutput(cmd) self.assertEqual(status,0, "Runtime error:\n" + output) print "status: %s\noutput:\n%s" % (status, output) return output def makeC(self,user_preamble="", user_postamble=""): # construct a C stub for testing preamble = ''' #include #include #include "cmap.cpp" typedef enum { INT = 0, FLOAT = 1, GRADIENT = 2 } e_paramtype; struct s_param { e_paramtype t; int intval; double doubleval; void *gradient; }; typedef struct { struct s_param *p; } pf_fake; int main(){ struct s_param params[20]; int i = 0; ListColorMap *pMap = new ListColorMap(); pMap->init(2); pMap->set(0,0.0,255,0,0,0); pMap->set(1,1.0,0,255,0,0); for(i = 0; i < 20; ++i) { params[i].t = FLOAT; params[i].intval = 773; params[i].doubleval = 0.0; params[i].gradient = pMap; }; pf_fake t__f; t__f.p = params; pf_fake *t__pfo = &t__f; double pixel_re = 0.0, pixel_im = 0.0; double t__h_zwpixel_re = 0.0, t__h_zwpixel_im = 0.0; double t__h_color_re = 0.0; double t__h_color_i = 0.0; double t__h_color_j = 0.0; double t__h_color_k = 0.0; double inputs[] = { 0, 0, 0, 1, 1, 0, 1, 1, 3, 2, 1,-0.0, 0,-1, -3,2, -2,-2, -1,0 }; for(int i__ = 0; i < sizeof(inputs)/sizeof(double); i__ += 2) { ''' codegen_symbols = self.codegen.output_symbols(self.codegen,{}) decls = string.join(map(lambda x: x.format(), codegen_symbols),"\n") str_output = string.join(map(lambda x : x.format(), self.codegen.out),"\n") postamble = "}\nreturn 0;}\n" return string.join([preamble,decls,"\n", user_preamble,str_output,"\n", user_postamble,postamble],"") def inspect_bool(self,name): return "printf(\"%s = %%d\\n\", f%s);" % (name,name) def inspect_float(self,name): return "printf(\"%s = %%g\\n\", f%s);" % (name,name) def inspect_int(self,name): return "printf(\"%s = %%d\\n\", f%s);" % (name,name) def inspect_complex(self,name,prefix="f"): return "printf(\"%s = (%%g,%%g)\\n\", %s%s_re, %s%s_im);" % \ (name,prefix,name,prefix,name) def inspect_hyper(self,name,prefix="f"): return ("printf(\"%s = (%%g,%%g,%%g,%%g)\\n\"," + "%s%s_re, %s%s_i, %s%s_j, %s%s_k);") % \ (name,prefix,name,prefix,name,prefix,name,prefix,name) def inspect_color(self,name,prefix="f"): return self.inspect_hyper(name, prefix) def inspect_colors(self,namelist): return "".join(map(lambda x : self.inspect_color(x), namelist)) def predict(self,f,arg1=0,arg2=1): # compare our compiler results to Python stdlib try: x = "%.6g" % f(arg1) except ZeroDivisionError: x = "inf" try: y = "%.6g" % f(arg2) except ZeroDivisionError: y = "inf" return "(%s,%s)" % (x,y) def cpredict(self,f,arg=(1+0j)): try: z = f(arg) return "(%.6g,%.6g)" % (z.real,z.imag) except OverflowError: return "(inf,inf)" except ZeroDivisionError: return "(nan,nan)" def make_test(self,myfunc,pyfunc,val,n): codefrag = "ct_%s%d = %s((%d,%d))" % (myfunc, n, myfunc, val.real, val.imag) lookat = "ct_%s%d" % (myfunc, n) result = self.cpredict(pyfunc,val) return [ codefrag, lookat, result] def manufacture_tests(self,myfunc,pyfunc): vals = [ 0+0j, 0+1j, 1+0j, 1+1j, 3+2j, 1-0j, 0-1j, -3+2j, -2-2j, -1+0j ] return map(lambda (x,y) : self.make_test(myfunc,pyfunc,x,y), \ zip(vals,range(1,len(vals)))) def cotantests(self): def mycotan(z): return cmath.cos(z)/cmath.sin(z) tests = self.manufacture_tests("cotan",mycotan) # CONSIDER: comes out as -0,1.31304 in python, but +0 in C++ and gf4d # think Python's probably in error, but not 100% sure tests[6][2] = "(0,1.31304)" return tests def logtests(self): tests = self.manufacture_tests("log",cmath.log) tests[0][2] = "(-inf,0)" # log(0+0j) is overflow in python return tests def asintests(self): tests = self.manufacture_tests("asin",cmath.asin) # asin(x+0j) = (?,-0) in python, which is wrong tests[0][2] = "(0,0)" tests[2][2] = tests[5][2] = "(1.5708,0)" return tests def acostests(self): # work around buggy python acos tests = self.manufacture_tests("acos",cmath.acos) tests[0][2] = "(1.5708,0)" tests[2][2] = tests[5][2] = "(0,0)" return tests def atantests(self): tests = self.manufacture_tests("atan",cmath.atan) tests[1][2] = "(nan,nan)" tests[6][2] = "(nan,-inf)" # not really sure who's right on this return tests def atanhtests(self): tests = self.manufacture_tests("atanh",cmath.atanh) tests[2][2] = tests[5][2] = "(inf,0)" # Python overflows the whole number return tests def test_stdlib(self): '''This is the slowest test, due to how much compilation it does. Calls standard functions with a variety of values, checking that they produce the right answers''' # additions to python math stdlib def myfcotan(x): return math.cos(x)/math.sin(x) def myfcotanh(x): return math.cosh(x)/math.sinh(x) def mycotanh(z): return cmath.cosh(z)/cmath.sinh(z) def myasinh(z): return cmath.log(z + cmath.sqrt(z*z+1)) def myacosh(z): return cmath.log(z + cmath.sqrt(z-1) * cmath.sqrt(z+1)) def myctrunc(z): return complex(int(z.real),int(z.imag)) def mycfloor(z): return complex(math.floor(z.real),math.floor(z.imag)) def mycround(z): return complex(int(z.real+0.5),int(z.imag+0.5)) def mycceil(z): x = complex(math.ceil(z.real),math.ceil(z.imag)) return x def mycosxx(z): cosz = cmath.cos(z) return complex(cosz.real, -cosz.imag) def myczero(z): return complex(0,0) tests = [] # # code to run, var to inspect, result # [ "fm = (3.0 % 2.0, 3.1 % 1.5)","fm","(1,0.1)"], # [ "cj = conj(y)", "cj", "(1,-2)"], # [ "fl = flip(y)", "fl", "(2,1)"], # [ "ri = (imag(y),real(y))","ri", "(2,1)"], # [ "m = |y|","m","(5,0)"], # [ "t = (4,2) * (2,-1)", "t", "(10,0)"], # [ "d1 = y/(1,0)","d1","(1,2)"], # [ "d2 = y/y","d2","(1,0)"], # [ "d3 = (4,2)/y","d3","(1.6,-1.2)"], # [ "d4 = (2,1)/2","d4","(1,0.5)"], # [ "recip1 = recip((4,0))/recip(4)", "recip1", "(1,0)"], # [ "i = ident(y)","i","(1,2)"], # [ "a = (abs(4),abs(-4))","a","(4,4)"], # [ "a2 = abs((4,-4))","a2","(4,4)"], # [ "cab = (cabs((0,0)), cabs((3,4)))", "cab", "(0,5)"], # [ "sq = (sqrt(4),sqrt(2))", "sq", self.predict(math.sqrt,4,2)], # [ "l = (log(1),log(3))", "l", self.predict(math.log,1,3)], # [ "ex = (exp(1),exp(2))","ex", self.predict(math.exp,1,2)], # [ "p = (2^2,9^0.5)","p", "(4,3)"], # [ "pow1 = (1,0)^2","pow1", "(1,0)"], # [ "pow2 = (-2,-3)^7.5","pow2","(-13320.5,6986.17)"], # [ "pow3 = (-2,-3)^(1.5,-3.1)","pow3","(0.00507248,-0.00681128)"], # [ "pow4 = (0,0)^(1.5,-3.1)","pow4","(0,0)"], # [ "manh1 = (manhattanish(2.0,-1.0),manhattanish(0.1,-0.1))", # "manh1", "(1,0)"], # [ "manh2 = (manhattan(2.0,-1.5),manhattan(-2,1.7))", # "manh2", "(3.5,3.7)"], # [ "manh3 = (manhattanish2(2.0,-1.0),manhattanish2(0.1,-0.1))", # "manh3", "(25,0.0004)"], # [ "mx2 = (max2(2,-3),max2(-3,0))", "mx2", "(9,9)"], # [ "mn2 = (min2(-1,-2),min2(7,4))", "mn2", "(1,16)"], # [ "r2 = (real2(3,1),real2(-2.5,2))","r2","(9,6.25)"], # [ "i2 = (imag2(3,2),imag2(2,-0))", "i2", "(4,0)"], # [ "ftrunc1 = (trunc(0.5), trunc(0.4))", "ftrunc1", "(0,0)"], # [ "ftrunc2 = (trunc(-0.5), trunc(-0.4))", "ftrunc2", "(0,0)"], # [ "frnd1 = (round(0.5), round(0.4))", "frnd1", "(1,0)"], # [ "frnd2 = (round(-0.5), round(-0.4))", "frnd2", "(0,0)"], # [ "fceil1 = (ceil(0.5), ceil(0.4))", "fceil1", "(1,1)"], # [ "fceil2 = (ceil(-0.5), ceil(-0.4))", "fceil2", "(0,0)"], # [ "ffloor1 = (floor(0.5), floor(0.4))", "ffloor1", "(0,0)"], # [ "ffloor2 = (floor(-0.5), floor(-0.4))", "ffloor2", "(-1,-1)"], # [ "fzero = (zero(77),zero(-41.2))", "fzero", "(0,0)"], # # # trig functions # [ "t_sin = (sin(0),sin(1))","t_sin", self.predict(math.sin)], # [ "t_cos = (cos(0),cos(1))","t_cos", self.predict(math.cos)], # [ "t_tan = (tan(0),tan(1))","t_tan", self.predict(math.tan)], # [ "t_cotan = (cotan(0),cotan(1))","t_cotan", self.predict(myfcotan)], # [ "t_sinh = (sinh(0),sinh(1))","t_sinh", self.predict(math.sinh)], # [ "t_cosh = (cosh(0),cosh(1))","t_cosh", self.predict(math.cosh)], # [ "t_tanh = (tanh(0),tanh(1))","t_tanh", self.predict(math.tanh)], # [ "t_cotanh = (cotanh(0),cotanh(1))","t_cotanh", # self.predict(myfcotanh)], # # # inverse trig functions # [ "t_asin = (asin(0),asin(1))","t_asin", self.predict(math.asin)], # [ "t_acos = (acos(0),acos(1))","t_acos", self.predict(math.acos)], # [ "t_atan = (atan(0),atan(1))","t_atan", self.predict(math.atan)], # [ "t_atan2 = (atan2((1,1)),atan2((-1,-1)))", # "t_atan2", "(0.785398,-2.35619)"], # # these aren't in python stdlib, need to hard-code results # [ "t_asinh = (asinh(0),asinh(1))","t_asinh", "(0,0.881374)" ], # [ "t_acosh = (acosh(10),acosh(1))","t_acosh", "(2.99322,0)" ], # [ "t_atanh = (atanh(0),atanh(0.5))","t_atanh", "(0,0.549306)" ], #] #tests += self.manufacture_tests("sin",cmath.sin) #tests += self.manufacture_tests("cos",cmath.cos) #tests += self.manufacture_tests("cosxx", mycosxx) #tests += self.manufacture_tests("tan",cmath.tan) #tests += self.manufacture_tests("sinh",cmath.sinh) #tests += self.manufacture_tests("cosh",cmath.cosh) #tests += self.manufacture_tests("tanh",cmath.tanh) #tests += self.manufacture_tests("exp",cmath.exp) #tests += self.manufacture_tests("sqrt",cmath.sqrt) #tests += self.manufacture_tests("round",mycround) #tests += self.manufacture_tests("ceil",mycceil) #tests += self.manufacture_tests("floor",mycfloor) #tests += self.manufacture_tests("trunc",myctrunc) #tests += self.manufacture_tests("zero",myczero) #tests += self.cotantests() #tests += self.manufacture_tests("cotanh",mycotanh) #tests += self.logtests() # #tests += self.asintests() #tests += self.acostests() #tests += self.atantests() #tests += self.manufacture_tests("asinh",myasinh) tests += self.manufacture_tests("acosh",myacosh) #tests += self.atanhtests() # construct a formula calculating all of the above, # run it and compare results with expected values src = 't_c6{\ninit: y = (1,2)\n' + \ string.join(map(lambda x : x[0], tests),"\n") + "\n}" check = string.join(map(lambda x :self.inspect_complex(x[1]),tests),"\n") exp = map(lambda x : "%s = %s" % (x[1],x[2]), tests) self.assertCSays(src,"init",check,exp) def assertCSays(self,source,section,check,result,dump=None): asm = self.sourceToAsm(source,section,dump) postamble = "t__end_f%s:\n%s\n" % (section,check) c_code = self.makeC("", postamble) output = self.compileAndRun(c_code) if isinstance(result,types.ListType): outputs = string.split(output,"\n") for (exp,res) in zip(result,outputs): self.assertEqual(exp,res) else: self.assertEqual(output,result) def translate(self,s,dump=None): fractlexer.lexer.lineno = 1 pt = self.parser.parse(s) #print pt.pretty() t = translate.T(pt.children[0],dump) #print t.pretty() self.assertNoErrors(t) self.codegen = codegen.T(t.symbols,dump) return t def translatecf(self,s,name,dump=None): fractlexer.lexer.lineno = 1 pt = self.parser.parse(s) #print pt.pretty() t = translate.ColorFunc(pt.children[0],name,dump) #print t.pretty() self.assertNoErrors(t) return t def sourceToAsm(self,s,section,dump=None): t = self.translate(s,dump) self.codegen.generate_all_code(t.canon_sections[section]) if dump != None and dump.get("dumpAsm") == 1: self.printAsm() return self.codegen.out def suite(): return unittest.makeSuite(Test,'test') if __name__ == '__main__': unittest.main(defaultTest='suite')