def corr_uvw(uvw, p, rho, dt, dxyz, obstacle=None, verbose=True): # ----------------------------------------------------------------------------- """ Args: uvw: .... Tuple with three staggered or centered velocity components. (Each component is object of type "Unknown"). p: ...... Unknown holding the pressure correction. rho: .... Three-dimensional array holding density for all cells. dt: ..... Time step dxyz: ... Tuple holding cell dimensions in "x", "y" and "z" directions. Each cell dimension is a three-dimensional array. obstacle: Obstacle, three-dimensional array with zeros and ones. It is zero in fluid, one in solid. Returns: None, but input argument uvw is modified. """ # Unpack received tuples dx, dy, dz = dxyz # Compute pressure correction gradients p_x = dif_x(p.val) / avg_x(dx) p_y = dif_y(p.val) / avg_y(dy) p_z = dif_z(p.val) / avg_z(dz) # Set to zero in obst if obstacle is not None: p_x = obst_zero_val(X, p_x, obstacle) p_y = obst_zero_val(Y, p_y, obstacle) p_z = obst_zero_val(Z, p_z, obstacle) # Pad with boundary values by expanding from interior # (This is done only for collocated formulation) if uvw[X].pos == C: p_x = avg_x(cat_x((p_x[:1, :, :], p_x, p_x[-1:, :, :]))) p_y = avg_y(cat_y((p_y[:, :1, :], p_y, p_y[:, -1:, :]))) p_z = avg_z(cat_z((p_z[:, :, :1], p_z, p_z[:, :, -1:]))) # Correct the velocities uvw[X].val[:] = uvw[X].val[:] - dt / avg(uvw[X].pos, rho) * p_x uvw[Y].val[:] = uvw[Y].val[:] - dt / avg(uvw[Y].pos, rho) * p_y uvw[Z].val[:] = uvw[Z].val[:] - dt / avg(uvw[Z].pos, rho) * p_z # Compute volume balance for checking if verbose is True: if not uvw[X].pos == C: err = vol_balance(uvw, (dx, dy, dz), obstacle) write.at(__name__) print(" Maximum volume error after correction: %12.5e" % abs(err).max()) return # end of function
def calc_p(p, uvwf, rho, dt, dxyz, obstacle=None, verbose=True): # ----------------------------------------------------------------------------- """ Args: p: ...... Object of the type "Unknown", holding the pressure. uvwf: ... Tuple with three staggered velocity components (where each component is an object of type "Unknown". rho: .... Three-dimensional array holding density for all cells. dt: ..... Time step. dxyz: ... Tuple holding cell dimensions in "x", "y" and "z" directions. Each cell dimension is a three-dimensional array. obstacle: Obstacle, three-dimensional array with zeros and ones. It is zero in fluid, one in solid. Returns: None, but input argument p is modified! """ # Fetch the resolution rc = p.val.shape # Create system matrix and right hand side A_p = diffusion(p, zeros(rc), dt / rho, dxyz, obstacle, NEUMANN) b_p = zeros(rc) # Compute the source for the pressure. Important: don't send "obst" # as a parameter here, because you don't want to take it into # account at this stage. After velocity corrections, you should. b_p = vol_balance(uvwf, dxyz, zeros(rc)) if verbose is True: write.at(__name__) print(" Volume error before correction : %12.5e" % abs(b_p).max()) print(" Volume imbalance before correction : %12.5e" % b_p.sum()) # Solve for pressure p.val[:] = bicgstab(A_p, p, b_p, TOL, False) # Anchor it to values around zero (the absolute value of pressure # correction can get really volatile. Although it is in prinicple not # important for incompressible flows, it is ugly for post-processing. p.val[:] = p.val[:] - p.val.mean() # Set to zero in obstacle (it can get strange # values during the iterative solution procedure) if obstacle is not None: p.val[:] = obst_zero_val(p.pos, p.val, obstacle) # Finally adjust the boundary values adj_n_bnds(p) return # end of function
def cfl_max(uvw, dt, dxyz, verbose=True): # ----------------------------------------------------------------------------- """ Args: uvw: Tuple with three staggered or centered velocity components. (Each component is an object of type "Unknown". dt: Time step dxyz: Tuple holding cell dimensions in "x", "y" and "z" directions. Each cell dimension is a three-dimensional array. Returns: cfl: Floating point number holding the maximum value of CFL. Note: It could be written in a more compact way, without unpacking the tuples, but that would only lead to poorer readability of the code. """ # Unpack received tuples u, v, w = uvw dx, dy, dz = dxyz # Take velocity's position d = u.pos # Mesh is cell-centered if d == C: cfl = dt * max( abs(u.val/dx).max(), \ abs(v.val/dy).max(), \ abs(w.val/dz).max() ) # Mesh is staggered else: cfl = dt * max( abs(u.val/avg_x(dx)).max(), \ abs(v.val/avg_y(dy)).max(), \ abs(w.val/avg_z(dz)).max() ) if verbose is True: write.at(__name__) print(" Maximum CFL number: %12.5e" % cfl) return cfl
def __init__(self, name, pos, res, default_bc, per=(False, False, False), verbose=False): # ------------------------------------------------------------------------- """ Args: name: ..... String holding the name of the variable. It is intended to be used for post-processing. pos: ...... Integer specifying if the variable is cell centered (value C), staggered in "x" (value X), "y" (value Y) or in "z" direction (value Z). res: ...... Vector specifying resolutions in "x", "y" and "z" directions. default_bc: Integer specifying if the default boundary condition is of Dirichlet, Neumann or Outlet type. per: ...... Tuple holding three Boolean type variables which specify if the unknown is periodic in "x", "y" or "z" direction. Returns: Oh well, its own self, isn't it? """ if verbose is True: write.at(__name__) # Store name, position and resolution self.name = name self.pos = pos self.per = per self.nx, self.ny, self.nz = res # Allocate memory space for new and old values self.val = zeros((self.nx, self.ny, self.nz)) self.old = zeros((self.nx, self.ny, self.nz)) # Create boundary tuple nx, ny, nz = res key = namedtuple("key", "typ val") self.bnd = (key(ndarray(shape=(1, ny, nz), dtype=int), zeros((1, ny, nz))), key(ndarray(shape=(1, ny, nz), dtype=int), zeros((1, ny, nz))), key(ndarray(shape=(nx, 1, nz), dtype=int), zeros((nx, 1, nz))), key(ndarray(shape=(nx, 1, nz), dtype=int), zeros((nx, 1, nz))), key(ndarray(shape=(nx, ny, 1), dtype=int), zeros((nx, ny, 1))), key(ndarray(shape=(nx, ny, 1), dtype=int), zeros((nx, ny, 1)))) # Prescribe default boundary conditions if self.per[X] == False: self.bnd[W].typ[0, :, :] = default_bc self.bnd[E].typ[0, :, :] = default_bc else: if verbose is True: print(" ", self.name, "is periodic in x direction;", end="") print(" skipping default boundary condition for it!") if self.per[Y] == False: self.bnd[S].typ[:, 0, :] = default_bc self.bnd[N].typ[:, 0, :] = default_bc else: if verbose is True: print(" ", self.name, "is periodic in y direction;", end="") print(" skipping default boundary condition for it!") if self.per[Z] == False: self.bnd[B].typ[:, :, 0] = default_bc self.bnd[T].typ[:, :, 0] = default_bc else: if verbose is True: print(" ", self.name, "is periodic in z direction;", end="") print(" skipping default boundary condition for it!") self.bnd[W].val[0, :, :] = 0 self.bnd[E].val[0, :, :] = 0 self.bnd[S].val[:, 0, :] = 0 self.bnd[N].val[:, 0, :] = 0 self.bnd[B].val[:, :, 0] = 0 self.bnd[T].val[:, :, 0] = 0 if verbose is True: print(" Created unknown:", self.name) return # end of function
def cgs(a, phi, b, tol, verbose=False, max_iter=-1): # ----------------------------------------------------------------------------- """ Args: a: ...... Object of the type "Matrix", holding the system matrix. phi: .... Object of the type "Unknown" to be solved. b: ...... Three-dimensional array holding the source term. tol: .... Absolute solver tolerance verbose: Logical variable setting if solver will be verbose (print info on Python console) or not. max_iter: Maxiumum number of iterations. Returns: x: Three-dimensional array with solution. """ if verbose is True: write.at(__name__) # Helping variable x = phi.val # Intitilize arrays p = zeros(x.shape) p_hat = Unknown("vec_p_hat", phi.pos, x.shape, -1, per=phi.per, verbose=False) q = zeros(x.shape) r = zeros(x.shape) r_tilda = zeros(x.shape) u = zeros(x.shape) u_hat = Unknown("vec_u_hat", phi.pos, x.shape, -1, per=phi.per, verbose=False) v_hat = zeros(x.shape) # r = b - A * x r[:, :, :] = b[:, :, :] - mat_vec_bnd(a, phi) # Chose r~ r_tilda[:, :, :] = r[:, :, :] # --------------- # Iteration loop # --------------- if max_iter == -1: max_iter = prod(phi.val.shape) for i in range(1, max_iter): if verbose is True: print(" iteration: %3d:" % (i), end="") # rho = r~ * r rho = vec_vec(r_tilda, r) # If rho == 0 method fails if abs(rho) < TINY * TINY: if verbose is True == True: write.at(__name__) print(" Fails becuase rho = %12.5e" % rho) return x if i == 1: # u = r u[:, :, :] = r[:, :, :] # p = u p[:, :, :] = u[:, :, :] else: # beta = rho / rho_old beta = rho / rho_old # u = r + beta q u[:, :, :] = r[:, :, :] + beta * q[:, :, :] # p = u + beta (q + beta p) p[:, :, :] = u[:, :, :] + beta * (q[:, :, :] + beta * p[:, :, :]) # Solve M p_hat = p p_hat.val[:, :, :] = p[:, :, :] / a.C[:, :, :] # v^ = A * p^ v_hat[:, :, :] = mat_vec_bnd(a, p_hat) # alfa = rho / (r~ * v^) alfa = rho / vec_vec(r_tilda, v_hat) # q = u - alfa v^ q[:, :, :] = u[:, :, :] - alfa * v_hat[:, :, :] # Solve M u^ = u + q u_hat.val[:, :, :] = (u[:, :, :] + q[:, :, :]) / a.C[:, :, :] # x = x + alfa u^ x[:, :, :] += alfa * u_hat.val[:, :, :] # q^ = A u^ q_hat = mat_vec_bnd(a, u_hat) # r = r - alfa q^ r[:, :, :] -= alfa * q_hat[:, :, :] # Compute residual res = norm(r) if verbose is True: print("%12.5e" % res) # If tolerance has been reached, get out of here if res < tol: return x # Prepare for next iteration rho_old = rho return x # end of function
def jacobi(a, phi, b, tol, verbose=False, max_iter=-1): # ----------------------------------------------------------------------------- """ Args: a: ...... Object of the type "Matrix", holding the system matrix. phi: .... Object of the type "Unknown" to be solved. b: ...... Three-dimensional array holding the source term. tol: .... Absolute solver tolerance verbose: Logical variable setting if solver will be verbose (print info on Python console) or not. max_iter: Maxiumum number of iterations. Returns: phi.val: Three-dimensional array with solution. """ if verbose is True: write.at(__name__) sum = zeros(phi.val.shape) r = zeros(phi.val.shape) if max_iter == -1: max_iter = prod(phi.val.shape) # Under-relaxation factor alfa = 0.9 # Main iteration loop for iter in range(0, max_iter): if verbose is True: print(" iteration: %3d:" % (iter), end="") # Add source term sum[:] = b[:] # Add contribution from west and east sum[:1, :, :] += a.W[:1, :, :] * phi.bnd[W].val[:1, :, :] sum[-1:, :, :] += a.E[-1:, :, :] * phi.bnd[E].val[:1, :, :] # Add up west and east neighbors from the inside sum[1:, :, :] += a.W[1:, :, :] * phi.val[:-1, :, :] sum[:-1, :, :] += a.E[:-1, :, :] * phi.val[1:, :, :] # Add contribution from south and north sum[:, :1, :] += a.S[:, :1, :] * phi.bnd[S].val[:, :1, :] sum[:, -1:, :] += a.N[:, -1:, :] * phi.bnd[N].val[:, :1, :] # Add up south and north neighbors from the inside sum[:, 1:, :] += a.S[:, 1:, :] * phi.val[:, :-1, :] sum[:, :-1, :] += a.N[:, :-1, :] * phi.val[:, 1:, :] # Add contribution from bottom and top sum[:, :, :1] += a.B[:, :, :1] * phi.bnd[B].val[:, :, :1] sum[:, :, -1:] += a.T[:, :, -1:] * phi.bnd[T].val[:, :, :1] # Add up south and north neighbors from the inside sum[:, :, 1:] += a.B[:, :, 1:] * phi.val[:, :, :-1] sum[:, :, :-1] += a.T[:, :, :-1] * phi.val[:, :, 1:] phi.val[:,:,:] = (1.0 - alfa) * phi.val[:,:,:] \ + alfa * sum[:,:,:] / a.C[:,:,:] phi.exchange() # r = b - A * x r[:, :, :] = b[:, :, :] - mat_vec_bnd(a, phi) # Compute residual res = norm(r) if verbose is True: print("%12.5e" % res) # If tolerance has been reached, get out of here if res < tol: return phi.val return phi.val # end of function
def bicgstab(a, phi, b, tol, verbose=False, max_iter=-1): # ----------------------------------------------------------------------------- """ Args: a: ...... Object of the type "Matrix", holding the system matrix. phi: .... Object of the type "Unknown" to be solved. b: ...... Three-dimensional array holding the source term. tol: .... Absolute solver tolerance verbose: Logical variable setting if solver will be verbose (print info on Python console) or not. max_iter: Maxiumum number of iterations. Returns: x: Three-dimensional array with solution. """ if verbose is True: write.at(__name__) # Helping variable x = phi.val # Intitilize arrays p = zeros(x.shape) p_hat = Unknown("vec_p_hat", phi.pos, x.shape, -1, per=phi.per, verbose=False) r = zeros(x.shape) r_tilda = zeros(x.shape) s = zeros(x.shape) s_hat = Unknown("vec_s_hat", phi.pos, x.shape, -1, per=phi.per, verbose=False) v = zeros(x.shape) # r = b - A * x r[:, :, :] = b[:, :, :] - mat_vec_bnd(a, phi) # Chose r~ r_tilda[:, :, :] = r[:, :, :] # --------------- # Iteration loop # --------------- if max_iter == -1: max_iter = prod(phi.val.shape) for i in range(1, max_iter): if verbose is True: print(" iteration: %3d:" % (i), end="") # rho = r~ * r rho = vec_vec(r_tilda, r) # If rho == 0 method fails if abs(rho) < TINY * TINY: write.at(__name__) print(" Fails becuase rho = %12.5e" % rho) return x if i == 1: # p = r p[:, :, :] = r[:, :, :] else: # beta = (rho / rho_old)(alfa/omega) beta = rho / rho_old * alfa / omega # p = r + beta (p - omega v) p[:, :, :] = r[:, :, :] + beta * (p[:, :, :] - omega * v[:, :, :]) # Solve M p_hat = p p_hat.val[:, :, :] = p[:, :, :] / a.C[:, :, :] # v = A * p^ v[:, :, :] = mat_vec_bnd(a, p_hat) # alfa = rho / (r~ * v) alfa = rho / vec_vec(r_tilda, v) # s = r - alfa v s[:, :, :] = r[:, :, :] - alfa * v[:, :, :] # Check norm of s, if small enough set x = x + alfa p_hat and stop res = norm(s) if res < tol: if verbose is True == True: write.at(__name__) print(" Fails becuase rho = %12.5e" % rho) x[:, :, :] += alfa * p_hat.val[:, :, :] return x # Solve M s^ = s s_hat.val[:, :, :] = s[:, :, :] / a.C[:, :, :] # t = A s^ t = mat_vec_bnd(a, s_hat) # omega = (t * s) / (t * t) omega = vec_vec(t, s) / vec_vec(t, t) # x = x + alfa p^ + omega * s^ x[:, :, :] += alfa * p_hat.val[:, :, :] + omega * s_hat.val[:, :, :] # r = s - omega q^ r[:, :, :] = s[:, :, :] - omega * t[:, :, :] # Compute residual res = norm(r) if verbose is True: print("%12.5e" % res) # If tolerance has been reached, get out of here if res < tol: return x # Prepare for next iteration rho_old = rho return x # end of function
def gamg_v_cycle(a, phi, b, tol, verbose=False, max_cycles=8, max_smooth=4): # ----------------------------------------------------------------------------- """ Args: a: ..... Object of the type "Matrix", holding the system matrix. phi: ... Object of the type "Unknown" to be solved. b: ..... Three-dimensional array holding the source term. tol: ... Absolute solver tolerance verbose: Logical variable setting if solver will be verbose (print info on Python console) or not. Returns: x: Three-dimensional array with solution. """ if verbose is True: write.at(__name__) # ------------------ # # Get system levels # # ------------------ n_, shape_, a_, i_, d_, phi_, b_, r_ = gamg_coarsen_system(a, phi, b) # Set a couple of parameters for the solver n_steps = n_ - 1 # number of steps down the "V" cycle # ----------------------------------------------------- # # Solve a bit on the finest level and compute residual # # ----------------------------------------------------- grid = 0 # finest level phi_[grid].val = jacobi(a_[grid], phi_[grid], b_[grid], TOL, verbose=True, max_iter=4) # r = b - A * x r_[grid].val[:] = b_[grid][:] - mat_vec_bnd(a_[grid], phi_[grid]) if verbose is True: print(" residual at level %d" % grid, norm(r_[grid].val)) # ========================================================================= # # Start the V-cycle # # ========================================================================= for cycle in range(0, max_cycles): if verbose is True: write.cycle(cycle) # -------------------- # # Go down a few steps # # -------------------- for level in range(1, n_steps + 1): grid = grid + 1 # Compute ratio between grid levels rx = shape_[grid - 1][X] // shape_[grid][X] ry = shape_[grid - 1][Y] // shape_[grid][Y] rz = shape_[grid - 1][Z] // shape_[grid][Z] # Lower bounds for browsing through grid levels wl = 0 el = 1 sl = 0 nl = 1 bl = 0 tl = 1 if rx < 2: el = 0 if ry < 2: nl = 0 if rz < 2: tl = 0 # ------------------------------------- # Restrict # # Computes r.h.s. on the coarser level # from residual on the finer level. # ------------------------------------- b_[grid][:] = r_[grid - 1].val[wl::rx, sl::ry, bl::rz] b_[grid][:] += r_[grid - 1].val[wl::rx, nl::ry, bl::rz] b_[grid][:] += r_[grid - 1].val[wl::rx, sl::ry, tl::rz] b_[grid][:] += r_[grid - 1].val[wl::rx, nl::ry, tl::rz] b_[grid][:] += r_[grid - 1].val[el::rx, sl::ry, bl::rz] b_[grid][:] += r_[grid - 1].val[el::rx, nl::ry, bl::rz] b_[grid][:] += r_[grid - 1].val[el::rx, sl::ry, tl::rz] b_[grid][:] += r_[grid - 1].val[el::rx, nl::ry, tl::rz] b_[grid][:] = b_[grid][:] / ((3 - rx) * (3 - ry) * (3 - rz)) # ------------------------------------------------ # Solve on the coarser level and compute residual # ------------------------------------------------ phi_[grid].val[:] = 0 # nulify to forget previous corrections phi_[grid].val = jacobi(a_[grid], phi_[grid], b_[grid], TOL, verbose=False, max_iter=max_smooth) # r = b - A * x r_[grid].val[:] = b_[grid][:] - mat_vec_bnd(a_[grid], phi_[grid]) if verbose is True: print(" residual at level %d" % grid, norm(r_[grid].val)) # ================================== # # This is the bottom of the V-cycle # # ================================== # ------------------ # # Go up a few steps # # ------------------ for level in range(1, n_steps + 1): grid = grid - 1 # Compute ratio between grid levels rx = shape_[grid][X] // shape_[grid + 1][X] ry = shape_[grid][Y] // shape_[grid + 1][Y] rz = shape_[grid][Z] // shape_[grid + 1][Z] # Lower bounds for browsing through grid levels wl = 0 el = 1 sl = 0 nl = 1 bl = 0 tl = 1 if rx < 2: el = 0 if ry < 2: nl = 0 if rz < 2: tl = 0 # ------------------------------------------- # Prolongation # # Interpolates the solution from the coarser # level as the correction to the current. # It also smoooths it out a little bit. # ------------------------------------------- r_[grid].val[:] = 0 # First copy in each available cell on fine level r_[grid].val[wl::rx, sl::ry, bl::rz] = phi_[grid + 1].val[:] # Then spread arond r_[grid].val[el::rx, sl::ry, bl::rz] = r_[grid].val[wl::rx, sl::ry, bl::rz] r_[grid].val[wl::rx, nl::ry, bl::rz] = r_[grid].val[wl::rx, sl::ry, bl::rz] r_[grid].val[el::rx, nl::ry, bl::rz] = r_[grid].val[wl::rx, sl::ry, bl::rz] r_[grid].val[wl::rx, sl::ry, tl::rz] = r_[grid].val[wl::rx, sl::ry, bl::rz] r_[grid].val[el::rx, sl::ry, tl::rz] = r_[grid].val[wl::rx, sl::ry, bl::rz] r_[grid].val[wl::rx, nl::ry, tl::rz] = r_[grid].val[wl::rx, sl::ry, bl::rz] r_[grid].val[el::rx, nl::ry, tl::rz] = r_[grid].val[wl::rx, sl::ry, bl::rz] # Then smooth them out a little bit for smooth in range(0, max_smooth): r_[grid].exchange() summ = zeros((shape_[grid])) summ[:] += cat_x((r_[grid].bnd[W].val, r_[grid].val[:-1, :, :])) * a_[grid].W summ[:] += cat_x( (r_[grid].val[1:, :, :], r_[grid].bnd[E].val)) * a_[grid].E summ[:] += cat_y((r_[grid].bnd[S].val, r_[grid].val[:, :-1, :])) * a_[grid].S summ[:] += cat_y( (r_[grid].val[:, 1:, :], r_[grid].bnd[N].val)) * a_[grid].N summ[:] += cat_z((r_[grid].bnd[B].val, r_[grid].val[:, :, :-1])) * a_[grid].B summ[:] += cat_z( (r_[grid].val[:, :, 1:], r_[grid].bnd[T].val)) * a_[grid].T r_[grid].val[:] = summ[:] / d_[grid][:] # ----------------------------------------------- # Correction on the finer level, followed by a # bit of smoothing and computation of residuals. # ----------------------------------------------- phi_[grid].val[:] += r_[grid].val[:] phi_[grid].val = jacobi(a_[grid], phi_[grid], b_[grid], TOL, verbose=False, max_iter=max_smooth) # r = b - A * x r_[grid].val[:] = b_[grid][:] \ - mat_vec_bnd(a_[grid], phi_[grid]) print(" residual at level %d" % grid, norm(r_[grid].val)) return phi_[0].val # end of function
def cg(a, phi, b, tol, verbose=False, max_iter=-1): # ----------------------------------------------------------------------------- """ Args: a: ...... Object of the type "Matrix", holding the system matrix. phi: .... Object of the type "Unknown" to be solved. b: ...... Three-dimensional array holding the source term. tol: .... Absolute solver tolerance verbose: Logical variable setting if solver will be verbose (print info on Python console) or not. max_iter: Maxiumum number of iterations. Returns: x: Three-dimensional array with solution. """ if verbose is True: write.at(__name__) # Helping variable x = phi.val # Intitilize arrays p = Unknown("vec_p", phi.pos, x.shape, -1, per=phi.per, verbose=False) q = zeros(x.shape) r = zeros(x.shape) z = zeros(x.shape) # r = b - A * x r[:, :, :] = b[:, :, :] - mat_vec_bnd(a, phi) # --------------- # Iteration loop # --------------- if max_iter == -1: max_iter = prod(phi.val.shape) for i in range(1, max_iter): if verbose is True: print(" iteration: %3d:" % (i), end="") # Solve M z = r z[:, :, :] = r[:, :, :] / a.C[:, :, :] # rho = r * z rho = vec_vec(r, z) if i == 1: # p = z p.val[:, :, :] = z[:, :, :] else: # beta = rho / rho_old beta = rho / rho_old # p = z + beta p p.val[:, :, :] = z[:, :, :] + beta * p.val[:, :, :] # q = A * p q[:, :, :] = mat_vec_bnd(a, p) # alfa = rho / (p * q) alfa = rho / vec_vec(p.val, q) # x = x + alfa p x[:, :, :] += alfa * p.val[:, :, :] # r = r - alfa q r[:, :, :] -= alfa * q[:, :, :] # Compute residual res = norm(r) if verbose is True: print("%12.5e" % res) # If tolerance has been reached, get out of here if res < tol: return x # Prepare for next iteration rho_old = rho return x # end of function