712 lines
18 KiB
Python
712 lines
18 KiB
Python
from lark import Lark, Visitor, Transformer, v_args, Token
|
|
|
|
|
|
class CollectDefinitions(Visitor):
|
|
|
|
def __init__ (self, primary, derived, averaged):
|
|
self.primary = primary
|
|
self.derived = derived
|
|
self.averaged = averaged
|
|
|
|
def varlist(self, tree):
|
|
for v in tree.children:
|
|
self.primary.add(v.value)
|
|
self.derived[v.value] = PrimaryField(v.value, self.derived)
|
|
|
|
def assign_var (self, tree):
|
|
lval, rval = tree.children
|
|
|
|
if lval.value in self.derived:
|
|
raise StandardError("duplicate definition of " + lval)
|
|
self.derived[lval.value] = Field(lval.value, rval, self.derived)
|
|
|
|
def assign_avg_var (self, tree):
|
|
w = tree.children[0]
|
|
targets = tree.children[1:]
|
|
|
|
try:
|
|
self.averaged[w.children[0].value] = set([x.value for x in targets])
|
|
except IndexError:
|
|
self.averaged[None] = set([x.value for x in targets])
|
|
|
|
|
|
class ExpInspector(Visitor):
|
|
def __init__(self):
|
|
self.fluctuation = False
|
|
self.dep = set([])
|
|
self.deriv = set([])
|
|
|
|
@classmethod
|
|
def inspect(cls, tree):
|
|
self = cls()
|
|
return self(tree)
|
|
|
|
def __call__(self, tree):
|
|
self.visit(tree)
|
|
return self.fluctuation, self.dep, self.deriv
|
|
|
|
def fluc(self, tree):
|
|
self.fluctuation = True
|
|
self.dep.add(tree.children[0].value)
|
|
|
|
def var(self, tree):
|
|
self.dep.add(tree.children[0].value)
|
|
|
|
def dnx (self, tree):
|
|
op, v = tree.children
|
|
deriv = "{}_{}".format(op.data, v.value)
|
|
self.dep.add(deriv)
|
|
self.deriv.add((op.data, v.value))
|
|
|
|
|
|
@v_args(inline=True) # Affects the signatures of the methods
|
|
class ExpToCode(Transformer):
|
|
|
|
def __init__(self, fdict):
|
|
self.fdict = fdict
|
|
|
|
def number(self, numeral):
|
|
return str(float(numeral))
|
|
|
|
def env(self, name):
|
|
return name.value
|
|
|
|
def var(self, name):
|
|
try:
|
|
arrname = self.fdict[name.value].array
|
|
except KeyError:
|
|
arrname = name.value
|
|
|
|
return arrname + "(i,j,k)"
|
|
|
|
def fluc(self, name):
|
|
try:
|
|
arrname = self.fdict[name.value].array
|
|
except KeyError:
|
|
arrname = name.value
|
|
|
|
fmt = "({0}(i,j,k) - {{0}}avg_{1}(i))"
|
|
return fmt.format(arrname, name.value)
|
|
|
|
def dnx (self, partial, b):
|
|
signature = "{}_{}".format(partial.data, b.value)
|
|
try:
|
|
arrname = self.fdict[signature].array
|
|
except KeyError:
|
|
arrname = signature
|
|
|
|
return arrname + "(i,j,k)"
|
|
|
|
def icall (self, a, b):
|
|
if a.data == "sqr":
|
|
fcode = "(({0})*({0}))".format(b)
|
|
elif a.data == "pow3":
|
|
fcode = "(({0})*({0})*({0}))".format(b)
|
|
else:
|
|
fcode = "({0})".format(b)
|
|
return fcode
|
|
|
|
def fcall (self, a, b):
|
|
fcode = "( {} ( {} ) )".format(a, b)
|
|
return fcode
|
|
|
|
def neg(self, b):
|
|
fcode = "( - {} )".format(b)
|
|
return fcode
|
|
|
|
def add(self, a, b):
|
|
fcode = "( {} + {} )".format(a, b)
|
|
return fcode
|
|
|
|
def sub(self, a, b):
|
|
fcode = "( {} - {} )".format(a, b)
|
|
return fcode
|
|
|
|
def mul(self, a, b):
|
|
fcode = "( {} * {} )".format(a, b)
|
|
return fcode
|
|
|
|
def div(self, a, b):
|
|
fcode = "( {} / {} )".format(a, b)
|
|
return fcode
|
|
|
|
log = lambda self : "log"
|
|
exp = lambda self : "exp"
|
|
sqrt = lambda self : "sqrt"
|
|
abs = lambda self : "dabs"
|
|
rxn_rate = lambda self : "rxn_rate"
|
|
|
|
|
|
|
|
|
|
class FieldBase (object):
|
|
def __init__ (self, name, fdict):
|
|
self.name = name
|
|
self.array = name
|
|
self.dep = set([])
|
|
self.fluc = False
|
|
self.prime = False
|
|
self.fdict = fdict
|
|
self.shape = "nxp,nyp,nzp"
|
|
self.dim = ":,:,:"
|
|
|
|
def depends_on (self, a):
|
|
return (a in self.dep)
|
|
|
|
def is_primary (self):
|
|
return self.prime
|
|
|
|
def not_primary (self):
|
|
return not self.prime
|
|
|
|
def is_fluctuation (self):
|
|
return self.fluc
|
|
|
|
def __repr__ (self):
|
|
return self.name
|
|
|
|
def checkFluctuation (self):
|
|
|
|
fset = set([])
|
|
|
|
for d in map(self.fdict.get, self.dep):
|
|
fset.update(d.checkFluctuation())
|
|
|
|
if self.is_fluctuation() or len(fset) > 0:
|
|
fset.add(self.name)
|
|
|
|
return fset
|
|
|
|
def depClosure (self):
|
|
|
|
fset = set(self.dep)
|
|
|
|
for d in self.dep:
|
|
fset.update(self.fdict[d].depClosure())
|
|
|
|
return fset
|
|
|
|
|
|
def code_decl (self):
|
|
real_array_decl = "real*8, allocatable, dimension({1}) :: {0}"
|
|
return real_array_decl.format(self.name, self.dim)
|
|
|
|
def code_alloc (self):
|
|
real_array_alloc = "allocate({0}({1}), stat=ierr) ; {0} = 0."
|
|
return real_array_alloc.format(self.name, self.shape)
|
|
|
|
def code_free (self):
|
|
real_array_free = "deallocate({})"
|
|
return real_array_free.format(self.name)
|
|
|
|
|
|
class Field (FieldBase):
|
|
|
|
def __init__ (self, name, exp, fdict):
|
|
super(Field,self).__init__(name, fdict)
|
|
self.exp = exp
|
|
self.fluc, self.dep, self.derivs = ExpInspector.inspect(exp)
|
|
self.comment = ExpToCode(self.fdict).transform(self.exp)
|
|
|
|
'''
|
|
for a in self.dep:
|
|
if a not in self.fdict:
|
|
raise StandardError(a + " is not defined")
|
|
'''
|
|
|
|
def code (self, alloc=None):
|
|
real_array_loop = """
|
|
! {1}
|
|
do k = 1, nzp
|
|
do j = 1, nyp
|
|
do i = 1, nxp
|
|
{0[0]}(i,j,k) = {0[1]}
|
|
end do
|
|
end do
|
|
end do
|
|
"""
|
|
self.array = alloc[self.name] if alloc else self.name
|
|
|
|
rhs = ExpToCode(self.fdict).transform(self.exp)
|
|
return real_array_loop.format((self.array, rhs), self.comment)
|
|
|
|
|
|
class FluctuationField (FieldBase):
|
|
|
|
def __init__ (self, w, field, fset, fdict):
|
|
|
|
super(FluctuationField,self).__init__(self.id(w,field), fdict)
|
|
|
|
if w is not None:
|
|
self.w = w + "_"
|
|
else:
|
|
self.w = ""
|
|
|
|
self.field = fdict[field]
|
|
self.dep = self.field.dep - fset
|
|
for df in self.field.dep & fset:
|
|
self.dep.add(self.id(w,df))
|
|
|
|
self.comment = ExpToCode(self.fdict).transform(self.field.exp)
|
|
|
|
if self.field.is_fluctuation():
|
|
self.comment = self.comment.format(self.w)
|
|
|
|
|
|
def code (self, alloc=None):
|
|
real_array_loop = """
|
|
! {1}
|
|
do k = 1, nzp
|
|
do j = 1, nyp
|
|
do i = 1, nxp
|
|
{0[0]}(i,j,k) = {0[1]}
|
|
end do
|
|
end do
|
|
end do
|
|
"""
|
|
self.array = alloc[self.name] if alloc else self.name
|
|
|
|
rhs = ExpToCode(self.fdict).transform(self.field.exp)
|
|
|
|
if self.field.is_fluctuation():
|
|
rhs = rhs.format(self.w)
|
|
|
|
return real_array_loop.format((self.array, rhs), self.comment)
|
|
|
|
@classmethod
|
|
def id (cls, w, field):
|
|
if w:
|
|
name = "{}____{}_avg".format(field, w)
|
|
else:
|
|
name = "{}____avg".format(field)
|
|
return name
|
|
|
|
|
|
class PrimaryField (FieldBase):
|
|
|
|
def __init__ (self, name, fdict):
|
|
super(PrimaryField,self).__init__(name, fdict)
|
|
self.derivs = set([])
|
|
self.prime = True
|
|
|
|
def code (self, alloc=None):
|
|
return "! {} is read from file".format(self.name)
|
|
|
|
def code_decl (self):
|
|
return "! {} is read from file".format(self.name)
|
|
|
|
def code_alloc (self):
|
|
return "! {} is read from file".format(self.name)
|
|
|
|
def code_free (self):
|
|
return "! {} is read from file".format(self.name)
|
|
|
|
|
|
class DerivedField (FieldBase):
|
|
|
|
def __init__ (self, op, v, fdict):
|
|
name = "{}_{}".format(op, v)
|
|
super(DerivedField,self).__init__(name, fdict)
|
|
self.op = op
|
|
self.v = v
|
|
self.dep = set([v])
|
|
|
|
def code (self, alloc=None):
|
|
self.array = alloc[self.name] if alloc else self.name
|
|
varray = alloc[self.v] if alloc else self.v
|
|
return "call {0} ( {2}, {1} )".format(self.op, varray, self.array)
|
|
|
|
|
|
class AveragedField (FieldBase):
|
|
|
|
@classmethod
|
|
def id (cls, w, tgt):
|
|
if w:
|
|
return "{}_avg_{}".format(w, tgt)
|
|
else:
|
|
return "avg_{}".format(tgt)
|
|
|
|
|
|
def __init__ (self, w, tgt, fdict):
|
|
|
|
name = self.id(w,tgt)
|
|
super(AveragedField,self).__init__(name, fdict)
|
|
self.shape = "nxp"
|
|
self.dim = ":"
|
|
|
|
tfield = fdict[tgt]
|
|
self.fset = tfield.checkFluctuation()
|
|
|
|
if not self.fset:
|
|
self.tgt = tgt
|
|
self.dep.add(tgt)
|
|
else:
|
|
ftgt = FluctuationField.id(w,tgt)
|
|
self.tgt = ftgt
|
|
self.dep.add(ftgt)
|
|
|
|
self.weighted = w
|
|
if w:
|
|
self.w = fdict[w]
|
|
self.dep.add(w)
|
|
|
|
def code (self, alloc=None):
|
|
|
|
avg_array_sum = """
|
|
do k = 1, nzp
|
|
do j = 1, nyp
|
|
do i = 1, nxp
|
|
{0}(i) = {0}(i) + {1}
|
|
end do
|
|
end do
|
|
end do
|
|
"""
|
|
arrname = self.fdict[self.tgt].array + "(i,j,k)"
|
|
if self.weighted is not None:
|
|
arrname = arrname + " * " + self.w.array + "(i,j,k)"
|
|
|
|
return avg_array_sum.format(self.name, arrname)
|
|
|
|
|
|
def code_avg (self):
|
|
|
|
avg_array_divide = """
|
|
call MPI_ALLREDUCE(MPI_IN_PLACE, {0}, nxp, MPI_REAL8, MPI_SUM, MPI_COMM_TASK, mpi_err)
|
|
|
|
{0} = {0} {1} / denum
|
|
"""
|
|
meanw = "/ avg_{}"
|
|
dWeight = (meanw.format(self.weighted) if self.weighted else "")
|
|
|
|
return avg_array_divide.format(self.name, dWeight)
|
|
|
|
def isWeighted (self):
|
|
return self.weighted is not None
|
|
|
|
def pass1 (self):
|
|
return not self.pass2()
|
|
|
|
def pass2 (self):
|
|
return len(self.fset) > 0
|
|
|
|
|
|
class Stage1():
|
|
''' conversion from tree to python data '''
|
|
|
|
def __init__ (self, raw_tree):
|
|
self.primary = set([])
|
|
self.derived = {}
|
|
self.averaged = {}
|
|
|
|
# Construct Field objects
|
|
CollectDefinitions( self.primary, self.derived, self.averaged ).visit(raw_tree)
|
|
|
|
def __repr__ (self):
|
|
return "\n".join(map(str, [self.primary, self.derived, self.averaged]))
|
|
|
|
|
|
class Stage2():
|
|
''' expand derivatives and averages'''
|
|
|
|
def __init__ (self, src):
|
|
self.src = src
|
|
self.primary = src.primary
|
|
self.derived = src.derived
|
|
self.derivative = {}
|
|
self.averaged = {}
|
|
|
|
# Construct Derivative Field objects
|
|
|
|
dset = set([])
|
|
for k, v in self.derived.iteritems():
|
|
dset.update(v.derivs)
|
|
|
|
for tup in dset:
|
|
a = DerivedField(tup[0], tup[1], self.derived)
|
|
self.derived[a.name] = a
|
|
self.derivative[tup] = a
|
|
|
|
# Construct Averaged Field objects
|
|
|
|
for w, tgts in src.averaged.iteritems():
|
|
for t in tgts:
|
|
a = AveragedField(w, t, self.derived)
|
|
self.averaged[a.name] = a
|
|
for ff in a.fset:
|
|
b = FluctuationField(w, ff, a.fset, self.derived)
|
|
self.derived[b.name] = b
|
|
|
|
def __repr__ (self):
|
|
return "\n".join(map(str, [self.derived, self.derivative, self.averaged]))
|
|
|
|
def dependency (self):
|
|
dgraph = {}
|
|
|
|
for k,v in self.derived.iteritems():
|
|
dgraph[k] = v.dep
|
|
|
|
for k,v in self.averaged.iteritems():
|
|
dgraph[k] = v.dep
|
|
|
|
return dgraph
|
|
|
|
|
|
class Stage3():
|
|
''' calculate execution order '''
|
|
|
|
def __init__ (self, src):
|
|
self.src = src
|
|
self.primary = src.primary
|
|
self.derived = src.derived
|
|
self.averaged = src.averaged
|
|
self.dependency = src.dependency()
|
|
|
|
assert set(self.derived.keys()).isdisjoint(self.averaged.keys())
|
|
|
|
|
|
self.avg1 = set(filter(AveragedField.pass1, self.averaged.values()))
|
|
self.avg2 = set(filter(AveragedField.pass2, self.averaged.values()))
|
|
|
|
pass1calc = set(map(repr, self.avg1))
|
|
map (pass1calc.update, map(AveragedField.depClosure, self.avg1))
|
|
self.pass1 = self.sort_vars(self.dependency, pass1calc - self.primary)
|
|
|
|
# self.sort_vars(self.dependency, pass1calc - self.primary -set(map(repr, self.avg1)))
|
|
|
|
pass2calc = set(map(repr, self.avg2))
|
|
map (pass2calc.update, map(AveragedField.depClosure, self.avg2))
|
|
self.pass2 = self.sort_vars(self.dependency, pass2calc - self.primary)
|
|
|
|
|
|
def __repr__ (self):
|
|
return "\n".join(map(str, [self.pass1, self.pass2]))
|
|
|
|
|
|
def sort_vars (self, dependency, group):
|
|
order = []
|
|
remain = list(group)
|
|
remain.sort()
|
|
|
|
while len(remain) > 0:
|
|
for v in remain:
|
|
if dependency[v].isdisjoint(remain):
|
|
order.append(v)
|
|
remain.remove(v)
|
|
|
|
return order
|
|
|
|
|
|
def print_program (self):
|
|
|
|
allvar = dict(self.derived)
|
|
allvar.update(self.averaged)
|
|
|
|
decl = "\n".join(allvar[v].code_decl() for v in set(self.pass1+self.pass2))
|
|
alloc = "\n".join(allvar[v].code_alloc() for v in set(self.pass1+self.pass2))
|
|
free = "\n".join(allvar[v].code_free() for v in set(self.pass1+self.pass2))
|
|
|
|
calc1 = "\n".join(allvar[v].code() for v in self.pass1)
|
|
calc2 = "\n".join(allvar[v].code() for v in self.pass2)
|
|
|
|
set1 = [a.name for a in filter(AveragedField.pass1, self.averaged.values())]
|
|
set2 = [a.name for a in filter(AveragedField.pass2, self.averaged.values())]
|
|
|
|
set1.sort()
|
|
set2.sort()
|
|
|
|
avg1 = "\n".join(allvar[v].code_avg() for v in set1)
|
|
avg2 = "\n".join(allvar[v].code_avg() for v in set2)
|
|
|
|
hfmt = 'character (len = *), parameter :: output_header="{}"'
|
|
declh = hfmt.format(" ".join(["x"] + set1 + set2))
|
|
|
|
avg_array_write = '''
|
|
integer :: i
|
|
|
|
open (200, file="qEdge_X.dat")
|
|
|
|
write (200,*) output_header
|
|
|
|
do i=1,nxp
|
|
write (200,'({0}e20.10)') real(i)*hxp, {1}
|
|
end do
|
|
|
|
close (200)
|
|
'''
|
|
|
|
avgarr = "{}(i)"
|
|
write_avg = avg_array_write.format(
|
|
len(self.averaged)+1,
|
|
", ".join(map(avgarr.format, set1+set2))
|
|
)
|
|
|
|
md = {}
|
|
md["module_name"] = "terms"
|
|
md["module_data"] = "\n".join((declh, decl))
|
|
md["module_init"] = alloc
|
|
md["module_finalize"] = free
|
|
md["module_pass1"] = calc1
|
|
md["module_pass1_avg"] = avg1
|
|
md["module_pass2"] = calc2
|
|
md["module_pass2_avg"] = avg2
|
|
md["module_write_result"] = write_avg
|
|
|
|
return md
|
|
|
|
|
|
class Stage4():
|
|
''' analyze liveness and allocate array '''
|
|
def __init__ (self, src):
|
|
self.src = src
|
|
self.derived = src.derived
|
|
self.averaged = src.averaged
|
|
self.dependency = src.dependency
|
|
self.avg1 = src.avg1
|
|
self.avg2 = src.avg2
|
|
self.pass1 = src.pass1
|
|
self.pass2 = src.pass2
|
|
|
|
self.array_name = "xyzbuffer{}"
|
|
|
|
narr1, alloc1 = (self.allocate_arr(self.pass1))
|
|
narr2, alloc2 = (self.allocate_arr(self.pass2))
|
|
|
|
self.narr = max(narr1, narr2)
|
|
|
|
self.alloc1 = alloc1
|
|
self.alloc2 = alloc2
|
|
|
|
|
|
def liveness (self, l1, g):
|
|
import numpy as np
|
|
img = np.zeros((len(l1), len(l1)))
|
|
for i, v in enumerate(l1):
|
|
for j in range(i, len(l1)):
|
|
img[i,i:j] = img[i,i:j] + (1 if v in g[l1[j]] else 0)
|
|
return img > 0
|
|
|
|
|
|
def allocate_arr (self, l):
|
|
|
|
import numpy as np
|
|
|
|
dg = self.dependency
|
|
|
|
mask = self.liveness(l, dg)
|
|
|
|
narr = mask.astype(np.int).sum(axis=0).max()
|
|
|
|
array_pool = set([self.array_name.format(i) for i in range(narr)])
|
|
|
|
livesets = [set([])] + [set(np.asarray(l)[row]) for row in mask.T]
|
|
|
|
var2arr = {}
|
|
|
|
for i, (s0, s1) in enumerate(zip(livesets[:-1], livesets[1:])):
|
|
|
|
array_pool.update(map(var2arr.get, s0 - s1))
|
|
|
|
for new in s1 - s0:
|
|
var2arr[new] = array_pool.pop()
|
|
|
|
return narr, var2arr
|
|
|
|
|
|
def array_codes (self):
|
|
|
|
array_names = [self.array_name.format(i) for i in range(self.narr)]
|
|
|
|
real_array_decl = "real*8, allocatable, dimension(:,:,:) :: {0}"
|
|
real_array_alloc = "allocate({0}(nxp,nyp,nzp), stat=ierr) ; {0} = 0."
|
|
real_array_free = "deallocate({})"
|
|
|
|
decl = "\n".join([real_array_decl.format(v) for v in array_names])
|
|
alloc = "\n".join([real_array_alloc.format(v) for v in array_names])
|
|
free = "\n".join([real_array_free.format(v) for v in array_names])
|
|
|
|
return decl, alloc, free
|
|
|
|
|
|
|
|
def print_program (self):
|
|
|
|
allvar = dict(self.derived)
|
|
allvar.update(self.averaged)
|
|
|
|
declarr, allocarr, freearr = self.array_codes()
|
|
|
|
declavg = "\n".join(v.code_decl() for v in self.averaged.itervalues())
|
|
allocavg = "\n".join(v.code_alloc() for v in self.averaged.itervalues())
|
|
freeavg = "\n".join(v.code_free() for v in self.averaged.itervalues())
|
|
|
|
calc1 = "\n".join(allvar[v].code(self.alloc1) for v in self.pass1)
|
|
calc2 = "\n".join(allvar[v].code(self.alloc2) for v in self.pass2)
|
|
|
|
set1 = [a.name for a in filter(AveragedField.pass1, self.averaged.values())]
|
|
set2 = [a.name for a in filter(AveragedField.pass2, self.averaged.values())]
|
|
|
|
set1.sort()
|
|
set2.sort()
|
|
|
|
avg1 = "\n".join(allvar[v].code_avg() for v in set1)
|
|
avg2 = "\n".join(allvar[v].code_avg() for v in set2)
|
|
|
|
hfmt = 'character (len = *), parameter :: output_header="{}"'
|
|
declh = hfmt.format(" ".join(["x"] + set1 + set2))
|
|
|
|
avg_array_write = '''
|
|
integer :: i
|
|
|
|
open (200, file="qEdge_X.dat")
|
|
|
|
write (200,*) output_header
|
|
|
|
do i=1,nxp
|
|
write (200,'({0}e20.10)') real(i)*hxp, {1}
|
|
end do
|
|
|
|
close (200)
|
|
'''
|
|
|
|
avgarr = "{}(i)"
|
|
write_avg = avg_array_write.format(
|
|
len(self.averaged)+1,
|
|
", ".join(map(avgarr.format, set1+set2))
|
|
)
|
|
|
|
md = {}
|
|
md["module_name"] = "terms"
|
|
md["module_data"] = "\n".join((declh, declavg, declarr))
|
|
md["module_init"] = "\n".join((allocavg, allocarr))
|
|
md["module_finalize"] = "\n".join((freeavg, freearr))
|
|
md["module_pass1"] = calc1
|
|
md["module_pass1_avg"] = avg1
|
|
md["module_pass2"] = calc2
|
|
md["module_pass2_avg"] = avg2
|
|
md["module_write_result"] = write_avg
|
|
|
|
return md
|
|
|
|
|
|
|
|
|
|
|
|
def save_ir (self):
|
|
|
|
import json
|
|
|
|
dg = {k:list(v) for k,v in self.dependency.items()}
|
|
|
|
with open("ir2.py", "w") as irf:
|
|
|
|
print >>irf, "g = ", json.dumps(dg, indent=4)
|
|
print >>irf, "l1 = ", json.dumps(self.pass1, indent=4)
|
|
print >>irf, "l2 = ", json.dumps(self.pass2, indent=4)
|
|
print >>irf, "avg1 = ", json.dumps(map(repr,self.avg1), indent=4)
|
|
print >>irf, "avg2 = ", json.dumps(map(repr,self.avg2), indent=4)
|
|
|
|
class Stage5():
|
|
''' pass1 and pass2 seperation and calculation ordering '''
|
|
|
|
class Stage6():
|
|
''' generate fortran code '''
|