DeepSDFStruct.optimization#

Structural Optimization Utilities#

This module provides tools for gradient-based optimization of SDF-based geometries, with a focus on structural design problems. It integrates with TorchFEM for finite element analysis and provides optimization algorithms suitable for constrained design problems.

Key Features#

MMA Optimizer

Implementation of the Method of Moving Asymptotes (MMA), a gradient-based algorithm well-suited for structural optimization with nonlinear constraints. MMA is particularly effective for: - Topology optimization - Shape optimization with constraints - Problems with expensive objective evaluations - Highly nonlinear design spaces

Finite Element Integration
  • Conversion between TorchFEM and PyVista mesh formats

  • Support for tetrahedral and hexahedral elements

  • Linear and quadratic element types

  • Integration with gradient computation

Mesh Quality Utilities
  • Signed volume computation for tetrahedra

  • Mesh quality metrics

  • Degeneracy detection

The module is designed to work seamlessly with differentiable SDF representations, enabling gradient-based optimization of complex 3D structures.

Functions

get_mesh_from_torchfem(Solid)

Convert a TorchFEM Solid mesh to PyVista UnstructuredGrid.

tet_signed_vol(vertices, tets)

Compute signed volumes of tetrahedral elements.

Classes

MMA(parameters, bounds[, max_step, ...])

Method of Moving Asymptotes (MMA) optimizer for constrained problems.

class DeepSDFStruct.optimization.MMA(parameters, bounds, max_step=0.1, n_constraints=1)#

Bases: object

Method of Moving Asymptotes (MMA) optimizer for constrained problems.

MMA is a gradient-based optimization algorithm designed for nonlinear constrained problems. It constructs convex subproblems using moving asymptotes and is particularly effective for structural optimization.

The optimizer handles a single objective function and a single constraint, with box bounds on design variables. It automatically normalizes the objective by its initial value for better numerical behavior.

Parameters:
  • parameters (torch.Tensor) – Initial design variables (will be optimized in-place).

  • bounds (array-like of shape (n, 2)) – Box constraints [[lower_1, upper_1], …, [lower_n, upper_n]] for each design variable.

  • max_step (float, default 0.1) – Maximum allowed change in design variables per iteration, as a fraction of the bound range.

parameters#

Current design variables (updated in-place each iteration).

Type:

torch.Tensor

loop#

Current iteration number.

Type:

int

x#

Current design variables in numpy format.

Type:

ndarray

xold1, xold2

Design variables from previous two iterations (for MMA history).

Type:

ndarray

step(F, dF, G, dG)#

Perform one MMA optimization step given objective, constraint, and their gradients.

Notes

MMA was developed by Krister Svanberg and is widely used in topology optimization. It is particularly effective for problems where: - The objective and constraints are expensive to evaluate - Gradients are available (via automatic differentiation) - The design space is high-dimensional - Strong nonlinearity is present

The implementation uses the mmapy package for the core MMA algorithm.

Examples

>>> import torch
>>> from DeepSDFStruct.optimization import MMA
>>>
>>> # Define design variables
>>> params = torch.ones(10, requires_grad=True)
>>> bounds = [[0.0, 2.0]] * 10
>>>
>>> # Create optimizer
>>> optimizer = MMA(params, bounds, max_step=0.1)
>>>
>>> # Optimization loop
>>> for i in range(100):
...     # Compute objective and constraint
...     objective = (params ** 2).sum()
...     constraint = params.sum() - 5.0
...
...     # Compute gradients
...     dF = torch.autograd.grad(objective, params, create_graph=True)[0]
...     dG = torch.autograd.grad(constraint, params, create_graph=True)[0]
...
...     # MMA step
...     optimizer.step(objective, dF, constraint, dG)
...
...     if optimizer.ch < 1e-3:
...         break

References

step(F, dF, G, dG, geom_eval=None, geom_rows=None, max_inner=1, feas_tol=0.05, restore_eval=None, restore_tol=0.005, restore_max_steps=8, restore_step_limit=None)#

Perform one MMA optimization step.

Updates design variables by solving a convex subproblem constructed from the objective, constraint, and their gradients.

Parameters:
  • F (torch.Tensor or float) – Objective function value at current design.

  • dF (torch.Tensor) – Gradient of objective w.r.t. design variables, shape (n,).

  • G (torch.Tensor or float) – Constraint values at current design (≤ 0 is feasible), reshaped to (m, 1) for the m = n_constraints rows this instance was built with. A scalar is accepted when m == 1.

  • dG (torch.Tensor) – Gradient of the constraints w.r.t. design variables, reshaped to (m, n). For m == 1 a flat (n,) tensor is accepted.

  • geom_eval (callable, optional) – Cheap geometry-only re-evaluation x_np -> np.ndarray. Given a candidate design vector it returns the true (nonlinear) constraint values g = value - target for the rows listed in geom_rows, without running the expensive (CFD) objective/constraints. Enables the hybrid-GCMMA conservativeness loop; when None this is a plain MMA step.

  • geom_rows (sequence of int, optional) – Row indices into G for the geometry-only constraints that geom_eval returns, in the same order. Required with geom_eval.

  • max_inner (int, optional) – Maximum GCMMA inner (conservativeness) iterations per step when geom_eval is supplied. Each rejected candidate raises the offending rows’ curvature parameter rho (Svanberg 2002 raaupdate) and re-solves; the move limit stays FIXED. Default 1 (no inner loop).

  • feas_tol (float, optional) – TRUE-violation level below which a candidate is accepted outright, in the units of the constraint rows (pass normalized, “fraction over budget” rows and the default 0.05 reads “5% over budget is tolerated transiently”). Must be well above 0: at an ACTIVE constraint the true value always reads slightly above the approximation (residual curvature, evaluation noise) – with a ~0 tolerance the inner loop fires on every boundary-riding step and burns max_inner solves per iteration for nothing.

  • restore_eval (callable, optional) – Enables POST-STEP FEASIBILITY RESTORATION on the cheap geometry rows: x_np -> (g, J) returning the true values (k,) AND their gradients (k, n) – masked for locked variables, in the same normalized units as the constraint rows. After the step is accepted (through whichever gate), the candidate is projected back onto the geometry-feasible set with minimum-norm Gauss-Newton passes, so geometry violations beyond restore_tol cannot survive an iteration. Independent of the GCMMA inner loop (works with or without geom_eval). No CFD is invoked.

  • restore_tol (float, optional) – Restoration target/trigger: rows with g <= restore_tol are left alone. Keep it above the geometry-evaluation noise floor (default 5e-3).

  • restore_max_steps (int, optional) – Maximum Gauss-Newton passes per outer iteration (default 8; typically 1-2 are used). Each pass costs one geometry evaluation plus up to 4 backtracks.

  • restore_step_limit (float, optional) – Per-pass infinity-norm cap on the correction; defaults to max_step.

Notes

The method automatically: - Normalizes the objective by its initial value - Enforces move limits based on max_step - Updates MMA history (xold1, xold2) - Computes and logs convergence metric (ch) - Updates self.parameters in-place

The convergence metric ch is the relative change in design variables.

DeepSDFStruct.optimization.get_mesh_from_torchfem(Solid)#

Convert a TorchFEM Solid mesh to PyVista UnstructuredGrid.

This function enables visualization and export of TorchFEM finite element meshes using PyVista. It supports both tetrahedral and hexahedral elements with linear and quadratic shape functions.

Parameters:

Solid (torchfem.Solid) – TorchFEM solid mesh object containing nodes, elements, and element type.

Returns:

PyVista mesh representation suitable for visualization and I/O.

Return type:

pyvista.UnstructuredGrid

Raises:

NotImplementedError – If input is not a torchfem.Solid object.

Notes

Supported element types: - Tetra1: 4-node linear tetrahedron - Tetra2: 10-node quadratic tetrahedron - Hexa1: 8-node linear hexahedron - Hexa2: 20-node quadratic hexahedron

Examples

>>> from DeepSDFStruct.optimization import get_mesh_from_torchfem
>>> import torchfem
>>>
>>> # Assume we have a TorchFEM solid mesh
>>> # solid = torchfem.Solid(...)
>>>
>>> # Convert to PyVista for visualization
>>> pv_mesh = get_mesh_from_torchfem(solid)
>>> pv_mesh.plot()
DeepSDFStruct.optimization.tet_signed_vol(vertices, tets)#

Compute signed volumes of tetrahedral elements.

Calculates the signed volume of each tetrahedron, which is positive for correctly oriented elements and negative for inverted elements. This is useful for detecting mesh degeneracies and enforcing mesh quality constraints.

Parameters:
  • vertices (torch.Tensor) – Vertex coordinates of shape (N, 3).

  • tets (torch.Tensor) – Tetrahedral connectivity of shape (M, 4), where each row contains vertex indices [v0, v1, v2, v3].

Returns:

Signed volumes of shape (M,), one per tetrahedron. Positive volumes indicate correctly oriented elements.

Return type:

torch.Tensor

Notes

The signed volume is computed as:

V = (1/6) * ((v1-v0) × (v2-v0)) · (v3-v0)

Examples

>>> import torch
>>> from DeepSDFStruct.optimization import tet_signed_vol
>>>
>>> # Define a simple tetrahedron
>>> vertices = torch.tensor([
...     [0.0, 0.0, 0.0],
...     [1.0, 0.0, 0.0],
...     [0.0, 1.0, 0.0],
...     [0.0, 0.0, 1.0]
... ])
>>> tets = torch.tensor([[0, 1, 2, 3]])
>>> volumes = tet_signed_vol(vertices, tets)
>>> print(f"Volume: {volumes[0]:.3f}")  # Should be 1/6 ≈ 0.167