In #224, @Patol75 wrote:
It took me a while, but I have something that seems to be working. However, given that it is the first time I get to that level of complexity with animate, I would not mind if you could give my workflow a quick look. In particular, I am not exactly sure when and in which order normalise, enforce_spd, and compute_hessian should be called. The function I use if the following:
def adapt_mesh(
mesh: fd.MeshGeometry, mesh_fields: dict[str, dict[str, fd.Function | bool]]
) -> fd.MeshGeometry:
for _ in range(prms.adapt_calls):
M = fd.TensorFunctionSpace(mesh, "CG", 1)
metric_fields = []
for field, field_specs in mesh_fields.items():
if isinstance(field_specs["add_to_metric"], list):
for dim, add_to_metric in enumerate(field_specs["add_to_metric"]):
if add_to_metric:
metric_fields.append(field.subfunctions[dim])
elif field_specs["add_to_metric"]:
metric_fields.append(field)
for field in metric_fields:
if isinstance(field.ufl_element(), fd.VectorElement):
metric_fields.remove(field)
for dim in range(field.ufl_shape[0]):
metric_fields.append(field.sub(dim))
metrics = []
for field in metric_fields:
# Firedrake function for a metric over a mesh where a field lives
metric = RiemannianMetric(M, name=f"Metric ({field.name()})")
metric.set_parameters(prms.metric_parameters) # Set metric parameters
metric.compute_hessian(field) # Field Hessian
metric.normalise() # Rescale metric to achieve the desired target complexity
metric.enforce_spd() # Ensure boundedness (symmetric positive-definite)
metrics.append(metric)
overall_metric = metrics[0].copy(deepcopy=True) # Overall metric
overall_metric.rename("Metric (overall)")
# Minimum in all directions across all metrics
overall_metric.intersect(*metrics[1:])
overall_metric.enforce_spd() # Ensure boundedness (symmetric positive-definite)
mesh = adapt(mesh, overall_metric) # Generate new mesh based on overall metric
# Ensure boundary coordinates are not exceeded
mesh.coordinates.dat.data[:, 0].clip(
0.0, prms.domain_dims[0], out=mesh.coordinates.dat.data[:, 0]
)
mesh.coordinates.dat.data[:, 1].clip(
0.0, prms.domain_dims[1], out=mesh.coordinates.dat.data[:, 1]
)
mesh.cartesian = True # Geometry tag informing other G-ADOPT objects
fields = list(mesh_fields)
for field in fields:
field_specs = mesh_fields.pop(field)
if isinstance(
field.function_space().topological,
fd.functionspaceimpl.MixedFunctionSpace,
):
spaces = []
for dim, element in enumerate(field.ufl_element().sub_elements):
spaces.append(fd.FunctionSpace(mesh, element))
mixed_space = fd.MixedFunctionSpace(spaces)
new_field = fd.Function(mixed_space, name=field.name())
for sub_field, new_sub_field in zip(
field.subfunctions, new_field.subfunctions
):
new_sub_field.rename(sub_field.name())
new_sub_field.interpolate(sub_field)
else:
space = fd.FunctionSpace(mesh, field.ufl_element())
new_field = fd.Function(space, name=field.name())
new_field.interpolate(field)
mesh_fields[new_field] = field_specs
return mesh
mesh_fields is defined as follows:
mesh_fields = {
stokes: {"add_to_metric": [True, False]},
T: {"add_to_metric": True},
psi: {"add_to_metric": True},
}
Thank you for your time and help.
In #224, @Patol75 wrote:
It took me a while, but I have something that seems to be working. However, given that it is the first time I get to that level of complexity with
animate, I would not mind if you could give my workflow a quick look. In particular, I am not exactly sure when and in which ordernormalise,enforce_spd, andcompute_hessianshould be called. The function I use if the following:mesh_fieldsis defined as follows:Thank you for your time and help.