|
Neko-TOP
A portable framework for high-order spectral element flow toplogy optimization.
|
In general case files are used as the main input in order to configure a simulation and optimization problem. Most settings pertaining to the simulation are defined by Neko, so please refer to the Neko documentation for more information on these settings.
The overall structure of the case file is the following
Neko-TOP has two optimizers, selected with optimization.solver.type:
"mma" — the Method of Moving Asymptotes, which handles both constrained and unconstrained problems."steepest_descent" — the steepest descent method with move limits (SDM), which handles unconstrained problems only.Both read a handful of settings directly from optimization.solver, and keep their own options in a nested object named after the type.
| Json entry | Description | Admissible Values | Default value |
|---|---|---|---|
type | Selects the optimizer. | "mma" or "steepest_descent" | *(required)* |
max_iterations | Maximum number of outer optimization iterations. | Integer, > 0 | 100 |
enable_output | Write the convergence log (optimization_data.csv) and the simulation output at every iteration. | .true. or .false. | .true. |
stop_design_change | Stop once the maximum design change drops strictly below this value. | Real, > 0 | machine epsilon |
max_runtime | Maximum wall-clock runtime, as a duration string (e.g. 1-12:00:00 for one day and twelve hours). Empty disables the limit. | String | "" (disabled) |
Restart and checkpoint keys (restart_file, checkpoint.*) are likewise read the same way by every optimizer; see Checkpointing and restart below.
In a case file, MMA is configured in optimization.solver, with MMA-specific settings under optimization.solver.mma.
| Json entry | Description | Admissible Values | Default value |
|---|---|---|---|
max_iter | Maximum number of iterations in the MMA subsolver. | Integer, > 0 | 100 |
epsimin | Minimum barrier parameter used by MMA. | Real, > 0 | 1.0e-9 * sqrt(m + n_global) |
asyinit | Initial asymptote distance factor. | Real, > 0 | 0.2 |
asyincr | Asymptote increase factor. | Real, > 0 | 1.05 |
asydecr | Asymptote decrease factor. | Real, > 0 | 0.65 |
move_limit | Per-iteration move cap around current design variables. | Real | 0.2 |
backend | MMA execution backend. | "cpu" or "device" | "cpu" (or "device" when NEKO_BCKND_DEVICE=1) |
subsolver | Interior-point variant used in MMA subproblems. | "dip" or "pdip" | "dip" |
xmin | Global lower bound applied to all design variables. | Real | 0.0 |
xmax | Global upper bound applied to all design variables. | Real | 1.0 |
a0 | MMA scalar coefficient \(a_0\). | Real | 1.0 |
a | MMA vector coefficient \(a_i\) (applied uniformly). | Real | 0.0 |
c | MMA vector coefficient \(c_i\) (applied uniformly). | Real | 1000.0 |
d | MMA vector coefficient \(d_i\) (applied uniformly). | Real | 0.0 |
scale | Constraint scaling target value. | Real, > 0 | 1.0 |
auto_scale | Enable adaptive scaling of constraints each iteration. | .true. or .false. | .false. |
subsolver selects the interior-point variant used inside MMA (dip/pdip).backend selects whether MMA update/KKT operations run on CPU or device routines.max_iter and epsimin define limits for the MMA subsolver iterations and barrier parameter.asyinit, asyincr, and asydecr control moving-asymptote updates.a0, a, c, and d are coefficients in the standard MMA subproblem formulation.xmin and xmax set global lower/upper bounds for all design variables.move_limit restricts each update to a local interval around the current iterate. The effective interval used by MMA is max(xmin, x - move_limit) to min(xmax, x + move_limit). If move_limit <= 0, only global bounds are used.scale sets the constraint scaling magnitude.auto_scale enables adaptive scaling from the current constraint value at each iteration.The steepest descent method with move limits (SDM) is Appendix D of da Silva, Aage, Beck & Sigmund, "Three-dimensional manufacturing tolerant topology
optimization with hundreds of millions of local stress constraints", IJNME 122 (2021) 548-578, doi:10.1002/nme.6548 (Eqs. D22-D24). It is configured in optimization.solver, with its own settings under optimization.solver.steepest_descent.
| Json entry | Description | Admissible Values | Default value |
|---|---|---|---|
xmin | Lower bound of the design variables. | Real | 0.0 |
xmax | Upper bound of the design variables. Must be larger than xmin. | Real | 1.0 |
step_length | Step length \(\Psi\) applied to the normalised direction. | Real, > 0 | 1.0 |
move_limit.initial | Initial per-variable move limit. Must not be smaller than move_limit.min. | Real | 0.1 |
move_limit.min | Smallest allowed move limit. | Real, > 0 | 0.001 |
move_limit.max | Largest allowed move limit. Must not be smaller than move_limit.initial. | Real | 0.1 |
move_limit.decrease | Factor applied to a variable's move limit when it oscillates. | Real, (0, 1] | 0.7 |
move_limit.increase | Factor applied to a variable's move limit when it moves monotonically. | Real, >= 1 | 1.1 |
Violating any of the relations in the "Admissible Values" column above stops the run with an error naming the offending key.
Let \(\rho^{(b)}\) be the design at iteration \(b\) and \(G = \nabla f(\rho^{(b)})\) its gradient (the directional derivative of the objective for a Brinkman-masked design, the raw objective sensitivity otherwise). Each iteration:
\[ S_e = 0 \quad\text{if}\quad (\rho_e^{(b)} \ge x^{\max} \text{ and } S_e > 0) \quad\text{or}\quad (\rho_e^{(b)} \le x^{\min} \text{ and } S_e < 0). \]
\[ S_{\max} = \max_e |S_e|, \]
taken over all MPI ranks.\[ \delta_e \leftarrow \begin{cases} \texttt{move\_limit.decrease} \cdot \delta_e & (\rho_e^{(b)} - \rho_e^{(b-1)})(\rho_e^{(b-1)} - \rho_e^{(b-2)}) < 0,\\[4pt] \texttt{move\_limit.increase} \cdot \delta_e & (\rho_e^{(b)} - \rho_e^{(b-1)})(\rho_e^{(b-1)} - \rho_e^{(b-2)}) > 0,\\[4pt] \delta_e & \text{otherwise}, \end{cases} \]
clamped to \([\texttt{move\_limit.min}, \texttt{move\_limit.max}]\) afterwards. A sign change (oscillation) shrinks the limit; monotonic progress grows it.\[ \rho^{(b+1)} = \min\!\Bigl(x^{\max}, \max\!\Bigl(x^{\min}, \min\bigl(\rho^{(b)} + \delta,\, \max\bigl(\rho^{(b)} - \delta,\, \rho^{(b)} + \Psi \tfrac{S}{S_{\max}}\bigr)\bigr)\Bigr)\Bigr), \]
with \(\Psi = \)step_length. This is exactly Eq. D24 whenever \(\rho^{(b)}\) lies in \([x^{\min}, x^{\max}]\) (see Bounds and the Brinkman mask below for when it does not).SDM handles box bounds only. If the case file defines any optimization.constraints, initialisation stops with an error pointing to the "mma" optimizer instead.
There is no tolerance-based convergence criterion. A run ends when one of:
stop_design_change;max_iterations or max_runtime is reached."stop_design_change": 0.01. The base check is a strict <.move_limit.decrease, until it is clamped to move_limit.min; the resulting step then sits at, or just above, move_limit.min. If stop_design_change is not larger than move_limit.min, criterion 2 above can never fire, and the run continues until max_iterations/max_runtime instead.For a Brinkman design with an optimization-domain mask, points outside that domain are held at 0 by the design's forward mapping, regardless of xmin. With xmin > 0 those points therefore sit below xmin permanently, and — because both the current and the updated design are masked to the same value there — they do not contribute to the reported maximum design change.
An initial design that is infeasible (outside [xmin, xmax]) is projected into the box on the very first step: the box clamp in the update above is applied after the move-limit window, so that first move is not bounded by move_limit.initial and can be larger than move_limit.max.
The optimizer adds a max_abs_gradient column to optimization_data.csv: the value logged for iteration \(k\) is \(S_{\max}\) of the bound-projected direction computed at the start of iteration \(k\), i.e. evaluated at the design produced by iteration \(k - 1\). Row 0, logged before any step is taken, is 0.
The design history \(\rho^{(b-1)}\), \(\rho^{(b-2)}\) and the per-variable move limits \(\delta\) are saved to, and restored from, the checkpoint under /SteepestDescent (see Checkpointing and restart below for the general checkpoint settings). A restart must use the same number of MPI ranks as the run that produced the checkpoint.
Neko-TOP supports checkpointing and restart of optimization runs. This is particularly useful for long-running optimization problems, as it allows the user to save the state of the optimization at regular intervals and restart from the last checkpoint in case of a failure or if the optimization needs to be paused. Additionally, a checkpoint can be emitted when a given maximum runtime is reached, allowing the user to effectively set a maximum runtime for the optimization.
When enabled, the checkpointing system will emit checkpoint files at regular intervals during the optimization. Each checkpoint will be named according to the following pattern: path/base_iter.extension, where path is the path specified by the user for checkpoint files, base is the base name specified by the user for checkpoint files, iter is the iteration number at which the checkpoint was emitted, and extension is the file extension corresponding to the chosen checkpoint format. A special case is the runtime-based checkpointing, which will be named as path/optimizer_rt_checkpoint.extension.
In order to restart an optimization from a checkpoint, the user can specify the checkpoint file to restart from in the case file under optimization.solver.restart_file. When this parameter is set, the optimization will be restarted from the specified checkpoint file. Please note that the checkpoint file must be compatible with the current case file and Neko-TOP version in order for the restart to work correctly. If a runtime-based checkpoint exists and the user has not specified a checkpoint file to restart from, the optimization will automatically restart from the runtime-based checkpoint.
On a restart, any continuation parameters are first restored to the values they held at the checkpointed iteration, before the problem is re-evaluated at the restored design — so the first post-restart gradient matches the one from the original run.
Parameters related to checkpointing can be set under the optimization.solver section of the case file.
| Parameter | Type | Default | Description |
|---|---|---|---|
restart_file | string | "" | Filename of a checkpoint file to restart from. If this parameter is set, the optimization will be restarted from the specified checkpoint file. |
checkpoint.interval | integer | -1 | Interval (in iterations) at which checkpoints are emitted. If set to a positive value, a checkpoint will be emitted every checkpoint.interval iterations. If set to -1, no checkpoints will be emitted. |
checkpoint.path | string | "./checkpoints/" | Path where checkpoint files will be saved. |
checkpoint.base | string | "optimizer_checkpoint" | Base name for checkpoint files. |
checkpoint.format | string | "hdf5" | Format for checkpoint files. Accepted spellings are "h5", "hdf5", "hf5" and "hdf", all of which write an HDF5 file. |
The individual components are described in greater detail in the linked sections.