if problem is ProblemType.DIRICHLET: precice_dt = precice.initialize(coupling_subdomain=coupling_boundary, mesh=mesh, read_field=u_D_function, write_field=f_N_function, u_n=u_n) elif problem is ProblemType.NEUMANN: precice_dt = precice.initialize(coupling_subdomain=coupling_boundary, mesh=mesh, read_field=f_N_function, write_field=u_D_function, u_n=u_n) dt = Constant(0) dt.assign(np.min([fenics_dt, precice_dt])) # Define variational problem u = TrialFunction(V) v = TestFunction(V) f = Expression( 'beta + gamma * x[0] * x[0] - 2 * gamma * t - 2 * (1-gamma) - 2 * alpha', degree=2, alpha=alpha, beta=beta, gamma=gamma, t=0) F = u * v / dt * dx + dot(grad(u), grad(v)) * dx - (u_n / dt + f) * v * dx if problem is ProblemType.DIRICHLET: # apply Dirichlet boundary condition on coupling interface
u_n.rename("Temperature", "") precice, precice_dt, initial_data = None, 0.0, None # Initialize the adapter according to the specific participant if problem is ProblemType.DIRICHLET: precice = Adapter(adapter_config_filename="precice-adapter-config-D.json") precice_dt = precice.initialize(coupling_boundary, read_function_space=V, write_object=f_N_function) elif problem is ProblemType.NEUMANN: precice = Adapter(adapter_config_filename="precice-adapter-config-N.json") precice_dt = precice.initialize(coupling_boundary, read_function_space=V_g, write_object=u_D_function) boundary_marker = False dt = Constant(0) dt.assign(np.min([fenics_dt, precice_dt])) # Define variational problem u = TrialFunction(V) v = TestFunction(V) f = Expression('beta + gamma*x[0]*x[0] - 2*gamma*t - 2*(1-gamma) - 2*alpha', degree=2, alpha=alpha, beta=beta, gamma=gamma, t=0) F = u * v / dt * dx + dot(grad(u), grad(v)) * dx - (u_n / dt + f) * v * dx bcs = [DirichletBC(V, u_D, remaining_boundary)] # Set boundary conditions at coupling interface once wrt to the coupling expression coupling_expression = precice.create_coupling_expression() if problem is ProblemType.DIRICHLET: # modify Dirichlet boundary condition on coupling interface bcs.append(DirichletBC(V, coupling_expression, coupling_boundary))
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
solution_out << u_n ranks << mesh_rank while precice.is_coupling_ongoing(): # write checkpoint if precice.is_action_required(precice.action_write_iteration_checkpoint()): precice.store_checkpoint(u_n, t, n) read_data = precice.read_data() # Update the coupling expression with the new read data precice.update_coupling_expression(volume_term, read_data) f.assign(interpolate(volume_term, V)) dt_inv.assign(1 / dt) # Compute solution u^n+1, use bcs u^n and coupling bcs a, L = lhs(F), rhs(F) solve(a == L, u_np1, bc) # Write data to preCICE according to which problem is being solved precice.write_data(u_np1) dt = precice.advance(dt) # roll back to checkpoint if precice.is_action_required(precice.action_read_iteration_checkpoint()): u_cp, t_cp, n_cp = precice.retrieve_checkpoint() u_n.assign(u_cp) t = t_cp
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)