preprocess.py 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348
  1. import os
  2. import json
  3. import ast
  4. import operator as op
  5. import numpy as np
  6. from collections import namedtuple
  7. from scipy.linalg import block_diag
  8. # load cfg file
  9. installDir = os.path.split(__file__)[0]
  10. cfgPath = os.path.join(installDir, "config")
  11. for loc in cfgPath, os.curdir, os.path.expanduser("~"):
  12. try:
  13. with open(os.path.join(loc, "config.json")) as source:
  14. configdata = json.load(source)
  15. except IOError:
  16. pass
  17. # evaluate the input json function with only these math operators
  18. operators = {ast.Add: op.add, ast.Sub: op.sub, ast.Mult: op.mul,
  19. ast.Div: op.truediv, ast.Pow: op.pow, ast.BitXor: op.xor,
  20. ast.USub: op.neg}
  21. def eval_expr(expr):
  22. return eval_(ast.parse(expr, mode='eval').body)
  23. def eval_(node):
  24. if isinstance(node, ast.Num): # <number>
  25. return node.n
  26. elif isinstance(node, "x"):
  27. return node.n
  28. elif isinstance(node, ast.BinOp): # <left> <operator> <right>
  29. return operators[type(node.op)](eval_(node.left), eval_(node.right))
  30. elif isinstance(node, ast.UnaryOp): # <operator> <operand> e.g., -1
  31. return operators[type(node.op)](eval_(node.operand))
  32. else:
  33. raise TypeError(node)
  34. def queryYesNo(question, default="yes"):
  35. valid = {"yes": True, "y": True, "ye": True,
  36. "no": False, "n": False}
  37. if default is None:
  38. prompt = " [y/n] "
  39. elif default == "yes":
  40. prompt = " [Y/n] "
  41. elif default == "no":
  42. prompt = " [y/N] "
  43. else:
  44. raise ValueError("invalid default answer: '%s'" % default)
  45. while True:
  46. print(question + prompt, end='')
  47. choice = input().lower()
  48. if default is not None and choice == '':
  49. return valid[default]
  50. elif choice in valid:
  51. return valid[choice]
  52. else:
  53. print("Please respond with 'yes' or 'no' "
  54. "(or 'y' or 'n').\n")
  55. def setDefaultCoefficients():
  56. question = "Do you want to use the default parameters?"
  57. isDefault = queryYesNo(question, "yes")
  58. if (isDefault):
  59. diffDefault = configdata["coefficients"]["diffusion"]
  60. convDefault = configdata["coefficients"]["convection"]
  61. reactionDefault = configdata["coefficients"]["reaction"]
  62. pOrderDefault = configdata["coefficients"]["pOrder"]
  63. numEleDefault = configdata["coefficients"]["numEle"]
  64. tauPlusDefault = configdata["coefficients"]["tauPlus"]
  65. tauMinusDefault = configdata["coefficients"]["tauMinus"]
  66. tol = configdata["coefficients"]["tol"]
  67. maxIt = configdata["coefficients"]["maxIt"]
  68. coeff = coefficients(diffDefault, convDefault, reactionDefault,
  69. pOrderDefault, numEleDefault, tauPlusDefault,
  70. tauMinusDefault, tol, maxIt)
  71. else:
  72. coeff = coefficients.fromInput()
  73. return coeff
  74. def shape(x, p):
  75. """generate p shape functions and its first order derivative
  76. at the given location x. x can be an array"""
  77. A = np.array([np.linspace(-1, 1, p)]).T**np.arange(p)
  78. C = np.linalg.inv(A).T
  79. x = np.array([x]).T
  80. shp = C.dot((x**np.arange(p)).T)
  81. shpx = C[:, 1::1].dot((x**np.arange(p - 1) * np.arange(1, p)).T)
  82. return shp, shpx
  83. def forcing(x):
  84. f = np.zeros(len(x))
  85. for i, forcingItem in enumerate(x):
  86. forcingExpr = configdata["forcing"]
  87. # replace the 'x' in the json file with the function parameters
  88. f[i] = eval_expr(forcingExpr.replace("x", str(forcingItem)))
  89. return f
  90. def boundaryCondition(case):
  91. if case == 'primal':
  92. # primal problem
  93. bcLeft = configdata["boundary"]["left"]
  94. bcRight = configdata["boundary"]["right"]
  95. bc = [bcLeft, bcRight]
  96. if case == 'adjoint':
  97. # adjoint problem
  98. bc = [0, 1]
  99. return bc
  100. class coefficients:
  101. def __init__(self, diff, conv, reaction, pOrder, numEle,
  102. tauPlus, tauMinus, tol, maxIt):
  103. if diff == 0:
  104. # set the diffusion constant to a small number
  105. # to avoid division by zero error
  106. diff = 1e-16
  107. self.DIFFUSION = diff
  108. self.CONVECTION = conv
  109. self.REACTION = reaction
  110. self.pOrder = pOrder
  111. self.numEle = numEle
  112. self.TAUPLUS = tauPlus
  113. self.TAUMINUS = tauMinus
  114. self.TOL = tol
  115. self.MAXIT = maxIt
  116. @classmethod
  117. def fromInput(cls):
  118. while True:
  119. try:
  120. print("Please provide the following coefficients.")
  121. diff = float(input("Diffusion constant (float): "))
  122. conv = float(input("Covection constant (float): "))
  123. reaction = float(input("Reaction constant (float): "))
  124. pOrder = int(input("Order of polynomials (int): "))
  125. numEle = int(input("Number of elements (int): "))
  126. tauPlus = float(input("Stablization parameter plus (float): "))
  127. tauMinus = float(
  128. input("Stablization parameter minus (float): "))
  129. tol = float(input("Error tolerance (float): "))
  130. maxIt = int(input("Max adaptive iterations (int): "))
  131. except ValueError:
  132. print("Sorry, wrong data type.")
  133. continue
  134. else:
  135. print("Something is wrong. Exit.")
  136. break
  137. return cls(diff, conv, reaction, pOrder, numEle,
  138. tauPlus, tauMinus, tol, maxIt)
  139. class discretization(object):
  140. """Given the problem statement and current mesh,
  141. construct the discretization matrices"""
  142. def __init__(self, coeff, mesh, enrich=None):
  143. self.mesh = mesh
  144. self.coeff = coeff
  145. self.enrich = enrich
  146. # the following init are for the sake of simplicity
  147. self.numBasisFuncs = coeff.pOrder + 1
  148. self.tau_pos = coeff.TAUPLUS
  149. self.tau_neg = coeff.TAUMINUS
  150. self.conv = coeff.CONVECTION
  151. self.kappa = coeff.DIFFUSION
  152. self.numEle = len(mesh) - 1
  153. self.dist = self.distGen()
  154. # shape function and gauss quadrature
  155. self.gqOrder = 5 * self.numBasisFuncs
  156. self.xi, self.wi = np.polynomial.legendre.leggauss(self.gqOrder)
  157. self.shp, self.shpx = shape(self.xi, self.numBasisFuncs)
  158. # enrich the space if the enrich argument is given
  159. if enrich is not None:
  160. self.shpEnrich, self.shpxEnrich = shape(
  161. self.xi, self.numBasisFuncs + enrich)
  162. def distGen(self):
  163. dist = np.zeros(self.numEle)
  164. for i in range(1, self.numEle + 1):
  165. dist[i - 1] = self.mesh[i] - self.mesh[i - 1]
  166. return dist
  167. def matGroup(self):
  168. lhsMat = self.lhsGen()
  169. F, L, R = lhsMat.F, lhsMat.L, lhsMat.R
  170. eleMat = self.eleGen()
  171. A, B, BonU, D = eleMat.A, eleMat.B, eleMat.BonU, eleMat.D
  172. faceMat = self.interfaceGen()
  173. C, E, G, H = faceMat.C, faceMat.E, faceMat.G, faceMat.H
  174. matGroup = namedtuple(
  175. 'matGroup', ['A', 'B', 'BonU', 'C', 'D', 'E', 'F', 'G', 'H', 'L', 'R'])
  176. return matGroup(A, B, BonU, C, D, E, F, G, H, L, R)
  177. def lhsGen(self):
  178. """Generate matrices associated with left hand side"""
  179. if self.enrich is not None:
  180. # when enrich is given
  181. # provide matrices used in the DWR residual calculation
  182. numBasisFuncs = self.numBasisFuncs + self.enrich
  183. shp = self.shpEnrich
  184. else:
  185. numBasisFuncs = self.numBasisFuncs
  186. shp = self.shp
  187. # forcing vector F
  188. F = np.zeros(numBasisFuncs * self.numEle)
  189. for i in range(1, self.numEle + 1):
  190. f = self.dist[i - 1] / 2 \
  191. * shp.dot(self.wi * forcing(self.mesh[i - 1] +
  192. 1 / 2 * (1 + self.xi) *
  193. self.dist[i - 1]))
  194. F[(i - 1) * numBasisFuncs:i * numBasisFuncs] = f
  195. F[0] += (self.conv + self.tau_pos) * boundaryCondition('primal')[0]
  196. F[-1] += (-self.conv + self.tau_neg) * boundaryCondition('primal')[1]
  197. # L, easy in 1d
  198. L = np.zeros(self.numEle - 1)
  199. # R, easy in 1d
  200. R = np.zeros(numBasisFuncs * self.numEle)
  201. R[0] = boundaryCondition('primal')[0]
  202. R[-1] = -boundaryCondition('primal')[1]
  203. lhsMat = namedtuple('lhsMat', ['F', 'L', 'R'])
  204. return lhsMat(F, L, R)
  205. def eleGen(self):
  206. """Generate matrices associated with elements"""
  207. if self.enrich is not None:
  208. numBasisFuncsEnrich = self.numBasisFuncs + self.enrich
  209. shpEnrich = self.shpEnrich
  210. shpxEnrich = self.shpxEnrich
  211. b = (self.shpx.T * np.ones((self.gqOrder, self.numBasisFuncs))
  212. ).T.dot(np.diag(self.wi).dot(shpEnrich.T))
  213. # BonQ is only used in calculating DWR residual
  214. BonQ = block_diag(*[b] * (self.numEle)).T
  215. else:
  216. numBasisFuncsEnrich = self.numBasisFuncs
  217. shpEnrich = self.shp
  218. shpxEnrich = self.shpx
  219. BonQ = None
  220. a = 1 / self.coeff.DIFFUSION * \
  221. shpEnrich.dot(np.diag(self.wi).dot(self.shp.T))
  222. A = np.repeat(self.dist, self.numBasisFuncs) / \
  223. 2 * block_diag(*[a] * (self.numEle))
  224. b = (shpxEnrich.T * np.ones((self.gqOrder, numBasisFuncsEnrich))
  225. ).T.dot(np.diag(self.wi).dot(self.shp.T))
  226. B = block_diag(*[b] * (self.numEle))
  227. d = self.coeff.REACTION * \
  228. shpEnrich.dot(np.diag(self.wi).dot(self.shp.T))
  229. # assemble D
  230. dFace = np.zeros((numBasisFuncsEnrich, self.numBasisFuncs))
  231. dFace[0, 0] = self.tau_pos
  232. dFace[-1, -1] = self.tau_neg
  233. dConv = -self.conv * (shpxEnrich.T * np.ones((self.gqOrder,
  234. numBasisFuncsEnrich)))\
  235. .T.dot(np.diag(self.wi).dot(self.shp.T))
  236. D = np.repeat(self.dist, self.numBasisFuncs) / 2 * \
  237. block_diag(*[d] * (self.numEle)) + \
  238. block_diag(*[dFace] * (self.numEle)) +\
  239. block_diag(*[dConv] * (self.numEle))
  240. eleMat = namedtuple('eleMat', ['A', 'B', 'BonU', 'D'])
  241. return eleMat(A, B, BonQ, D)
  242. def interfaceGen(self):
  243. """Generate matrices associated with interfaces"""
  244. if self.enrich is not None:
  245. # when enrich is given
  246. # provide matrices used in the DWR residual calculation
  247. numBasisFuncs = self.numBasisFuncs + self.enrich
  248. else:
  249. numBasisFuncs = self.numBasisFuncs
  250. tau_pos, tau_neg, conv = self.tau_pos, self.tau_neg, self.conv
  251. # elemental h
  252. h = np.zeros((2, 2))
  253. h[0, 0], h[-1, -1] = -conv - tau_pos, conv - tau_neg
  254. # mappinng matrix
  255. map_h = np.zeros((2, self.numEle), dtype=int)
  256. map_h[:, 0] = np.arange(2)
  257. for i in np.arange(1, self.numEle):
  258. map_h[:, i] = np.arange(
  259. map_h[2 - 1, i - 1], map_h[2 - 1, i - 1] + 2)
  260. # assemble H and eliminate boundaries
  261. H = np.zeros((self.numEle + 1, self.numEle + 1))
  262. for i in range(self.numEle):
  263. for j in range(2):
  264. m = map_h[j, i]
  265. for k in range(2):
  266. n = map_h[k, i]
  267. H[m, n] += h[j, k]
  268. H = H[1:self.numEle][:, 1:self.numEle]
  269. # elemental e
  270. e = np.zeros((numBasisFuncs, 2))
  271. e[0, 0], e[-1, -1] = -conv - tau_pos, conv - tau_neg
  272. # mapping matrix
  273. map_e_x = np.arange(numBasisFuncs * self.numEle,
  274. dtype=int).reshape(self.numEle,
  275. numBasisFuncs).T
  276. map_e_y = map_h
  277. # assemble global E
  278. E = np.zeros((numBasisFuncs * self.numEle, self.numEle + 1))
  279. for i in range(self.numEle):
  280. for j in range(numBasisFuncs):
  281. m = map_e_x[j, i]
  282. for k in range(2):
  283. n = map_e_y[k, i]
  284. E[m, n] += e[j, k]
  285. E = E[:, 1:self.numEle]
  286. # elemental c
  287. c = np.zeros((numBasisFuncs, 2))
  288. c[0, 0], c[-1, -1] = -1, 1
  289. # assemble global C
  290. C = np.zeros((numBasisFuncs * self.numEle, self.numEle + 1))
  291. for i in range(self.numEle):
  292. for j in range(numBasisFuncs):
  293. m = map_e_x[j, i]
  294. for k in range(2):
  295. n = map_e_y[k, i]
  296. C[m, n] += c[j, k]
  297. C = C[:, 1:self.numEle]
  298. # elemental g
  299. g = np.zeros((2, numBasisFuncs))
  300. g[0, 0], g[-1, -1] = tau_pos, tau_neg
  301. # mapping matrix
  302. map_g_x = map_h
  303. map_g_y = np.arange(numBasisFuncs * self.numEle,
  304. dtype=int).reshape(self.numEle,
  305. numBasisFuncs).T
  306. # assemble global G
  307. G = np.zeros((self.numEle + 1, numBasisFuncs * self.numEle))
  308. for i in range(self.numEle):
  309. for j in range(2):
  310. m = map_g_x[j, i]
  311. for k in range(numBasisFuncs):
  312. n = map_g_y[k, i]
  313. G[m, n] += g[j, k]
  314. G = G[1:self.numEle, :]
  315. faceMat = namedtuple('faceMat', ['C', 'E', 'G', 'H'])
  316. return faceMat(C, E, G, H)