v0.15.0
Loading...
Searching...
No Matches
adjoint.cpp
Go to the documentation of this file.
1/**
2 * @file adjoint.cpp
3 * @brief Topology optimization using adjoint method with Python objective functions
4 * @details This tutorial demonstrates:
5 * - Adjoint-based sensitivity analysis for structural topology optimization
6 * - Integration with Python for flexible objective function definition
7 * - Higher-order finite element analysis with geometry fields
8 * - Automatic differentiation using MoFEM's adjoint capabilities
9 * @author Anonymous author(s) committing under MIT license
10 * @example mofem/tutorials/vec-7/adjoint.cpp
11 *
12 */
13
14#include <boost/python.hpp>
15#include <boost/python/def.hpp>
16#include <boost/python/numpy.hpp>
17namespace bp = boost::python;
18namespace np = boost::python::numpy;
19
20#include <MoFEM.hpp>
21
22using namespace MoFEM;
23
24//! [Constants and material properties]
25constexpr int BASE_DIM = 1; ///< Dimension of the base functions
26
27//! [Define dimension]
28constexpr int SPACE_DIM =
29 EXECUTABLE_DIMENSION; ///< Space dimension of problem (2D or 3D), set at compile time
30
31//! [Define dimension]
32constexpr AssemblyType A = AssemblyType::PETSC; ///< Use PETSc for matrix/vector assembly
33constexpr IntegrationType I =
34 IntegrationType::GAUSS; ///< Use Gauss quadrature for integration
35
36//! [Material properties for linear elasticity]
37constexpr double young_modulus = 1; ///< Young's modulus E
38constexpr double poisson_ratio = 0.3; ///< Poisson's ratio ν
39constexpr double bulk_modulus_K = young_modulus / (3 * (1 - 2 * poisson_ratio)); ///< Bulk modulus K = E/(3(1-2ν))
40constexpr double shear_modulus_G = young_modulus / (2 * (1 + poisson_ratio)); ///< Shear modulus G = E/(2(1+ν))
41
42PetscBool is_plane_strain = PETSC_FALSE; ///< Flag for plane strain vs plane stress in 2D
43//! [Constants and material properties]
44
45//! [Define finite element types and operators]
46using EntData = EntitiesFieldData::EntData; ///< Entity data for field operations
49using DomainEleOp = DomainEle::UserDataOperator; ///< Domain element operators
50using BoundaryEleOp = BoundaryEle::UserDataOperator; ///< Boundary element operators
51//! [Define finite element types and operators]
52
53//! [Boundary condition types]
54struct DomainBCs {}; ///< Domain boundary conditions marker
55struct BoundaryBCs {}; ///< Boundary conditions marker
56
57//! [Natural boundary condition operators]
64
65template <int DIM> struct PostProcEleByDim;
66
67template <> struct PostProcEleByDim<2> {
71};
72
73template <> struct PostProcEleByDim<3> {
77};
78
82
83//! [Adjoint operator declarations]
84/// Forward declaration of adjoint test operator for geometric sensitivity
85template <int SPACE_DIM, IntegrationType I, typename OpBase>
87
88/// Forward declaration of operator for gradient times symmetric tensor operations
89template <int SPACE_DIM, IntegrationType I, typename OpBase>
91//! [Adjoint operator declarations]
92
93#include <ElasticSpring.hpp>
94#include <FluidLevel.hpp>
95#include <CalculateTraction.hpp>
96#include <NaturalDomainBC.hpp>
97#include <NaturalBoundaryBC.hpp>
98#include <HookeOps.hpp>
99
100/**
101 * @brief Abstract interface for Python-defined objective functions
102 *
103 * This interface allows users to define custom objective functions in Python
104 * that can be called from C++ during the optimization process. The objective
105 * function and its gradients are evaluated at integration points using:
106 * - Coordinates of integration points
107 * - Displacement field values
108 * - Stress and strain tensors
109 *
110 * Key methods:
111 * - evalObjectiveFunction: Compute f(x,u,σ,ε)
112 * - evalObjectiveGradientStress: Compute ∂f/∂σ
113 * - evalObjectiveGradientStrain: Compute ∂f/∂ε
114 * - numberOfModes: Return number of topology modes for optimization
115 * - blockModes: Define spatial topology modes for shape optimization
116 */
118
119 /// Evaluate objective function f(coords, u, stress, strain)
120 virtual MoFEMErrorCode
122 boost::shared_ptr<MatrixDouble> u_ptr,
123 boost::shared_ptr<MatrixDouble> stress_ptr,
124 boost::shared_ptr<MatrixDouble> strain_ptr,
125 boost::shared_ptr<VectorDouble> o_ptr) = 0;
126
127 /// Evaluate gradient of objective function w.r.t. stress: ∂f/∂σ
128 virtual MoFEMErrorCode
130 boost::shared_ptr<MatrixDouble> u_ptr,
131 boost::shared_ptr<MatrixDouble> stress_ptr,
132 boost::shared_ptr<MatrixDouble> strain_ptr,
133 boost::shared_ptr<MatrixDouble> o_ptr) = 0;
134
135 /// Evaluate gradient of objective function w.r.t. strain: ∂f/∂ε
136 virtual MoFEMErrorCode
138 boost::shared_ptr<MatrixDouble> u_ptr,
139 boost::shared_ptr<MatrixDouble> stress_ptr,
140 boost::shared_ptr<MatrixDouble> strain_ptr,
141 boost::shared_ptr<MatrixDouble> o_ptr) = 0;
142
143 /// Return number of topology optimization modes for given block
144 virtual MoFEMErrorCode numberOfModes(int block_id, int &modes) = 0;
145
146 /// Define spatial modes for topology optimization
147 virtual MoFEMErrorCode blockModes(int block_id, MatrixDouble &coords,
148 std::array<double, 3> &centroid,
149 std::array<double, 6> &bbox,
150 MatrixDouble &o_ptr) = 0;
151
152 virtual ~ObjectiveFunctionData() = default;
153};
154
155boost::shared_ptr<ObjectiveFunctionData>
156create_python_objective_function(std::string py_file);
157
159 const std::string block_name, int dim);
160
161MoFEMErrorCode save_range(moab::Interface &moab, const std::string name,
162 const Range r);
163/**
164 * @brief Main class for topology optimization using adjoint method
165 *
166 * This class implements a complete topology optimization workflow:
167 * 1. Setup finite element problems for elasticity and adjoint analysis
168 * 2. Define topology modes for design parametrization
169 * 3. Use TAO optimization library to minimize Python-defined objective function
170 * 4. Compute sensitivities using adjoint method for efficient gradient calculation
171 *
172 * The optimization process alternates between:
173 * - Forward analysis: solve elastic problem for current geometry
174 * - Adjoint analysis: solve adjoint problem for objective function gradients
175 * - Geometry update: modify design using optimization algorithm (L-BFGS)
176 */
177struct Example {
178
179 Example(MoFEM::Interface &m_field) : mField(m_field) {}
180
181 /// Main driver function for the optimization process
183
184private:
185 MoFEM::Interface &mField; ///< Reference to MoFEM interface
186
187 boost::shared_ptr<MatrixDouble> vectorFieldPtr = nullptr; ///< Field values at evaluation points
188
189 // Problem setup methods
190 MoFEMErrorCode readMesh(); ///< Read mesh from file and setup meshsets
191 MoFEMErrorCode setupProblem(); ///< Setup fields, approximation spaces and DOFs
192 MoFEMErrorCode setupAdJoint(); ///< Setup adjoint fields and finite elements
193 MoFEMErrorCode boundaryCondition(); ///< Apply essential boundary conditions
194 MoFEMErrorCode topologyModes(); ///< Compute topology optimization modes
195 MoFEMErrorCode assembleSystem(); ///< Setup operators in finite element pipeline
196
197 // Analysis methods
198 MoFEMErrorCode solveElastic(); ///< Solve forward elastic problem
199 MoFEMErrorCode postprocessElastic(int iter, SmartPetscObj<Vec> adjoint_vector = nullptr); ///< Post-process and output results
200
201 /// Calculate objective function gradient using adjoint method
202 MoFEMErrorCode calculateGradient(PetscReal *objective_function_value,
203 Vec objective_function_gradient,
204 Vec adjoint_vector);
205
206 // Problem configuration
207 FieldApproximationBase base; ///< Choice of finite element basis functions
208 int fieldOrder = 2; ///< Polynomial order for approximation
209
210 // Solver and data management
211 SmartPetscObj<KSP> kspElastic; ///< Linear solver for elastic problem
212 SmartPetscObj<DM> adjointDM; ///< Data manager for adjoint problem
213 boost::shared_ptr<ObjectiveFunctionData> pythonPtr; ///< Interface to Python objective function
214
215 // Topology optimization data
216 std::vector<SmartPetscObj<Vec>> modeVecs; ///< Topology mode vectors (design variables)
217 std::vector<std::array<double, 3>> modeCentroids; ///< Centroids of optimization blocks
218 std::vector<std::array<double, 6>> modeBBoxes; ///< Bounding boxes of optimization blocks
219 SmartPetscObj<Vec> initialGeometry; ///< Initial geometry field
220};
221
222//! [Run topology optimization problem]
223/**
224 * @brief Main driver for topology optimization using adjoint sensitivity analysis
225 *
226 * This function orchestrates the complete topology optimization workflow:
227 * 1. Initialize Python objective function interface
228 * 2. Setup finite element problems (forward and adjoint)
229 * 3. Compute initial elastic solution
230 * 4. Generate topology optimization modes
231 * 5. Run TAO optimization loop with adjoint-based gradients
232 *
233 * The optimization uses TAO (Toolkit for Advanced Optimization) with L-BFGS
234 * algorithm. At each iteration:
235 * - Update geometry based on current design variables
236 * - Solve forward elastic problem
237 * - Compute objective function and gradients using adjoint method
238 * - Post-process results
239 *
240 * @return MoFEMErrorCode Success or error code
241 */
244
245 // Read objective function from Python file
246 char objective_function_file_name[255] = "objective_function.py";
248 PETSC_NULLPTR, PETSC_NULLPTR, "-objective_function",
249 objective_function_file_name, 255, PETSC_NULLPTR);
250
251 // Verify that the Python objective function file exists
252 auto file_exists = [](std::string myfile) {
253 std::ifstream file(myfile.c_str());
254 if (file) {
255 return true;
256 }
257 return false;
258 };
259 if (!file_exists(objective_function_file_name)) {
260 MOFEM_LOG("WORLD", Sev::error) << "Objective function file NOT found: "
261 << objective_function_file_name;
262 CHK_THROW_MESSAGE(MOFEM_NOT_FOUND, "file NOT found");
263 }
264
265 // Create Python interface for objective function
266 pythonPtr = create_python_objective_function(objective_function_file_name);
267
268 // Setup finite element problems
269 CHKERR readMesh(); // Read mesh and meshsets
270 CHKERR setupProblem(); // Setup displacement field and geometry field
271 CHKERR setupAdJoint(); // Setup adjoint field for sensitivity analysis
272 CHKERR boundaryCondition(); // Apply essential boundary conditions
273 CHKERR assembleSystem(); // Setup finite element operators
274
275 // Create linear solver for elastic problem
276 auto pip = mField.getInterface<PipelineManager>();
277 kspElastic = pip->createKSP();
278 CHKERR KSPSetFromOptions(kspElastic);
279
280 // Solve initial elastic problem
282 CHKERR postprocessElastic(-1); // Post-process initial solution
283
284 auto create_vec_modes = [&](auto block_name) {
286 auto mesh_mng = mField.getInterface<MeshsetsManager>();
287 auto bcs = mesh_mng->getCubitMeshsetPtr(
288
289 std::regex((boost::format("%s(.*)") % block_name).str())
290
291 );
292
293 int nb_total_modes = 0;
294 for (auto &bc : bcs) {
295 auto id = bc->getMeshsetId();
296 int nb_modes;
297 CHKERR pythonPtr->numberOfModes(id, nb_modes);
298 nb_total_modes += nb_modes;
299 }
300
301 MOFEM_LOG("WORLD", Sev::inform)
302 << "Total number of modes to apply: " << nb_total_modes;
303
304 modeVecs.resize(nb_total_modes);
305
307 };
308
309 auto get_modes_bounding_boxes = [&](auto block_name) {
311 auto mesh_mng = mField.getInterface<MeshsetsManager>();
312 auto bcs = mesh_mng->getCubitMeshsetPtr(
313
314 std::regex((boost::format("%s(.*)") % block_name).str())
315
316 );
317
318 for (auto &bc : bcs) {
319 auto meshset = bc->getMeshset();
320 Range ents;
321 CHKERR mField.get_moab().get_entities_by_handle(meshset, ents, true);
322 Range verts;
323 CHKERR mField.get_moab().get_connectivity(ents, verts, false);
324 std::vector<double> x(verts.size());
325 std::vector<double> y(verts.size());
326 std::vector<double> z(verts.size());
327 CHKERR mField.get_moab().get_coords(verts, x.data(), y.data(), z.data());
328 std::array<double, 3> centroid = {0, 0, 0};
329 for (int i = 0; i != verts.size(); ++i) {
330 centroid[0] += x[i];
331 centroid[1] += y[i];
332 centroid[2] += z[i];
333 }
334 MPI_Allreduce(MPI_IN_PLACE, centroid.data(), 3, MPI_DOUBLE, MPI_SUM,
335 mField.get_comm());
336 int nb_vertex = verts.size();
337 MPI_Allreduce(MPI_IN_PLACE, &nb_vertex, 1, MPI_INT, MPI_SUM,
338 mField.get_comm());
339 if (nb_vertex) {
340 centroid[0] /= nb_vertex;
341 centroid[1] /= nb_vertex;
342 centroid[2] /= nb_vertex;
343 }
344 std::array<double, 6> bbox = {centroid[0], centroid[1], centroid[2],
345 centroid[0], centroid[1], centroid[2]};
346 for (int i = 0; i != verts.size(); ++i) {
347 bbox[0] = std::min(bbox[0], x[i]);
348 bbox[1] = std::min(bbox[1], y[i]);
349 bbox[2] = std::min(bbox[2], z[i]);
350 bbox[3] = std::max(bbox[3], x[i]);
351 bbox[4] = std::max(bbox[4], y[i]);
352 bbox[5] = std::max(bbox[5], z[i]);
353 }
354 MPI_Allreduce(MPI_IN_PLACE, &bbox[0], 3, MPI_DOUBLE, MPI_MIN,
355 mField.get_comm());
356 MPI_Allreduce(MPI_IN_PLACE, &bbox[3], 3, MPI_DOUBLE, MPI_MAX,
357 mField.get_comm());
358
359 MOFEM_LOG("WORLD", Sev::inform)
360 << "Block: " << bc->getName() << " centroid: " << centroid[0] << " "
361 << centroid[1] << " " << centroid[2];
362 MOFEM_LOG("WORLD", Sev::inform)
363 << "Block: " << bc->getName() << " bbox: " << bbox[0] << " "
364 << bbox[1] << " " << bbox[2] << " " << bbox[3] << " " << bbox[4]
365 << " " << bbox[5];
366
367 modeCentroids.push_back(centroid);
368 modeBBoxes.push_back(bbox);
369 }
370
372 };
373
374 auto eval_objective_and_gradient = [](Tao tao, Vec x, PetscReal *f, Vec g,
375 void *ctx) -> PetscErrorCode {
377 auto *ex_ptr = (Example *)ctx;
378
379 int iter;
380 CHKERR TaoGetIterationNumber(tao, &iter);
381
382 auto set_geometry = [&](Vec x) {
384
385 VecScatter ctx;
386 Vec x_local;
387 CHKERR VecScatterCreateToAll(x, &ctx, &x_local);
388 // scatter as many times as you need
389 CHKERR VecScatterBegin(ctx, x, x_local, INSERT_VALUES, SCATTER_FORWARD);
390 CHKERR VecScatterEnd(ctx, x, x_local, INSERT_VALUES, SCATTER_FORWARD);
391 // destroy scatter context and local vector when no longer needed
392 CHKERR VecScatterDestroy(&ctx);
393
394 auto current_geometry = vectorDuplicate(ex_ptr->initialGeometry);
395 CHKERR VecCopy(ex_ptr->initialGeometry, current_geometry);
396 const double *a;
397 CHKERR VecGetArrayRead(x_local, &a);
398 const double *coeff = a;
399 for (auto &mode_vec : ex_ptr->modeVecs) {
400 MOFEM_LOG("WORLD", Sev::verbose)
401 << "Adding mode with coeff: " << *coeff;
402 CHKERR VecAXPY(current_geometry, (*coeff), mode_vec);
403 ++coeff;
404 }
405 CHKERR VecRestoreArrayRead(x_local, &a);
406 CHKERR VecGhostUpdateBegin(current_geometry, INSERT_VALUES,
407 SCATTER_FORWARD);
408 CHKERR VecGhostUpdateEnd(current_geometry, INSERT_VALUES,
409 SCATTER_FORWARD);
410 CHKERR ex_ptr->mField.getInterface<VecManager>()
411 ->setOtherLocalGhostVector("ADJOINT", "ADJOINT_FIELD", "GEOMETRY",
412 RowColData::ROW, current_geometry,
413 INSERT_VALUES, SCATTER_REVERSE);
414
415 CHKERR VecDestroy(&x_local);
417 };
418
419 CHKERR set_geometry(x);
420 CHKERR KSPReset(ex_ptr->kspElastic);
421 CHKERR ex_ptr->solveElastic();
422 auto simple = ex_ptr->mField.getInterface<Simple>();
423 auto adjoint_vector = createDMVector(simple->getDM());
424 CHKERR ex_ptr->calculateGradient(f, g, adjoint_vector);
425 CHKERR ex_ptr->postprocessElastic(iter, adjoint_vector);
426
428 };
429
430 // Create topology optimization modes and setup TAO solver
431 CHKERR create_vec_modes("OPTIMISE");
432 CHKERR get_modes_bounding_boxes("OPTIMISE");
433
434 /**
435 * Setup TAO (Toolkit for Advanced Optimization) solver for topology optimization
436 *
437 * TAO provides various optimization algorithms. Here we use TAOLMVM (Limited Memory
438 * Variable Metric) which is a quasi-Newton method suitable for unconstrained
439 * optimization with gradient information.
440 *
441 * The optimization variables are coefficients for the topology modes, and
442 * gradients are computed using the adjoint method for efficiency.
443 */
444 auto tao = createTao(mField.get_comm());
445 CHKERR TaoSetType(tao, TAOLMVM);
446
447 auto rank = mField.get_comm_rank();
448 auto g = createVectorMPI(mField.get_comm(), (!rank) ? modeVecs.size() : 0,
449 PETSC_DECIDE);
450 CHKERR TaoSetObjectiveAndGradient(tao, g, eval_objective_and_gradient,
451 (void *)this);
452
453 // Store initial geometry for reference during optimization
455 CHKERR mField.getInterface<VecManager>()->setOtherLocalGhostVector(
456 "ADJOINT", "ADJOINT_FIELD", "GEOMETRY", RowColData::ROW, initialGeometry,
457 INSERT_VALUES, SCATTER_FORWARD);
458
459 // Setup topology optimization modes
460 /**
461 * Generate modes for topology optimization design parameterization
462 *
463 * These modes represent perturbations to the geometry that can be used
464 * as design variables in topology optimization. The modes are defined
465 * through the Python interface and provide spatial basis functions
466 * for shape modifications.
467 */
469
470 // Initialize optimization variables and run solver
471 /**
472 * Start optimization with zero initial guess for design variables
473 *
474 * The TAO solver will iteratively:
475 * 1. Evaluate objective function at current design point
476 * 2. Compute gradients using adjoint sensitivity analysis
477 * 3. Update design variables using L-BFGS algorithm
478 * 4. Check convergence criteria
479 *
480 * Each function evaluation involves solving the forward elastic problem
481 * and the adjoint problem to compute sensitivities efficiently.
482 */
483 auto x0 = vectorDuplicate(g);
484
485 CHKERR VecSet(x0, 0.0);
486 CHKERR TaoSetSolution(tao, x0);
487 CHKERR TaoSetFromOptions(tao);
488 CHKERR TaoSolve(tao);
489
490 // Optimization complete - results available in solution vectors
491 MOFEM_LOG("WORLD", Sev::inform) << "Topology optimization completed";
492
494}
495//! [Run problem]
496
497//! [Read mesh]
498/**
499 * @brief Read mesh from file and setup material/boundary condition meshsets
500 *
501 * This function loads the finite element mesh from file and processes
502 * associated meshsets that define material properties and boundary conditions.
503 * The mesh is typically generated using CUBIT and exported in .h5m format.
504 *
505 * Meshsets are used to group elements/faces by:
506 * - Material properties (for different material blocks)
507 * - Boundary conditions (for applying loads and constraints)
508 * - Optimization regions (for topology optimization)
509 *
510 * @return MoFEMErrorCode Success or error code
511 */
515 CHKERR simple->getOptions(); // Read command line options
516 CHKERR simple->loadFile(); // Load mesh from file
517 // Add meshsets if config file provided
518 CHKERR mField.getInterface<MeshsetsManager>()->setMeshsetFromFile();
520}
521//! [Read mesh]
522
523//! [Set up problem]
524/**
525 * @brief Setup finite element fields, approximation spaces and degrees of freedom
526 *
527 * This function configures the finite element problem by:
528 * 1. Setting up the displacement field "U" with vector approximation
529 * 2. Setting up the geometry field "GEOMETRY" for mesh deformation
530 * 3. Defining polynomial approximation order and basis functions
531 * 4. Creating degrees of freedom on mesh entities
532 *
533 * The displacement field uses H1 vector space for standard elasticity.
534 * The geometry field allows mesh modification during topology optimization.
535 * Different basis functions (Ainsworth-Legendre vs Demkowicz) can be selected.
536 *
537 * @return MoFEMErrorCode Success or error code
538 */
542
543 // Select basis functions for finite element approximation
544 enum bases { AINSWORTH, DEMKOWICZ, LASBASETOPT };
545 const char *list_bases[LASBASETOPT] = {"ainsworth", "demkowicz"};
546 PetscInt choice_base_value = AINSWORTH;
547 CHKERR PetscOptionsGetEList(PETSC_NULLPTR, NULL, "-base", list_bases,
548 LASBASETOPT, &choice_base_value, PETSC_NULLPTR);
549
550 switch (choice_base_value) {
551 case AINSWORTH:
553 MOFEM_LOG("WORLD", Sev::inform)
554 << "Set AINSWORTH_LEGENDRE_BASE for displacements";
555 break;
556 case DEMKOWICZ:
558 MOFEM_LOG("WORLD", Sev::inform)
559 << "Set DEMKOWICZ_JACOBI_BASE for displacements";
560 break;
561 default:
562 base = LASTBASE;
563 break;
564 }
565
566 // Add finite element fields
567 /**
568 * Setup displacement field "U" - the primary unknown in elasticity
569 * This field represents displacement vector at each node/DOF
570 */
571 CHKERR simple->addDomainField("U", H1, base, SPACE_DIM);
572 CHKERR simple->addBoundaryField("U", H1, base, SPACE_DIM);
573 CHKERR PetscOptionsGetInt(PETSC_NULLPTR, "", "-order", &fieldOrder,
574 PETSC_NULLPTR);
575
576 /**
577 * Setup geometry field "GEOMETRY" - used for mesh deformation in optimization
578 * This field stores current nodal coordinates and can be modified
579 * during topology optimization to represent design changes
580 */
581 CHKERR simple->addDataField("GEOMETRY", H1, base, SPACE_DIM);
582
583 // Set polynomial approximation order for both fields
584 CHKERR simple->setFieldOrder("U", fieldOrder);
585 CHKERR simple->setFieldOrder("GEOMETRY", fieldOrder);
586 CHKERR simple->setUp();
587
588 // Project higher-order geometry representation onto geometry field
589 /**
590 * For higher-order elements, this projects the exact geometry
591 * onto the geometry field to maintain curved boundaries accurately
592 */
593 auto project_ho_geometry = [&]() {
594 Projection10NodeCoordsOnField ent_method(mField, "GEOMETRY");
595 return mField.loop_dofs("GEOMETRY", ent_method);
596 };
597 CHKERR project_ho_geometry();
598
599 // Check if plane strain assumption should be used
600 CHKERR PetscOptionsGetBool(PETSC_NULLPTR, "", "-plane_strain",
601 &is_plane_strain, PETSC_NULLPTR);
602
604}
605//! [Set up problem]
606
607//! [Setup adjoint]
608/**
609 * @brief Setup adjoint fields and finite elements for sensitivity analysis
610 *
611 * The adjoint method is used to efficiently compute gradients of the objective
612 * function with respect to design variables. This function sets up:
613 *
614 * 1. ADJOINT_FIELD - stores adjoint variables (Lagrange multipliers)
615 * 2. ADJOINT_DM - data manager for adjoint problem
616 * 3. Adjoint finite elements for domain and boundary
617 *
618 * The adjoint equation is: K^T * λ = ∂f/∂u
619 * where λ are adjoint variables, K is stiffness matrix, f is objective
620 *
621 * @return MoFEMErrorCode Success or error code
622 */
626
627 // Create adjoint data manager and field
628 auto create_adjoint_dm = [&]() {
629 auto adjoint_dm = createDM(mField.get_comm(), "DMMOFEM");
630
631 auto add_field = [&]() {
633 CHKERR mField.add_field("ADJOINT_FIELD", H1, base, SPACE_DIM);
635 "ADJOINT_FIELD");
636 for (auto tt = MBEDGE; tt <= moab::CN::TypeDimensionMap[SPACE_DIM].second;
637 ++tt)
638 CHKERR mField.set_field_order(simple->getMeshset(), tt, "ADJOINT_FIELD",
639 fieldOrder);
640 CHKERR mField.set_field_order(simple->getMeshset(), MBVERTEX,
641 "ADJOINT_FIELD", 1);
644 };
645
646 auto add_adjoint_fe_impl = [&]() {
648 CHKERR mField.add_finite_element("ADJOINT_DOMAIN_FE");
650 "ADJOINT_FIELD");
652 "ADJOINT_FIELD");
654 "ADJOINT_FIELD");
656 "GEOMETRY");
658 simple->getMeshset(), SPACE_DIM, "ADJOINT_DOMAIN_FE");
659 CHKERR mField.build_finite_elements("ADJOINT_DOMAIN_FE");
660
661 CHKERR mField.add_finite_element("ADJOINT_BOUNDARY_FE");
663 "ADJOINT_FIELD");
665 "ADJOINT_FIELD");
667 "ADJOINT_FIELD");
669 "GEOMETRY");
670
671 auto block_name = "OPTIMISE";
672 auto mesh_mng = mField.getInterface<MeshsetsManager>();
673 auto bcs = mesh_mng->getCubitMeshsetPtr(
674
675 std::regex((boost::format("%s(.*)") % block_name).str())
676
677 );
678
679 for (auto bc : bcs) {
681 bc->getMeshset(), SPACE_DIM - 1, "ADJOINT_BOUNDARY_FE");
682 }
683
684 CHKERR mField.build_finite_elements("ADJOINT_BOUNDARY_FE");
685
686 CHKERR mField.build_adjacencies(simple->getBitRefLevel(),
687 simple->getBitRefLevelMask());
688
690 };
691
692 auto set_adjoint_dm_imp = [&]() {
694 CHKERR DMMoFEMCreateMoFEM(adjoint_dm, &mField, "ADJOINT",
695 simple->getBitRefLevel(),
696 simple->getBitRefLevelMask());
697 CHKERR DMMoFEMSetDestroyProblem(adjoint_dm, PETSC_TRUE);
698 CHKERR DMSetFromOptions(adjoint_dm);
699 CHKERR DMMoFEMAddElement(adjoint_dm, "ADJOINT_DOMAIN_FE");
700 CHKERR DMMoFEMAddElement(adjoint_dm, "ADJOINT_BOUNDARY_FE");
701 CHKERR DMMoFEMSetSquareProblem(adjoint_dm, PETSC_TRUE);
702 CHKERR DMMoFEMSetIsPartitioned(adjoint_dm, PETSC_TRUE);
703 mField.getInterface<ProblemsManager>()->buildProblemFromFields =
704 PETSC_TRUE;
705 CHKERR DMSetUp(adjoint_dm);
706 mField.getInterface<ProblemsManager>()->buildProblemFromFields =
707 PETSC_FALSE;
709 };
710
711 CHK_THROW_MESSAGE(add_field(), "add adjoint field");
712 CHK_THROW_MESSAGE(add_adjoint_fe_impl(), "add adjoint fe");
713 CHK_THROW_MESSAGE(set_adjoint_dm_imp(), "set adjoint dm");
714
715 return adjoint_dm;
716 };
717
718 adjointDM = create_adjoint_dm();
719
721}
722//
723
724//! [Boundary condition]
728 auto bc_mng = mField.getInterface<BcManager>();
729
730 CHKERR bc_mng->removeBlockDOFsOnEntities(simple->getProblemName(), "REMOVE_X",
731 "U", 0, 0);
732 CHKERR bc_mng->removeBlockDOFsOnEntities(simple->getProblemName(), "REMOVE_Y",
733 "U", 1, 1);
734 CHKERR bc_mng->removeBlockDOFsOnEntities(simple->getProblemName(), "REMOVE_Z",
735 "U", 2, 2);
736 CHKERR bc_mng->removeBlockDOFsOnEntities(simple->getProblemName(),
737 "REMOVE_ALL", "U", 0, 3);
738 CHKERR bc_mng->pushMarkDOFsOnEntities<DisplacementCubitBcData>(
739 simple->getProblemName(), "U");
740
742}
743//! [Boundary condition]
744
745//! [Adjoint modes]
748
749 auto opt_ents = get_range_from_block(mField, "OPTIMISE", SPACE_DIM - 1);
750 auto subset_dm_bdy = createDM(mField.get_comm(), "DMMOFEM");
751 CHKERR DMMoFEMSetSquareProblem(subset_dm_bdy, PETSC_TRUE);
752 CHKERR DMMoFEMCreateSubDM(subset_dm_bdy, adjointDM, "SUBSET_BDY");
753 CHKERR DMMoFEMAddElement(subset_dm_bdy, "ADJOINT_BOUNDARY_FE");
754 CHKERR DMMoFEMAddSubFieldRow(subset_dm_bdy, "ADJOINT_FIELD",
755 boost::make_shared<Range>(opt_ents));
756 CHKERR DMMoFEMAddSubFieldCol(subset_dm_bdy, "ADJOINT_FIELD",
757 boost::make_shared<Range>(opt_ents));
758 CHKERR DMSetUp(subset_dm_bdy);
759
760 auto subset_dm_domain = createDM(mField.get_comm(), "DMMOFEM");
761 CHKERR DMMoFEMSetSquareProblem(subset_dm_domain, PETSC_TRUE);
762 CHKERR DMMoFEMCreateSubDM(subset_dm_domain, adjointDM, "SUBSET_DOMAIN");
763 CHKERR DMMoFEMAddElement(subset_dm_domain, "ADJOINT_DOMAIN_FE");
764 CHKERR DMMoFEMAddSubFieldRow(subset_dm_domain, "ADJOINT_FIELD");
765 CHKERR DMMoFEMAddSubFieldCol(subset_dm_domain, "ADJOINT_FIELD");
766 CHKERR DMSetUp(subset_dm_domain);
767
768 // remove dofs on boundary of the domain
769 auto remove_dofs = [&]() {
771
772 std::array<Range, 3> remove_dim_ents;
773 remove_dim_ents[0] =
774 get_range_from_block(mField, "OPT_REMOVE_X", SPACE_DIM - 1);
775 remove_dim_ents[1] =
776 get_range_from_block(mField, "OPT_REMOVE_Y", SPACE_DIM - 1);
777 remove_dim_ents[2] =
778 get_range_from_block(mField, "OPT_REMOVE_Z", SPACE_DIM - 1);
779
780 for (int d = 0; d != 3; ++d) {
781 MOFEM_LOG("WORLD", Sev::inform)
782 << "Removing topology modes on block OPT_REMOVE_" << (char)('X' + d)
783 << " with " << remove_dim_ents[d].size() << " entities";
784 }
785
786 Range body_ents;
787 CHKERR mField.get_moab().get_entities_by_dimension(0, SPACE_DIM, body_ents,
788 true);
789 auto skin = moab::Skinner(&mField.get_moab());
790 Range boundary_ents;
791 CHKERR skin.find_skin(0, body_ents, false, boundary_ents);
792 for (int d = 0; d != 3; ++d) {
793 boundary_ents = subtract(boundary_ents, remove_dim_ents[d]);
794 }
795 ParallelComm *pcomm =
796 ParallelComm::get_pcomm(&mField.get_moab(), MYPCOMM_INDEX);
797 CHKERR pcomm->filter_pstatus(boundary_ents,
798 PSTATUS_SHARED | PSTATUS_MULTISHARED,
799 PSTATUS_NOT, -1, nullptr);
800 for (auto d = SPACE_DIM - 2; d >= 0; --d) {
801 if (d >= 0) {
802 Range ents;
803 CHKERR mField.get_moab().get_adjacencies(boundary_ents, d, false, ents,
804 moab::Interface::UNION);
805 boundary_ents.merge(ents);
806 } else {
807 Range verts;
808 CHKERR mField.get_moab().get_connectivity(boundary_ents, verts);
809 boundary_ents.merge(verts);
810 }
811 CHKERR mField.getInterface<CommInterface>()->synchroniseEntities(
812 boundary_ents);
813 }
814 boundary_ents.merge(opt_ents);
815 CHKERR mField.getInterface<ProblemsManager>()->removeDofsOnEntities(
816 "SUBSET_DOMAIN", "ADJOINT_FIELD", boundary_ents);
817 for (int d = 0; d != 3; ++d) {
818 CHKERR mField.getInterface<ProblemsManager>()->removeDofsOnEntities(
819 "SUBSET_DOMAIN", "ADJOINT_FIELD", remove_dim_ents[d], d, d);
820 }
821
822 // #ifndef NDEBUG
823 if (mField.get_comm_rank() == 0) {
824 CHKERR save_range(mField.get_moab(), "topoMode_boundary_ents.vtk",
825 boundary_ents);
826 }
827 // #endif
828
830 };
831
832 CHKERR remove_dofs();
833
834 auto get_lhs_fe = [&]() {
835 auto fe_lhs = boost::make_shared<BoundaryEle>(mField);
836 fe_lhs->getRuleHook = [](int, int, int p_data) {
837 return 2 * p_data + p_data - 1;
838 };
839 auto &pip = fe_lhs->getOpPtrVector();
841 "GEOMETRY");
844 pip.push_back(new OpMass("ADJOINT_FIELD", "ADJOINT_FIELD",
845 [](double, double, double) { return 1.; }));
846 return fe_lhs;
847 };
848
849 auto get_rhs_fe = [&]() {
850 auto fe_rhs = boost::make_shared<BoundaryEle>(mField);
851 fe_rhs->getRuleHook = [](int, int, int p_data) {
852 return 2 * p_data + p_data - 1;
853 };
854 auto &pip = fe_rhs->getOpPtrVector();
856 "GEOMETRY");
857
858 return fe_rhs;
859 };
860
861 auto block_name = "OPTIMISE";
862 auto mesh_mng = mField.getInterface<MeshsetsManager>();
863 auto bcs = mesh_mng->getCubitMeshsetPtr(
864
865 std::regex((boost::format("%s(.*)") % block_name).str())
866
867 );
868
869 for (auto &v : modeVecs) {
870 v = createDMVector(subset_dm_bdy);
871 }
872
874 struct OpMode : public OP {
875 OpMode(const std::string name,
876 boost::shared_ptr<ObjectiveFunctionData> python_ptr, int id,
877 std::vector<SmartPetscObj<Vec>> mode_vecs,
878 std::vector<std::array<double, 3>> mode_centroids,
879 std::vector<std::array<double, 6>> mode_bboxes, int block_counter,
880 int mode_counter, boost::shared_ptr<Range> range = nullptr)
881 : OP(name, name, OP::OPROW, range), pythonPtr(python_ptr), iD(id),
882 modeVecs(mode_vecs), modeCentroids(mode_centroids),
883 modeBboxes(mode_bboxes), blockCounter(block_counter),
884 modeCounter(mode_counter) {}
885
886 MoFEMErrorCode doWork(int side, EntityType type, EntData &data) {
888
889 if (OP::entsPtr) {
890 if (OP::entsPtr->find(this->getFEEntityHandle()) == OP::entsPtr->end())
892 }
893
894 auto nb_rows = data.getIndices().size();
895 if (!nb_rows) {
897 }
898 auto nb_base_functions = data.getN().size2();
899
901 CHKERR pythonPtr->blockModes(iD, OP::getCoordsAtGaussPts(),
902 modeCentroids[blockCounter],
903 modeBboxes[blockCounter], blockModes);
904
905 auto nb_integration_pts = getGaussPts().size2();
906 if (blockModes.size2() != 3 * nb_integration_pts) {
907 MOFEM_LOG("WORLD", Sev::error)
908 << "Number of modes does not match number of integration points: "
909 << blockModes.size2() << "!=" << 3 * nb_integration_pts;
910 CHK_THROW_MESSAGE(MOFEM_DATA_INCONSISTENCY, "modes/integration points");
911 }
912
913 VectorDouble nf(nb_rows);
914
915 int nb_modes = blockModes.size1();
916 for (auto mode = 0; mode != nb_modes; ++mode) {
917 nf.clear();
918 // get mode
919 auto t_mode = getFTensor1FromPtr<3>(&blockModes(mode, 0));
920 // get element volume
921 const double vol = OP::getMeasure();
922 // get integration weights
923 auto t_w = OP::getFTensor0IntegrationWeight();
924 // get base function gradient on rows
925 auto t_base = data.getFTensor0N();
926 // loop over integration points
927 for (int gg = 0; gg != nb_integration_pts; gg++) {
928
929 // take into account Jacobian
930 const double alpha = t_w * vol;
931 // loop over rows base functions
932 auto t_nf = getFTensor1FromPtr<SPACE_DIM>(nf.data().data());
933 int rr = 0;
934 for (; rr != nb_rows / SPACE_DIM; ++rr) {
935 t_nf(i) += alpha * t_base * t_mode(i);
936 ++t_base;
937 ++t_nf;
938 }
939 for (; rr < nb_base_functions; ++rr)
940 ++t_base;
941 ++t_w; // move to another integration weight
942 ++t_mode; // move to another mode
943 }
944 Vec vec = modeVecs[modeCounter + mode];
945 auto size = data.getIndices().size();
946 auto *indices = data.getIndices().data().data();
947 auto *nf_data = nf.data().data();
948 CHKERR VecSetValues(vec, size, indices, nf_data, ADD_VALUES);
949 }
950
952 }
953
954 private:
955 boost::shared_ptr<ObjectiveFunctionData> pythonPtr;
956 MatrixDouble blockModes;
957 std::vector<std::array<double, 3>> modeCentroids;
958 std::vector<std::array<double, 6>> modeBboxes;
959 int iD;
960 std::vector<SmartPetscObj<Vec>> modeVecs;
961 int blockCounter;
962 int modeCounter;
963 };
964
965 auto solve_bdy = [&]() {
967
968 auto fe_lhs = get_lhs_fe();
969 auto fe_rhs = get_rhs_fe();
970 int block_counter = 0;
971 int mode_counter = 0;
972 for (auto &bc : bcs) {
973 auto id = bc->getMeshsetId();
974 Range ents;
975 CHKERR mField.get_moab().get_entities_by_handle(bc->getMeshset(), ents,
976 true);
977 auto range = boost::make_shared<Range>(ents);
978 auto &pip_rhs = fe_rhs->getOpPtrVector();
979 pip_rhs.push_back(new OpMode("ADJOINT_FIELD", pythonPtr, id, modeVecs,
980 modeCentroids, modeBBoxes, block_counter,
981 mode_counter, range));
982 CHKERR DMoFEMLoopFiniteElements(subset_dm_bdy, "ADJOINT_BOUNDARY_FE",
983 fe_rhs);
984 pip_rhs.pop_back();
985 int nb_modes;
986 CHKERR pythonPtr->numberOfModes(id, nb_modes);
987 ++block_counter;
988 mode_counter += nb_modes;
989 MOFEM_LOG("WORLD", Sev::inform)
990 << "Setting mode block block: " << bc->getName()
991 << " with ID: " << bc->getMeshsetId()
992 << " total modes: " << mode_counter;
993 }
994
995 for (auto &v : modeVecs) {
996 CHKERR VecAssemblyBegin(v);
997 CHKERR VecAssemblyEnd(v);
998 CHKERR VecGhostUpdateBegin(v, ADD_VALUES, SCATTER_REVERSE);
999 CHKERR VecGhostUpdateEnd(v, ADD_VALUES, SCATTER_REVERSE);
1000 }
1001
1002 auto M = createDMMatrix(subset_dm_bdy);
1003 fe_lhs->B = M;
1004 CHKERR DMoFEMLoopFiniteElements(subset_dm_bdy, "ADJOINT_BOUNDARY_FE",
1005 fe_lhs);
1006 CHKERR MatAssemblyBegin(M, MAT_FINAL_ASSEMBLY);
1007 CHKERR MatAssemblyEnd(M, MAT_FINAL_ASSEMBLY);
1008
1009 auto solver = createKSP(mField.get_comm());
1010 CHKERR KSPSetOperators(solver, M, M);
1011 CHKERR KSPSetFromOptions(solver);
1012 CHKERR KSPSetUp(solver);
1013 auto v = createDMVector(subset_dm_bdy);
1014 for (auto &f : modeVecs) {
1015 CHKERR KSPSolve(solver, f, v);
1016 CHKERR VecSwap(f, v);
1017 }
1018
1019 for (auto &v : modeVecs) {
1020 CHKERR VecGhostUpdateBegin(v, INSERT_VALUES, SCATTER_FORWARD);
1021 CHKERR VecGhostUpdateEnd(v, INSERT_VALUES, SCATTER_FORWARD);
1022 }
1023
1025 };
1026
1027 CHKERR solve_bdy();
1028
1029 auto get_elastic_fe_lhs = [&]() {
1030 auto fe = boost::make_shared<DomainEle>(mField);
1031 fe->getRuleHook = [](int, int, int p_data) {
1032 return 2 * p_data + p_data - 1;
1033 };
1034 auto &pip = fe->getOpPtrVector();
1036 "GEOMETRY");
1037 CHKERR HookeOps::opFactoryDomainLhs<SPACE_DIM, A, I, DomainEleOp>(
1038 mField, pip, "ADJOINT_FIELD", "MAT_ADJOINT", Sev::noisy);
1039 return fe;
1040 };
1041
1042 auto get_elastic_fe_rhs = [&]() {
1043 auto fe = boost::make_shared<DomainEle>(mField);
1044 fe->getRuleHook = [](int, int, int p_data) {
1045 return 2 * p_data + p_data - 1;
1046 };
1047 auto &pip = fe->getOpPtrVector();
1049 "GEOMETRY");
1050 CHKERR HookeOps::opFactoryDomainRhs<SPACE_DIM, A, I, DomainEleOp>(
1051 mField, pip, "ADJOINT_FIELD", "MAT_ADJOINT", Sev::noisy);
1052 return fe;
1053 };
1054
1055 auto adjoint_gradient_postprocess = [&](auto mode) {
1057 auto post_proc_mesh = boost::make_shared<moab::Core>();
1058 auto post_proc_begin =
1059 boost::make_shared<PostProcBrokenMeshInMoabBaseBegin>(mField,
1060 post_proc_mesh);
1061 auto post_proc_end = boost::make_shared<PostProcBrokenMeshInMoabBaseEnd>(
1062 mField, post_proc_mesh);
1063
1064 auto geom_vec = boost::make_shared<MatrixDouble>();
1065
1066 auto post_proc_fe =
1067 boost::make_shared<PostProcEleDomain>(mField, post_proc_mesh);
1069 post_proc_fe->getOpPtrVector(), {H1}, "GEOMETRY");
1070 post_proc_fe->getOpPtrVector().push_back(
1071 new OpCalculateVectorFieldValues<SPACE_DIM>("ADJOINT_FIELD", geom_vec,
1072 modeVecs[mode]));
1073
1075
1076 post_proc_fe->getOpPtrVector().push_back(
1077
1078 new OpPPMap(
1079
1080 post_proc_fe->getPostProcMesh(), post_proc_fe->getMapGaussPts(),
1081
1082 {},
1083
1084 {{"MODE", geom_vec}},
1085
1086 {},
1087
1088 {}
1089
1090 )
1091
1092 );
1093
1095 post_proc_begin->getFEMethod());
1096 CHKERR DMoFEMLoopFiniteElements(adjointDM, "ADJOINT_DOMAIN_FE",
1097 post_proc_fe);
1099 post_proc_begin->getFEMethod());
1100
1101 CHKERR post_proc_end->writeFile("mode_" + std::to_string(mode) + ".h5m");
1102
1104 };
1105
1106 auto solve_domain = [&]() {
1108 auto fe_lhs = get_elastic_fe_lhs();
1109 auto fe_rhs = get_elastic_fe_rhs();
1110 auto v = createDMVector(subset_dm_domain);
1111 auto F = vectorDuplicate(v);
1112 fe_rhs->f = F;
1113
1114 auto M = createDMMatrix(subset_dm_domain);
1115 fe_lhs->B = M;
1116 CHKERR DMoFEMLoopFiniteElements(subset_dm_domain, "ADJOINT_DOMAIN_FE",
1117 fe_lhs);
1118 CHKERR MatAssemblyBegin(M, MAT_FINAL_ASSEMBLY);
1119 CHKERR MatAssemblyEnd(M, MAT_FINAL_ASSEMBLY);
1120
1121 auto solver = createKSP(mField.get_comm());
1122 CHKERR KSPSetOperators(solver, M, M);
1123 CHKERR KSPSetFromOptions(solver);
1124 CHKERR KSPSetUp(solver);
1125
1126 int mode_counter = 0;
1127 for (auto &f : modeVecs) {
1128 CHKERR mField.getInterface<FieldBlas>()->setField(0, "ADJOINT_FIELD");
1129 CHKERR DMoFEMMeshToLocalVector(subset_dm_bdy, f, INSERT_VALUES,
1130 SCATTER_REVERSE);
1131 CHKERR VecZeroEntries(F);
1132 CHKERR DMoFEMLoopFiniteElements(subset_dm_domain, "ADJOINT_DOMAIN_FE",
1133 fe_rhs);
1134 CHKERR VecAssemblyBegin(F);
1135 CHKERR VecAssemblyEnd(F);
1136 CHKERR VecGhostUpdateBegin(F, ADD_VALUES, SCATTER_REVERSE);
1137 CHKERR VecGhostUpdateEnd(F, ADD_VALUES, SCATTER_REVERSE);
1138 CHKERR KSPSolve(solver, F, v);
1139 CHKERR VecGhostUpdateBegin(v, INSERT_VALUES, SCATTER_FORWARD);
1140 CHKERR VecGhostUpdateEnd(v, INSERT_VALUES, SCATTER_FORWARD);
1141 CHKERR DMoFEMMeshToLocalVector(subset_dm_domain, v, INSERT_VALUES,
1142 SCATTER_REVERSE);
1143 auto m = createDMVector(adjointDM);
1144 CHKERR DMoFEMMeshToLocalVector(adjointDM, m, INSERT_VALUES,
1145 SCATTER_FORWARD);
1146 f = m;
1147 ++mode_counter;
1148 }
1150 };
1151
1152 CHKERR solve_domain();
1153
1154 for (int i = 0; i < modeVecs.size(); ++i) {
1155 CHKERR adjoint_gradient_postprocess(i);
1156 }
1157
1159}
1160//! [Adjoint modes]
1161
1162//! [Push operators to pipeline]
1165 auto pip = mField.getInterface<PipelineManager>();
1166
1167 //! [Integration rule]
1168 auto integration_rule = [](int, int, int approx_order) {
1169 return 2 * approx_order + 1;
1170 };
1171
1172 auto integration_rule_bc = [](int, int, int approx_order) {
1173 return 2 * approx_order + 1;
1174 };
1175
1177 CHKERR pip->setDomainLhsIntegrationRule(integration_rule);
1178 CHKERR pip->setBoundaryRhsIntegrationRule(integration_rule_bc);
1179 CHKERR pip->setBoundaryLhsIntegrationRule(integration_rule_bc);
1180 //! [Integration rule]
1181
1183 pip->getOpDomainLhsPipeline(), {H1}, "GEOMETRY");
1185 pip->getOpDomainRhsPipeline(), {H1}, "GEOMETRY");
1187 pip->getOpBoundaryRhsPipeline(), {NOSPACE}, "GEOMETRY");
1189 pip->getOpBoundaryLhsPipeline(), {NOSPACE}, "GEOMETRY");
1190
1191 //! [Push domain stiffness matrix to pipeline]
1192 // Add LHS operator for elasticity (stiffness matrix)
1193 CHKERR HookeOps::opFactoryDomainLhs<SPACE_DIM, A, I, DomainEleOp>(
1194 mField, pip->getOpDomainLhsPipeline(), "U", "MAT_ELASTIC", Sev::noisy);
1195 //! [Push domain stiffness matrix to pipeline]
1196
1197 // Add RHS operator for internal forces
1198 CHKERR HookeOps::opFactoryDomainRhs<SPACE_DIM, A, I, DomainEleOp>(
1199 mField, pip->getOpDomainRhsPipeline(), "U", "MAT_ELASTIC", Sev::noisy);
1200
1201 //! [Push Body forces]
1203 pip->getOpDomainRhsPipeline(), mField, "U", Sev::inform);
1204 //! [Push Body forces]
1205
1206 //! [Push natural boundary conditions]
1207 // Add force boundary condition
1209 pip->getOpBoundaryRhsPipeline(), mField, "U", -1, Sev::inform);
1210 // Add case for mix type of BCs
1212 pip->getOpBoundaryLhsPipeline(), mField, "U", Sev::noisy);
1213 //! [Push natural boundary conditions]
1215}
1216//! [Push operators to pipeline]
1217
1218//! [Solve]
1221 auto simple = mField.getInterface<Simple>();
1222 auto dm = simple->getDM();
1223 auto f = createDMVector(dm);
1224 auto d = vectorDuplicate(f);
1225 CHKERR VecZeroEntries(d);
1226 CHKERR DMoFEMMeshToLocalVector(dm, d, INSERT_VALUES, SCATTER_REVERSE);
1227
1228 auto set_essential_bc = [&]() {
1230 // This is low level pushing finite elements (pipelines) to solver
1231
1232 auto ksp_ctx_ptr = getDMKspCtx(dm);
1233 auto pre_proc_rhs = boost::make_shared<FEMethod>();
1234 auto post_proc_rhs = boost::make_shared<FEMethod>();
1235 auto post_proc_lhs = boost::make_shared<FEMethod>();
1236
1237 auto get_pre_proc_hook = [&]() {
1239 {});
1240 };
1241 pre_proc_rhs->preProcessHook = get_pre_proc_hook();
1242
1243 auto get_post_proc_hook_rhs = [this, post_proc_rhs]() {
1245
1247 post_proc_rhs, 1.)();
1249 };
1250
1251 auto get_post_proc_hook_lhs = [this, post_proc_lhs]() {
1253
1255 post_proc_lhs, 1.)();
1257 };
1258
1259 post_proc_rhs->postProcessHook = get_post_proc_hook_rhs;
1260 post_proc_lhs->postProcessHook = get_post_proc_hook_lhs;
1261
1262 ksp_ctx_ptr->getPreProcComputeRhs().push_front(pre_proc_rhs);
1263 ksp_ctx_ptr->getPostProcComputeRhs().push_back(post_proc_rhs);
1264 ksp_ctx_ptr->getPostProcSetOperators().push_back(post_proc_lhs);
1266 };
1267
1268 auto setup_and_solve = [&](auto solver) {
1270 BOOST_LOG_SCOPED_THREAD_ATTR("Timeline", attrs::timer());
1271 MOFEM_LOG("TIMER", Sev::noisy) << "KSPSetUp";
1272 CHKERR KSPSetUp(solver);
1273 MOFEM_LOG("TIMER", Sev::noisy) << "KSPSetUp <= Done";
1274 MOFEM_LOG("TIMER", Sev::noisy) << "KSPSolve";
1275 CHKERR KSPSolve(solver, f, d);
1276 MOFEM_LOG("TIMER", Sev::noisy) << "KSPSolve <= Done";
1278 };
1279
1280 MOFEM_LOG_CHANNEL("TIMER");
1281 MOFEM_LOG_TAG("TIMER", "timer");
1282
1283 CHKERR set_essential_bc();
1284 CHKERR setup_and_solve(kspElastic);
1285
1286 CHKERR VecGhostUpdateBegin(d, INSERT_VALUES, SCATTER_FORWARD);
1287 CHKERR VecGhostUpdateEnd(d, INSERT_VALUES, SCATTER_FORWARD);
1288 CHKERR DMoFEMMeshToLocalVector(dm, d, INSERT_VALUES, SCATTER_REVERSE);
1289
1290 auto evaluate_field_at_the_point = [&]() {
1292
1293 int coords_dim = 3;
1294 std::array<double, 3> field_eval_coords{0.0, 0.0, 0.0};
1295 PetscBool do_eval_field = PETSC_FALSE;
1296 CHKERR PetscOptionsGetRealArray(NULL, NULL, "-field_eval_coords",
1297 field_eval_coords.data(), &coords_dim,
1298 &do_eval_field);
1299
1300 if (do_eval_field) {
1301
1302 vectorFieldPtr = boost::make_shared<MatrixDouble>();
1303 auto field_eval_data =
1304 mField.getInterface<FieldEvaluatorInterface>()->getData<DomainEle>();
1305
1307 ->buildTree<SPACE_DIM>(field_eval_data, simple->getDomainFEName());
1308
1309 field_eval_data->setEvalPoints(field_eval_coords.data(), 1);
1310 auto no_rule = [](int, int, int) { return -1; };
1311 auto field_eval_fe_ptr = field_eval_data->feMethodPtr.lock();
1312 field_eval_fe_ptr->getRuleHook = no_rule;
1313
1314 field_eval_fe_ptr->getOpPtrVector().push_back(
1316
1318 ->evalFEAtThePoint<SPACE_DIM>(
1319 field_eval_coords.data(), 1e-12, simple->getProblemName(),
1320 simple->getDomainFEName(), field_eval_data,
1322 QUIET);
1323
1324 if (vectorFieldPtr->size1()) {
1325 auto t_disp = getFTensor1FromMat<SPACE_DIM>(*vectorFieldPtr);
1326 if constexpr (SPACE_DIM == 2)
1327 MOFEM_LOG("FieldEvaluator", Sev::inform)
1328 << "U_X: " << t_disp(0) << " U_Y: " << t_disp(1);
1329 else
1330 MOFEM_LOG("FieldEvaluator", Sev::inform)
1331 << "U_X: " << t_disp(0) << " U_Y: " << t_disp(1)
1332 << " U_Z: " << t_disp(2);
1333 }
1334
1336 }
1338 };
1339
1340 CHKERR evaluate_field_at_the_point();
1341
1343}
1344//! [Solve]
1345
1346//! [Postprocess results]
1348 SmartPetscObj<Vec> adjoint_vector) {
1350 auto simple = mField.getInterface<Simple>();
1351 auto pip = mField.getInterface<PipelineManager>();
1352 auto det_ptr = boost::make_shared<VectorDouble>();
1353 auto jac_ptr = boost::make_shared<MatrixDouble>();
1354 auto inv_jac_ptr = boost::make_shared<MatrixDouble>();
1355 //! [Postprocess clean]
1356 pip->getDomainRhsFE().reset();
1357 pip->getDomainLhsFE().reset();
1358 pip->getBoundaryRhsFE().reset();
1359 pip->getBoundaryLhsFE().reset();
1360 //! [Postprocess clean]
1361
1362 //! [Postprocess initialise]
1363 auto post_proc_mesh = boost::make_shared<moab::Core>();
1364 auto post_proc_begin = boost::make_shared<PostProcBrokenMeshInMoabBaseBegin>(
1365 mField, post_proc_mesh);
1366 auto post_proc_end = boost::make_shared<PostProcBrokenMeshInMoabBaseEnd>(
1367 mField, post_proc_mesh);
1368 //! [Postprocess initialise]
1369
1370 // lambada function to calculate displacement, strain and stress
1371 auto calculate_stress_ops = [&](auto &pip) {
1372 // Add H1 geometry ops
1374
1375 // Use HookeOps commonDataFactory to get matStrainPtr and matCauchyStressPtr
1376 auto common_ptr = HookeOps::commonDataFactory<SPACE_DIM, GAUSS, DomainEle>(
1377 mField, pip, "U", "MAT_ELASTIC", Sev::noisy);
1378
1379 // Store U and GEOMETRY values if needed
1380 auto u_ptr = boost::make_shared<MatrixDouble>();
1381 pip.push_back(new OpCalculateVectorFieldValues<SPACE_DIM>("U", u_ptr));
1382 auto x_ptr = boost::make_shared<MatrixDouble>();
1383 pip.push_back(
1384 new OpCalculateVectorFieldValues<SPACE_DIM>("GEOMETRY", x_ptr));
1385
1386 using DataMapMat = std::map<std::string, boost::shared_ptr<MatrixDouble>>;
1387
1388 DataMapMat vec_map = {{"U", u_ptr}, {"GEOMETRY", x_ptr}};
1389 DataMapMat sym_map = {{"STRAIN", common_ptr->getMatStrain()},
1390 {"STRESS", common_ptr->getMatCauchyStress()}};
1391
1392 if (adjoint_vector) {
1393 auto adjoint_ptr = boost::make_shared<MatrixDouble>();
1395 "U", adjoint_ptr, adjoint_vector));
1396 vec_map["ADJOINT"] = adjoint_ptr;
1397 }
1398
1399 // Return what you need: displacements, coordinates, strain, stress
1400 return boost::make_tuple(u_ptr, x_ptr, common_ptr->getMatStrain(),
1401 common_ptr->getMatCauchyStress(), vec_map,
1402 sym_map);
1403 };
1404
1405 auto get_tag_id_on_pmesh = [&](bool post_proc_skin) {
1406 int def_val_int = 0;
1407 Tag tag_mat;
1408 CHKERR mField.get_moab().tag_get_handle(
1409 "MAT_ELASTIC", 1, MB_TYPE_INTEGER, tag_mat,
1410 MB_TAG_CREAT | MB_TAG_SPARSE, &def_val_int);
1411 auto meshset_vec_ptr =
1412 mField.getInterface<MeshsetsManager>()->getCubitMeshsetPtr(
1413 std::regex((boost::format("%s(.*)") % "MAT_ELASTIC").str()));
1414
1415 Range skin_ents;
1416 std::unique_ptr<Skinner> skin_ptr;
1417 if (post_proc_skin) {
1418 skin_ptr = std::make_unique<Skinner>(&mField.get_moab());
1419 auto boundary_meshset = simple->getBoundaryMeshSet();
1420 CHKERR mField.get_moab().get_entities_by_handle(boundary_meshset,
1421 skin_ents, true);
1422 }
1423
1424 for (auto m : meshset_vec_ptr) {
1425 Range ents_3d;
1426 CHKERR mField.get_moab().get_entities_by_handle(m->getMeshset(), ents_3d,
1427 true);
1428 int const id = m->getMeshsetId();
1429 ents_3d = ents_3d.subset_by_dimension(SPACE_DIM);
1430 if (post_proc_skin) {
1431 Range skin_faces;
1432 CHKERR skin_ptr->find_skin(0, ents_3d, false, skin_faces);
1433 ents_3d = intersect(skin_ents, skin_faces);
1434 }
1435 CHKERR mField.get_moab().tag_clear_data(tag_mat, ents_3d, &id);
1436 }
1437
1438 return tag_mat;
1439 };
1440
1441 auto post_proc_domain = [&](auto post_proc_mesh) {
1442 auto post_proc_fe =
1443 boost::make_shared<PostProcEleDomain>(mField, post_proc_mesh);
1445
1446 auto [u_ptr, x_ptr, mat_strain_ptr, mat_stress_ptr, vec_map, sym_map] =
1447 calculate_stress_ops(post_proc_fe->getOpPtrVector());
1448
1449 post_proc_fe->getOpPtrVector().push_back(
1450
1451 new OpPPMap(
1452
1453 post_proc_fe->getPostProcMesh(), post_proc_fe->getMapGaussPts(),
1454
1455 {},
1456
1457 vec_map,
1458
1459 {},
1460
1461 sym_map
1462
1463 )
1464
1465 );
1466
1467 post_proc_fe->setTagsToTransfer({get_tag_id_on_pmesh(false)});
1468 return post_proc_fe;
1469 };
1470
1471 auto post_proc_boundary = [&](auto post_proc_mesh) {
1472 auto post_proc_fe =
1473 boost::make_shared<PostProcEleBdy>(mField, post_proc_mesh);
1475 post_proc_fe->getOpPtrVector(), {}, "GEOMETRY");
1476 auto op_loop_side =
1477 new OpLoopSide<SideEle>(mField, simple->getDomainFEName(), SPACE_DIM);
1478 // push ops to side element, through op_loop_side operator
1479 auto [u_ptr, x_ptr, mat_strain_ptr, mat_stress_ptr, vec_map, sym_map] =
1480 calculate_stress_ops(op_loop_side->getOpPtrVector());
1481 post_proc_fe->getOpPtrVector().push_back(op_loop_side);
1482 auto mat_traction_ptr = boost::make_shared<MatrixDouble>();
1483 post_proc_fe->getOpPtrVector().push_back(
1484 new ElasticOps::OpCalculateTraction(mat_stress_ptr, mat_traction_ptr));
1485 vec_map["T"] = mat_traction_ptr;
1486
1488
1489 post_proc_fe->getOpPtrVector().push_back(
1490
1491 new OpPPMap(
1492
1493 post_proc_fe->getPostProcMesh(), post_proc_fe->getMapGaussPts(),
1494
1495 {},
1496
1497 vec_map,
1498
1499 {},
1500
1501 sym_map
1502
1503 )
1504
1505 );
1506
1507 post_proc_fe->setTagsToTransfer({get_tag_id_on_pmesh(true)});
1508 return post_proc_fe;
1509 };
1510
1511 PetscBool post_proc_skin_only = PETSC_FALSE;
1512 if (SPACE_DIM == 3) {
1513 post_proc_skin_only = PETSC_TRUE;
1514 CHKERR PetscOptionsGetBool(PETSC_NULLPTR, "", "-post_proc_skin_only",
1515 &post_proc_skin_only, PETSC_NULLPTR);
1516 }
1517 if (post_proc_skin_only == PETSC_FALSE) {
1518 pip->getDomainRhsFE() = post_proc_domain(post_proc_mesh);
1519 } else {
1520 pip->getBoundaryRhsFE() = post_proc_boundary(post_proc_mesh);
1521 }
1522
1524 post_proc_begin->getFEMethod());
1525 CHKERR pip->loopFiniteElements();
1527 post_proc_end->getFEMethod());
1528
1529 CHKERR post_proc_end->writeFile("out_elastic_" + std::to_string(iter) +
1530 ".h5m");
1532}
1533//! [Postprocess results]
1534
1535//! [calculateGradient]
1536
1538
1539/**
1540 * @brief Adjoint test operator for elastic problems
1541 *
1542 * This operator implements the adjoint (transpose) of the elastic stiffness operator.
1543 * It's used in the adjoint equation K^T * λ = ∂f/∂u to compute sensitivities.
1544 *
1545 * The operator computes: ∫_Ω ∇v : σ dΩ
1546 * where v are test functions and σ is the Cauchy stress tensor.
1547 *
1548 * This is the transpose of the standard elastic operator ∫_Ω ∇u : C : ∇v dΩ
1549 *
1550 * @tparam SPACE_DIM Spatial dimension (2 or 3)
1551 */
1552template <int SPACE_DIM>
1554 : public DomainBaseOp {
1555
1557
1558 OpAdJointTestOp(const std::string field_name,
1559 boost::shared_ptr<HookeOps::CommonData> comm_ptr)
1560 : OP(field_name, field_name, DomainEleOp::OPROW), commPtr(comm_ptr) {}
1561
1562protected:
1563 boost::shared_ptr<HookeOps::CommonData> commPtr;
1565};
1566
1567/**
1568 * @brief Integration implementation for adjoint test operator
1569 *
1570 * Computes the integral: ∫_Ω ∇v : σ dΩ
1571 * where v are test functions and σ is Cauchy stress tensor
1572 *
1573 * This forms the right-hand side of the adjoint equation K^T * λ = ∂f/∂u
1574 *
1575 * @param row_data Test function data for current element
1576 * @return MoFEMErrorCode Success or error code
1577 */
1578template <int SPACE_DIM>
1581 EntitiesFieldData::EntData &row_data) {
1583 // Define tensor indices for Einstein notation
1590
1591 // Get element geometric information
1592 const double vol = OP::getMeasure();
1593 // Get integration weights for Gauss quadrature
1594 auto t_w = OP::getFTensor0IntegrationWeight();
1595 // Get test function gradients ∇v
1596 auto t_row_grad = row_data.getFTensor1DiffN<SPACE_DIM>();
1597 // Get Cauchy stress tensor σ from forward solution
1598 auto t_cauchy_stress =
1599 getFTensor2SymmetricFromMat<SPACE_DIM>(*(commPtr->getMatCauchyStress()));
1600
1601 // Loop over integration points for numerical integration
1602 for (int gg = 0; gg != OP::nbIntegrationPts; gg++) {
1603 // take into account Jacobian
1604 const double alpha = t_w * vol;
1605
1606 // get rhs vector
1607 auto t_nf = OP::template getNf<SPACE_DIM>();
1608 // loop over rows base functions
1609 int rr = 0;
1610 for (; rr != OP::nbRows / SPACE_DIM; rr++) {
1611 // Variation due to change in geometry (diff base)
1612 t_nf(j) += alpha * t_row_grad(i) * t_cauchy_stress(i, j);
1613
1614 ++t_row_grad;
1615 ++t_nf;
1616 }
1617 for (; rr < OP::nbRowBaseFunctions; ++rr) {
1618 ++t_row_grad;
1619 }
1620
1621 ++t_cauchy_stress;
1622 ++t_w; // move to another integration weight
1623 }
1625}
1626
1627template <int SPACE_DIM>
1629 DomainBaseOp> : public DomainBaseOp {
1630
1632
1634 boost::shared_ptr<HookeOps::CommonData> comm_ptr,
1635 boost::shared_ptr<MatrixDouble> jac,
1636 boost::shared_ptr<MatrixDouble> diff_jac,
1637 boost::shared_ptr<VectorDouble> cof_vals)
1638 : OP(field_name, field_name, DomainEleOp::OPROW), commPtr(comm_ptr),
1639 jac(jac), diffJac(diff_jac), cofVals(cof_vals) {}
1640
1641protected:
1642 boost::shared_ptr<HookeOps::CommonData> commPtr;
1643 boost::shared_ptr<MatrixDouble> jac;
1644 boost::shared_ptr<MatrixDouble> diffJac;
1645 boost::shared_ptr<VectorDouble> cofVals;
1647};
1648
1649template <int DIM> inline auto diff_symmetrize(FTensor::Number<DIM>) {
1650
1651 FTensor::Index<'i', DIM> i;
1652 FTensor::Index<'j', DIM> j;
1653 FTensor::Index<'k', DIM> k;
1654 FTensor::Index<'l', DIM> l;
1655
1657
1658 t_diff(i, j, k, l) = 0;
1659 t_diff(0, 0, 0, 0) = 1;
1660 t_diff(1, 1, 1, 1) = 1;
1661
1662 t_diff(1, 0, 1, 0) = 0.5;
1663 t_diff(1, 0, 0, 1) = 0.5;
1664
1665 t_diff(0, 1, 0, 1) = 0.5;
1666 t_diff(0, 1, 1, 0) = 0.5;
1667
1668 if constexpr (DIM == 3) {
1669 t_diff(2, 2, 2, 2) = 1;
1670
1671 t_diff(2, 0, 2, 0) = 0.5;
1672 t_diff(2, 0, 0, 2) = 0.5;
1673 t_diff(0, 2, 0, 2) = 0.5;
1674 t_diff(0, 2, 2, 0) = 0.5;
1675
1676 t_diff(2, 1, 2, 1) = 0.5;
1677 t_diff(2, 1, 1, 2) = 0.5;
1678 t_diff(1, 2, 1, 2) = 0.5;
1679 t_diff(1, 2, 2, 1) = 0.5;
1680 }
1681
1682 return t_diff;
1683};
1684
1685template <int SPACE_DIM>
1696
1697 auto t_diff_symm = diff_symmetrize(FTensor::Number<SPACE_DIM>());
1698
1699 // get element volume
1700 const double vol = OP::getMeasure();
1701 // get integration weights
1702 auto t_w = OP::getFTensor0IntegrationWeight();
1703 // get Jacobian values
1704 auto t_jac = getFTensor2FromMat<SPACE_DIM, SPACE_DIM>(*(jac));
1705 // get diff Jacobian values
1706 auto t_diff_jac = getFTensor2FromMat<SPACE_DIM, SPACE_DIM>(*(diffJac));
1707 // get cofactor values
1708 auto t_cof = getFTensor0FromVec(*(cofVals));
1709 // get base function gradient on rows
1710 auto t_row_grad = row_data.getFTensor1DiffN<SPACE_DIM>();
1711 // get fradient of the field
1712 auto t_grad_u =
1713 getFTensor2FromMat<SPACE_DIM, SPACE_DIM>(*(commPtr->matGradPtr));
1714 // get field gradient values
1715 auto t_cauchy_stress =
1716 getFTensor2SymmetricFromMat<SPACE_DIM>(*(commPtr->getMatCauchyStress()));
1717 // material stiffness tensor
1718 auto t_D =
1719 getFTensor4DdgFromMat<SPACE_DIM, SPACE_DIM, 0>(*(commPtr->matDPtr));
1720 // loop over integration points
1721 for (int gg = 0; gg != OP::nbIntegrationPts; gg++) {
1722 // take into account Jacobian
1723 const double alpha = t_w * vol;
1724
1725 auto t_det = determinantTensor(t_jac);
1727 CHKERR invertTensor(t_jac, t_det, t_inv_jac);
1728
1729 // Calculate the variation of the gradient due to geometry change
1731 t_diff_inv_jac(i, j) =
1732 -(t_inv_jac(i, I) * t_diff_jac(I, J)) * t_inv_jac(J, j);
1734 t_diff_grad(i, j) = t_grad_u(i, k) * t_diff_inv_jac(k, j);
1735
1736 // Calculate the variation of the strain tensor
1738 t_diff_strain(i, j) = t_diff_symm(i, j, k, l) * t_diff_grad(k, l);
1739
1740 // Calculate the variation of the stress tensor
1742 t_diff_stress(i, j) = t_D(i, j, k, l) * t_diff_strain(k, l);
1743
1744 // get rhs vector
1745 auto t_nf = OP::template getNf<SPACE_DIM>();
1746 // loop over rows base functions
1747 int rr = 0;
1748 for (; rr != OP::nbRows / SPACE_DIM; rr++) {
1749
1751 t_diff_row_grad(k) = t_row_grad(j) * t_diff_inv_jac(j, k);
1752
1753 // Variation due to change in geometry (diff base)
1754 t_nf(j) += alpha * t_diff_row_grad(i) * t_cauchy_stress(i, j);
1755
1756 // Variation due to change in domain (cofactor)
1757 t_nf(j) += (alpha * t_cof) * t_row_grad(i) * t_cauchy_stress(i, j);
1758
1759 // Variation due to change in stress (diff stress)
1760 t_nf(j) += alpha * t_row_grad(i) * t_diff_stress(i, j);
1761
1762 ++t_row_grad;
1763 ++t_nf;
1764 }
1765 for (; rr < OP::nbRowBaseFunctions; ++rr) {
1766 ++t_row_grad;
1767 }
1768
1769 ++t_grad_u;
1770 ++t_cauchy_stress;
1771 ++t_jac;
1772 ++t_diff_jac;
1773 ++t_cof;
1774 ++t_w; // move to another integration weight
1775 }
1777}
1778
1779/**
1780 * @brief Operator for computing objective function and gradients in topology optimization
1781 *
1782 * This operator interfaces with Python-defined objective functions to:
1783 * 1. Evaluate objective function f(u, x) at current design point
1784 * 2. Compute gradients ∂f/∂u and ∂f/∂x for adjoint sensitivity analysis
1785 *
1786 * The objective function typically depends on:
1787 * - u: displacement field from forward elastic solution
1788 * - x: design variables (topology parameters)
1789 * - σ: stress field computed from displacement
1790 *
1791 * This enables flexible objective function definition (compliance, stress
1792 * constraints, volume constraints, etc.) through Python scripting.
1793 */
1796
1797 OpAdJointObjective(boost::shared_ptr<ObjectiveFunctionData> python_ptr,
1798 boost::shared_ptr<HookeOps::CommonData> comm_ptr,
1799 boost::shared_ptr<MatrixDouble> jac_ptr,
1800 boost::shared_ptr<MatrixDouble> diff_jac,
1801 boost::shared_ptr<VectorDouble> cof_vals,
1802 boost::shared_ptr<MatrixDouble> d_grad_ptr,
1803 boost::shared_ptr<MatrixDouble> u_ptr,
1804 boost::shared_ptr<double> glob_objective_ptr,
1805 boost::shared_ptr<double> glob_objective_grad_ptr)
1806 : OP(NOSPACE, OP::OPSPACE), pythonPtr(python_ptr), commPtr(comm_ptr),
1807 jacPtr(jac_ptr), diffJacPtr(diff_jac), cofVals(cof_vals),
1808 dGradPtr(d_grad_ptr), uPtr(u_ptr), globObjectivePtr(glob_objective_ptr),
1809 globObjectiveGradPtr(glob_objective_grad_ptr) {}
1810
1811 /**
1812 * @brief Compute objective function contributions at element level
1813 *
1814 * Evaluates Python objective function with current displacement and stress
1815 * state, and accumulates global objective value and gradients.
1816 */
1817 MoFEMErrorCode doWork(int side, EntityType type,
1820
1821 // Define tensor indices for calculations
1826
1829
1830 constexpr auto symm_size = (SPACE_DIM * (SPACE_DIM + 1)) / 2;
1831
1832 auto t_diff_symm = diff_symmetrize(FTensor::Number<SPACE_DIM>());
1833
1834 auto nb_gauss_pts = getGaussPts().size2();
1835 auto objective_ptr = boost::make_shared<VectorDouble>(nb_gauss_pts);
1836 auto objective_dstress =
1837 boost::make_shared<MatrixDouble>(symm_size, nb_gauss_pts);
1838 auto objective_dstrain =
1839 boost::make_shared<MatrixDouble>(symm_size, nb_gauss_pts);
1840 auto obj_grad = boost::make_shared<MatrixDouble>(SPACE_DIM, nb_gauss_pts);
1841
1842 auto evaluate_python = [&]() {
1844 auto &coords = OP::getCoordsAtGaussPts();
1845 CHKERR pythonPtr->evalObjectiveFunction(
1846 coords, uPtr, commPtr->getMatCauchyStress(), commPtr->getMatStrain(),
1847 objective_ptr);
1848 CHKERR pythonPtr->evalObjectiveGradientStress(
1849 coords, uPtr, commPtr->getMatCauchyStress(), commPtr->getMatStrain(),
1850 objective_dstress);
1851 CHKERR pythonPtr->evalObjectiveGradientStrain(
1852 coords, uPtr, commPtr->getMatCauchyStress(), commPtr->getMatStrain(),
1853 objective_dstrain);
1854
1855 auto t_grad_u =
1856 getFTensor2FromMat<SPACE_DIM, SPACE_DIM>(*(commPtr->matGradPtr));
1857 auto t_D =
1858 getFTensor4DdgFromMat<SPACE_DIM, SPACE_DIM, 0>(*(commPtr->matDPtr));
1859 auto t_jac = getFTensor2FromMat<SPACE_DIM, SPACE_DIM>(*(jacPtr));
1860 auto t_diff_jac = getFTensor2FromMat<SPACE_DIM, SPACE_DIM>(*(diffJacPtr));
1861 auto t_cof = getFTensor0FromVec(*(cofVals));
1862 auto t_d_grad = getFTensor2FromMat<SPACE_DIM, SPACE_DIM>(*(dGradPtr));
1863
1864 auto t_obj = getFTensor0FromVec(*objective_ptr);
1865 auto t_obj_dstress =
1866 getFTensor2SymmetricFromMat<SPACE_DIM>(*objective_dstress);
1867 auto t_obj_dstrain =
1868 getFTensor2SymmetricFromMat<SPACE_DIM>(*objective_dstrain);
1869
1870 auto vol = OP::getMeasure();
1871 auto t_w = getFTensor0IntegrationWeight();
1872 for (auto gg = 0; gg != nb_gauss_pts; ++gg) {
1873
1874 auto t_det = determinantTensor(t_jac);
1876 CHKERR invertTensor(t_jac, t_det, t_inv_jac);
1877
1879 t_diff_inv_jac(i, j) =
1880 -(t_inv_jac(i, I) * t_diff_jac(I, J)) * t_inv_jac(J, j);
1882 t_diff_grad(i, j) = t_grad_u(i, k) * t_diff_inv_jac(k, j);
1883
1885 t_d_strain(i, j) = t_diff_symm(i, j, k, l) * (
1886
1887 t_d_grad(k, l)
1888
1889 +
1890
1891 t_diff_grad(k, l)
1892
1893 );
1894
1895 auto alpha = t_w * vol;
1896
1897 (*globObjectivePtr) += alpha * t_obj;
1898 (*globObjectiveGradPtr) +=
1899 alpha *
1900 (
1901
1902 t_obj_dstress(i, j) * (t_D(i, j, k, l) * t_d_strain(k, l))
1903
1904 +
1905
1906 t_obj_dstrain(i, j) * t_d_strain(i, j)
1907
1908 +
1909
1910 t_obj * t_cof
1911
1912 );
1913
1914 ++t_w;
1915 ++t_jac;
1916 ++t_diff_jac;
1917 ++t_cof;
1918
1919 ++t_obj;
1920 ++t_obj_dstress;
1921 ++t_obj_dstrain;
1922
1923 ++t_grad_u;
1924 ++t_d_grad;
1925 }
1927 };
1928
1929 CHKERR evaluate_python();
1930
1932 }
1933
1934private:
1935 boost::shared_ptr<ObjectiveFunctionData> pythonPtr;
1936 boost::shared_ptr<HookeOps::CommonData> commPtr;
1937 boost::shared_ptr<MatrixDouble> jacPtr;
1938 boost::shared_ptr<MatrixDouble> diffJacPtr;
1939 boost::shared_ptr<VectorDouble> cofVals;
1940 boost::shared_ptr<MatrixDouble> dGradPtr;
1941 boost::shared_ptr<MatrixDouble> uPtr;
1942
1943 boost::shared_ptr<double> globObjectivePtr;
1944 boost::shared_ptr<double> globObjectiveGradPtr;
1945};
1946
1947MoFEMErrorCode Example::calculateGradient(PetscReal *objective_function_value,
1948 Vec objective_function_gradient,
1949 Vec adjoint_vector) {
1950 MOFEM_LOG_CHANNEL("WORLD");
1952 auto simple = mField.getInterface<Simple>();
1953
1954 auto ents = get_range_from_block(mField, "OPTIMISE", SPACE_DIM - 1);
1955
1956 auto get_essential_fe = [this]() {
1957 auto post_proc_rhs = boost::make_shared<FEMethod>();
1958 auto get_post_proc_hook_rhs = [this, post_proc_rhs]() {
1960
1962 post_proc_rhs, 0)();
1964 };
1965 post_proc_rhs->postProcessHook = get_post_proc_hook_rhs;
1966 return post_proc_rhs;
1967 };
1968
1969 auto get_fd_direvative_fe = [&]() {
1970 auto fe = boost::make_shared<DomainEle>(mField);
1971 fe->getRuleHook = [](int, int, int p_data) {
1972 return 2 * p_data + p_data - 1;
1973 };
1974 auto &pip = fe->getOpPtrVector();
1976 // Add RHS operators for internal forces
1977 constexpr bool debug = false;
1978 if constexpr (debug) {
1979 auto common_ptr = HookeOps::commonDataFactory<SPACE_DIM, I, DomainEleOp>(
1980 mField, pip, "U", "MAT_ELASTIC", Sev::noisy);
1981 pip.push_back(
1982 new OpAdJointTestOp<SPACE_DIM, I, DomainBaseOp>("U", common_ptr));
1983 } else {
1984 HookeOps::opFactoryDomainRhs<SPACE_DIM, A, I, DomainEleOp>(
1985 mField, pip, "U", "MAT_ELASTIC", Sev::noisy);
1986 }
1987 return fe;
1988 };
1989
1990 auto calulate_fd_residual = [&](auto eps, auto diff_vec, auto fd_vec) {
1992
1993 constexpr bool debug = false;
1994
1995 auto geom_norm = [](MoFEM::Interface &mField) {
1997 auto field_blas = mField.getInterface<FieldBlas>();
1998 double nrm2 = 0.0;
1999 auto norm2_field = [&](const double val) {
2000 nrm2 += val * val;
2001 return val;
2002 };
2003 CHKERR field_blas->fieldLambdaOnValues(norm2_field, "GEOMETRY");
2004 MPI_Allreduce(MPI_IN_PLACE, &nrm2, 1, MPI_DOUBLE, MPI_SUM,
2005 mField.get_comm());
2006 MOFEM_LOG("WORLD", Sev::inform) << "Geometry norm: " << sqrt(nrm2);
2008 };
2009
2010 if constexpr (debug)
2011 CHKERR geom_norm(mField);
2012
2013 auto initial_current_geometry = createDMVector(adjointDM);
2014 CHKERR mField.getInterface<VecManager>()->setOtherLocalGhostVector(
2015 "ADJOINT", "ADJOINT_FIELD", "GEOMETRY", RowColData::ROW,
2016 initial_current_geometry, INSERT_VALUES, SCATTER_FORWARD);
2017 CHKERR VecAssemblyBegin(initial_current_geometry);
2018 CHKERR VecAssemblyEnd(initial_current_geometry);
2019
2020 if constexpr (debug)
2021 CHKERR geom_norm(mField);
2022
2023 auto perturb_geometry = [&](auto eps, auto diff_vec) {
2025 auto current_geometry = vectorDuplicate(initial_current_geometry);
2026 CHKERR VecCopy(initial_current_geometry, current_geometry);
2027 CHKERR VecAXPY(current_geometry, eps, diff_vec);
2028 CHKERR mField.getInterface<VecManager>()->setOtherLocalGhostVector(
2029 "ADJOINT", "ADJOINT_FIELD", "GEOMETRY", RowColData::ROW,
2030 current_geometry, INSERT_VALUES, SCATTER_REVERSE);
2032 };
2033
2034 auto fe = get_fd_direvative_fe();
2035 auto fp = vectorDuplicate(diff_vec);
2036 auto fm = vectorDuplicate(diff_vec);
2037 auto calc_impl = [&](auto f, auto eps) {
2039 CHKERR VecZeroEntries(f);
2040 fe->f = f;
2041 CHKERR perturb_geometry(eps, diff_vec);
2043 simple->getDomainFEName(), fe);
2044 CHKERR VecAssemblyBegin(f);
2045 CHKERR VecAssemblyEnd(f);
2046 CHKERR VecGhostUpdateBegin(f, ADD_VALUES, SCATTER_REVERSE);
2047 CHKERR VecGhostUpdateEnd(f, ADD_VALUES, SCATTER_REVERSE);
2048 auto post_proc_rhs = get_essential_fe();
2049 post_proc_rhs->f = f;
2051 post_proc_rhs.get());
2053 };
2054 CHKERR calc_impl(fp, eps);
2055 CHKERR calc_impl(fm, -eps);
2056 CHKERR VecWAXPY(fd_vec, -1.0, fm, fp);
2057 CHKERR VecScale(fd_vec, 1.0 / (2.0 * eps));
2058
2059 CHKERR mField.getInterface<VecManager>()->setOtherLocalGhostVector(
2060 "ADJOINT", "ADJOINT_FIELD", "GEOMETRY", RowColData::ROW,
2061 initial_current_geometry, INSERT_VALUES, SCATTER_REVERSE);
2062
2063 if constexpr (debug)
2064 CHKERR geom_norm(mField);
2065
2067 };
2068
2069 auto get_direvative_fe = [&](auto diff_vec) {
2070 auto fe_adjoint = boost::make_shared<DomainEle>(mField);
2071 fe_adjoint->getRuleHook = [](int, int, int p_data) {
2072 return 2 * p_data + p_data - 1;
2073 };
2074 auto &pip = fe_adjoint->getOpPtrVector();
2075
2076 auto jac_ptr = boost::make_shared<MatrixDouble>();
2077 auto det_ptr = boost::make_shared<VectorDouble>();
2078 auto inv_jac_ptr = boost::make_shared<MatrixDouble>();
2079 auto diff_jac_ptr = boost::make_shared<MatrixDouble>();
2080 auto cof_ptr = boost::make_shared<VectorDouble>();
2081
2082 using OpCoFactor =
2083 AdJoint<DomainEleOp>::Integration<GAUSS>::OpGetCoFactor<SPACE_DIM>;
2084
2086
2088 "GEOMETRY", jac_ptr));
2090 "U", diff_jac_ptr,
2091 diff_vec)); // Note: that vector is stored on displacemnt vector, that
2092 // why is used here
2093
2094 pip.push_back(new OpCoFactor(jac_ptr, diff_jac_ptr, cof_ptr));
2095
2096 // Add RHS operators for internal forces
2097 auto common_ptr = HookeOps::commonDataFactory<SPACE_DIM, I, DomainEleOp>(
2098 mField, pip, "U", "MAT_ELASTIC", Sev::noisy);
2100 "U", common_ptr, jac_ptr, diff_jac_ptr, cof_ptr));
2101
2102 return fe_adjoint;
2103 };
2104
2105 auto get_objective_fe = [&](auto diff_vec, auto grad_vec,
2106 auto glob_objective_ptr,
2107 auto glob_objective_grad_ptr) {
2108 auto fe_adjoint = boost::make_shared<DomainEle>(mField);
2109 fe_adjoint->getRuleHook = [](int, int, int p_data) {
2110 return 2 * p_data + p_data - 1;
2111 };
2112 auto &pip = fe_adjoint->getOpPtrVector();
2114
2115 auto jac_ptr = boost::make_shared<MatrixDouble>();
2116 auto det_ptr = boost::make_shared<VectorDouble>();
2117 auto inv_jac_ptr = boost::make_shared<MatrixDouble>();
2118 auto diff_jac_ptr = boost::make_shared<MatrixDouble>();
2119 auto cof_ptr = boost::make_shared<VectorDouble>();
2120 auto d_grad_ptr = boost::make_shared<MatrixDouble>();
2121 auto u_ptr = boost::make_shared<MatrixDouble>();
2122
2123 using OpCoFactor =
2124 AdJoint<DomainEleOp>::Integration<GAUSS>::OpGetCoFactor<SPACE_DIM>;
2126 "GEOMETRY", jac_ptr));
2128 "U", diff_jac_ptr,
2129 diff_vec)); // Note: that vector is stored on displacemnt vector, that
2130 // why is used here
2131 pip.push_back(new OpCoFactor(jac_ptr, diff_jac_ptr, cof_ptr));
2133 "U", d_grad_ptr, grad_vec));
2134 pip.push_back(new OpCalculateVectorFieldValues<SPACE_DIM>("U", u_ptr));
2135
2136 auto common_ptr = HookeOps::commonDataFactory<SPACE_DIM, I, DomainEleOp>(
2137 mField, pip, "U", "MAT_ELASTIC", Sev::noisy);
2138 pip.push_back(new OpAdJointObjective(
2139 pythonPtr, common_ptr, jac_ptr, diff_jac_ptr, cof_ptr, d_grad_ptr,
2140 u_ptr, glob_objective_ptr, glob_objective_grad_ptr));
2141
2142 return fe_adjoint;
2143 };
2144
2145 auto dm = simple->getDM();
2146 auto f = createDMVector(dm);
2147 auto d = vectorDuplicate(f);
2148 auto dm_diff_vec = vectorDuplicate(d);
2149
2150 auto adjoint_fe = get_direvative_fe(dm_diff_vec);
2151 auto glob_objective_ptr = boost::make_shared<double>(0.0);
2152 auto glob_objective_grad_ptr = boost::make_shared<double>(0.0);
2153 auto objective_fe = get_objective_fe(dm_diff_vec, d, glob_objective_ptr,
2154 glob_objective_grad_ptr);
2155
2156 auto set_variance_of_geometry = [&](auto mode, auto mod_vec) {
2158 CHKERR DMoFEMMeshToLocalVector(adjointDM, mod_vec, INSERT_VALUES,
2159 SCATTER_REVERSE);
2160 CHKERR mField.getInterface<VecManager>()->setOtherLocalGhostVector(
2161 simple->getProblemName(), "U", "ADJOINT_FIELD", RowColData::ROW,
2162 dm_diff_vec, INSERT_VALUES, SCATTER_FORWARD);
2163 CHKERR VecGhostUpdateBegin(dm_diff_vec, INSERT_VALUES, SCATTER_FORWARD);
2164 CHKERR VecGhostUpdateEnd(dm_diff_vec, INSERT_VALUES, SCATTER_FORWARD);
2166 };
2167
2168 auto calculate_variance_internal_forces = [&](auto mode, auto mod_vec) {
2170 CHKERR VecZeroEntries(f);
2171 CHKERR VecGhostUpdateBegin(f, INSERT_VALUES, SCATTER_FORWARD);
2172 CHKERR VecGhostUpdateEnd(f, INSERT_VALUES, SCATTER_FORWARD);
2173 adjoint_fe->f = f;
2174 CHKERR DMoFEMLoopFiniteElements(dm, simple->getDomainFEName(), adjoint_fe);
2175 CHKERR VecAssemblyBegin(f);
2176 CHKERR VecAssemblyEnd(f);
2177 CHKERR VecGhostUpdateBegin(f, ADD_VALUES, SCATTER_REVERSE);
2178 CHKERR VecGhostUpdateEnd(f, ADD_VALUES, SCATTER_REVERSE);
2179 auto post_proc_rhs = get_essential_fe();
2180 post_proc_rhs->f = f;
2182 post_proc_rhs.get());
2183 CHKERR VecScale(f, -1.0);
2184
2185#ifndef NDEBUG
2186 constexpr bool debug = false;
2187 if constexpr (debug) {
2188 double norm0;
2189 CHKERR VecNorm(f, NORM_2, &norm0);
2190 auto fd_check = vectorDuplicate(f);
2191 double eps = 1e-5;
2192 CHKERR calulate_fd_residual(eps, dm_diff_vec, fd_check);
2193 double nrm;
2194 CHKERR VecAXPY(fd_check, -1.0, f);
2195 CHKERR VecNorm(fd_check, NORM_2, &nrm);
2196 MOFEM_LOG("WORLD", Sev::inform)
2197 << " FD check for internal forces [ " << mode << " ]: " << nrm
2198 << " / " << norm0 << " ( " << (nrm / norm0) << " )";
2199 }
2200#endif
2201
2203 };
2204
2205 auto calulate_variance_of_displacement = [&](auto mode, auto mod_vec) {
2207 CHKERR KSPSolve(kspElastic, f, d);
2208 CHKERR VecGhostUpdateBegin(d, INSERT_VALUES, SCATTER_FORWARD);
2209 CHKERR VecGhostUpdateEnd(d, INSERT_VALUES, SCATTER_FORWARD);
2211 };
2212
2213 auto calculate_variance_of_objective_function = [&](auto mode, auto mod_vec) {
2215 *glob_objective_ptr = 0.0;
2216 *glob_objective_grad_ptr = 0.0;
2217 CHKERR DMoFEMLoopFiniteElements(dm, simple->getDomainFEName(),
2218 objective_fe);
2219 CHKERR VecSetValue(objective_function_gradient, mode,
2220 *glob_objective_grad_ptr, ADD_VALUES);
2221 std::array<double, 2> array = {*glob_objective_ptr,
2222 *glob_objective_grad_ptr};
2223 MPI_Allreduce(MPI_IN_PLACE, array.data(), 2, MPI_DOUBLE, MPI_SUM,
2224 mField.get_comm());
2225 *glob_objective_ptr = array[0];
2226 *glob_objective_grad_ptr = array[1];
2227 (*objective_function_value) += *glob_objective_ptr;
2228 MOFEM_LOG_CHANNEL("WORLD");
2229 MOFEM_LOG("WORLD", Sev::verbose) << " Objective gradient [ " << mode
2230 << " ]: " << *glob_objective_grad_ptr;
2232 };
2233
2234 CHKERR VecZeroEntries(objective_function_gradient);
2235 CHKERR VecZeroEntries(adjoint_vector);
2236 *objective_function_value = 0.0;
2237 int mode = 0;
2238 for (auto mod_vec : modeVecs) {
2239
2240 CHKERR set_variance_of_geometry(mode, mod_vec);
2241 CHKERR calculate_variance_internal_forces(mode, mod_vec);
2242 CHKERR calulate_variance_of_displacement(mode, mod_vec);
2243 CHKERR calculate_variance_of_objective_function(mode, mod_vec);
2244
2245 CHKERR VecAXPY(adjoint_vector, *glob_objective_grad_ptr, dm_diff_vec);
2246 ++mode;
2247 }
2248
2249 (*objective_function_value) /= modeVecs.size();
2250 MOFEM_LOG("WORLD", Sev::verbose)
2251 << "Objective function: " << *glob_objective_ptr;
2252
2253 CHKERR VecAssemblyBegin(objective_function_gradient);
2254 CHKERR VecAssemblyEnd(objective_function_gradient);
2255
2256 CHKERR VecAssemblyBegin(adjoint_vector);
2257 CHKERR VecAssemblyEnd(adjoint_vector);
2258 CHKERR VecGhostUpdateBegin(adjoint_vector, INSERT_VALUES, SCATTER_FORWARD);
2259 CHKERR VecGhostUpdateEnd(adjoint_vector, INSERT_VALUES, SCATTER_FORWARD);
2260
2262}
2263//! [calculateGradient]
2264
2265static char help[] = "...\n\n";
2266
2267/**
2268 * @brief Main function for topology optimization tutorial using adjoint method
2269 *
2270 * This tutorial demonstrates structural topology optimization using:
2271 * - MoFEM finite element library for elasticity analysis
2272 * - Adjoint method for efficient gradient computation
2273 * - TAO optimization library for design optimization
2274 * - Python interface for flexible objective function definition
2275 *
2276 * Workflow:
2277 * 1. Initialize MoFEM, PETSc and Python environments
2278 * 2. Read mesh and setup finite element problem
2279 * 3. Define objective function via Python interface
2280 * 4. Compute topology optimization modes as design variables
2281 * 5. Run gradient-based optimization using adjoint sensitivities
2282 * 6. Post-process optimized design
2283 *
2284 * The adjoint method enables efficient gradient computation with cost
2285 * independent of the number of design variables, making it suitable
2286 * for large-scale topology optimization problems.
2287 *
2288 * Required input files:
2289 * - Mesh file (.h5m format from CUBIT)
2290 * - Parameter file (param_file.petsc)
2291 * - Objective function (objective_function.py)
2292 *
2293 * @param argc Command line argument count
2294 * @param argv Command line argument values
2295 * @return int Exit code (0 for success)
2296 */
2297int main(int argc, char *argv[]) {
2298
2299 // Initialize Python environment for objective function interface
2300 Py_Initialize();
2301 np::initialize();
2302
2303 // Initialize MoFEM/PETSc and MOAB data structures
2304 const char param_file[] = "param_file.petsc";
2305 MoFEM::Core::Initialize(&argc, &argv, param_file, help);
2306
2307 auto core_log = logging::core::get();
2308 core_log->add_sink(
2310
2311 core_log->add_sink(
2312 LogManager::createSink(LogManager::getStrmSync(), "FieldEvaluator"));
2313 LogManager::setLog("FieldEvaluator");
2314 MOFEM_LOG_TAG("FieldEvaluator", "field_eval");
2315
2316 try {
2317
2318 //! [Register MoFEM discrete manager in PETSc]
2319 DMType dm_name = "DMMOFEM";
2320 CHKERR DMRegister_MoFEM(dm_name);
2321 DMType dm_name_mg = "DMMOFEM_MG";
2323 //! [Register MoFEM discrete manager in PETSc
2324
2325 //! [Create MoAB]
2326 moab::Core mb_instance; ///< mesh database
2327 moab::Interface &moab = mb_instance; ///< mesh database interface
2328 //! [Create MoAB]
2329
2330 //! [Create MoFEM]
2331 MoFEM::Core core(moab); ///< finite element database
2332 MoFEM::Interface &m_field = core; ///< finite element database interface
2333 //! [Create MoFEM]
2334
2335 //! [Example]
2336 Example ex(m_field);
2337 CHKERR ex.runProblem();
2338 //! [Example]
2339 }
2341
2343
2344 if (Py_FinalizeEx() < 0) {
2345 exit(120);
2346 }
2347}
2348
2349/**
2350 * @brief Implementation of ObjectiveFunctionData interface using Python integration
2351 *
2352 * This class provides a concrete implementation of the ObjectiveFunctionData interface
2353 * that bridges MoFEM C++ data structures with Python-defined objective functions.
2354 * It enables flexible definition of optimization objectives through Python scripting
2355 * while maintaining high-performance computation in the C++ finite element framework.
2356 *
2357 * Key features:
2358 * - Python function evaluation with automatic data conversion
2359 * - Support for objective function and gradient computations
2360 * - Topology optimization mode definition through Python
2361 * - Efficient NumPy array interfacing for large data sets
2362 * - Automatic memory management between C++ and Python
2363 *
2364 * The class handles:
2365 * 1. Loading and executing Python objective function scripts
2366 * 2. Converting MoFEM data structures to NumPy arrays
2367 * 3. Calling Python functions for objective evaluation
2368 * 4. Converting Python results back to MoFEM format
2369 * 5. Managing Python interpreter state and namespace
2370 *
2371 * Example Python interface functions that must be defined:
2372 * - objectiveFunction(coords, u, stress, strain) -> objective_value
2373 * - objectiveGradientStress(coords, u, stress, strain) -> gradient_wrt_stress
2374 * - numberOfModes(block_id) -> number_of_topology_modes
2375 * - blockModes(block_id, coords, centroid, bbox) -> mode_vectors
2376 */
2379 virtual ~ObjectiveFunctionDataImpl() = default;
2380
2381 /// Initialize Python interpreter and load objective function script
2382 MoFEMErrorCode initPython(const std::string py_file);
2383
2384 /**
2385 * @brief Evaluate objective function at current state
2386 *
2387 * Calls Python-defined objective function with current displacement,
2388 * stress, and strain fields. Used during optimization to compute
2389 * the objective value that drives the optimization process.
2390 *
2391 * @param coords Gauss point coordinates
2392 * @param u_ptr Displacement field values
2393 * @param stress_ptr Stress tensor values
2394 * @param strain_ptr Strain tensor values
2395 * @param o_ptr Output objective function values
2396 * @return MoFEMErrorCode Success or error code
2397 */
2400 boost::shared_ptr<MatrixDouble> u_ptr,
2401 boost::shared_ptr<MatrixDouble> stress_ptr,
2402 boost::shared_ptr<MatrixDouble> strain_ptr,
2403 boost::shared_ptr<VectorDouble> o_ptr);
2404
2405 /**
2406 * @brief Compute gradient of objective function with respect to stress
2407 *
2408 * Evaluates ∂f/∂σ where f is objective function and σ is stress tensor.
2409 * This gradient is used in the adjoint method to compute sensitivities
2410 * efficiently. Essential for gradient-based topology optimization.
2411 *
2412 * @param coords Gauss point coordinates
2413 * @param u_ptr Displacement field values
2414 * @param stress_ptr Stress tensor values
2415 * @param strain_ptr Strain tensor values
2416 * @param o_ptr Output gradient values
2417 * @return MoFEMErrorCode Success or error code
2418 */
2421 boost::shared_ptr<MatrixDouble> u_ptr,
2422 boost::shared_ptr<MatrixDouble> stress_ptr,
2423 boost::shared_ptr<MatrixDouble> strain_ptr,
2424 boost::shared_ptr<MatrixDouble> o_ptr);
2425
2426 /**
2427 * @brief Compute gradient of objective function with respect to strain
2428 *
2429 * Evaluates ∂f/∂ε where f is objective function and ε is strain tensor.
2430 * Used when objective function depends directly on strain measures,
2431 * complementing stress-based gradients in adjoint sensitivity analysis.
2432 *
2433 * @param coords Gauss point coordinates
2434 * @param u_ptr Displacement field values
2435 * @param stress_ptr Stress tensor values
2436 * @param strain_ptr Strain tensor values
2437 * @param o_ptr Output gradient values
2438 * @return MoFEMErrorCode Success or error code
2439 */
2442 boost::shared_ptr<MatrixDouble> u_ptr,
2443 boost::shared_ptr<MatrixDouble> stress_ptr,
2444 boost::shared_ptr<MatrixDouble> strain_ptr,
2445 boost::shared_ptr<MatrixDouble> o_ptr);
2446
2447 /**
2448 * @brief Compute gradient of objective function with respect to displacement
2449 *
2450 * Evaluates ∂f/∂u where f is objective function and u is displacement field.
2451 * This provides direct sensitivity of objective to displacement changes,
2452 * used as right-hand side in adjoint equation K^T * λ = ∂f/∂u.
2453 *
2454 * @param coords Gauss point coordinates
2455 * @param u_ptr Displacement field values
2456 * @param stress_ptr Stress tensor values
2457 * @param strain_ptr Strain tensor values
2458 * @param o_ptr Output gradient values
2459 * @return MoFEMErrorCode Success or error code
2460 */
2463 boost::shared_ptr<MatrixDouble> u_ptr,
2464 boost::shared_ptr<MatrixDouble> stress_ptr,
2465 boost::shared_ptr<MatrixDouble> strain_ptr,
2466 boost::shared_ptr<MatrixDouble> o_ptr);
2467
2468 /// Return number of topology optimization modes for given material block
2469 MoFEMErrorCode numberOfModes(int block_id, int &modes);
2470
2471 /**
2472 * @brief Define spatial topology modes for design optimization
2473 *
2474 * Generates basis functions that define how the geometry can be modified
2475 * during topology optimization. These modes serve as design variables
2476 * and define the design space for optimization.
2477 *
2478 * @param block_id Material block identifier
2479 * @param coords Element coordinates
2480 * @param centroid Block centroid coordinates
2481 * @param bbodx Bounding box dimensions [xmin,xmax,ymin,ymax,zmin,zmax]
2482 * @param o_ptr Output mode vectors
2483 * @return MoFEMErrorCode Success or error code
2484 */
2485 MoFEMErrorCode blockModes(int block_id, MatrixDouble &coords,
2486 std::array<double, 3> &centroid,
2487 std::array<double, 6> &bbodx, MatrixDouble &o_ptr);
2488
2489private:
2490 // Python interpreter objects for objective function evaluation
2491 bp::object mainNamespace; ///< Main Python namespace for script execution
2492 bp::object objectiveFunction; ///< Python function: f(coords,u,stress,strain) -> objective
2493 bp::object objectiveGradientStress; ///< Python function: ∂f/∂σ(coords,u,stress,strain) -> gradient
2494 bp::object objectiveGradientStrain; ///< Python function: ∂f/∂ε(coords,u,stress,strain) -> gradient
2495 bp::object objectiveGradientU; ///< Python function: ∂f/∂u(coords,u,stress,strain) -> gradient
2496 bp::object objectNumberOfModes; ///< Python function: numberOfModes(block_id) -> int
2497 bp::object objectBlockModes; ///< Python function: blockModes(block_id,coords,centroid,bbox) -> modes
2498
2499 /**
2500 * @brief Internal implementation for objective function evaluation
2501 *
2502 * Handles low-level Python function call with NumPy array conversion.
2503 * Converts MoFEM matrices to NumPy arrays, calls Python function,
2504 * and handles return value conversion.
2505 *
2506 * @param coords NumPy array of coordinates
2507 * @param u NumPy array of displacements
2508 * @param stress NumPy array of stress tensors
2509 * @param strain NumPy array of strain tensors
2510 * @param o Output NumPy array for objective values
2511 * @return MoFEMErrorCode Success or error code
2512 */
2514
2515 np::ndarray coords, np::ndarray u,
2516
2517 np::ndarray stress, np::ndarray strain, np::ndarray &o
2518
2519 );
2520
2521 /**
2522 * @brief Internal implementation for stress gradient computation
2523 *
2524 * Calls Python function to compute ∂f/∂σ with automatic array conversion.
2525 * Essential for adjoint-based sensitivity analysis in topology optimization.
2526 *
2527 * @param coords NumPy array of coordinates
2528 * @param u NumPy array of displacements
2529 * @param stress NumPy array of stress tensors
2530 * @param strain NumPy array of strain tensors
2531 * @param o Output NumPy array for gradient values
2532 * @return MoFEMErrorCode Success or error code
2533 */
2535
2536 np::ndarray coords, np::ndarray u,
2537
2538 np::ndarray stress, np::ndarray strain, np::ndarray &o
2539
2540 );
2541
2542 /**
2543 * @brief Internal implementation for strain gradient computation
2544 *
2545 * Evaluates ∂f/∂ε through Python interface with NumPy array handling.
2546 * Provides strain-based sensitivities for comprehensive gradient computation.
2547 *
2548 * @param coords NumPy array of coordinates
2549 * @param u NumPy array of displacements
2550 * @param stress NumPy array of stress tensors
2551 * @param strain NumPy array of strain tensors
2552 * @param o Output NumPy array for gradient values
2553 * @return MoFEMErrorCode Success or error code
2554 */
2556
2557 np::ndarray coords, np::ndarray u,
2558
2559 np::ndarray stress, np::ndarray strain, np::ndarray &o
2560
2561 );
2562
2563 /**
2564 * @brief Internal implementation for displacement gradient computation
2565 *
2566 * Computes ∂f/∂u through Python interface for adjoint equation right-hand side.
2567 * This gradient drives the adjoint solution that enables efficient sensitivity
2568 * computation independent of design variable count.
2569 *
2570 * @param coords NumPy array of coordinates
2571 * @param u NumPy array of displacements
2572 * @param stress NumPy array of stress tensors
2573 * @param strain NumPy array of strain tensors
2574 * @param o Output NumPy array for gradient values
2575 * @return MoFEMErrorCode Success or error code
2576 */
2578
2579 np::ndarray coords, np::ndarray u,
2580
2581 np::ndarray stress, np::ndarray strain, np::ndarray &o
2582
2583 );
2584
2585 /**
2586 * @brief Internal implementation for topology mode generation
2587 *
2588 * Calls Python function to generate spatial basis functions for topology
2589 * optimization. These modes define the design space and parametrize
2590 * allowable geometry modifications during optimization.
2591 *
2592 * @param block_id Material block identifier
2593 * @param coords NumPy array of element coordinates
2594 * @param centroid NumPy array of block centroid
2595 * @param bbodx NumPy array of bounding box dimensions
2596 * @param o_ptr Output NumPy array for mode vectors
2597 * @return MoFEMErrorCode Success or error code
2598 */
2600
2601 int block_id, np::ndarray coords, np::ndarray centroid, np::ndarray bbodx,
2602 np::ndarray &o_ptr
2603
2604 );
2605
2606 /**
2607 * @brief Convert std::vector to NumPy array for Python interface
2608 *
2609 * Efficient conversion from MoFEM data structures to NumPy arrays
2610 * for seamless Python function calls without data copying.
2611 *
2612 * @param data Source vector data
2613 * @param rows Number of rows in resulting array
2614 * @param nb_gauss_pts Number of Gauss points (affects array structure)
2615 * @return np::ndarray NumPy array for Python use
2616 */
2617 np::ndarray convertToNumPy(std::vector<double> &data, int rows,
2618 int nb_gauss_pts);
2619
2620 /**
2621 * @brief Convert raw pointer to NumPy array for Python interface
2622 *
2623 * Low-level conversion for direct memory access to create NumPy arrays.
2624 * Provides zero-copy conversion when possible for performance.
2625 *
2626 * @param ptr Raw data pointer
2627 * @param s Array size
2628 * @return np::ndarray NumPy array for Python use
2629 */
2630 np::ndarray convertToNumPy(double *ptr, int s);
2631
2632 /// Convert symmetric tensor storage to full matrix format
2634
2635 /// Convert full matrix to symmetric tensor storage format
2636 void copyToSymmetric(double *ptr, MatrixDouble &s);
2637};
2638
2639/**
2640 * @brief Factory function to create Python-integrated objective function interface
2641 *
2642 * Creates and initializes an ObjectiveFunctionDataImpl instance that bridges
2643 * MoFEM finite element computations with Python-defined objective functions.
2644 * This enables flexible objective function definition for topology optimization
2645 * while maintaining computational efficiency.
2646 *
2647 * The Python file must define specific functions with correct signatures:
2648 * - objectiveFunction(coords, u, stress, strain) -> objective_value
2649 * - objectiveGradientStress(coords, u, stress, strain) -> gradient_array
2650 * - objectiveGradientStrain(coords, u, stress, strain) -> gradient_array
2651 * - objectiveGradientU(coords, u, stress, strain) -> gradient_array
2652 * - numberOfModes(block_id) -> integer
2653 * - blockModes(block_id, coords, centroid, bbox) -> mode_array
2654 *
2655 * @param py_file Path to Python file containing objective function definitions
2656 * @return boost::shared_ptr<ObjectiveFunctionData> Configured objective function interface
2657 * @throws MoFEM exception if Python initialization fails
2658 */
2659boost::shared_ptr<ObjectiveFunctionData>
2661 auto ptr = boost::make_shared<ObjectiveFunctionDataImpl>();
2662 CHK_THROW_MESSAGE(ptr->initPython(py_file), "init python");
2663 return ptr;
2664}
2665
2666/**
2667 * @brief Initialize Python interpreter and load objective function script
2668 *
2669 * This method sets up the Python environment for objective function evaluation
2670 * by loading a Python script that defines the required optimization functions.
2671 * It establishes the bridge between MoFEM's C++ finite element computations
2672 * and user-defined Python objective functions.
2673 *
2674 * The Python script must define the following functions:
2675 * - f(coords, u, stress, strain): Main objective function
2676 * - f_stress(coords, u, stress, strain): Gradient w.r.t. stress ∂f/∂σ
2677 * - f_strain(coords, u, stress, strain): Gradient w.r.t. strain ∂f/∂ε
2678 * - f_u(coords, u, stress, strain): Gradient w.r.t. displacement ∂f/∂u
2679 * - number_of_modes(block_id): Return number of topology modes
2680 * - block_modes(block_id, coords, centroid, bbox): Define topology modes
2681 *
2682 * All functions receive NumPy arrays and must return NumPy arrays of
2683 * appropriate dimensions for the finite element computation.
2684 *
2685 * @param py_file Path to Python script containing objective function definitions
2686 * @return MoFEMErrorCode Success or error code
2687 * @throws MOFEM_OPERATION_UNSUCCESSFUL if Python script has errors
2688 */
2690ObjectiveFunctionDataImpl::initPython(const std::string py_file) {
2692 try {
2693
2694 // Create main Python module and namespace for script execution
2695 auto main_module = bp::import("__main__");
2696 mainNamespace = main_module.attr("__dict__");
2697
2698 // Execute the Python script in the main namespace
2699 bp::exec_file(py_file.c_str(), mainNamespace, mainNamespace);
2700
2701 // Create references to required Python functions for later calling
2702 objectiveFunction = mainNamespace["f"]; // Main objective function
2703 objectiveGradientStress = mainNamespace["f_stress"]; // ∂f/∂σ gradient function
2704 objectiveGradientStrain = mainNamespace["f_strain"]; // ∂f/∂ε gradient function
2705 objectiveGradientU = mainNamespace["f_u"]; // ∂f/∂u gradient function
2706 objectNumberOfModes = mainNamespace["number_of_modes"]; // Topology mode count function
2707 objectBlockModes = mainNamespace["block_modes"]; // Topology mode definition function
2708
2709 } catch (bp::error_already_set const &) {
2710 // Handle Python errors by printing to stderr and throwing MoFEM exception
2711 PyErr_Print();
2713 }
2715}
2716
2718 MatrixDouble f(9, s.size2());
2719 f.clear();
2722 auto t_f = getFTensor2FromMat<3, 3>(f);
2723 auto t_s = getFTensor2SymmetricFromMat<SPACE_DIM>(s);
2724 for (int ii = 0; ii != s.size2(); ++ii) {
2725 t_f(i, j) = t_s(i, j);
2726 ++t_f;
2727 ++t_s;
2728 }
2729 return f;
2730};
2731
2736
2737 ptr + 0 * s.size2(), ptr + 1 * s.size2(),
2738 ptr + 2 * s.size2(), ptr + 3 * s.size2(),
2739 ptr + 4 * s.size2(), ptr + 5 * s.size2(),
2740 ptr + 6 * s.size2(), ptr + 7 * s.size2(),
2741 ptr + 8 * s.size2()
2742
2743 };
2744 auto t_s = getFTensor2SymmetricFromMat<SPACE_DIM>(s);
2745 for (int ii = 0; ii != s.size2(); ++ii) {
2746 t_s(i, j) = (t_f(i, j) || t_f(j, i)) / 2.0;
2747 ++t_f;
2748 ++t_s;
2749 }
2750}
2751
2752/**
2753 * @brief Evaluate objective function at current finite element state
2754 *
2755 * This method bridges MoFEM finite element data with Python-defined objective
2756 * functions for topology optimization. It handles the complete data conversion
2757 * workflow from MoFEM matrices to NumPy arrays, calls the Python objective
2758 * function, and converts results back to MoFEM format.
2759 *
2760 * Process:
2761 * 1. Convert coordinate matrix to NumPy format for Python access
2762 * 2. Convert displacement field data to NumPy arrays
2763 * 3. Convert symmetric stress/strain tensors to full 3x3 matrix format
2764 * 4. Call Python objective function: f(coords, u, stress, strain)
2765 * 5. Extract results and copy back to MoFEM vector format
2766 *
2767 * The objective function typically computes scalar quantities like:
2768 * - Compliance: ∫ u^T * f dΩ (minimize structural deformation)
2769 * - Stress constraints: ∫ ||σ - σ_target||² dΩ (control stress distribution)
2770 * - Volume constraints: ∫ ρ dΩ (material usage limitations)
2771 *
2772 * @param coords Gauss point coordinates for current element
2773 * @param u_ptr Displacement field values at Gauss points
2774 * @param stress_ptr Cauchy stress tensor values (symmetric storage)
2775 * @param strain_ptr Strain tensor values (symmetric storage)
2776 * @param o_ptr Output objective function values at each Gauss point
2777 * @return MoFEMErrorCode Success or error code
2778 */
2780 MatrixDouble &coords, boost::shared_ptr<MatrixDouble> u_ptr,
2781 boost::shared_ptr<MatrixDouble> stress_ptr,
2782 boost::shared_ptr<MatrixDouble> strain_ptr,
2783 boost::shared_ptr<VectorDouble> o_ptr) {
2785 try {
2786
2787 // Convert coordinates to NumPy array for Python function
2788 auto np_coords =
2789 convertToNumPy(coords.data(), coords.size1(), coords.size2());
2790 // Convert displacement field to NumPy array
2791 auto np_u = convertToNumPy(u_ptr->data(), u_ptr->size1(), u_ptr->size2());
2792
2793 // Convert symmetric tensor storage to full matrix format for Python
2794 // MoFEM stores symmetric tensors in Voigt notation, Python expects full 3x3 matrices
2795 auto full_stress = copyToFull(*(stress_ptr));
2796 auto full_strain = copyToFull(*(strain_ptr));
2797
2798 // Create NumPy arrays for stress and strain tensors
2799 auto np_stress = convertToNumPy(full_stress.data(), full_stress.size1(),
2800 full_stress.size2());
2801 auto np_strain = convertToNumPy(full_strain.data(), full_strain.size1(),
2802 full_strain.size2());
2803
2804 // Prepare output array for objective function values
2805 np::ndarray np_output = np::empty(bp::make_tuple(strain_ptr->size2()),
2806 np::dtype::get_builtin<double>());
2807
2808 // Call Python objective function implementation
2809 CHKERR objectiveFunctionImpl(np_coords, np_u, np_stress, np_strain,
2810 np_output);
2811
2812 // Copy Python results back to MoFEM vector format
2813 o_ptr->resize(stress_ptr->size2(), false);
2814 double *val_ptr = reinterpret_cast<double *>(np_output.get_data());
2815 std::copy(val_ptr, val_ptr + strain_ptr->size2(), o_ptr->data().begin());
2816
2817 } catch (bp::error_already_set const &) {
2818 // Handle Python errors with detailed error reporting
2819 PyErr_Print();
2821 }
2823}
2824
2825/**
2826 * @brief Compute gradient of objective function with respect to stress tensor
2827 *
2828 * This method evaluates ∂f/∂σ, the partial derivative of the objective function
2829 * with respect to the Cauchy stress tensor. This gradient is fundamental to the
2830 * adjoint method for topology optimization, as it provides the driving force
2831 * for the adjoint equation solution.
2832 *
2833 * Mathematical context:
2834 * The adjoint method requires ∂f/∂σ to compute sensitivities efficiently.
2835 * For stress-based objectives like von Mises stress constraints:
2836 * ∂f/∂σ = ∂/∂σ[∫(σ_vm - σ_target)² dΩ] = 2(σ_vm - σ_target) * ∂σ_vm/∂σ
2837 *
2838 * Process:
2839 * 1. Convert all field data to NumPy format for Python processing
2840 * 2. Call Python function f_stress(coords, u, stress, strain)
2841 * 3. Python returns full 3x3 gradient matrices for each Gauss point
2842 * 4. Convert back to symmetric tensor storage used by MoFEM
2843 * 5. Store results for use in adjoint equation assembly
2844 *
2845 * The resulting gradients drive the adjoint solution that enables efficient
2846 * computation of design sensitivities independent of design variable count.
2847 *
2848 * @param coords Gauss point coordinates
2849 * @param u_ptr Displacement field values
2850 * @param stress_ptr Current stress tensor values (symmetric storage)
2851 * @param strain_ptr Current strain tensor values (symmetric storage)
2852 * @param o_ptr Output stress gradients ∂f/∂σ (symmetric storage)
2853 * @return MoFEMErrorCode Success or error code
2854 */
2856 MatrixDouble &coords, boost::shared_ptr<MatrixDouble> u_ptr,
2857 boost::shared_ptr<MatrixDouble> stress_ptr,
2858 boost::shared_ptr<MatrixDouble> strain_ptr,
2859 boost::shared_ptr<MatrixDouble> o_ptr) {
2861 try {
2862
2863 // Convert coordinates and displacement field to NumPy format
2864 auto np_coords =
2865 convertToNumPy(coords.data(), coords.size1(), coords.size2());
2866 auto np_u = convertToNumPy(u_ptr->data(), u_ptr->size1(), u_ptr->size2());
2867
2868 // Convert symmetric tensors to full 3x3 format for Python processing
2869 auto full_stress = copyToFull(*(stress_ptr));
2870 auto full_strain = copyToFull(*(strain_ptr));
2871
2872 // Create NumPy arrays for stress and strain tensors
2873 auto np_stress = convertToNumPy(full_stress.data(), full_stress.size1(),
2874 full_stress.size2());
2875 auto np_strain = convertToNumPy(full_strain.data(), full_strain.size1(),
2876 full_strain.size2());
2877
2878 // Prepare output array for stress gradients (full matrix format)
2879 np::ndarray np_output =
2880 np::empty(bp::make_tuple(full_strain.size1(), full_strain.size2()),
2881 np::dtype::get_builtin<double>());
2882
2883 // Call Python implementation for stress gradient computation
2884 CHKERR objectiveGradientStressImpl(np_coords, np_u, np_stress, np_strain,
2885 np_output);
2886
2887 // Prepare output matrix in symmetric format
2888 o_ptr->resize(stress_ptr->size1(), stress_ptr->size2(), false);
2889 double *val_ptr = reinterpret_cast<double *>(np_output.get_data());
2890 // Convert full matrix results back to symmetric tensor storage
2891 copyToSymmetric(val_ptr, *(o_ptr));
2892
2893 } catch (bp::error_already_set const &) {
2894 PyErr_Print();
2896 }
2898}
2899
2900/**
2901 * @brief Compute gradient of objective function with respect to strain tensor
2902 *
2903 * This method evaluates ∂f/∂ε, the partial derivative of the objective function
2904 * with respect to the strain tensor. While many structural objectives depend
2905 * primarily on stress, strain-based gradients are important for certain
2906 * optimization formulations and provide additional sensitivity information.
2907 *
2908 * Mathematical context:
2909 * For strain energy-based objectives: f = ½ε:C:ε
2910 * The gradient is: ∂f/∂ε = C:ε = σ (stress tensor)
2911 *
2912 * For strain-based constraints or objectives like strain concentration:
2913 * ∂f/∂ε = ∂/∂ε[∫(ε_vm - ε_target)² dΩ] = 2(ε_vm - ε_target) * ∂ε_vm/∂ε
2914 *
2915 * Process:
2916 * 1. Convert field data to NumPy format for Python compatibility
2917 * 2. Call Python function f_strain(coords, u, stress, strain)
2918 * 3. Python returns gradient matrices ∂f/∂ε for each Gauss point
2919 * 4. Convert from full 3x3 format back to symmetric storage
2920 * 5. Results used in adjoint sensitivity analysis
2921 *
2922 * This gradient complements stress-based gradients in comprehensive
2923 * sensitivity analysis for topology optimization problems.
2924 *
2925 * @param coords Gauss point coordinates
2926 * @param u_ptr Displacement field values
2927 * @param stress_ptr Current stress tensor values (symmetric storage)
2928 * @param strain_ptr Current strain tensor values (symmetric storage)
2929 * @param o_ptr Output strain gradients ∂f/∂ε (symmetric storage)
2930 * @return MoFEMErrorCode Success or error code
2931 */
2933 MatrixDouble &coords, boost::shared_ptr<MatrixDouble> u_ptr,
2934 boost::shared_ptr<MatrixDouble> stress_ptr,
2935 boost::shared_ptr<MatrixDouble> strain_ptr,
2936 boost::shared_ptr<MatrixDouble> o_ptr) {
2938 try {
2939
2940 // Convert coordinates and displacement data to NumPy format
2941 auto np_coords =
2942 convertToNumPy(coords.data(), coords.size1(), coords.size2());
2943 auto np_u = convertToNumPy(u_ptr->data(), u_ptr->size1(), u_ptr->size2());
2944
2945 // Convert symmetric tensor data to full 3x3 matrices for Python
2946 auto full_stress = copyToFull(*(stress_ptr));
2947 auto full_strain = copyToFull(*(strain_ptr));
2948 auto np_stress = convertToNumPy(full_stress.data(), full_stress.size1(),
2949 full_stress.size2());
2950 auto np_strain = convertToNumPy(full_strain.data(), full_strain.size1(),
2951 full_strain.size2());
2952
2953 // Prepare output array for strain gradients
2954 np::ndarray np_output =
2955 np::empty(bp::make_tuple(full_strain.size1(), full_strain.size2()),
2956 np::dtype::get_builtin<double>());
2957
2958 // Call Python implementation for strain gradient computation
2959 CHKERR objectiveGradientStrainImpl(np_coords, np_u, np_stress, np_strain,
2960 np_output);
2961 o_ptr->resize(strain_ptr->size1(), strain_ptr->size2(), false);
2962 double *val_ptr = reinterpret_cast<double *>(np_output.get_data());
2963 copyToSymmetric(val_ptr, *(o_ptr));
2964
2965 } catch (bp::error_already_set const &) {
2966 PyErr_Print();
2968 }
2970}
2971
2972/**
2973 * @brief Compute gradient of objective function with respect to displacement field
2974 *
2975 * This method evaluates ∂f/∂u, the partial derivative of the objective function
2976 * with respect to the displacement field. This gradient is crucial for the adjoint
2977 * method as it forms the right-hand side of the adjoint equation: K^T * λ = ∂f/∂u
2978 *
2979 * Mathematical context:
2980 * The adjoint method solves: K^T * λ = ∂f/∂u
2981 * where λ are the adjoint variables (Lagrange multipliers)
2982 *
2983 * For compliance minimization: f = ½u^T * K * u
2984 * The gradient is: ∂f/∂u = K * u (applied forces)
2985 *
2986 * For displacement-based constraints: f = ||u - u_target||²
2987 * The gradient is: ∂f/∂u = 2(u - u_target)
2988 *
2989 * Process:
2990 * 1. Convert all field data to NumPy format for Python processing
2991 * 2. Call Python function f_u(coords, u, stress, strain)
2992 * 3. Python returns displacement gradients for each component
2993 * 4. Copy results directly (no tensor conversion needed for vectors)
2994 * 5. Results drive adjoint equation solution for sensitivity analysis
2995 *
2996 * This gradient is fundamental to adjoint-based topology optimization,
2997 * enabling efficient sensitivity computation for any number of design variables.
2998 *
2999 * @param coords Gauss point coordinates
3000 * @param u_ptr Displacement field values
3001 * @param stress_ptr Current stress tensor values
3002 * @param strain_ptr Current strain tensor values
3003 * @param o_ptr Output displacement gradients ∂f/∂u
3004 * @return MoFEMErrorCode Success or error code
3005 */
3007 MatrixDouble &coords, boost::shared_ptr<MatrixDouble> u_ptr,
3008 boost::shared_ptr<MatrixDouble> stress_ptr,
3009 boost::shared_ptr<MatrixDouble> strain_ptr,
3010 boost::shared_ptr<MatrixDouble> o_ptr) {
3012 try {
3013
3014 // Convert coordinates and displacement field to NumPy format
3015 auto np_coords =
3016 convertToNumPy(coords.data(), coords.size1(), coords.size2());
3017 auto np_u = convertToNumPy(u_ptr->data(), u_ptr->size1(), u_ptr->size2());
3018
3019 // Convert stress and strain tensors to full matrix format
3020 auto full_stress = copyToFull(*(stress_ptr));
3021 auto full_strain = copyToFull(*(strain_ptr));
3022 auto np_stress = convertToNumPy(full_stress.data(), full_stress.size1(),
3023 full_stress.size2());
3024 auto np_strain = convertToNumPy(full_strain.data(), full_strain.size1(),
3025 full_strain.size2());
3026
3027 // Prepare output array for displacement gradients (same size as displacement field)
3028 np::ndarray np_output =
3029 np::empty(bp::make_tuple(u_ptr->size1(), u_ptr->size2()),
3030 np::dtype::get_builtin<double>());
3031
3032 // Call Python implementation for displacement gradient computation
3033 // Note: This should call objectiveGradientUImpl, not objectiveGradientStrainImpl
3034 CHKERR objectiveGradientUImpl(np_coords, np_u, np_stress, np_strain,
3035 np_output);
3036
3037 // Copy results directly to output matrix (no tensor conversion needed for vectors)
3038 o_ptr->resize(u_ptr->size1(), u_ptr->size2(), false);
3039 double *val_ptr = reinterpret_cast<double *>(np_output.get_data());
3040 std::copy(val_ptr, val_ptr + u_ptr->size1() * u_ptr->size2(),
3041 o_ptr->data().begin());
3042
3043 } catch (bp::error_already_set const &) {
3044 // Handle Python errors with detailed reporting
3045 PyErr_Print();
3047 }
3049}
3050
3051/**
3052 * @brief Generate spatial topology modes for design optimization
3053 *
3054 * This method defines the design parameterization for topology optimization by
3055 * generating spatial basis functions (modes) that describe how the geometry
3056 * can be modified during optimization. These modes serve as design variables
3057 * and define the feasible design space for the optimization problem.
3058 *
3059 * Mathematical context:
3060 * The geometry modification is parameterized as: x_new = x_original + Σ(αᵢ * φᵢ(x))
3061 * where αᵢ are design variables and φᵢ(x) are spatial mode functions
3062 *
3063 * Common mode types:
3064 * - Radial basis functions: φ(x) = exp(-||x-c||²/σ²) for localized changes
3065 * - Polynomial modes: φ(x) = xⁿyᵐzᵖ for global shape changes
3066 * - Sinusoidal modes: φ(x) = sin(kx)cos(ly) for periodic patterns
3067 * - Principal component modes: Derived from geometric sensitivity analysis
3068 *
3069 * Process:
3070 * 1. Query Python function for number of modes for this material block
3071 * 2. Convert coordinate data and geometric information to NumPy format
3072 * 3. Call Python function block_modes(block_id, coords, centroid, bbox)
3073 * 4. Python returns mode vectors for each coordinate at each mode
3074 * 5. Reshape and store modes for use as design variables in optimization
3075 *
3076 * The modes enable efficient design space exploration and gradient-based
3077 * optimization while maintaining geometric feasibility and smoothness.
3078 *
3079 * @param block_id Material block identifier for mode generation
3080 * @param coords Element coordinates where modes are evaluated
3081 * @param centroid Geometric centroid of the material block [x,y,z]
3082 * @param bbodx Bounding box dimensions [xmin,xmax,ymin,ymax,zmin,zmax]
3083 * @param o_ptr Output matrix: modes × (coordinates × spatial_dimension)
3084 * @return MoFEMErrorCode Success or error code
3085 */
3087 int block_id, MatrixDouble &coords, std::array<double, 3> &centroid,
3088 std::array<double, 6> &bbodx, MatrixDouble &o_ptr) {
3090 try {
3091
3092 // Query Python function for number of topology modes for this block
3093 int nb_modes = bp::extract<int>(objectNumberOfModes(block_id));
3094
3095 // Convert coordinate matrix to NumPy format for Python processing
3096 auto np_coords =
3097 convertToNumPy(coords.data(), coords.size1(), coords.size2());
3098
3099 // Convert geometric information to NumPy arrays
3100 auto np_centroid = convertToNumPy(centroid.data(), 3); // Block centroid [x,y,z]
3101 auto np_bbodx = convertToNumPy(bbodx.data(), 6); // Bounding box [xmin,xmax,ymin,ymax,zmin,zmax]
3102
3103 // Prepare output array: [coordinates × modes × spatial_dimensions]
3104 np::ndarray np_output =
3105 np::empty(bp::make_tuple(coords.size1(), nb_modes, 3),
3106 np::dtype::get_builtin<double>());
3107
3108 // Call Python implementation to generate topology modes
3109 CHKERR blockModesImpl(block_id, np_coords, np_centroid, np_bbodx,
3110 np_output);
3111
3112 // Reshape output matrix for MoFEM format: [modes × (coordinates * spatial_dimensions)]
3113 o_ptr.resize(nb_modes, coords.size1() * coords.size2(), false);
3114 double *val_ptr = reinterpret_cast<double *>(np_output.get_data());
3115 // Copy flattened mode data to output matrix
3116 std::copy(val_ptr, val_ptr + coords.size1() * coords.size2() * nb_modes,
3117 o_ptr.data().begin());
3118
3119 } catch (bp::error_already_set const &) {
3120 // Handle Python errors in mode generation
3121 PyErr_Print();
3123 }
3125}
3126
3128
3129 np::ndarray coords, np::ndarray u,
3130
3131 np::ndarray stress, np::ndarray strain, np::ndarray &o
3132
3133) {
3135 try {
3136
3137 // call python function
3138 o = bp::extract<np::ndarray>(objectiveFunction(coords, u, stress, strain));
3139
3140 } catch (bp::error_already_set const &) {
3141 // print all other errors to stderr
3142 PyErr_Print();
3144 }
3146}
3147
3149
3150 np::ndarray coords, np::ndarray u,
3151
3152 np::ndarray stress, np::ndarray strain, np::ndarray &o
3153
3154) {
3156 try {
3157
3158 // call python function
3159 o = bp::extract<np::ndarray>(
3160 objectiveGradientStress(coords, u, stress, strain));
3161
3162 } catch (bp::error_already_set const &) {
3163 // print all other errors to stderr
3164 PyErr_Print();
3166 }
3168}
3169
3171
3172 np::ndarray coords, np::ndarray u,
3173
3174 np::ndarray stress, np::ndarray strain, np::ndarray &o
3175
3176) {
3178 try {
3179
3180 // call python function
3181 o = bp::extract<np::ndarray>(
3182 objectiveGradientStrain(coords, u, stress, strain));
3183
3184 } catch (bp::error_already_set const &) {
3185 // print all other errors to stderr
3186 PyErr_Print();
3188 }
3190}
3191
3193
3194 np::ndarray coords, np::ndarray u,
3195
3196 np::ndarray stress, np::ndarray strain, np::ndarray &o
3197
3198) {
3200 try {
3201
3202 // call python function
3203 o = bp::extract<np::ndarray>(objectiveGradientU(coords, u, stress, strain));
3204
3205 } catch (bp::error_already_set const &) {
3206 // print all other errors to stderr
3207 PyErr_Print();
3209 }
3211}
3212
3214 int &modes) {
3216 try {
3217
3218 modes = bp::extract<int>(objectNumberOfModes(block_id));
3219
3220 } catch (bp::error_already_set const &) {
3221 // print all other errors to stderr
3222 PyErr_Print();
3224 }
3226}
3227
3229 np::ndarray coords,
3230 np::ndarray centroid,
3231 np::ndarray bbodx,
3232 np::ndarray &o) {
3234 try {
3235 // call python function
3236 o = bp::extract<np::ndarray>(
3237 objectBlockModes(block_id, coords, centroid, bbodx));
3238 } catch (bp::error_already_set const &) {
3239 // print all other errors to stderr
3240 PyErr_Print();
3242 }
3244}
3245
3246/**
3247 * @brief Converts a std::vector<double> to a NumPy ndarray.
3248 *
3249 * This function wraps the given vector data into a NumPy array with the
3250 * specified number of rows and Gauss points. The resulting ndarray shares
3251 * memory with the input vector, so changes to one will affect the other.
3252 *
3253 * @param data Reference to the vector containing double values to be converted.
3254 * @param rows Number of rows in the resulting NumPy array.
3255 * @param nb_gauss_pts Number of Gauss points (columns) in the resulting NumPy
3256 * array.
3257 * @return np::ndarray NumPy array view of the input data.
3258 *
3259 * @note
3260 * - `size` specifies the shape of the resulting ndarray as a tuple (rows,
3261 * nb_gauss_pts).
3262 * - `stride` specifies the step size in bytes to move to the next element in
3263 * memory. Here, it is set to sizeof(double), indicating contiguous storage for
3264 * each element.
3265 */
3266inline np::ndarray
3267ObjectiveFunctionDataImpl::convertToNumPy(std::vector<double> &data, int rows,
3268 int nb_gauss_pts) {
3269 auto dtype = np::dtype::get_builtin<double>();
3270 auto size = bp::make_tuple(rows, nb_gauss_pts);
3271 auto stride = bp::make_tuple(nb_gauss_pts * sizeof(double), sizeof(double));
3272 return (np::from_data(data.data(), dtype, size, stride, bp::object()));
3273}
3274
3275inline np::ndarray ObjectiveFunctionDataImpl::convertToNumPy(double *ptr,
3276 int s) {
3277 auto dtype = np::dtype::get_builtin<double>();
3278 auto size = bp::make_tuple(s);
3279 auto stride = bp::make_tuple(sizeof(double));
3280 return (np::from_data(ptr, dtype, size, stride, bp::object()));
3281}
3282
3284 const std::string block_name, int dim) {
3285 Range r;
3286
3287 auto mesh_mng = m_field.getInterface<MeshsetsManager>();
3288 auto bcs = mesh_mng->getCubitMeshsetPtr(
3289
3290 std::regex((boost::format("%s(.*)") % block_name).str())
3291
3292 );
3293
3294 for (auto bc : bcs) {
3295 Range faces;
3296 CHK_MOAB_THROW(bc->getMeshsetIdEntitiesByDimension(m_field.get_moab(), dim,
3297 faces, true),
3298 "get meshset ents");
3299 r.merge(faces);
3300 }
3301
3302 for (auto dd = dim - 1; dd >= 0; --dd) {
3303 if (dd >= 0) {
3304 Range ents;
3305 CHK_MOAB_THROW(m_field.get_moab().get_adjacencies(r, dd, false, ents,
3306 moab::Interface::UNION),
3307 "get adjs");
3308 r.merge(ents);
3309 } else {
3310 Range verts;
3311 CHK_MOAB_THROW(m_field.get_moab().get_connectivity(r, verts),
3312 "get verts");
3313 r.merge(verts);
3314 }
3316 m_field.getInterface<CommInterface>()->synchroniseEntities(r), "comm");
3317 }
3318
3319 return r;
3320};
3321
3322MoFEMErrorCode save_range(moab::Interface &moab, const std::string name,
3323 const Range r) {
3325 auto out_meshset = get_temp_meshset_ptr(moab);
3326 CHKERR moab.add_entities(*out_meshset, r);
3327 CHKERR moab.write_file(name.c_str(), "VTK", "", out_meshset->get_ptr(), 1);
3329};
#define MOFEM_LOG_SYNCHRONISE(comm)
Synchronise "SYNC" channel.
#define FTENSOR_INDEX(DIM, I)
void simple(double P1[], double P2[], double P3[], double c[], const int N)
Definition acoustic.cpp:69
auto diff_symmetrize(FTensor::Number< DIM >)
Definition adjoint.cpp:1649
static char help[]
[calculateGradient]
Definition adjoint.cpp:2265
PetscBool is_plane_strain
Definition adjoint.cpp:42
constexpr int SPACE_DIM
[Define dimension]
Definition adjoint.cpp:28
constexpr double poisson_ratio
Poisson's ratio ν
Definition adjoint.cpp:38
constexpr int BASE_DIM
[Constants and material properties]
Definition adjoint.cpp:25
constexpr double shear_modulus_G
Shear modulus G = E/(2(1+ν))
Definition adjoint.cpp:40
constexpr IntegrationType I
Use Gauss quadrature for integration.
Definition adjoint.cpp:33
constexpr double bulk_modulus_K
Bulk modulus K = E/(3(1-2ν))
Definition adjoint.cpp:39
boost::shared_ptr< ObjectiveFunctionData > create_python_objective_function(std::string py_file)
Factory function to create Python-integrated objective function interface.
Definition adjoint.cpp:2660
constexpr AssemblyType A
[Define dimension]
Definition adjoint.cpp:32
Range get_range_from_block(MoFEM::Interface &m_field, const std::string block_name, int dim)
Definition adjoint.cpp:3283
constexpr double young_modulus
[Material properties for linear elasticity]
Definition adjoint.cpp:37
int main()
constexpr double a
ElementsAndOps< SPACE_DIM >::DomainEle DomainEle
constexpr int SPACE_DIM
[Define dimension]
ElementsAndOps< SPACE_DIM >::BoundaryEle BoundaryEle
@ QUIET
@ ROW
#define CATCH_ERRORS
Catch errors.
@ MF_EXIST
FieldApproximationBase
approximation base
Definition definitions.h:58
@ LASTBASE
Definition definitions.h:69
@ AINSWORTH_LEGENDRE_BASE
Ainsworth Cole (Legendre) approx. base .
Definition definitions.h:60
@ DEMKOWICZ_JACOBI_BASE
Definition definitions.h:66
#define CHK_THROW_MESSAGE(err, msg)
Check and throw MoFEM exception.
#define MoFEMFunctionReturnHot(a)
Last executable line of each PETSc function used for error handling. Replaces return()
@ H1
continuous field
Definition definitions.h:85
@ NOSPACE
Definition definitions.h:83
#define MYPCOMM_INDEX
default communicator number PCOMM
#define MoFEMFunctionBegin
First executable line of each MoFEM function, used for error handling. Final line of MoFEM functions ...
#define CHK_MOAB_THROW(err, msg)
Check error code of MoAB function and throw MoFEM exception.
@ MOFEM_NOT_FOUND
Definition definitions.h:33
@ MOFEM_OPERATION_UNSUCCESSFUL
Definition definitions.h:34
@ MOFEM_DATA_INCONSISTENCY
Definition definitions.h:31
#define MoFEMFunctionReturn(a)
Last executable line of each PETSc function used for error handling. Replaces return()
#define CHKERR
Inline error check.
#define MoFEMFunctionBeginHot
First executable line of each MoFEM function, used for error handling. Final line of MoFEM functions ...
PostProcEleByDim< SPACE_DIM >::PostProcEleDomain PostProcEleDomain
PostProcEleByDim< SPACE_DIM >::PostProcEleBdy PostProcEleBdy
@ F
auto integration_rule
PetscErrorCode DMMoFEMSetIsPartitioned(DM dm, PetscBool is_partitioned)
Definition DMMoFEM.cpp:1113
PetscErrorCode DMMoFEMCreateSubDM(DM subdm, DM dm, const char problem_name[])
Must be called by user to set Sub DM MoFEM data structures.
Definition DMMoFEM.cpp:215
PetscErrorCode DMMoFEMAddElement(DM dm, std::string fe_name)
add element to dm
Definition DMMoFEM.cpp:488
PetscErrorCode DMMoFEMSetSquareProblem(DM dm, PetscBool square_problem)
set squared problem
Definition DMMoFEM.cpp:450
PetscErrorCode DMMoFEMCreateMoFEM(DM dm, MoFEM::Interface *m_field_ptr, const char problem_name[], const MoFEM::BitRefLevel bit_level, const MoFEM::BitRefLevel bit_mask=MoFEM::BitRefLevel().set())
Must be called by user to set MoFEM data structures.
Definition DMMoFEM.cpp:114
PetscErrorCode DMoFEMPostProcessFiniteElements(DM dm, MoFEM::FEMethod *method)
execute finite element method for each element in dm (problem)
Definition DMMoFEM.cpp:546
PetscErrorCode DMoFEMMeshToLocalVector(DM dm, Vec l, InsertMode mode, ScatterMode scatter_mode)
set local (or ghosted) vector values on mesh for partition only
Definition DMMoFEM.cpp:514
PetscErrorCode DMMoFEMAddSubFieldRow(DM dm, const char field_name[])
Definition DMMoFEM.cpp:238
PetscErrorCode DMRegister_MoFEM(const char sname[])
Register MoFEM problem.
Definition DMMoFEM.cpp:43
MoFEMErrorCode DMRegister_MGViaApproxOrders(const char sname[])
Register DM for Multi-Grid via approximation orders.
PetscErrorCode DMoFEMLoopFiniteElements(DM dm, const char fe_name[], MoFEM::FEMethod *method, CacheTupleWeakPtr cache_ptr=CacheTupleSharedPtr())
Executes FEMethod for finite elements in DM.
Definition DMMoFEM.cpp:576
auto createDMVector(DM dm)
Get smart vector from DM.
Definition DMMoFEM.hpp:1234
PetscErrorCode DMMoFEMAddSubFieldCol(DM dm, const char field_name[])
Definition DMMoFEM.cpp:280
auto createDMMatrix(DM dm)
Get smart matrix from DM.
Definition DMMoFEM.hpp:1191
PetscErrorCode DMoFEMPreProcessFiniteElements(DM dm, MoFEM::FEMethod *method)
execute finite element method for each element in dm (problem)
Definition DMMoFEM.cpp:536
virtual MoFEMErrorCode add_ents_to_finite_element_by_dim(const EntityHandle entities, const int dim, const std::string name, const bool recursive=true)=0
add entities to finite element
virtual MoFEMErrorCode add_finite_element(const std::string &fe_name, enum MoFEMTypes bh=MF_EXCL, int verb=DEFAULT_VERBOSITY)=0
add finite element
virtual MoFEMErrorCode build_finite_elements(int verb=DEFAULT_VERBOSITY)=0
Build finite elements.
virtual MoFEMErrorCode modify_finite_element_add_field_col(const std::string &fe_name, const std::string name_row)=0
set field col which finite element use
virtual MoFEMErrorCode modify_finite_element_add_field_row(const std::string &fe_name, const std::string name_row)=0
set field row which finite element use
virtual MoFEMErrorCode modify_finite_element_add_field_data(const std::string &fe_name, const std::string name_field)=0
set finite element field data
virtual MoFEMErrorCode build_fields(int verb=DEFAULT_VERBOSITY)=0
virtual MoFEMErrorCode add_ents_to_field_by_dim(const Range &ents, const int dim, const std::string &name, int verb=DEFAULT_VERBOSITY)=0
Add entities to field meshset.
virtual MoFEMErrorCode set_field_order(const EntityHandle meshset, const EntityType type, const std::string &name, const ApproximationOrder order, int verb=DEFAULT_VERBOSITY)=0
Set order approximation of the entities in the field.
IntegrationType
Form integrator integration types.
AssemblyType
[Storage and set boundary conditions]
@ GAUSS
Gaussian quadrature integration.
static LoggerType & setLog(const std::string channel)
Set ans resset chanel logger.
#define MOFEM_LOG(channel, severity)
Log.
#define MOFEM_LOG_TAG(channel, tag)
Tag channel.
#define MOFEM_LOG_CHANNEL(channel)
Set and reset channel.
virtual MoFEMErrorCode loop_dofs(const Problem *problem_ptr, const std::string &field_name, RowColData rc, DofMethod &method, int lower_rank, int upper_rank, int verb=DEFAULT_VERBOSITY)=0
Make a loop over dofs.
MoFEMErrorCode getCubitMeshsetPtr(const int ms_id, const CubitBCType cubit_bc_type, const CubitMeshSets **cubit_meshset_ptr) const
get cubit meshset
FTensor::Index< 'i', SPACE_DIM > i
const double v
phase velocity of light in medium (cm/ns)
FTensor::Index< 'J', DIM1 > J
Definition level_set.cpp:30
FTensor::Index< 'l', 3 > l
FTensor::Index< 'j', 3 > j
FTensor::Index< 'k', 3 > k
const FTensor::Tensor2< T, Dim, Dim > Vec
constexpr double eps
Definition HenckyOps.hpp:13
PetscErrorCode MoFEMErrorCode
MoFEM/PETSc error code.
implementation of Data Operators for Forces and Sources
Definition Common.hpp:10
auto createKSP(MPI_Comm comm)
PetscErrorCode DMMoFEMSetDestroyProblem(DM dm, PetscBool destroy_problem)
Definition DMMoFEM.cpp:434
PetscErrorCode PetscOptionsGetInt(PetscOptions *, const char pre[], const char name[], PetscInt *ivalue, PetscBool *set)
static const bool debug
PetscErrorCode PetscOptionsGetBool(PetscOptions *, const char pre[], const char name[], PetscBool *bval, PetscBool *set)
SmartPetscObj< Vec > vectorDuplicate(Vec vec)
Create duplicate vector of smart vector.
PetscErrorCode PetscOptionsGetRealArray(PetscOptions *, const char pre[], const char name[], PetscReal dval[], PetscInt *nmax, PetscBool *set)
auto createVectorMPI(MPI_Comm comm, PetscInt n, PetscInt N)
Create MPI Vector.
auto getDMKspCtx(DM dm)
Get KSP context data structure used by DM.
Definition DMMoFEM.hpp:1248
static MoFEMErrorCode invertTensor(FTensor::Tensor2< T1, DIM, DIM > &t, T2 &det, FTensor::Tensor2< T3, DIM, DIM > &inv_t)
static auto determinantTensor(FTensor::Tensor2< T, DIM, DIM > &t)
Calculate the determinant of a tensor of rank DIM.
static auto getFTensor0FromVec(ublas::vector< T, A > &data)
Get tensor rank 0 (scalar) form data vector.
PetscErrorCode PetscOptionsGetEList(PetscOptions *, const char pre[], const char name[], const char *const *list, PetscInt next, PetscInt *value, PetscBool *set)
PetscErrorCode PetscOptionsGetString(PetscOptions *, const char pre[], const char name[], char str[], size_t size, PetscBool *set)
auto get_temp_meshset_ptr(moab::Interface &moab)
Create smart pointer to temporary meshset.
PetscErrorCode TaoSetObjectiveAndGradient(Tao tao, Vec x, PetscReal *f, Vec g, void *ctx)
Sets the objective function value and gradient for a TAO optimization solver.
Definition TaoCtx.cpp:178
auto createDM(MPI_Comm comm, const std::string dm_type_name)
Creates smart DM object.
MoFEMErrorCode VecSetValues(Vec V, const EntitiesFieldData::EntData &data, const double *ptr, InsertMode iora)
Assemble PETSc vector.
auto createTao(MPI_Comm comm)
constexpr IntegrationType I
constexpr AssemblyType A
[Define dimension]
OpPostProcMapInMoab< SPACE_DIM, SPACE_DIM > OpPPMap
constexpr auto field_name
static constexpr int approx_order
OpBaseImpl< PETSC, EdgeEleOp > OpBase
Definition radiation.cpp:29
FormsIntegrators< DomainEleOp >::Assembly< PETSC >::BiLinearForm< GAUSS >::OpMass< 1, SPACE_DIM > OpMass
[Only used with Hooke equation (linear material model)]
Definition seepage.cpp:56
constexpr double g
FTensor::Index< 'm', 3 > m
Boundary conditions marker.
Definition elastic.cpp:39
[Define entities]
Definition elastic.cpp:38
[Example]
Definition plastic.cpp:216
MoFEMErrorCode boundaryCondition()
Apply essential boundary conditions.
MoFEMErrorCode assembleSystem()
Setup operators in finite element pipeline.
MoFEMErrorCode readMesh()
Read mesh from file and setup meshsets.
SmartPetscObj< KSP > kspElastic
Linear solver for elastic problem.
Definition adjoint.cpp:211
std::vector< SmartPetscObj< Vec > > modeVecs
Topology mode vectors (design variables)
Definition adjoint.cpp:216
FieldApproximationBase base
Choice of finite element basis functions
Definition plot_base.cpp:68
std::vector< std::array< double, 3 > > modeCentroids
Centroids of optimization blocks
Definition adjoint.cpp:217
MoFEMErrorCode topologyModes()
Compute topology optimization modes.
Definition adjoint.cpp:746
SmartPetscObj< Vec > initialGeometry
Initial geometry field.
Definition adjoint.cpp:219
int fieldOrder
Polynomial order for approximation.
Definition adjoint.cpp:208
Example(MoFEM::Interface &m_field)
Definition adjoint.cpp:179
SmartPetscObj< Mat > M
MoFEMErrorCode runProblem()
Main driver function for the optimization process.
MoFEMErrorCode calculateGradient(PetscReal *objective_function_value, Vec objective_function_gradient, Vec adjoint_vector)
Calculate objective function gradient using adjoint method.
Definition adjoint.cpp:1947
MoFEMErrorCode setupAdJoint()
Setup adjoint fields and finite elements
Definition adjoint.cpp:623
MoFEM::Interface & mField
Reference to MoFEM interface.
Definition plastic.cpp:226
std::vector< std::array< double, 6 > > modeBBoxes
Bounding boxes of optimization blocks.
Definition adjoint.cpp:218
boost::shared_ptr< ObjectiveFunctionData > pythonPtr
Interface to Python objective function.
Definition adjoint.cpp:213
SmartPetscObj< DM > adjointDM
Data manager for adjoint problem.
Definition adjoint.cpp:212
MoFEMErrorCode setupProblem()
Setup fields, approximation spaces and DOFs.
MoFEMErrorCode postprocessElastic(int iter, SmartPetscObj< Vec > adjoint_vector=nullptr)
Post-process and output results.
Definition adjoint.cpp:1347
MoFEMErrorCode solveElastic()
Solve forward elastic problem.
Definition adjoint.cpp:1219
boost::shared_ptr< MatrixDouble > vectorFieldPtr
Field values at evaluation points.
Definition adjoint.cpp:187
Add operators pushing bases from local to physical configuration.
Boundary condition manager for finite element problem setup.
Managing BitRefLevels.
MoFEMErrorCode synchroniseEntities(Range &ent, std::map< int, Range > *received_ents, int verb=DEFAULT_VERBOSITY)
synchronize entity range on processors (collective)
virtual moab::Interface & get_moab()=0
virtual MoFEMErrorCode build_adjacencies(const Range &ents, int verb=DEFAULT_VERBOSITY)=0
build adjacencies
virtual MoFEMErrorCode add_field(const std::string name, const FieldSpace space, const FieldApproximationBase base, const FieldCoefficientsNumber nb_of_coefficients, const TagType tag_type=MB_TAG_SPARSE, const enum MoFEMTypes bh=MF_EXCL, int verb=DEFAULT_VERBOSITY)=0
Add field.
virtual MPI_Comm & get_comm() const =0
virtual int get_comm_rank() const =0
Core (interface) class.
Definition Core.hpp:82
static MoFEMErrorCode Initialize(int *argc, char ***args, const char file[], const char help[])
Initializes the MoFEM database PETSc, MOAB and MPI.
Definition Core.cpp:72
static MoFEMErrorCode Finalize()
Checks for options to be called at the conclusion of the program.
Definition Core.cpp:118
Deprecated interface functions.
Definition of the displacement bc data structure.
Definition BCData.hpp:72
Data on single entity (This is passed as argument to DataOperator::doWork)
FTensor::Tensor1< FTensor::PackPtr< double *, Tensor_Dim >, Tensor_Dim > getFTensor1DiffN(const FieldApproximationBase base)
Get derivatives of base functions.
FTensor::Tensor0< FTensor::PackPtr< double *, 1 > > getFTensor0N(const FieldApproximationBase base)
Get base function as Tensor0.
MatrixDouble & getN(const FieldApproximationBase base)
get base functions this return matrix (nb. of rows is equal to nb. of Gauss pts, nb....
const VectorInt & getIndices() const
Get global indices of degrees of freedom on entity.
Class (Function) to enforce essential constrains on the left hand side diagonal.
Definition Essential.hpp:33
Class (Function) to enforce essential constrains on the right hand side diagonal.
Definition Essential.hpp:41
Class (Function) to enforce essential constrains.
Definition Essential.hpp:25
Basic algebra on fields.
Definition FieldBlas.hpp:21
MoFEMErrorCode fieldLambdaOnValues(OneFieldFunctionOnValues lambda, const std::string field_name, Range *ents_ptr=nullptr)
field lambda
Definition FieldBlas.cpp:21
Field evaluator interface.
static boost::shared_ptr< SinkType > createSink(boost::shared_ptr< std::ostream > stream_ptr, std::string comm_filter)
Create a sink object.
static boost::shared_ptr< std::ostream > getStrmWorld()
Get the strm world object.
static boost::shared_ptr< std::ostream > getStrmSync()
Get the strm sync object.
Interface for managing meshsets containing materials and boundary conditions.
Assembly methods.
Definition Natural.hpp:65
Get field gradients at integration pts for scalar field rank 0, i.e. vector field.
Specialization for double precision vector field values calculation.
Element used to execute operators on side of the element.
Post post-proc data at points from hash maps.
Template struct for dimension-specific finite element types.
PipelineManager interface.
MoFEMErrorCode setDomainRhsIntegrationRule(RuleHookFun rule)
Set integration rule for domain right-hand side finite element.
Problem manager is used to build and partition problems.
Projection of edge entities with one mid-node on hierarchical basis.
Simple interface for fast problem set-up.
Definition Simple.hpp:27
MoFEMErrorCode getOptions()
get options
Definition Simple.cpp:180
MoFEMErrorCode getDM(DM *dm)
Get DM.
Definition Simple.cpp:800
intrusive_ptr for managing petsc objects
MoFEMErrorCode getInterface(IFACE *&iface) const
Get interface reference to pointer of interface.
Vector manager is used to create vectors \mofem_vectors.
Implementation of ObjectiveFunctionData interface using Python integration.
Definition adjoint.cpp:2377
MoFEMErrorCode objectiveGradientStrainImpl(np::ndarray coords, np::ndarray u, np::ndarray stress, np::ndarray strain, np::ndarray &o)
Internal implementation for strain gradient computation.
Definition adjoint.cpp:3170
MoFEMErrorCode numberOfModes(int block_id, int &modes)
Return number of topology optimization modes for given material block.
Definition adjoint.cpp:3213
MoFEMErrorCode blockModesImpl(int block_id, np::ndarray coords, np::ndarray centroid, np::ndarray bbodx, np::ndarray &o_ptr)
Internal implementation for topology mode generation.
Definition adjoint.cpp:3228
bp::object objectNumberOfModes
Python function: numberOfModes(block_id) -> int.
Definition adjoint.cpp:2496
MoFEMErrorCode evalObjectiveGradientStrain(MatrixDouble &coords, boost::shared_ptr< MatrixDouble > u_ptr, boost::shared_ptr< MatrixDouble > stress_ptr, boost::shared_ptr< MatrixDouble > strain_ptr, boost::shared_ptr< MatrixDouble > o_ptr)
Compute gradient of objective function with respect to strain.
Definition adjoint.cpp:2932
bp::object objectiveGradientStress
Python function: ∂f/∂σ(coords,u,stress,strain) -> gradient
Definition adjoint.cpp:2493
ObjectiveFunctionDataImpl()=default
MoFEMErrorCode initPython(const std::string py_file)
Initialize Python interpreter and load objective function script.
Definition adjoint.cpp:2690
MoFEMErrorCode blockModes(int block_id, MatrixDouble &coords, std::array< double, 3 > &centroid, std::array< double, 6 > &bbodx, MatrixDouble &o_ptr)
Define spatial topology modes for design optimization.
Definition adjoint.cpp:3086
bp::object objectiveGradientStrain
Python function: ∂f/∂ε(coords,u,stress,strain) -> gradient.
Definition adjoint.cpp:2494
MoFEMErrorCode evalObjectiveGradientU(MatrixDouble &coords, boost::shared_ptr< MatrixDouble > u_ptr, boost::shared_ptr< MatrixDouble > stress_ptr, boost::shared_ptr< MatrixDouble > strain_ptr, boost::shared_ptr< MatrixDouble > o_ptr)
Compute gradient of objective function with respect to displacement.
Definition adjoint.cpp:3006
virtual ~ObjectiveFunctionDataImpl()=default
MoFEMErrorCode evalObjectiveGradientStress(MatrixDouble &coords, boost::shared_ptr< MatrixDouble > u_ptr, boost::shared_ptr< MatrixDouble > stress_ptr, boost::shared_ptr< MatrixDouble > strain_ptr, boost::shared_ptr< MatrixDouble > o_ptr)
Compute gradient of objective function with respect to stress.
Definition adjoint.cpp:2855
bp::object objectiveFunction
Python function: f(coords,u,stress,strain) -> objective.
Definition adjoint.cpp:2492
MoFEMErrorCode evalObjectiveFunction(MatrixDouble &coords, boost::shared_ptr< MatrixDouble > u_ptr, boost::shared_ptr< MatrixDouble > stress_ptr, boost::shared_ptr< MatrixDouble > strain_ptr, boost::shared_ptr< VectorDouble > o_ptr)
Evaluate objective function at current state.
Definition adjoint.cpp:2779
bp::object objectiveGradientU
Python function: ∂f/∂u(coords,u,stress,strain) -> gradient.
Definition adjoint.cpp:2495
MatrixDouble copyToFull(MatrixDouble &s)
Convert symmetric tensor storage to full matrix format.
Definition adjoint.cpp:2717
np::ndarray convertToNumPy(std::vector< double > &data, int rows, int nb_gauss_pts)
Convert std::vector to NumPy array for Python interface.
Definition adjoint.cpp:3267
MoFEMErrorCode objectiveGradientUImpl(np::ndarray coords, np::ndarray u, np::ndarray stress, np::ndarray strain, np::ndarray &o)
Internal implementation for displacement gradient computation.
Definition adjoint.cpp:3192
MoFEMErrorCode objectiveGradientStressImpl(np::ndarray coords, np::ndarray u, np::ndarray stress, np::ndarray strain, np::ndarray &o)
Internal implementation for stress gradient computation.
Definition adjoint.cpp:3148
void copyToSymmetric(double *ptr, MatrixDouble &s)
Convert full matrix to symmetric tensor storage format
Definition adjoint.cpp:2732
bp::object objectBlockModes
Python function: blockModes(block_id,coords,centroid,bbox) -> modes.
Definition adjoint.cpp:2497
bp::object mainNamespace
Main Python namespace for script execution.
Definition adjoint.cpp:2491
MoFEMErrorCode objectiveFunctionImpl(np::ndarray coords, np::ndarray u, np::ndarray stress, np::ndarray strain, np::ndarray &o)
Internal implementation for objective function evaluation.
Definition adjoint.cpp:3127
[Adjoint operator declarations]
Definition adjoint.cpp:117
virtual ~ObjectiveFunctionData()=default
virtual MoFEMErrorCode evalObjectiveGradientStress(MatrixDouble &coords, boost::shared_ptr< MatrixDouble > u_ptr, boost::shared_ptr< MatrixDouble > stress_ptr, boost::shared_ptr< MatrixDouble > strain_ptr, boost::shared_ptr< MatrixDouble > o_ptr)=0
Evaluate gradient of objective function w.r.t. stress: ∂f/∂σ
virtual MoFEMErrorCode evalObjectiveGradientStrain(MatrixDouble &coords, boost::shared_ptr< MatrixDouble > u_ptr, boost::shared_ptr< MatrixDouble > stress_ptr, boost::shared_ptr< MatrixDouble > strain_ptr, boost::shared_ptr< MatrixDouble > o_ptr)=0
Evaluate gradient of objective function w.r.t. strain: ∂f/∂ε
virtual MoFEMErrorCode numberOfModes(int block_id, int &modes)=0
Return number of topology optimization modes for given block.
virtual MoFEMErrorCode blockModes(int block_id, MatrixDouble &coords, std::array< double, 3 > &centroid, std::array< double, 6 > &bbox, MatrixDouble &o_ptr)=0
Define spatial modes for topology optimization.
virtual MoFEMErrorCode evalObjectiveFunction(MatrixDouble &coords, boost::shared_ptr< MatrixDouble > u_ptr, boost::shared_ptr< MatrixDouble > stress_ptr, boost::shared_ptr< MatrixDouble > strain_ptr, boost::shared_ptr< VectorDouble > o_ptr)=0
Evaluate objective function f(coords, u, stress, strain)
OpAdJointGradTimesSymTensor(const std::string field_name, boost::shared_ptr< HookeOps::CommonData > comm_ptr, boost::shared_ptr< MatrixDouble > jac, boost::shared_ptr< MatrixDouble > diff_jac, boost::shared_ptr< VectorDouble > cof_vals)
Definition adjoint.cpp:1633
Forward declaration of operator for gradient times symmetric tensor operations.
Definition adjoint.cpp:90
Operator for computing objective function and gradients in topology optimization.
Definition adjoint.cpp:1794
boost::shared_ptr< MatrixDouble > dGradPtr
Definition adjoint.cpp:1940
boost::shared_ptr< double > globObjectiveGradPtr
Definition adjoint.cpp:1944
boost::shared_ptr< ObjectiveFunctionData > pythonPtr
Definition adjoint.cpp:1935
boost::shared_ptr< double > globObjectivePtr
Definition adjoint.cpp:1943
ForcesAndSourcesCore::UserDataOperator OP
Definition adjoint.cpp:1795
MoFEMErrorCode doWork(int side, EntityType type, EntitiesFieldData::EntData &data)
Compute objective function contributions at element level.
Definition adjoint.cpp:1817
boost::shared_ptr< HookeOps::CommonData > commPtr
Definition adjoint.cpp:1936
boost::shared_ptr< VectorDouble > cofVals
Definition adjoint.cpp:1939
boost::shared_ptr< MatrixDouble > uPtr
Definition adjoint.cpp:1941
OpAdJointObjective(boost::shared_ptr< ObjectiveFunctionData > python_ptr, boost::shared_ptr< HookeOps::CommonData > comm_ptr, boost::shared_ptr< MatrixDouble > jac_ptr, boost::shared_ptr< MatrixDouble > diff_jac, boost::shared_ptr< VectorDouble > cof_vals, boost::shared_ptr< MatrixDouble > d_grad_ptr, boost::shared_ptr< MatrixDouble > u_ptr, boost::shared_ptr< double > glob_objective_ptr, boost::shared_ptr< double > glob_objective_grad_ptr)
Definition adjoint.cpp:1797
boost::shared_ptr< MatrixDouble > diffJacPtr
Definition adjoint.cpp:1938
boost::shared_ptr< MatrixDouble > jacPtr
Definition adjoint.cpp:1937
OpAdJointTestOp(const std::string field_name, boost::shared_ptr< HookeOps::CommonData > comm_ptr)
Definition adjoint.cpp:1558
PipelineManager::ElementsAndOpsByDim< 2 >::FaceSideEle SideEle
PipelineManager::ElementsAndOpsByDim< 3 >::FaceSideEle SideEle
ElementsAndOps< SPACE_DIM >::SideEle SideEle
Definition plastic.cpp:61
#define EXECUTABLE_DIMENSION
Definition plastic.cpp:13
PetscBool do_eval_field
Evaluate field.
Definition plastic.cpp:119
auto save_range
constexpr int SPACE_DIM