def define_function_assigners(self): """ Create function assigners to update the current and previous time values of all field variables. This is specific to incompressible simulations since the mixed function space formulation requires the handling of the mixed functions and the copies of its subcomponents in a specific manner. """ W = self.functionSpace v = self.velocity p = self.pressure self.assigner_sys2v = dlf.FunctionAssigner( [v.function_space(), p.function_space()], W) self.assigner_v2sys = dlf.FunctionAssigner( W, [v.function_space(), p.function_space()]) if self.config['formulation']['time']['unsteady']: v0 = self.velocity0 p0 = self.pressure0 self.assigner_v02sys = dlf.FunctionAssigner( W, [v0.function_space(), p0.function_space()]) self.assigner_sys2v0 = dlf.FunctionAssigner( [v0.function_space(), p0.function_space()], W) return None
def __init__( self, *, brain: CoupledBrainModel, parameters: CoupledSplittingSolverParameters, ) -> None: """Create solver from given Model and (optional) parameters.""" self._brain = brain self._parameters = parameters # Create ODE solver and extract solution fields self.ode_solver = self.create_ode_solver() self.vs_prev, self.vs = self.ode_solver.solution_fields() self.VS = self.vs.function_space() # Create PDE solver and extract solution fields self.pde_solver = self.create_pde_solver() self.v_prev, self.vur = self.pde_solver.solution_fields() # # Create function assigner for merging v from self.vur into self.vs[0] if self.vur.function_space().num_sub_spaces() > 1: V = self.vur.function_space().sub(0) else: V = self.vur.function_space() self.merger = df.FunctionAssigner(self.VS.sub(0), V)
def _init_states(self, src4init='map_solution'): """ Initialize the states from existing solution or DNS data. """ # Initialize the state variable using the DNS data or existing solution self.states_fwd = dl.Function(self.Vh_STATE) f_name = src4init + '.h5' self.from_file = os.path.isfile(f_name) if self.from_file: input_file = dl.HDF5File(self.mpi_comm, f_name, "r") input_file.read(self.states_fwd, "state") input_file.close() elif src4init is 'DNS' or not self.from_file: upke_init = [self.u_fun, self._scalar_zero, self.k_fun, self.e_fun] upkefun = [ dl.interpolate(fi, Vi) for (fi, Vi) in zip(upke_init, self.Vhs) ] assigner_upke = dl.FunctionAssigner(self.Vh_STATE, self.Vhs) assigner_upke.assign(self.states_fwd, upkefun) self.from_file = False else: self.from_file = False if self.rank == 0: print( 'States have initialized at zero. Please provide data source if you want otherwise.' )
def setup(self): """ Create mesh velocity and deformation functions """ sim = self.simulation assert self.active is False, 'Trying to setup mesh morphing twice in the same simulation' # Store previous cell volumes mesh = sim.data['mesh'] Vcvol = dolfin.FunctionSpace(mesh, 'DG', 0) sim.data['cvolp'] = dolfin.Function(Vcvol) # The function spaces for mesh velocities and displacements Vmesh = dolfin.FunctionSpace(mesh, 'CG', 1) Vmesh_vec = dolfin.VectorFunctionSpace(mesh, 'CG', 1) sim.data['Vmesh'] = Vmesh # Create mesh velocity functions u_mesh = [] for d in range(sim.ndim): umi = dolfin.Function(Vmesh) sim.data['u_mesh%d' % d] = umi u_mesh.append(umi) u_mesh = dolfin.as_vector(u_mesh) sim.data['u_mesh'] = u_mesh # Create mesh displacement vector function self.displacement = dolfin.Function(Vmesh_vec) self.assigners = [ dolfin.FunctionAssigner(Vmesh_vec.sub(d), Vmesh) for d in range(sim.ndim) ] self.active = True
def functionspace(self): if self.options["is_projection"]: dim_Local = self.particle.dimension dim_Global = self.particle.dimension*self.particle.number_particles_all self.Vh_Global = dl.VectorFunctionSpace(self.particle.Vh_COEFF.mesh(), "R", degree=0, dim=dim_Global) self.Vh_Local = dl.VectorFunctionSpace(self.particle.Vh_COEFF.mesh(), "R", degree=0, dim=dim_Local) # dim_Universal = self.nproc*self.particle.dimension*self.particle.number_particles_all # self.Vh_Universal = dl.VectorFunctionSpace(self.particle.Vh_COEFF.mesh(), "R", degree=0, # dim=dim_Universal) else: if self.options["type_parameter"] is 'field': if self.particle.number_particles_all == 1: self.Vh_Global = dl.FunctionSpace(self.model.problem.Vh[PARAMETER].mesh(), 'Lagrange', self.model.problem.Vh[PARAMETER].ufl_element().degree()) self.Vh_Local = dl.FunctionSpace(self.model.problem.Vh[PARAMETER].mesh(), 'Lagrange', self.model.problem.Vh[PARAMETER].ufl_element().degree()) # if self.nproc == 1: # self.Vh_Universal = dl.FunctionSpace(self.model.problem.Vh[PARAMETER].mesh(), # 'Lagrange', # self.model.problem.Vh[PARAMETER].ufl_element().degree()) # else: # self.Vh_Universal = dl.VectorFunctionSpace(self.model.problem.Vh[PARAMETER].mesh(), # 'Lagrange', self.model.problem.Vh[PARAMETER].ufl_element().degree(), # dim=self.nproc) else: self.Vh_Global = dl.VectorFunctionSpace(self.model.problem.Vh[PARAMETER].mesh(), 'Lagrange', self.model.problem.Vh[PARAMETER].ufl_element().degree(), dim=self.particle.number_particles_all) self.Vh_Local = self.Vh_Global.sub(0).collapse() # self.Vh_Universal = dl.VectorFunctionSpace(self.model.problem.Vh[PARAMETER].mesh(), # 'Lagrange', self.model.problem.Vh[PARAMETER].ufl_element().degree(), # dim=self.nproc*self.particle.number_particles_all) self.local_spaces = [self.Vh_Local for m in range(self.particle.number_particles_all)] self.global_space = self.Vh_Global self.to_sub = dl.FunctionAssigner(self.local_spaces, self.global_space) self.from_sub = dl.FunctionAssigner(self.global_space, self.local_spaces) elif self.options["type_parameter"] is 'vector': dim_Local = self.model.prior.dim dim_Global = self.model.prior.dim*self.particle.number_particles_all self.Vh_Global = dl.VectorFunctionSpace(self.model.problem.Vh[PARAMETER].mesh(), "R", degree=0, dim=dim_Global) self.Vh_Local = dl.VectorFunctionSpace(self.model.problem.Vh[PARAMETER].mesh(), "R", degree=0, dim=dim_Local)
def solve_coupled(self): """ Solve the coupled equations """ # Assemble the equation system A = self.eqs.assemble_lhs() b = self.eqs.assemble_rhs() if self.fix_pressure_dof: A.ident(self.pressure_row_to_fix) elif self.remove_null_space: if self.pressure_null_space is None: # Create null space vector in Vp Space null_func = dolfin.Function(self.simulation.data['Vp']) null_vec = null_func.vector() null_vec[:] = 1 null_vec *= 1 / null_vec.norm("l2") # Convert null space vector to coupled space null_func2 = dolfin.Function(self.simulation.data['Vcoupled']) ndim = self.simulation.ndim fa = dolfin.FunctionAssigner(self.subspaces[ndim], self.simulation.data['Vp']) fa.assign(null_func2.sub(ndim), null_func) # Create the null space basis self.pressure_null_space = dolfin.VectorSpaceBasis( [null_func2.vector()]) # Make sure the null space is set on the matrix dolfin.as_backend_type(A).set_nullspace(self.pressure_null_space) # Orthogonalize b with respect to the null space self.pressure_null_space.orthogonalize(b) # Solve the equation system self.simulation.hooks.matrix_ready('Coupled', A, b) self.coupled_solver.solve(A, self.coupled_func.vector(), b) # Assign into the regular (split) functions from the coupled function funcs = [self.simulation.data[name] for name in self.subspace_names] self.assigner.assign(funcs, self.coupled_func) # If we remove the null space of the matrix system this will not be the exact same as # removing the proper null space of the equation, so we fix this here if self.fix_pressure_dof: p = self.simulation.data['p'] dx2 = dolfin.dx(domain=p.function_space().mesh()) vol = dolfin.assemble(dolfin.Constant(1) * dx2) # Perform correction multiple times due to round-of error. The first correction # can be i.e 1e14 while the next correction is around unity pavg = 1e10 while abs(pavg) > 1000: pavg = dolfin.assemble(p * dx2) / vol p.vector()[:] -= pavg
def create_vec_func(V): "Create a vector function from the components" family = V.ufl_element().family() degree = V.ufl_element().degree() cd = sim.data['constrained_domain'] V_vec = dolfin.VectorFunctionSpace( sim.data['mesh'], family, degree, constrained_domain=cd ) vec_func = dolfin.Function(V_vec) assigner = dolfin.FunctionAssigner(V_vec, [V] * sim.ndim) return vec_func, assigner
def create_functions(self): """ Create functions to hold solutions """ sim = self.simulation # Function spaces Vu = sim.data['Vu'] Vp = sim.data['Vp'] cd = sim.data['constrained_domain'] # Create coupled mixed function space and mixed function to hold results func_spaces = [Vu] * sim.ndim + [Vp] self.subspace_names = ['u%d' % d for d in range(sim.ndim)] + ['p'] # Create stress tensor space P = Vu.ufl_element().degree() Vs = dolfin.FunctionSpace(sim.data['mesh'], 'DG', P, constrained_domain=cd) for i in range(sim.ndim**2): stress_name = 'stress_%d' % i sim.data[stress_name] = dolfin.Function(Vs) func_spaces.append(Vs) self.subspace_names.append(stress_name) # Create mixed space e_mixed = dolfin.MixedElement([fs.ufl_element() for fs in func_spaces]) Vcoupled = dolfin.FunctionSpace(sim.data['mesh'], e_mixed) sim.data['Vcoupled'] = Vcoupled # Create function assigner Nspace = len(func_spaces) self.subspaces = [Vcoupled.sub(i) for i in range(Nspace)] sim.data['coupled'] = self.coupled_func = dolfin.Function(Vcoupled) self.assigner = dolfin.FunctionAssigner(func_spaces, Vcoupled) # Create segregated functions on component and vector form u_list, up_list, upp_list, u_conv = [], [], [], [] for d in range(sim.ndim): sim.data['u%d' % d] = u = dolfin.Function(Vu) sim.data['up%d' % d] = up = dolfin.Function(Vu) sim.data['upp%d' % d] = upp = dolfin.Function(Vu) sim.data['u_conv%d' % d] = uc = dolfin.Function(Vu) u_list.append(u) up_list.append(up) upp_list.append(upp) u_conv.append(uc) sim.data['u'] = dolfin.as_vector(u_list) sim.data['up'] = dolfin.as_vector(up_list) sim.data['upp'] = dolfin.as_vector(upp_list) sim.data['u_conv'] = dolfin.as_vector(u_conv) sim.data['p'] = dolfin.Function(Vp)
def __init__(self, u, name="Assigned Vector Function"): self.u = u assert isinstance(u, ListTensor) V = u[0].function_space() mesh = V.mesh() family = V.ufl_element().family() degree = V.ufl_element().degree() constrained_domain = V.dofmap().constrained_domain Vv = df.VectorFunctionSpace(mesh, family, degree, constrained_domain=constrained_domain) df.Function.__init__(self, Vv, name=name) self.fa = [df.FunctionAssigner(Vv.sub(i), V) for i, _u in enumerate(u)]
def make_unit_vector(V, VV, dofs_x, fill_coordinates, foc=None): e_c_x = dolfin.Function(V) e_c_y = dolfin.Function(V) e_c_z = dolfin.Function(V) for i, coord in enumerate(dofs_x): fill_coordinates(i, e_c_x, e_c_y, e_c_z, coord, foc) e = dolfin.Function(VV) fa = [dolfin.FunctionAssigner(VV.sub(i), V) for i in range(3)] for i, e_c_comp in enumerate([e_c_x, e_c_y, e_c_z]): fa[i].assign(e.split()[i], e_c_comp) return e
def create_functions(self): """ Create functions to hold solutions """ sim = self.simulation # Function spaces Vu = sim.data['Vu'] Vp = sim.data['Vp'] # Create velocity functions on component and vector form create_vector_functions(sim, 'u', 'u%d', Vu) create_vector_functions(sim, 'up', 'up%d', Vu) create_vector_functions(sim, 'upp', 'upp%d', Vu) create_vector_functions(sim, 'u_conv', 'u_conv%d', Vu) create_vector_functions(sim, 'up_conv', 'up_conv%d', Vu) create_vector_functions(sim, 'upp_conv', 'upp_conv%d', Vu) create_vector_functions(sim, 'u_unlim', 'u_unlim%d', Vu) sim.data['ui_tmp'] = dolfin.Function(Vu) # Create coupled vector function ue = Vu.ufl_element() e_mixed = dolfin.MixedElement([ue] * sim.ndim) Vcoupled = dolfin.FunctionSpace(Vu.mesh(), e_mixed) sim.data['uvw_star'] = dolfin.Function(Vcoupled) sim.data['uvw_temp'] = dolfin.Function(Vcoupled) sim.ndofs += Vcoupled.dim() + Vp.dim() # Create assigner to extract split function from uvw and vice versa self.assigner_split = dolfin.FunctionAssigner([Vu] * sim.ndim, Vcoupled) self.assigner_merge = dolfin.FunctionAssigner(Vcoupled, [Vu] * sim.ndim) # Create pressure function sim.data['p'] = dolfin.Function(Vp) sim.data['p_hat'] = dolfin.Function(Vp)
def __init__(self, Vh_STATE, Vhs, weights, geo, bcs0, datafile, variance_u, variance_g): if hasattr(geo, "dx"): self.dx = geo.dx(geo.PHYSICAL) else: self.dx = dl.dx self.ds = geo.ds(geo.AXIS) x, y, U, V, uu, vv, ww, uv, k = np.loadtxt(datafile, skiprows=2, unpack=True) u_fun_data = VelocityDNS(x=x, y=y, U=U, V=V, symmetrize=True, coflow=0.) u_data = dl.interpolate(u_fun_data, Vhs[0]) if Vh_STATE.num_sub_spaces() == 3: u_trial, p_trial, g_trial = dl.TrialFunctions(Vh_STATE) u_test, p_test, g_test = dl.TestFunctions(Vh_STATE) else: raise InputError() Wform = dl.Constant(1./variance_u)*dl.inner(u_trial, u_test)*self.dx +\ dl.Constant(1./variance_g)*g_trial*g_test*self.ds self.W = dl.assemble(Wform) dummy = dl.Vector() self.W.init_vector(dummy, 0) [bc.zero(self.W) for bc in bcs0] Wt = Transpose(self.W) [bc.zero(Wt) for bc in bcs0] self.W = Transpose(Wt) xfun = dl.Function(Vh_STATE) assigner = dl.FunctionAssigner(Vh_STATE, Vhs) assigner.assign(xfun, [ u_data, dl.Function(Vhs[1]), dl.interpolate(dl.Constant(1.), Vhs[2]) ]) self.d = xfun.vector() self.w = (weights * 0.5)
def __init__(self, Vh_STATE, Vhs, bcs0, datafile, dx=dl.dx): self.dx = dx x, y, U, V, uu, vv, ww, uv, k = np.loadtxt(datafile, skiprows=2, unpack=True) u_fun_mean = VelocityDNS(x=x, y=y, U=U, V=V, symmetrize=True, coflow=0.) u_fun_data = VelocityDNS(x=x, y=y, U=U, V=V, symmetrize=False, coflow=0.) k_fun_mean = KDNS(x=x, y=y, k=k, symmetrize=True) k_fun_data = KDNS(x=x, y=y, k=k, symmetrize=False) u_data = dl.interpolate(u_fun_data, Vhs[0]) k_data = dl.interpolate(k_fun_data, Vhs[2]) noise_var_u = dl.assemble( dl.inner(u_data - u_fun_mean, u_data - u_fun_mean) * self.dx) noise_var_k = dl.assemble( dl.inner(k_data - k_fun_mean, k_data - k_fun_mean) * self.dx) u_trial, p_trial, k_trial, e_trial = dl.TrialFunctions(Vh_STATE) u_test, p_test, k_test, e_test = dl.TestFunctions(Vh_STATE) Wform = dl.Constant(1./noise_var_u)*dl.inner(u_trial, u_test)*self.dx + \ dl.Constant(1./noise_var_k)*dl.inner(k_trial, k_test)*self.dx self.W = dl.assemble(Wform) dummy = dl.Vector() self.W.init_vector(dummy, 0) [bc.zero(self.W) for bc in bcs0] Wt = Transpose(self.W) [bc.zero(Wt) for bc in bcs0] self.W = Transpose(Wt) xfun = dl.Function(Vh_STATE) assigner = dl.FunctionAssigner(Vh_STATE, Vhs) assigner.assign( xfun, [u_data, dl.Function(Vhs[1]), k_data, dl.Function(Vhs[3])]) self.d = xfun.vector()
def assign_ic(func, data): mixed_func_space = func.function_space() functions = func.split(deepcopy=True) V = df.FunctionSpace(mixed_func_space.mesh(), "CG", 1) ic_indices = np.random.randint(0, data.shape[0], size=functions[0].vector().local_size()) _data = data[ic_indices] for i, f in enumerate(functions): ic_func = df.Function(V) ic_func.vector()[:] = np.array(_data[:, i]) assigner = df.FunctionAssigner(mixed_func_space.sub(i), V) assigner.assign(func.sub(i), ic_func)
def assign_restart_ic(receiving_function: df.Function, assigning_func_iterator: Iterable[df.Function]) -> None: """Assign a seriess of functions to the `receiving_function`. This function is indended for use when restarting simulations, using previously computed solutions as initial conditions. """ # Get receiving function space mixed_function_space = receiving_function.function_space() assigning_function_space = df.FunctionSpace(mixed_function_space.mesh(), "CG", 1) for subfunc_idx, assigning_sub_function in enumerate( assigning_func_iterator): assigner = df.FunctionAssigner(mixed_function_space.sub(subfunc_idx), assigning_function_space) assigner.assign(receiving_function.sub(subfunc_idx), assigning_sub_function)
def create_functions(self): """ Create functions to hold solutions """ sim = self.simulation # Function spaces Vu = sim.data['Vu'] Vp = sim.data['Vp'] cd = sim.data['constrained_domain'] # Create coupled mixed function space and mixed function to hold results func_spaces = [Vu] * sim.ndim + [Vp] self.subspace_names = ['u%d' % d for d in range(sim.ndim)] + ['p'] if self.use_lagrange_multiplicator: Vl = dolfin.FunctionSpace(sim.data['mesh'], "R", 0, constrained_domain=cd) sim.data['l'] = dolfin.Function(Vl) func_spaces.append(Vl) self.subspace_names.append('l') e_mixed = dolfin.MixedElement([fs.ufl_element() for fs in func_spaces]) Vcoupled = dolfin.FunctionSpace(sim.data['mesh'], e_mixed) sim.data['Vcoupled'] = Vcoupled sim.ndofs += Vcoupled.dim() Nspace = len(func_spaces) self.subspaces = [Vcoupled.sub(i) for i in range(Nspace)] sim.data['coupled'] = self.coupled_func = dolfin.Function(Vcoupled) self.assigner = dolfin.FunctionAssigner(func_spaces, Vcoupled) # Create segregated functions on component and vector form create_vector_functions(sim, 'u', 'u%d', Vu) create_vector_functions(sim, 'up', 'up%d', Vu) create_vector_functions(sim, 'upp', 'upp%d', Vu) create_vector_functions(sim, 'u_conv', 'u_conv%d', Vu) create_vector_functions(sim, 'up_conv', 'up_conv%d', Vu) create_vector_functions(sim, 'upp_conv', 'upp_conv%d', Vu) create_vector_functions(sim, 'u_unlim', 'u_unlim%d', Vu) sim.data['p'] = dolfin.Function(Vp) sim.data['ui_tmp'] = dolfin.Function(Vu)
def normalize_vector_field(u): dim = len(u) S = u.function_space().sub(0).collapse() components = vectorfield_to_components(u, S, dim) normarray = np.sqrt( sum(gather_broadcast(components[i].vector().array()) ** 2 for i in range(dim)) ) for i in range(dim): assign_to_vector( components[i].vector(), gather_broadcast(components[i].vector().array()) / normarray, ) assigners = [df.FunctionAssigner(u.function_space().sub(i), S) for i in range(dim)] for i, comp, assigner in zip(list(range(dim)), components, assigners): assigner.assign(u.sub(i), comp) return u
def assign_ic_subdomain( *, brain: CoupledBrainModel, vs_prev: df.Function, value: float, subdomain_id: int, subfunction_index: int ) -> None: """ Compute a function with `value` in the subdomain corresponding to `subdomain_id`. Assign this function to subfunction `subfunction_index` of vs_prev. """ mesh = brain._mesh cell_function = brain._cell_function dX = df.Measure("dx", domain=mesh, subdomain_data=cell_function) V = df.FunctionSpace(mesh, "DG", 0) u = df.TrialFunction(V) v = df.TestFunction(V) sol = df.Function(V) sol.vector().zero() # Make sure it is initialised to zero F = -u*v*dX(subdomain_id) + df.Constant(value)*v*dX(subdomain_id) a = df.lhs(F) L = df.rhs(F) A = df.assemble(a, keep_diagonal=True) A.ident_zeros() b = df.assemble(L) solver = df.KrylovSolver("cg", "petsc_amg") solver.set_operator(A) solver.solve(sol.vector(), b) VCG = df.FunctionSpace(mesh, "CG", 1) v_new = df.Function(VCG) v_new.interpolate(sol) Vp = vs_prev.function_space().sub(subfunction_index) merger = df.FunctionAssigner(Vp, VCG) merger.assign(vs_prev.sub(subfunction_index), v_new)
def new_assign_ic(receiving_function: df.Function, ic_generator: NonuniformIC, degree: int = 1) -> None: """ Assign receiving_function(x, y) <- `ic_function`(x, y), for x, in the mesh. Arguments: receiving_function: The function which is assigned the initial condition. ic_function_tuple: A tuple of python callables which return the initial condition for each point (x, y). The number of functions must match the number of subfunctions in `receiving_function`. """ mixed_func_space = receiving_function.function_space() mesh = mixed_func_space.mesh() V = df.FunctionSpace(mesh, "CG", 1) # TODO: infer this somehow class InitialConditionInterpolator(df.UserExpression): def __init__(self, **kwargs): super().__init__(kwargs) self._ic_func = None def set_interpolator(self, interpolation_function): self._ic_func = interpolation_function def eval(self, value, x): value[0] = self._ic_func(x[0]) # TODO: 1D for now ic_interpolator = InitialConditionInterpolator() # Copy functions to be able to assign to them functions = receiving_function.split(deepcopy=True) for i, (f, ic_func) in enumerate(zip(functions, ic_generator())): class IC(df.Expression): def eval(self, value, x): value[0] = ic_func(x[0]) # TODO: 1D for now ic = IC(degree=degree) assigner = df.FunctionAssigner(mixed_func_space.sub(i), V) assigner.assign(receiving_function.sub(i), df.project(ic, V))
def __init__(self, model: Model, ode_timestep: float = None, periodic_domain: df.SubDomain = None, parameters: df.Parameters = None) -> None: """Create solver from given Cardiac Model and (optional) parameters.""" assert isinstance(model, Model), "Expecting Model as first argument" self.periodic_domain = periodic_domain self._parameters = self.default_parameters() if parameters is not None: self._parameters.update(parameters) self._ode_timestep = ode_timestep # Set model and parameters self._model = model # Extract solution domain self._domain = self._model.mesh self._time = self._model.time self._cell_function = self._model.cell_domains # Create ODE solver and extract solution fields self.ode_solver = self._create_ode_solver() self.vs_, self.vs = self.ode_solver.solution_fields() self.VS = self.vs.function_space() # Create PDE solver and extract solution fields self.pde_solver = self._create_pde_solver() self.v_, self.vur = self.pde_solver.solution_fields() # # Create function assigner for merging v from self.vur into self.vs[0] if self._parameters["pde_solver"] == "bidomain": V = self.vur.function_space().sub(0) else: V = self.vur.function_space() self.merger = df.FunctionAssigner(self.VS.sub(0), V)
def interpolate_ic(time: Sequence[float], data: np.ndarray, receiving_function: df.Function, boundaries: Iterable[np.ndarray], wavespeed: float = 1.0) -> None: mixed_func_space = receiving_function.function_space() mesh = mixed_func_space.mesh() V = df.FunctionSpace(mesh, "CG", 1) # TODO: infer this somehow class InitialConditionInterpolator(df.UserExpression): def __init__(self, **kwargs): super().__init__(kwargs) self._ic_func = None self._nearest_edge_interpolator = NearestEdgeTree(boundaries) def set_interpolator(self, interpolation_function): self._ic_func = interpolation_function def eval(self, value, x): _, r = self._nearest_edge_interpolator.query(x) value[0] = self._ic_func(r / wavespeed) # TODO: 1D for now # value[0] = r # value[0] = self._ic_func(x[0]/wavespeed) # TODO: 1D for now ic_interpolator = InitialConditionInterpolator() # Copy functions to be able to assign to them subfunction_copy = receiving_function.split(deepcopy=True) for i, f in enumerate(subfunction_copy): # from IPython import embed; embed() # assert False ic_interpolator.set_interpolator( lambda x: np.interp(x, time, data[i, :])) assigner = df.FunctionAssigner(mixed_func_space.sub(i), V) assigner.assign(receiving_function.sub(i), df.project(ic_interpolator, V))
# Now we want to modify the vector function space # to get the vector (2, 1, 1) in every vertex vec = df.assemble( df.dot(df.as_vector((2 * f1, f1, f1)), df.TestFunction(space_S3)) * df.dP) f3.vector().axpy(1, vec) # Scalar space assign g2 = df.Function(space_S1) g2.assign(df.FunctionAXPY(f1, 2.)) g2.vector().axpy(-1, f2.vector()) assert df.near(g2.vector().norm('l2'), 0) # Assigner for components of the vector space S3_assigners = [ df.FunctionAssigner(space_S3.sub(i), space_S1) for i in range(space_S3.num_sub_spaces()) ] g3 = df.Function(space_S3) # Assign to components comps = [f2, f1, f1] [S3_assigners[i].assign(g3.sub(i), comp) for i, comp in enumerate(comps)] g3.vector().axpy(-1, f3.vector()) assert df.near(g3.vector().norm('l2'), 0) df.plot(f2) df.plot(f3) df.interactive()
def __init__( self, energy, state, bcs, # rayleigh=None, Hessian=None, nullspace=None, parameters=None): """Solves second order stability problem - computes inertia - solves full eigenvalue problem * Parameters: - energy (form) - state (tuple) - bcs (list) Optional arguments: rayleigh (form), Hessian (form), nullspace, parameters """ OptDB = PETSc.Options() OptDB.view() self.i = 0 self.u = state['u'] self.alpha = state['alpha'] self._u = dolfin.Vector(self.u.vector()) self._alpha = dolfin.Vector(self.alpha.vector()) self.mesh = state['alpha'].function_space().mesh() self.Z = dolfin.FunctionSpace( self.mesh, dolfin.MixedElement( [self.u.ufl_element(), self.alpha.ufl_element()])) self.z = dolfin.Function(self.Z) self.z_old = dolfin.Function(self.Z) zeta = dolfin.TestFunction(self.Z) v, beta = dolfin.split(zeta) self.dm = self.Z.dofmap() self.ownership = self.Z.dofmap().ownership_range() Zu = self.Z.extract_sub_space([0]) Za = self.Z.extract_sub_space([1]) self.Xa = Za.collapse().tabulate_dof_coordinates() self.Xu = Zu.collapse().tabulate_dof_coordinates() (_, self.mapa) = Za.collapse(collapsed_dofs=True) (_, self.mapu) = Zu.collapse(collapsed_dofs=True) self.assigner = dolfin.FunctionAssigner( self.Z, # receiving space [self.u.function_space(), self.alpha.function_space()]) # assigning spaces self.parameters = self.setParameters(parameters) self.ownership = self.Z.dofmap().ownership_range() self.assigner = dolfin.FunctionAssigner( self.Z, # receiving space [self.u.function_space(), self.alpha.function_space()]) # assigning space dim = self.u.function_space().ufl_element().value_size() self.stable = '' self.negev = -1 self.mineig = 1. self.Ealpha = derivative( energy, self.alpha, dolfin.TestFunction(self.alpha.ufl_function_space())) self.energy = energy (z_u, z_a) = dolfin.split(self.z) energy = ufl.replace(energy, {self.u: z_u, self.alpha: z_a}) self.J = derivative(energy, self.z, dolfin.TestFunction(self.Z)) self.H = derivative(self.J, self.z, dolfin.TrialFunction(self.Z)) self.nullspace = nullspace if Hessian: self.Hessian = Hessian self.ownership_range = self.Z.dofmap().ownership_range() if len(bcs) > 0: self.bcs = bcs self.bc_dofs = self.get_bc_dofs(bcs) else: self.bcs = None self.bc_dofs = set() self.perturbation_v = dolfin.Function(self.Z.sub(0).collapse()) self.perturbation_beta = dolfin.Function(self.Z.sub(1).collapse()) self._Hessian = Hessian if Hessian.__class__ == ufl.form.Form else self.H
def __init__( self, simulation, w, incompressibility_flux_type='central', D12=None, degree=None, use_bcs=True, use_nedelec=True, ): """ Implement equation 4a and 4b in "Two new techniques for generating exactly incompressible approximate velocities" by Bernardo Cocburn (2009) For each element K in the mesh: <u⋅n, φ> = <û⋅n, φ> ∀ ϕ ∈ P_{k}(F) for any face F ∈ ∂K (u, ϕ) = (w, ϕ) ∀ φ ∈ P_{k-2}(K)^2 (u, ϕ) = (w, ϕ) ∀ φ ∈ {ϕ ∈ P_{k}(K)^2 : ∇⋅ϕ = 0 in K, ϕ⋅n = 0 on ∂K} Here w is the input velocity function in DG2 space and û is the flux at each face. P_{x} is the space of polynomials of order k The flux type can be 'central' or 'upwind' """ self.simulation = simulation simulation.log.info(' Setting up velocity BDM projection') V = w[0].function_space() ue = V.ufl_element() gdim = w.ufl_shape[0] if degree is None: pdeg = ue.degree() Vout = V else: pdeg = degree Vout = FunctionSpace(V.mesh(), 'DG', degree) pg = (pdeg, gdim) assert ue.family() == 'Discontinuous Lagrange' assert incompressibility_flux_type in ('central', 'upwind') if use_nedelec and pdeg > 1: a, L, V = self._setup_projection_nedelec( w, incompressibility_flux_type, D12, use_bcs, pdeg, gdim) elif gdim == 2 and pdeg == 1: a, L, V = self._setup_dg1_projection_2D( w, incompressibility_flux_type, D12, use_bcs) elif gdim == 2 and pdeg == 2: a, L, V = self._setup_dg2_projection_2D( w, incompressibility_flux_type, D12, use_bcs) else: raise NotImplementedError('VelocityBDMProjection does not support ' 'degree %d and dimension %d' % pg) # Pre-factorize matrices and store for usage in projection self.local_solver = LocalSolver(a, L) self.local_solver.factorize() self.temp_function = Function(V) self.w = w # Create function assigners self.assigners = [] for i in range(gdim): self.assigners.append(dolfin.FunctionAssigner(Vout, V.sub(i)))
def reconstruction( localization_tensors: dict, macro_kinematic: dict, function_spaces: dict, localization_rules: dict = {}, output_request=("u", "eps"), **kwargs, ): """ One argument among localization_rules and trunc_order must be used. Parameters ---------- localization_tensors : dictionnary Format : { [Macro_field] : { [micro_field] : [list of lists : for each Macro_field component a list contains the components of the localization tensor field.] } } macro_kinematic : dictionnary of fields The macroscopic kinematic fields. Keys : 'U', 'EG', 'EGG',\\dots Values : lists of components. None can be used to indicate a component that is equal to 0 or irrelevant. function_spaces : dictionnary Function space into which each mechanical field have to be built. - keys : 'u', 'eps' or 'sigma' - values : FEniCS function space localization_rules : dict, optional The rules that have to be followed for the construction of the fluctuations. Defaults to {} i.e. the trunc_order parameter will be used. output_request : tuple of strings, optional Fields that have to be calculated. The request must be consistent with the keys of other parameters : - function_spaces - localization_rules outputs can be : ======= =================== name Description ======= =================== 'u' displacement field 'eps' strain field 'sigma' stress field ======= =================== Defaults to ('u', 'eps'). Return ------ Dictionnary Mechanical fields with microscopic fluctuations. Keys are "eps", "sigma" and "u" respectively for the strain, stress and displacement fields. Other Parameters ---------------- **kwargs : Valid kwargs are =============== ===== ============================================= Key Type Description =============== ===== ============================================= interp_fnct str The name of the desired function for the interpolations. Allowed values are : "dolfin.interpolate" and "interpolate_nonmatching_mesh_any" trunc_order int Order of truncation for the reconstruction of the displacement according to the notations used in ???. Override localization_rules parameter. =============== ====== ============================================ """ # TODO : récupérer topo_dim à partir des tenseurs de localisation, ou mieux, à partir des espaces fonctionnels # TODO : choisir comment on fixe la len des listes correspondantes aux composantes de u et de epsilon. # TODO : permettre la translation du RVE par rapport à la structure macro autre part # TODO : translation_microstructure: np.array, optional # TODO : Vector, 1D array (shape (2,) or (3,)), position origin used for the description of the RVE with regards to the macroscopic origin. # Au choix, utilisation de trunc_order ou localization_rules dans les kargs trunc_order = kwargs.pop("trunc_order", None) if trunc_order: localization_rules = { "u": [(MACRO_FIELDS_NAMES[i], MACRO_FIELDS_NAMES[i]) for i in range(trunc_order + 1)], "eps": [(MACRO_FIELDS_NAMES[i], MACRO_FIELDS_NAMES[i]) for i in range(1, trunc_order + 1)], } # * Ex. for truncation order = 2: # * localization_rules = { # * 'u': [('U','U'), ('E','E'), ('EG','EG')], # * 'eps': [('E','E'), ('EG', 'EG')] # *} interpolate = kwargs.pop("interp_fnct", None) if interpolate: if interpolate == "dolfin.interpolate": interpolate = fe.interpolate elif interpolate == "interpolate_nonmatching_mesh_any": interpolate = interpolate_nonmatching_mesh_any else: interpolate = fe.interpolate else: interpolate = fe.interpolate reconstructed_fields = dict() for mecha_field in output_request: # * Prepare function spaces and assigner fspace = function_spaces[mecha_field] value_shape = fspace.ufl_element()._value_shape if len(value_shape) == 1: vector_dim = value_shape[0] mesh = fspace.mesh() element = fe.FiniteElement( fspace._ufl_element._family, mesh.ufl_cell(), fspace._ufl_element._degree, ) element_family = element.family() constrain = fspace.dofmap().constrained_domain scalar_fspace = fe.FunctionSpace(mesh, element, constrained_domain=constrain) assigner = fe.FunctionAssigner(fspace, [scalar_fspace] * vector_dim) logger.debug( f"for reconstruction of {mecha_field}, vector_dim = {vector_dim}" ) elif len(value_shape) == 0: logger.warning( "The value_shape attribute has not been found for the following function space. It is therefore assumed to be a scalar function space for the reconstruction : %s", fspace, ) scalar_fspace = fspace vector_dim = 1 element_family = fspace._ufl_element._family assigner = fe.FunctionAssigner(fspace, scalar_fspace) else: raise NotImplementedError( "Only vector fields are supported by the reconstruction function for now." ) macro_kin_funct = dict() for key, field in macro_kinematic.items(): macro_kin_funct[key] = list() for comp in field: if comp: macro_kin_funct[key].append( interpolate(comp, scalar_fspace)) else: macro_kin_funct[key].append(0) # * Reconstruction proper contributions = [list() for i in range(vector_dim)] for macro_key, localization_key in localization_rules[mecha_field]: macro_f = macro_kin_funct[macro_key] loc_tens = localization_tensors[localization_key][mecha_field] for macro_comp, loc_tens_comps in zip(macro_f, loc_tens): if not macro_comp: continue for i in range(vector_dim): loc_comp = interpolate(loc_tens_comps[i], scalar_fspace) contributions[i].append((macro_comp, loc_comp)) # components = [sum(compnt_contrib) for compnt_contrib in contributions] components = [fe.Function(scalar_fspace) for i in range(vector_dim)] for i in range(vector_dim): vec = components[i].vector() values = vec.get_local() new_val = np.zeros_like(values) for macro_kin, loc in contributions[i]: loc_local_val = loc.vector().get_local() kin_local_val = macro_kin.vector().get_local() new_val += loc_local_val * kin_local_val values[:] = new_val vec.set_local(values) vec.apply("insert") # TODO : Regarder si le passage par np array faire perdre du temps, et auquel cas si l'on peut s'en passer. # TODO : avec axpy par exemple. # https://fenicsproject.org/docs/dolfin/2016.2.0/cpp/programmers-reference/la/GenericVector.html #noqa # * Components -> vector field field = fe.Function(fspace) if len(value_shape) == 0: components = components[0] assigner.assign(field, components) reconstructed_fields[mecha_field] = field return reconstructed_fields
import dolfin as dl mesh = dl.UnitSquareMesh(10, 10) V1 = dl.VectorFunctionSpace(mesh, "CG", 2) V2 = dl.FunctionSpace(mesh, "CG", 1) V3 = dl.FunctionSpace(mesh, "CG", 1) V = dl.MixedFunctionSpace([V1, V2, V3]) f1 = dl.Expression(("A + B*x[1]*(1-x[1])", "0."), A=1., B=1.) f2 = dl.Expression("A*x[0]", A=1.) f3 = dl.Expression("A", A=-3.) F1, F2, F3 = [ dl.interpolate(fi, Vi) for fi, Vi in zip((f1, f2, f3), (V1, V2, V3)) ] assigner = dl.FunctionAssigner(V, [V1, V2, V3]) state = dl.Function(V) assigner.assign(state, [F1, F2, F3]) print "|| state ||^2_L^(2) = ", dl.assemble(dl.inner(state, state) * dl.dx) dl.plot(state.sub(0)) dl.plot(state.sub(1)) dl.plot(state.sub(2)) dl.interactive()
def __init__(self, mesh: df.Mesh, time: df.Constant, M_i: tp.Union[df.Expression, tp.Dict[int, df.Expression]], M_e: tp.Union[df.Expression, tp.Dict[int, df.Expression]], I_s: tp.Union[df.Expression, tp.Dict[int, df.Expression]] = None, I_a: tp.Union[df.Expression, tp.Dict[int, df.Expression]] = None, ect_current: tp.Dict[int, df.Expression] = None, v_: df.Function = None, cell_domains: df.MeshFunction = None, facet_domains: df.MeshFunction = None, dirichlet_bc: tp.List[tp.Tuple[df.Expression, int]] = None, dirichlet_bc_v: tp.List[tp.Tuple[df.Expression, int]] = None, periodic_domain: df.SubDomain = None, parameters: df.Parameters = None) -> None: """Initialise solverand check all parametersare correct. NB! The periodic domain has to be set in the cellsolver too. """ self._timestep = None comm = df.MPI.comm_world rank = df.MPI.rank(comm) msg = "Expecting mesh to be a Mesh instance, not {}".format(mesh) assert isinstance(mesh, df.Mesh), msg msg = "Expecting time to be a Constant instance (or None)." assert isinstance(time, df.Constant) or time is None, msg msg = "Expecting parameters to be a Parameters instance (or None)" assert isinstance(parameters, df.Parameters) or parameters is None, msg self._nullspace_basis = None # Store input self._mesh = mesh self._time = time # Initialize and update parameters if given self._parameters = self.default_parameters() if parameters is not None: self._parameters.update(parameters) if self._parameters["Chi"] == -1 or self._parameters["Cm"] == -1: raise ValueError( "Need Chi and Cm to be specified explicitly throug the parameters." ) # Set-up function spaces k = self._parameters["polynomial_degree"] Ve = df.FiniteElement("CG", self._mesh.ufl_cell(), k) V = df.FunctionSpace(self._mesh, "CG", k, constrained_domain=periodic_domain) Ue = df.FiniteElement("CG", self._mesh.ufl_cell(), k) if self._parameters["linear_solver_type"] == "direct": Re = df.FiniteElement("R", self._mesh.ufl_cell(), 0) _element = df.MixedElement((Ve, Ue, Re)) self.VUR = df.FunctionSpace(mesh, _element, constrained_domain=periodic_domain) else: _element = df.MixedElement((Ve, Ue)) self.VUR = df.FunctionSpace(mesh, _element, constrained_domain=periodic_domain) self.V = V if cell_domains is None: cell_domains = df.MeshFunction("size_t", mesh, self._mesh.geometry().dim()) cell_domains.set_all(0) # Chech that it is indeed a cell function. cell_dim = cell_domains.dim() mesh_dim = self._mesh.geometry().dim() msg = "Got {cell_dim}, expected {mesh_dim}.".format(cell_dim=cell_dim, mesh_dim=mesh_dim) assert cell_dim == mesh_dim, msg self._cell_domains = cell_domains if facet_domains is None: facet_domains = df.MeshFunction("size_t", mesh, self._mesh.geometry().dim() - 1) facet_domains.set_all(0) # Check that it is indeed a facet function. facet_dim = facet_domains.dim() msg = "Got {facet_dim}, expected {mesh_dim}.".format( facet_dim=facet_dim, mesh_dim=mesh_dim - 1) assert facet_dim == mesh_dim - 1, msg self._facet_domains = facet_domains # Gather all cell keys on all processes. Greatly simplifies things cell_keys = set(self._cell_domains.array()) all_cell_keys = comm.allgather(cell_keys) all_cell_keys = reduce(or_, all_cell_keys) # If Mi is not dict, make dict if not isinstance(M_i, dict): M_i = {int(i): M_i for i in all_cell_keys} else: # Check that the keys match the cell function M_i_keys = set(M_i.keys()) msg = "Got {M_i_keys}, expected {cell_keys}.".format( M_i_keys=M_i_keys, cell_keys=all_cell_keys) assert M_i_keys == all_cell_keys, msg # If Me is not dict, make dict if not isinstance(M_e, dict): M_e = {int(i): M_e for i in all_cell_keys} else: # Check that the keys match the cell function M_e_keys = set(M_e.keys()) msg = "Got {M_e_keys}, expected {cell_keys}.".format( M_e_keys=M_e_keys, cell_keys=all_cell_keys) assert M_e_keys == all_cell_keys, msg self._M_i = M_i self._M_e = M_e # Store source terms if I_s is not None and not isinstance(I_s, dict): I_s = {key: I_s for key in all_cell_keys} self._I_s = I_s if I_a is not None and not isinstance(I_a, dict): I_a = {key: I_a for key in all_cell_keys} self._I_a = I_a # Set the ECT current, Note, it myst depend on `time` to be updated if ect_current is not None: ect_tags = set(ect_current.keys()) facet_tags = set(self._facet_domains.array()) msg = "{} not in facet domains ({}).".format(ect_tags, facet_tags) assert ect_tags <= facet_tags, msg self._ect_current = ect_current # Set-up solution fields: if v_ is None: self.merger = df.FunctionAssigner(V, self.VUR.sub(0)) self.v_ = df.Function(V, name="v_") else: # df.debug("Experimental: v_ shipped from elsewhere.") self.merger = None self.v_ = v_ self.vur = df.Function(self.VUR, name="vur") # Set Dirichlet bcs for the transmembrane potential self._bcs = [] if dirichlet_bc_v is not None: for function, marker in dirichlet_bc_v: self._bcs.append( df.DirichletBC(self.VUR.sub(0), function, self._facet_domains, marker)) # Set Dirichlet bcs for the extra cellular potential if dirichlet_bc is not None: for function, marker in dirichlet_bc: self._bcs.append( df.DirichletBC(self.VUR.sub(1), function, self._facet_domains, marker))
def __init__(self, Vh_STATE, Vhs, bcs0, datafile, dx=dl.dx): self.dx = dx x, y, U, V, uu, vv, ww, uv, k = np.loadtxt(datafile, skiprows=2, unpack=True) u_fun_mean = VelocityDNS(x=x, y=y, U=U, V=V, symmetrize=True, coflow=0.) u_fun_data = VelocityDNS(x=x, y=y, U=U, V=V, symmetrize=False, coflow=0.) u_mean = dl.interpolate(u_fun_mean, Vhs[0]) u_data = dl.interpolate(u_fun_data, Vhs[0]) q_order = dl.parameters["form_compiler"]["quadrature_degree"] dl.parameters["form_compiler"]["quadrature_degree"] = 6 noise_var_u = dl.assemble( dl.inner(u_fun_data - u_mean, u_fun_data - u_mean) * self.dx, form_compiler_parameters=dl.parameters["form_compiler"]) dl.parameters["form_compiler"]["quadrature_degree"] = q_order noise_var_u = 1.e-3 mpi_comm = Vh_STATE.mesh().mpi_comm() rank = dl.MPI.rank(mpi_comm) if rank == 0: print "Noise Variance = {0}".format(noise_var_u) if Vh_STATE.num_sub_spaces() == 2: u_trial, p_trial = dl.TrialFunctions(Vh_STATE) u_test, p_test = dl.TestFunctions(Vh_STATE) elif Vh_STATE.num_sub_spaces() == 3: u_trial, p_trial, g_trial = dl.TrialFunctions(Vh_STATE) u_test, p_test, g_test = dl.TestFunctions(Vh_STATE) else: raise InputError() Wform = dl.Constant(1. / noise_var_u) * dl.inner(u_trial, u_test) * self.dx self.W = dl.assemble(Wform) dummy = dl.Vector() self.W.init_vector(dummy, 0) [bc.zero(self.W) for bc in bcs0] Wt = Transpose(self.W) [bc.zero(Wt) for bc in bcs0] self.W = Transpose(Wt) xfun = dl.Function(Vh_STATE) assigner = dl.FunctionAssigner(Vh_STATE, Vhs) if Vh_STATE.num_sub_spaces() == 2: assigner.assign(xfun, [u_data, dl.Function(Vhs[1])]) elif Vh_STATE.num_sub_spaces() == 3: assigner.assign( xfun, [u_data, dl.Function(Vhs[1]), dl.Function(Vhs[2])]) self.d = xfun.vector()
def vectorfield_to_components(u, S, dim): components = [df.Function(S) for i in range(dim)] assigners = [df.FunctionAssigner(S, u.function_space().sub(i)) for i in range(dim)] for i, comp, assigner in zip(list(range(dim)), components, assigners): assigner.assign(comp, u.sub(i)) return components
def __init__( self, time: df.Constant, mesh: df.Mesh, intracellular_conductivity: Dict[int, df.Expression], extracellular_conductivity: Dict[int, df.Expression], cell_function: df.MeshFunction, cell_tags: CellTags, interface_function: df.MeshFunction, interface_tags: InterfaceTags, parameters: CoupledBidomainParameters, neumann_boundary_condition: Dict[int, df.Expression] = None, v_prev: df.Function = None, surface_to_volume_factor: Union[float, df.Constant] = None, membrane_capacitance: Union[float, df.Constant] = None, ) -> None: self._time = time self._mesh = mesh self._parameters = parameters # Strip none from cell tags cell_tags = set(cell_tags) - {None} if surface_to_volume_factor is None: surface_to_volume_factor = df.constant(1) if membrane_capacitance is None: membrane_capacitance = df.constant(1) # Set Chi*Cm self._chi_cm = df.Constant(surface_to_volume_factor) * df.Constant( membrane_capacitance) if not set(intracellular_conductivity.keys()) == { *tuple(extracellular_conductivity.keys()) }: raise ValueError( "intracellular conductivity and lambda does not havemnatching keys." ) if not set(cell_tags) == set(intracellular_conductivity.keys()): raise ValueError("Cell tags does not match conductivity keys") self._intracellular_conductivity = intracellular_conductivity self._extracellular_conductivity = extracellular_conductivity # Check cell tags _cell_function_tags = set(cell_function.array()) if set(cell_tags) != _cell_function_tags: # If not equal msg = "Mismatching cell tags. Expected {}, got {}" raise ValueError(msg.format(set(cell_tags), _cell_function_tags)) self._cell_tags = set(cell_tags) self._cell_function = cell_function restrict_tags = self._parameters.restrict_tags if set(restrict_tags) >= self._cell_tags: msg = "restrict tags ({})is not a subset of cell tags ({})" raise ValueError(msg.format(set(restrict_tags), self._cell_tags)) self._restrict_tags = set(restrict_tags) # Check interface tags _interface_function_tags = {*set(interface_function.array()), None} if not set(interface_tags ) <= _interface_function_tags: # if not subset of msg = "Mismatching interface tags. Expected {}, got {}" raise ValueError( msg.format(set(interface_tags), _interface_function_tags)) self._interface_function = interface_function self._interface_tags = interface_tags # Set up function spaces self._transmembrane_function_space = df.FunctionSpace( self._mesh, "CG", 1) transmembrane_element = df.FiniteElement("CG", self._mesh.ufl_cell(), 1) extracellular_element = df.FiniteElement("CG", self._mesh.ufl_cell(), 1) if neumann_boundary_condition is None: self._neumann_bc: Dict[int, df.Expression] = dict() else: self._neumann_bc = neumann_boundary_condition if self._parameters.linear_solver_type == "direct": lagrange_element = df.FiniteElement("R", self._mesh.ufl_cell(), 0) mixed_element = df.MixedElement( (transmembrane_element, extracellular_element, lagrange_element)) else: mixed_element = df.MixedElement( (transmembrane_element, extracellular_element)) self._VUR = df.FunctionSpace( mesh, mixed_element) # TODO: rename to something sensible # Set-up solution fields: if v_prev is None: self._merger = df.FunctionAssigner( self._transmembrane_function_space, self._VUR.sub(0)) self._v_prev = df.Function(self._transmembrane_function_space, name="v_prev") else: self._merger = None self._v_prev = v_prev self._vur = df.Function(self._VUR, name="vur") # TODO: Give sensible name # For normlising rhs_vector. TODO: Unsure about this. Check the nullspace from cbcbeat self._extracellular_dofs = np.asarray(self._VUR.sub(1).dofmap().dofs()) # Mark first timestep self._timestep: df.Constant = None