Coverage for python/src/dolfinx_mpc/multipointconstraint.py: 82%

175 statements  

« prev     ^ index     » next       coverage.py v7.10.1, created at 2025-07-29 22:03 +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 

7 

8from typing import Callable, Dict, List, Optional, Tuple, Union 

9 

10from petsc4py import PETSc as _PETSc 

11 

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 

18 

19import dolfinx_mpc.cpp 

20 

21from .dictcondition import create_dictionary_constraint 

22 

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] 

42 

43 

44class MPCData: 

45 _cpp_object: _mpc_data_classes 

46 

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") 

65 

66 @property 

67 def slaves(self): 

68 return self._cpp_object.slaves 

69 

70 @property 

71 def masters(self): 

72 return self._cpp_object.masters 

73 

74 @property 

75 def coeffs(self): 

76 return self._cpp_object.coeffs 

77 

78 @property 

79 def owners(self): 

80 return self._cpp_object.owners 

81 

82 @property 

83 def offsets(self): 

84 return self._cpp_object.offsets 

85 

86 

87class MultiPointConstraint: 

88 """ 

89 Hold data for multi point constraint relation ships, 

90 including new index maps for local assembly of matrices and vectors. 

91 

92 Args: 

93 V: The function space 

94 dtype: The dtype of the underlying functions 

95 """ 

96 

97 _slaves: npt.NDArray[numpy.int32] 

98 _masters: npt.NDArray[numpy.int64] 

99 _coeffs: _float_array_types 

100 _owners: npt.NDArray[numpy.int32] 

101 _offsets: npt.NDArray[numpy.int32] 

102 V: _fem.FunctionSpace 

103 finalized: bool 

104 _cpp_object: _mpc_classes 

105 _dtype: numpy.floating 

106 __slots__ = tuple(__annotations__) 

107 

108 def __init__(self, V: _fem.FunctionSpace, dtype: numpy.floating = default_scalar_type): 

109 self._slaves = numpy.array([], dtype=numpy.int32) 

110 self._masters = numpy.array([], dtype=numpy.int64) 

111 self._coeffs = numpy.array([], dtype=dtype) # type: ignore 

112 self._owners = numpy.array([], dtype=numpy.int32) 

113 self._offsets = numpy.array([0], dtype=numpy.int32) 

114 self.V = V 

115 self.finalized = False 

116 self._dtype = dtype 

117 

118 def add_constraint( 

119 self, 

120 V: _fem.FunctionSpace, 

121 slaves: npt.NDArray[numpy.int32], 

122 masters: npt.NDArray[numpy.int64], 

123 coeffs: _float_array_types, 

124 owners: npt.NDArray[numpy.int32], 

125 offsets: npt.NDArray[numpy.int32], 

126 ): 

127 """ 

128 Add new constraint given by numpy arrays. 

129 

130 Args: 

131 V: The function space for the constraint 

132 slaves: List of all slave dofs (using local dof numbering) on this process 

133 masters: List of all master dofs (using global dof numbering) on this process 

134 coeffs: The coefficients corresponding to each master. 

135 owners: The process each master is owned by. 

136 offsets: Array indicating the location in the masters array for the i-th slave 

137 in the slaves arrays, i.e. 

138 

139 .. highlight:: python 

140 .. code-block:: python 

141 

142 masters_of_owned_slave[i] = masters[offsets[i]:offsets[i+1]] 

143 

144 """ 

145 assert V == self.V 

146 self._already_finalized() 

147 

148 if len(slaves) > 0: 

149 self._offsets = numpy.append(self._offsets, offsets[1:] + len(self._masters)) 

150 self._slaves = numpy.append(self._slaves, slaves) 

151 self._masters = numpy.append(self._masters, masters) 

152 self._coeffs = numpy.append(self._coeffs, coeffs) 

153 self._owners = numpy.append(self._owners, owners) 

154 

155 def add_constraint_from_mpc_data(self, V: _fem.FunctionSpace, mpc_data: Union[_mpc_data_classes, MPCData]): 

156 """ 

157 Add new constraint given by an `dolfinc_mpc.cpp.mpc.mpc_data`-object 

158 """ 

159 self._already_finalized() 

160 self.add_constraint( 

161 V, 

162 mpc_data.slaves, 

163 mpc_data.masters, 

164 mpc_data.coeffs, 

165 mpc_data.owners, 

166 mpc_data.offsets, 

167 ) 

168 

169 def finalize(self) -> None: 

170 """ 

171 Finializes the multi point constraint. After this function is called, no new constraints can be added 

172 to the constraint. This function creates a map from the cells (local to index) to the slave degrees of 

173 freedom and builds a new index map and function space where unghosted master dofs are added as ghosts. 

174 """ 

175 self._already_finalized() 

176 self._coeffs.astype(numpy.dtype(self._dtype)) 

177 # Initialize C++ object and create slave->cell maps 

178 if self._dtype == numpy.float32: 

179 self._cpp_object = dolfinx_mpc.cpp.mpc.MultiPointConstraint_float( 

180 self.V._cpp_object, 

181 self._slaves, 

182 self._masters, 

183 self._coeffs.astype(self._dtype), 

184 self._owners, 

185 self._offsets, 

186 ) 

187 elif self._dtype == numpy.float64: 

188 self._cpp_object = dolfinx_mpc.cpp.mpc.MultiPointConstraint_double( 

189 self.V._cpp_object, 

190 self._slaves, 

191 self._masters, 

192 self._coeffs.astype(self._dtype), 

193 self._owners, 

194 self._offsets, 

195 ) 

196 elif self._dtype == numpy.complex64: 

197 self._cpp_object = dolfinx_mpc.cpp.mpc.MultiPointConstraint_complex_float( 

198 self.V._cpp_object, 

199 self._slaves, 

200 self._masters, 

201 self._coeffs.astype(self._dtype), 

202 self._owners, 

203 self._offsets, 

204 ) 

205 

206 elif self._dtype == numpy.complex128: 

207 self._cpp_object = dolfinx_mpc.cpp.mpc.MultiPointConstraint_complex_double( 

208 self.V._cpp_object, 

209 self._slaves, 

210 self._masters, 

211 self._coeffs.astype(self._dtype), 

212 self._owners, 

213 self._offsets, 

214 ) 

215 else: 

216 raise ValueError("Unsupported dtype {coeffs.dtype.type} for coefficients") 

217 

218 # Replace function space 

219 self.V = _fem.FunctionSpace(self.V.mesh, self.V.ufl_element(), self._cpp_object.function_space) 

220 

221 self.finalized = True 

222 # Delete variables that are no longer required 

223 del (self._slaves, self._masters, self._coeffs, self._owners, self._offsets) 

224 

225 def create_periodic_constraint_topological( 

226 self, 

227 V: _fem.FunctionSpace, 

228 meshtag: _mesh.MeshTags, 

229 tag: int, 

230 relation: Callable[[numpy.ndarray], numpy.ndarray], 

231 bcs: List[_fem.DirichletBC], 

232 scale: _float_classes = default_scalar_type(1.0), 

233 tol: _float_classes = 500 * numpy.finfo(default_real_type).eps, 

234 ): 

235 """ 

236 Create periodic condition for all closure dofs of on all entities in `meshtag` with value `tag`. 

237 :math:`u(x_i) = scale * u(relation(x_i))` for all of :math:`x_i` on marked entities. 

238 

239 Args: 

240 V: The function space to assign the condition to. Should either be the space of the MPC or a sub space. 

241 meshtag: MeshTag for entity to apply the periodic condition on 

242 tag: Tag indicating which entities should be slaves 

243 relation: Lambda-function describing the geometrical relation 

244 bcs: Dirichlet boundary conditions for the problem (Periodic constraints will be ignored for these dofs) 

245 scale: Float for scaling bc 

246 tol: Tolerance for adding scaled basis values to MPC. Any contribution that is less than this value 

247 is ignored. The tolerance is also added as padding for the bounding box trees and corresponding 

248 collision searches to determine periodic degrees of freedom. 

249 """ 

250 bcs_ = [bc._cpp_object for bc in bcs] 

251 if isinstance(scale, numpy.generic): # nanobind conversion of numpy dtypes to general Python types 

252 scale = scale.item() # type: ignore 

253 if V is self.V: 

254 mpc_data = dolfinx_mpc.cpp.mpc.create_periodic_constraint_topological( 

255 self.V._cpp_object, meshtag._cpp_object, tag, relation, bcs_, scale, False, float(tol) 

256 ) 

257 elif self.V.contains(V): 

258 mpc_data = dolfinx_mpc.cpp.mpc.create_periodic_constraint_topological( 

259 V._cpp_object, meshtag._cpp_object, tag, relation, bcs_, scale, True, float(tol) 

260 ) 

261 else: 

262 raise RuntimeError("The input space has to be a sub space (or the full space) of the MPC") 

263 self.add_constraint_from_mpc_data(self.V, mpc_data=mpc_data) 

264 

265 def create_periodic_constraint_geometrical( 

266 self, 

267 V: _fem.FunctionSpace, 

268 indicator: Callable[[numpy.ndarray], numpy.ndarray], 

269 relation: Callable[[numpy.ndarray], numpy.ndarray], 

270 bcs: List[_fem.DirichletBC], 

271 scale: _float_classes = default_scalar_type(1.0), 

272 tol: _float_classes = 500 * numpy.finfo(default_real_type).eps, 

273 ): 

274 """ 

275 Create a periodic condition for all degrees of freedom whose physical location satisfies 

276 :math:`indicator(x_i)==True`, i.e. 

277 :math:`u(x_i) = scale * u(relation(x_i))` for all :math:`x_i` 

278 

279 Args: 

280 V: The function space to assign the condition to. Should either be the space of the MPC or a sub space. 

281 indicator: Lambda-function to locate degrees of freedom that should be slaves 

282 relation: Lambda-function describing the geometrical relation to master dofs 

283 bcs: Dirichlet boundary conditions for the problem 

284 (Periodic constraints will be ignored for these dofs) 

285 scale: Float for scaling bc 

286 tol: Tolerance for adding scaled basis values to MPC. Any contribution that is less than this value 

287 is ignored. The tolerance is also added as padding for the bounding box trees and corresponding 

288 collision searches to determine periodic degrees of freedom. 

289 """ 

290 if isinstance(scale, numpy.generic): # nanobind conversion of numpy dtypes to general Python types 

291 scale = scale.item() # type: ignore 

292 bcs = [] if bcs is None else [bc._cpp_object for bc in bcs] 

293 if V is self.V: 

294 mpc_data = dolfinx_mpc.cpp.mpc.create_periodic_constraint_geometrical( 

295 self.V._cpp_object, indicator, relation, bcs, scale, False, float(tol) 

296 ) 

297 elif self.V.contains(V): 

298 mpc_data = dolfinx_mpc.cpp.mpc.create_periodic_constraint_geometrical( 

299 V._cpp_object, indicator, relation, bcs, scale, True, float(tol) 

300 ) 

301 else: 

302 raise RuntimeError("The input space has to be a sub space (or the full space) of the MPC") 

303 self.add_constraint_from_mpc_data(self.V, mpc_data=mpc_data) 

304 

305 def create_slip_constraint( 

306 self, 

307 space: _fem.FunctionSpace, 

308 facet_marker: Tuple[_mesh.MeshTags, int], 

309 v: _fem.Function, 

310 bcs: List[_fem.DirichletBC] = [], 

311 ): 

312 """ 

313 Create a slip constraint :math:`u \\cdot v=0` over the entities defined in `facet_marker` with the given index. 

314 

315 Args: 

316 space: Function space (possible sub space) for the current constraint 

317 facet_marker: Tuple containomg the mesh tag and marker used to locate degrees of freedom 

318 v: Function containing the directional vector to dot your slip condition (most commonly a normal vector) 

319 bcs: List of Dirichlet BCs (slip conditions will be ignored on these dofs) 

320 

321 Examples: 

322 Create constaint :math:`u\\cdot n=0` of all indices in `mt` marked with `i` 

323 

324 .. highlight:: python 

325 .. code-block:: python 

326 

327 V = dolfinx.fem.functionspace(mesh, ("CG", 1)) 

328 mpc = MultiPointConstraint(V) 

329 n = dolfinx.fem.Function(V) 

330 mpc.create_slip_constaint(V, (mt, i), n) 

331 

332 Create slip constaint for a mixed function space: 

333 

334 .. highlight:: python 

335 .. code-block:: python 

336 

337 cellname = mesh.ufl_cell().cellname() 

338 Ve = basix.ufl.element(basix.ElementFamily.P, cellname , 2, shape=(mesh.geometry.dim,)) 

339 Qe = basix.ufl.element(basix.ElementFamily.P, cellname , 1) 

340 me = basix.ufl.mixed_element([Ve, Qe]) 

341 W = dolfinx.fem.functionspace(mesh, me) 

342 mpc = MultiPointConstraint(W) 

343 n_space, _ = W.sub(0).collapse() 

344 normal = dolfinx.fem.Function(n_space) 

345 mpc.create_slip_constraint(W.sub(0), (mt, i), normal, bcs=[]) 

346 

347 A slip condition cannot be applied on the same degrees of freedom as a Dirichlet BC, and therefore 

348 any Dirichlet bc for the space of the multi point constraint should be supplied. 

349 

350 .. highlight:: python 

351 .. code-block:: python 

352 

353 cellname = mesh.ufl_cell().cellname() 

354 Ve = basix.ufl.element(basix.ElementFamily.P, cellname , 2, shape=(mesh.geometry.dim,)) 

355 Qe = basix.ufl.element(basix.ElementFamily.P, cellname , 1) 

356 me = basix.ufl.mixed_element([Ve, Qe]) 

357 W = dolfinx.fem.functionspace(mesh, me) 

358 mpc = MultiPointConstraint(W) 

359 n_space, _ = W.sub(0).collapse() 

360 normal = Function(n_space) 

361 bc = dolfinx.fem.dirichletbc(inlet_velocity, dofs, W.sub(0)) 

362 mpc.create_slip_constraint(W.sub(0), (mt, i), normal, bcs=[bc]) 

363 """ 

364 bcs = [] if bcs is None else [bc._cpp_object for bc in bcs] 

365 if space is self.V: 

366 sub_space = False 

367 elif self.V.contains(space): 

368 sub_space = True 

369 else: 

370 raise ValueError("Input space has to be a sub space of the MPC space") 

371 mpc_data = dolfinx_mpc.cpp.mpc.create_slip_condition( 

372 space._cpp_object, 

373 facet_marker[0]._cpp_object, 

374 facet_marker[1], 

375 v._cpp_object, 

376 bcs, 

377 sub_space, 

378 ) 

379 self.add_constraint_from_mpc_data(self.V, mpc_data=mpc_data) 

380 

381 def create_general_constraint( 

382 self, 

383 slave_master_dict: Dict[bytes, Dict[bytes, float]], 

384 subspace_slave: Optional[int] = None, 

385 subspace_master: Optional[int] = None, 

386 ): 

387 """ 

388 Args: 

389 V: The function space 

390 slave_master_dict: Nested dictionary, where the first key is the bit representing the slave dof's 

391 coordinate in the mesh. The item of this key is a dictionary, where each key of this dictionary 

392 is the bit representation of the master dof's coordinate, and the item the coefficient for 

393 the MPC equation. 

394 subspace_slave: If using mixed or vector space, and only want to use dofs from a sub space 

395 as slave add index here 

396 subspace_master: Subspace index for mixed or vector spaces 

397 

398 Example: 

399 If the dof `D` located at `[d0, d1]` should be constrained to the dofs 

400 `E` and `F` at `[e0, e1]` and `[f0, f1]` as :math:`D = \\alpha E + \\beta F` 

401 the dictionary should be: 

402 

403 .. highlight:: python 

404 .. code-block:: python 

405 

406 {numpy.array([d0, d1], dtype=mesh.geometry.x.dtype).tobytes(): 

407 {numpy.array([e0, e1], dtype=mesh.geometry.x.dtype).tobytes(): alpha, 

408 numpy.array([f0, f1], dtype=mesh.geometry.x.dtype).tobytes(): beta}} 

409 """ 

410 slaves, masters, coeffs, owners, offsets = create_dictionary_constraint( 

411 self.V, slave_master_dict, subspace_slave, subspace_master 

412 ) 

413 self.add_constraint(self.V, slaves, masters, coeffs, owners, offsets) 

414 

415 def create_contact_slip_condition( 

416 self, 

417 meshtags: _mesh.MeshTags, 

418 slave_marker: int, 

419 master_marker: int, 

420 normal: _fem.Function, 

421 eps2: float = 1e-20, 

422 ): 

423 """ 

424 Create a slip condition between two sets of facets marker with individual markers. 

425 The interfaces should be within machine precision of eachother, but the vertices does not need to align. 

426 The condition created is :math:`u_s \\cdot normal_s = u_m \\cdot normal_m` where `s` is the 

427 restriction to the slave facets, `m` to the master facets. 

428 

429 Args: 

430 meshtags: The meshtags of the set of facets to tie together 

431 slave_marker: The marker of the slave facets 

432 master_marker: The marker of the master facets 

433 normal: The function used in the dot-product of the constraint 

434 eps2: The tolerance for the squared distance between cells to be considered as a collision 

435 """ 

436 if isinstance(eps2, numpy.generic): # nanobind conversion of numpy dtypes to general Python types 

437 eps2 = eps2.item() # type: ignore 

438 mpc_data = dolfinx_mpc.cpp.mpc.create_contact_slip_condition( 

439 self.V._cpp_object, 

440 meshtags._cpp_object, 

441 slave_marker, 

442 master_marker, 

443 normal._cpp_object, 

444 eps2, 

445 ) 

446 self.add_constraint_from_mpc_data(self.V, mpc_data) 

447 

448 def create_contact_inelastic_condition( 

449 self, 

450 meshtags: _cpp.mesh.MeshTags_int32, 

451 slave_marker: int, 

452 master_marker: int, 

453 eps2: float = 1e-20, 

454 allow_missing_masters: bool = False, 

455 ): 

456 """ 

457 Create a contact inelastic condition between two sets of facets marker with individual markers. 

458 The interfaces should be within machine precision of eachother, but the vertices does not need to align. 

459 The condition created is :math:`u_s = u_m` where `s` is the restriction to the 

460 slave facets, `m` to the master facets. 

461 

462 Args: 

463 meshtags: The meshtags of the set of facets to tie together 

464 slave_marker: The marker of the slave facets 

465 master_marker: The marker of the master facets 

466 eps2: The tolerance for the squared distance between cells to be considered as a collision 

467 allow_missing_masters: If true, the function will not throw an error if a degree of freedom 

468 in the closure of the master entities does not have a corresponding set of slave degree 

469 of freedom. 

470 """ 

471 if isinstance(eps2, numpy.generic): # nanobind conversion of numpy dtypes to general Python types 

472 eps2 = eps2.item() # type: ignore 

473 mpc_data = dolfinx_mpc.cpp.mpc.create_contact_inelastic_condition( 

474 self.V._cpp_object, meshtags._cpp_object, slave_marker, master_marker, eps2, allow_missing_masters 

475 ) 

476 self.add_constraint_from_mpc_data(self.V, mpc_data) 

477 

478 @property 

479 def is_slave(self) -> numpy.ndarray: 

480 """ 

481 Returns a vector of integers where the ith entry indicates if a degree of freedom (local to process) is a slave. 

482 """ 

483 self._not_finalized() 

484 return self._cpp_object.is_slave 

485 

486 @property 

487 def slaves(self): 

488 """ 

489 Returns the degrees of freedom for all slaves local to process 

490 """ 

491 self._not_finalized() 

492 return self._cpp_object.slaves 

493 

494 @property 

495 def masters(self) -> _cpp.graph.AdjacencyList_int32: 

496 """ 

497 Returns an adjacency-list whose ith node corresponds to 

498 a degree of freedom (local to process), and links the corresponding master dofs (local to process). 

499 

500 Examples: 

501 

502 .. highlight:: python 

503 .. code-block:: python 

504 

505 masters = mpc.masters 

506 masters_of_dof_i = masters.links(i) 

507 """ 

508 self._not_finalized() 

509 return self._cpp_object.masters 

510 

511 def coefficients(self) -> _float_array_types: 

512 """ 

513 Returns a vector containing the coefficients for the constraint, and the corresponding offsets 

514 for the ith degree of freedom. 

515 

516 Examples: 

517 

518 .. highlight:: python 

519 .. code-block:: python 

520 

521 coeffs, offsets = mpc.coefficients() 

522 coeffs_of_slave_i = coeffs[offsets[i]:offsets[i+1]] 

523 """ 

524 self._not_finalized() 

525 return self._cpp_object.coefficients() 

526 

527 @property 

528 def num_local_slaves(self): 

529 """ 

530 Return the number of slaves owned by the current process. 

531 """ 

532 self._not_finalized() 

533 return self._cpp_object.num_local_slaves 

534 

535 @property 

536 def cell_to_slaves(self): 

537 """ 

538 Returns an `dolfinx.cpp.graph.AdjacencyList_int32` whose ith node corresponds to 

539 the ith cell (local to process), and links the corresponding slave degrees of 

540 freedom in the cell (local to process). 

541 

542 Examples: 

543 

544 .. highlight:: python 

545 .. code-block:: python 

546 

547 cell_to_slaves = mpc.cell_to_slaves() 

548 slaves_in_cell_i = cell_to_slaves.links(i) 

549 """ 

550 self._not_finalized() 

551 return self._cpp_object.cell_to_slaves 

552 

553 @property 

554 def function_space(self): 

555 """ 

556 Return the function space for the multi-point constraint with the updated index map 

557 """ 

558 self._not_finalized() 

559 return self.V 

560 

561 def backsubstitution(self, u: Union[_fem.Function, _PETSc.Vec]) -> None: # type: ignore 

562 """ 

563 For a Function, impose the multi-point constraint by backsubstiution. 

564 This function is used after solving the reduced problem to obtain the values 

565 at the slave degrees of freedom 

566 

567 .. note:: 

568 It is the users responsibility to destroy the PETSc vector 

569 

570 Args: 

571 u: The input function 

572 """ 

573 try: 

574 self._cpp_object.backsubstitution(u.x.array) 

575 u.x.scatter_forward() 

576 except AttributeError: 

577 with u.localForm() as vector_local: 

578 self._cpp_object.backsubstitution(vector_local.array_w) 

579 u.ghostUpdate(addv=_PETSc.InsertMode.INSERT, mode=_PETSc.ScatterMode.FORWARD) # type: ignore 

580 

581 def homogenize(self, u: _fem.Function) -> None: 

582 """ 

583 For a vector, homogenize (set to zero) the vector components at the multi-point 

584 constraint slave DoF indices. This is particularly useful for nonlinear problems. 

585 

586 Args: 

587 u: The input vector 

588 """ 

589 self._cpp_object.homogenize(u.x.array) 

590 u.x.scatter_forward() 

591 

592 def _already_finalized(self): 

593 """ 

594 Check if we have already finalized the multi point constraint 

595 """ 

596 if self.finalized: 

597 raise RuntimeError("MultiPointConstraint has already been finalized") 

598 

599 def _not_finalized(self): 

600 """ 

601 Check if we have finalized the multi point constraint 

602 """ 

603 if not self.finalized: 

604 raise RuntimeError("MultiPointConstraint has not been finalized")