Developer documentation
Version 3.0.3-105-gd3941f44
constrained_least_squares.h
Go to the documentation of this file.
1/* Copyright (c) 2008-2022 the MRtrix3 contributors.
2 *
3 * This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
6 *
7 * Covered Software is provided under this License on an "as is"
8 * basis, without warranty of any kind, either expressed, implied, or
9 * statutory, including, without limitation, warranties that the
10 * Covered Software is free of defects, merchantable, fit for a
11 * particular purpose or non-infringing.
12 * See the Mozilla Public License v. 2.0 for more details.
13 *
14 * For more details, see http://www.mrtrix.org/.
15 */
16
17#ifndef __math_constrained_least_squares_h__
18#define __math_constrained_least_squares_h__
19
20#include <set>
21#include "math/math.h"
22
23#include <Eigen/Cholesky>
24
25
26//#define DEBUG_ICLS
27
28namespace MR
29{
30 namespace Math
31 {
32
44
71 namespace ICLS {
72
73 template <typename ValueType>
75 public:
76
77 using value_type = ValueType;
78 using matrix_type = Eigen::Matrix<value_type,Eigen::Dynamic,Eigen::Dynamic>;
79 using vector_type = Eigen::Matrix<value_type,Eigen::Dynamic,1>;
80
81 Problem () { }
83
91 Problem (const matrix_type& problem_matrix,
92 const matrix_type& inequality_constraint_matrix,
93 const vector_type& inequality_constraint_vector = vector_type(),
94 size_t num_equalities = 0,
95 value_type solution_min_norm_regularisation = 0.0,
96 value_type constraint_min_norm_regularisation = 0.0,
97 size_t max_iterations = 0,
98 value_type tolerance = 0.0,
99 bool problem_in_standard_form = false) :
100 H (problem_matrix),
101 chol_HtH (H.cols(), H.cols()),
102 t (inequality_constraint_vector),
103 lambda_min_norm (constraint_min_norm_regularisation),
104 tol (tolerance),
105 max_niter (max_iterations ? max_iterations : 10*problem_matrix.cols()),
106 num_eq (num_equalities) {
107
108 if (H.cols() != inequality_constraint_matrix.cols())
109 throw Exception ("FIXME: dimensions of problem and constraint matrices do not match (ICLS)");
110
111 if (solution_min_norm_regularisation < 0.0)
112 throw Exception ("FIXME: solution norm regularisation is negative (ICLS)");
113
114 if (lambda_min_norm < 0.0)
115 throw Exception ("FIXME: constraint norm regularisation is negative (ICLS)");
116
117 if (tolerance < 0.0)
118 throw Exception ("FIXME: tolerance is negative (ICLS)");
119
120 if (t.size() && t.size() != inequality_constraint_matrix.rows())
121 throw Exception ("FIXME: dimensions of constraint matrix and vector do not match (ICLS)");
122
123 if (problem_in_standard_form) {
124 chol_HtH = H;
125 }
126 else {
127 // form quadratic problem matrix H'*H:
128 chol_HtH.setZero();
129 chol_HtH.template triangularView<Eigen::Lower>() = H.transpose() * H;
130 }
131 // add minimum norm constraint:
132 chol_HtH.diagonal().array() += solution_min_norm_regularisation * chol_HtH.diagonal().maxCoeff();
133 // get Cholesky decomposition:
134 chol_HtH.template triangularView<Eigen::Lower>() = chol_HtH.template selfadjointView<Eigen::Lower>().llt().matrixL();
135
136 // form (transpose of) matrix projecting b onto preconditioned
137 // quadratic problem chol_HtH\H:
138 if (problem_in_standard_form)
139 b2d.noalias() = chol_HtH.template triangularView<Eigen::Lower>().transpose().template solve<Eigen::OnTheRight> (Eigen::MatrixXd::Identity (H.rows(),H.cols()));
140 else
141 b2d.noalias() = chol_HtH.template triangularView<Eigen::Lower>().transpose().template solve<Eigen::OnTheRight> (H);
142
143 // project constraint onto preconditioned quadratic domain,
144 B.noalias() = chol_HtH.template triangularView<Eigen::Lower>().transpose().template solve<Eigen::OnTheRight> (inequality_constraint_matrix);
145 for (ssize_t n = 0; n < B.rows(); ++n) {
146 double norm = B.row(n).norm();
147 B.row(n) /= norm;
148 if (t.size())
149 t[n] /= norm;
150 }
151
152 }
153
155
163 Problem (const matrix_type& problem_matrix,
164 const matrix_type& inequality_constraint_matrix,
165 const matrix_type& equality_constraint_matrix,
166 const vector_type& inequality_constraint_vector = vector_type(),
167 const vector_type& equality_constraint_vector = vector_type(),
168 value_type solution_min_norm_regularisation = 0.0,
169 value_type constraint_min_norm_regularisation = 0.0,
170 size_t max_iterations = 0,
171 value_type tolerance = 0.0,
172 bool problem_in_standard_form = false) :
173 Problem (problem_matrix,
174 concat (inequality_constraint_matrix, equality_constraint_matrix),
175 concat (inequality_constraint_vector, equality_constraint_vector),
176 equality_constraint_matrix.rows(),
177 solution_min_norm_regularisation,
178 constraint_min_norm_regularisation,
179 max_iterations,
180 tolerance,
181 problem_in_standard_form) {
182 if (equality_constraint_vector.size() || inequality_constraint_vector.size()) {
183 if (ssize_t(num_eq) != equality_constraint_vector.size())
184 throw Exception ("FIXME: dimensions of equality constraint matrix and vector do not match (ICLS)");
185 }
186 }
187
188
189 size_t num_parameters () const { return H.cols(); }
190 size_t num_measurements () const { return H.rows(); }
191 size_t num_constraints () const { return B.rows(); }
192 size_t num_equalities () const { return num_eq; }
193
194 matrix_type H, chol_HtH, B, b2d;
195 vector_type t;
196 value_type lambda_min_norm, tol;
197 size_t max_niter, num_eq;
198
199 static inline matrix_type concat (const matrix_type& a, const matrix_type& b) {
200 matrix_type c (a.rows()+b.rows(), a.cols());
201 c.topRows (a.rows()) = a;
202 c.bottomRows (b.rows()) = b;
203 return c;
204 }
205
206 static inline vector_type concat (const vector_type& a, const vector_type& b) {
207 vector_type c (a.size()+b.size());
208 c.head (a.size()) = a;
209 c.tail (b.size()) = b;
210 return c;
211 }
212 };
213
214
215
216
217 template <typename ValueType>
219 public:
220
221 using value_type = ValueType;
222 using matrix_type = Eigen::Matrix<value_type,Eigen::Dynamic,Eigen::Dynamic>;
223 using vector_type = Eigen::Matrix<value_type,Eigen::Dynamic,1>;
224
225 Solver (const Problem<value_type>& problem) :
226 P (problem),
227 BtB (P.chol_HtH.rows(), P.chol_HtH.cols()),
228 B (P.B.rows(), P.B.cols()),
229 y_u (BtB.rows()),
230 c (P.B.rows()),
231 c_u (P.B.rows()),
232 lambda (c.size()),
233 lambda_prev (c.size()),
234 l (lambda.size()),
235 active (lambda.size(), false) { }
236
237 size_t operator() (vector_type& x, const vector_type& b)
238 {
239#ifdef MRTRIX_ICLS_DEBUG
240 std::ofstream l_stream ("l.txt");
241 std::ofstream n_stream ("n.txt");
242#endif
243 // compute unconstrained solution:
244 y_u = P.b2d.transpose() * b;
245 // compute constraint violations for unconstrained solution:
246 c_u = P.B * y_u;
247 if (P.t.size())
248 c_u -= P.t;
249
250 const size_t num_eq = P.num_equalities();
251 const size_t num_ineq = P.num_constraints() - num_eq;
252
253 // set all Lagrangian multipliers to zero:
254 lambda.setZero();
255 lambda_prev.setZero();
256 // set active set empty:
257 std::fill (active.begin(), active.end(), false);
258 if (num_eq > 0)
259 std::fill (active.begin() + num_ineq, active.end(), true);
260
261 // initial estimate of constraint values:
262 c = c_u;
263
264 // initial estimate of solution:
265 x = y_u;
266
267 size_t min_c_index;
268 size_t niter = 0;
269
270 while (c.head(num_ineq).minCoeff (&min_c_index) < -P.tol) {
271 bool active_set_changed = !active[min_c_index];
272 active[min_c_index] = true;
273
274 while (1) {
275 // form submatrix of active constraints:
276 size_t num_active = 0;
277 for (size_t n = 0; n < active.size(); ++n) {
278 if (active[n]) {
279 B.row (num_active) = P.B.row (n);
280 l[num_active] = -c_u[n];
281 ++num_active;
282 }
283 }
284 auto B_active = B.topRows (num_active);
285 auto l_active = l.head (num_active);
286
287 BtB.resize (num_active, num_active);
288 // solve for l in B*B'l = -c_u by Cholesky decomposition:
289 BtB.template triangularView<Eigen::Lower>() = B_active * B_active.transpose();
290 BtB.diagonal().array() += P.lambda_min_norm;
291 BtB.template selfadjointView<Eigen::Lower>().llt().solveInPlace (l_active);
292
293 // update lambda values in full vector
294 // and identify worst offender if any lambda < 0
295 // by projection from previous onto feasible
296 // subset (i.e. l>=0):
297 value_type s_min = std::numeric_limits<value_type>::infinity();
298 size_t s_min_index = 0;
299 size_t a = 0;
300 for (size_t n = 0; n < num_ineq; ++n) {
301 if (active[n]) {
302 if (l_active[a] < 0.0) {
303 value_type s = lambda_prev[n] / (lambda_prev[n] - l_active[a]);
304 if (s < s_min) {
305 s_min = s;
306 s_min_index = n;
307 }
308 }
309 lambda[n] = l_active[a];
310 ++a;
311 }
312 else
313 lambda[n] = 0.0;
314 }
315
316 // if no lambda < 0, proceed:
317 if (!std::isfinite (s_min)) {
318 // update solution vector:
319 x = y_u + B_active.transpose() * l_active;
320 break;
321 }
322#ifdef MRTRIX_ICLS_DEBUG
323 l_stream << lambda << "\n";
324#endif
325
326 // remove worst offending lambda from active set,
327 // and re-estimate remaining lambdas:
328 if (active[s_min_index])
329 active_set_changed = true;
330 active[s_min_index] = false;
331 }
332
333 // store feasible subset of lambdas:
335
336
337#ifdef MRTRIX_ICLS_DEBUG
338 l_stream << lambda << "\n";
339 for (const auto& a : active)
340 n_stream << a << " ";
341 n_stream << "\n";
342#endif
343
344 ++niter;
345 if (!active_set_changed || niter > P.max_niter)
346 break;
347
348 // compute constraint values at updated solution:
349 c = P.B * x;
350 if (P.t.size())
351 c -= P.t;
352 }
353
354 // project back to unconditioned domain:
355 P.chol_HtH.template triangularView<Eigen::Lower>().transpose().solveInPlace (x);
356 return niter;
357 }
358
359 const Problem<value_type>& problem () const { return P; }
360
361 protected:
363 matrix_type BtB, B;
364 vector_type y_u, c, c_u, lambda, lambda_prev, l;
366 };
367
368
369
370 }
371
375 }
376}
377
378#endif
379
380
381
382
383
384
385
386
const Problem< value_type > & P
MR::default_type value_type
Definition: typedefs.h:33
Definition: base.h:24
#define MEMALIGN(...)
Definition: types.h:185