def shooting_gmres(direction): # Define empty functions on interface increment = Function(interface.function_space) (increment_displacement, increment_velocity) = increment.split(increment) # Split entrance vectors direction_split = np.split(direction, 2) # Set values of functions on interface increment_displacement.vector().set_local(displacement_interface + param.EPSILON * direction_split[0]) increment_velocity.vector().set_local(velocity_interface + param.EPSILON * direction_split[1]) # Interpolate functions on solid subdomain increment_displacement_solid = interpolate( increment_displacement, solid.function_space_split[0]) increment_velocity_solid = interpolate(increment_velocity, solid.function_space_split[1]) displacement_solid.new.assign(increment_displacement_solid) velocity_solid.new.assign(increment_velocity_solid) # Compute shooting function shooting_function_increment = shooting_function( displacement_fluid, velocity_fluid, displacement_solid, velocity_solid, functional_fluid_initial, functional_solid_initial, bilinear_form_fluid, functional_fluid, bilinear_form_solid, functional_solid, first_time_step, fluid, solid, interface, param, fluid_macrotimestep, solid_macrotimestep, adjoint, ) return (shooting_function_increment - shooting_function_value) / param.EPSILON
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
0) # call dt(0) to evaluate FEniCS Constant. Todo: is there a better way? f.t = t + dt(0) V_g = VectorFunctionSpace(mesh, 'P', 1) flux = Function(V_g) flux.rename("Flux", "") while precice.is_coupling_ongoing(): # Compute solution u^n+1, use bcs u_D^n+1, u^n and coupling bcs solve(a == L, u_np1, bcs) if problem is ProblemType.DIRICHLET: # Dirichlet problem obtains flux from solution and sends flux on boundary to Neumann problem determine_gradient(V_g, u_np1, flux) flux_x, flux_y = flux.split() if domain_part is DomainPart.RIGHT: flux_x = -1 * flux_x t, n, precice_timestep_complete, precice_dt = precice.advance( flux_x, u_np1, u_n, t, dt(0), n) elif problem is ProblemType.NEUMANN: # Neumann problem obtains sends temperature on boundary to Dirichlet problem t, n, precice_timestep_complete, precice_dt = precice.advance( u_np1, u_np1, u_n, t, dt(0), n) dt.assign( np.min([fenics_dt, precice_dt]) ) # todo we could also consider deciding on time stepping size inside the adapter if precice_timestep_complete: u_ref = interpolate(u_D, V)
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)
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 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)
def compute_conv_diff_reac_video(self, initial_condition=None, video_ref=None, video_size=None): names = {'Cl', 'K'} P1 = FiniteElement('P', fe.triangle, 1) element = fe.MixedElement([P1, P1]) V_single = FunctionSpace(self.mesh, P1) V = FunctionSpace(self.mesh, element) self.V_conc = V # load video video = VideoData(element=P1) video.load_video(self.video_filename) if( video_ref is not None and video_size is not None): video.set_reference_frame(video_ref, video_size) print(video) dt = 0.05 t = 0. t_end = 20. u_init = Function(V) (u_cl, u_k) = TrialFunction(V) (v_cl, v_k) = TestFunction(V) #u_na = video if initial_condition is None: #initial_condition = Expression(("f*exp(-0.5*((x[0]-a)*(x[0]-a)+(x[1]-b)*(x[1]-b))/var)/(sqrt(2*pi)*var)", # "0."), a = 80, b= 55, var=10, f=10, pi=fe.pi, element=element) initial_condition = Expression(("f", "0."), a=80, b=55, var=0.1, f=0.1, pi=fe.pi, element=element) u_init = fe.interpolate(initial_condition, V) u_init_cl = u_init[0] u_init_k = u_init[1] u_init_na : VideoData= video assert (self.flow is not None) n = fe.FacetNormal(self.mesh) dx, ds = self.dx, self.ds flow = 5. * self.flow f_in = fe.Constant(0.00) f_in_cl = fe.Constant(-0.05) D = fe.Constant(0.1) C_na = fe.Constant(0.1) k1 = fe.Constant(0.2) k_1 = fe.Constant(0.00001) # explicit F = ( (u_cl - u_init_cl) * v_cl * dx + dt * D * inner(grad(u_cl), grad(v_cl)) * dx + dt * inner(flow, grad(u_cl)) * v_cl * dx #+ (u_na - u_init_na) * v_na * dx #+ dt * D * inner(grad(u_na), grad(v_na)) * dx #+ dt * inner(flow, grad(u_na)) * v_na * dx + (u_k - u_init_k) * v_k * dx + dt * D * inner(grad(u_k), grad(v_k)) * dx + dt * inner(flow, grad(u_k)) * v_k * dx + f_in_cl * v_cl * dx #+ f_in * v_na * dx + f_in * v_k * dx + dt * k1 * u_init_cl * C_na * u_init_na * v_cl * dx #+ dt * k1 * u_init_cl * u_init_na * v_na * dx - dt * k1 * u_init_cl * C_na * u_init_na * v_k * dx - dt * k_1 * u_init_k * v_cl * dx #- dt * k_1 * u_init_k * v_na * dx + dt * k_1 * u_init_k * v_k * dx ) # implicit F = ( (u_cl - u_init_cl) * v_cl * dx + dt * D * inner(grad(u_cl), grad(v_cl)) * dx + dt * inner(flow, grad(u_cl)) * v_cl * dx # + (u_na - u_init_na) * v_na * dx # + dt * D * inner(grad(u_na), grad(v_na)) * dx # + dt * inner(flow, grad(u_na)) * v_na * dx + (u_k - u_init_k) * v_k * dx + dt * D * inner(grad(u_k), grad(v_k)) * dx + dt * inner(flow, grad(u_k)) * v_k * dx + f_in_cl * v_cl * dx # + f_in * v_na * dx + f_in * v_k * dx + dt * k1 * u_cl * C_na * u_init_na * v_cl * dx # + dt * k1 * u_init_cl * u_init_na * v_na * dx - dt * k1 * u_cl * C_na * u_init_na * v_k * dx - dt * k_1 * u_k * v_cl * dx # - dt * k_1 * u_init_k * v_na * dx + dt * k_1 * u_k * v_k * dx ) self.F = F a, L = fe.lhs(F), fe.rhs(F) a_mat = fe.assemble(a) L_vec = fe.assemble(L) output1 = fe.File('/tmp/cl_dyn.pvd') output2 = fe.File('/tmp/na_dyn.pvd') output3 = fe.File('/tmp/k_dyn.pvd') output4 = fe.File('/tmp/all_dyn.pvd') # solve self.sol = [] u_na = Function(V_single) u_na = fe.interpolate(u_init_na,V_single) na_inflow = 0 t_plot = 0.5 t_last_plot = 0 while t < t_end: t = t + dt t_last_plot += dt print(t) u = Function(V) u_init_na.set_time(5*t) a_mat = fe.assemble(a) L_vec = fe.assemble(L) fe.solve(a_mat, u.vector(), L_vec) # NonlinearVariationalProblem(F,u) u_init.assign(u) u_cl, u_k = u.split() # u_init_cl.assign(u_cl) # u_init_na.assign(u_na) # u_init_k.assign(u_k) u_na = fe.interpolate(u_init_na, V_single) u_cl.rename("cl", "cl") u_na.rename("na", "na") u_k.rename("k", "k") output1 << u_cl, t output2 << u_na, t output3 << u_k, t self.sol.append((u_cl, u_na, u_k)) print( fe.assemble(u_cl*self.dx)) if t_last_plot > t_plot: t_last_plot = 0 plt.figure(figsize=(8,16)) plt.subplot(211) fe.plot(u_cl) plt.subplot(212) fe.plot(u_k) plt.show() self.u_cl = u_cl #self.u_na = u_na self.u_k = u_k
def compute_conv_diff_reac(self, initial_condition=None): names = {'Cl', 'Na', 'K'} dt = 0.1 t = 0. t_end = 1. P1 = FiniteElement('P', fe.triangle, 3) element = MixedElement([P1, P1, P1]) V = FunctionSpace(self.mesh, element) self.V_conc = V u_init = Function(V) (u_cl, u_na, u_k) = TrialFunction(V) (v_cl, v_na, v_k) = TestFunction(V) if initial_condition is None: initial_condition = Expression(("exp(-((x[0]-0.1)*(x[0]-0.1)+x[1]*x[1])/0.01)", "exp(-((x[0]-0.12)*(x[0]-0.12)+x[1]*x[1])/0.01)", "0."), element=element) u_init = fe.interpolate(initial_condition, V) u_init_cl = u_init[0] u_init_na = u_init[1] u_init_k = u_init[2] assert (self.flow is not None) n = fe.FacetNormal(self.mesh) dx, ds = self.dx, self.ds flow = 10 * self.flow f_in = fe.Constant(0.00) D = fe.Constant(0.01) k1 = fe.Constant(0.1) k_1 = fe.Constant(0.001) F = ( (u_cl - u_init_cl) * v_cl * dx + dt * D * inner(grad(u_cl), grad(v_cl)) * dx + dt * inner(flow, grad(u_cl)) * v_cl * dx + (u_na - u_init_na) * v_na * dx + dt * D * inner(grad(u_na), grad(v_na)) * dx + dt * inner(flow, grad(u_na)) * v_na * dx + (u_k - u_init_k) * v_k * dx + dt * D * inner(grad(u_k), grad(v_k)) * dx + dt * inner(flow, grad(u_k)) * v_k * dx + f_in * v_cl * dx + f_in * v_na * dx + f_in * v_k * dx + dt * k1 * u_init_cl * u_init_na * v_cl * dx + dt * k1 * u_init_cl * u_init_na * v_na * dx - dt * k1 * u_init_cl * u_init_na * v_k * dx - dt * k_1 * u_init_k * v_cl * dx - dt * k_1 * u_init_k * v_na * dx + dt * k_1 * u_init_k * v_k * dx ) self.F = F a, L = fe.lhs(F), fe.rhs(F) a_mat = fe.assemble(a) L_vec = fe.assemble(L) output1 = fe.File('/tmp/cl_dyn.pvd') output2 = fe.File('/tmp/na_dyn.pvd') output3 = fe.File('/tmp/k_dyn.pvd') output4 = fe.File('/tmp/all_dyn.pvd') # solve self.sol = [] while t < t_end: t = t + dt print(t) u = Function(V) a_mat = fe.assemble(a) L_vec = fe.assemble(L) fe.solve(a_mat, u.vector(), L_vec) # NonlinearVariationalProblem(F,u) u_init.assign(u) u_cl, u_na, u_k = u.split() # u_init_cl.assign(u_cl) # u_init_na.assign(u_na) # u_init_k.assign(u_k) u_cl.rename("cl", "cl") u_na.rename("na", "na") u_k.rename("k", "k") output1 << u_cl, t output2 << u_na, t output3 << u_k, t self.sol.append((u_cl, u_na, u_k)) self.u_cl = u_cl self.u_na = u_na self.u_k = u_k
class KSDGSolverMultiple(KSDGSolver): default_params = dict( rho_min = 1e-7, U_min = 1e-7, width = 1.0, rhopen = 10, Upen = 1, grhopen = 1, gUpen = 1, ligands = None, ) def __init__( self, mesh=None, width=1.0, dim=1, nelements=8, degree=2, parameters={}, V=(lambda U: U), U0=[], 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 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 nligands=1, number of ligands V=(lambda Us: 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 """ logMULTIPLE('creating KSDGSolverMultiple') 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, 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.nligands = ligands.nligands() 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 logMULTIPLE('self.mesh', self.mesh) logMULTIPLE('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') ] logMULTIPLE('self.VS', self.VS) self.sol = Function(self.VS) # sol, current soln logMULTIPLE('self.sol', self.sol) self.srho, self.sUs = self.sol.sub(0), self.sol.split()[1:] splitsol = fe.split(self.sol) self.irho, self.iUs = splitsol[0], splitsol[1:] tfs = TestFunctions(self.VS) self.wrho, self.wUs = tfs[0], tfs[1:] self.tdsol = TrialFunction(self.VS) splittdsol = fe.split(self.tdsol) self.tdrho, self.tdUs = splittdsol[0], splittdsol[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) def realV(Us, rho): return V(Us, rho) except TypeError: def realV(Us, rho): return V(Us) 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.t0 = t0 # # initialize state # logMULTIPLE('restarting') self.restart() logMULTIPLE('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] * (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): logMULTIPLE('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) 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)) logMULTIPLE('U0s assign returned') 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 = sum( [tdUi*wUi*self.dx for tdUi,wUi in zip(self.tdUs, self.wUs)] ) self.A = fe.assemble(self.drho_integral + self.dU_integral) 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.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.iUs, 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.Upen = self.params['Upen'] self.gUpen = self.params['gUpen'] self.U_decay = sum( [-lig.gamma * iUi * wUi * self.dx for lig,iUi,wUi in zip(self.ligands.ligands(), self.iUs, self.wUs)] ) self.U_secretion = sum( [lig.s * self.irho * wUi * self.dx for lig,wUi in zip(self.ligands.ligands(), self.wUs)] ) self.jump_gUw = sum( [lig.D * ufl.jump(wUi * ufl.grad(iUi), self.n) * self.dS for lig,wUi,iUi in zip(self.ligands.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.ligands.ligands(), self.iUs, self.wUs)] ) self.U_penalty = sum( [-(self.Upen*self.degree**2/self.havg) * ufl.dot(ufl.jump(iUi, self.n), ufl.jump(wUi, self.n))*self.dS for iUi,wUi in zip(self.iUs, self.wUs)] ) self.gU_penalty = -self.gUpen * self.degree**2 * sum( [ufl.jump(ufl.grad(iUi), self.n) * ufl.jump(ufl.grad(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, 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)