Coverage for python/src/dolfinx_mpc/multipointconstraint.py: 86%
201 statements
« prev ^ index » next coverage.py v7.16.1, created at 2026-09-16 10:47 +0000
« prev ^ index » next coverage.py v7.16.1, created at 2026-09-16 10:47 +0000
1# Copyright (C) 2020-2023 Jørgen S. Dokken
2#
3# This file is part of DOLFINX_MPC
4#
5# SPDX-License-Identifier: MIT
6from __future__ import annotations
8from typing import Callable, Dict, List, Optional, Tuple, Union
10from petsc4py import PETSc as _PETSc
12import dolfinx.cpp as _cpp
13import dolfinx.fem as _fem
14import dolfinx.mesh as _mesh
15import numpy
16import numpy.typing as npt
17from dolfinx import default_real_type, default_scalar_type
19import dolfinx_mpc.cpp
21from .dictcondition import create_dictionary_constraint
23_mpc_classes = Union[
24 dolfinx_mpc.cpp.mpc.MultiPointConstraint_double,
25 dolfinx_mpc.cpp.mpc.MultiPointConstraint_float,
26 dolfinx_mpc.cpp.mpc.MultiPointConstraint_complex_double,
27 dolfinx_mpc.cpp.mpc.MultiPointConstraint_complex_float,
28]
29_float_classes = Union[numpy.float32, numpy.float64, numpy.complex128, numpy.complex64]
30_float_array_types = Union[
31 npt.NDArray[numpy.float32],
32 npt.NDArray[numpy.float64],
33 npt.NDArray[numpy.complex64],
34 npt.NDArray[numpy.complex128],
35]
36_mpc_data_classes = Union[
37 dolfinx_mpc.cpp.mpc.mpc_data_double,
38 dolfinx_mpc.cpp.mpc.mpc_data_float,
39 dolfinx_mpc.cpp.mpc.mpc_data_complex_double,
40 dolfinx_mpc.cpp.mpc.mpc_data_complex_float,
41]
44class MPCData:
45 _cpp_object: _mpc_data_classes
47 def __init__(
48 self,
49 slaves: npt.NDArray[numpy.int32],
50 masters: npt.NDArray[numpy.int64],
51 coeffs: _float_array_types,
52 owners: npt.NDArray[numpy.int32],
53 offsets: npt.NDArray[numpy.int32],
54 ):
55 if coeffs.dtype.type == numpy.float32:
56 self._cpp_object = dolfinx_mpc.cpp.mpc.mpc_data_float(slaves, masters, coeffs, owners, offsets)
57 elif coeffs.dtype.type == numpy.float64:
58 self._cpp_object = dolfinx_mpc.cpp.mpc.mpc_data_double(slaves, masters, coeffs, owners, offsets)
59 elif coeffs.dtype.type == numpy.complex64:
60 self._cpp_object = dolfinx_mpc.cpp.mpc.mpc_data_complex_float(slaves, masters, coeffs, owners, offsets)
61 elif coeffs.dtype.type == numpy.complex128:
62 self._cpp_object = dolfinx_mpc.cpp.mpc.mpc_data_complex_double(slaves, masters, coeffs, owners, offsets)
63 else:
64 raise ValueError("Unsupported dtype {coeffs.dtype.type} for coefficients")
66 @property
67 def slaves(self):
68 return self._cpp_object.slaves
70 @property
71 def masters(self):
72 return self._cpp_object.masters
74 @property
75 def coeffs(self):
76 return self._cpp_object.coeffs
78 @property
79 def owners(self):
80 return self._cpp_object.owners
82 @property
83 def offsets(self):
84 return self._cpp_object.offsets
87class MultiPointConstraint:
88 """
89 Hold data for multi point constraint relation ships,
90 including new index maps for local assembly of matrices and vectors.
92 The constraint is affine, :math:`x = K x_{red} + g`, where :math:`g` is
93 supplied through `rhs_coeffs` and through the Dirichlet conditions in
94 `bcs`. With neither, :math:`g=0` and the constraint is the usual linear
95 one.
97 Args:
98 V: The function space
99 dtype: The dtype of the underlying functions
100 bcs: Dirichlet boundary conditions for the problem. A master degree of
101 freedom that is constrained by one of these is removed from the
102 equation of its slave, and its contribution folded into the
103 constraint offset :math:`g`. As the offset is recomputed from the
104 current values of the conditions by :func:`update_constants`, time
105 dependent boundary data is supported.
106 rhs_coeffs: Function holding an additional inhomogeneity :math:`g_s`
107 for the slave degrees of freedom, i.e.
108 :math:`u_s = \\sum_j c_j u_{m_j} + g_s`.
109 """
111 _slaves: npt.NDArray[numpy.int32]
112 _masters: npt.NDArray[numpy.int64]
113 _coeffs: _float_array_types
114 _owners: npt.NDArray[numpy.int32]
115 _offsets: npt.NDArray[numpy.int32]
116 _bcs: List[_fem.DirichletBC]
117 _rhs_coeffs: Optional[_fem.Function]
118 V: _fem.FunctionSpace
119 finalized: bool
120 _cpp_object: _mpc_classes
121 _dtype: npt.DTypeLike
122 __slots__ = tuple(__annotations__)
124 def __init__(
125 self,
126 V: _fem.FunctionSpace,
127 dtype: npt.DTypeLike = default_scalar_type,
128 bcs: Optional[List[_fem.DirichletBC]] = None,
129 rhs_coeffs: Optional[_fem.Function] = None,
130 ):
131 self._slaves = numpy.array([], dtype=numpy.int32)
132 self._masters = numpy.array([], dtype=numpy.int64)
133 self._coeffs = numpy.array([], dtype=dtype) # type: ignore
134 self._owners = numpy.array([], dtype=numpy.int32)
135 self._offsets = numpy.array([0], dtype=numpy.int32)
136 self._bcs = [] if bcs is None else list(bcs)
137 if rhs_coeffs is not None:
138 if not rhs_coeffs.x.array.dtype == dtype:
139 raise ValueError("rhs_coeffs must have the same dtype as the MPC")
140 if rhs_coeffs.function_space != V:
141 raise ValueError("rhs_coeffs must be a Function in the space of the constraint")
142 self._rhs_coeffs = rhs_coeffs
143 self.V = V
144 self.finalized = False
145 self._dtype = dtype
147 def add_constraint(
148 self,
149 V: _fem.FunctionSpace,
150 slaves: npt.NDArray[numpy.int32],
151 masters: npt.NDArray[numpy.int64],
152 coeffs: _float_array_types,
153 owners: npt.NDArray[numpy.int32],
154 offsets: npt.NDArray[numpy.int32],
155 ):
156 """
157 Add new constraint given by numpy arrays.
159 Args:
160 V: The function space for the constraint
161 slaves: List of all slave dofs (using local dof numbering) on this process
162 masters: List of all master dofs (using global dof numbering) on this process
163 coeffs: The coefficients corresponding to each master.
164 owners: The process each master is owned by.
165 offsets: Array indicating the location in the masters array for the i-th slave
166 in the slaves arrays, i.e.
168 .. highlight:: python
169 .. code-block:: python
171 masters_of_owned_slave[i] = masters[offsets[i]:offsets[i+1]]
173 """
174 assert V == self.V
175 self._already_finalized()
177 if len(slaves) > 0:
178 self._offsets = numpy.append(self._offsets, offsets[1:] + len(self._masters))
179 self._slaves = numpy.append(self._slaves, slaves)
180 self._masters = numpy.append(self._masters, masters)
181 self._coeffs = numpy.array(numpy.append(self._coeffs, coeffs), dtype=self._dtype)
182 self._owners = numpy.append(self._owners, owners)
184 def add_constraint_from_mpc_data(self, V: _fem.FunctionSpace, mpc_data: Union[_mpc_data_classes, MPCData]):
185 """
186 Add new constraint given by an `dolfinc_mpc.cpp.mpc.mpc_data`-object
187 """
188 self._already_finalized()
189 self.add_constraint(
190 V,
191 mpc_data.slaves,
192 mpc_data.masters,
193 mpc_data.coeffs,
194 mpc_data.owners,
195 mpc_data.offsets,
196 )
198 def finalize(self) -> None:
199 """
200 Finializes the multi point constraint. After this function is called, no new constraints can be added
201 to the constraint. This function creates a map from the cells (local to index) to the slave degrees of
202 freedom and builds a new index map and function space where unghosted master dofs are added as ghosts.
203 """
204 self._already_finalized()
206 num_dofs_local = self.V.dofmap.index_map_bs * (
207 self.V.dofmap.index_map.size_local + self.V.dofmap.index_map.num_ghosts
208 )
209 if self._rhs_coeffs is None:
210 rhs_coeffs = numpy.zeros(0, dtype=self._dtype)
211 else:
212 rhs_coeffs = self._rhs_coeffs.x.array[:num_dofs_local].astype(self._dtype)
213 bcs = [bc._cpp_object for bc in self._bcs]
215 try:
216 cpp_class = {
217 numpy.float32: dolfinx_mpc.cpp.mpc.MultiPointConstraint_float,
218 numpy.float64: dolfinx_mpc.cpp.mpc.MultiPointConstraint_double,
219 numpy.complex64: dolfinx_mpc.cpp.mpc.MultiPointConstraint_complex_float,
220 numpy.complex128: dolfinx_mpc.cpp.mpc.MultiPointConstraint_complex_double,
221 }[numpy.dtype(self._dtype).type]
222 except KeyError:
223 raise ValueError(f"Unsupported dtype {self._dtype} for coefficients")
225 # Initialize C++ object and create slave->cell maps
226 self._cpp_object = cpp_class(
227 self.V._cpp_object,
228 self._slaves,
229 self._masters,
230 self._coeffs.astype(self._dtype),
231 self._owners,
232 self._offsets,
233 rhs_coeffs,
234 bcs,
235 )
237 # Replace function space
238 self.V = _fem.FunctionSpace(self.V.mesh, self.V.ufl_element(), self._cpp_object.function_space)
240 self.finalized = True
241 # Delete variables that are no longer required
242 del (self._slaves, self._masters, self._coeffs, self._owners, self._offsets)
244 def update_constants(self) -> None:
245 """
246 Recompute the constraint offset :math:`g` from the current values of the Dirichlet
247 conditions supplied to the constructor.
249 Call this whenever the value of one of those conditions changes, for instance between
250 time steps, before re-assembling. :class:`LinearProblem` calls it automatically.
252 Note:
253 Collective. Must be called by every process.
254 """
255 self._not_finalized()
256 if self._rhs_coeffs is not None:
257 # Pass the array natively. Zero-copy, zero-allocation.
258 num_dofs_local = self.V.dofmap.index_map_bs * (
259 self.V.dofmap.index_map.size_local + self.V.dofmap.index_map.num_ghosts
260 )
261 rhs_coeffs = self._rhs_coeffs.x.array[:num_dofs_local]
262 self._cpp_object.set_rhs_coeffs(rhs_coeffs)
264 self._cpp_object.update_constants()
266 @property
267 def constants(self) -> _float_array_types:
268 """
269 The constraint offset :math:`g` for each degree of freedom local to the process,
270 i.e. the affine term in :math:`x = K x_{red} + g`.
271 """
272 self._not_finalized()
273 return self._cpp_object.constants
275 @property
276 def has_inhomogeneity(self) -> bool:
277 """
278 Whether any process carries a non-zero constraint offset. The value is globally
279 reduced, so it is identical on every process.
280 """
281 self._not_finalized()
282 return self._cpp_object.has_inhomogeneity
284 def create_periodic_constraint_topological(
285 self,
286 V: _fem.FunctionSpace,
287 meshtag: _mesh.MeshTags,
288 tag: int,
289 relation: Callable[[numpy.ndarray], numpy.ndarray],
290 bcs: List[_fem.DirichletBC],
291 scale: _float_classes = default_scalar_type(1.0), # type: ignore
292 tol: _float_classes = 500 * numpy.finfo(default_real_type).eps,
293 num_threads: Optional[int] = 1,
294 ):
295 """
296 Create periodic condition for all closure dofs of on all entities in `meshtag` with value `tag`.
297 :math:`u(x_i) = scale * u(relation(x_i))` for all of :math:`x_i` on marked entities.
299 Args:
300 V: The function space to assign the condition to. Should either be the space of the MPC or a sub space.
301 meshtag: MeshTag for entity to apply the periodic condition on
302 tag: Tag indicating which entities should be slaves
303 relation: Lambda-function describing the geometrical relation
304 bcs: Dirichlet boundary conditions for the problem (Periodic constraints will be ignored for these dofs)
305 scale: Float for scaling bc
306 tol: Tolerance for adding scaled basis values to MPC. Any contribution that is less than this value
307 is ignored. The tolerance is also added as padding for the bounding box trees and corresponding
308 collision searches to determine periodic degrees of freedom.
309 num_threads: The number of threads to use for certain operations
310 """
311 bcs_ = [bc._cpp_object for bc in bcs]
312 if isinstance(scale, numpy.generic): # nanobind conversion of numpy dtypes to general Python types
313 scale = scale.item() # type: ignore
314 if V is self.V:
315 mpc_data = dolfinx_mpc.cpp.mpc.create_periodic_constraint_topological(
316 self.V._cpp_object,
317 meshtag._cpp_object,
318 tag,
319 relation,
320 bcs_,
321 scale,
322 False,
323 float(tol),
324 num_threads=num_threads,
325 )
326 elif self.V.contains(V):
327 mpc_data = dolfinx_mpc.cpp.mpc.create_periodic_constraint_topological(
328 V._cpp_object,
329 meshtag._cpp_object,
330 tag,
331 relation,
332 bcs_,
333 scale,
334 True,
335 float(tol),
336 num_threads=num_threads,
337 )
338 else:
339 raise RuntimeError("The input space has to be a sub space (or the full space) of the MPC")
340 self.add_constraint_from_mpc_data(self.V, mpc_data=mpc_data)
342 def create_periodic_constraint_geometrical(
343 self,
344 V: _fem.FunctionSpace,
345 indicator: Callable[[numpy.ndarray], numpy.ndarray],
346 relation: Callable[[numpy.ndarray], numpy.ndarray],
347 bcs: List[_fem.DirichletBC],
348 scale: _float_classes = default_scalar_type(1.0), # type: ignore
349 tol: _float_classes = 500 * numpy.finfo(default_real_type).eps,
350 num_threads: Optional[int] = 1,
351 ):
352 """
353 Create a periodic condition for all degrees of freedom whose physical location satisfies
354 :math:`indicator(x_i)==True`, i.e.
355 :math:`u(x_i) = scale * u(relation(x_i))` for all :math:`x_i`
357 Args:
358 V: The function space to assign the condition to. Should either be the space of the MPC or a sub space.
359 indicator: Lambda-function to locate degrees of freedom that should be slaves
360 relation: Lambda-function describing the geometrical relation to master dofs
361 bcs: Dirichlet boundary conditions for the problem
362 (Periodic constraints will be ignored for these dofs)
363 scale: Float for scaling bc
364 tol: Tolerance for adding scaled basis values to MPC. Any contribution that is less than this value
365 is ignored. The tolerance is also added as padding for the bounding box trees and corresponding
366 collision searches to determine periodic degrees of freedom.
367 num_threads: The number of threads to use for certain operations.
368 """
369 if isinstance(scale, numpy.generic): # nanobind conversion of numpy dtypes to general Python types
370 scale = scale.item() # type: ignore
371 bcs = [] if bcs is None else [bc._cpp_object for bc in bcs]
372 if V is self.V:
373 mpc_data = dolfinx_mpc.cpp.mpc.create_periodic_constraint_geometrical(
374 self.V._cpp_object, indicator, relation, bcs, scale, False, float(tol), num_threads
375 )
376 elif self.V.contains(V):
377 mpc_data = dolfinx_mpc.cpp.mpc.create_periodic_constraint_geometrical(
378 V._cpp_object, indicator, relation, bcs, scale, True, float(tol), num_threads
379 )
380 else:
381 raise RuntimeError("The input space has to be a sub space (or the full space) of the MPC")
382 self.add_constraint_from_mpc_data(self.V, mpc_data=mpc_data)
384 def create_slip_constraint(
385 self,
386 space: _fem.FunctionSpace,
387 facet_marker: Tuple[_mesh.MeshTags, int],
388 v: _fem.Function,
389 bcs: List[_fem.DirichletBC] = [],
390 ):
391 """
392 Create a slip constraint :math:`u \\cdot v=0` over the entities defined in `facet_marker` with the given index.
394 Args:
395 space: Function space (possible sub space) for the current constraint
396 facet_marker: Tuple containomg the mesh tag and marker used to locate degrees of freedom
397 v: Function containing the directional vector to dot your slip condition (most commonly a normal vector)
398 bcs: List of Dirichlet BCs (slip conditions will be ignored on these dofs)
400 Examples:
401 Create constaint :math:`u\\cdot n=0` of all indices in `mt` marked with `i`
403 .. highlight:: python
404 .. code-block:: python
406 V = dolfinx.fem.functionspace(mesh, ("CG", 1))
407 mpc = MultiPointConstraint(V)
408 n = dolfinx.fem.Function(V)
409 mpc.create_slip_constaint(V, (mt, i), n)
411 Create slip constaint for a mixed function space:
413 .. highlight:: python
414 .. code-block:: python
416 cellname = mesh.basix_cell()
417 Ve = basix.ufl.element(basix.ElementFamily.P, cellname , 2, shape=(mesh.geometry.dim,))
418 Qe = basix.ufl.element(basix.ElementFamily.P, cellname , 1)
419 me = basix.ufl.mixed_element([Ve, Qe])
420 W = dolfinx.fem.functionspace(mesh, me)
421 mpc = MultiPointConstraint(W)
422 n_space, _ = W.sub(0).collapse()
423 normal = dolfinx.fem.Function(n_space)
424 mpc.create_slip_constraint(W.sub(0), (mt, i), normal, bcs=[])
426 A slip condition cannot be applied on the same degrees of freedom as a Dirichlet BC, and therefore
427 any Dirichlet bc for the space of the multi point constraint should be supplied.
429 .. highlight:: python
430 .. code-block:: python
432 cellname = mesh.basix_cell()
433 Ve = basix.ufl.element(basix.ElementFamily.P, cellname , 2, shape=(mesh.geometry.dim,))
434 Qe = basix.ufl.element(basix.ElementFamily.P, cellname , 1)
435 me = basix.ufl.mixed_element([Ve, Qe])
436 W = dolfinx.fem.functionspace(mesh, me)
437 mpc = MultiPointConstraint(W)
438 n_space, _ = W.sub(0).collapse()
439 normal = Function(n_space)
440 bc = dolfinx.fem.dirichletbc(inlet_velocity, dofs, W.sub(0))
441 mpc.create_slip_constraint(W.sub(0), (mt, i), normal, bcs=[bc])
442 """
443 bcs = [] if bcs is None else [bc._cpp_object for bc in bcs]
444 if space is self.V:
445 sub_space = False
446 elif self.V.contains(space):
447 sub_space = True
448 else:
449 raise ValueError("Input space has to be a sub space of the MPC space")
450 mpc_data = dolfinx_mpc.cpp.mpc.create_slip_condition(
451 space._cpp_object,
452 facet_marker[0]._cpp_object,
453 facet_marker[1],
454 v._cpp_object,
455 bcs,
456 sub_space,
457 )
458 self.add_constraint_from_mpc_data(self.V, mpc_data=mpc_data)
460 def create_general_constraint(
461 self,
462 slave_master_dict: Dict[bytes, Dict[bytes, float]],
463 subspace_slave: Optional[int] = None,
464 subspace_master: Optional[int] = None,
465 ):
466 """
467 Args:
468 V: The function space
469 slave_master_dict: Nested dictionary, where the first key is the bit representing the slave dof's
470 coordinate in the mesh. The item of this key is a dictionary, where each key of this dictionary
471 is the bit representation of the master dof's coordinate, and the item the coefficient for
472 the MPC equation.
473 subspace_slave: If using mixed or vector space, and only want to use dofs from a sub space
474 as slave add index here
475 subspace_master: Subspace index for mixed or vector spaces
477 Example:
478 If the dof `D` located at `[d0, d1]` should be constrained to the dofs
479 `E` and `F` at `[e0, e1]` and `[f0, f1]` as :math:`D = \\alpha E + \\beta F`
480 the dictionary should be:
482 .. highlight:: python
483 .. code-block:: python
485 {numpy.array([d0, d1], dtype=mesh.geometry.x.dtype).tobytes():
486 {numpy.array([e0, e1], dtype=mesh.geometry.x.dtype).tobytes(): alpha,
487 numpy.array([f0, f1], dtype=mesh.geometry.x.dtype).tobytes(): beta}}
488 """
489 slaves, masters, coeffs, owners, offsets = create_dictionary_constraint(
490 self.V, slave_master_dict, subspace_slave, subspace_master
491 )
492 self.add_constraint(self.V, slaves, masters, coeffs, owners, offsets)
494 def create_contact_slip_condition(
495 self,
496 meshtags: _mesh.MeshTags,
497 slave_marker: int,
498 master_marker: int,
499 normal: _fem.Function,
500 eps2: float = 1e-20,
501 num_threads: Optional[int] = 1,
502 ):
503 """
504 Create a slip condition between two sets of facets marker with individual markers.
505 The interfaces should be within machine precision of eachother, but the vertices does not need to align.
506 The condition created is :math:`u_s \\cdot normal_s = u_m \\cdot normal_m` where `s` is the
507 restriction to the slave facets, `m` to the master facets.
509 Args:
510 meshtags: The meshtags of the set of facets to tie together
511 slave_marker: The marker of the slave facets
512 master_marker: The marker of the master facets
513 normal: The function used in the dot-product of the constraint
514 eps2: The tolerance for the squared distance between cells to be considered as a collision
515 num_threads: The number of threads to use for certain operations
516 """
517 if isinstance(eps2, numpy.generic): # nanobind conversion of numpy dtypes to general Python types
518 eps2 = eps2.item() # type: ignore
519 mpc_data = dolfinx_mpc.cpp.mpc.create_contact_slip_condition(
520 self.V._cpp_object, meshtags._cpp_object, slave_marker, master_marker, normal._cpp_object, eps2, num_threads
521 )
522 self.add_constraint_from_mpc_data(self.V, mpc_data)
524 def create_contact_inelastic_condition(
525 self,
526 meshtags: _cpp.mesh.MeshTags_int32,
527 slave_marker: int,
528 master_marker: int,
529 eps2: float = 1e-20,
530 allow_missing_masters: bool = False,
531 num_threads: Optional[int] = 1,
532 ):
533 """
534 Create a contact inelastic condition between two sets of facets marker with individual markers.
535 The interfaces should be within machine precision of eachother, but the vertices does not need to align.
536 The condition created is :math:`u_s = u_m` where `s` is the restriction to the
537 slave facets, `m` to the master facets.
539 Args:
540 meshtags: The meshtags of the set of facets to tie together
541 slave_marker: The marker of the slave facets
542 master_marker: The marker of the master facets
543 eps2: The tolerance for the squared distance between cells to be considered as a collision
544 allow_missing_masters: If true, the function will not throw an error if a degree of freedom
545 in the closure of the master entities does not have a corresponding set of slave degree
546 of freedom.
547 num_threads: The number of threads to use for certain operations
548 """
549 if isinstance(eps2, numpy.generic): # nanobind conversion of numpy dtypes to general Python types
550 eps2 = eps2.item() # type: ignore
551 mpc_data = dolfinx_mpc.cpp.mpc.create_contact_inelastic_condition(
552 self.V._cpp_object,
553 meshtags._cpp_object,
554 slave_marker,
555 master_marker,
556 eps2,
557 allow_missing_masters,
558 num_threads,
559 )
560 self.add_constraint_from_mpc_data(self.V, mpc_data)
562 @property
563 def is_slave(self) -> numpy.ndarray:
564 """
565 Returns a vector of integers where the ith entry indicates if a degree of freedom (local to process) is a slave.
566 """
567 self._not_finalized()
568 return self._cpp_object.is_slave
570 @property
571 def slaves(self):
572 """
573 Returns the degrees of freedom for all slaves local to process
574 """
575 self._not_finalized()
576 return self._cpp_object.slaves
578 @property
579 def masters(self) -> _cpp.graph.AdjacencyList_int32:
580 """
581 Returns an adjacency-list whose ith node corresponds to
582 a degree of freedom (local to process), and links the corresponding master dofs (local to process).
584 Examples:
586 .. highlight:: python
587 .. code-block:: python
589 masters = mpc.masters
590 masters_of_dof_i = masters.links(i)
591 """
592 self._not_finalized()
593 return self._cpp_object.masters
595 def coefficients(self) -> _float_array_types:
596 """
597 Returns a vector containing the coefficients for the constraint, and the corresponding offsets
598 for the ith degree of freedom.
600 Examples:
602 .. highlight:: python
603 .. code-block:: python
605 coeffs, offsets = mpc.coefficients()
606 coeffs_of_slave_i = coeffs[offsets[i]:offsets[i+1]]
607 """
608 self._not_finalized()
609 return self._cpp_object.coefficients()
611 @property
612 def num_local_slaves(self):
613 """
614 Return the number of slaves owned by the current process.
615 """
616 self._not_finalized()
617 return self._cpp_object.num_local_slaves
619 @property
620 def cell_to_slaves(self):
621 """
622 Returns an `dolfinx.cpp.graph.AdjacencyList_int32` whose ith node corresponds to
623 the ith cell (local to process), and links the corresponding slave degrees of
624 freedom in the cell (local to process).
626 Examples:
628 .. highlight:: python
629 .. code-block:: python
631 cell_to_slaves = mpc.cell_to_slaves()
632 slaves_in_cell_i = cell_to_slaves.links(i)
633 """
634 self._not_finalized()
635 return self._cpp_object.cell_to_slaves
637 @property
638 def function_space(self):
639 """
640 Return the function space for the multi-point constraint with the updated index map
641 """
642 self._not_finalized()
643 return self.V
645 def backsubstitution(self, u: Union[_fem.Function, _PETSc.Vec]) -> None: # type: ignore
646 """
647 For a Function, impose the multi-point constraint by backsubstiution.
648 This function is used after solving the reduced problem to obtain the values
649 at the slave degrees of freedom
651 .. note::
652 It is the users responsibility to destroy the PETSc vector
654 Args:
655 u: The input function
656 """
657 try:
658 self._cpp_object.backsubstitution(u.x.array) # type: ignore
659 assert isinstance(u, _fem.Function)
660 u.x.scatter_forward()
661 except AttributeError:
662 assert isinstance(u, _PETSc.Vec)
663 with u.localForm() as vector_local:
664 self._cpp_object.backsubstitution(vector_local.array_w)
665 u.ghostUpdate(addv=_PETSc.InsertMode.INSERT, mode=_PETSc.ScatterMode.FORWARD) # type: ignore
667 def homogenize(self, u: _fem.Function) -> None:
668 """
669 For a vector, homogenize (set to zero) the vector components at the multi-point
670 constraint slave DoF indices. This is particularly useful for nonlinear problems.
672 Args:
673 u: The input vector
674 """
675 self._cpp_object.homogenize(u.x.array)
676 u.x.scatter_forward()
678 def _already_finalized(self):
679 """
680 Check if we have already finalized the multi point constraint
681 """
682 if self.finalized:
683 raise RuntimeError("MultiPointConstraint has already been finalized")
685 def _not_finalized(self):
686 """
687 Check if we have finalized the multi point constraint
688 """
689 if not self.finalized:
690 raise RuntimeError("MultiPointConstraint has not been finalized")