def test_multiplication(self): print( '\n== testing multiplication of system matrix for problem of weighted projection ====' ) for dim, pol_order in itertools.product([2, 3], [1, 2]): N = 2 # no. of elements print('dim={0}, pol_order={1}, N={2}'.format(dim, pol_order, N)) # creating MESH and defining MATERIAL if dim == 2: mesh = UnitSquareMesh(N, N) m = Expression("1+10*16*x[0]*(1-x[0])*x[1]*(1-x[1])", degree=2) # material coefficients elif dim == 3: mesh = UnitCubeMesh(N, N, N) m = Expression("1+10*16*x[0]*(1-x[0])*(1-x[1])*x[2]", degree=2) # material coefficients mesh.coordinates()[:] += 0.1 * np.random.random( mesh.coordinates().shape) # mesh perturbation V = FunctionSpace(mesh, "CG", pol_order) # original FEM space W = FunctionSpace(mesh, "CG", 2 * pol_order) # double-grid space print('assembling local matrices for DoGIP...') Bhat = get_Bhat( dim, pol_order, problem=0) # projection between V on W on a reference element AT_dogip = get_A_T(m, V, W, problem=0) dofmapV = V.dofmap() def system_multiplication_DoGIP(AT_dogip, Bhat, u_vec): # mutliplication with DoGIP decomposition Au = np.zeros_like(u_vec) for ii, cell in enumerate(cells(mesh)): ind = dofmapV.cell_dofs(ii) # local to global map Au[ind] += Bhat.T.dot(AT_dogip[ii] * Bhat.dot(u_vec[ind])) return Au print('assembling FEM sparse matrix') u, v = TrialFunction(V), TestFunction(V) Asp = assemble(m * u * v * dx, tensor=EigenMatrix()) # Asp = Asp.sparray() print('multiplication...') ur = Function(V) # creating random vector ur_vec = 5 * np.random.random(V.dim()) ur.vector().set_local(ur_vec) Au_DoGIP = system_multiplication_DoGIP( AT_dogip, Bhat, ur_vec) # DoGIP multiplication Auex = Asp.dot(ur_vec) # FEM multiplication with sparse matrix # testing the difference between DoGIP and FEniCS self.assertAlmostEqual(0, np.linalg.norm(Auex - Au_DoGIP)) print('...ok')
def function_interpolate(fin, fsout, coords=None, method='nearest'): """Copy a fenics Function Required arguments: fin: the input Function to copy fsout: the output FunctionSpace onto which to copy it Optional arguments: coords: the global coordinates of the DOFs of the FunctionSpace on which fin is defined. If not provided, gather_dof_coords will be called to get them. If you intend to do several interpolations from functions defined on the same FunctionSpace, you can avoid multiple calls to gather_dof_coords by supplying this argument. method='nearest': the method argument of scipy.interpolate.griddata. Returns: The values from fin are interpolated into fout, a Function defined on fsout. fout is returned. """ comm = fsout.mesh().mpi_comm() logGATHER('comm.size', comm.size) try: fsin = fin.function_space() except AttributeError: # fallback for Constant fout = fe.interpolate(fin, fsout) return (fout) vlen = fsin.dim() # # dofs logGATHER('vlen', vlen) if coords is None: coords = gather_dof_coords(fsin) logGATHER('fsin.mesh().mpi_comm().size', fsin.mesh().mpi_comm().size) try: vec0 = fin.vector().gather_on_zero() except AttributeError: # fallback for Constant fout = fe.interpolate(fin, fsout) return (fout) if comm.rank == 0: vec = vec0.copy() else: vec = np.empty(vlen) comm.Bcast(vec, root=0) logGATHER('vec', vec) fout = Function(fsout) fout.vector()[:] = griddata(coords, vec, fsout.tabulate_dof_coordinates(), method=method).flatten() fout.vector().apply('insert') return (fout)
def solve(): mesh = UnitIntervalMesh(10) V = FunctionSpace(mesh, "Lagrange", 1) dt = 0.01 bt_params = bulk_temperature.BulkTemperatureParameters(V=V, mesh=mesh, dt=dt) bt = bulk_temperature.BulkTemperature(params=bt_params) a_T, L_T = bt.construct_variation_problem() T = Function(V) bt_params.T_prev.vector().set_local( np.random.uniform(0, -10, bt_params.T_prev.vector().size())) map_params = mass_air_phase.MassAirPhaseParameters(V=V, mesh=mesh, dt=dt) map_params.T_s = T ma_ph = mass_air_phase.MassAirPhase(params=map_params) a_P, L_P = ma_ph.construct_variation_problem() P = Function(V) num_steps = 10 t = 0 T_sols = [bt_params.T_prev.vector().array()] P_sols = [] for n in range(num_steps): bc = bt.make_bcs(V, 0, -10) _, bt_params.T_prev, T = bulk_temperature.step(time=t, dt=dt, t_prev=bt_params.T_prev, t=T, a=a_T, l=L_T, bc=bc) _, map_params.P_prev, P = mass_air_phase.step(time=t, dt=dt, p_prev=map_params.P_prev, p=P, a=a_P, l=L_P) t += dt T_sols.append(T.vector().array()) P_sols.append(P.vector().array()) for T_s in T_sols: plt.plot(T_s) plt.figure() for P_s in P_sols: plt.plot(P_s) plt.show()
def main(): np.random.seed(793817931) degree = 3 mesh = UnitSquareMesh(2, 2) gdim = mesh.geometry().dim() fs = FunctionSpace(mesh, 'CG', degree) f = Function(fs) random_function(f) print(f.vector()[:])
def test_DoGIP_vs_FEniCS(self): print( '\n== testing DoGIP vs. FEniCS for problem of weighted projection ====' ) for dim, pol_order in itertools.product([2, 3], [1, 2]): print('dim={}; pol_order={}'.format(dim, pol_order)) N = 2 # creating MESH, defining MATERIAL and SOURCE if dim == 2: mesh = UnitSquareMesh(N, N) m = Expression("1+10*16*x[0]*(1-x[0])*x[1]*(1-x[1])", degree=3) # material coefficients f = Expression("x[0]*x[0]*x[1]", degree=2) elif dim == 3: mesh = UnitCubeMesh(N, N, N) m = Expression("1+100*x[0]*(1-x[0])*x[1]*x[2]", degree=2) # material coefficients f = Expression("(1-x[0])*x[1]*x[2]", degree=2) mesh.coordinates()[:] += 0.1 * np.random.random( mesh.coordinates().shape) # mesh perturbation ## standard approach with FEniCS ############################################# V = FunctionSpace(mesh, "CG", pol_order) # original FEM space u, v = TrialFunction(V), TestFunction(V) u_fenics = Function(V) solve(m * u * v * dx == m * f * v * dx, u_fenics) ## DoGIP - double-grid integration with interpolation-projection ############# W = FunctionSpace(mesh, "CG", 2 * pol_order) # double-grid space w = TestFunction(W) A_dogip = assemble( m * w * dx).get_local() # diagonal matrix of material coefficients b = assemble(m * f * v * dx) # vector of right-hand side # assembling interpolation-projection matrix B B = get_B(V, W, problem=0) # # linear solver on double grid, standard Afun = lambda x: B.T.dot(A_dogip * B.dot(x)) Alinoper = linalg.LinearOperator((V.dim(), V.dim()), matvec=Afun, dtype=np.float) x, info = linalg.cg(Alinoper, b.get_local(), x0=np.zeros(V.dim()), tol=1e-10, maxiter=1e3, callback=None) # testing the difference between DoGIP and FEniCS self.assertAlmostEqual( 0, np.linalg.norm(u_fenics.vector().get_local() - x)) print('...ok')
def fluxes_from_temperature_full_domain(F, V): """ compute flux from weak form (see p.3 in Toselli, Andrea, and Olof Widlund. Domain decomposition methods-algorithms and theory. Vol. 34. Springer Science & Business Media, 2006.) :param F: weak form with known u^{n+1} :param V: function space :param hy: spatial resolution perpendicular to flux direction :return: """ fluxes_vector = assemble(F) # assemble weak form -> evaluate integral v = TestFunction(V) fluxes = Function(V) # create function for flux area = assemble(v * ds).get_local() for i in range(area.shape[0]): if area[i] != 0: # put weight from assemble on function fluxes.vector()[i] = fluxes_vector[i] / area[i] # scale by surface area else: assert(abs(fluxes_vector[i]) < 10**-10) # for non surface parts, we expect zero flux fluxes.vector()[i] = fluxes_vector[i] return fluxes
def fluxes_from_temperature_full_domain(f, v_vec, k): """Computes flux from weak form (see p.3 in Toselli, Andrea, and Olof Widlund. Domain decomposition methods-algorithms and theory. Vol. 34. Springer Science & Business Media, 2006.). :param f: weak form with known u^{n+1} :param v_vec: vector function space :param k: thermal conductivity :return: fluxes function """ fluxes_vector = assemble(f) # assemble weak form -> evaluate integral v = TestFunction(v_vec) fluxes = Function(v_vec) # create function for flux area = assemble(v * ds).get_local() for i in range(area.shape[0]): if area[i] != 0: # put weight from assemble on function fluxes.vector()[i] = - k * fluxes_vector[i] / area[i] # scale by surface area else: assert (abs(fluxes_vector[i]) < 1E-9) # for non surface parts, we expect zero flux fluxes.vector()[i] = - k * fluxes_vector[i] return fluxes
def get_nearest(self, fn): """ returns a dolfin Function object with values given by interpolated nearest-neighbor data <fn>. """ #FIXME: get to work with a change of projection. # get the dofmap to map from mesh vertex indices to function indicies : df = self.func_space.dofmap() dfmap = df.vertex_to_dof_map(self.mesh) unew = Function(self.func_space) # existing dataset projection uocom = unew.vector().array() # mesh indexed main vertex values d = float64(self.data[fn]) # original matlab spec dataset # get arrays of x-values for specific domain xs = self.x ys = self.y for v in vertices(self.mesh): # mesh vertex x,y coordinate : i = v.index() p = v.point() x = p.x() y = p.y() # indexes of closest datapoint to specific dataset's x and y domains : idx = abs(xs - x).argmin() idy = abs(ys - y).argmin() # data value for closest value : dv = d[idy, idx] if dv > 0: dv = 1.0 uocom[i] = dv # set the values of the empty function's vertices to the data values : unew.vector().set_local(uocom[dfmap]) return unew
def read_dem(bounds, res): """ Function to read in a DEM from SRTM amd interplolate it onto a dolphyn mesh. This function uses the python package 'elevation' (http://elevation.bopen.eu/en/stable/) and the gdal libraries. I will assume you want the 30m resolution SRTM model. :param bounds: west, south, east, north coordinates :return u_n, lx, ly: the elevation interpolated onto the dolphyn mesh and the lengths of the domain """ west, south, east, north = bounds # Create a temporary file to store the DEM and go get it using elevation dem_path = 'tmp.tif' output = os.getcwd() + '/' + dem_path elv.clip(bounds=bounds, output=output, product='SRTM1') # read in the DEM into a numpy array gdal_data = gdal.Open(output) data_array = gdal_data.ReadAsArray().astype(np.float) # The DEM is 30m per pixel, so lets make a array for x and y at 30 m ny, nx = np.shape(data_array) lx = nx * 30 ly = ny * 30 x, y = np.meshgrid(np.linspace(0, lx / ly, nx), np.linspace(0, 1, ny)) # Create mesh and define function space domain = Rectangle(Point(0, 0), Point(lx / ly, 1)) mesh = generate_mesh(domain, res) V = FunctionSpace(mesh, 'P', 1) u_n = Function(V) # Get the global coordinates gdim = mesh.geometry().dim() gc = V.tabulate_dof_coordinates().reshape((-1, gdim)) # Interpolate elevation into the initial condition elevation = interpolate.griddata((x.flatten(), y.flatten()), data_array.flatten(), (gc[:, 0], gc[:, 1]), method='nearest') u_n.vector()[:] = elevation # remove tmp DEM os.remove(output) return u_n, lx, ly, mesh, V
def compute_steady_state(self): names = {'Cl', 'Na', 'K'} P1 = FiniteElement('P', fe.triangle, 1) element = MixedElement([P1, P1, P1]) V = FunctionSpace(self.mesh, element) self.V_conc = V (u_cl, u_na, u_k) = TrialFunction(V) (v_cl, v_na, v_k) = TestFunction(V) assert (self.flow is not None) n = fe.FacetNormal(self.mesh) # F = ( self.F_diff_conv(u_cl, v_cl, n, grad(self.phi), 1. ,1., 0.) # + self.F_diff_conv(u_na, v_na, n, grad(self.phi), 1. ,1., 0.) # + self.F_diff_conv(u_k , v_k , n, grad(self.phi), 1. ,1., 0.) ) dx, ds = self.dx, self.ds flow = self.flow F = inner(grad(u_cl), grad(v_cl)) * dx \ + inner(flow, grad(u_cl)) * v_cl * dx \ + inner(grad(u_na), grad(v_na)) * dx \ + inner(flow, grad(u_na)) * v_na * dx \ + inner(grad(u_k), grad(v_k)) * dx \ + inner(flow, grad(u_k)) * v_k * dx a, L = fe.lhs(F), fe.rhs(F) a_mat = fe.assemble(a) L_vec = fe.assemble(L) # solve u = Function(V) fe.solve(a_mat, u.vector(), L_vec) u_cl, u_na, u_k = u.split() output1 = fe.File('/tmp/steady_state_cl.pvd') output1 << u_cl output2 = fe.File('/tmp/steady_state_na.pvd') output2 << u_na output3 = fe.File('/tmp/steady_state_k.pvd') output3 << u_k self.u_cl = u_cl self.u_na = u_na self.u_k = u_k
def test_solve() -> None: dt = 0.01 mesh = UnitIntervalMesh(10) params = MassAirPhaseParameters(V=None, mesh=mesh, dt=dt) mass_air_phase = MassAirPhase(params=params) a, L = mass_air_phase.construct_variation_problem() print(type(a)) print(type(L)) P = Function(params.V) P_prev = params.P_prev num_steps = 10 t = 0 sols = [] for n in range(num_steps): t, P_prev, P = step(time=t, dt=dt, p_prev=P_prev, p=P, a=a, l=L) sols.append(P.vector().get_local()) for s in sols: plt.plot(s) plt.show()
def test_solve() -> None: mesh = UnitIntervalMesh(10) p = BulkTemperatureParameters(V=None, mesh=mesh, dt=0.01) bulk_temp = BulkTemperature(params=p) V = p.V dt = p.dt T_prev = p.T_prev a, L = bulk_temp.construct_variation_problem() T = Function(V) num_steps = 10 t = 0 sols = [] for n in range(num_steps): bc = bulk_temp.make_bcs(V, 0, -10) t, T_prev, T = step(time=t, dt=dt, t_prev=T_prev, t=T, a=a, l=L, bc=bc) sols.append(T.vector().array()) for s in sols: plt.plot(s) plt.show()
def fluid_to_solid(function, solid: Space, fluid: Space, param: Parameters, subspace_index): function_vector = function.vector() vertex_to_dof_fluid = vertex_to_dof_map( fluid.function_space_split[subspace_index]) result = Function(solid.function_space_split[subspace_index]) result_vector = result.vector() vector_to_dif_solid = vertex_to_dof_map( solid.function_space_split[subspace_index]) horizontal = param.NUMBER_ELEMENTS_HORIZONTAL + 1 vertical = param.NUMBER_ELEMENTS_VERTICAL + 1 for i in range(2): for j in range(horizontal): result_vector[vector_to_dif_solid[ (vertical - i - 1) * horizontal + j]] = function_vector[vertex_to_dof_fluid[i * horizontal + j]] return result
def solve_wave_equation(u0, u1, u_boundary, f, domain, mesh, degree): """Solving the wave equation using CG-CG method. Args: u0: Initial data. u1: Initial velocity. u_boundary: Dirichlet boundary condition. f: Right-hand side. domain: Space-time domain. mesh: Computational mesh. degree: CG(degree) will be used as the finite element. Outputs: uh: Numerical solution. """ # Element V = FunctionSpace(mesh, "CG", degree) # Measures on the initial and terminal slice mask = MeshFunction("size_t", mesh, mesh.topology().dim() - 1, 0) domain.get_initial_slice().mark(mask, 1) ends = ds(subdomain_data=mask) # Form g = Constant(((-1.0, 0.0), (0.0, 1.0))) u = TrialFunction(V) v = TestFunction(V) a = dot(grad(v), dot(g, grad(u))) * dx L = f * v * dx + u1 * v * ends(1) # Assembled matrices A = assemble(a, keep_diagonal=True) b = assemble(L, keep_diagonal=True) # Spatial boundary condition bc = DirichletBC(V, u_boundary, domain.get_spatial_boundary()) bc.apply(A, b) # Temporal boundary conditions (by hand) (A, b) = apply_time_boundary_conditions(domain, V, u0, A, b) # Solve solver = LUSolver() solver.set_operator(A) uh = Function(V) solver.solve(uh.vector(), b) return uh
def morph_fenics(mesh, nodes, u, other_fix=[]): """ Morph using FEniCS Functions. Returns a CG0 Function of DeltaX, such that w = DeltaX / dt """ X_orig = mesh.coordinates().copy() X_defo = X_orig.copy() uN = u.compute_vertex_values().reshape(u.geometric_dimension(), len(nodes)).T X_defo[list(nodes), :] += uN # Warp the mesh X_new = do_tri_map(list(nodes) + list(other_fix), X_defo, X_orig) mesh.coordinates()[:] = X_new # Calculate w from fenics import VectorFunctionSpace, Function V = VectorFunctionSpace(mesh, "CG", 1) DeltaX = Function(V) nodeorder = V.dofmap().dofs(mesh, 0) utot = (X_new - X_orig).ravel() for i, l in enumerate(nodeorder): DeltaX.vector()[l] = utot[i] return DeltaX # w = DeltaX / Dt
def get_B(V, W, problem=1, sparse=True, threshold=1e-14): """ Projection/interpolation operator between spaces V and W on the whole computational domain. Parameters ---------- V : FunctionSpace original finite element space W : FunctionSpace double-grid finite element space problem : {int, string} parameter defining problem: 0 - weighted projection, 1 - elliptic problem sparse : boolean determining the format of output matrix threshold : float parameter under which the values are considered to be zero Returns ------- B : ndarray interpolation matrix between original and double-grid finite element basis """ if problem in [1, 'elliptic']: # elliptic problem operator = grad try: IP = Project_multiple(W) except: IP = project elif problem in [0, 'projection']: # weighted projection operator = lambda x: x IP = interpolate if sparse: # sparse version col = np.array([]) row = np.array([]) data = np.array([]) for ii in range(V.dim()): bfun = Function(V) bfun.vector()[ii] = 1. bfund = IP(operator(bfun), W) vals = bfund.vector().get_local() indc = np.where(np.abs(vals) > threshold)[0] col = np.hstack([col, indc]) row = np.hstack([row, ii * np.ones(indc.size)]) data = np.hstack([data, vals[indc]]) col = np.array(col) row = np.array(row) data = np.array(data) B = scipy.sparse.csr_matrix((data, (row, col)), shape=(V.dim(), W.dim())) else: # dense version B = np.zeros([V.dim(), W.dim()]) for ii in range(V.dim()): bfun = Function(V) bfun.vector()[ii] = 1. bfund = IP(operator(bfun), W) B[ii] = bfund.vector().get_local()() np.putmask(B, np.abs(B) < threshold, 0) B = scipy.sparse.csr_matrix(B) B.eliminate_zeros() return B.T
def main(): nelements = 8 dim = 1 degree = 2 params = { 'alpha': 1, 'beta': 1, 'mu': 0.4, 'Umax': 1, 'Ufac': 4, 'sU': 1, 'sigma': 1, 'N': 1, 'M': 1, 's': 10, 'gamma': 1, 'D': 0.01, 'srho0': 0.01, 'grhopen': 10, } U0str = 'Ufac/dim * (0.25 - pow(x[0] - mu, 2)/(2*sU*sU))' rho0str = 'N*exp(-beta*log((%s)+alpha)/(sigma*sigma/2))' % U0str def Vfunc(U): return -params['beta'] * ufl.ln(U + params['alpha']) mesh = unit_mesh(nelements, dim) fe.parameters["form_compiler"]["representation"] = "uflacs" fe.parameters['linear_algebra_backend'] = 'PETSc' solver = makeKSDGSolver(dim=dim, degree=degree, nelements=nelements, parameters=params, V=Vfunc, U0=U0str, rho0=rho0str, debug=True) print(str(solver)) print(solver.ddt(debug=True)) print("dsol/dt components:", solver.dsol.vector()[:]) eksolver = EKKSDGSolver(dim=dim, degree=degree, nelements=nelements, parameters=params, V=Vfunc, U0=U0str, rho0=rho0str, debug=True) print(str(eksolver)) print(eksolver.ddt(debug=True)) print("dsol/dt components:", eksolver.dsol.vector()[:]) # # try out the time-stepper # np.random.seed(793817931) murho0 = params['N'] / params['M'] Cd1 = fe.FunctionSpace(mesh, 'CG', 1) rho0 = Function(Cd1) rho0.vector()[:] = np.random.normal(murho0, params['srho0'], rho0.vector()[:].shape) U0 = Function(Cd1) U0.vector()[:] = (params['s'] / params['gamma']) * rho0.vector() ksdg = makeKSDGSolver(degree=degree, mesh=mesh, parameters=params, V=Vfunc, U0=U0, rho0=rho0) ksdg.implicitTS() print('Final time point') print(ksdg.history[-1]) ksdg.imExTS() print('Final time point') print(ksdg.history[-1]) ksdg.explicitTS() print('Final time point') print(ksdg.history[-1])
class KSDGSolverPeriodic(KSDGSolver): default_params = dict( rho_min = 1e-7, U_min = 1e-7, width = 1.0, rhopen = 10, Upen = 1, grhopen = 1, gUpen = 1, ) def __init__( self, mesh=None, width=1.0, dim=1, nelements=8, degree=2, parameters={}, V=(lambda U: U), U0=None, rho0=None, t0=0.0, debug=False, solver_type = 'lu', preconditioner_type = 'default', periodic=True, ligands=None ): """DG solver for the periodic Keller-Segel PDE system Keyword parameters: mesh=None: the mesh on which to solve the problem width=1.0: the width of the domain dim=1: # of spatial dimensions. nelements=8: If mesh is not supplied, one will be contructed using UnitIntervalMesh, UnitSquareMesh, or UnitCubeMesh (depending on dim). dim and nelements are not needed if mesh is supplied. degree=2: degree of the polynomial approximation parameters={}: a dict giving the values of scalar parameters of .V, U0, and rho0 Expressions. This dict needs to also define numerical parameters that appear in the PDE. Some of these have defaults: dim = dim: # of spatial dimensions sigma: organism movement rate s: attractant secretion rate gamma: attractant decay rate D: attractant diffusion constant rho_min=10.0**-7: minimum feasible worm density U_min=10.0**-7: minimum feasible attractant concentration rhopen=10: penalty for discontinuities in rho Upen=1: penalty for discontinuities in U grhopen=1, gUpen=1: penalties for discontinuities in gradients V=(lambda U: U): a callable taking two numerical arguments, U and rho, or a single argument, U, and returning a single number, V, the potential corresponding to U. Use fenics versions of mathematical functions, e.g. fe.ln, abs, fe.exp. U0, rho0: Expressions, Functions, or strs specifying the initial condition. t0=0.0: initial time solver_type='lu' preconditioner_type='default' periodic=True: Allowed for compatibility, but ignored ligands=None: ignored for compatibility """ logPERIODIC('creating KSDGSolverPeriodic') self.args = dict( mesh=mesh, width=width, dim=dim, nelements=nelements, degree=degree, parameters=parameters, V=V, U0=U0, rho0=rho0, t0=t0, debug=debug, solver_type = solver_type, preconditioner_type = preconditioner_type, periodic=True, ligands=ligands ) self.debug = debug self.solver_type = solver_type self.preconditioner_type = preconditioner_type self.periodic = True self.params = self.default_params.copy() # # Store the original mesh in self.omesh. self.mesh will be the # corner mesh. # if (mesh): self.omesh = mesh else: self.omesh = box_mesh(width=width, dim=dim, nelements=nelements) self.nelements = nelements try: comm = self.omesh.mpi_comm().tompi4py() except AttributeError: comm = self.omesh.mpi_comm() self.lmesh = gather_mesh(self.omesh) omeshstats = mesh_stats(self.omesh) logPERIODIC('omeshstats', omeshstats) self.xmin = omeshstats['xmin'] self.xmax = omeshstats['xmax'] self.xmid = omeshstats['xmid'] self.delta_ = omeshstats['dx'] self.mesh = corner_submesh(self.lmesh) meshstats = mesh_stats(self.mesh) logPERIODIC('meshstats', meshstats) logPERIODIC('self.omesh', self.omesh) logPERIODIC('self.mesh', self.mesh) logPERIODIC('self.mesh.mpi_comm().size', self.mesh.mpi_comm().size) self.nelements = nelements self.degree = degree self.dim = self.mesh.geometry().dim() self.params['dim'] = self.dim self.params.update(parameters) # # Solution spaces and Functions # # The solution function space is a vector space with # 2*(2**dim) elements. The first 2**dim components are even # and odd parts of rho; These are followed by even and # odd parts of U. The array self.evenodd identifies even # and odd components. Each row is a length dim sequence 0s and # 1s and represnts one component. For instance, if evenodd[i] # is [0, 1, 0], then component i of the vector space is even # in dimensions 0 and 2 (x and z conventionally) and off in # dimension 1 (y). # self.symmetries = evenodd_symmetries(self.dim) self.signs = [fe.as_matrix(np.diagflat(1.0 - 2.0*eo)) for eo in self.symmetries] self.eomat = evenodd_matrix(self.symmetries) fss = self.make_function_space() (self.SE, self.SS, self.VE, self.VS) = [ fss[fs] for fs in ('SE', 'SS', 'VE', 'VS') ] (self.SE, self.SS, self.VE, self.VS) = self.make_function_space() self.sol = Function(self.VS) # sol, current soln logPERIODIC('self.sol', self.sol) # srhos and sUs are fcuntions defiend on subspaces self.srhos = self.sol.split()[:2**self.dim] self.sUs = self.sol.split()[2**self.dim:] # irhos and iUs are Indexed UFL expressions self.irhos = fe.split(self.sol)[:2**self.dim] self.iUs = fe.split(self.sol)[2**self.dim:] self.wrhos = TestFunctions(self.VS)[: 2**self.dim] self.wUs = TestFunctions(self.VS)[2**self.dim :] self.tdsol = TrialFunction(self.VS) # time derivatives self.tdrhos = fe.split(self.tdsol)[: 2**self.dim] self.tdUs = fe.split(self.tdsol)[2**self.dim :] bc_method = 'geometric' if self.dim > 1 else 'pointwise' rhobcs = [DirichletBC( self.VS.sub(i), Constant(0), FacesDomain(self.mesh, self.symmetries[i]), method=bc_method ) for i in range(2**self.dim) if np.any(self.symmetries[i] != 0.0)] Ubcs = [DirichletBC( self.VS.sub(i + 2**self.dim), Constant(0), FacesDomain(self.mesh, self.symmetries[i]), method=bc_method ) for i in range(2**self.dim) if np.any(self.symmetries[i] != 0.0)] self.bcs = rhobcs + Ubcs self.n = FacetNormal(self.mesh) self.h = CellDiameter(self.mesh) self.havg = fe.avg(self.h) self.dx = fe.dx self.dS = fe.dS # # record initial state # if not U0: U0 = Constant(0.0) if isinstance(U0, ufl.coefficient.Coefficient): self.U0 = U0 else: self.U0 = Expression(U0, **self.params, degree=self.degree, domain=self.mesh) if not rho0: rho0 = Constant(0.0) if isinstance(rho0, ufl.coefficient.Coefficient): self.rho0 = rho0 else: self.rho0 = Expression(rho0, **self.params, degree=self.degree, domain=self.mesh) try: V(self.U0, self.rho0) def realV(U, rho): return V(U, rho) except TypeError: def realV(U, rho): return V(U) self.V = realV self.t0 = t0 # # initialize state # # cache assigners logPERIODIC('restarting') self.restart() logPERIODIC('restart returned') return(None) def make_function_space(self, mesh=None, dim=None, degree=None ): if not mesh: mesh = self.mesh if not dim: dim = self.dim if not degree: degree = self.degree SE = FiniteElement('DG', cellShapes[dim-1], degree) SS = FunctionSpace(mesh, SE) # scalar space elements = [SE] * (2*2**self.dim) VE = MixedElement(elements) VS = FunctionSpace(mesh, VE) # vector space logPERIODIC('VS', VS) return dict(SE=SE, SS=SS, VE=VE, VS=VS) def restart(self): logPERIODIC('restart') self.t = self.t0 U0comps = evenodd_functions( omesh=self.omesh, degree=self.degree, func=self.U0, evenodd=self.symmetries, width=self.xmax ) rho0comps = evenodd_functions( omesh=self.omesh, degree=self.degree, func=self.rho0, evenodd=self.symmetries, width=self.xmax ) coords = gather_dof_coords(rho0comps[0].function_space()) for i in range(2**self.dim): fe.assign(self.sol.sub(i), function_interpolate(rho0comps[i], self.SS, coords=coords)) fe.assign(self.sol.sub(i + 2**self.dim), function_interpolate(U0comps[i], self.SS, coords=coords)) def setup_problem(self, debug=False): # # assemble the matrix, if necessary (once for all time points) # if not hasattr(self, 'A'): drho_integral = vectotal( [tdrho*wrho*self.dx for tdrho,wrho in zip(self.tdrhos, self.wrhos)] ) dU_integral = vectotal( [tdU*wU*self.dx for tdU,wU in zip(self.tdUs, self.wUs) ] ) self.A = fe.assemble(drho_integral + dU_integral) for bc in self.bcs: bc.apply(self.A) # if self.solver_type == 'lu': # self.solver = fe.LUSolver( # self.A, # ) # self.solver.parameters['reuse_factorization'] = True # else: # self.solver = fe.KrylovSolver( # self.A, # self.solver_type, # self.preconditioner_type # ) self.dsol = Function(self.VS) self.drhos = self.dsol.split()[: 2**self.dim] self.dUs = self.dsol.split()[2**self.dim :] # # These are the values of rho and U themselves (not their # symmetrized versions) on all subdomains of the original # domain. # if not hasattr(self, 'rhosds'): self.rhosds = matmul(self.eomat, self.irhos) if not hasattr(self, 'Usds'): self.Usds = matmul(self.eomat, self.iUs) # # assemble RHS (for each time point, but compile only once) # if not hasattr(self, 'rho_terms'): self.sigma = self.params['sigma'] self.s2 = self.sigma * self.sigma / 2 self.rho_min = self.params['rho_min'] self.rhopen = self.params['rhopen'] self.grhopen = self.params['grhopen'] # # Compute fluxes on subdomains. # self.Vsds = [self.V(Usd, rhosd) for Usd,rhosd in zip(self.Usds, self.rhosds)] # # I may need to adjust the signs of the subdomain vs by # the symmetries of the combinations # self.vsds = [-ufl.grad(Vsd) - ( self.s2*ufl.grad(rhosd)/ufl.max_value(rhosd, self.rho_min) ) for Vsd,rhosd in zip(self.Vsds, self.rhosds)] self.fluxsds = [vsd * rhosd for vsd,rhosd in zip(self.vsds, self.rhosds)] self.vnsds = [ufl.max_value(ufl.dot(vsd, self.n), 0) for vsd in self.vsds] self.facet_fluxsds = [( vnsd('+')*ufl.max_value(rhosd('+'), 0.0) - vnsd('-')*ufl.max_value(rhosd('-'), 0.0) ) for vnsd,rhosd in zip(self.vnsds, self.rhosds)] # # Now combine the subdomain fluxes to get the fluxes for # the symmetrized functions # self.fluxs = matmul((2.0**-self.dim)*self.eomat, self.fluxsds) self.facet_fluxs = matmul((2.0**-self.dim)*self.eomat, self.facet_fluxsds) self.rho_flux_jump = vectotal( [-facet_flux*ufl.jump(wrho)*self.dS for facet_flux,wrho in zip(self.facet_fluxs, self.wrhos)] ) self.rho_grad_move = vectotal( [ufl.dot(flux, ufl.grad(wrho))*self.dx for flux,wrho in zip(self.fluxs, self.wrhos)] ) self.rho_penalty = vectotal( [-(self.rhopen * self.degree**2 / self.havg) * ufl.dot(ufl.jump(rho, self.n), ufl.jump(wrho, self.n)) * self.dS for rho,wrho in zip(self.irhos, self.wrhos)] ) self.grho_penalty = vectotal( [-self.grhopen * self.degree**2 * (ufl.jump(ufl.grad(rho), self.n) * ufl.jump(ufl.grad(wrho), self.n)) * self.dS for rho,wrho in zip(self.irhos, self.wrhos)] ) self.rho_terms = ( self.rho_flux_jump + self.rho_grad_move + self.rho_penalty + self.grho_penalty ) if not hasattr(self, 'U_terms'): self.U_min = self.params['U_min'] self.gamma = self.params['gamma'] self.s = self.params['s'] self.D = self.params['D'] self.Upen = self.params['Upen'] self.gUpen = self.params['gUpen'] self.U_decay = vectotal( [-self.gamma * U * wU * self.dx for U,wU in zip(self.iUs, self.wUs)] ) self.U_secretion = vectotal( [self.s * rho * wU * self.dx for rho, wU in zip(self.irhos, self.wUs)] ) self.jump_gUw = vectotal( [self.D * ufl.jump(wU * ufl.grad(U), self.n) * self.dS for wU, U in zip(self.wUs, self.iUs) ] ) self.U_diffusion = vectotal( [-self.D * ufl.dot(ufl.grad(U), ufl.grad(wU))*self.dx for U,wU in zip(self.iUs, self.wUs) ] ) self.U_penalty = vectotal( [-(self.Upen * self.degree**2 / self.havg) * ufl.dot(ufl.jump(U, self.n), ufl.jump(wU, self.n))*self.dS for U,wU in zip(self.iUs, self.wUs) ] ) self.gU_penalty = vectotal( [-self.gUpen * self.degree**2 * ufl.jump(ufl.grad(U), self.n) * ufl.jump(ufl.grad(wU), self.n) * self.dS for U,wU in zip(self.iUs, self.wUs) ] ) self.U_terms = ( # decay and secretion self.U_decay + self.U_secretion + # diffusion self.jump_gUw + self.U_diffusion + # penalties (to enforce continuity) self.U_penalty + self.gU_penalty ) if not hasattr(self, 'all_terms'): self.all_terms = self.rho_terms + self.U_terms if not hasattr(self, 'J_terms'): self.J_terms = fe.derivative(self.all_terms, self.sol) # if not hasattr(self, 'JU_terms'): # self.JU_terms = [fe.derivative(self.all_terms, U) # for U in self.Us] # if not hasattr(self, 'Jrho_terms'): # self.Jrho_terms = [fe.derivative(self.all_terms, rho) # for rho in self.rhos] def ddt(self, debug=False): """Calculate time derivative of rho and U Results are left in self.dsol as a two-component vector function. """ self.setup_problem(debug) self.b = fe.assemble(self.all_terms) for bc in self.bcs: bc.apply(self.b) return fe.solve(self.A, self.dsol.vector(), self.b, self.solver_type)
def theta_method(A, f, ah, bh, θ, k, T): """θ-method time-stepper. Abstract problem: u'' + Au = f, u(0) = ah, u'(0) = bh is formulated as a first-order system: u' - v = 0, v' + Au = f, u(0) = ah, v(0) = bh. Args: A (Function -> Function -> Form): Possibly nonlinear functional A(u, v) f (float -> Function): Right-hand side ah (Function): Initial value bh (Function): Initial velocity k (float): Time step T (float): Max time Returns: list(float): Times list(Function): Solution at each time """ # pylint: disable=invalid-name, too-many-arguments, too-many-locals # Basic setup V = ah.function_space() u = TrialFunction(V) w = TestFunction(V) v = TrialFunction(V) y = TestFunction(V) α = k * θ β = k * (1.0 - θ) # Prepare initial condition u0 = Function(V) u0.vector()[:] = ah.vector() v0 = Function(V) v0.vector()[:] = bh.vector() # Initialize time stepper t = k u1 = Function(V) u1.vector()[:] = u0.vector() v1 = Function(V) v1.vector()[:] = v0.vector() ts = [0] uh = [interpolate(u0, V)] progress = -1 print("Progress: ", end="") while t < T: # Print progress pct = int(t / T * 100) // 10 * 10 if pct > progress: print("{}%..".format(pct), end="") progress = pct # Solve for the next u (nonlinear) lhs = (dot(u, w) + α**2 * A(u, w)) * dx rhs = (dot(u0, w) - α * β * A(u0, w) + k * dot(v0, w) + α**2 * f(t + k, w) + α * β * f(t, w)) * dx act = action(lhs - rhs, u1) J = derivative(act, u1) problem = NonlinearVariationalProblem(act, u1, [], J) solver = NonlinearVariationalSolver(problem) solver.parameters["newton_solver"]["linear_solver"] = "gmres" solver.parameters["newton_solver"]["preconditioner"] = "ilu" try: solver.solve() except RuntimeError: print("blowup at t={}.".format(t)) return (ts, uh) # Solve for the next v (linear) lhs = dot(v, y) * dx rhs = (dot(u1 - u0, y) - β * dot(v0, w)) / α * dx problem = LinearVariationalProblem(lhs, rhs, v1) solver = LinearVariationalSolver(problem) solver.parameters["linear_solver"] = "cg" solver.parameters["preconditioner"] = "hypre_amg" solver.solve() # Update t += k u0.vector()[:] = u1.vector() v0.vector()[:] = v1.vector() # Record result ts.append(t) uh.append(interpolate(u1, V)) print("done") return (ts, uh)
def leapfrog(A, f, ah, bh, k, T): """Leapfrog time-stepper. Abstract problem: u'' + Au = f, u(0) = ah, u'(0) = bh Args: A (Function -> Function -> Form): Possibly nonlinear functional A(u, v) f (float -> Function): Right-hand side ah (Function): Initial value bh (Function): Initial velocity k (float): Time step T (float): Max time Returns: list(float): Times list(Function): Solution at each time """ # pylint: disable=invalid-name, too-many-arguments, too-many-locals # Basic setup V = ah.function_space() u = TrialFunction(V) v = TestFunction(V) # Prepare initial condition u0 = Function(V) u0.vector()[:] = ah.vector() u1 = Function(V) u1.vector()[:] = ah.vector() + k * bh.vector() # Initialize time stepper t = k u2 = Function(V) u2.vector()[:] = u1.vector() ts = [0, t] uh = [interpolate(u0, V), interpolate(u1, V)] progress = -1 print("Progress: ", end="") while t < T: # Print progress pct = int(t / T * 100) // 10 * 10 if pct > progress: print("{}%..".format(pct), end="") progress = pct # Solve lhs = dot(u, v) * dx rhs = (dot(2 * u1 - u0, v) + k * k * (f(t, v) - A(u1, v))) * dx problem = LinearVariationalProblem(lhs, rhs, u2) solver = LinearVariationalSolver(problem) solver.parameters["linear_solver"] = "cg" solver.parameters["preconditioner"] = "hypre_amg" try: solver.solve() except RuntimeError: print("blowup at t={}.".format(t)) return (ts, uh) # Update t += k u0.vector()[:] = u1.vector() u1.vector()[:] = u2.vector() # Record result ts.append(t) uh.append(interpolate(u1, V)) print("done") return (ts, uh)
def solve(self): """ Performs the physics, evaluating and updating the enthalpy and age as well as storing the velocity, temperature, and the age in vtk files. """ model = self.model config = self.config t = config['t_start'] t_end = config['t_end'] dt = config['time_step'] thklim = config['free_surface']['thklim'] mesh = model.mesh smb = model.smb sigma = model.sigma S = model.S B = model.B smb.interpolate(config['free_surface']['observed_smb']) if config['periodic_boundary_conditions']: d2v = dof_to_vertex_map(model.Q_non_periodic) mhat_non = Function(model.Q_non_periodic) else: d2v = dof_to_vertex_map(model.Q) # Loop over all times while t <= t_end: B_a = B.compute_vertex_values() S_v = S.compute_vertex_values() tic = time() S_0 = S_v f_0 = self.rhs_func_explicit(t, S_0) S_1 = S_0 + dt * f_0 S_1[(S_1 - B_a) < thklim] = thklim + B_a[(S_1 - B_a) < thklim] S.vector().set_local(S_1[d2v]) S.vector().apply('') f_1 = self.rhs_func_explicit(t, S_1) S_2 = 0.5 * S_0 + 0.5 * S_1 + 0.5 * dt * f_1 S_2[(S_2 - B_a) < thklim] = thklim + B_a[(S_2 - B_a) < thklim] S.vector().set_local(S_2[d2v]) S.vector().apply('') mesh.coordinates( )[:, 2] = sigma.compute_vertex_values() * (S_2 - B_a) + B_a if config['periodic_boundary_conditions']: temp = (S_2[d2v] - S_0[d2v]) / dt * sigma.vector().get_local() mhat_non.vector().set_local(temp) mhat_non.vector().apply('') m_temp = project(mhat_non, model.Q) model.mhat.vector().set_local(m_temp.vector().get_local()) model.mhat.vector().apply('') else: temp = (S_2[d2v] - S_0[d2v]) / dt * sigma.vector().get_local() model.mhat.vector().set_local(temp) model.mhat.vector().apply('') # Calculate enthalpy update if self.config['enthalpy']['on']: self.enthalpy_instance.solve(H0=model.H, Hhat=model.H, uhat=model.u, vhat=model.v, what=model.w, mhat=model.mhat) if self.config['log']: self.file_T << (model.T, t) # Calculate age update if self.config['age']['on']: self.age_instance.solve(A0=model.A, Ahat=model.A, uhat=model.u, vhat=model.v, what=model.w, mhat=model.mhat) if config['log']: self.file_a << (model.age, t) # Store velocity, temperature, and age to vtk files if self.config['log']: self.t_log.append(t) M = assemble(self.surface_instance.M) self.mass.append(M) # Increment time step if MPI.rank(mpi_comm_world()) == 0: string = 'Time: {0}, CPU time for last time step: {1}, Mass: {2}' print string.format(t, time() - tic, M / self.M_prev) self.M_prev = M t += dt self.step_time.append(time() - tic)
def mfd_cellcell(mesh, V, u_n, De, nexp): """ MFD cell-to-cell Flow routing distributed from cell-to-cell :param mesh: mesh object generated using mshr (fenics) :param V: finite element function space :param u_n: solution (trial function) for water flux :param De: dimensionless diffusion coefficient :param nexp: water flux exponent :return: """ # get a map of neighbours (thanks google!) tdim = mesh.topology().dim() mesh.init(tdim - 1, tdim) cell_neighbors = np.array([ sum((filter(lambda ci: ci != cell.index(), facet.entities(tdim)) for facet in facets(cell)), []) for cell in cells(mesh) ]) # first get the elevation area of each element dofmap = V.dofmap() elevation = [] area = [] xm = [] ym = [] for cell in cells(mesh): cellnodes = dofmap.cell_dofs(cell.index()) elevation.append(sum(u_n.vector()[cellnodes]) / 3) area.append(cell.volume()) p = cell.midpoint() xm.append(p.x()) ym.append(p.y()) elevation = np.array(elevation) area = np.array(area) xm = np.array(xm) ym = np.array(ym) # now sort the vector of elevations by decending topography ind = np.argsort(-elevation) sorted_neighbors = cell_neighbors[ind] # determine length between elements steep_len = [] for cell in cells(mesh): xh = xm[cell.index()] yh = ym[cell.index()] neicells = cell_neighbors[cell.index()] tnei = elevation[neicells] imin = np.argmin(tnei) ncell = neicells[imin] xn = xm[ncell] yn = ym[ncell] steep_len.append(np.sqrt((xh - xn) * (xh - xn) + (yh - yn) * (yh - yn))) steep_len = np.array(steep_len) flux = area / steep_len # determine flux from highest to lowest cells for cell in cells(mesh): neicells = sorted_neighbors[cell.index()] tnei = elevation[neicells] imin = np.argmin(tnei) ncell = neicells[imin] weight = np.zeros(len(neicells)) i = 0 for neicell in neicells: weight[i] = elevation[ind[cell.index()]] - elevation[neicell] # downhill only if weight[i] < 0: weight[i] = 0 i += 1 # weight flux by the sum of the lengths down slope if max(weight) > 0: weight = weight / sum(weight) else: weight[:] = 0 i = 0 for neicell in neicells: flux[neicell] = flux[neicell] + flux[ind[cell.index()]] * weight[i] i += 1 # interpolate to the nodes gc = mesh.coordinates() flux_node = np.zeros(len(gc)) for cell in cells(mesh): cellnodes = dofmap.cell_dofs(cell.index()) for nod in cellnodes: flux_node[nod] = flux_node[nod] + flux[cell.index()] / 3 q = Function(V) q.vector()[:] = 1 + De * pow(flux_node, nexp) return (q)
def sd_nodenode(mesh, V, u_n, De, nexp): """ SD node-to-node Flow routing from node-to-node based on the steepest route of descent :param mesh: mesh object generated using mshr (fenics) :param V: finite element function space :param u_n: solution (trial function) for water flux :param De: dimensionless diffusion coefficient :param nexp: water flux exponent :return: """ # get the global coordinates gdim = mesh.geometry().dim() if dolfin.dolfin_version() == '1.6.0': dofmap = V.dofmap() gc = dofmap.tabulate_all_coordinates(mesh).reshape((-1, gdim)) else: gc = V.tabulate_dof_coordinates().reshape((-1, gdim)) vtd = vertex_to_dof_map(V) # first get the elevation of each vertex elevation = np.zeros(len(gc)) elevation = u_n.compute_vertex_values(mesh) # loop to get the local flux mesh.init(0, 1) flux = np.zeros(len(gc)) neighbors = [] for v in vertices(mesh): idx = v.index() # get the local neighbourhood neighborhood = [Edge(mesh, i).entities(0) for i in v.entities(1)] neighborhood = np.array(neighborhood).flatten() # Remove own index from neighborhood neighborhood = neighborhood[np.where(neighborhood != idx)[0]] neighbors.append(neighborhood) # get location xh = v.x(0) yh = v.x(1) # get distance to neighboring vertices length = np.zeros(len(neighborhood)) weight = np.zeros(len(neighborhood)) i = 0 for vert in neighborhood: nidx = vtd[vert] xn = gc[nidx, 0] yn = gc[nidx, 1] length[i] = np.sqrt((xh - xn) * (xh - xn) + (yh - yn) * (yh - yn)) flux[vert] = length[i] # weight[i] = elevation[idx] - elevation[vert] # # downhill only # if weight[i] < 0: # weight[i] = 0 # i += 1 # # # find steepest slope # steepest = len(neighborhood)+2 # if max(weight) > 0: # steepest = np.argmax(weight) # else: # weight[:] = 0 # i = 0 # for vert in neighborhood: # if i == steepest: # weight[i] = 1 # else: # weight[i] = 0 # flux[vert] = flux[vert] + length[i]*weight[i] # i += 1 # sort from top to botton sortedidx = np.argsort(-elevation) # accumulate fluxes from top to bottom for idx in sortedidx: neighborhood = neighbors[idx] weight = np.zeros(len(neighborhood)) i = 0 for vert in neighborhood: weight[i] = elevation[idx] - elevation[vert] # downhill only if weight[i] < 0: weight[i] = 0 i += 1 # find steepest slope steepest = len(neighborhood) + 2 if max(weight) > 0: steepest = np.argmax(weight) else: weight[:] = 0 i = 0 for vert in neighborhood: if i == steepest: weight[i] = 1 else: weight[i] = 0 flux[vert] = flux[vert] + flux[idx] * weight[i] i += 1 # calculate the diffusion coefficient q0 = 1 + De * pow(flux, nexp) q = Function(V) q.vector()[:] = q0[dof_to_vertex_map(V)] return (q)
forces_x, forces_y, forces_z = precice.get_point_sources(read_data) A, b = assemble_system(a_form, L_form, bc) b_forces = b.copy( ) # b is the same for every iteration, only forces change for ps in forces_x: ps.apply(b_forces) for ps in forces_y: ps.apply(b_forces) for ps in forces_z: ps.apply(b_forces) assert (b is not b_forces) solve(A, u_np1.vector(), b_forces) dt = Constant(np.min([precice_dt, fenics_dt])) # Write relative displacements to preCICE u_delta.vector()[:] = u_np1.vector()[:] - u_n.vector()[:] precice.write_data(u_delta) # Call to advance coupling, also returns the optimum time step value precice_dt = precice.advance(dt(0)) # Either revert to old step if timestep has not converged or move to next timestep if precice.is_action_required(precice.action_read_iteration_checkpoint() ): # roll back to checkpoint u_cp, t_cp, n_cp = precice.retrieve_checkpoint() u_n.assign(u_cp)
# Update the point sources on the coupling boundary with the new read data Forces_x, Forces_y = precice.get_point_sources(read_data) A, b = assemble_system(a_form, L_form, bc) b_forces = b.copy( ) # b is the same for every iteration, only forces change for ps in Forces_x: ps.apply(b_forces) for ps in Forces_y: ps.apply(b_forces) assert (b is not b_forces) solve(A, u_np1.vector(), b_forces) dt = Constant(np.min([precice_dt, fenics_dt])) # Write new displacements to preCICE precice.write_data(u_np1) # Call to advance coupling, also returns the optimum time step value precice_dt = precice.advance(dt(0)) # Either revert to old step if timestep has not converged or move to next timestep if precice.is_action_required(precice.action_read_iteration_checkpoint() ): # roll back to checkpoint u_cp, t_cp, n_cp = precice.retrieve_checkpoint() u_n.assign(u_cp) t = t_cp
class KSDGSolverVariablePeriodic(KSDGSolverVariable, KSDGSolverPeriodic): default_params = collections.OrderedDict( sigma=1.0, rhomin=1e-7, Umin=1e-7, width=1.0, rhopen=10.0, Upen=1.0, grhopen=1.0, gUpen=1.0, ) def __init__(self, mesh=None, width=1.0, dim=1, nelements=8, degree=2, parameters={}, param_funcs={}, V=(lambda U, params={}: sum(U)), U0=[], rho0=None, t0=0.0, debug=False, solver_type='petsc', preconditioner_type='default', periodic=True, ligands=None): """Discontinuous Galerkin solver for the Keller-Segel PDE system Like KSDGSolverVariable, but with periodic boundary conditions. """ logVARIABLE('creating KSDGSolverVariablePeriodic') if not ligands: ligands = LigandGroups() else: ligands = copy.deepcopy(ligands) self.args = dict(mesh=mesh, width=width, dim=dim, nelements=nelements, degree=degree, parameters=parameters, param_funcs=param_funcs, V=V, U0=U0, rho0=rho0, t0=t0, debug=debug, solver_type=solver_type, preconditioner_type=preconditioner_type, periodic=True, ligands=ligands) self.t0 = t0 self.debug = debug self.solver_type = solver_type self.preconditioner_type = preconditioner_type self.periodic = True self.ligands = ligands self.nligands = ligands.nligands() self.init_params(parameters, param_funcs) if nelements is None: self.nelements = 8 else: self.nelements = nelements if (mesh): self.omesh = self.mesh = mesh else: self.omesh = self.mesh = box_mesh(width=width, dim=dim, nelements=self.nelements) self.nelements = nelements omeshstats = mesh_stats(self.omesh) try: comm = self.omesh.mpi_comm().tompi4py() except AttributeError: comm = self.omesh.mpi_comm() self.lmesh = gather_mesh(self.omesh) logVARIABLE('omeshstats', omeshstats) self.xmin = omeshstats['xmin'] self.xmax = omeshstats['xmax'] self.xmid = omeshstats['xmid'] self.delta_ = omeshstats['dx'] if nelements is None: self.nelements = (self.xmax - self.xmin) / self.delta_ self.mesh = corner_submesh(self.lmesh) meshstats = mesh_stats(self.mesh) self.degree = degree self.dim = self.mesh.geometry().dim() # # Solution spaces and Functions # self.symmetries = evenodd_symmetries(self.dim) self.signs = [ fe.as_matrix(np.diagflat(1.0 - 2.0 * eo)) for eo in self.symmetries ] self.eomat = evenodd_matrix(self.symmetries) fss = self.make_function_space() (self.SE, self.SS, self.VE, self.VS) = [fss[fs] for fs in ('SE', 'SS', 'VE', 'VS')] logVARIABLE('self.VS', self.VS) self.sol = Function(self.VS) # sol, current soln logVARIABLE('self.sol', self.sol) splitsol = self.sol.split() self.srhos = splitsol[:2**self.dim] self.sUs = splitsol[2**self.dim:] splitsol = list(fe.split(self.sol)) self.irhos = splitsol[:2**self.dim] self.iUs = splitsol[2**self.dim:] self.iPs = list(fe.split(self.PSf)) self.iparams = collections.OrderedDict(zip(self.param_names, self.iPs)) self.iligands = copy.deepcopy(self.ligands) self.iligand_params = ParameterList( [p for p in self.iligands.params() if p[0] in self.param_numbers]) for k in self.iligand_params.keys(): i = self.param_numbers[k] self.iligand_params[k] = self.iPs[i] tfs = list(TestFunctions(self.VS)) self.wrhos, self.wUs = tfs[:2**self.dim], tfs[2**self.dim:] tfs = list(TrialFunctions(self.VS)) self.tdrhos, self.tdUs = tfs[:2**self.dim], tfs[2**self.dim:] bc_method = 'geometric' if self.dim > 1 else 'pointwise' rhobcs = [ DirichletBC(self.VS.sub(i), Constant(0), FacesDomain(self.mesh, self.symmetries[i]), method=bc_method) for i in range(2**self.dim) if np.any(self.symmetries[i] != 0.0) ] Ubcs = list( itertools.chain(*[[ DirichletBC(self.VS.sub(i + (lig + 1) * 2**self.dim), Constant(0), FacesDomain(self.mesh, self.symmetries[i]), method=bc_method) for i in range(2**self.dim) if np.any(self.symmetries[i] != 0.0) ] for lig in range(self.nligands)])) self.bcs = rhobcs + Ubcs self.n = FacetNormal(self.mesh) self.h = CellDiameter(self.mesh) self.havg = fe.avg(self.h) self.dx = fe.dx self.dS = fe.dS # # record initial state # if not U0: U0 = [Constant(0.0)] * self.nligands self.U0s = [Constant(0.0)] * self.nligands for i, U0i in enumerate(U0): if isinstance(U0i, ufl.coefficient.Coefficient): self.U0s[i] = U0i else: self.U0s[i] = Expression(U0i, **self.params, degree=self.degree, domain=self.mesh) if not rho0: rho0 = Constant(0.0) if isinstance(rho0, ufl.coefficient.Coefficient): self.rho0 = rho0 else: self.rho0 = Expression(rho0, **self.params, degree=self.degree, domain=self.mesh) self.set_time(t0) # # work out how to call V # try: V(self.U0s, self.rho0, params=self.iparams) def realV(Us, rho): return V(Us, rho, params=self.iparams) except TypeError: def realV(Us, rho): return V(Us, self.iparams) self.V = realV # # initialize state # self.restart() return None def make_function_space(self, mesh=None, dim=None, degree=None): if not mesh: mesh = self.mesh if not dim: dim = self.dim if not degree: degree = self.degree SE = FiniteElement('DG', cellShapes[dim - 1], degree) SS = FunctionSpace(mesh, SE) # scalar space elements = [SE] * ((self.nligands + 1) * 2**self.dim) VE = MixedElement(elements) VS = FunctionSpace(mesh, VE) # vector space return dict(SE=SE, SS=SS, VE=VE, VS=VS) def restart(self): logVARIABLE('restart') self.set_time(self.t0) U0comps = [None] * self.nligands * 2**self.dim for i, U0i in enumerate(self.U0s): eofuncs = evenodd_functions(omesh=self.omesh, degree=self.degree, func=U0i, evenodd=self.symmetries, width=self.xmax) U0comps[i * 2**self.dim:(i + 1) * 2**self.dim] = eofuncs rho0comps = evenodd_functions(omesh=self.omesh, degree=self.degree, func=self.rho0, evenodd=self.symmetries, width=self.xmax) coords = gather_dof_coords(rho0comps[0].function_space()) for i in range(2**self.dim): fe.assign( self.sol.sub(i), function_interpolate(rho0comps[i], self.SS, coords=coords)) for i in range(self.nligands * 2**self.dim): fe.assign(self.sol.sub(i + 2**self.dim), function_interpolate(U0comps[i], self.SS, coords=coords)) def setup_problem(self, t, debug=False): self.set_time(t) # # assemble the matrix, if necessary (once for all time points) # if not hasattr(self, 'A'): logVARIABLE('making matrix A') self.drho_integral = sum([ tdrho * wrho * self.dx for tdrho, wrho in zip(self.tdrhos, self.wrhos) ]) self.dU_integral = sum( [tdU * wU * self.dx for tdU, wU in zip(self.tdUs, self.wUs)]) logVARIABLE('assembling A') self.A = fe.PETScMatrix() logVARIABLE('self.A', self.A) fe.assemble(self.drho_integral + self.dU_integral, tensor=self.A) logVARIABLE('A assembled. Applying BCs') pA = fe.as_backend_type(self.A).mat() Adiag = pA.getDiagonal() logVARIABLE('Adiag.array', Adiag.array) # self.A = fe.assemble(self.drho_integral + self.dU_integral + # self.dP_integral) for bc in self.bcs: bc.apply(self.A) Adiag = pA.getDiagonal() logVARIABLE('Adiag.array', Adiag.array) self.dsol = Function(self.VS) dsolsplit = self.dsol.split() self.drhos, self.dUs = (dsolsplit[:2**self.dim], dsolsplit[2**self.dim:]) # # assemble RHS (for each time point, but compile only once) # # # These are the values of rho and U themselves (not their # symmetrized versions) on all subdomains of the original # domain. # if not hasattr(self, 'rhosds'): self.rhosds = matmul(self.eomat, self.irhos) # self.Usds is a list of nligands lists. Sublist i is of # length 2**dim and lists the value of ligand i on each of the # 2**dim subdomains. # if not hasattr(self, 'Usds'): self.Usds = [ matmul(self.eomat, self.iUs[i * 2**self.dim:(i + 1) * 2**self.dim]) for i in range(self.nligands) ] if not hasattr(self, 'rho_terms'): logVARIABLE('making rho_terms') self.sigma = self.iparams['sigma'] self.s2 = self.sigma * self.sigma / 2 self.rhomin = self.iparams['rhomin'] self.rhopen = self.iparams['rhopen'] self.grhopen = self.iparams['grhopen'] # # Compute fluxes on subdomains. # Vsds is a list of length 2**dim, the value of V on each # subdomain. # self.Vsds = [] for Usd, rhosd in zip(zip(*self.Usds), self.rhosds): self.Vsds.append(self.V(Usd, ufl.max_value(rhosd, self.rhomin))) self.vsds = [ -ufl.grad(Vsd) - (self.s2 * ufl.grad(rhosd) / ufl.max_value(rhosd, self.rhomin)) for Vsd, rhosd in zip(self.Vsds, self.rhosds) ] self.fluxsds = [ vsd * rhosd for vsd, rhosd in zip(self.vsds, self.rhosds) ] self.vnsds = [ ufl.max_value(ufl.dot(vsd, self.n), 0) for vsd in self.vsds ] self.facet_fluxsds = [ (vnsd('+') * ufl.max_value(rhosd('+'), 0.0) - vnsd('-') * ufl.max_value(rhosd('-'), 0.0)) for vnsd, rhosd in zip(self.vnsds, self.rhosds) ] # # Now combine the subdomain fluxes to get the fluxes for # the symmetrized functions # self.fluxs = matmul((2.0**-self.dim) * self.eomat, self.fluxsds) self.facet_fluxs = matmul((2.0**-self.dim) * self.eomat, self.facet_fluxsds) self.rho_flux_jump = sum([ -facet_flux * ufl.jump(wrho) * self.dS for facet_flux, wrho in zip(self.facet_fluxs, self.wrhos) ]) self.rho_grad_move = sum([ ufl.dot(flux, ufl.grad(wrho)) * self.dx for flux, wrho in zip(self.fluxs, self.wrhos) ]) self.rho_penalty = sum([ -(self.degree**2 / self.havg) * ufl.dot(ufl.jump(rho, self.n), ufl.jump(self.rhopen * wrho, self.n)) * self.dS for rho, wrho in zip(self.irhos, self.wrhos) ]) self.grho_penalty = sum([ self.degree**2 * (ufl.jump(ufl.grad(rho), self.n) * ufl.jump(ufl.grad(-self.grhopen * wrho), self.n)) * self.dS for rho, wrho in zip(self.irhos, self.wrhos) ]) self.rho_terms = (self.rho_flux_jump + self.rho_grad_move + self.rho_penalty + self.grho_penalty) logVARIABLE('rho_terms made') if not hasattr(self, 'U_terms'): logVARIABLE('making U_terms') self.Umin = self.iparams['Umin'] self.Upen = self.iparams['Upen'] self.gUpen = self.iparams['gUpen'] self.U_decay = 0.0 self.U_secretion = 0.0 self.jump_gUw = 0.0 self.U_diffusion = 0.0 self.U_penalty = 0.0 self.gU_penalty = 0.0 for j, lig in enumerate(self.iligands.ligands()): sl = slice(j * 2**self.dim, (j + 1) * 2**self.dim) self.U_decay += sum([ -lig.gamma * iUi * wUi * self.dx for iUi, wUi in zip(self.iUs[sl], self.wUs[sl]) ]) self.U_secretion += sum([ lig.s * rho * wU * self.dx for rho, wU in zip(self.irhos, self.wUs[sl]) ]) self.jump_gUw += sum([ ufl.jump(lig.D * wU * ufl.grad(U), self.n) * self.dS for wU, U in zip(self.wUs[sl], self.iUs[sl]) ]) self.U_diffusion += sum([ -lig.D * ufl.dot(ufl.grad(U), ufl.grad(wU)) * self.dx for U, wU in zip(self.iUs[sl], self.wUs[sl]) ]) self.U_penalty += sum([ (-self.degree**2 / self.havg) * ufl.dot(ufl.jump(U, self.n), ufl.jump(self.Upen * wU, self.n)) * self.dS for U, wU in zip(self.iUs[sl], self.wUs[sl]) ]) self.gU_penalty += sum([ -self.degree**2 * ufl.jump(ufl.grad(U), self.n) * ufl.jump(ufl.grad(self.gUpen * wU), self.n) * self.dS for U, wU in zip(self.iUs[sl], self.wUs[sl]) ]) self.U_terms = ( # decay and secretion self.U_decay + self.U_secretion + # diffusion self.jump_gUw + self.U_diffusion + # penalties (to enforce continuity) self.U_penalty + self.gU_penalty) logVARIABLE('U_terms made') if not hasattr(self, 'all_terms'): logVARIABLE('making all_terms') self.all_terms = self.rho_terms + self.U_terms if not hasattr(self, 'J_terms'): logVARIABLE('making J_terms') self.J_terms = fe.derivative(self.all_terms, self.sol) def ddt(self, t, debug=False): """Calculate time derivative of rho and U Results are left in self.dsol as a two-component vector function. """ self.setup_problem(t, debug=debug) self.b = fe.assemble(self.all_terms) for bc in self.bcs: bc.apply(self.b) return fe.solve(self.A, self.dsol.vector(), self.b, self.solver_type)
class ExpandedMesh: def __init__(self, function): """Expand a Function defined on a corner mesh to a full mesh. Required parameter: funcion: the Function to be expanded. """ self.VS = function.function_space() self.submesh = self.VS.mesh() self.dim = self.submesh.geometric_dimension() self.nss = self.VS.num_sub_spaces() self.nfields = self.nss // (2**self.dim) self.subcoords = self.submesh.coordinates() self.vtrans = integerify_transform(self.subcoords) self.icols = ['i' + str(i) for i in range(self.dim)] self.emesh, self.maps, self.cellmaps, self.vertexmaps = (expand_mesh( self.submesh, self.vtrans)) self.dcoords = np.reshape(self.VS.tabulate_dof_coordinates(), (-1, self.dim)) self.dtrans = integerify_transform(self.dcoords) self.eSE = scalar_element(self.VS) self.degree = self.eSE.degree() self.eVE = MixedElement([self.eSE] * self.nfields) self.eVS = FunctionSpace(self.emesh, self.eVE) self.sub_dof_list = dof_list(self.VS, self.dtrans) subs = self.sub_dof_list['sub'].values self.sub_dof_list['submesh'] = subs % (2**self.dim) self.sub_dof_list['field'] = subs // (2**self.dim) sc = self.sub_dof_list[['submesh', 'cell']].values self.sub_dof_list['ecell'] = self.cellmaps[(sc[:, 0], sc[:, 1])] self.e_dof_list = dof_list(self.eVS, self.dtrans) self.remap = self.sub2e_map() self.symmetries = evenodd_symmetries(self.dim) self.eomat = evenodd_matrix(self.symmetries) self.sub_function = Function(self.VS) self.expanded_function = Function(self.eVS) def expand(self): fvec = self.sub_function.vector()[:] fvec = np.reshape(fvec, (-1, 2**self.dim)) fvec = np.matmul(fvec, self.eomat) fvec = fvec.flatten() fvec = fvec[self.remap] self.expanded_function.vector()[:] = fvec def sub2e_map(self): cs = list(range(self.dim)) sdlcols = ['dof', 'submesh', 'field', 'ecell'] + self.icols sdls = pd.DataFrame(columns=sdlcols + cs) sdl = self.sub_dof_list.copy() for submesh in range(2**self.dim): sdlsm = sdl[sdl['submesh'] == submesh].copy() sdlsm[cs] = self.maps[submesh](sdlsm[cs]) sdls = sdls.append(sdlsm[sdlcols + cs], sort=False) sdls[self.icols] = integerify(sdls[cs].values, transform=self.dtrans) sdls.sort_values('dof', inplace=True) sdls[sdlcols] = np.array(sdls[sdlcols].values, dtype='int') remapdf = self.e_dof_list.merge(right=sdls, left_on=['sub', 'cell'] + self.icols, right_on=['field', 'ecell'] + self.icols) assert (self.e_dof_list.shape[0] == sdls.shape[0] and remapdf.shape[0] == sdls.shape[0]) remapdf.sort_values(['dof_x'], inplace=True) return remapdf['dof_y'].values
class KSDGSolver: default_params = dict( rho_min=1e-7, U_min=1e-7, width=1.0, rhopen=10, Upen=1, grhopen=1, gUpen=1, ) def __init__(self, mesh=None, width=1.0, dim=1, nelements=8, degree=2, parameters={}, V=(lambda U: U), U0=None, rho0=None, t0=0.0, debug=False, solver_type='gmres', preconditioner_type='default', periodic=False, ligands=None): """Discontinuous Galerkin solver for the Keller-Segel PDE system Keyword parameters: mesh=None: the mesh on which to solve the problem width=1.0: the width of the domain dim=1: # of spatial dimensions. nelements=8: If mesh is not supplied, one will be contructed using UnitIntervalMesh, UnitSquareMesh, or UnitCubeMesh (depending on dim). dim and nelements are not needed if mesh is supplied. degree=2: degree of the polynomial approximation parameters={}: a dict giving the values of scalar parameters of .V, U0, and rho0 Expressions. This dict needs to also define numerical parameters that appear in the PDE. Some of these have defaults: dim = dim: # of spatial dimensions sigma: organism movement rate s: attractant secretion rate gamma: attractant decay rate D: attractant diffusion constant rho_min=10.0**-7: minimum feasible worm density U_min=10.0**-7: minimum feasible attractant concentration rhopen=10: penalty for discontinuities in rho Upen=1: penalty for discontinuities in U grhopen=1, gUpen=1: penalties for discontinuities in gradients V=(lambda U: U): a callable taking two numerical arguments, U and rho, or a single argument, U, and returning a single number, V, the potential corresponding to U. Use fenics versions of mathematical functions, e.g. ufl.ln, abs, ufl.exp. U0, rho0: Expressions, Functions, or strs specifying the initial condition. t0=0.0: initial time solver_type='gmres' preconditioner_type='default' periodic, ligands: ignored for caompatibility """ logSOLVER('creating KSDGSolver') self.args = dict(mesh=mesh, width=width, dim=dim, nelements=nelements, degree=degree, parameters=parameters, V=V, U0=U0, rho0=rho0, t0=t0, debug=debug, solver_type=solver_type, preconditioner_type=preconditioner_type, periodic=periodic, ligands=ligands) self.debug = debug self.solver_type = solver_type self.preconditioner_type = preconditioner_type self.periodic = False self.ligands = ligands self.params = self.default_params.copy() if (mesh): self.omesh = self.mesh = mesh else: self.omesh = self.mesh = box_mesh(width=width, dim=dim, nelements=nelements) self.nelements = nelements logSOLVER('self.mesh', self.mesh) logSOLVER('self.mesh.mpi_comm().size', self.mesh.mpi_comm().size) self.nelements = nelements self.degree = degree self.dim = self.mesh.geometry().dim() self.params['dim'] = self.dim self.params.update(parameters) # # Solution spaces and Functions # fss = self.make_function_space() (self.SE, self.SS, self.VE, self.VS) = [fss[fs] for fs in ('SE', 'SS', 'VE', 'VS')] logSOLVER('self.VS', self.VS) self.sol = Function(self.VS) # sol, current soln logSOLVER('self.sol', self.sol) self.srho, self.sU = self.sol.sub(0), self.sol.sub(1) self.irho, self.iU = fe.split(self.sol) self.wrho, self.wU = TestFunctions(self.VS) self.tdsol = TrialFunction(self.VS) self.tdrho, self.tdU = fe.split(self.tdsol) self.n = FacetNormal(self.mesh) self.h = CellDiameter(self.mesh) self.havg = fe.avg(self.h) self.dx = fe.dx # self.dx = fe.dx(metadata={'quadrature_degree': min(degree, 10)}) self.dS = fe.dS # self.dS = fe.dS(metadata={'quadrature_degree': min(degree, 10)}) # # record initial state # try: V(self.iU, self.irho) def realV(U, rho): return V(U, rho) except TypeError: def realV(U, rho): return V(U) self.V = realV if not U0: U0 = Constant(0.0) if isinstance(U0, ufl.coefficient.Coefficient): self.U0 = U0 else: self.U0 = Expression(U0, **self.params, degree=self.degree, domain=self.mesh) if not rho0: rho0 = Constant(0.0) if isinstance(rho0, ufl.coefficient.Coefficient): self.rho0 = rho0 else: self.rho0 = Expression(rho0, **self.params, degree=self.degree, domain=self.mesh) self.t0 = t0 # # initialize state # # cache assigners logSOLVER('restarting') self.restart() logSOLVER('restart returned') return (None) def make_function_space(self, mesh=None, dim=None, degree=None): if not mesh: mesh = self.mesh if not dim: dim = self.dim if not degree: degree = self.degree SE = FiniteElement('DG', cellShapes[dim - 1], degree) SS = FunctionSpace(mesh, SE) # scalar space VE = MixedElement(SE, SE) VS = FunctionSpace(mesh, VE) # vector space return dict(SE=SE, SS=SS, VE=VE, VS=VS) def restart(self): logSOLVER('restart') self.t = self.t0 CE = FiniteElement('CG', cellShapes[self.dim - 1], self.degree) CS = FunctionSpace(self.mesh, CE) # scalar space coords = gather_dof_coords(CS) logSOLVER('function_interpolate(self.U0, self.SS, coords=coords)', function_interpolate(self.U0, self.SS, coords=coords)) fe.assign(self.sol.sub(1), function_interpolate(self.U0, self.SS, coords=coords)) logSOLVER('U0 assign returned') fe.assign(self.sol.sub(0), function_interpolate(self.rho0, self.SS, coords=coords)) def set_time(t): """Stub for derived classes to override""" self.t = t def setup_problem(self, debug=False): # # assemble the matrix, if necessary (once for all time points) # if not hasattr(self, 'A'): self.drho_integral = self.tdrho * self.wrho * self.dx self.dU_integral = self.tdU * self.wU * self.dx self.A = fe.assemble(self.drho_integral + self.dU_integral) # if self.solver_type == 'lu': # self.solver = fe.LUSolver( # self.A, # ) # self.solver.parameters['reuse_factorization'] = True # else: # self.solver = fe.KrylovSolver( # self.A, # self.solver_type, # self.preconditioner_type # ) # self.solver.parameters.add('linear_solver', self.solver_type) # kparams = fe.Parameters('krylov_solver') # kparams.add('report', True) # kparams.add('nonzero_initial_guess', True) # self.solver.parameters.add(kparams) # lparams = fe.Parameters('lu_solver') # lparams.add('report', True) # lparams.add('reuse_factorization', True) # lparams.add('verbose', True) # self.solver.parameters.add(lparams) self.dsol = Function(self.VS) self.drho, self.dU = self.dsol.sub(0), self.dsol.sub(1) # # assemble RHS (for each time point, but compile only once) # if not hasattr(self, 'rho_terms'): self.sigma = self.params['sigma'] self.s2 = self.sigma * self.sigma / 2 self.rho_min = self.params['rho_min'] self.rhopen = self.params['rhopen'] self.grhopen = self.params['grhopen'] self.v = -ufl.grad(self.V(self.iU, self.irho)) - ( self.s2 * ufl.grad(self.irho) / ufl.max_value(self.irho, self.rho_min)) self.flux = self.v * self.irho self.vn = ufl.max_value(ufl.dot(self.v, self.n), 0) self.facet_flux = ( self.vn('+') * ufl.max_value(self.irho('+'), 0.0) - self.vn('-') * ufl.max_value(self.irho('-'), 0.0)) self.rho_flux_jump = -self.facet_flux * ufl.jump( self.wrho) * self.dS self.rho_grad_move = ufl.dot(self.flux, ufl.grad( self.wrho)) * self.dx self.rho_penalty = -( (self.rhopen * self.degree**2 / self.havg) * ufl.dot( ufl.jump(self.irho, self.n), ufl.jump(self.wrho, self.n)) * self.dS) self.grho_penalty = -(self.grhopen * self.degree**2 * (ufl.jump(ufl.grad(self.irho), self.n) * ufl.jump(ufl.grad(self.wrho), self.n)) * self.dS) self.rho_terms = (self.rho_flux_jump + self.rho_grad_move + self.rho_penalty + self.grho_penalty) if not hasattr(self, 'U_terms'): self.U_min = self.params['U_min'] self.gamma = self.params['gamma'] self.s = self.params['s'] self.D = self.params['D'] self.Upen = self.params['Upen'] self.gUpen = self.params['gUpen'] self.U_decay = -self.gamma * self.iU * self.wU * self.dx self.U_secretion = self.s * self.irho * self.wU * self.dx self.jump_gUw = (self.D * ufl.jump(self.wU * ufl.grad(self.iU), self.n) * self.dS) self.U_diffusion = -self.D * ufl.dot(ufl.grad(self.iU), ufl.grad(self.wU)) * self.dx self.U_penalty = -( (self.Upen * self.degree**2 / self.havg) * ufl.dot(ufl.jump(self.iU, self.n), ufl.jump(self.wU, self.n)) * self.dS) self.gU_penalty = -(self.gUpen * self.degree**2 * (ufl.jump(ufl.grad(self.iU), self.n) * ufl.jump(ufl.grad(self.wU), self.n)) * self.dS) self.U_terms = ( # decay and secretion self.U_decay + self.U_secretion + # diffusion self.jump_gUw + self.U_diffusion + # penalties (to enforce continuity) self.U_penalty + self.gU_penalty) if not hasattr(self, 'all_terms'): self.all_terms = self.rho_terms + self.U_terms if not hasattr(self, 'J_terms'): self.J_terms = fe.derivative(self.all_terms, self.sol) # if not hasattr(self, 'JU_terms'): # self.JU_terms = fe.derivative(self.all_terms, self.sU) # if not hasattr(self, 'Jrho_terms'): # self.Jrho_terms = fe.derivative(self.all_terms, self.srho) def ddt(self, debug=False): """Calculate time derivative of rho and U Results are left in self.dsol as a two-component vector function. """ self.setup_problem(debug) self.b = fe.assemble(self.all_terms) return fe.solve(self.A, self.dsol.vector(), self.b, self.solver_type) # # The following member functions should not really exist -- use # the TS classes in ts instead. These are here only to allow some # old code to work. def implicitTS(self, t0=0.0, dt=0.001, tmax=20, maxsteps=100, rtol=1e-5, atol=1e-5, prt=True, restart=True, tstype=PETSc.TS.Type.ROSW, finaltime=PETSc.TS.ExactFinalTime.STEPOVER): """ Create an implicit timestepper and solve the DE Keyword arguments: t0=0.0: the initial time. dt=0.001: the initial time step. tmax=20: the final time. maxsteps=100: maximum number of steps to take. rtol=1e-5: relative error tolerance. atol=1e-5: absolute error tolerance. prt=True: whether to print results as solution progresses restart=True: whether to set the initial condition to rho0, U0 tstype=PETSc.TS.Type.ROSW: implicit solver to use. finaltime=PETSc.TS.ExactFinalTime.STEPOVER: how to handle final time step. Other options can be set by modifying the PETSc Options database. """ # print("KSDGSolver.implicitTS __init__ entered") from .ts import implicitTS # done here to avoid circular imports self.ts = implicitTS( self, t0=t0, dt=dt, tmax=tmax, maxsteps=maxsteps, rtol=rtol, atol=atol, restart=restart, tstype=tstype, finaltime=finaltime, ) self.ts.setMonitor(self.ts.historyMonitor) if prt: self.ts.setMonitor(self.ts.printMonitor) self.ts.solve() self.ts.cleanup() def cleanupTS(): """Should be called when finished with a TS Leaves history unchanged. """ del self.ts, self.tsparams def imExTS(self, t0=0.0, dt=0.001, tmax=20, maxsteps=100, rtol=1e-5, atol=1e-5, prt=True, restart=True, tstype=PETSc.TS.Type.ARKIMEX, finaltime=PETSc.TS.ExactFinalTime.STEPOVER): """ Create an implicit/explicit timestepper and solve the DE Keyword arguments: t0=0.0: the initial time. dt=0.001: the initial time step. tmax=20: the final time. maxsteps=100: maximum number of steps to take. rtol=1e-5: relative error tolerance. atol=1e-5: absolute error tolerance. prt=True: whether to print results as solution progresses restart=True: whether to set the initial condition to rho0, U0 tstype=PETSc.TS.Type.ARKIMEX: implicit solver to use. finaltime=PETSc.TS.ExactFinalTime.STEPOVER: how to handle final time step. Other options can be set by modifying the PETSc options database. """ from .ts import imExTS # done here to avoid circular imports self.ts = imExTS( self, t0=t0, dt=dt, tmax=tmax, maxsteps=maxsteps, rtol=rtol, atol=atol, restart=restart, tstype=tstype, finaltime=finaltime, ) self.ts.setMonitor(self.ts.historyMonitor) if prt: self.ts.setMonitor(self.ts.printMonitor) self.ts.solve() self.ts.cleanup() def explicitTS(self, t0=0.0, dt=0.001, tmax=20, maxsteps=100, rtol=1e-5, atol=1e-5, prt=True, restart=True, tstype=PETSc.TS.Type.RK, finaltime=PETSc.TS.ExactFinalTime.STEPOVER): """ Create an explicit timestepper and solve the DE Keyword arguments: t0=0.0: the initial time. dt=0.001: the initial time step. tmax=20: the final time. maxsteps=100: maximum number of steps to take. rtol=1e-5: relative error tolerance. atol=1e-5: absolute error tolerance. prt=True: whether to print results as solution progresses restart=True: whether to set the initial condition to rho0, U0 tstype=PETSc.TS.Type.RK: explicit solver to use. finaltime=PETSc.TS.ExactFinalTime.STEPOVER: how to handle final time step. Other options can be set by modifyign the PETSc options database. """ from .ts import explicitTS # done here to avoid circular imports self.ts = explicitTS( self, t0=t0, dt=dt, tmax=tmax, maxsteps=maxsteps, rtol=rtol, atol=atol, restart=restart, tstype=tstype, finaltime=finaltime, ) self.ts.setMonitor(self.ts.historyMonitor) if prt: self.ts.setMonitor(self.ts.printMonitor) self.ts.solve() self.ts.cleanup()
def solve_linear_pde( u_D_array, T, D=1, C1=0, num_r=100, min_r=0.001, tol=1e-14, degree=1, ): # disable logging set_log_active(False) num_t = len(u_D_array) dt = T / num_t # time step size mesh = IntervalMesh(num_r, min_r, 1) r = mesh.coordinates().flatten() r_args = np.argsort(r) V = FunctionSpace(mesh, "P", 1) # Define boundary conditions # Dirichlet condition at R def boundary_at_R(x, on_boundary): return on_boundary and near(x[0], 1, tol) D = Constant(D) u_D = Constant(u_D_array[0]) bc_at_R = DirichletBC(V, u_D, boundary_at_R) # Define initial values for free c c_0 = Expression("C1", C1=C1, degree=degree) c_n = interpolate(c_0, V) # Define variational problem c = TrialFunction(V) v = TestFunction(V) # define Constants r_squ = Expression("4*pi*pow(x[0],2)", degree=degree) F_tmp = (D * dt * inner(grad(c), grad(v)) * r_squ * dx + c * v * r_squ * dx - c_n * v * r_squ * dx) a, L = lhs(F_tmp), rhs(F_tmp) u = Function(V) data_c = np.zeros((num_t, len(r)), dtype=np.double) for n in range(num_t): u_D.assign(u_D_array[n]) # Compute solution solve(a == L, u, bc_at_R) data_c[n, :] = u.vector().vec().array c_n.assign(u) data_c = data_c[:, r_args[::-1]] r = r[r_args] return data_c, r
class KSDGSolverVariable(KSDGSolverMultiple): default_params = collections.OrderedDict( sigma=1.0, rhomin=1e-7, Umin=1e-7, width=1.0, rhopen=10.0, Upen=1.0, grhopen=1.0, gUpen=1.0, ) def __init__(self, mesh=None, width=1.0, dim=1, nelements=8, degree=2, parameters={}, param_funcs={}, V=(lambda U, params={}: sum(U)), U0=[], rho0=None, t0=0.0, debug=False, solver_type='petsc', preconditioner_type='default', periodic=False, ligands=None): """Discontinuous Galerkin solver for the Keller-Segel PDE system Keyword parameters: mesh=None: the mesh on which to solve the problem width=1.0: the width of the domain dim=1: # of spatial dimensions. nelements=8: If mesh is not supplied, one will be contructed using UnitIntervalMesh, UnitSquareMesh, or UnitCubeMesh (depending on dim). dim and nelements are not needed if mesh is supplied. degree=2: degree of the polynomial approximation parameters={}: a dict giving the initial values of scalar parameters of .V, U0, and rho0 Expressions. This dict needs to also define numerical parameters that appear in the PDE. Some of these have defaults: dim = dim: # of spatial dimensions sigma: organism movement rate rhomin=10.0**-7: minimum feasible worm density Umin=10.0**-7: minimum feasible attractant concentration rhopen=10: penalty for discontinuities in rho Upen=1: penalty for discontinuities in U grhopen=1, gUpen=1: penalties for discontinuities in gradients nligands=1, number of ligands. V=(lambda Us, params={}: sum(Us)): a callable taking two arguments, Us and rho, or a single argument, Us. Us is a list of length nligands. rho is a single expression. V returns a single number, V, the potential corresponding to Us (and rho). Use ufl versions of mathematical functions, e.g. ufl.ln, abs, ufl.exp. rho0: Expressions, Functions, or strs specifying the initial condition for rho. U0: a list of nligands Expressions, Functions or strs specifying the initial conditions for the ligands. t0=0.0: initial time solver_type='gmres' preconditioner_type='default' ligands=LigandGroups(): ligand list periodic=False: ignored for compatibility """ logVARIABLE('creating KSDGSolverVariable') if not ligands: ligands = LigandGroups() else: ligands = copy.deepcopy(ligands) self.args = dict(mesh=mesh, width=width, dim=dim, nelements=nelements, degree=degree, parameters=parameters, param_funcs=param_funcs, V=V, U0=U0, rho0=rho0, t0=t0, debug=debug, solver_type=solver_type, preconditioner_type=preconditioner_type, periodic=periodic, ligands=ligands) self.t0 = t0 self.debug = debug self.solver_type = solver_type self.preconditioner_type = preconditioner_type self.periodic = False self.ligands = ligands self.nligands = ligands.nligands() self.init_params(parameters, param_funcs) if (mesh): self.omesh = self.mesh = mesh else: self.omesh = self.mesh = box_mesh(width=width, dim=dim, nelements=nelements) self.nelements = nelements logVARIABLE('self.mesh', self.mesh) logVARIABLE('self.mesh.mpi_comm().size', self.mesh.mpi_comm().size) self.nelements = nelements self.degree = degree self.dim = self.mesh.geometry().dim() # # Solution spaces and Functions # fss = self.make_function_space() (self.SE, self.SS, self.VE, self.VS) = [fss[fs] for fs in ('SE', 'SS', 'VE', 'VS')] logVARIABLE('self.VS', self.VS) self.sol = Function(self.VS) # sol, current soln logVARIABLE('self.sol', self.sol) splitsol = self.sol.split() self.srho, self.sUs = splitsol[0], splitsol[1:] splitsol = list(fe.split(self.sol)) self.irho, self.iUs = splitsol[0], splitsol[1:] self.iPs = list(fe.split(self.PSf)) self.iparams = collections.OrderedDict(zip(self.param_names, self.iPs)) self.iligands = copy.deepcopy(self.ligands) self.iligand_params = ParameterList( [p for p in self.iligands.params() if p[0] in self.param_numbers]) for k in self.iligand_params.keys(): i = self.param_numbers[k] self.iligand_params[k] = self.iPs[i] tfs = list(TestFunctions(self.VS)) self.wrho, self.wUs = tfs[0], tfs[1:] tfs = list(TrialFunctions(self.VS)) self.tdrho, self.tdUs = tfs[0], tfs[1:] self.n = FacetNormal(self.mesh) self.h = CellDiameter(self.mesh) self.havg = fe.avg(self.h) self.dx = fe.dx # self.dx = fe.dx(metadata={'quadrature_degree': min(degree, 10)}) self.dS = fe.dS # self.dS = fe.dS(metadata={'quadrature_degree': min(degree, 10)}) # # record initial state # try: V(self.iUs, self.irho, params=self.iparams) def realV(Us, rho): return V(Us, rho, params=self.iparams) except TypeError: def realV(Us, rho): return V(Us, self.iparams) self.V = realV if not U0: U0 = [Constant(0.0)] * self.nligands self.U0s = [Constant(0.0)] * self.nligands for i, U0i in enumerate(U0): if isinstance(U0i, ufl.coefficient.Coefficient): self.U0s[i] = U0i else: self.U0s[i] = Expression(U0i, **self.params, degree=self.degree, domain=self.mesh) if not rho0: rho0 = Constant(0.0) if isinstance(rho0, ufl.coefficient.Coefficient): self.rho0 = rho0 else: self.rho0 = Expression(rho0, **self.params, degree=self.degree, domain=self.mesh) self.set_time(t0) # # initialize state # self.restart() return None def init_params(self, parameters, param_funcs): """Initialize parameter attributes from __init__ arguments The attributes initialized are: self.params0: a dict giving initial values of all parameters (not just floats). This is basically a copy of the parameters argument to __init__, with the insertion of 't' as a new parameter (always param_names[-1]). self.param_names: a list of the names of the time-varying parameters. This is the keys of params0 whose corrsponding values are of type float. The order is the order of the parameters in self.PSf. self.nparams: len(self.param_names) self.param_numbers: a dict mapping param names to numbers (ints) in the list param_names and the parameters subspace of the solution FunctionSpace. self.param_funcs: a dict whose keys are the param_names and whose values are functions to determine their values as a function of time, as explained above. These are copied from the param_funcs argument of __init__, except that the default initial value function is filled in for parameters not present in the argument. Also, the function defined for 't' always returns t. self.PSf: a Constant object of dimension self.nparams, holding the initial values of the parameters. """ self.param_names = [ n for n, v in parameters.items() if (type(v) is float and n != 't') ] self.param_names.append('t') self.nparams = len(self.param_names) logVARIABLE('self.param_names', self.param_names) logVARIABLE('self.nparams', self.nparams) self.param_numbers = collections.OrderedDict( zip(self.param_names, itertools.count())) self.params0 = collections.OrderedDict(parameters) self.params0['t'] = 0.0 self.param_funcs = param_funcs.copy() def identity(t, params={}): return t self.param_funcs['t'] = identity for n in self.param_names: if n not in self.param_funcs: def value0(t, params={}, v0=self.params0[n]): return v0 self.param_funcs[n] = value0 self.PSf = Constant([self.params0[n] for n in self.param_names]) return def set_time(self, t): self.t = t params = collections.OrderedDict( zip(self.param_names, self.PSf.values())) self.PSf.assign( Constant([ self.param_funcs[n](t, params=params) for n in self.param_names ])) logVARIABLE('self.t', self.t) logVARIABLE( 'collections.OrderedDict(zip(self.param_names, self.PSf.values()))', collections.OrderedDict(zip(self.param_names, self.PSf.values()))) def make_function_space(self, mesh=None, dim=None, degree=None): if not mesh: mesh = self.mesh if not dim: dim = self.dim if not degree: degree = self.degree SE = FiniteElement('DG', cellShapes[dim - 1], degree) SS = FunctionSpace(mesh, SE) # scalar space elements = [SE] * (self.nligands + 1) VE = MixedElement(elements) VS = FunctionSpace(mesh, VE) # vector space return dict(SE=SE, SS=SS, VE=VE, VS=VS) def restart(self): logVARIABLE('restart') self.set_time(self.t0) CE = FiniteElement('CG', cellShapes[self.dim - 1], self.degree) CS = FunctionSpace(self.mesh, CE) # scalar space coords = gather_dof_coords(CS) fe.assign(self.sol.sub(0), function_interpolate(self.rho0, self.SS, coords=coords)) for i, U0i in enumerate(self.U0s): fe.assign(self.sol.sub(i + 1), function_interpolate(U0i, self.SS, coords=coords)) def setup_problem(self, t, debug=False): self.set_time(t) # # assemble the matrix, if necessary (once for all time points) # if not hasattr(self, 'A'): self.drho_integral = self.tdrho * self.wrho * self.dx self.dU_integral = sum([ tdUi * wUi * self.dx for tdUi, wUi in zip(self.tdUs, self.wUs) ]) logVARIABLE('assembling A') self.A = PETScMatrix() logVARIABLE('self.A', self.A) fe.assemble(self.drho_integral + self.dU_integral, tensor=self.A) logVARIABLE('A assembled. Applying BCs') self.dsol = Function(self.VS) dsolsplit = self.dsol.split() self.drho, self.dUs = dsolsplit[0], dsolsplit[1:] # # assemble RHS (for each time point, but compile only once) # if not hasattr(self, 'rho_terms'): self.sigma = self.iparams['sigma'] self.s2 = self.sigma * self.sigma / 2 self.rhomin = self.iparams['rhomin'] self.rhopen = self.iparams['rhopen'] self.grhopen = self.iparams['grhopen'] self.v = -ufl.grad( self.V(self.iUs, ufl.max_value(self.irho, self.rhomin)) - (self.s2 * ufl.grad(self.irho) / ufl.max_value(self.irho, self.rhomin))) self.flux = self.v * self.irho self.vn = ufl.max_value(ufl.dot(self.v, self.n), 0) self.facet_flux = ufl.jump(self.vn * ufl.max_value(self.irho, 0.0)) self.rho_flux_jump = -self.facet_flux * ufl.jump( self.wrho) * self.dS self.rho_grad_move = ufl.dot(self.flux, ufl.grad( self.wrho)) * self.dx self.rho_penalty = -( (self.degree**2 / self.havg) * ufl.dot(ufl.jump(self.irho, self.n), ufl.jump(self.rhopen * self.wrho, self.n)) * self.dS) self.grho_penalty = -( self.degree**2 * (ufl.jump(ufl.grad(self.irho), self.n) * ufl.jump( ufl.grad(self.grhopen * self.wrho), self.n)) * self.dS) self.rho_terms = (self.rho_flux_jump + self.rho_grad_move + self.rho_penalty + self.grho_penalty) if not hasattr(self, 'U_terms'): self.Umin = self.iparams['Umin'] self.Upen = self.iparams['Upen'] self.gUpen = self.iparams['gUpen'] self.U_decay = sum([ -lig.gamma * iUi * wUi * self.dx for lig, iUi, wUi in zip( self.iligands.ligands(), self.iUs, self.wUs) ]) self.U_secretion = sum([ lig.s * self.irho * wUi * self.dx for lig, wUi in zip(self.iligands.ligands(), self.wUs) ]) self.jump_gUw = sum([ ufl.jump(lig.D * wUi * ufl.grad(iUi), self.n) * self.dS for lig, wUi, iUi in zip(self.iligands.ligands(), self.wUs, self.iUs) ]) self.U_diffusion = sum([ -lig.D * ufl.dot(ufl.grad(iUi), ufl.grad(wUi)) * self.dx for lig, iUi, wUi in zip(self.iligands.ligands(), self.iUs, self.wUs) ]) self.U_penalty = sum([ -(self.degree**2 / self.havg) * ufl.dot( ufl.jump(iUi, self.n), ufl.jump(self.Upen * wUi, self.n)) * self.dS for iUi, wUi in zip(self.iUs, self.wUs) ]) self.gU_penalty = sum([ -self.degree**2 * ufl.jump(ufl.grad(iUi), self.n) * ufl.jump(ufl.grad(self.gUpen * wUi), self.n) * self.dS for iUi, wUi in zip(self.iUs, self.wUs) ]) self.U_terms = ( # decay and secretion self.U_decay + self.U_secretion + # diffusion self.jump_gUw + self.U_diffusion + # penalties (to enforce continuity) self.U_penalty + self.gU_penalty) if not hasattr(self, 'all_terms'): self.all_terms = self.rho_terms + self.U_terms if not hasattr(self, 'J_terms'): self.J_terms = fe.derivative(self.all_terms, self.sol) def ddt(self, t, debug=False): """Calculate time derivative of rho and U Results are left in self.dsol as a two-component vector function. """ self.setup_problem(t, debug=debug) self.b = fe.assemble(self.all_terms) return fe.solve(self.A, self.dsol.vector(), self.b, self.solver_type)