Skip to content

Commit 0108c28

Browse files
committed
Merge branch 'connorjward/pyop3' into indiamai/fuse_pyop3_merge
2 parents c42f3a9 + d9e3a2c commit 0108c28

18 files changed

Lines changed: 175 additions & 1021 deletions

File tree

.github/workflows/core.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,8 @@
11
# Main Firedrake CI workflow run for pull requests or pushes to main or release
2+
<<<<<<< HEAD
3+
=======
4+
5+
>>>>>>> connorjward/pyop3
26
on:
37
workflow_call:
48
inputs:

firedrake/assemble.py

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@
2828
from firedrake.ufl_expr import extract_domains
2929
from firedrake.bcs import DirichletBC, EquationBC, EquationBCSplit
3030
from firedrake.matrix import MatrixBase, Matrix, ImplicitMatrix
31-
from firedrake.functionspaceimpl import WithGeometry, FunctionSpace, FiredrakeDualSpace
31+
from firedrake.functionspaceimpl import WithGeometry, FunctionSpace, FiredrakeDualSpace, is_mixed
3232
from firedrake.interpolation import get_interpolator
3333
from firedrake.pack import pack, modified_lgmaps
3434
from firedrake.petsc import PETSc, local_submat
@@ -1092,6 +1092,11 @@ def assemble(self, tensor=None, current_state=None):
10921092
else:
10931093
parloop(**{self._tensor_name[local_kernel]: subtensor}, compiler_parameters=pyop3_compiler_parameters)
10941094

1095+
# FIXME: This is necessary for test_submesh_solve_simple to pass for the moment
1096+
# This is unsatisfying because in theory this isn't required - we can stash up the
1097+
# pending increments and only apply them lazily. Something is going wrong somewhere.
1098+
subtensor.assemble()
1099+
10951100
for bc in self._bcs:
10961101
self._apply_bc(tensor, bc, u=current_state)
10971102

@@ -1508,7 +1513,7 @@ def _sub_mat_type(self) -> str | None:
15081513
@staticmethod
15091514
def _make_sparsity(test, trial, mat_spec, maps_and_regions):
15101515
# Is this overly restrictive?
1511-
if any(len(a.function_space()) > 1 for a in [test, trial]) and mat_spec.mat_type == "baij":
1516+
if any(is_mixed(a) for a in [test, trial]) and mat_spec.mat_type == "baij":
15121517
raise ValueError("BAIJ matrix type makes no sense for mixed spaces, use 'aij'")
15131518

15141519
sparsity = op3.Mat.sparsity(
@@ -1526,7 +1531,7 @@ def _make_sparsity(test, trial, mat_spec, maps_and_regions):
15261531
for loop_info, (test_index, trial_index) in maps_and_regions:
15271532
# If indices are 'None' then this means all to allocate for all spaces
15281533
if test_index is None:
1529-
if len(test.function_space()) > 1:
1534+
if is_mixed(test):
15301535
test_spaces = tuple(test.function_space())
15311536
test_indices = test.function_space().field_axis.component_labels
15321537
else:
@@ -1537,7 +1542,7 @@ def _make_sparsity(test, trial, mat_spec, maps_and_regions):
15371542
test_index = test.function_space().field_axis.component_labels[test_index]
15381543
test_indices = (test_index,)
15391544
if trial_index is None:
1540-
if len(trial.function_space()) > 1:
1545+
if is_mixed(trial):
15411546
trial_spaces = tuple(trial.function_space())
15421547
trial_indices = trial.function_space().field_axis.component_labels
15431548
else:

firedrake/cofunction.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,7 @@ def _analyze_form_arguments(self):
113113
def subfunctions(self):
114114
r"""Extract any sub :class:`Cofunction`\s defined on the component spaces
115115
of this this :class:`Cofunction`'s :class:`.FunctionSpace`."""
116-
if len(self.function_space()) > 1:
116+
if functionspaceimpl.is_mixed(self.function_space()):
117117
subfuncs = []
118118
for i, component in enumerate(self.dat.axes.trees[0].root.components):
119119
subspace = self.function_space().sub(i)

firedrake/cython/dmcommon.pyx

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2949,10 +2949,6 @@ def compute_dm_renumbering(
29492949
# CHKERR(ISInvertPermutation(ordering_is.iset, -1, &renumbering_is.iset))
29502950
# return renumbering_is
29512951

2952-
# return ordering_is.invertPermutation()
2953-
return ordering_is
2954-
# CHKERR(ISInvertPermutation(ordering_is.iset, -1, &renumbering_is.iset))
2955-
# return renumbering_is
29562952

29572953
def partition_renumbering(PETSc.DM dm, PETSc.IS serial_new_to_old_renumbering) -> PETSc.IS:
29582954
"""Partition a serial point renumbering into owned and ghost points."""
@@ -4055,7 +4051,6 @@ cdef int DMPlexGetAdjacency_Facet_Support(PETSc.PetscDM dm,
40554051
if numAdj > maxAdjSize:
40564052
CHKERR(PETSC_ERR_LIB)
40574053
CHKERR(DMPlexRestoreTransitiveClosure(dm, point, PETSC_TRUE, &closureSize, &closure))
4058-
CHKERR(DMPlexRestoreTransitiveClosure(dm, p, PETSC_FALSE, &starSize, &star))
40594054
adjSize[0] = numAdj
40604055
return 0
40614056

@@ -4422,11 +4417,13 @@ def submesh_create(PETSc.DM dm,
44224417
ignoreHalo=ignore_label_halo,
44234418
sanitizeSubMesh=PETSC_TRUE,
44244419
comm=comm)
4420+
44254421
# Destroy temp_label.
44264422
dm.removeLabel(temp_label_name)
44274423
subdm.removeLabel(temp_label_name)
44284424
submesh_update_facet_labels(dm, subdm)
44294425
submesh_correct_entity_classes(dm, subdm, ownership_transfer_sf)
4426+
44304427
return subdm
44314428

44324429

@@ -4466,6 +4463,7 @@ def submesh_correct_entity_classes(PETSc.DM dm,
44664463
CHKERR(DMPlexGetChart(subdm.dm, &subpStart, &subpEnd))
44674464
assert pStart == 0
44684465
assert subpStart == 0
4466+
44694467
CHKERR(DMGetLabel(subdm.dm, b"firedrake_is_ghost", &is_ghost))
44704468
CHKERR(DMLabelCreateIndex(is_ghost, subpStart, subpEnd))
44714469

@@ -4499,7 +4497,7 @@ def submesh_correct_entity_classes(PETSc.DM dm,
44994497
if ownership_gain[p] == 1:
45004498
CHKERR(DMLabelHasPoint(is_ghost, subp, &has))
45014499
assert has
4502-
CHKERR(DMLabelSetValue(is_ghost, subp, 0))
4500+
CHKERR(DMLabelClearValue(is_ghost, subp, 1))
45034501

45044502
CHKERR(ISRestoreIndices(subpoint_is.iset, &subpoint_indices))
45054503
CHKERR(DMLabelDestroyIndex(is_ghost))

firedrake/function.py

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@
3333
from firedrake import utils
3434
from firedrake.adjoint_utils import FunctionMixin
3535
from firedrake.petsc import PETSc
36-
from firedrake.functionspaceimpl import MixedFunctionSpace, parse_component_indices
36+
from firedrake.functionspaceimpl import MixedFunctionSpace, parse_component_indices, is_mixed
3737
from firedrake.mesh import MeshGeometry, VertexOnlyMesh, extract_mesh_topologies
3838
from firedrake.functionspace import FunctionSpace, VectorFunctionSpace, TensorFunctionSpace
3939
from firedrake.exceptions import PointNotInDomainError
@@ -118,7 +118,7 @@ def ufl_id(self):
118118
def subfunctions(self):
119119
r"""Extract any sub :class:`Function`\s defined on the component spaces
120120
of this this :class:`Function`'s :class:`.FunctionSpace`."""
121-
if isinstance(self.function_space(), MixedFunctionSpace):
121+
if is_mixed(self.function_space()):
122122
# NOTE: This is quite tricky for fieldsplit. Previously the fields would
123123
# be renumbered when split, but now we retain the labels in the dat but
124124
# not the function space.
@@ -317,7 +317,7 @@ def __dir__(self):
317317
def subfunctions(self):
318318
r"""Extract any sub :class:`Function`\s defined on the component spaces
319319
of this this :class:`Function`'s :class:`.FunctionSpace`."""
320-
if isinstance(self.function_space().topological, MixedFunctionSpace):
320+
if is_mixed(self.function_space()):
321321
return tuple(
322322
type(self)(self.function_space().sub(i), val)
323323
for (i, val) in zip(range(len(self.function_space())), self.topological.subfunctions))
@@ -355,7 +355,7 @@ def sub(self, indices: tuple[int] | int) -> "Function":
355355
subfunctions
356356
357357
"""
358-
if type(self.function_space().ufl_element()) is MixedElement:
358+
if is_mixed(self.function_space()):
359359
return self.subfunctions[indices]
360360
elif not self.function_space().shape:
361361
# TODO: Decide if this is acceptable usage
@@ -538,12 +538,17 @@ def __itruediv__(self, expr):
538538
return self
539539

540540
def __float__(self):
541-
542541
if (
543542
self.ufl_element().family() == "Real"
544543
and self.function_space().shape == ()
545544
):
546-
return float(self.dat.data_ro[0])
545+
self.dat.assemble()
546+
with op3.mpi.temp_internal_comm(self.comm) as icomm:
547+
if icomm.rank == 0:
548+
value = icomm.bcast(utils.just_one(self.dat.data_ro))
549+
else:
550+
value = icomm.bcast(None)
551+
return float(value)
547552
else:
548553
raise ValueError("Can only cast scalar 'Real' Functions to float.")
549554

firedrake/functionspaceimpl.py

Lines changed: 121 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -660,7 +660,10 @@ def local_section(self):
660660
@cached_property
661661
@deprecated("axes.template_vec")
662662
def template_vec(self):
663-
block_shape = self.shape if len(self) == 1 else ()
663+
if is_mixed(self):
664+
block_shape = ()
665+
else:
666+
block_shape = self.shape
664667
return self.layout_axes.template_vec(block_shape)
665668

666669
@cached_method()
@@ -682,7 +685,7 @@ def lgmap(self, bcs: Iterable[DirichletBC] = (), index: int | None = None) -> PE
682685
683686
"""
684687
lgmap_axes = self.axes
685-
if len(self) > 1 or any(bc.function_space().component is not None for bc in bcs):
688+
if is_mixed(self) or any(bc.function_space().component is not None for bc in bcs):
686689
block_size = 1
687690
else:
688691
lgmap_axes = lgmap_axes.blocked(self.shape)
@@ -692,7 +695,7 @@ def lgmap(self, bcs: Iterable[DirichletBC] = (), index: int | None = None) -> PE
692695
# track which BCs are used so we can warn if any are missed
693696
unused_bcs = set(bcs)
694697
if index is None: # The lgmap is for the full space
695-
if len(self) > 1:
698+
if is_mixed(self):
696699
split_bcs = []
697700
for subspace in self:
698701
matching_bcs = []
@@ -781,7 +784,7 @@ def interior_facet_node_map_dat(self) -> op3.Dat:
781784
@property
782785
@deprecated("cell_node_map_dat.data_ro")
783786
def cell_node_list(self) -> numpy.ndarray:
784-
if len(self) > 1 or self.parent:
787+
if is_mixed(self) or self.parent:
785788
warnings.warn(
786789
"For mixed spaces it is no longer the case that offset=node*bsize, "
787790
"use a DoF list instead."
@@ -791,7 +794,7 @@ def cell_node_list(self) -> numpy.ndarray:
791794
@property
792795
@deprecated("exterior_facet_node_map_dat.data_ro")
793796
def exterior_facet_node_list(self) -> numpy.ndarray:
794-
if len(self) > 1 or self.parent:
797+
if is_mixed(self) or self.parent:
795798
warnings.warn(
796799
"For mixed spaces it is no longer the case that offset=node*bsize, "
797800
"use a DoF list instead."
@@ -801,7 +804,7 @@ def exterior_facet_node_list(self) -> numpy.ndarray:
801804
@property
802805
@deprecated("interior_facet_node_map_dat.data_ro")
803806
def interior_facet_node_list(self) -> numpy.ndarray:
804-
if len(self) > 1 or self.parent:
807+
if is_mixed(self) or self.parent:
805808
warnings.warn(
806809
"For mixed spaces it is no longer the case that offset=node*bsize "
807810
"because the strides between nodes are not constant as they are "
@@ -898,7 +901,7 @@ def _iterset_to_dof_map_dat(
898901
# └──➤ {field: [{functionspace0: 1}, {functionspace1: 1}]}
899902
# ├──➤ {some_label: 2}
900903
# └──➤ {some_label: 1}
901-
if len(self) > 1:
904+
if is_mixed(self):
902905
map_dof_axes = iterset_axes.add_axis(None, packed_offsets.axes.root)
903906
for label, subspace in zip(self._labels, self):
904907
path = iterset_axes.leaf_path | {"field": label}
@@ -1606,7 +1609,7 @@ def entity_node_map(self, iteration_spec):
16061609
Entity node map.
16071610
16081611
"""
1609-
if len(self) > 1:
1612+
if is_mixed(self):
16101613
raise NotImplementedError("will this work?")
16111614

16121615
iter_mesh = iteration_spec.mesh
@@ -2558,6 +2561,96 @@ def _(index: int, shape: tuple[int, ...]) -> tuple[int, ...]:
25582561

25592562

25602563

2564+
def merge_axis_constraints(root_axis: op3.Axis, axis_constraintss: Sequence[Sequence[AxisConstraint]]) -> tuple[AxisConstraint]:
2565+
# start by collecting like axes
2566+
axis_info: defaultdict[op3.Axis, dict[op3.ComponentLabelT, idict]] = defaultdict(dict)
2567+
for root_component, constraints in zip(root_axis.components, axis_constraintss, strict=True):
2568+
for constraint in constraints:
2569+
axis_info[constraint.axis][root_component.label] = constraint.within_axes
2570+
2571+
# Now build the new set of constraints. To do this we inspect the
2572+
# per-component constraints for each axis: if the constraints are all the
2573+
# same then it is not necessary to specialise by component, otherwise an
2574+
# extra constraint is needed. For example:
2575+
#
2576+
# * Consider the "dof" axis for a mixed space with identical subspaces:
2577+
#
2578+
# {dof_axis: {0: {"mesh": None}, 1: {"mesh": None}}}
2579+
#
2580+
# Here this is saying that 'dof_axis' exists under root components 0 and 1
2581+
# and each time must satisfy the constraint of having '{"mesh": None}'
2582+
# above them.
2583+
#
2584+
# Since the constraints are identical for all components they do not need
2585+
# to be specialised. The final constraint is thus:
2586+
#
2587+
# AxisConstraint(dof_axis, {"mesh": None})
2588+
#
2589+
# * Alternatively consider a mixed space of CG1 x Real:
2590+
#
2591+
# {mesh_axis: {0: {}}}
2592+
#
2593+
# The "mesh" axis only exists for the CG1 subspace and so a new constraint
2594+
# is needed:
2595+
#
2596+
# AxisConstraint(mesh_axis, {"field": 0})
2597+
constraints = [AxisConstraint(root_axis)]
2598+
for axis, per_component_info in axis_info.items():
2599+
if (
2600+
per_component_info.keys() == set(root_axis.component_labels)
2601+
and utils.is_single_valued(per_component_info.values())
2602+
):
2603+
# Axis present for all components and constraints match: use as is
2604+
within_axes = utils.single_valued(per_component_info.values())
2605+
constraints.append(AxisConstraint(axis, within_axes))
2606+
else:
2607+
# Constraint mismatch: need to specialise by component
2608+
for component_label, orig_within_axes in per_component_info.items():
2609+
within_axes = orig_within_axes | {root_axis.label: component_label}
2610+
constraints.append(AxisConstraint(axis, within_axes))
2611+
return tuple(constraints)
2612+
2613+
2614+
@functools.singledispatch
2615+
def parse_component_indices(indices: Any, shape: tuple[int, ...]) -> tuple[int, ...]:
2616+
raise TypeError
2617+
2618+
2619+
@parse_component_indices.register(tuple)
2620+
def _(indices: tuple[int, ...], shape: tuple[int, ...]) -> tuple[int, ...]:
2621+
return indices
2622+
2623+
2624+
@parse_component_indices.register(int)
2625+
def _(index: int, shape: tuple[int, ...]) -> tuple[int, ...]:
2626+
# Historically tensor-valued spaces would be addressed using a flat index
2627+
# instead of a tuple. Here we convert the old-style flat index to a
2628+
# nested one. Eventually we should be able to remove this and simply cast
2629+
# an integer index to a tuple (e.g. '3' to '(3,)').
2630+
if len(shape) > 1:
2631+
warnings.warn(
2632+
"Scalar indexing of a tensor-valued space is no longer recommended "
2633+
"practice, please pass a tuple instead",
2634+
FutureWarning,
2635+
)
2636+
return list(numpy.ndindex(shape))[index]
2637+
2638+
for component in axis.components:
2639+
subconstraints_ = tuple(
2640+
subconstraint
2641+
for subconstraint in subconstraints
2642+
if axis.label not in subconstraint.within_axes
2643+
or (axis.label, component.label) in subconstraint.within_axes.items()
2644+
)
2645+
if subconstraints_:
2646+
# FIXME: Not doing anything with visited_axes
2647+
subnest = _axis_nest_from_constraints(subconstraints_, visited_axes)
2648+
axis_nest[axis].append(subnest)
2649+
2650+
return idict(axis_nest) if axis_nest else axis
2651+
2652+
2653+
25612654
def merge_axis_constraints(root_axis: op3.Axis, axis_constraintss: Sequence[Sequence[AxisConstraint]]) -> tuple[AxisConstraint]:
25622655
# start by collecting like axes
25632656
axis_info: defaultdict[op3.Axis, dict[op3.ComponentLabelT, idict]] = defaultdict(dict)
@@ -2667,3 +2760,23 @@ def entity_permutations_key(entity_permutations):
26672760
key.append(tuple(sub_key))
26682761
key = tuple(key)
26692762
return key
2763+
2764+
2765+
@functools.singledispatch
2766+
def is_mixed(obj) -> bool:
2767+
raise TypeError
2768+
2769+
2770+
@is_mixed.register
2771+
def _(elem: finat.ufl.FiniteElementBase) -> bool:
2772+
return type(elem) is finat.ufl.MixedElement
2773+
2774+
2775+
@is_mixed.register
2776+
def _(V: WithGeometryBase | AbstractFunctionSpace) -> bool:
2777+
return is_mixed(V.ufl_element())
2778+
2779+
2780+
@is_mixed.register
2781+
def _(arg: ufl.Argument, /) -> bool:
2782+
return is_mixed(arg.ufl_function_space())

0 commit comments

Comments
 (0)