dragonfly_sim.utils.dv_sensitivity
Total-derivative norms per individual design parameter.
Motivation
PODBasis.construct_basis’s local_weighted mode weights snapshots by a plain
Euclidean distance across the snapshot parameter vector. When design variables of
different physical character share that vector – control point motions in metres
alongside angle of attack in radians – an equal distance in different dimensions
implies a wildly different change in the state, so the weighting ends up dominated
by whichever variable happens to have the loosest bound. The intended fix is a
diagonal weighting matrix on the distance, one weight per design parameter, sized
by how strongly the solution responds to that parameter.
Adjoint-based optimization never forms du/dx, so the stand-in used here is the
total derivative of the OUTPUT quantities, dF/dx, which the gradient evaluation
already computes. This module extracts it and reports, for each individual design
parameter j,
s_j = || [dF_1/dp_j, …, dF_m/dp_j] ||_2
over all optimization outputs F (objective and constraints).
Where the numbers come from
SimulatorBase.compute_optimization_derivatives() returns df of shape
(1, n_dv) and dc of shape (n_c, n_dv) – the total derivatives of the
objective and constraints with respect to the flattened design variable vector, in
PHYSICAL (unscaled) design variable units. modopt applies its own
o_scaler/x_scaler only afterwards, so what the simulator hands back is
exactly what is wanted here. attach_total_derivative_probe wraps that method on
the simulator INSTANCE, so nothing in csdl_alpha or modopt is modified and
no additional solves are performed – it reads the dict that is already being
returned to the optimizer.
sim.dv_meta / sim.c_meta / sim.o_meta (built by
csdl_alpha.backends.simulator.build_opt_metadata) map each csdl Variable to its
l_ind:u_ind slice of the flat vector, which is what lets every column be
labelled with its design variable and its index inside that variable.
Units: param_scales
If the POD parameter vector is not the design variable vector verbatim,
param_scales[j] is the factor mapping one to the other,
p_j = param_scales[j] * x_j, so the reported sensitivities are divided by it:
dF/dp_j = (dF/dx_j) / param_scales[j]
That puts the printed numbers directly in the units the POD distance is measured
in. In DRAGonFLy the snapshot parameter vector (DG_windtunnel_model.
_parameter_vector: the flat shape parameters, then alpha) is the design variable
vector, so param_scales is normally left out.
Dependencies
Deliberately numpy-only in its numeric core – no csdl, petsc or dolfinx –
following the same rule as ffd_dv_utils, so it can be exercised without an MPI
environment. extract_metadata is the single function that touches csdl objects,
and it only reads plain attributes (.name, .shape, .size). Callers pass
their own printer (typically PETSc.Sys.Print) to keep output on rank 0.
Classes
Labels for the rows and columns of the stacked total-derivative matrix. |
Functions
|
One label per entry of a variable, flattened in C order. |
|
The objective and constraint VALUES, in the row order of the stacked J. |
|
Print the per-parameter total-derivative norms on every gradient evaluation. |
|
Build a DVSensitivityMeta from a csdl_alpha simulator. |
|
Render the norms as a printable multi-line table. |
|
Compute and print the norms for one |
|
Stack the objective gradient and constraint Jacobian into one (n_out, n_dv). |
|
Column and block norms of the stacked total-derivative matrix. |
Module Contents
- class dragonfly_sim.utils.dv_sensitivity.DVSensitivityMeta
Bases:
NamedTupleLabels for the rows and columns of the stacked total-derivative matrix.
- param_labelsn_dv strings, one per individual design parameter, in the
order of the flattened design variable vector
group_slices : [(name, l_ind, u_ind)] per design VARIABLE, same order output_labels: n_out strings, objective first then constraints, matching the
row order of
stack_total_derivatives- group_slices: list
- property n_dv
- property n_out
- output_labels: list
- param_labels: list
- dragonfly_sim.utils.dv_sensitivity._entry_labels(name, shape, size)
One label per entry of a variable, flattened in C order.
C order is not a free choice:
build_opt_metadatafills the flat vector withvar.value.flatten(), andffd_dv_utils.build_cp_motion_dvbuilds the matching bounds the same way, so entry[i, j, k]of a design variable is at flat indexravel_multi_index((i, j, k), shape).
- dragonfly_sim.utils.dv_sensitivity._stack_output_values(deriv_dict, n_out)
The objective and constraint VALUES, in the row order of the stacked J.
Returns None if they are absent or do not add up to n_out, in which case the magnitude-normalized column norms are simply skipped – they are a diagnostic, not worth failing the probe over.
- dragonfly_sim.utils.dv_sensitivity.attach_total_derivative_probe(sim, printer=print, param_scales=None, record_to=None, record_attr='total_derivative_norms_per_iteration', eval_idx_getter=None, on_norms=None, max_entries=64)
Print the per-parameter total-derivative norms on every gradient evaluation.
Wraps
compute_optimization_derivativeson the simulator INSTANCE (an instance attribute shadowing the class method), which is what keeps this out of csdl_alpha and modopt entirely. modopt guards that call with its own warm-xcheck, so it fires once per new design point and costs no additional solves – the wrapper only reads the dict already being returned.- Parameters:
- sima csdl_alpha SimulatorBase set up as an optimization problem
- printercallable taking one string; pass PETSc.Sys.Print under MPI. The
derivatives are replicated across ranks (the custom operations allreduce their jacvec products), so rank-0 printing loses nothing.
- param_scales(n_dv,) design-variable-to-parameter unit conversion; see the
module docstring. Its length is checked against the design variable count HERE, at attach time, rather than at the first gradient evaluation – that is the guard that catches a design variable being added without the snapshot parameter vector being updated together.
- record_tooptional object (a DataStore) with a list attribute named
record_attr, appended with(eval_idx, column_norms)per call.- eval_idx_gettercallable returning the evaluation index these derivatives
belong to. Without one, the probe counts its own calls.
- on_normsoptional callable receiving the full norms dict after each
gradient evaluation. This is how a consumer that wants to ACT on the sensitivities gets them – see ReducedOrderModel.set_distance_sensitivities, which turns them into the diagonal weights for the local_weighted POD distance. Called inside the same guard as the reporting, so a consumer that raises degrades the diagnostic rather than the optimization.
- Returns:
- callable that detaches the probe.
- dragonfly_sim.utils.dv_sensitivity.extract_metadata(sim)
Build a DVSensitivityMeta from a csdl_alpha simulator.
The ONLY function here that touches csdl objects. Everything downstream works on numpy arrays and plain python lists.
Variables created by
CustomOperation.create_outputcarry no name (it does not calladd_name), so unnamed outputs fall back to positional labels. Give the objective a name withadd_nameat the call site if the printout should identify it.
- dragonfly_sim.utils.dv_sensitivity.format_total_derivative_norms(norms, meta, header=None, max_entries=64)
Render the norms as a printable multi-line table.
The per-group summary and the per-output x per-group block norms are always shown; the full per-parameter listing only when there are at most
max_entriesdesign parameters, since a large FFD block would otherwise bury the rest of the log.
- dragonfly_sim.utils.dv_sensitivity.print_total_derivative_norms(deriv_dict, meta, printer=print, param_scales=None, header=None, max_entries=64)
Compute and print the norms for one
compute_optimization_derivativesresult.Returns the norms dict, or None when the result carried no derivatives at all.
- dragonfly_sim.utils.dv_sensitivity.stack_total_derivatives(df, dc)
Stack the objective gradient and constraint Jacobian into one (n_out, n_dv).
dfis(1, n_dv)anddcis(n_c, n_dv), exactly ascompute_optimization_derivativesreturns them; either may be None (an unconstrained problem, or a feasibility problem with no objective). Returns None when both are.
- dragonfly_sim.utils.dv_sensitivity.total_derivative_norms(J, meta=None, param_scales=None, output_values=None)
Column and block norms of the stacked total-derivative matrix.
- Parameters:
- J(n_out, n_dv) array from stack_total_derivatives
- metaDVSensitivityMeta, optional – needed for the labelled block norms
- param_scales(n_dv,) array, optional. p_j = param_scales[j]*x_j; J is
divided by it so every reported quantity is per unit of the POD parameter, not per unit of the design variable. See the module docstring.
- output_values(n_out,) array, optional – the values of the outputs
themselves, used for the magnitude-normalized variant.
- Returns:
- dict with keys ‘J’ (the scaled matrix), ‘column_norms’,
- ‘column_norms_relative’ (None without output_values) and ‘block_norms’
- (None without meta).