| 153 | // to gradients automatically via `MaxZeroExpression`. |
| 154 | template <typename TScalar, DifferentiabilityMode Mode, int TDim> |
| 155 | FunctionExpr<TScalar, Mode, TDim> FormInequalityPart( |
| 156 | const ConstrainedOptimizationProblem<TScalar, Mode, TDim>& prob, |
| 157 | const LagrangeMultiplierState<TScalar>& mult_state, |
| 158 | const PenaltyState<TScalar>& pen_state) { |
| 159 | FunctionExpr<TScalar, Mode, TDim> inequalityPart = |
| 160 | ConstExpression<TScalar, Mode, TDim>(0); |
| 161 | const TScalar penalty = pen_state.penalty; |
| 162 | // A zero or negative penalty in the PHR formulation is ill-defined |
| 163 | // (we would divide by zero in the constant offset). The outer |
| 164 | // loop's auto-scaling guarantees `rho > 0` before this function is |
| 165 | // called; this guard returns the zero constant if a user bypasses |
| 166 | // auto-scaling with `rho = 0`. |
| 167 | if (penalty <= TScalar{0}) { |
| 168 | return inequalityPart; |
| 169 | } |
| 170 | for (size_t j = 0; j < prob.inequality_constraints.size(); j++) { |
| 171 | const TScalar mu = mult_state.inequality_multipliers[j]; |
| 172 | const auto& g = prob.inequality_constraints[j]; |
| 173 | // argument(x) = mu - penalty * g(x). At the inner solver's |
| 174 | // tape level this is a scalar - scalar*Function composition |
| 175 | // built out of expression templates; `MaxZeroExpression` then |
| 176 | // clamps the result at zero and carries the piecewise gradient. |
| 177 | auto argument = mu - penalty * g; |
| 178 | auto positive_part = MaxZeroExpression<decltype(argument)>(argument); |
| 179 | // half_inv_rho = 1 / (2 rho). Folded as a scalar multiplier so |
| 180 | // the expression template still produces a single weighted |
| 181 | // squared term per constraint. |
| 182 | const TScalar half_inv_rho = TScalar{1} / (TScalar{2} * penalty); |
| 183 | inequalityPart = |
| 184 | inequalityPart + half_inv_rho * (positive_part * positive_part); |
| 185 | // Subtract the constant offset `mu^2 / (2 rho)`. A constant in |
| 186 | // the composite does not affect the inner solver's minimum but |
| 187 | // makes the reported composite value match the closed-form PHR |
| 188 | // expression (useful for diagnostics and tests). |
| 189 | const TScalar constant_offset = mu * mu * half_inv_rho; |
| 190 | inequalityPart = |
| 191 | inequalityPart - ConstExpression<TScalar, Mode, TDim>(constant_offset); |
| 192 | } |
| 193 | return inequalityPart; |
| 194 | } |
| 195 | |
| 196 | // --- Forming the Penalty-only composite (no Lagrangian part) --- |
| 197 | // |
no outgoing calls
no test coverage detected