Derivative Computation¶
Several coordination methods — for example ALADIN and SBDP — need first- and sometimes second-order derivative information of each subsystem's objective \(v_f\), constraints \(v_h\) & \(v_g\) and mapped \(H\)(r) responses.
The DDO framework lets the user supply as much or as little derivative information in Analysis<id>, LocalObjective<id> and LocalConstraints<id> (see Tutorial > Problem Definition and Algorithm Execution) as they know, and transparently fills every remaining gap: missing first-order entries are approximated by finite differences (FiniteDifferencesJacobian), and missing second-order information is approximated by a BFGS update (HessianApproximationBFGS).
User-provided derivative interfaces¶
Users express whatever derivatives they know by implementing the (optional) derivative methods of three interfaces, listed below. Most of these methods return the derivative they compute — an objective gradient, a constraint Jacobian, or a (list of) Hessian(s) — as a nested list whose unknown entries are simply left as None. The single exception is mapLocalResponsesDesignVariables_to_CouplingParameters_Jacobians(), which does not return anything: it writes each neighbor's mapped-response Jacobian through the setter set_MappedResponses_Jacobian(id=, mappedresponses_jacobian_in=). Whatever is left as None — a whole matrix or individual entries — is filled in transparently by the fallback machinery described below.
| Interface | Derivative methods | Quantity |
|---|---|---|
| [`LocalObjectiveInterface`](../api/Distributed_Design_Optimizer/subsystem/optimization/designproblem/LocalObjectiveInterface.md) | `evaluate_Gradient_LocalObjective()` | Gradient of the local objective |
| `evaluate_Hessian_LocalObjective()` | Hessian of the local objective | |
| [`LocalConstraintsInterface`](../api/Distributed_Design_Optimizer/subsystem/optimization/designproblem/LocalConstraintsInterface.md) | `evaluate_Jacobian_EqualityLocalConstraints()` | Jacobian of local equality constraints |
| `evaluate_Jacobian_InEqualityLocalConstraints()` | Jacobian of local inequality constraints | |
| `evaluate_Hessians_EqualityLocalConstraints()` | Hessians of local equality constraints | |
| `evaluate_Hessians_InEqualityLocalConstraints()` | Hessians of local inequality constraints | |
| [`AnalysisInterface`](../api/Distributed_Design_Optimizer/subsystem/optimization/AnalysisInterface.md) | `mapLocalResponsesDesignVariables_to_CouplingParameters_Jacobians()` | Jacobians of the mapped coupling responses |
| `mapLocalResponsesDesignVariables_to_CouplingParameters_Hessian()` | Hessians of the mapped coupling responses |
The coordination-objective gradient and the coordination-constraint Jacobians are not user-facing: they are derived internally from the formulation of each coordination method. SubSystemInterface declares evaluate_Gradient_CoordinationObjective(), evaluate_Jacobian_CoordinationEqualityConstraints() and evaluate_Jacobian_CoordinationInEqualityConstraints(), and each coordination method's LocalSubSystem<Method> class (e.g. LocalSubSystemALC, LocalSubSystemALADIN, LocalSubSystemSBDP) overrides them with its own coordination formulation — filling in analytic entries where available and otherwise leaving them None for the finite-difference / BFGS fallback described below.
When adding a new use-case, implementing the derivative methods of the three interfaces above is entirely optional: they can be left empty to rely on finite differences and BFGS, or filled with whatever analytic entries are known to improve accuracy and reduce analysis calls. The GeometricProgramming example demonstrates the fully analytic path — LocalObjective0 returns an analytic gradient and Hessian and LocalConstraints0 returns analytic constraint Jacobians and Hessians — whereas the SSBJ example keeps its mapped-response Jacobians as all-None matrices of the correct shape so the framework finite-differences them (its underlying physics has no closed form). See Tutorial > Novel Distributed Optimization Method for where subsystem classes plug into this machinery.
Scaled-space convention¶
The optimizer works entirely in the scaled \([0, 1]\) design domain, so every user-provided derivative is expected in that same scaled space. Because each scaler is affine with a constant slope $s = $ scaler.get_scale() $ = \mathrm{d}(\text{scaled})/\mathrm{d}(\text{unscaled})$, converting an analytic partial computed in physical (unscaled) units is a plain chain-rule rescaling:
where \(s_{d_j}\) is the slope of the \(j\)-th design-variable scaler and \(s_y\) the slope of the scaler of the differentiated quantity \(y\) (objective, constraint or mapped response; \(s_y = 1\) for a quantity that is itself left unscaled). Second-order terms rescale analogously with an additional factor \(1/s_{d_k}\) per extra derivative direction. The Tutorial examples carry # === Tutorial: scaled <-> unscaled ... (chain rule) === comment blocks inside each derivative method showing this conversion in place.
Coordination-objective and coordination-constraint derivatives¶
The value of the coordination-specific terms — \(P\) (or, for methods with dedicated coordination constraints, \(Q^{\leq}\) and \(Q^{=}\)) — is evaluated every solver iteration: LocalSubSystemBasis.evaluateTotalObjective() calls evaluateCoordinationObjective(), and evaluateTotalConstraint() calls evaluateCoordinationEqualityConstraint() and evaluateCoordinationInequalityConstraint(), each overridden per coordination method to compute the value from that method's penalty / augmented-Lagrangian / consensus formulation (see SubSystem Optimization). The corresponding derivative methods evaluate_Gradient_CoordinationObjective(), evaluate_Jacobian_CoordinationEqualityConstraints() and evaluate_Jacobian_CoordinationInEqualityConstraints() are a separate, independent set of overrides on the same LocalSubSystem<Method> class, only consulted by the Orchestration step below via evaluateAllJacobians().
Inspecting the current coordination methods shows that none of them fill in the coordination-objective gradient analytically — LocalSubSystemALC, LocalSubSystemConsensusALC, LocalSubSystemLC, LocalSubSystemPC, LocalSubSystemALADIN and LocalSubSystemSBDP all leave evaluate_Gradient_CoordinationObjective() unimplemented (marked # TODO), relying entirely on finite differences whenever it is actually consulted. Coordination constraints only exist for SBDP, whose hard equality constraint \(c_h\), \(c_z\) is computed analytically in evaluateCoordinationEqualityConstraint(); its Jacobian evaluate_Jacobian_CoordinationEqualityConstraints() is nonetheless still # TODO (again deferring to finite differences). All other methods (ALC and its variants, Consensus ALC, LC, PC, ALADIN) have no coordination constraints at all — their coupling is instead enforced through the coordination objective \(P\) (a penalty / augmented-Lagrangian term for ALC-family methods and Consensus ALC, or the regularization term for ALADIN) — so their evaluate_Jacobian_CoordinationEqualityConstraints() / evaluate_Jacobian_CoordinationInEqualityConstraints() are simple no-ops.
As with the user-facing interfaces above, implementing these coordination-derivative overrides is entirely optional when adding a new coordination method: they can be left as no-ops (as every built-in method currently does) to rely on finite differences and BFGS, or filled with analytic entries to improve accuracy and reduce analysis calls. See Tutorial > Novel Distributed Optimization Method for where subsystem classes plug into this machinery.
First-order approximation — FiniteDifferencesJacobian¶
FiniteDifferencesJacobian approximates, by perturbing the design variables, every first-order quantity listed above.
The routine operates on the design variables. The per-direction step size \(\varepsilon_j\) is derived from each design variable's granularity.
For each perturbed direction \(j\) the method chooses the difference scheme according to the box bounds, so that no perturbation ever leaves the feasible interval. Writing \(e_j\) for the \(j\)-th unit vector, the three admissible schemes are
Each perturbation is applied through updateSubsystem(), which sets the perturbed design variables and re-runs the analysis, mapping, objective, and constraint evaluations so that every dependent quantity is refreshed before the difference is taken.
Orchestration — evaluateAllJacobians()¶
The merge of user-provided and finite-difference derivatives happens in LocalSubSystemBasis.evaluateAllJacobians(). It (1) collects whatever the user provides, (2) determines which directions are still missing, (3) runs finite differences once over the union of those directions, and (4) writes the approximations into the None slots only.
The design-variable bound Jacobians (active lower / upper bounds) are always assembled by the framework itself, and the total objective gradient and constraint Jacobians are finally composed from the local and coordination parts.
Second-order information — HessianApproximationBFGS¶
Methods that need Hessians (notably ALADIN) obtain them through HessianApproximationBFGS, a subclass of scipy.optimize.BFGS. It maintains, across coordination iterations, a curvature-based approximation of the Hessian of a scalar function (the local objective, or an individual constraint / mapped-response component) from successive gradient evaluations.
Because the BFGS estimate depends on the whole gradient history, LocalSubSystemALADIN.evaluateAllHessians() advances the approximation every iteration. The merge with user-provided Hessians is, however, all-or-nothing per matrix rather than entry-wise: if the user's Hessian for a given function is None or contains any None entry, the complete BFGS matrix is used for that function; only a fully specified user Hessian is taken as-is. A separate BFGS object is kept for the local objective and for each local equality constraint, local inequality constraint, and mapped-response component.
Where this is used in the workflow¶
evaluateAllJacobians() is implemented once in LocalSubSystemBasis, but it is not called automatically after every local optimization — each coordination method's LocalSubSystem<Method>.postprocess_Optimization() decides whether its coordination scheme actually needs gradients / Jacobians and, if so, calls evaluateAllJacobians() itself. Currently LocalSubSystemALADIN and LocalSubSystemSBDP call it, so that the gradients and Jacobians are evalauted at the optimal desing varibal point \(d\). Methods that also require curvature, such as ALADIN, additionally call evaluateAllHessians() to assemble the Hessian of the Lagrangian exchanged with the controller. Methods whose coordination scheme needs neither derivative order (for example standard ALC, which updates its multipliers and weights by a subgradient rule) leave postprocess_Optimization() empty and simply never trigger these routines.