A constraint that is marked soft may be violated, at a cost. Softening a constraint guarantees that the problem stays feasible, and the penalty decides how the unavoidable violation is shared among the soft constraints.
The penalty
With soft constraints being given by the index set \(\mathcal{S}\), DAQP solves
\[\begin{aligned} \min_{x,\,s_l,\,s_u}\quad & \tfrac{1}{2}x^\top H x + f^\top x + \sum_{i\in\mathcal{S}} \left( w_{l,i}\, s_{l,i} + \frac{s_{l,i}^2}{2\rho_{l,i}} + w_{u,i}\, s_{u,i} + \frac{s_{u,i}^2}{2\rho_{u,i}} \right) \\ \text{subject to}\quad & b_l - s_l \leq A x \leq b_u + s_u, \qquad s_l,\, s_u \geq 0, \end{aligned}\]so each side of a soft constraint has two weights:
| meaning | effect | |
|---|---|---|
| \(w\) | linear (L1) weight | the multiplier has to exceed \(w\) before the constraint is violated at all, so a large enough \(w\) makes the penalty exact: the constraint is satisfied whenever that is possible |
| \(\rho\) | reciprocal quadratic (L2) weight | a larger \(\rho\) permits more violation; \(\rho \to 0\) recovers a hard constraint |
The penalty is continuously differentiable, and the slack is zero while \(\lvert \lambda \rvert \leq w\) and \(\rho(\lvert \lambda \rvert - w)\) beyond that, where \(\lambda\) is the multiplier of the constraint.
Marking a constraint as soft
Set bit 8 (DAQP_SOFT) in sense for the constraints that may be violated.
C
int sense[3] = {0, DAQP_SOFT, DAQP_SOFT}; // rows 1 and 2 may be violated
Julia
sense = Cint[0, 8, 8]
MATLAB
d.soften_constraints([2 3]); % or set sense(i) = 8 directly
Python
sense = np.array([0, 8, 8], dtype=np.intc)
Uniform weights
By default every soft constraint uses the settings rho_soft and w_soft (see Settings). With the default w_soft = 0 the penalty is purely quadratic, and rho_soft = 1e-6 keeps the violation small, i.e. the constraint is almost hard.
These two are given in the normalized formulation that the solver works in, where the rows of the constraint matrix have unit norm. The individual weights below are instead given in the scale of the original problem.
Individual weights
To weight the constraints differently, set one weight per constraint and side. Only the entries that are nonzero take effect; a zero entry falls back to rho_soft and w_soft.
C
#include "api.h"
DAQPWorkspace work = {0};
setup_daqp(&qp, &work, NULL);
double rho_l[3] = {0, 1e-2, 1e-4}; // reciprocal quadratic weights
double rho_u[3] = {0, 1e-2, 1e-4};
double w_l[3] = {0, 0.5, 10.0}; // linear weights
double w_u[3] = {0, 0.5, 10.0};
if(!daqp_set_soft_weights(&work, rho_l, rho_u, w_l, w_u))
printf("built with DAQP_NO_SOFT_WEIGHTS\n");
DAQPResult result = {x, lam};
daqp_solve(&result, &work);
A NULL argument leaves that weight alone, so daqp_set_soft_weights(&work, rho, rho, NULL, NULL) sets only the quadratic weights. The weights may also be written directly into work.rho_ls, work.rho_us, work.w_ls and work.w_us after daqp_allocate_soft_weights(&work). If you change work.settings->rho_soft or work.settings->w_soft after setup, call daqp_refresh_soft_weights(&work) before the next solve.
Julia
d = DAQP.Model()
DAQP.setup(d, H, f, A, bupper, blower, sense)
soft_weights(d; rho_l = [0, 1e-2, 1e-4], rho_u = [0, 1e-2, 1e-4],
w_l = [0, 0.5, 10.0], w_u = [0, 0.5, 10.0])
x, fval, exitflag, info = DAQP.solve(d)
MATLAB
d.soft_weights([0 1e-2 1e-4], [0 1e-2 1e-4], [0 0.5 10], [0 0.5 10]);
% [] leaves that weight at its default, e.g. d.soft_weights(rho_l, rho_u);
Python
d = daqp.Model()
d.setup(H, f, A, bupper, blower, sense)
d.soft_weights(rho_l=[0, 1e-2, 1e-4], rho_u=[0, 1e-2, 1e-4],
w_l=[0, 0.5, 10.0], w_u=[0, 0.5, 10.0])
x, fval, exitflag, info = d.solve()
C++ (Eigen)
DAQP solver(n, m, m);
solver.update(H, f, A, bupper, blower, sense, break_points);
Eigen::VectorXd rho(3), w(3);
rho << 0, 1e-2, 1e-4;
w << 0, 0.5, 10.0;
solver.set_soft_weights(rho, rho, w, w); // false if built without support
solver.solve();
Individual weights are part of every default build. They can be compiled out with -DDAQP_NO_SOFT_WEIGHTS (cmake -DSOFT_WEIGHTS=OFF), which makes every soft constraint use rho_soft/w_soft; the functions above then report that the weights are unavailable instead of silently ignoring them.
The resulting violation
If a soft constraint ends up violated, the solver returns the exit flag 2 (SOFT_OPTIMAL) instead of 1, and soft_slack in the result holds the largest violation, in the units of the original problem.