v0.15.5
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<VectorDouble> 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 MatrixDouble f(9, s.size2());
420 f.clear();
423 auto t_f = getFTensor2FromMat<3, 3>(f);
424 auto t_s = getFTensor2SymmetricFromMat<SPACE_DIM>(s);
425 for (int ii = 0; ii != s.size2(); ++ii) {
426 t_f(i, j) = t_s(i, j);
427 ++t_f;
428 ++t_s;
429 }
430 return f;
431};
432
437
438 ptr + 0 * s.size2(), ptr + 1 * s.size2(),
439 ptr + 2 * s.size2(), ptr + 3 * s.size2(),
440 ptr + 4 * s.size2(), ptr + 5 * s.size2(),
441 ptr + 6 * s.size2(), ptr + 7 * s.size2(),
442 ptr + 8 * s.size2()
443
444 };
445 auto t_s = getFTensor2SymmetricFromMat<SPACE_DIM>(s);
446 for (int ii = 0; ii != s.size2(); ++ii) {
447 t_s(i, j) = (t_f(i, j) || t_f(j, i)) / 2.0;
448 ++t_f;
449 ++t_s;
450 }
451}
452
453/**
454 * @brief Evaluate objective function at current finite element state
455 *
456 * This method bridges MoFEM finite element data with Python-defined objective
457 * functions for topology optimization. It handles the complete data conversion
458 * workflow from MoFEM matrices to NumPy arrays, calls the Python objective
459 * function, and converts results back to MoFEM format.
460 *
461 * Process:
462 * 1. Convert coordinate matrix to NumPy format for Python access
463 * 2. Convert displacement field data to NumPy arrays
464 * 3. Convert symmetric stress/strain tensors to full 3x3 matrix format
465 * 4. Call Python objective function: f(coords, u, stress, strain)
466 * 5. Extract results and copy back to MoFEM vector format
467 *
468 * The objective function typically computes scalar quantities like:
469 * - Compliance: ∫ u^T * f dΩ (minimize structural deformation)
470 * - Stress constraints: ∫ ||σ - σ_target||² dΩ (control stress distribution)
471 * - Volume constraints: ∫ ρ dΩ (material usage limitations)
472 *
473 * @param coords Gauss point coordinates for current element
474 * @param u_ptr Displacement field values at Gauss points
475 * @param stress_ptr Cauchy stress tensor values (symmetric storage)
476 * @param strain_ptr Strain tensor values (symmetric storage)
477 * @param o_ptr Output objective function values at each Gauss point
478 * @return MoFEMErrorCode Success or error code
479 */
481 MatrixDouble &coords, boost::shared_ptr<MatrixDouble> u_ptr,
482 boost::shared_ptr<MatrixDouble> stress_ptr,
483 boost::shared_ptr<MatrixDouble> strain_ptr,
484 boost::shared_ptr<VectorDouble> o_ptr, bool symmetrize) {
486 try {
487
488 // Convert coordinates to NumPy array for Python function
489 auto np_coords =
490 convertToNumPy(coords.data(), coords.size1(), coords.size2());
491 // Convert displacement field to NumPy array
492 auto np_u = convertToNumPy(u_ptr->data(), u_ptr->size1(), u_ptr->size2());
493
494 // Convert symmetric tensor storage to full matrix format for Python
495 // MoFEM stores symmetric tensors in like Voigt notation, Python expects full 3x3 matrices
496 auto full_stress = symmetrize ? copyToFull(*(stress_ptr)) : *(stress_ptr);
497 auto full_strain = symmetrize ? copyToFull(*(strain_ptr)) : *(strain_ptr);
498
499 // Create NumPy arrays for stress and strain tensors
500 auto np_stress = convertToNumPy(full_stress.data(), full_stress.size1(),
501 full_stress.size2());
502 auto np_strain = convertToNumPy(full_strain.data(), full_strain.size1(),
503 full_strain.size2());
504
505 // Prepare output array for objective function values
506 np::ndarray np_output = np::empty(bp::make_tuple(strain_ptr->size2()),
507 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 // Copy Python results back to MoFEM vector format
514 o_ptr->resize(stress_ptr->size2(), false);
515 double *val_ptr = reinterpret_cast<double *>(np_output.get_data());
516 std::copy(val_ptr, val_ptr + strain_ptr->size2(), o_ptr->data().begin());
517
518 } catch (bp::error_already_set const &) {
519 // Handle Python errors with detailed error reporting
520 PyErr_Print();
522 }
524}
525
526/**
527 * @brief Compute gradient of objective function with respect to stress tensor
528 *
529 * This method evaluates ∂f/∂σ, the partial derivative of the objective function
530 * with respect to the Cauchy stress tensor. This gradient is fundamental to the
531 * adjoint method for topology optimization, as it provides the driving force
532 * for the adjoint equation solution.
533 *
534 * Mathematical context:
535 * The adjoint method requires ∂f/∂σ to compute sensitivities efficiently.
536 * For stress-based objectives like von Mises stress constraints:
537 * ∂f/∂σ = ∂/∂σ[∫(σ_vm - σ_target)² dΩ] = 2(σ_vm - σ_target) * ∂σ_vm/∂σ
538 *
539 * Process:
540 * 1. Convert all field data to NumPy format for Python processing
541 * 2. Call Python function f_stress(coords, u, stress, strain)
542 * 3. Python returns full 3x3 gradient matrices for each Gauss point
543 * 4. Convert back to symmetric tensor storage used by MoFEM
544 * 5. Store results for use in adjoint equation assembly
545 *
546 * The resulting gradients drive the adjoint solution that enables efficient
547 * computation of design sensitivities independent of design variable count.
548 *
549 * @param coords Gauss point coordinates
550 * @param u_ptr Displacement field values
551 * @param stress_ptr Current stress tensor values (symmetric storage)
552 * @param strain_ptr Current strain tensor values (symmetric storage)
553 * @param o_ptr Output stress gradients ∂f/∂σ (symmetric storage)
554 * @return MoFEMErrorCode Success or error code
555 */
557 MatrixDouble &coords, boost::shared_ptr<MatrixDouble> u_ptr,
558 boost::shared_ptr<MatrixDouble> stress_ptr,
559 boost::shared_ptr<MatrixDouble> strain_ptr,
560 boost::shared_ptr<MatrixDouble> o_ptr, bool symmetrize) {
562 try {
563
564 // Convert coordinates and displacement field to NumPy format
565 auto np_coords =
566 convertToNumPy(coords.data(), coords.size1(), coords.size2());
567 auto np_u = convertToNumPy(u_ptr->data(), u_ptr->size1(), u_ptr->size2());
568
569 // Convert symmetric tensors to full 3x3 format for Python processing
570 auto full_stress = symmetrize ? copyToFull(*(stress_ptr)) : *(stress_ptr);
571 auto full_strain = symmetrize ? copyToFull(*(strain_ptr)) : *(strain_ptr);
572
573 // Create NumPy arrays for stress and strain tensors
574 auto np_stress = convertToNumPy(full_stress.data(), full_stress.size1(),
575 full_stress.size2());
576 auto np_strain = convertToNumPy(full_strain.data(), full_strain.size1(),
577 full_strain.size2());
578 // Prepare output array for stress gradients (full matrix format)
579 np::ndarray np_output =
580 np::empty(bp::make_tuple(full_strain.size1(), full_strain.size2()),
581 np::dtype::get_builtin<double>());
582
583 // Call Python implementation for stress gradient computation
584 CHKERR interiorObjectiveGradientStressImpl(np_coords, np_u, np_stress, np_strain,
585 np_output);
586
587 // Prepare output matrix in symmetric format
588 o_ptr->resize(stress_ptr->size1(), stress_ptr->size2(), false);
589 double *val_ptr = reinterpret_cast<double *>(np_output.get_data());
590 // Convert full matrix results back to symmetric tensor storage
591 copyToSymmetric(val_ptr, *(o_ptr));
592
593 } catch (bp::error_already_set const &) {
594 PyErr_Print();
596 }
598}
599
600/**
601 * @brief Compute gradient of objective function with respect to strain tensor
602 *
603 * This method evaluates ∂f/∂ε, the partial derivative of the objective function
604 * with respect to the strain tensor. While many structural objectives depend
605 * primarily on stress, strain-based gradients are important for certain
606 * optimization formulations and provide additional sensitivity information.
607 *
608 * Mathematical context:
609 * For strain energy-based objectives: f = ½ε:C:ε
610 * The gradient is: ∂f/∂ε = C:ε = σ (stress tensor)
611 *
612 * For strain-based constraints or objectives like strain concentration:
613 * ∂f/∂ε = ∂/∂ε[∫(ε_vm - ε_target)² dΩ] = 2(ε_vm - ε_target) * ∂ε_vm/∂ε
614 *
615 * Process:
616 * 1. Convert field data to NumPy format for Python compatibility
617 * 2. Call Python function f_strain(coords, u, stress, strain)
618 * 3. Python returns gradient matrices ∂f/∂ε for each Gauss point
619 * 4. Convert from full 3x3 format back to symmetric storage
620 * 5. Results used in adjoint sensitivity analysis
621 *
622 * This gradient complements stress-based gradients in comprehensive
623 * sensitivity analysis for topology optimization problems.
624 *
625 * @param coords Gauss point coordinates
626 * @param u_ptr Displacement field values
627 * @param stress_ptr Current stress tensor values (symmetric storage)
628 * @param strain_ptr Current strain tensor values (symmetric storage)
629 * @param o_ptr Output strain gradients ∂f/∂ε (symmetric storage)
630 * @return MoFEMErrorCode Success or error code
631 */
633 MatrixDouble &coords, boost::shared_ptr<MatrixDouble> u_ptr,
634 boost::shared_ptr<MatrixDouble> stress_ptr,
635 boost::shared_ptr<MatrixDouble> strain_ptr,
636 boost::shared_ptr<MatrixDouble> o_ptr, bool symmetrize) {
638 try {
639
640 // Convert coordinates and displacement data to NumPy format
641 auto np_coords =
642 convertToNumPy(coords.data(), coords.size1(), coords.size2());
643 auto np_u = convertToNumPy(u_ptr->data(), u_ptr->size1(), u_ptr->size2());
644
645 // Convert symmetric tensor data to full 3x3 matrices for Python
646 auto full_stress = symmetrize ? copyToFull(*(stress_ptr)) : *(stress_ptr);
647 auto full_strain = symmetrize ? copyToFull(*(strain_ptr)) : *(strain_ptr);
648
649 auto np_stress = convertToNumPy(full_stress.data(), full_stress.size1(),
650 full_stress.size2());
651 auto np_strain = convertToNumPy(full_strain.data(), full_strain.size1(),
652 full_strain.size2());
653
654 // Prepare output array for strain gradients
655 np::ndarray np_output =
656 np::empty(bp::make_tuple(full_strain.size1(), full_strain.size2()),
657 np::dtype::get_builtin<double>());
658
659 // Call Python implementation for strain gradient computation
660 CHKERR interiorObjectiveGradientStrainImpl(np_coords, np_u, np_stress, np_strain,
661 np_output);
662 o_ptr->resize(strain_ptr->size1(), strain_ptr->size2(), false);
663 double *val_ptr = reinterpret_cast<double *>(np_output.get_data());
664 copyToSymmetric(val_ptr, *(o_ptr));
665
666 } catch (bp::error_already_set const &) {
667 PyErr_Print();
669 }
671}
672
673/**
674 * @brief Compute gradient of objective function with respect to displacement field
675 *
676 * This method evaluates ∂f/∂u, the partial derivative of the objective function
677 * with respect to the displacement field. This gradient is crucial for the adjoint
678 * method as it forms the right-hand side of the adjoint equation: K^T * λ = ∂f/∂u
679 *
680 * Mathematical context:
681 * The adjoint method solves: K^T * λ = ∂f/∂u
682 * where λ are the adjoint variables (Lagrange multipliers)
683 *
684 * For compliance minimization: f = ½u^T * K * u
685 * The gradient is: ∂f/∂u = K * u (applied forces)
686 *
687 * For displacement-based constraints: f = ||u - u_target||²
688 * The gradient is: ∂f/∂u = 2(u - u_target)
689 *
690 * Process:
691 * 1. Convert all field data to NumPy format for Python processing
692 * 2. Call Python function f_u(coords, u, stress, strain)
693 * 3. Python returns displacement gradients for each component
694 * 4. Copy results directly (no tensor conversion needed for vectors)
695 * 5. Results drive adjoint equation solution for sensitivity analysis
696 *
697 * This gradient is fundamental to adjoint-based topology optimization,
698 * enabling efficient sensitivity computation for any number of design variables.
699 *
700 * @param coords Gauss point coordinates
701 * @param u_ptr Displacement field values
702 * @param stress_ptr Current stress tensor values
703 * @param strain_ptr Current strain tensor values
704 * @param o_ptr Output displacement gradients ∂f/∂u
705 * @return MoFEMErrorCode Success or error code
706 */
708 MatrixDouble &coords, boost::shared_ptr<MatrixDouble> u_ptr,
709 boost::shared_ptr<MatrixDouble> stress_ptr,
710 boost::shared_ptr<MatrixDouble> strain_ptr,
711 boost::shared_ptr<MatrixDouble> o_ptr, bool symmetrize) {
713 try {
714
715 // Convert coordinates and displacement field to NumPy format
716 auto np_coords =
717 convertToNumPy(coords.data(), coords.size1(), coords.size2());
718 auto np_u = convertToNumPy(u_ptr->data(), u_ptr->size1(), u_ptr->size2());
719
720 // Convert stress and strain tensors to full matrix format
721 auto full_stress = symmetrize ? copyToFull(*(stress_ptr)) : *(stress_ptr);
722 auto full_strain = symmetrize ? copyToFull(*(strain_ptr)) : *(strain_ptr);
723
724 auto np_stress = convertToNumPy(full_stress.data(), full_stress.size1(),
725 full_stress.size2());
726 auto np_strain = convertToNumPy(full_strain.data(), full_strain.size1(),
727 full_strain.size2());
728
729 // Prepare output array for displacement gradients (same size as displacement field)
730 np::ndarray np_output =
731 np::empty(bp::make_tuple(u_ptr->size1(), u_ptr->size2()),
732 np::dtype::get_builtin<double>());
733
734 // Call Python implementation for displacement gradient computation
735 // Note: This should call interiorObjectiveGradientUImpl, not interiorObjectiveGradientStrainImpl
736 CHKERR interiorObjectiveGradientUImpl(np_coords, np_u, np_stress, np_strain,
737 np_output);
738
739 // Copy results directly to output matrix (no tensor conversion needed for vectors)
740 o_ptr->resize(u_ptr->size1(), u_ptr->size2(), false);
741 double *val_ptr = reinterpret_cast<double *>(np_output.get_data());
742 std::copy(val_ptr, val_ptr + u_ptr->size1() * u_ptr->size2(),
743 o_ptr->data().begin());
744
745 } catch (bp::error_already_set const &) {
746 // Handle Python errors with detailed reporting
747 PyErr_Print();
749 }
751}
752
754 MatrixDouble &coords, boost::shared_ptr<MatrixDouble> u_ptr,
755 boost::shared_ptr<MatrixDouble> t_ptr,
756 boost::shared_ptr<MatrixDouble> o_ptr) {
758 try {
759
760 auto np_coords =
761 convertToNumPy(coords.data(), coords.size1(), coords.size2());
762 auto np_u = convertToNumPy(u_ptr->data(), u_ptr->size1(), u_ptr->size2());
763 auto np_t = convertToNumPy(t_ptr->data(), t_ptr->size1(), t_ptr->size2());
764
765 np::ndarray np_output =
766 np::empty(bp::make_tuple(u_ptr->size1(), u_ptr->size2()),
767 np::dtype::get_builtin<double>());
768
769 CHKERR boundaryObjectiveGradientTractionImpl(np_coords, np_u, np_t, np_output);
770
771 o_ptr->resize(u_ptr->size1(), u_ptr->size2(), false);
772 double *val_ptr = reinterpret_cast<double *>(np_output.get_data());
773 std::copy(val_ptr, val_ptr + u_ptr->size1() * u_ptr->size2(),
774 o_ptr->data().begin());
775
776 } catch (bp::error_already_set const &) {
777 PyErr_Print();
779 }
781}
782
784 MatrixDouble &coords, boost::shared_ptr<MatrixDouble> u_ptr,
785 boost::shared_ptr<MatrixDouble> t_ptr,
786 boost::shared_ptr<VectorDouble> o_ptr, bool symmetrize) {
788 try {
789 (void)symmetrize;
790
791 auto np_coords =
792 convertToNumPy(coords.data(), coords.size1(), coords.size2());
793 auto np_u = convertToNumPy(u_ptr->data(), u_ptr->size1(), u_ptr->size2());
794 auto np_t = convertToNumPy(t_ptr->data(), t_ptr->size1(), t_ptr->size2());
795
796 np::ndarray np_output = np::empty(bp::make_tuple(t_ptr->size2()),
797 np::dtype::get_builtin<double>());
798
799 CHKERR boundaryObjectiveFunctionImpl(np_coords, np_u, np_t, np_output);
800
801 o_ptr->resize(t_ptr->size2(), false);
802 double *val_ptr = reinterpret_cast<double *>(np_output.get_data());
803 std::copy(val_ptr, val_ptr + t_ptr->size2(), o_ptr->data().begin());
804
805 } catch (bp::error_already_set const &) {
806 PyErr_Print();
808 }
810}
811
813 MatrixDouble &coords, boost::shared_ptr<MatrixDouble> u_ptr,
814 boost::shared_ptr<MatrixDouble> t_ptr,
815 boost::shared_ptr<MatrixDouble> o_ptr) {
817 try {
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 boundaryObjectiveGradientUImpl(np_coords, np_u, np_t, np_output);
828
829 o_ptr->resize(u_ptr->size1(), u_ptr->size2(), false);
830 double *val_ptr = reinterpret_cast<double *>(np_output.get_data());
831 std::copy(val_ptr, val_ptr + u_ptr->size1() * u_ptr->size2(),
832 o_ptr->data().begin());
833
834 } catch (bp::error_already_set const &) {
835 PyErr_Print();
837 }
839}
840
841/**
842 * @brief Generate spatial topology modes for design optimization
843 *
844 * This method defines the design parameterization for topology optimization by
845 * generating spatial basis functions (modes) that describe how the geometry
846 * can be modified during optimization. These modes serve as design variables
847 * and define the feasible design space for the optimization problem.
848 *
849 * Mathematical context:
850 * The geometry modification is parameterized as: x_new = x_original + Σ(αᵢ * φᵢ(x))
851 * where αᵢ are design variables and φᵢ(x) are spatial mode functions
852 *
853 * Common mode types:
854 * - Radial basis functions: φ(x) = exp(-||x-c||²/σ²) for localized changes
855 * - Polynomial modes: φ(x) = xⁿyᵐzᵖ for global shape changes
856 * - Sinusoidal modes: φ(x) = sin(kx)cos(ly) for periodic patterns
857 * - Principal component modes: Derived from geometric sensitivity analysis
858 *
859 * Process:
860 * 1. Query Python function for number of modes for this material block
861 * 2. Convert coordinate data and geometric information to NumPy format
862 * 3. Call Python function block_modes(block_id, coords, centroid, bbox)
863 * 4. Python returns mode vectors for each coordinate at each mode
864 * 5. Reshape and store modes for use as design variables in optimization
865 *
866 * The modes enable efficient design space exploration and gradient-based
867 * optimization while maintaining geometric feasibility and smoothness.
868 *
869 * @param block_id Material block identifier for mode generation
870 * @param coords Element coordinates where modes are evaluated
871 * @param centroid Geometric centroid of the material block [x,y,z]
872 * @param bbodx Bounding box dimensions [xmin,xmax,ymin,ymax,zmin,zmax]
873 * @param o_ptr Output matrix: modes × (coordinates × spatial_dimension)
874 * @return MoFEMErrorCode Success or error code
875 */
877 int block_id, MatrixDouble &coords, std::array<double, 3> &centroid,
878 std::array<double, 6> &bbodx, MatrixDouble &o_ptr) {
880 try {
881
882 // Query Python function for number of topology modes for this block
883 int nb_modes;
884 CHKERR numberOfModes(block_id, nb_modes);
885
886 // Convert coordinate matrix to NumPy format for Python processing
887 auto np_coords =
888 convertToNumPy(coords.data(), coords.size1(), coords.size2());
889
890 // Convert geometric information to NumPy arrays
891 auto np_centroid =
892 convertToNumPy(centroid.data(), 3); // Block centroid [x,y,z]
893 auto np_bbodx = convertToNumPy(
894 bbodx.data(), 6); // Bounding box [xmin,xmax,ymin,ymax,zmin,zmax]
895
896 // Prepare output array: [coordinates × modes × spatial_dimensions]
897 np::ndarray np_output =
898 np::empty(bp::make_tuple(coords.size1(), nb_modes, 3),
899 np::dtype::get_builtin<double>());
900
901 // Call Python implementation to generate topology modes
902 CHKERR blockModesImpl(block_id, np_coords, np_centroid, np_bbodx,
903 np_output);
904
905 // Reshape output matrix for MoFEM format: [modes × (coordinates * spatial_dimensions)]
906 o_ptr.resize(nb_modes, coords.size1() * coords.size2(), false);
907 double *val_ptr = reinterpret_cast<double *>(np_output.get_data());
908 // Copy flattened mode data to output matrix
909 std::copy(val_ptr, val_ptr + coords.size1() * coords.size2() * nb_modes,
910 o_ptr.data().begin());
911
912 } catch (bp::error_already_set const &) {
913 // Handle Python errors in mode generation
914 PyErr_Print();
916 }
918}
919
921
922 np::ndarray coords, np::ndarray u,
923
924 np::ndarray stress, np::ndarray strain, np::ndarray &o
925
926) {
928 try {
929
930 if (bp::extract<bool>(mainNamespace.attr("__contains__")("f"))) {
931 // Deprecated: Check for main objective function 'f' first for backward compatibility
932 o = bp::extract<np::ndarray>(
933 mainNamespace["f"](coords, u, stress, strain));
934 } else if (bp::extract<bool>(
935 mainNamespace.attr("__contains__")("f_interior"))) {
936 o = bp::extract<np::ndarray>(
937 mainNamespace["f_interior"](coords, u, stress, strain));
938 } else {
941 "Python function f_interior(coords,u,stress,strain) is not defined");
942 }
943
944 } catch (bp::error_already_set const &) {
945 // print all other errors to stderr
946 PyErr_Print();
948 }
950}
951
953
954 np::ndarray coords, np::ndarray u,
955
956 np::ndarray stress, np::ndarray strain, np::ndarray &o
957
958) {
960 try {
961
962 if (bp::extract<bool>(mainNamespace.attr("__contains__")("f_stress"))) {
963 // Deprecated: keep legacy name first for backward compatibility
964 o = bp::extract<np::ndarray>(
965 mainNamespace["f_stress"](coords, u, stress, strain));
966 } else if (bp::extract<bool>(
967 mainNamespace.attr("__contains__")("f_interior_stress"))) {
968 o = bp::extract<np::ndarray>(
969 mainNamespace["f_interior_stress"](coords, u, stress, strain));
970 } else {
973 "Python function f_interior_stress(coords,u,stress,strain) is not defined");
974 }
975
976 } catch (bp::error_already_set const &) {
977 // print all other errors to stderr
978 PyErr_Print();
980 }
982}
983
985
986 np::ndarray coords, np::ndarray u,
987
988 np::ndarray stress, np::ndarray strain, np::ndarray &o
989
990) {
992 try {
993
994 if (bp::extract<bool>(mainNamespace.attr("__contains__")("f_strain"))) {
995 // Deprecated: keep legacy name first for backward compatibility
996 o = bp::extract<np::ndarray>(
997 mainNamespace["f_strain"](coords, u, stress, strain));
998 } else if (bp::extract<bool>(
999 mainNamespace.attr("__contains__")("f_interior_strain"))) {
1000 o = bp::extract<np::ndarray>(
1001 mainNamespace["f_interior_strain"](coords, u, stress, strain));
1002 } else {
1005 "Python function f_interior_strain(coords,u,stress,strain) is not defined");
1006 }
1007
1008 } catch (bp::error_already_set const &) {
1009 // print all other errors to stderr
1010 PyErr_Print();
1012 }
1014}
1015
1017
1018 np::ndarray coords, np::ndarray u,
1019
1020 np::ndarray stress, np::ndarray strain, np::ndarray &o
1021
1022) {
1024 try {
1025
1026 if (bp::extract<bool>(mainNamespace.attr("__contains__")("f_u"))) {
1027 // Deprecated: keep legacy name first for backward compatibility
1028 o = bp::extract<np::ndarray>(
1029 mainNamespace["f_u"](coords, u, stress, strain));
1030 } else if (bp::extract<bool>(
1031 mainNamespace.attr("__contains__")("f_interior_u"))) {
1032 o = bp::extract<np::ndarray>(
1033 mainNamespace["f_interior_u"](coords, u, stress, strain));
1034 } else {
1037 "Python function f_interior_u(coords,u,stress,strain) is not defined");
1038 }
1039
1040 } catch (bp::error_already_set const &) {
1041 // print all other errors to stderr
1042 PyErr_Print();
1044 }
1046}
1047
1049
1050 np::ndarray coords, np::ndarray u,
1051
1052 np::ndarray t, np::ndarray &o
1053
1054) {
1056 try {
1057
1058 if (bp::extract<bool>(mainNamespace.attr("__contains__")("f_boundary_t"))) {
1059 o = bp::extract<np::ndarray>(mainNamespace["f_boundary_t"](coords, u, t));
1060 } else {
1063 "Python function f_boundary_t(coords,u,t) is not defined");
1064 }
1065
1066 } catch (bp::error_already_set const &) {
1067 PyErr_Print();
1069 }
1071}
1072
1074
1075 np::ndarray coords, np::ndarray u,
1076
1077 np::ndarray t, np::ndarray &o
1078
1079) {
1081 try {
1082
1083 if (bp::extract<bool>(mainNamespace.attr("__contains__")("f_boundary"))) {
1084 o = bp::extract<np::ndarray>(mainNamespace["f_boundary"](coords, u, t));
1085 } else if (bp::extract<bool>(
1086 mainNamespace.attr("__contains__")("f_boundary_function"))) {
1087 o = bp::extract<np::ndarray>(
1088 mainNamespace["f_boundary_function"](coords, u, t));
1089 } else {
1092 "Python function f_boundary(coords,u,t) is not defined");
1093 }
1094
1095 } catch (bp::error_already_set const &) {
1096 PyErr_Print();
1098 }
1100}
1101
1103
1104 np::ndarray coords, np::ndarray u,
1105
1106 np::ndarray t, np::ndarray &o
1107
1108) {
1110 try {
1111
1112 if (bp::extract<bool>(mainNamespace.attr("__contains__")("f_boundary_u"))) {
1113 o = bp::extract<np::ndarray>(mainNamespace["f_boundary_u"](coords, u, t));
1114 } else {
1117 "Python function f_boundary_u(coords,u,t) is not defined");
1118 }
1119
1120 } catch (bp::error_already_set const &) {
1121 PyErr_Print();
1123 }
1125}
1126
1128 int &modes) {
1130 try {
1131
1132 if (bp::extract<bool>(
1133 mainNamespace.attr("__contains__")("number_of_modes"))) {
1134 modes = bp::extract<int>(mainNamespace["number_of_modes"](block_id));
1135 } else {
1138 "Python function number_of_modes(block_id) is not defined");
1139 }
1140
1141 } catch (bp::error_already_set const &) {
1142 // print all other errors to stderr
1143 PyErr_Print();
1145 }
1147}
1148
1150 np::ndarray coords,
1151 np::ndarray centroid,
1152 np::ndarray bbodx,
1153 np::ndarray &o) {
1155 try {
1156 if (bp::extract<bool>(mainNamespace.attr("__contains__")("block_modes"))) {
1157 o = bp::extract<np::ndarray>(
1158 mainNamespace["block_modes"](block_id, coords, centroid, bbodx));
1159 } else {
1162 "Python function block_modes(block_id,coords,centroid,bbox) is not "
1163 "defined");
1164 }
1165 } catch (bp::error_already_set const &) {
1166 // print all other errors to stderr
1167 PyErr_Print();
1169 }
1171}
1172
1173/**
1174 * @brief Converts a std::vector<double> to a NumPy ndarray.
1175 *
1176 * This function wraps the given vector data into a NumPy array with the
1177 * specified number of rows and Gauss points. The resulting ndarray shares
1178 * memory with the input vector, so changes to one will affect the other.
1179 *
1180 * @param data Reference to the vector containing double values to be converted.
1181 * @param rows Number of rows in the resulting NumPy array.
1182 * @param nb_gauss_pts Number of Gauss points (columns) in the resulting NumPy
1183 * array.
1184 * @return np::ndarray NumPy array view of the input data.
1185 *
1186 * @note
1187 * - `size` specifies the shape of the resulting ndarray as a tuple (rows,
1188 * nb_gauss_pts).
1189 * - `stride` specifies the step size in bytes to move to the next element in
1190 * memory. Here, it is set to sizeof(double), indicating contiguous storage for
1191 * each element.
1192 */
1193inline np::ndarray
1194ObjectiveFunctionDataImpl::convertToNumPy(std::vector<double> &data, int rows,
1195 int nb_gauss_pts) {
1196 auto dtype = np::dtype::get_builtin<double>();
1197 auto size = bp::make_tuple(rows, nb_gauss_pts);
1198 auto stride = bp::make_tuple(nb_gauss_pts * sizeof(double), sizeof(double));
1199 return (np::from_data(data.data(), dtype, size, stride, bp::object()));
1200}
1201
1202inline np::ndarray ObjectiveFunctionDataImpl::convertToNumPy(double *ptr,
1203 int s) {
1204 auto dtype = np::dtype::get_builtin<double>();
1205 auto size = bp::make_tuple(s);
1206 auto stride = bp::make_tuple(sizeof(double));
1207 return (np::from_data(ptr, dtype, size, stride, bp::object()));
1208}
1209
1210} // 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
#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
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 evalInteriorObjectiveFunction(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, bool symmetrize=true)
Evaluate objective function at current state.
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)
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