From 8ac0eb63c9eae769d2f21a71dfe68ee3c7fb2917 Mon Sep 17 00:00:00 2001 From: Pablo Brubeck Date: Mon, 18 Nov 2024 15:59:28 +0000 Subject: [PATCH 01/12] RestrictedFunctionSpace(MixedFunctionSpace) --- firedrake/eigensolver.py | 5 ++--- firedrake/functionspaceimpl.py | 17 ++++++++++++----- .../test_restricted_function_space.py | 16 +++++++++++++--- 3 files changed, 27 insertions(+), 11 deletions(-) diff --git a/firedrake/eigensolver.py b/firedrake/eigensolver.py index ae8a9f020b..f33618f950 100644 --- a/firedrake/eigensolver.py +++ b/firedrake/eigensolver.py @@ -1,6 +1,5 @@ """Specify and solve finite element eigenproblems.""" from firedrake.assemble import assemble -from firedrake.bcs import DirichletBC from firedrake.function import Function from firedrake.functionspace import RestrictedFunctionSpace from firedrake.ufl_expr import TrialFunction, TestFunction @@ -71,12 +70,12 @@ def __init__(self, A, M=None, bcs=None, bc_shift=0.0, restrict=True): M = inner(u, v) * dx if restrict and bcs: # assumed u and v are in the same space here - V_res = RestrictedFunctionSpace(self.output_space, boundary_set=set([bc.sub_domain for bc in bcs])) + V_res = RestrictedFunctionSpace(self.output_space, boundary_set=set(bc.sub_domain for bc in bcs)) u_res = TrialFunction(V_res) v_res = TestFunction(V_res) self.M = replace(M, {u: u_res, v: v_res}) self.A = replace(A, {u: u_res, v: v_res}) - self.bcs = [DirichletBC(V_res, bc.function_arg, bc.sub_domain) for bc in bcs] + self.bcs = [bc.reconstruct(V=V_res) for bc in bcs] self.restricted_space = V_res else: self.A = A # LHS diff --git a/firedrake/functionspaceimpl.py b/firedrake/functionspaceimpl.py index 53f69e92ce..328f225bfd 100644 --- a/firedrake/functionspaceimpl.py +++ b/firedrake/functionspaceimpl.py @@ -860,17 +860,25 @@ class RestrictedFunctionSpace(FunctionSpace): output of the solver. :arg function_space: The :class:`FunctionSpace` to restrict. - :kwarg name: An optional name for this :class:`RestrictedFunctionSpace`, - useful for later identification. :kwarg boundary_set: A set of subdomains on which a DirichletBC will be applied. + :kwarg name: An optional name for this :class:`RestrictedFunctionSpace`, + useful for later identification. Notes ----- If using this class to solve or similar, a list of DirichletBCs will still need to be specified on this space and passed into the function. """ - def __init__(self, function_space, name=None, boundary_set=frozenset()): + def __new__(cls, *args, boundary_set=frozenset(), name=None, **kwargs): + if cls is not RestrictedFunctionSpace: + return super().__new__(cls) + function_space = args[0] + if len(function_space) > 1: + return MixedFunctionSpace([cls(Vsub, boundary_set=boundary_set) for Vsub in function_space], name=name) + return super().__new__(cls) + + def __init__(self, function_space, boundary_set=frozenset(), name=None): label = "" for boundary_domain in boundary_set: label += str(boundary_domain) @@ -884,8 +892,7 @@ def __init__(self, function_space, name=None, boundary_set=frozenset()): label=self._label) self.function_space = function_space self.name = name or (function_space.name or "Restricted" + "_" - + "_".join(sorted( - [str(i) for i in self.boundary_set]))) + + "_".join(sorted(map(str, self.boundary_set)))) def set_shared_data(self): sdata = get_shared_data(self._mesh, self.ufl_element(), self.boundary_set) diff --git a/tests/regression/test_restricted_function_space.py b/tests/regression/test_restricted_function_space.py index 4a26e7834c..28b0a4ffc6 100644 --- a/tests/regression/test_restricted_function_space.py +++ b/tests/regression/test_restricted_function_space.py @@ -168,17 +168,27 @@ def test_restricted_function_space_coord_change(j): new_mesh = Mesh(Function(V).interpolate(as_vector([x, y]))) new_V = FunctionSpace(new_mesh, "CG", j) bc = DirichletBC(new_V, 0, 1) - new_V_restricted = RestrictedFunctionSpace(new_V, name="Restricted", boundary_set=[1]) + new_V_restricted = RestrictedFunctionSpace(new_V, boundary_set=[1], name="Restricted") compare_function_space_assembly(new_V, new_V_restricted, [bc]) +def test_restricted_mixed_spaces(): + mesh = UnitSquareMesh(1, 1) + V = FunctionSpace(mesh, "RT", 1) + Q = FunctionSpace(mesh, "DG", 0) + Z = V * Q + Z_restricted = RestrictedFunctionSpace(Z, boundary_set=[1]) + bcs = [DirichletBC(Z.sub(0), 0, [1])] + compare_function_space_assembly(Z, Z_restricted, bcs) + + @pytest.mark.parametrize(["i", "j"], [(1, 0), (2, 0), (2, 1)]) -def test_restricted_mixed_spaces(i, j): +def test_poisson_mixed_restricted_spaces(i, j): mesh = UnitSquareMesh(1, 1) DG = FunctionSpace(mesh, "DG", j) CG = VectorFunctionSpace(mesh, "CG", i) - CG_res = RestrictedFunctionSpace(CG, "Restricted", boundary_set=[4]) + CG_res = RestrictedFunctionSpace(CG, boundary_set=[4], name="Restricted") W = CG * DG W_res = CG_res * DG bc = DirichletBC(W.sub(0), 0, 4) From c7fcbf46eae51bb2d05e4c01b516a8f34152daf5 Mon Sep 17 00:00:00 2001 From: Pablo Brubeck Date: Mon, 18 Nov 2024 16:28:55 +0000 Subject: [PATCH 02/12] Detect subdomains from DirichletBCs --- firedrake/functionspaceimpl.py | 16 +++++++++++++--- .../regression/test_restricted_function_space.py | 2 +- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/firedrake/functionspaceimpl.py b/firedrake/functionspaceimpl.py index 328f225bfd..842728d591 100644 --- a/firedrake/functionspaceimpl.py +++ b/firedrake/functionspaceimpl.py @@ -860,8 +860,8 @@ class RestrictedFunctionSpace(FunctionSpace): output of the solver. :arg function_space: The :class:`FunctionSpace` to restrict. - :kwarg boundary_set: A set of subdomains on which a DirichletBC will be - applied. + :kwarg boundary_set: An iterable of DirichletBCs or a set of subdomains on + which a DirichletBC will be applied. :kwarg name: An optional name for this :class:`RestrictedFunctionSpace`, useful for later identification. @@ -875,10 +875,20 @@ def __new__(cls, *args, boundary_set=frozenset(), name=None, **kwargs): return super().__new__(cls) function_space = args[0] if len(function_space) > 1: - return MixedFunctionSpace([cls(Vsub, boundary_set=boundary_set) for Vsub in function_space], name=name) + return MixedFunctionSpace([cls(Vsub, boundary_set=boundary_set) + for Vsub in function_space], name=name) return super().__new__(cls) def __init__(self, function_space, boundary_set=frozenset(), name=None): + if all(hasattr(bc, "sub_domain") for bc in boundary_set): + bcs = boundary_set + boundary_set = [] + for bc in bcs: + if bc.function_space() == function_space: + if type(bc.sub_domain) in {str, int}: + boundary_set.append(bc.sub_domain) + else: + boundary_set.extend(bc.sub_domain) label = "" for boundary_domain in boundary_set: label += str(boundary_domain) diff --git a/tests/regression/test_restricted_function_space.py b/tests/regression/test_restricted_function_space.py index 28b0a4ffc6..054f063a72 100644 --- a/tests/regression/test_restricted_function_space.py +++ b/tests/regression/test_restricted_function_space.py @@ -178,8 +178,8 @@ def test_restricted_mixed_spaces(): V = FunctionSpace(mesh, "RT", 1) Q = FunctionSpace(mesh, "DG", 0) Z = V * Q - Z_restricted = RestrictedFunctionSpace(Z, boundary_set=[1]) bcs = [DirichletBC(Z.sub(0), 0, [1])] + Z_restricted = RestrictedFunctionSpace(Z, bcs) compare_function_space_assembly(Z, Z_restricted, bcs) From af9dabaaf8653638bc23e5d161c44ecac8f7663d Mon Sep 17 00:00:00 2001 From: Pablo Brubeck Date: Mon, 18 Nov 2024 17:13:48 +0000 Subject: [PATCH 03/12] Interpolation of mixed UFL expressions --- firedrake/interpolation.py | 23 +++++++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/firedrake/interpolation.py b/firedrake/interpolation.py index 1a26285904..a7079d0fb9 100644 --- a/firedrake/interpolation.py +++ b/firedrake/interpolation.py @@ -992,10 +992,25 @@ def callable(): if numpy.prod(expr.ufl_shape, dtype=int) != V.value_size: raise RuntimeError('Expression of length %d required, got length %d' % (V.value_size, numpy.prod(expr.ufl_shape, dtype=int))) - if len(V) > 1: - raise NotImplementedError( - "UFL expressions for mixed functions are not yet supported.") - loops.extend(_interpolator(V, tensor, expr, subset, arguments, access, bcs=bcs)) + + if len(V) == 1: + loops.extend(_interpolator(V, tensor, expr, subset, arguments, access, bcs=bcs)) + else: + assert len(arguments) == 0 + offset = 0 + for Vsub, usub in zip(V, tensor): + shape = Vsub.value_shape + rank = len(shape) + components = [expr[offset + j] for j in range(Vsub.value_size)] + if rank == 0: + Vexpr = components[0] + elif rank == 1: + Vexpr = ufl.as_vector(components) + else: + Vexpr = ufl.as_tensor(numpy.reshape(components, Vsub.value_shape).tolist()) + loops.extend(_interpolator(Vsub, usub, Vexpr, subset, arguments, access, bcs=bcs)) + offset += Vsub.value_size + if bcs and len(arguments) == 0: loops.extend(partial(bc.apply, f) for bc in bcs) From cfff93c06d87fe1271645967dd0c61017377405e Mon Sep 17 00:00:00 2001 From: Pablo Brubeck Date: Mon, 18 Nov 2024 17:34:32 +0000 Subject: [PATCH 04/12] Fix restrict=True for Mixed problems --- firedrake/bcs.py | 6 ++-- firedrake/functionspaceimpl.py | 2 +- firedrake/variational_solver.py | 10 ++++-- .../test_restricted_function_space.py | 31 +++++++++++++++++-- 4 files changed, 42 insertions(+), 7 deletions(-) diff --git a/firedrake/bcs.py b/firedrake/bcs.py index 272b74ddbf..0b9b492bfc 100644 --- a/firedrake/bcs.py +++ b/firedrake/bcs.py @@ -289,8 +289,10 @@ def __init__(self, V, g, sub_domain, method=None): warnings.simplefilter('always', DeprecationWarning) warnings.warn("Selecting a bcs method is deprecated. Only topological association is supported", DeprecationWarning) - if len(V.boundary_set) and sub_domain not in V.boundary_set: - raise ValueError(f"Sub-domain {sub_domain} not in the boundary set of the restricted space.") + if len(V.boundary_set): + subs = [sub_domain] if type(sub_domain) in {int, str} else sub_domain + if any(sub not in V.boundary_set for sub in subs): + raise ValueError(f"Sub-domain {sub_domain} not in the boundary set of the restricted space.") super().__init__(V, sub_domain) if len(V) > 1: raise ValueError("Cannot apply boundary conditions on mixed spaces directly.\n" diff --git a/firedrake/functionspaceimpl.py b/firedrake/functionspaceimpl.py index 842728d591..493febb432 100644 --- a/firedrake/functionspaceimpl.py +++ b/firedrake/functionspaceimpl.py @@ -880,7 +880,7 @@ def __new__(cls, *args, boundary_set=frozenset(), name=None, **kwargs): return super().__new__(cls) def __init__(self, function_space, boundary_set=frozenset(), name=None): - if all(hasattr(bc, "sub_domain") for bc in boundary_set): + if len(boundary_set) > 0 and all(hasattr(bc, "sub_domain") for bc in boundary_set): bcs = boundary_set boundary_set = [] for bc in bcs: diff --git a/firedrake/variational_solver.py b/firedrake/variational_solver.py index 609d599800..65a7078d09 100644 --- a/firedrake/variational_solver.py +++ b/firedrake/variational_solver.py @@ -22,6 +22,12 @@ "NonlinearVariationalSolver"] +def get_sub(V, indices): + for i in indices: + V = V.sub(i) + return V + + def check_pde_args(F, J, Jp): if not isinstance(F, (ufl.BaseForm, slate.slate.TensorBase)): raise TypeError("Provided residual is a '%s', not a BaseForm or Slate Tensor" % type(F).__name__) @@ -88,8 +94,8 @@ def __init__(self, F, u, bcs=None, J=None, self.restrict = restrict if restrict and bcs: - V_res = RestrictedFunctionSpace(V, boundary_set=set([bc.sub_domain for bc in bcs])) - bcs = [DirichletBC(V_res, bc.function_arg, bc.sub_domain) for bc in bcs] + V_res = RestrictedFunctionSpace(V, boundary_set=bcs) + bcs = [bc.reconstruct(V=get_sub(V_res, bc._indices)) for bc in bcs] self.u_restrict = Function(V_res).interpolate(u) v_res, u_res = TestFunction(V_res), TrialFunction(V_res) F_arg, = F.arguments() diff --git a/tests/regression/test_restricted_function_space.py b/tests/regression/test_restricted_function_space.py index 054f063a72..e026677c79 100644 --- a/tests/regression/test_restricted_function_space.py +++ b/tests/regression/test_restricted_function_space.py @@ -173,16 +173,43 @@ def test_restricted_function_space_coord_change(j): compare_function_space_assembly(new_V, new_V_restricted, [bc]) -def test_restricted_mixed_spaces(): +def test_restricted_mixed_space(): mesh = UnitSquareMesh(1, 1) V = FunctionSpace(mesh, "RT", 1) Q = FunctionSpace(mesh, "DG", 0) Z = V * Q bcs = [DirichletBC(Z.sub(0), 0, [1])] - Z_restricted = RestrictedFunctionSpace(Z, bcs) + Z_restricted = RestrictedFunctionSpace(Z, boundary_set=bcs) compare_function_space_assembly(Z, Z_restricted, bcs) +def test_poisson_restricted_mixed_space(): + mesh = UnitSquareMesh(1, 1) + V = FunctionSpace(mesh, "RT", 1) + Q = FunctionSpace(mesh, "DG", 0) + Z = V*Q + + u, p = TrialFunctions(Z) + v, q = TestFunctions(Z) + a = inner(u, v)*dx + inner(p, div(v))*dx + inner(div(u), q)*dx + L = inner(1, q)*dx + + bcs = [DirichletBC(Z.sub(0), 0, [1])] + + w = Function(Z) + problem = LinearVariationalProblem(a, L, w, bcs=bcs, restrict=False) + solver = LinearVariationalSolver(problem) + solver.solve() + + w2 = Function(Z) + problem = LinearVariationalProblem(a, L, w2, bcs=bcs, restrict=True) + solver = LinearVariationalSolver(problem) + solver.solve() + + assert errornorm(w.subfunctions[0], w2.subfunctions[0]) < 1.e-12 + assert errornorm(w.subfunctions[1], w2.subfunctions[1]) < 1.e-12 + + @pytest.mark.parametrize(["i", "j"], [(1, 0), (2, 0), (2, 1)]) def test_poisson_mixed_restricted_spaces(i, j): mesh = UnitSquareMesh(1, 1) From 562852fcdb37bcdfa7c79f513aa9c5914be79351 Mon Sep 17 00:00:00 2001 From: Pablo Brubeck Date: Mon, 18 Nov 2024 23:54:26 +0000 Subject: [PATCH 05/12] Geometric RestrictedFunctionSpace --- firedrake/functionspace.py | 25 +++++++++++++++---- firedrake/functionspaceimpl.py | 21 +--------------- firedrake/interpolation.py | 3 ++- .../test_restricted_function_space.py | 2 +- 4 files changed, 24 insertions(+), 27 deletions(-) diff --git a/firedrake/functionspace.py b/firedrake/functionspace.py index 74dab04703..55564e963f 100644 --- a/firedrake/functionspace.py +++ b/firedrake/functionspace.py @@ -308,20 +308,35 @@ def rec(eles): @PETSc.Log.EventDecorator("CreateFunctionSpace") -def RestrictedFunctionSpace(function_space, name=None, boundary_set=[]): +def RestrictedFunctionSpace(function_space, boundary_set=[], name=None): """Create a :class:`.RestrictedFunctionSpace`. Parameters ---------- function_space : FunctionSpace object to restrict - name : - An optional name for the function space. boundary_set : A set of subdomains of the mesh in which Dirichlet boundary conditions will be applied. + name : + An optional name for the function space. """ - return impl.WithGeometry.create(impl.RestrictedFunctionSpace(function_space, name=name, - boundary_set=boundary_set), + if len(function_space) > 1: + return MixedFunctionSpace([RestrictedFunctionSpace(Vsub, boundary_set=boundary_set) + for Vsub in function_space], name=name) + + if len(boundary_set) > 0 and all(hasattr(bc, "sub_domain") for bc in boundary_set): + bcs = boundary_set + boundary_set = [] + for bc in bcs: + if bc.function_space() == function_space: + if type(bc.sub_domain) in {str, int}: + boundary_set.append(bc.sub_domain) + else: + boundary_set.extend(bc.sub_domain) + + return impl.WithGeometry.create(impl.RestrictedFunctionSpace(function_space, + boundary_set=boundary_set, + name=name), function_space.mesh()) diff --git a/firedrake/functionspaceimpl.py b/firedrake/functionspaceimpl.py index 493febb432..2e359458ed 100644 --- a/firedrake/functionspaceimpl.py +++ b/firedrake/functionspaceimpl.py @@ -860,8 +860,7 @@ class RestrictedFunctionSpace(FunctionSpace): output of the solver. :arg function_space: The :class:`FunctionSpace` to restrict. - :kwarg boundary_set: An iterable of DirichletBCs or a set of subdomains on - which a DirichletBC will be applied. + :kwarg boundary_set: A set of subdomains on which a DirichletBC will be applied. :kwarg name: An optional name for this :class:`RestrictedFunctionSpace`, useful for later identification. @@ -870,25 +869,7 @@ class RestrictedFunctionSpace(FunctionSpace): If using this class to solve or similar, a list of DirichletBCs will still need to be specified on this space and passed into the function. """ - def __new__(cls, *args, boundary_set=frozenset(), name=None, **kwargs): - if cls is not RestrictedFunctionSpace: - return super().__new__(cls) - function_space = args[0] - if len(function_space) > 1: - return MixedFunctionSpace([cls(Vsub, boundary_set=boundary_set) - for Vsub in function_space], name=name) - return super().__new__(cls) - def __init__(self, function_space, boundary_set=frozenset(), name=None): - if len(boundary_set) > 0 and all(hasattr(bc, "sub_domain") for bc in boundary_set): - bcs = boundary_set - boundary_set = [] - for bc in bcs: - if bc.function_space() == function_space: - if type(bc.sub_domain) in {str, int}: - boundary_set.append(bc.sub_domain) - else: - boundary_set.extend(bc.sub_domain) label = "" for boundary_domain in boundary_set: label += str(boundary_domain) diff --git a/firedrake/interpolation.py b/firedrake/interpolation.py index a7079d0fb9..9de5f048e8 100644 --- a/firedrake/interpolation.py +++ b/firedrake/interpolation.py @@ -996,7 +996,8 @@ def callable(): if len(V) == 1: loops.extend(_interpolator(V, tensor, expr, subset, arguments, access, bcs=bcs)) else: - assert len(arguments) == 0 + if len(arguments) > 0: + raise NotImplementedError("Cannot interpolate a mixed expression with %d arguments" % len(arguments)) offset = 0 for Vsub, usub in zip(V, tensor): shape = Vsub.value_shape diff --git a/tests/regression/test_restricted_function_space.py b/tests/regression/test_restricted_function_space.py index e026677c79..83026f7c52 100644 --- a/tests/regression/test_restricted_function_space.py +++ b/tests/regression/test_restricted_function_space.py @@ -179,7 +179,7 @@ def test_restricted_mixed_space(): Q = FunctionSpace(mesh, "DG", 0) Z = V * Q bcs = [DirichletBC(Z.sub(0), 0, [1])] - Z_restricted = RestrictedFunctionSpace(Z, boundary_set=bcs) + Z_restricted = RestrictedFunctionSpace(Z, bcs) compare_function_space_assembly(Z, Z_restricted, bcs) From bb18f221eed59802cce36fe82271106de55b1814 Mon Sep 17 00:00:00 2001 From: Pablo Brubeck Date: Tue, 19 Nov 2024 10:49:19 +0000 Subject: [PATCH 06/12] Cleanup --- firedrake/bcs.py | 4 +++- firedrake/eigensolver.py | 4 ++-- firedrake/interpolation.py | 27 ++++++++++++++------------- firedrake/variational_solver.py | 17 +++++------------ 4 files changed, 24 insertions(+), 28 deletions(-) diff --git a/firedrake/bcs.py b/firedrake/bcs.py index 0b9b492bfc..311a5eb8bb 100644 --- a/firedrake/bcs.py +++ b/firedrake/bcs.py @@ -313,10 +313,12 @@ def function_arg(self): return self._function_arg @PETSc.Log.EventDecorator() - def reconstruct(self, field=None, V=None, g=None, sub_domain=None, use_split=False): + def reconstruct(self, field=None, V=None, g=None, sub_domain=None, use_split=False, indices=()): fs = self.function_space() if V is None: V = fs + for index in indices: + V = V.sub(index) if g is None: g = self._original_arg if sub_domain is None: diff --git a/firedrake/eigensolver.py b/firedrake/eigensolver.py index f33618f950..fab3766ae0 100644 --- a/firedrake/eigensolver.py +++ b/firedrake/eigensolver.py @@ -70,12 +70,12 @@ def __init__(self, A, M=None, bcs=None, bc_shift=0.0, restrict=True): M = inner(u, v) * dx if restrict and bcs: # assumed u and v are in the same space here - V_res = RestrictedFunctionSpace(self.output_space, boundary_set=set(bc.sub_domain for bc in bcs)) + V_res = RestrictedFunctionSpace(self.output_space, bcs) u_res = TrialFunction(V_res) v_res = TestFunction(V_res) self.M = replace(M, {u: u_res, v: v_res}) self.A = replace(A, {u: u_res, v: v_res}) - self.bcs = [bc.reconstruct(V=V_res) for bc in bcs] + self.bcs = [bc.reconstruct(V=V_res, indices=bc._indices) for bc in bcs] self.restricted_space = V_res else: self.A = A # LHS diff --git a/firedrake/interpolation.py b/firedrake/interpolation.py index 9de5f048e8..d02b4dcfc0 100644 --- a/firedrake/interpolation.py +++ b/firedrake/interpolation.py @@ -995,22 +995,23 @@ def callable(): if len(V) == 1: loops.extend(_interpolator(V, tensor, expr, subset, arguments, access, bcs=bcs)) - else: - if len(arguments) > 0: - raise NotImplementedError("Cannot interpolate a mixed expression with %d arguments" % len(arguments)) + elif (hasattr(expr, "subfunctions") and len(expr.subfunctions) == len(V) + and all(sub_expr.ufl_shape == Vsub.value_shape for Vsub, sub_expr in zip(V, expr.subfunctions))): + for Vsub, sub_tensor, sub_expr in zip(V, tensor, expr.subfunctions): + loops.extend(_interpolator(Vsub, sub_tensor, sub_expr, subset, arguments, access, bcs=bcs)) + elif len(arguments) == 0: + # Unflatten the expression into each of the mixed components offset = 0 - for Vsub, usub in zip(V, tensor): - shape = Vsub.value_shape - rank = len(shape) - components = [expr[offset + j] for j in range(Vsub.value_size)] - if rank == 0: - Vexpr = components[0] - elif rank == 1: - Vexpr = ufl.as_vector(components) + for Vsub, sub_tensor in zip(V, tensor): + if len(Vsub.value_shape) == 0: + sub_expr = expr[offset] else: - Vexpr = ufl.as_tensor(numpy.reshape(components, Vsub.value_shape).tolist()) - loops.extend(_interpolator(Vsub, usub, Vexpr, subset, arguments, access, bcs=bcs)) + components = [expr[offset + j] for j in range(Vsub.value_size)] + sub_expr = ufl.as_tensor(numpy.reshape(components, Vsub.value_shape)) + loops.extend(_interpolator(Vsub, sub_tensor, sub_expr, subset, arguments, access, bcs=bcs)) offset += Vsub.value_size + else: + raise NotImplementedError("Cannot interpolate a mixed expression with %d arguments" % len(arguments)) if bcs and len(arguments) == 0: loops.extend(partial(bc.apply, f) for bc in bcs) diff --git a/firedrake/variational_solver.py b/firedrake/variational_solver.py index 65a7078d09..9a7cf17d83 100644 --- a/firedrake/variational_solver.py +++ b/firedrake/variational_solver.py @@ -22,12 +22,6 @@ "NonlinearVariationalSolver"] -def get_sub(V, indices): - for i in indices: - V = V.sub(i) - return V - - def check_pde_args(F, J, Jp): if not isinstance(F, (ufl.BaseForm, slate.slate.TensorBase)): raise TypeError("Provided residual is a '%s', not a BaseForm or Slate Tensor" % type(F).__name__) @@ -94,19 +88,18 @@ def __init__(self, F, u, bcs=None, J=None, self.restrict = restrict if restrict and bcs: - V_res = RestrictedFunctionSpace(V, boundary_set=bcs) - bcs = [bc.reconstruct(V=get_sub(V_res, bc._indices)) for bc in bcs] + V_res = RestrictedFunctionSpace(V, bcs) + bcs = [bc.reconstruct(V=V_res, indices=bc._indices) for bc in bcs] self.u_restrict = Function(V_res).interpolate(u) v_res, u_res = TestFunction(V_res), TrialFunction(V_res) F_arg, = F.arguments() - replace_dict = {F_arg: v_res} - replace_dict[self.u] = self.u_restrict + replace_dict = {F_arg: v_res, self.u: self.u_restrict} self.F = replace(F, replace_dict) v_arg, u_arg = self.J.arguments() - self.J = replace(self.J, {v_arg: v_res, u_arg: u_res}) + self.J = replace(self.J, {v_arg: v_res, u_arg: u_res, self.u: self.u_restrict}) if self.Jp: v_arg, u_arg = self.Jp.arguments() - self.Jp = replace(self.Jp, {v_arg: v_res, u_arg: u_res}) + self.Jp = replace(self.Jp, {v_arg: v_res, u_arg: u_res, self.u: self.u_restrict}) self.restricted_space = V_res else: self.u_restrict = u From d6ef63a725231060ede7a6642a5d2ac5c4f350fe Mon Sep 17 00:00:00 2001 From: Pablo Brubeck Date: Tue, 19 Nov 2024 10:49:19 +0000 Subject: [PATCH 07/12] clean up --- firedrake/bcs.py | 4 +++- firedrake/eigensolver.py | 4 ++-- firedrake/interpolation.py | 27 ++++++++++++++------------- firedrake/variational_solver.py | 18 +++++------------- 4 files changed, 24 insertions(+), 29 deletions(-) diff --git a/firedrake/bcs.py b/firedrake/bcs.py index 0b9b492bfc..311a5eb8bb 100644 --- a/firedrake/bcs.py +++ b/firedrake/bcs.py @@ -313,10 +313,12 @@ def function_arg(self): return self._function_arg @PETSc.Log.EventDecorator() - def reconstruct(self, field=None, V=None, g=None, sub_domain=None, use_split=False): + def reconstruct(self, field=None, V=None, g=None, sub_domain=None, use_split=False, indices=()): fs = self.function_space() if V is None: V = fs + for index in indices: + V = V.sub(index) if g is None: g = self._original_arg if sub_domain is None: diff --git a/firedrake/eigensolver.py b/firedrake/eigensolver.py index f33618f950..fab3766ae0 100644 --- a/firedrake/eigensolver.py +++ b/firedrake/eigensolver.py @@ -70,12 +70,12 @@ def __init__(self, A, M=None, bcs=None, bc_shift=0.0, restrict=True): M = inner(u, v) * dx if restrict and bcs: # assumed u and v are in the same space here - V_res = RestrictedFunctionSpace(self.output_space, boundary_set=set(bc.sub_domain for bc in bcs)) + V_res = RestrictedFunctionSpace(self.output_space, bcs) u_res = TrialFunction(V_res) v_res = TestFunction(V_res) self.M = replace(M, {u: u_res, v: v_res}) self.A = replace(A, {u: u_res, v: v_res}) - self.bcs = [bc.reconstruct(V=V_res) for bc in bcs] + self.bcs = [bc.reconstruct(V=V_res, indices=bc._indices) for bc in bcs] self.restricted_space = V_res else: self.A = A # LHS diff --git a/firedrake/interpolation.py b/firedrake/interpolation.py index 9de5f048e8..d02b4dcfc0 100644 --- a/firedrake/interpolation.py +++ b/firedrake/interpolation.py @@ -995,22 +995,23 @@ def callable(): if len(V) == 1: loops.extend(_interpolator(V, tensor, expr, subset, arguments, access, bcs=bcs)) - else: - if len(arguments) > 0: - raise NotImplementedError("Cannot interpolate a mixed expression with %d arguments" % len(arguments)) + elif (hasattr(expr, "subfunctions") and len(expr.subfunctions) == len(V) + and all(sub_expr.ufl_shape == Vsub.value_shape for Vsub, sub_expr in zip(V, expr.subfunctions))): + for Vsub, sub_tensor, sub_expr in zip(V, tensor, expr.subfunctions): + loops.extend(_interpolator(Vsub, sub_tensor, sub_expr, subset, arguments, access, bcs=bcs)) + elif len(arguments) == 0: + # Unflatten the expression into each of the mixed components offset = 0 - for Vsub, usub in zip(V, tensor): - shape = Vsub.value_shape - rank = len(shape) - components = [expr[offset + j] for j in range(Vsub.value_size)] - if rank == 0: - Vexpr = components[0] - elif rank == 1: - Vexpr = ufl.as_vector(components) + for Vsub, sub_tensor in zip(V, tensor): + if len(Vsub.value_shape) == 0: + sub_expr = expr[offset] else: - Vexpr = ufl.as_tensor(numpy.reshape(components, Vsub.value_shape).tolist()) - loops.extend(_interpolator(Vsub, usub, Vexpr, subset, arguments, access, bcs=bcs)) + components = [expr[offset + j] for j in range(Vsub.value_size)] + sub_expr = ufl.as_tensor(numpy.reshape(components, Vsub.value_shape)) + loops.extend(_interpolator(Vsub, sub_tensor, sub_expr, subset, arguments, access, bcs=bcs)) offset += Vsub.value_size + else: + raise NotImplementedError("Cannot interpolate a mixed expression with %d arguments" % len(arguments)) if bcs and len(arguments) == 0: loops.extend(partial(bc.apply, f) for bc in bcs) diff --git a/firedrake/variational_solver.py b/firedrake/variational_solver.py index 65a7078d09..bdea03c2d8 100644 --- a/firedrake/variational_solver.py +++ b/firedrake/variational_solver.py @@ -22,12 +22,6 @@ "NonlinearVariationalSolver"] -def get_sub(V, indices): - for i in indices: - V = V.sub(i) - return V - - def check_pde_args(F, J, Jp): if not isinstance(F, (ufl.BaseForm, slate.slate.TensorBase)): raise TypeError("Provided residual is a '%s', not a BaseForm or Slate Tensor" % type(F).__name__) @@ -94,19 +88,17 @@ def __init__(self, F, u, bcs=None, J=None, self.restrict = restrict if restrict and bcs: - V_res = RestrictedFunctionSpace(V, boundary_set=bcs) - bcs = [bc.reconstruct(V=get_sub(V_res, bc._indices)) for bc in bcs] + V_res = RestrictedFunctionSpace(V, bcs) + bcs = [bc.reconstruct(V=V_res, indices=bc._indices) for bc in bcs] self.u_restrict = Function(V_res).interpolate(u) v_res, u_res = TestFunction(V_res), TrialFunction(V_res) F_arg, = F.arguments() - replace_dict = {F_arg: v_res} - replace_dict[self.u] = self.u_restrict - self.F = replace(F, replace_dict) + self.F = replace(F, {F_arg: v_res, self.u: self.u_restrict}) v_arg, u_arg = self.J.arguments() - self.J = replace(self.J, {v_arg: v_res, u_arg: u_res}) + self.J = replace(self.J, {v_arg: v_res, u_arg: u_res, self.u: self.u_restrict}) if self.Jp: v_arg, u_arg = self.Jp.arguments() - self.Jp = replace(self.Jp, {v_arg: v_res, u_arg: u_res}) + self.Jp = replace(self.Jp, {v_arg: v_res, u_arg: u_res, self.u: self.u_restrict}) self.restricted_space = V_res else: self.u_restrict = u From df65c80ba5b795385ab21860c53e75ba6ce1eade Mon Sep 17 00:00:00 2001 From: Pablo Brubeck Date: Wed, 20 Nov 2024 13:29:50 +0000 Subject: [PATCH 08/12] flatten the boundary_set kwarg --- firedrake/functionspace.py | 26 ++++++++++++------- .../test_interpolation_from_parent.py | 2 +- 2 files changed, 18 insertions(+), 10 deletions(-) diff --git a/firedrake/functionspace.py b/firedrake/functionspace.py index 55564e963f..3d88c70f51 100644 --- a/firedrake/functionspace.py +++ b/firedrake/functionspace.py @@ -317,24 +317,32 @@ def RestrictedFunctionSpace(function_space, boundary_set=[], name=None): FunctionSpace object to restrict boundary_set : A set of subdomains of the mesh in which Dirichlet boundary conditions - will be applied. + will be applied. Alternatively, an iterable of boundary conditions. name : An optional name for the function space. """ + from firedrake.bcs import BCBase if len(function_space) > 1: return MixedFunctionSpace([RestrictedFunctionSpace(Vsub, boundary_set=boundary_set) for Vsub in function_space], name=name) - if len(boundary_set) > 0 and all(hasattr(bc, "sub_domain") for bc in boundary_set): - bcs = boundary_set - boundary_set = [] - for bc in bcs: + if not isinstance(boundary_set, (tuple, list, set, frozenset)): + boundary_set = (boundary_set,) + + flat_boundary_set = [] + for sub_domain in boundary_set: + if isinstance(sub_domain, BCBase): + bc = sub_domain if bc.function_space() == function_space: - if type(bc.sub_domain) in {str, int}: - boundary_set.append(bc.sub_domain) - else: - boundary_set.extend(bc.sub_domain) + sub_domain = bc.sub_domain + else: + continue + if isinstance(sub_domain, (str, int)): + flat_boundary_set.append(sub_domain) + else: + flat_boundary_set.extend(sub_domain) + boundary_set = flat_boundary_set return impl.WithGeometry.create(impl.RestrictedFunctionSpace(function_space, boundary_set=boundary_set, diff --git a/tests/vertexonly/test_interpolation_from_parent.py b/tests/vertexonly/test_interpolation_from_parent.py index 0c78eba4ac..3a9c54a78b 100644 --- a/tests/vertexonly/test_interpolation_from_parent.py +++ b/tests/vertexonly/test_interpolation_from_parent.py @@ -298,7 +298,7 @@ def test_mixed_function_interpolation(parentmesh, vertexcoords, tfs): tfs_fam, tfs_deg, tfs_typ = tfs vm = VertexOnlyMesh(parentmesh, vertexcoords, missing_points_behaviour=None) - vertexcoords = vm.coordinates.dat.data_ro + vertexcoords = vm.coordinates.dat.data_ro.reshape(-1, parentmesh.geometric_dimension()) if ( parentmesh.coordinates.function_space().ufl_element().family() == "Discontinuous Lagrange" From 88cd868cc0d426b3ba760b3514ffcd86e59c729f Mon Sep 17 00:00:00 2001 From: Pablo Brubeck Date: Wed, 20 Nov 2024 19:51:25 +0000 Subject: [PATCH 09/12] new function restricted_function_space --- firedrake/bcs.py | 37 ++++++++++++++++++- firedrake/eigensolver.py | 4 +- firedrake/functionspace.py | 24 +----------- firedrake/variational_solver.py | 5 +-- .../test_restricted_function_space.py | 12 ++---- 5 files changed, 45 insertions(+), 37 deletions(-) diff --git a/firedrake/bcs.py b/firedrake/bcs.py index 311a5eb8bb..e6b60a0e4c 100644 --- a/firedrake/bcs.py +++ b/firedrake/bcs.py @@ -23,7 +23,7 @@ from firedrake.adjoint_utils.dirichletbc import DirichletBCMixin from firedrake.petsc import PETSc -__all__ = ['DirichletBC', 'homogenize', 'EquationBC'] +__all__ = ['DirichletBC', 'homogenize', 'EquationBC', 'restricted_function_space'] class BCBase(object): @@ -690,3 +690,38 @@ def homogenize(bc): return DirichletBC(bc.function_space(), 0, bc.sub_domain) else: raise TypeError("homogenize only takes a DirichletBC or a list/tuple of DirichletBCs") + + +@PETSc.Log.EventDecorator("CreateFunctionSpace") +def restricted_function_space(V, bcs, name=None): + """Create a :class:`.RestrictedFunctionSpace` from a list of boundary conditions. + + Parameters + ---------- + V : + FunctionSpace object to restrict + bcs : + A list of boundary conditions. + name : + An optional name for the function space. + + """ + if len(V) > 1: + spaces = [restricted_function_space(Vsub, bcs) for Vsub in V] + return firedrake.MixedFunctionSpace(spaces, name=name) + + if not isinstance(bcs, (tuple, list)): + bcs = (bcs,) + + boundary_set = [] + for bc in bcs: + if bc.function_space() != V: + continue + for dbc in bc.dirichlet_bcs(): + if isinstance(dbc.sub_domain, (str, int)): + boundary_set.append(dbc.sub_domain) + else: + boundary_set.extend(dbc.sub_domain) + if len(boundary_set) == 0: + return V + return firedrake.RestrictedFunctionSpace(V, boundary_set=boundary_set, name=name) diff --git a/firedrake/eigensolver.py b/firedrake/eigensolver.py index fab3766ae0..10dc58abad 100644 --- a/firedrake/eigensolver.py +++ b/firedrake/eigensolver.py @@ -1,7 +1,7 @@ """Specify and solve finite element eigenproblems.""" from firedrake.assemble import assemble +from firedrake.bcs import restricted_function_space from firedrake.function import Function -from firedrake.functionspace import RestrictedFunctionSpace from firedrake.ufl_expr import TrialFunction, TestFunction from firedrake import utils from firedrake.petsc import OptionsManager, flatten_parameters @@ -70,7 +70,7 @@ def __init__(self, A, M=None, bcs=None, bc_shift=0.0, restrict=True): M = inner(u, v) * dx if restrict and bcs: # assumed u and v are in the same space here - V_res = RestrictedFunctionSpace(self.output_space, bcs) + V_res = restricted_function_space(self.output_space, bcs) u_res = TrialFunction(V_res) v_res = TestFunction(V_res) self.M = replace(M, {u: u_res, v: v_res}) diff --git a/firedrake/functionspace.py b/firedrake/functionspace.py index 3d88c70f51..509f7a2863 100644 --- a/firedrake/functionspace.py +++ b/firedrake/functionspace.py @@ -317,33 +317,11 @@ def RestrictedFunctionSpace(function_space, boundary_set=[], name=None): FunctionSpace object to restrict boundary_set : A set of subdomains of the mesh in which Dirichlet boundary conditions - will be applied. Alternatively, an iterable of boundary conditions. + will be applied. name : An optional name for the function space. """ - from firedrake.bcs import BCBase - if len(function_space) > 1: - return MixedFunctionSpace([RestrictedFunctionSpace(Vsub, boundary_set=boundary_set) - for Vsub in function_space], name=name) - - if not isinstance(boundary_set, (tuple, list, set, frozenset)): - boundary_set = (boundary_set,) - - flat_boundary_set = [] - for sub_domain in boundary_set: - if isinstance(sub_domain, BCBase): - bc = sub_domain - if bc.function_space() == function_space: - sub_domain = bc.sub_domain - else: - continue - if isinstance(sub_domain, (str, int)): - flat_boundary_set.append(sub_domain) - else: - flat_boundary_set.extend(sub_domain) - boundary_set = flat_boundary_set - return impl.WithGeometry.create(impl.RestrictedFunctionSpace(function_space, boundary_set=boundary_set, name=name), diff --git a/firedrake/variational_solver.py b/firedrake/variational_solver.py index bdea03c2d8..89a80ab593 100644 --- a/firedrake/variational_solver.py +++ b/firedrake/variational_solver.py @@ -10,9 +10,8 @@ DEFAULT_SNES_PARAMETERS ) from firedrake.function import Function -from firedrake.functionspace import RestrictedFunctionSpace from firedrake.ufl_expr import TrialFunction, TestFunction -from firedrake.bcs import DirichletBC, EquationBC +from firedrake.bcs import DirichletBC, EquationBC, restricted_function_space from firedrake.adjoint_utils import NonlinearVariationalProblemMixin, NonlinearVariationalSolverMixin from ufl import replace @@ -88,7 +87,7 @@ def __init__(self, F, u, bcs=None, J=None, self.restrict = restrict if restrict and bcs: - V_res = RestrictedFunctionSpace(V, bcs) + V_res = restricted_function_space(V, bcs) bcs = [bc.reconstruct(V=V_res, indices=bc._indices) for bc in bcs] self.u_restrict = Function(V_res).interpolate(u) v_res, u_res = TestFunction(V_res), TrialFunction(V_res) diff --git a/tests/regression/test_restricted_function_space.py b/tests/regression/test_restricted_function_space.py index 83026f7c52..ae3ea28ed4 100644 --- a/tests/regression/test_restricted_function_space.py +++ b/tests/regression/test_restricted_function_space.py @@ -179,7 +179,7 @@ def test_restricted_mixed_space(): Q = FunctionSpace(mesh, "DG", 0) Z = V * Q bcs = [DirichletBC(Z.sub(0), 0, [1])] - Z_restricted = RestrictedFunctionSpace(Z, bcs) + Z_restricted = restricted_function_space(Z, bcs) compare_function_space_assembly(Z, Z_restricted, bcs) @@ -187,7 +187,7 @@ def test_poisson_restricted_mixed_space(): mesh = UnitSquareMesh(1, 1) V = FunctionSpace(mesh, "RT", 1) Q = FunctionSpace(mesh, "DG", 0) - Z = V*Q + Z = V * Q u, p = TrialFunctions(Z) v, q = TestFunctions(Z) @@ -197,14 +197,10 @@ def test_poisson_restricted_mixed_space(): bcs = [DirichletBC(Z.sub(0), 0, [1])] w = Function(Z) - problem = LinearVariationalProblem(a, L, w, bcs=bcs, restrict=False) - solver = LinearVariationalSolver(problem) - solver.solve() + solve(a == L, w, bcs=bcs, restrict=False) w2 = Function(Z) - problem = LinearVariationalProblem(a, L, w2, bcs=bcs, restrict=True) - solver = LinearVariationalSolver(problem) - solver.solve() + solve(a == L, w2, bcs=bcs, restrict=True) assert errornorm(w.subfunctions[0], w2.subfunctions[0]) < 1.e-12 assert errornorm(w.subfunctions[1], w2.subfunctions[1]) < 1.e-12 From 9cd4da66b75d5a6975ddf34689f73c87b34c67a4 Mon Sep 17 00:00:00 2001 From: Pablo Brubeck Date: Wed, 27 Nov 2024 19:00:54 +0000 Subject: [PATCH 10/12] split interface --- firedrake/bcs.py | 73 ++++++++++++------- firedrake/eigensolver.py | 4 +- firedrake/variational_solver.py | 4 +- .../test_restricted_function_space.py | 10 --- 4 files changed, 51 insertions(+), 40 deletions(-) diff --git a/firedrake/bcs.py b/firedrake/bcs.py index e6b60a0e4c..1fc6aa5a6a 100644 --- a/firedrake/bcs.py +++ b/firedrake/bcs.py @@ -23,7 +23,7 @@ from firedrake.adjoint_utils.dirichletbc import DirichletBCMixin from firedrake.petsc import PETSc -__all__ = ['DirichletBC', 'homogenize', 'EquationBC', 'restricted_function_space'] +__all__ = ['DirichletBC', 'homogenize', 'EquationBC'] class BCBase(object): @@ -692,36 +692,57 @@ def homogenize(bc): raise TypeError("homogenize only takes a DirichletBC or a list/tuple of DirichletBCs") -@PETSc.Log.EventDecorator("CreateFunctionSpace") -def restricted_function_space(V, bcs, name=None): - """Create a :class:`.RestrictedFunctionSpace` from a list of boundary conditions. +def extract_subdomain_ids(bcs): + """Return a tuple of subdomain ids for each component of a MixedFunctionSpace. Parameters ---------- - V : - FunctionSpace object to restrict bcs : A list of boundary conditions. - name : - An optional name for the function space. + + Returns + ------- + A tuple of subdomain ids for each component of a MixedFunctionSpace. """ - if len(V) > 1: - spaces = [restricted_function_space(Vsub, bcs) for Vsub in V] - return firedrake.MixedFunctionSpace(spaces, name=name) - - if not isinstance(bcs, (tuple, list)): - bcs = (bcs,) - - boundary_set = [] - for bc in bcs: - if bc.function_space() != V: - continue - for dbc in bc.dirichlet_bcs(): - if isinstance(dbc.sub_domain, (str, int)): - boundary_set.append(dbc.sub_domain) - else: - boundary_set.extend(dbc.sub_domain) - if len(boundary_set) == 0: + if len(bcs) == 0: + return None + + V = bcs[0].function_space() + while V.parent: + V = V.parent + + _chain = itertools.chain.from_iterable + subdomain_ids = tuple(tuple(_chain(as_tuple(bc.sub_domain) + for bc in bcs if bc.function_space() == Vsub)) + for Vsub in V) + return subdomain_ids + + +def restricted_function_space(V, ids): + """Create a :class:`.RestrictedFunctionSpace` from a tuple of subdomain ids. + + Parameters + ---------- + V : + FunctionSpace object to restrict + ids : + A tuple of subdomain ids. + + Returns + ------- + The RestrictedFunctionSpace. + + """ + if not ids: return V - return firedrake.RestrictedFunctionSpace(V, boundary_set=boundary_set, name=name) + + assert len(ids) == len(V) + spaces = [Vsub if len(boundary_set) == 0 else + firedrake.RestrictedFunctionSpace(Vsub, boundary_set=boundary_set) + for Vsub, boundary_set in zip(V, ids)] + + if len(spaces) == 1: + return spaces[0] + else: + return firedrake.MixedFunctionSpace(spaces) diff --git a/firedrake/eigensolver.py b/firedrake/eigensolver.py index 10dc58abad..ad05815527 100644 --- a/firedrake/eigensolver.py +++ b/firedrake/eigensolver.py @@ -1,6 +1,6 @@ """Specify and solve finite element eigenproblems.""" from firedrake.assemble import assemble -from firedrake.bcs import restricted_function_space +from firedrake.bcs import extract_subdomain_ids, restricted_function_space from firedrake.function import Function from firedrake.ufl_expr import TrialFunction, TestFunction from firedrake import utils @@ -70,7 +70,7 @@ def __init__(self, A, M=None, bcs=None, bc_shift=0.0, restrict=True): M = inner(u, v) * dx if restrict and bcs: # assumed u and v are in the same space here - V_res = restricted_function_space(self.output_space, bcs) + V_res = restricted_function_space(self.output_space, extract_subdomain_ids(bcs)) u_res = TrialFunction(V_res) v_res = TestFunction(V_res) self.M = replace(M, {u: u_res, v: v_res}) diff --git a/firedrake/variational_solver.py b/firedrake/variational_solver.py index 89a80ab593..4a1ac396c5 100644 --- a/firedrake/variational_solver.py +++ b/firedrake/variational_solver.py @@ -11,7 +11,7 @@ ) from firedrake.function import Function from firedrake.ufl_expr import TrialFunction, TestFunction -from firedrake.bcs import DirichletBC, EquationBC, restricted_function_space +from firedrake.bcs import DirichletBC, EquationBC, extract_subdomain_ids, restricted_function_space from firedrake.adjoint_utils import NonlinearVariationalProblemMixin, NonlinearVariationalSolverMixin from ufl import replace @@ -87,7 +87,7 @@ def __init__(self, F, u, bcs=None, J=None, self.restrict = restrict if restrict and bcs: - V_res = restricted_function_space(V, bcs) + V_res = restricted_function_space(V, extract_subdomain_ids(bcs)) bcs = [bc.reconstruct(V=V_res, indices=bc._indices) for bc in bcs] self.u_restrict = Function(V_res).interpolate(u) v_res, u_res = TestFunction(V_res), TrialFunction(V_res) diff --git a/tests/regression/test_restricted_function_space.py b/tests/regression/test_restricted_function_space.py index ae3ea28ed4..51e2139ae6 100644 --- a/tests/regression/test_restricted_function_space.py +++ b/tests/regression/test_restricted_function_space.py @@ -173,16 +173,6 @@ def test_restricted_function_space_coord_change(j): compare_function_space_assembly(new_V, new_V_restricted, [bc]) -def test_restricted_mixed_space(): - mesh = UnitSquareMesh(1, 1) - V = FunctionSpace(mesh, "RT", 1) - Q = FunctionSpace(mesh, "DG", 0) - Z = V * Q - bcs = [DirichletBC(Z.sub(0), 0, [1])] - Z_restricted = restricted_function_space(Z, bcs) - compare_function_space_assembly(Z, Z_restricted, bcs) - - def test_poisson_restricted_mixed_space(): mesh = UnitSquareMesh(1, 1) V = FunctionSpace(mesh, "RT", 1) From 249e2a694f6fa55c27178a45d8840f067078447e Mon Sep 17 00:00:00 2001 From: Pablo Brubeck Date: Wed, 4 Dec 2024 18:16:08 +0000 Subject: [PATCH 11/12] fix tests --- firedrake/bcs.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/firedrake/bcs.py b/firedrake/bcs.py index 1fc6aa5a6a..f1568824bd 100644 --- a/firedrake/bcs.py +++ b/firedrake/bcs.py @@ -705,6 +705,8 @@ def extract_subdomain_ids(bcs): A tuple of subdomain ids for each component of a MixedFunctionSpace. """ + if isinstance(bcs, DirichletBC): + bcs = (bcs,) if len(bcs) == 0: return None @@ -713,7 +715,8 @@ def extract_subdomain_ids(bcs): V = V.parent _chain = itertools.chain.from_iterable - subdomain_ids = tuple(tuple(_chain(as_tuple(bc.sub_domain) + _to_tuple = lambda s: (s,) if isinstance(s, (int, str)) else s + subdomain_ids = tuple(tuple(_chain(_to_tuple(bc.sub_domain) for bc in bcs if bc.function_space() == Vsub)) for Vsub in V) return subdomain_ids From f61c978c6477df204caa7974796ce5042c47b892 Mon Sep 17 00:00:00 2001 From: Pablo Brubeck Date: Wed, 4 Dec 2024 18:16:48 +0000 Subject: [PATCH 12/12] fix tuple((self, )) --- firedrake/function.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/firedrake/function.py b/firedrake/function.py index 585a1511d1..da4d264971 100644 --- a/firedrake/function.py +++ b/firedrake/function.py @@ -125,7 +125,7 @@ def split(self): @utils.cached_property def _components(self): if self.function_space().rank == 0: - return tuple((self, )) + return (self, ) else: if self.dof_dset.cdim == 1: return (CoordinatelessFunction(self.function_space().sub(0), val=self.dat, @@ -335,7 +335,7 @@ def split(self): @utils.cached_property def _components(self): if self.function_space().rank == 0: - return tuple((self, )) + return (self, ) else: return tuple(type(self)(self.function_space().sub(i), self.topological.sub(i)) for i in range(self.function_space().block_size))