# easy and fast reverse mode autodiff # # author: justin domke # history: # May 26-June 2009, First version # # Aug 20-25 2009 next hacking spurt (converted to outputting # bytecode with a c interpreter, along with lots of other # efficiencies, then converted back to outputting c code after # discovering that tcc exists and can compile source codes 100x # larger than gcc can. Now have about a 2x-10x speed advantage # over pycppad, depending on the problem and the compiler used. # (If we could somehow compile 600mb source code at O3 we would # be in business!) # # Nov 29, 2009 released # # THIS CODE IS SUBJECT TO THE MIT LICENSE: # http://www.opensource.org/licenses/mit-license.php # # todo: # convert code to output integers in hexadecimal to save space!? import numpy import array as a from numpy import * import copy from scipy.weave import inline from scipy.weave import converters import os import time NOOP = 0 MULT = 1 MULT_N1 = 2 MULT_N1N2 = 3 MULT_N2N1 = 4 MULT___N1 = 5 MULT_REP = 6 ADD = 7 ADD_N1 = 8 ADD_N1N2 = 9 ADD_N2N1 = 10 ADD___N1 = 11 DIV = 12 DIV_N1 = 13 DIV_N1N2 = 14 DIV_N2N1 = 15 DIV___N1 = 16 SUB = 17 SUB_N1 = 18 SUB_N1N2 = 19 SUB_N2N1 = 20 SUB___N1 = 21 EXP = 22 LOG = 23 NOID = 987654321 # todo # fix the insanity with the id numbers (not an issue!?) # full id renumbering (prevent unnecessary allocations # and allow user to declare arrays in any order.) # todo # why need to init D to zero!? import sys # this is a bad, bad, bad idea sys.setrecursionlimit(1000000) def independent(*args): eargs = [] Atype = type(zeros(0)) dtype = type(0.123) for n in range(len(args)): A = args[n] if type(A)==Atype: eargs.append(getmat(A.shape)) elif type(A)==dtype: eargs.append(etree()) else: eargs.append(independent(*A)) return eargs def compile_fun(fun,*args): # first, make all the inputs into etrees reset() # coerce inputs to be floats args = copy.deepcopy(list(args)) testmat = numpy.zeros(1,dtype=float) mattype = type(testmat) matdtype = testmat.dtype for i in range(len(args)): if type(args[i]) == mattype: if args[i].dtype != matdtype: args[i] = numpy.array(args[i],dtype=float) else: args[i] = float(args[i]) outargs = copy.deepcopy(list(args)) eargs = independent(*args) # now, run the function on the etree types z = fun(*eargs) f = z.compile(outargs,*eargs) # call the function once to force weave to actually compile it f(*args) return f def getmat(N): A = numpy.zeros(N,dtype=object) for position,element in numpy.ndenumerate(A): A[position] = etree() return A def reset(): etree.id = 0 etree.ops = a.array('B') etree.arg1 = a.array('l') etree.arg2 = a.array('l') def gencode(ops,arg1s,arg2s,froz): nops = len(arg1s) # run some inline c code that will generate the workhorse c code writercode = "const int NOOP = %d;\n" % NOOP writercode += "const int MULT = %d;\n" % MULT writercode += "const int ADD = %d;\n" % ADD writercode += "const int SUB = %d;\n" % SUB writercode += "const int DIV = %d;\n" % DIV writercode += "const int EXP = %d;\n" % EXP writercode += "const int LOG = %d;\n" % LOG writercode += "const int nops = %d;\n" % nops writercode += "int i;\n" # theoretically shouldn't need to cast to (int*) writercode += "int *touched = (int*)malloc(nops * sizeof(int));\n" writercode += "for(i=0; i=0; i--){ // if no-one has updated you, you have no impact if(touched[i]==0){ //fprintf(file,"D[%d] = 0.0;\\n",i); continue; } sprintf(arg1str,"A[%d]", arg1s[i]); sprintf(arg2str,"A[%d]", arg2s[i]); if(arg1s[i]<0) sprintf(arg1str,"%f",froz[-arg1s[i]-1]); if(arg2s[i]<0) sprintf(arg2str,"%f",froz[-arg2s[i]-1]); sprintf(arg1sym,"="); if(ops[i] > NOOP && arg1s[i] >= 0){ if(touched[arg1s[i]]>0) sprintf(arg1sym,"+="); else touched[arg1s[i]] = 1; } sprintf(arg2sym,"="); if(ops[i] > NOOP && ops[i] < EXP && arg2s[i] >= 0){ //printf("t: %d \\n", touched[arg2s[i]]); if(touched[arg2s[i]]>0) sprintf(arg2sym,"+="); else touched[arg2s[i]] = 1; } if(ops[i]==MULT){ if(arg1s[i]>= 0) fprintf(file,"D[%d] %s D[%d] * %s;\\n", arg1s[i],arg1sym,i,arg2str); // HERE! if(arg2s[i] >= 0) fprintf(file,"D[%d] %s D[%d] * %s;\\n", arg2s[i],arg2sym,i,arg1str); } else if(ops[i]==ADD){ if(arg1s[i] >= 0) fprintf(file,"D[%d] %s D[%d];\\n", arg1s[i],arg1sym,i); if(arg2s[i] >= 0) fprintf(file,"D[%d] %s D[%d];\\n", arg2s[i],arg2sym,i); } else if(ops[i]==DIV){ if(arg1s[i] >= 0) fprintf(file,"D[%d] %s D[%d] / %s;\\n", arg1s[i],arg1sym,i,arg2str); if(arg2s[i] >= 0) fprintf(file,"D[%d] %s -D[%d] * %s / (%s*%s);\\n", arg2s[i],arg2sym,i,arg1str,arg2str,arg2str); } else if(ops[i]==SUB){ if(arg1s[i] >= 0) fprintf(file,"D[%d] %s D[%d];\\n", arg1s[i],arg1sym,i); if(arg2s[i] >= 0) fprintf(file,"D[%d] %s -D[%d];\\n", arg2s[i],arg2sym,i); } else if(ops[i]==EXP){ if(arg1s[i] >= 0) fprintf(file,"D[%d] %s D[%d] * A[%d];\\n", arg1s[i],arg1sym,i,i); } else if(ops[i]==LOG){ if(arg1s[i] >= 0) fprintf(file,"D[%d] %s D[%d] / A[%d];\\n", arg1s[i],arg1sym,i,arg1s[i]); } } free(touched); fprintf(file,"}\\n"); fclose(file); ''' #print writercode inline(writercode,['ops','arg1s','arg2s','froz'], extra_compile_args=['-w']) class etree: id = 0 ops = a.array('B') arg1 = a.array('l') arg2 = a.array('l') froz = a.array('f') #mark = a.array('b') def __init__(self,doinits=True): self.id = etree.id etree.id += 1 #self.mark = 0 #self.op = NOOP if doinits: etree.ops .append(NOOP) etree.arg1.append(NOID) etree.arg2.append(NOID) def __str__(self): return "etree" def __add__(self,other): new_etree = etree(False) etree.ops.append(ADD) etree.arg1.append(self.id) if hasattr(other,'id'): etree.arg2.append(other.id) else: etree.froz.append(other) etree.arg2.append(-len(etree.froz)) return new_etree __radd__ = __add__ def __mul__(self,other): new_etree = etree(False) etree.ops .append(MULT) etree.arg1.append(self.id) if hasattr(other,'id'): etree.arg2.append(other.id) else: etree.froz.append(other) etree.arg2.append(-len(etree.froz)) return new_etree __rmul__ = __mul__ def __sub__(self,other): new_etree = etree(False) etree.ops .append(SUB) etree.arg1.append(self.id) if hasattr(other,'id'): etree.arg2.append(other.id) else: etree.froz.append(other) etree.arg2.append(-len(etree.froz)) return new_etree def __rsub__(self,other): new_etree = etree(False) etree.ops.append(SUB) if hasattr(other,'id'): etree.arg1.append(other.id) else: etree.froz.append(float(other)) etree.arg1.append(-len(etree.froz)) etree.arg2.append(self.id) return new_etree def __div__(self,other): new_etree = etree(False) etree.ops .append(DIV) etree.arg1.append(self.id) if hasattr(other,'id'): etree.arg2.append(other.id) else: etree.froz.append(other) etree.arg2.append(-len(etree.froz)) return new_etree def __rdiv__(self,other): new_etree = etree(False) etree.ops.append(DIV) if hasattr(other,'id'): etree.arg1.append(other.id) else: etree.froz.append(other) etree.arg1.append(-len(etree.froz)) etree.arg2.append(self.id) return new_etree def __neg__(self): return self*(-1) def exp(self): new_etree = etree(False) etree.ops .append(EXP) etree.arg1.append(self.id) etree.arg2.append(NOID) return new_etree def log(self): new_etree = etree(False) etree.ops .append(LOG) etree.arg1.append(self.id) etree.arg2.append(NOID) return new_etree def compile(self,outargs,*args): mattype = type(numpy.zeros(1)) N = self.id+1 graphsize = self.id+1 # this will get increased to include frozen-in data # at the end #frozens = array(frozendata,dtype=float) print "generating init code" initcode = '//printf("how are you my children\\n");\n' initcode += "const int graphsize =%d;\n" % graphsize initcode += "const int nops = graphsize\n;\n" initcode += "int c2d = sizeof(double)/sizeof(char);\n" initcode += "double* A = new double[graphsize];\n" initcode += "double* D = new double[graphsize];\n" #initcode += "for(int i=0; i