v0.16.3
Loading...
Searching...
No Matches
ObjectiveFunctionData.cpp
Go to the documentation of this file.
1/**
2 * @file ObjectiveFunctionData.cpp
3 * @brief Implementation of Python-integrated objective function interface
4 * @details Implements Python-C++ bridge for objective function evaluation
5 * using Boost.Python and NumPy for topology optimization
6 */
7
8#include <boost/python.hpp>
9#include <boost/python/def.hpp>
10#include <boost/python/numpy.hpp>
11namespace bp = boost::python;
12namespace np = boost::python::numpy;
13
14#include <MoFEM.hpp>
15using namespace MoFEM;
17
19
20constexpr int SPACE_DIM =
21 EXECUTABLE_DIMENSION; ///< Space dimension of problem (2D or 3D), set at compile time
22
23/**
24 * @brief Implementation of ObjectiveFunctionData interface using Python integration
25 *
26 * This class provides a concrete implementation of the ObjectiveFunctionData interface
27 * that bridges MoFEM C++ data structures with Python-defined objective functions.
28 * It enables flexible definition of optimization objectives through Python scripting
29 * while maintaining high-performance computation in the C++ finite element framework.
30 *
31 * Key features:
32 * - Python function evaluation with automatic data conversion
33 * - Support for objective function and gradient computations
34 * - Topology optimization mode definition through Python
35 * - Efficient NumPy array interfacing for large data sets
36 * - Automatic memory management between C++ and Python
37 *
38 * The class handles:
39 * 1. Loading and executing Python objective function scripts
40 * 2. Converting MoFEM data structures to NumPy arrays
41 * 3. Calling Python functions for objective evaluation
42 * 4. Converting Python results back to MoFEM format
43 * 5. Managing Python interpreter state and namespace
44 *
45 * Example Python interface functions that must be defined:
46 * - objectiveInteriorFunction(coords, u, stress, strain) -> objective_value
47 * - objectiveInteriorGradientStress(coords, u, stress, strain) -> gradient_wrt_stress
48 * - numberOfModes(block_id) -> number_of_topology_modes
49 * - blockModes(block_id, coords, centroid, bbox) -> mode_vectors
50 */
53 virtual ~ObjectiveFunctionDataImpl() = default;
54
55 /// Initialize Python interpreter and load objective function script
56 MoFEMErrorCode initPython(const std::string py_file);
57
58 /**
59 * @brief Evaluate objective function at current state
60 *
61 * Calls Python-defined objective function with current displacement,
62 * stress, and strain fields. Used during optimization to compute
63 * the objective value that drives the optimization process.
64 *
65 * @param coords Gauss point coordinates
66 * @param u_ptr Displacement field values
67 * @param stress_ptr Stress tensor values
68 * @param strain_ptr Strain tensor values
69 * @param o_ptr Output objective function values
70 * @return MoFEMErrorCode Success or error code
71 */
73 MatrixDouble &coords, boost::shared_ptr<MatrixDouble> u_ptr,
74 boost::shared_ptr<MatrixDouble> stress_ptr,
75 boost::shared_ptr<MatrixDouble> strain_ptr,
76 boost::shared_ptr<MatrixDouble> o_ptr, bool symmetrize = true);
77
78 /**
79 * @brief Compute gradient of objective function with respect to stress
80 *
81 * Evaluates ∂f/∂σ where f is objective function and σ is stress tensor.
82 * This gradient is used in the adjoint method to compute sensitivities
83 * efficiently. Essential for gradient-based topology optimization.
84 *
85 * @param coords Gauss point coordinates
86 * @param u_ptr Displacement field values
87 * @param stress_ptr Stress tensor values
88 * @param strain_ptr Strain tensor values
89 * @param o_ptr Output gradient values
90 * @return MoFEMErrorCode Success or error code
91 */
93 MatrixDouble &coords, boost::shared_ptr<MatrixDouble> u_ptr,
94 boost::shared_ptr<MatrixDouble> stress_ptr,
95 boost::shared_ptr<MatrixDouble> strain_ptr,
96 boost::shared_ptr<MatrixDouble> o_ptr, bool symmetrize = true);
97
98 /**
99 * @brief Compute gradient of objective function with respect to strain
100 *
101 * Evaluates ∂f/∂ε where f is objective function and ε is strain tensor.
102 * Used when objective function depends directly on strain measures,
103 * complementing stress-based gradients in adjoint sensitivity analysis.
104 *
105 * @param coords Gauss point coordinates
106 * @param u_ptr Displacement field values
107 * @param stress_ptr Stress tensor values
108 * @param strain_ptr Strain tensor values
109 * @param o_ptr Output gradient values
110 * @return MoFEMErrorCode Success or error code
111 */
113 MatrixDouble &coords, boost::shared_ptr<MatrixDouble> u_ptr,
114 boost::shared_ptr<MatrixDouble> stress_ptr,
115 boost::shared_ptr<MatrixDouble> strain_ptr,
116 boost::shared_ptr<MatrixDouble> o_ptr, bool symmetrize = true);
117
118 /**
119 * @brief Compute gradient of objective function with respect to displacement
120 *
121 * Evaluates ∂f/∂u where f is objective function and u is displacement field.
122 * This provides direct sensitivity of objective to displacement changes,
123 * used as right-hand side in adjoint equation K^T * λ = ∂f/∂u.
124 *
125 * @param coords Gauss point coordinates
126 * @param u_ptr Displacement field values
127 * @param stress_ptr Stress tensor values
128 * @param strain_ptr Strain tensor values
129 * @param o_ptr Output gradient values
130 * @return MoFEMErrorCode Success or error code
131 */
133 MatrixDouble &coords, boost::shared_ptr<MatrixDouble> u_ptr,
134 boost::shared_ptr<MatrixDouble> stress_ptr,
135 boost::shared_ptr<MatrixDouble> strain_ptr,
136 boost::shared_ptr<MatrixDouble> o_ptr, bool symmetrize = true);
137
140 boost::shared_ptr<MatrixDouble> u_ptr,
141 boost::shared_ptr<MatrixDouble> t_ptr,
142 boost::shared_ptr<VectorDouble> o_ptr,
143 bool symmetrize = true);
144
147 boost::shared_ptr<MatrixDouble> u_ptr,
148 boost::shared_ptr<MatrixDouble> t_ptr,
149 boost::shared_ptr<MatrixDouble> o_ptr);
150
153 boost::shared_ptr<MatrixDouble> u_ptr,
154 boost::shared_ptr<MatrixDouble> t_ptr,
155 boost::shared_ptr<MatrixDouble> o_ptr);
156
157 /// Return number of topology optimization modes for given material block
158 MoFEMErrorCode numberOfModes(int block_id, int &modes);
159
160 /**
161 * @brief Define spatial topology modes for design optimization
162 *
163 * Generates basis functions that define how the geometry can be modified
164 * during topology optimization. These modes serve as design variables
165 * and define the design space for optimization.
166 *
167 * @param block_id Material block identifier
168 * @param coords Element coordinates
169 * @param centroid Block centroid coordinates
170 * @param bbodx Bounding box dimensions [xmin,xmax,ymin,ymax,zmin,zmax]
171 * @param o_ptr Output mode vectors
172 * @return MoFEMErrorCode Success or error code
173 */
174 MoFEMErrorCode blockModes(int block_id, MatrixDouble &coords,
175 std::array<double, 3> &centroid,
176 std::array<double, 6> &bbodx, MatrixDouble &o_ptr);
177
178private:
179 // Python interpreter objects for objective function evaluation
180 bp::object mainNamespace; ///< Main Python namespace for script execution
181
182 /**
183 * @brief Internal implementation for objective function evaluation
184 *
185 * Handles low-level Python function call with NumPy array conversion.
186 * Converts MoFEM matrices to NumPy arrays, calls Python function,
187 * and handles return value conversion.
188 *
189 * @param coords NumPy array of coordinates
190 * @param u NumPy array of displacements
191 * @param stress NumPy array of stress tensors
192 * @param strain NumPy array of strain tensors
193 * @param o Output NumPy array for objective values
194 * @return MoFEMErrorCode Success or error code
195 */
197
198 np::ndarray coords, np::ndarray u,
199
200 np::ndarray stress, np::ndarray strain, np::ndarray &o
201
202 );
203
204 /**
205 * @brief Internal implementation for stress gradient computation
206 *
207 * Calls Python function to compute ∂f/∂σ with automatic array conversion.
208 * Essential for adjoint-based sensitivity analysis in topology optimization.
209 *
210 * @param coords NumPy array of coordinates
211 * @param u NumPy array of displacements
212 * @param stress NumPy array of stress tensors
213 * @param strain NumPy array of strain tensors
214 * @param o Output NumPy array for gradient values
215 * @return MoFEMErrorCode Success or error code
216 */
218
219 np::ndarray coords, np::ndarray u,
220
221 np::ndarray stress, np::ndarray strain, np::ndarray &o
222
223 );
224
225 /**
226 * @brief Internal implementation for strain gradient computation
227 *
228 * Evaluates ∂f/∂ε through Python interface with NumPy array handling.
229 * Provides strain-based sensitivities for comprehensive gradient computation.
230 *
231 * @param coords NumPy array of coordinates
232 * @param u NumPy array of displacements
233 * @param stress NumPy array of stress tensors
234 * @param strain NumPy array of strain tensors
235 * @param o Output NumPy array for gradient values
236 * @return MoFEMErrorCode Success or error code
237 */
239
240 np::ndarray coords, np::ndarray u,
241
242 np::ndarray stress, np::ndarray strain, np::ndarray &o
243
244 );
245
246 /**
247 * @brief Internal implementation for displacement gradient computation
248 *
249 * Computes ∂f/∂u through Python interface for adjoint equation right-hand side.
250 * This gradient drives the adjoint solution that enables efficient sensitivity
251 * computation independent of design variable count.
252 *
253 * @param coords NumPy array of coordinates
254 * @param u NumPy array of displacements
255 * @param stress NumPy array of stress tensors
256 * @param strain NumPy array of strain tensors
257 * @param o Output NumPy array for gradient values
258 * @return MoFEMErrorCode Success or error code
259 */
261
262 np::ndarray coords, np::ndarray u,
263
264 np::ndarray stress, np::ndarray strain, np::ndarray &o
265
266 );
267
269
270 np::ndarray coords, np::ndarray u,
271
272 np::ndarray t, np::ndarray &o
273
274 );
275
277
278 np::ndarray coords, np::ndarray u,
279
280 np::ndarray t, np::ndarray &o
281
282 );
283
285
286 np::ndarray coords, np::ndarray u,
287
288 np::ndarray t, np::ndarray &o
289
290 );
291
292 /**
293 * @brief Internal implementation for topology mode generation
294 *
295 * Calls Python function to generate spatial basis functions for topology
296 * optimization. These modes define the design space and parametrize
297 * allowable geometry modifications during optimization.
298 *
299 * @param block_id Material block identifier
300 * @param coords NumPy array of element coordinates
301 * @param centroid NumPy array of block centroid
302 * @param bbodx NumPy array of bounding box dimensions
303 * @param o_ptr Output NumPy array for mode vectors
304 * @return MoFEMErrorCode Success or error code
305 */
307
308 int block_id, np::ndarray coords, np::ndarray centroid, np::ndarray bbodx,
309 np::ndarray &o_ptr
310
311 );
312
313 /**
314 * @brief Convert std::vector to NumPy array for Python interface
315 *
316 * Efficient conversion from MoFEM data structures to NumPy arrays
317 * for seamless Python function calls without data copying.
318 *
319 * @param data Source vector data
320 * @param rows Number of rows in resulting array
321 * @param nb_gauss_pts Number of Gauss points (affects array structure)
322 * @return np::ndarray NumPy array for Python use
323 */
324 np::ndarray convertToNumPy(std::vector<double> &data, int rows,
325 int nb_gauss_pts);
326
327 /**
328 * @brief Convert raw pointer to NumPy array for Python interface
329 *
330 * Low-level conversion for direct memory access to create NumPy arrays.
331 * Provides zero-copy conversion when possible for performance.
332 *
333 * @param ptr Raw data pointer
334 * @param s Array size
335 * @return np::ndarray NumPy array for Python use
336 */
337 np::ndarray convertToNumPy(double *ptr, int s);
338
339 /// Convert symmetric tensor storage to full matrix format
341
342 /// Convert full matrix to symmetric tensor storage format
343 void copyToSymmetric(double *ptr, MatrixDouble &s);
344};
345
346/**
347 * @brief Factory function to create Python-integrated objective function interface
348 *
349 * Creates and initializes an ObjectiveFunctionDataImpl instance that bridges
350 * MoFEM finite element computations with Python-defined objective functions.
351 * This enables flexible objective function definition for topology optimization
352 * while maintaining computational efficiency.
353 *
354 * The Python file must define specific functions with correct signatures:
355 * - objectiveInteriorFunction(coords, u, stress, strain) -> objective_value
356 * - objectiveInteriorGradientStress(coords, u, stress, strain) -> gradient_array
357 * - objectiveInteriorGradientStrain(coords, u, stress, strain) -> gradient_array
358 * - objectiveInteriorGradientU(coords, u, stress, strain) -> gradient_array
359 * - numberOfModes(block_id) -> integer
360 * - blockModes(block_id, coords, centroid, bbox) -> mode_array
361 *
362 * @param py_file Path to Python file containing objective function definitions
363 * @return boost::shared_ptr<ObjectiveFunctionData> Configured objective function interface
364 * @throws MoFEM exception if Python initialization fails
365 */
366boost::shared_ptr<ObjectiveFunctionData>
368 auto ptr = boost::make_shared<ObjectiveFunctionDataImpl>();
369 CHK_THROW_MESSAGE(ptr->initPython(py_file), "init python");
370 return ptr;
371}
372
373/**
374 * @brief Initialize Python interpreter and load objective function script
375 *
376 * This method sets up the Python environment for objective function evaluation
377 * by loading a Python script that defines the required optimization functions.
378 * It establishes the bridge between MoFEM's C++ finite element computations
379 * and user-defined Python objective functions.
380 *
381 * The Python script must define the following functions:
382 * - f(coords, u, stress, strain): Main objective function
383 * - f_stress(coords, u, stress, strain): Gradient w.r.t. stress ∂f/∂σ
384 * - f_strain(coords, u, stress, strain): Gradient w.r.t. strain ∂f/∂ε
385 * - f_u(coords, u, stress, strain): Gradient w.r.t. displacement ∂f/∂u
386 * - number_of_modes(block_id): Return number of topology modes
387 * - block_modes(block_id, coords, centroid, bbox): Define topology modes
388 *
389 * All functions receive NumPy arrays and must return NumPy arrays of
390 * appropriate dimensions for the finite element computation.
391 *
392 * @param py_file Path to Python script containing objective function definitions
393 * @return MoFEMErrorCode Success or error code
394 * @throws MOFEM_OPERATION_UNSUCCESSFUL if Python script has errors
395 */
397ObjectiveFunctionDataImpl::initPython(const std::string py_file) {
399 try {
400
401 // Create main Python module and namespace for script execution
402 auto main_module = bp::import("__main__");
403 mainNamespace = main_module.attr("__dict__");
404
405 // Execute the Python script in the main namespace
406 bp::exec_file(py_file.c_str(), mainNamespace, mainNamespace);
407
408 // Python callbacks are resolved lazily with explicit existence checks
409
410 } catch (bp::error_already_set const &) {
411 // Handle Python errors by printing to stderr and throwing MoFEM exception
412 PyErr_Print();
414 }
416}
417
419 const auto nb_gauss_pts = s.size1();
420 MatrixDouble f(nb_gauss_pts, 9);
421 f.clear();
424 auto t_f =
426 f);
427 auto t_s = getFTensor2SymmetricFromMat<SPACE_DIM>(s);
428 for (size_t ii = 0; ii != nb_gauss_pts; ++ii) {
429 t_f(i, j) = t_s(i, j);
430 ++t_f;
431 ++t_s;
432 }
433 return f;
434};
435
437 const auto nb_gauss_pts = s.size1();
440 auto t_f =
442 ptr);
443
444 auto t_s = getFTensor2SymmetricFromMat<SPACE_DIM>(s);
445 for (size_t ii = 0; ii != nb_gauss_pts; ++ii) {
446 t_s(i, j) = (t_f(i, j) || t_f(j, i)) / 2.0;
447 ++t_f;
448 ++t_s;
449 }
450}
451
452/**
453 * @brief Evaluate objective function at current finite element state
454 *
455 * This method bridges MoFEM finite element data with Python-defined objective
456 * functions for topology optimization. It handles the complete data conversion
457 * workflow from MoFEM matrices to NumPy arrays, calls the Python objective
458 * function, and converts results back to MoFEM format.
459 *
460 * Process:
461 * 1. Convert coordinate matrix to NumPy format for Python access
462 * 2. Convert displacement field data to NumPy arrays
463 * 3. Convert symmetric stress/strain tensors to full 3x3 matrix format
464 * 4. Call Python objective function: f(coords, u, stress, strain)
465 * 5. Extract results and copy back to MoFEM vector format
466 *
467 * The objective function typically computes scalar quantities like:
468 * - Compliance: ∫ u^T * f dΩ (minimize structural deformation)
469 * - Stress constraints: ∫ ||σ - σ_target||² dΩ (control stress distribution)
470 * - Volume constraints: ∫ ρ dΩ (material usage limitations)
471 *
472 * @param coords Gauss point coordinates for current element
473 * @param u_ptr Displacement field values at Gauss points
474 * @param stress_ptr Cauchy stress tensor values (symmetric storage)
475 * @param strain_ptr Strain tensor values (symmetric storage)
476 * @param o_ptr Output objective function values at each Gauss point
477 * @return MoFEMErrorCode Success or error code
478 */
480 MatrixDouble &coords, boost::shared_ptr<MatrixDouble> u_ptr,
481 boost::shared_ptr<MatrixDouble> stress_ptr,
482 boost::shared_ptr<MatrixDouble> strain_ptr,
483 boost::shared_ptr<MatrixDouble> o_ptr, bool symmetrize) {
485 try {
486
487 // Convert coordinates to NumPy array for Python function
488 auto np_coords =
489 convertToNumPy(coords.data(), coords.size1(), coords.size2());
490 // Convert displacement field to NumPy array
491 auto np_u = convertToNumPy(u_ptr->data(), u_ptr->size1(), u_ptr->size2());
492
493 // Convert symmetric tensor storage to full matrix format for Python
494 // MoFEM stores symmetric tensors in like Voigt notation, Python expects full 3x3 matrices
495 auto full_stress = symmetrize ? copyToFull(*(stress_ptr)) : *(stress_ptr);
496 auto full_strain = symmetrize ? copyToFull(*(strain_ptr)) : *(strain_ptr);
497
498 // Create NumPy arrays for stress and strain tensors
499 auto np_stress = convertToNumPy(full_stress.data(), full_stress.size1(),
500 full_stress.size2());
501 auto np_strain = convertToNumPy(full_strain.data(), full_strain.size1(),
502 full_strain.size2());
503
504 // Prepare output array for objective function values
505 const auto nb_gauss_pts = full_strain.size1();
506 np::ndarray np_output =
507 np::empty(bp::make_tuple(nb_gauss_pts), np::dtype::get_builtin<double>());
508
509 // Call Python objective function implementation
510 CHKERR interiorObjectiveFunctionImpl(np_coords, np_u, np_stress, np_strain,
511 np_output);
512
513 //Check the shape of returned array
514 if (np_output.get_nd() != 1 || np_output.get_shape()[0] != nb_gauss_pts) {
517 "Wrong shape of Objective Function from python expected (" +
518 std::to_string(nb_gauss_pts) + "), got (" +
519 std::to_string(np_output.get_shape()[0]) + ")");
520 }
521
522 // Copy Python results back to a 1 x n matrix, matching common-data storage.
523 o_ptr->resize(1, nb_gauss_pts, false);
524 double *val_ptr = reinterpret_cast<double *>(np_output.get_data());
525 std::copy(val_ptr, val_ptr + nb_gauss_pts, o_ptr->data().begin());
526
527 } catch (bp::error_already_set const &) {
528 // Handle Python errors with detailed error reporting
529 PyErr_Print();
531 }
533}
534
535/**
536 * @brief Compute gradient of objective function with respect to stress tensor
537 *
538 * This method evaluates ∂f/∂σ, the partial derivative of the objective function
539 * with respect to the Cauchy stress tensor. This gradient is fundamental to the
540 * adjoint method for topology optimization, as it provides the driving force
541 * for the adjoint equation solution.
542 *
543 * Mathematical context:
544 * The adjoint method requires ∂f/∂σ to compute sensitivities efficiently.
545 * For stress-based objectives like von Mises stress constraints:
546 * ∂f/∂σ = ∂/∂σ[∫(σ_vm - σ_target)² dΩ] = 2(σ_vm - σ_target) * ∂σ_vm/∂σ
547 *
548 * Process:
549 * 1. Convert all field data to NumPy format for Python processing
550 * 2. Call Python function f_stress(coords, u, stress, strain)
551 * 3. Python returns full 3x3 gradient matrices for each Gauss point
552 * 4. Convert back to symmetric tensor storage used by MoFEM
553 * 5. Store results for use in adjoint equation assembly
554 *
555 * The resulting gradients drive the adjoint solution that enables efficient
556 * computation of design sensitivities independent of design variable count.
557 *
558 * @param coords Gauss point coordinates
559 * @param u_ptr Displacement field values
560 * @param stress_ptr Current stress tensor values (symmetric storage)
561 * @param strain_ptr Current strain tensor values (symmetric storage)
562 * @param o_ptr Output stress gradients ∂f/∂σ (symmetric storage)
563 * @return MoFEMErrorCode Success or error code
564 */
566 MatrixDouble &coords, boost::shared_ptr<MatrixDouble> u_ptr,
567 boost::shared_ptr<MatrixDouble> stress_ptr,
568 boost::shared_ptr<MatrixDouble> strain_ptr,
569 boost::shared_ptr<MatrixDouble> o_ptr, bool symmetrize) {
571 try {
572
573 // Convert coordinates and displacement field to NumPy format
574 auto np_coords =
575 convertToNumPy(coords.data(), coords.size1(), coords.size2());
576 auto np_u = convertToNumPy(u_ptr->data(), u_ptr->size1(), u_ptr->size2());
577
578 // Convert symmetric tensors to full 3x3 format for Python processing
579 auto full_stress = symmetrize ? copyToFull(*(stress_ptr)) : *(stress_ptr);
580 auto full_strain = symmetrize ? copyToFull(*(strain_ptr)) : *(strain_ptr);
581
582 // Create NumPy arrays for stress and strain tensors
583 auto np_stress = convertToNumPy(full_stress.data(), full_stress.size1(),
584 full_stress.size2());
585 auto np_strain = convertToNumPy(full_strain.data(), full_strain.size1(),
586 full_strain.size2());
587 // Prepare output array for stress gradients (full matrix format)
588 np::ndarray np_output =
589 np::empty(bp::make_tuple(full_strain.size1(), full_strain.size2()),
590 np::dtype::get_builtin<double>());
591
592 // Call Python implementation for stress gradient computation
593 CHKERR interiorObjectiveGradientStressImpl(np_coords, np_u, np_stress, np_strain,
594 np_output);
595
596 // Check the shape of returned array
597 if (np_output.get_shape()[0] != full_strain.size1() ||
598 np_output.get_shape()[1] != full_strain.size2()) {
601 "Wrong shape of Objective Gradient from python expected (" +
602 std::to_string(full_strain.size1()) + ", " +
603 std::to_string(full_strain.size2()) + "), got (" +
604 std::to_string(np_output.get_shape()[0]) + ", " +
605 std::to_string(np_output.get_shape()[1]) + ")");
606 }
607
608 // Prepare output matrix in the requested tensor storage format
609 o_ptr->resize(stress_ptr->size1(), stress_ptr->size2(), false);
610 if (symmetrize) {
611 double *val_ptr = reinterpret_cast<double *>(np_output.get_data());
612 // Convert full matrix results back to symmetric tensor storage
613 copyToSymmetric(val_ptr, *(o_ptr));
614 } else {
615 double *val_ptr = reinterpret_cast<double *>(np_output.get_data());
616 std::copy(val_ptr, val_ptr + stress_ptr->size1() * stress_ptr->size2(),
617 o_ptr->data().begin());
618 }
619
620 } catch (bp::error_already_set const &) {
621 PyErr_Print();
623 }
625}
626
627/**
628 * @brief Compute gradient of objective function with respect to strain tensor
629 *
630 * This method evaluates ∂f/∂ε, the partial derivative of the objective function
631 * with respect to the strain tensor. While many structural objectives depend
632 * primarily on stress, strain-based gradients are important for certain
633 * optimization formulations and provide additional sensitivity information.
634 *
635 * Mathematical context:
636 * For strain energy-based objectives: f = ½ε:C:ε
637 * The gradient is: ∂f/∂ε = C:ε = σ (stress tensor)
638 *
639 * For strain-based constraints or objectives like strain concentration:
640 * ∂f/∂ε = ∂/∂ε[∫(ε_vm - ε_target)² dΩ] = 2(ε_vm - ε_target) * ∂ε_vm/∂ε
641 *
642 * Process:
643 * 1. Convert field data to NumPy format for Python compatibility
644 * 2. Call Python function f_strain(coords, u, stress, strain)
645 * 3. Python returns gradient matrices ∂f/∂ε for each Gauss point
646 * 4. Convert from full 3x3 format back to symmetric storage
647 * 5. Results used in adjoint sensitivity analysis
648 *
649 * This gradient complements stress-based gradients in comprehensive
650 * sensitivity analysis for topology optimization problems.
651 *
652 * @param coords Gauss point coordinates
653 * @param u_ptr Displacement field values
654 * @param stress_ptr Current stress tensor values (symmetric storage)
655 * @param strain_ptr Current strain tensor values (symmetric storage)
656 * @param o_ptr Output strain gradients ∂f/∂ε (symmetric storage)
657 * @return MoFEMErrorCode Success or error code
658 */
660 MatrixDouble &coords, boost::shared_ptr<MatrixDouble> u_ptr,
661 boost::shared_ptr<MatrixDouble> stress_ptr,
662 boost::shared_ptr<MatrixDouble> strain_ptr,
663 boost::shared_ptr<MatrixDouble> o_ptr, bool symmetrize) {
665 try {
666
667 // Convert coordinates and displacement data to NumPy format
668 auto np_coords =
669 convertToNumPy(coords.data(), coords.size1(), coords.size2());
670 auto np_u = convertToNumPy(u_ptr->data(), u_ptr->size1(), u_ptr->size2());
671
672 // Convert symmetric tensor data to full 3x3 matrices for Python
673 auto full_stress = symmetrize ? copyToFull(*(stress_ptr)) : *(stress_ptr);
674 auto full_strain = symmetrize ? copyToFull(*(strain_ptr)) : *(strain_ptr);
675
676 auto np_stress = convertToNumPy(full_stress.data(), full_stress.size1(),
677 full_stress.size2());
678 auto np_strain = convertToNumPy(full_strain.data(), full_strain.size1(),
679 full_strain.size2());
680
681 // Prepare output array for strain gradients
682 np::ndarray np_output =
683 np::empty(bp::make_tuple(full_strain.size1(), full_strain.size2()),
684 np::dtype::get_builtin<double>());
685
686 // Call Python implementation for strain gradient computation
687 CHKERR interiorObjectiveGradientStrainImpl(np_coords, np_u, np_stress, np_strain,
688 np_output);
689
690 // Check the shape of returned array
691 if (np_output.get_shape()[0] != full_strain.size1() ||
692 np_output.get_shape()[1] != full_strain.size2()) {
695 "Wrong shape of Objective Gradient from python expected (" +
696 std::to_string(full_strain.size1()) + ", " +
697 std::to_string(full_strain.size2()) + "), got (" +
698 std::to_string(np_output.get_shape()[0]) + ", " +
699 std::to_string(np_output.get_shape()[1]) + ")");
700 }
701
702 o_ptr->resize(strain_ptr->size1(), strain_ptr->size2(), false);
703 if (symmetrize) {
704 double *val_ptr = reinterpret_cast<double *>(np_output.get_data());
705 copyToSymmetric(val_ptr, *(o_ptr));
706 } else {
707 double *val_ptr = reinterpret_cast<double *>(np_output.get_data());
708 std::copy(val_ptr, val_ptr + strain_ptr->size1() * strain_ptr->size2(),
709 o_ptr->data().begin());
710 }
711
712 } catch (bp::error_already_set const &) {
713 PyErr_Print();
715 }
717}
718
719/**
720 * @brief Compute gradient of objective function with respect to displacement field
721 *
722 * This method evaluates ∂f/∂u, the partial derivative of the objective function
723 * with respect to the displacement field. This gradient is crucial for the adjoint
724 * method as it forms the right-hand side of the adjoint equation: K^T * λ = ∂f/∂u
725 *
726 * Mathematical context:
727 * The adjoint method solves: K^T * λ = ∂f/∂u
728 * where λ are the adjoint variables (Lagrange multipliers)
729 *
730 * For compliance minimization: f = ½u^T * K * u
731 * The gradient is: ∂f/∂u = K * u (applied forces)
732 *
733 * For displacement-based constraints: f = ||u - u_target||²
734 * The gradient is: ∂f/∂u = 2(u - u_target)
735 *
736 * Process:
737 * 1. Convert all field data to NumPy format for Python processing
738 * 2. Call Python function f_u(coords, u, stress, strain)
739 * 3. Python returns displacement gradients for each component
740 * 4. Copy results directly (no tensor conversion needed for vectors)
741 * 5. Results drive adjoint equation solution for sensitivity analysis
742 *
743 * This gradient is fundamental to adjoint-based topology optimization,
744 * enabling efficient sensitivity computation for any number of design variables.
745 *
746 * @param coords Gauss point coordinates
747 * @param u_ptr Displacement field values
748 * @param stress_ptr Current stress tensor values
749 * @param strain_ptr Current strain tensor values
750 * @param o_ptr Output displacement gradients ∂f/∂u
751 * @return MoFEMErrorCode Success or error code
752 */
754 MatrixDouble &coords, boost::shared_ptr<MatrixDouble> u_ptr,
755 boost::shared_ptr<MatrixDouble> stress_ptr,
756 boost::shared_ptr<MatrixDouble> strain_ptr,
757 boost::shared_ptr<MatrixDouble> o_ptr, bool symmetrize) {
759 try {
760
761 // Convert coordinates and displacement field to NumPy format
762 auto np_coords =
763 convertToNumPy(coords.data(), coords.size1(), coords.size2());
764 auto np_u = convertToNumPy(u_ptr->data(), u_ptr->size1(), u_ptr->size2());
765
766 // Convert stress and strain tensors to full matrix format
767 auto full_stress = symmetrize ? copyToFull(*(stress_ptr)) : *(stress_ptr);
768 auto full_strain = symmetrize ? copyToFull(*(strain_ptr)) : *(strain_ptr);
769
770 auto np_stress = convertToNumPy(full_stress.data(), full_stress.size1(),
771 full_stress.size2());
772 auto np_strain = convertToNumPy(full_strain.data(), full_strain.size1(),
773 full_strain.size2());
774
775 // Prepare output array for displacement gradients (same size as displacement field)
776 np::ndarray np_output =
777 np::empty(bp::make_tuple(u_ptr->size1(), u_ptr->size2()),
778 np::dtype::get_builtin<double>());
779
780 // Call Python implementation for displacement gradient computation
781 // Note: This should call interiorObjectiveGradientUImpl, not interiorObjectiveGradientStrainImpl
782 CHKERR interiorObjectiveGradientUImpl(np_coords, np_u, np_stress, np_strain,
783 np_output);
784
785 // Check the shape of returned array
786 if (np_output.get_shape()[0] != u_ptr->size1() ||
787 np_output.get_shape()[1] != u_ptr->size2()) {
790 "Wrong shape of Objective Gradient from python expected (" +
791 std::to_string(u_ptr->size1()) + ", " +
792 std::to_string(u_ptr->size2()) + "), got (" +
793 std::to_string(np_output.get_shape()[0]) + ", " +
794 std::to_string(np_output.get_shape()[1]) + ")");
795 }
796
797 // Copy results directly to output matrix (no tensor conversion needed for vectors)
798 o_ptr->resize(u_ptr->size1(), u_ptr->size2(), false);
799 double *val_ptr = reinterpret_cast<double *>(np_output.get_data());
800 std::copy(val_ptr, val_ptr + u_ptr->size1() * u_ptr->size2(),
801 o_ptr->data().begin());
802
803 } catch (bp::error_already_set const &) {
804 // Handle Python errors with detailed reporting
805 PyErr_Print();
807 }
809}
810
812 MatrixDouble &coords, boost::shared_ptr<MatrixDouble> u_ptr,
813 boost::shared_ptr<MatrixDouble> t_ptr,
814 boost::shared_ptr<MatrixDouble> o_ptr) {
816 try {
817
818 auto np_coords =
819 convertToNumPy(coords.data(), coords.size1(), coords.size2());
820 auto np_u = convertToNumPy(u_ptr->data(), u_ptr->size1(), u_ptr->size2());
821 auto np_t = convertToNumPy(t_ptr->data(), t_ptr->size1(), t_ptr->size2());
822
823 np::ndarray np_output =
824 np::empty(bp::make_tuple(u_ptr->size1(), u_ptr->size2()),
825 np::dtype::get_builtin<double>());
826
827 CHKERR boundaryObjectiveGradientTractionImpl(np_coords, np_u, np_t, np_output);
828
829 // Check the shape of returned array
830 if (np_output.get_shape()[0] != u_ptr->size1() ||
831 np_output.get_shape()[1] != u_ptr->size2()) {
834 "Wrong shape of Objective Gradient from python expected (" +
835 std::to_string(u_ptr->size1()) + ", " +
836 std::to_string(u_ptr->size2()) + "), got (" +
837 std::to_string(np_output.get_shape()[0]) + ", " +
838 std::to_string(np_output.get_shape()[1]) + ")");
839 }
840
841 o_ptr->resize(u_ptr->size1(), u_ptr->size2(), false);
842 double *val_ptr = reinterpret_cast<double *>(np_output.get_data());
843 std::copy(val_ptr, val_ptr + u_ptr->size1() * u_ptr->size2(),
844 o_ptr->data().begin());
845
846 } catch (bp::error_already_set const &) {
847 PyErr_Print();
849 }
851}
852
854 MatrixDouble &coords, boost::shared_ptr<MatrixDouble> u_ptr,
855 boost::shared_ptr<MatrixDouble> t_ptr,
856 boost::shared_ptr<VectorDouble> o_ptr, bool symmetrize) {
858 try {
859 (void)symmetrize;
860
861 auto np_coords =
862 convertToNumPy(coords.data(), coords.size1(), coords.size2());
863 auto np_u = convertToNumPy(u_ptr->data(), u_ptr->size1(), u_ptr->size2());
864 auto np_t = convertToNumPy(t_ptr->data(), t_ptr->size1(), t_ptr->size2());
865
866 np::ndarray np_output = np::empty(bp::make_tuple(t_ptr->size2()),
867 np::dtype::get_builtin<double>());
868
869 CHKERR boundaryObjectiveFunctionImpl(np_coords, np_u, np_t, np_output);
870
871 // Check the shape of returned array
872 if (np_output.get_nd() != 1 || np_output.get_shape()[0] != t_ptr->size2()) {
875 "Wrong shape of Objective Function from python expected (" +
876 std::to_string(t_ptr->size2()) + "), got (" +
877 std::to_string(np_output.get_shape()[0]) + ")");
878 }
879
880 o_ptr->resize(t_ptr->size2(), false);
881 double *val_ptr = reinterpret_cast<double *>(np_output.get_data());
882 std::copy(val_ptr, val_ptr + t_ptr->size2(), o_ptr->data().begin());
883
884 } catch (bp::error_already_set const &) {
885 PyErr_Print();
887 }
889}
890
892 MatrixDouble &coords, boost::shared_ptr<MatrixDouble> u_ptr,
893 boost::shared_ptr<MatrixDouble> t_ptr,
894 boost::shared_ptr<MatrixDouble> o_ptr) {
896 try {
897 auto np_coords =
898 convertToNumPy(coords.data(), coords.size1(), coords.size2());
899 auto np_u = convertToNumPy(u_ptr->data(), u_ptr->size1(), u_ptr->size2());
900 auto np_t = convertToNumPy(t_ptr->data(), t_ptr->size1(), t_ptr->size2());
901
902 np::ndarray np_output =
903 np::empty(bp::make_tuple(u_ptr->size1(), u_ptr->size2()),
904 np::dtype::get_builtin<double>());
905
906 CHKERR boundaryObjectiveGradientUImpl(np_coords, np_u, np_t, np_output);
907
908 // Check the shape of returned array
909 if (np_output.get_shape()[0] != u_ptr->size1() ||
910 np_output.get_shape()[1] != u_ptr->size2()) {
913 "Wrong shape of Objective Gradient from python expected (" +
914 std::to_string(u_ptr->size1()) + ", " +
915 std::to_string(u_ptr->size2()) + "), got (" +
916 std::to_string(np_output.get_shape()[0]) + ", " +
917 std::to_string(np_output.get_shape()[1]) + ")");
918 }
919
920 o_ptr->resize(u_ptr->size1(), u_ptr->size2(), false);
921 double *val_ptr = reinterpret_cast<double *>(np_output.get_data());
922 std::copy(val_ptr, val_ptr + u_ptr->size1() * u_ptr->size2(),
923 o_ptr->data().begin());
924
925 } catch (bp::error_already_set const &) {
926 PyErr_Print();
928 }
930}
931
932/**
933 * @brief Generate spatial topology modes for design optimization
934 *
935 * This method defines the design parameterization for topology optimization by
936 * generating spatial basis functions (modes) that describe how the geometry
937 * can be modified during optimization. These modes serve as design variables
938 * and define the feasible design space for the optimization problem.
939 *
940 * Mathematical context:
941 * The geometry modification is parameterized as: x_new = x_original + Σ(αᵢ * φᵢ(x))
942 * where αᵢ are design variables and φᵢ(x) are spatial mode functions
943 *
944 * Common mode types:
945 * - Radial basis functions: φ(x) = exp(-||x-c||²/σ²) for localized changes
946 * - Polynomial modes: φ(x) = xⁿyᵐzᵖ for global shape changes
947 * - Sinusoidal modes: φ(x) = sin(kx)cos(ly) for periodic patterns
948 * - Principal component modes: Derived from geometric sensitivity analysis
949 *
950 * Process:
951 * 1. Query Python function for number of modes for this material block
952 * 2. Convert coordinate data and geometric information to NumPy format
953 * 3. Call Python function block_modes(block_id, coords, centroid, bbox)
954 * 4. Python returns mode vectors for each coordinate at each mode
955 * 5. Reshape and store modes for use as design variables in optimization
956 *
957 * The modes enable efficient design space exploration and gradient-based
958 * optimization while maintaining geometric feasibility and smoothness.
959 *
960 * @param block_id Material block identifier for mode generation
961 * @param coords Element coordinates where modes are evaluated
962 * @param centroid Geometric centroid of the material block [x,y,z]
963 * @param bbodx Bounding box dimensions [xmin,xmax,ymin,ymax,zmin,zmax]
964 * @param o_ptr Output matrix: modes × (coordinates × spatial_dimension)
965 * @return MoFEMErrorCode Success or error code
966 */
968 int block_id, MatrixDouble &coords, std::array<double, 3> &centroid,
969 std::array<double, 6> &bbodx, MatrixDouble &o_ptr) {
971 try {
972
973 // Query Python function for number of topology modes for this block
974 int nb_modes;
975 CHKERR numberOfModes(block_id, nb_modes);
976
977 // Convert coordinate matrix to NumPy format for Python processing
978 auto np_coords =
979 convertToNumPy(coords.data(), coords.size1(), coords.size2());
980
981 // Convert geometric information to NumPy arrays
982 auto np_centroid =
983 convertToNumPy(centroid.data(), 3); // Block centroid [x,y,z]
984 auto np_bbodx = convertToNumPy(
985 bbodx.data(), 6); // Bounding box [xmin,xmax,ymin,ymax,zmin,zmax]
986
987 // Prepare output array: [modes × (coordinates * spatial_dimensions)]
988 np::ndarray np_output =
989 np::empty(bp::make_tuple(nb_modes, coords.size1(), coords.size2()),
990 np::dtype::get_builtin<double>());
991
992 // Call Python implementation to generate topology modes
993 CHKERR blockModesImpl(block_id, np_coords, np_centroid, np_bbodx,
994 np_output);
995
996 // Check the shape of returned array
997 if (np_output.get_shape()[0] != nb_modes ||
998 np_output.get_shape()[1] != coords.size1() ||
999 np_output.get_shape()[2] != coords.size2()) {
1001 "Wrong shape of Modes from python expected (" +
1002 std::to_string(nb_modes) + ", " +
1003 std::to_string(coords.size1()) + ", " +
1004 std::to_string(coords.size2()) + "), got (" +
1005 std::to_string(np_output.get_shape()[0]) + ", " +
1006 std::to_string(np_output.get_shape()[1]) + ", " +
1007 std::to_string(np_output.get_shape()[2]) + ")");
1008 }
1009
1010 // Reshape output matrix for MoFEM format: [modes × (coordinates * spatial_dimensions)]
1011 o_ptr.resize(nb_modes, coords.size1() * coords.size2(), false);
1012 double *val_ptr = reinterpret_cast<double *>(np_output.get_data());
1013 // Copy flattened mode data to output matrix
1014 std::copy(val_ptr, val_ptr + coords.size1() * coords.size2() * nb_modes,
1015 o_ptr.data().begin());
1016
1017 } catch (bp::error_already_set const &) {
1018 // Handle Python errors in mode generation
1019 PyErr_Print();
1021 }
1023}
1024
1026
1027 np::ndarray coords, np::ndarray u,
1028
1029 np::ndarray stress, np::ndarray strain, np::ndarray &o
1030
1031) {
1033 try {
1034
1035 if (bp::extract<bool>(mainNamespace.attr("__contains__")("f"))) {
1036 // Deprecated: Check for main objective function 'f' first for backward compatibility
1037 o = bp::extract<np::ndarray>(
1038 mainNamespace["f"](coords, u, stress, strain));
1039 } else if (bp::extract<bool>(
1040 mainNamespace.attr("__contains__")("f_interior"))) {
1041 o = bp::extract<np::ndarray>(
1042 mainNamespace["f_interior"](coords, u, stress, strain));
1043 } else {
1046 "Python function f_interior(coords,u,stress,strain) is not defined");
1047 }
1048
1049 } catch (bp::error_already_set const &) {
1050 // print all other errors to stderr
1051 PyErr_Print();
1053 }
1055}
1056
1058
1059 np::ndarray coords, np::ndarray u,
1060
1061 np::ndarray stress, np::ndarray strain, np::ndarray &o
1062
1063) {
1065 try {
1066
1067 if (bp::extract<bool>(mainNamespace.attr("__contains__")("f_stress"))) {
1068 // Deprecated: keep legacy name first for backward compatibility
1069 o = bp::extract<np::ndarray>(
1070 mainNamespace["f_stress"](coords, u, stress, strain));
1071 } else if (bp::extract<bool>(
1072 mainNamespace.attr("__contains__")("f_interior_stress"))) {
1073 o = bp::extract<np::ndarray>(
1074 mainNamespace["f_interior_stress"](coords, u, stress, strain));
1075 } else {
1078 "Python function f_interior_stress(coords,u,stress,strain) is not defined");
1079 }
1080
1081 } catch (bp::error_already_set const &) {
1082 // print all other errors to stderr
1083 PyErr_Print();
1085 }
1087}
1088
1090
1091 np::ndarray coords, np::ndarray u,
1092
1093 np::ndarray stress, np::ndarray strain, np::ndarray &o
1094
1095) {
1097 try {
1098
1099 if (bp::extract<bool>(mainNamespace.attr("__contains__")("f_strain"))) {
1100 // Deprecated: keep legacy name first for backward compatibility
1101 o = bp::extract<np::ndarray>(
1102 mainNamespace["f_strain"](coords, u, stress, strain));
1103 } else if (bp::extract<bool>(
1104 mainNamespace.attr("__contains__")("f_interior_strain"))) {
1105 o = bp::extract<np::ndarray>(
1106 mainNamespace["f_interior_strain"](coords, u, stress, strain));
1107 } else {
1110 "Python function f_interior_strain(coords,u,stress,strain) is not defined");
1111 }
1112
1113 } catch (bp::error_already_set const &) {
1114 // print all other errors to stderr
1115 PyErr_Print();
1117 }
1119}
1120
1122
1123 np::ndarray coords, np::ndarray u,
1124
1125 np::ndarray stress, np::ndarray strain, np::ndarray &o
1126
1127) {
1129 try {
1130
1131 if (bp::extract<bool>(mainNamespace.attr("__contains__")("f_u"))) {
1132 // Deprecated: keep legacy name first for backward compatibility
1133 o = bp::extract<np::ndarray>(
1134 mainNamespace["f_u"](coords, u, stress, strain));
1135 } else if (bp::extract<bool>(
1136 mainNamespace.attr("__contains__")("f_interior_u"))) {
1137 o = bp::extract<np::ndarray>(
1138 mainNamespace["f_interior_u"](coords, u, stress, strain));
1139 } else {
1142 "Python function f_interior_u(coords,u,stress,strain) is not defined");
1143 }
1144
1145 } catch (bp::error_already_set const &) {
1146 // print all other errors to stderr
1147 PyErr_Print();
1149 }
1151}
1152
1154
1155 np::ndarray coords, np::ndarray u,
1156
1157 np::ndarray t, np::ndarray &o
1158
1159) {
1161 try {
1162
1163 if (bp::extract<bool>(mainNamespace.attr("__contains__")("f_boundary_t"))) {
1164 o = bp::extract<np::ndarray>(mainNamespace["f_boundary_t"](coords, u, t));
1165 } else {
1168 "Python function f_boundary_t(coords,u,t) is not defined");
1169 }
1170
1171 } catch (bp::error_already_set const &) {
1172 PyErr_Print();
1174 }
1176}
1177
1179
1180 np::ndarray coords, np::ndarray u,
1181
1182 np::ndarray t, np::ndarray &o
1183
1184) {
1186 try {
1187
1188 if (bp::extract<bool>(mainNamespace.attr("__contains__")("f_boundary"))) {
1189 o = bp::extract<np::ndarray>(mainNamespace["f_boundary"](coords, u, t));
1190 } else if (bp::extract<bool>(
1191 mainNamespace.attr("__contains__")("f_boundary_function"))) {
1192 o = bp::extract<np::ndarray>(
1193 mainNamespace["f_boundary_function"](coords, u, t));
1194 } else {
1197 "Python function f_boundary(coords,u,t) is not defined");
1198 }
1199
1200 } catch (bp::error_already_set const &) {
1201 PyErr_Print();
1203 }
1205}
1206
1208
1209 np::ndarray coords, np::ndarray u,
1210
1211 np::ndarray t, np::ndarray &o
1212
1213) {
1215 try {
1216
1217 if (bp::extract<bool>(mainNamespace.attr("__contains__")("f_boundary_u"))) {
1218 o = bp::extract<np::ndarray>(mainNamespace["f_boundary_u"](coords, u, t));
1219 } else {
1222 "Python function f_boundary_u(coords,u,t) is not defined");
1223 }
1224
1225 } catch (bp::error_already_set const &) {
1226 PyErr_Print();
1228 }
1230}
1231
1233 int &modes) {
1235 try {
1236
1237 if (bp::extract<bool>(
1238 mainNamespace.attr("__contains__")("number_of_modes"))) {
1239 modes = bp::extract<int>(mainNamespace["number_of_modes"](block_id));
1240 } else {
1243 "Python function number_of_modes(block_id) is not defined");
1244 }
1245
1246 } catch (bp::error_already_set const &) {
1247 // print all other errors to stderr
1248 PyErr_Print();
1250 }
1252}
1253
1255 np::ndarray coords,
1256 np::ndarray centroid,
1257 np::ndarray bbodx,
1258 np::ndarray &o) {
1260 try {
1261 if (bp::extract<bool>(mainNamespace.attr("__contains__")("block_modes"))) {
1262 o = bp::extract<np::ndarray>(
1263 mainNamespace["block_modes"](block_id, coords, centroid, bbodx));
1264 } else {
1267 "Python function block_modes(block_id,coords,centroid,bbox) is not "
1268 "defined");
1269 }
1270 } catch (bp::error_already_set const &) {
1271 // print all other errors to stderr
1272 PyErr_Print();
1274 }
1276}
1277
1278/**
1279 * @brief Converts a std::vector<double> to a NumPy ndarray.
1280 *
1281 * This function wraps the given vector data into a NumPy array with the
1282 * specified number of rows and Gauss points. The resulting ndarray shares
1283 * memory with the input vector, so changes to one will affect the other.
1284 *
1285 * @param data Reference to the vector containing double values to be converted.
1286 * @param rows Number of rows in the resulting NumPy array.
1287 * @param nb_gauss_pts Number of Gauss points (columns) in the resulting NumPy
1288 * array.
1289 * @return np::ndarray NumPy array view of the input data.
1290 *
1291 * @note
1292 * - `size` specifies the shape of the resulting ndarray as a tuple (rows,
1293 * nb_gauss_pts).
1294 * - `stride` specifies the step size in bytes to move to the next element in
1295 * memory. Here, it is set to sizeof(double), indicating contiguous storage for
1296 * each element.
1297 */
1298inline np::ndarray
1299ObjectiveFunctionDataImpl::convertToNumPy(std::vector<double> &data, int rows,
1300 int nb_gauss_pts) {
1301 auto dtype = np::dtype::get_builtin<double>();
1302 auto size = bp::make_tuple(rows, nb_gauss_pts);
1303 auto stride = bp::make_tuple(nb_gauss_pts * sizeof(double), sizeof(double));
1304 return (np::from_data(data.data(), dtype, size, stride, bp::object()));
1305}
1306
1307inline np::ndarray ObjectiveFunctionDataImpl::convertToNumPy(double *ptr,
1308 int s) {
1309 auto dtype = np::dtype::get_builtin<double>();
1310 auto size = bp::make_tuple(s);
1311 auto stride = bp::make_tuple(sizeof(double));
1312 return (np::from_data(ptr, dtype, size, stride, bp::object()));
1313}
1314
1315} // namespace ShapeOptimization
Interface for Python-based objective function evaluation in topology optimization.
#define FTENSOR_INDEX(DIM, I)
#define CHK_THROW_MESSAGE(err, msg)
Check and throw MoFEM exception.
#define MoFEMFunctionBegin
First executable line of each MoFEM function, used for error handling. Final line of MoFEM functions ...
@ 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.
FTensor::Index< 'i', SPACE_DIM > i
FTensor::Index< 'j', 3 > j
PetscErrorCode MoFEMErrorCode
MoFEM/PETSc error code.
implementation of Data Operators for Forces and Sources
Definition Common.hpp:10
auto getFTensor2FromMat(M &data)
Get tensor rank 2 (matrix) form data matrix.
auto getFTensor2FromPtr(double *ptr)
constexpr int SPACE_DIM
Space dimension of problem (2D or 3D), set at compile time.
boost::shared_ptr< ObjectiveFunctionData > create_python_objective_function(std::string py_file)
Factory function to create Python-integrated objective function interface.
constexpr double t
plate stiffness
Definition plate.cpp:58
Implementation of ObjectiveFunctionData interface using Python integration.
MoFEMErrorCode blockModesImpl(int block_id, np::ndarray coords, np::ndarray centroid, np::ndarray bbodx, np::ndarray &o_ptr)
Internal implementation for topology mode generation.
MoFEMErrorCode boundaryObjectiveFunctionImpl(np::ndarray coords, np::ndarray u, np::ndarray t, np::ndarray &o)
MoFEMErrorCode evalBoundaryObjectiveGradientTraction(MatrixDouble &coords, boost::shared_ptr< MatrixDouble > u_ptr, boost::shared_ptr< MatrixDouble > t_ptr, boost::shared_ptr< MatrixDouble > o_ptr)
Evaluate gradient of objective function w.r.t. traction-like vector field.
MoFEMErrorCode boundaryObjectiveGradientUImpl(np::ndarray coords, np::ndarray u, np::ndarray t, np::ndarray &o)
MoFEMErrorCode evalInteriorObjectiveFunction(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, bool symmetrize=true)
Evaluate objective function at current state.
bp::object mainNamespace
Main Python namespace for script execution.
MoFEMErrorCode interiorObjectiveGradientStrainImpl(np::ndarray coords, np::ndarray u, np::ndarray stress, np::ndarray strain, np::ndarray &o)
Internal implementation for strain gradient computation.
MoFEMErrorCode initPython(const std::string py_file)
Initialize Python interpreter and load objective function script.
MoFEMErrorCode interiorObjectiveFunctionImpl(np::ndarray coords, np::ndarray u, np::ndarray stress, np::ndarray strain, np::ndarray &o)
Internal implementation for objective function evaluation.
void copyToSymmetric(double *ptr, MatrixDouble &s)
Convert full matrix to symmetric tensor storage format.
MoFEMErrorCode evalInteriorObjectiveGradientStrain(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, bool symmetrize=true)
Compute gradient of objective function with respect to strain.
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.
MoFEMErrorCode evalBoundaryObjectiveFunction(MatrixDouble &coords, boost::shared_ptr< MatrixDouble > u_ptr, boost::shared_ptr< MatrixDouble > t_ptr, boost::shared_ptr< VectorDouble > o_ptr, bool symmetrize=true)
np::ndarray convertToNumPy(std::vector< double > &data, int rows, int nb_gauss_pts)
Convert std::vector to NumPy array for Python interface.
MatrixDouble copyToFull(MatrixDouble &s)
Convert symmetric tensor storage to full matrix format.
MoFEMErrorCode interiorObjectiveGradientUImpl(np::ndarray coords, np::ndarray u, np::ndarray stress, np::ndarray strain, np::ndarray &o)
Internal implementation for displacement gradient computation.
MoFEMErrorCode numberOfModes(int block_id, int &modes)
Return number of topology optimization modes for given material block.
MoFEMErrorCode evalInteriorObjectiveGradientU(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, bool symmetrize=true)
Compute gradient of objective function with respect to displacement.
MoFEMErrorCode interiorObjectiveGradientStressImpl(np::ndarray coords, np::ndarray u, np::ndarray stress, np::ndarray strain, np::ndarray &o)
Internal implementation for stress gradient computation.
MoFEMErrorCode evalInteriorObjectiveGradientStress(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, bool symmetrize=true)
Compute gradient of objective function with respect to stress.
MoFEMErrorCode evalBoundaryObjectiveGradientU(MatrixDouble &coords, boost::shared_ptr< MatrixDouble > u_ptr, boost::shared_ptr< MatrixDouble > t_ptr, boost::shared_ptr< MatrixDouble > o_ptr)
MoFEMErrorCode boundaryObjectiveGradientTractionImpl(np::ndarray coords, np::ndarray u, np::ndarray t, np::ndarray &o)
Abstract interface for Python-defined objective functions.
#define EXECUTABLE_DIMENSION
Definition plastic.cpp:13