1 // =============================================================================
2 // PROJECT CHRONO - http://projectchrono.org
3 //
4 // Copyright (c) 2014 projectchrono.org
5 // All rights reserved.
6 //
7 // Use of this source code is governed by a BSD-style license that can be found
8 // in the LICENSE file at the top level of the distribution and at
9 // http://projectchrono.org/license-chrono.txt.
10 //
11 // =============================================================================
12 // Authors: Alessandro Tasora, Radu Serban
13 // =============================================================================
14
15 #include "chrono/solver/ChSolverBB.h"
16 #include "chrono/core/ChMathematics.h"
17
18 namespace chrono {
19
20 // Register into the object factory, to enable run-time dynamic creation and persistence
CH_FACTORY_REGISTER(ChSolverBB)21 CH_FACTORY_REGISTER(ChSolverBB)
22
23 ChSolverBB::ChSolverBB() : n_armijo(10), max_armijo_backtrace(3), lastgoodres(1e30) {}
24
Solve(ChSystemDescriptor & sysd)25 double ChSolverBB::Solve(ChSystemDescriptor& sysd) {
26 std::vector<ChConstraint*>& mconstraints = sysd.GetConstraintsList();
27 std::vector<ChVariables*>& mvariables = sysd.GetVariablesList();
28
29 if (sysd.GetKblocksList().size() > 0) {
30 throw ChException("ChSolverBB: Do NOT use Barzilai-Borwein solver if you have stiffness matrices.");
31 }
32
33 // Tuning of the spectral gradient search
34 double a_min = 1e-13;
35 double a_max = 1e13;
36 double sigma_min = 0.1;
37 double sigma_max = 0.9;
38 double alpha = 0.0001;
39 double gamma = 1e-4;
40 double gdiff = 0.000001;
41
42 bool do_BB1e2 = true;
43 bool do_BB1 = false;
44 bool do_BB2 = false;
45 double neg_BB1_fallback = 0.11;
46 double neg_BB2_fallback = 0.12;
47
48 m_iterations = 0;
49 // Allocate auxiliary vectors;
50
51 int nc = sysd.CountActiveConstraints();
52 if (verbose)
53 GetLog() << "\n-----Barzilai-Borwein, solving nc=" << nc << "unknowns \n";
54
55 ChVectorDynamic<> ml(nc);
56 ChVectorDynamic<> ml_candidate(nc);
57 ChVectorDynamic<> mg(nc);
58 ChVectorDynamic<> mg_p(nc);
59 ChVectorDynamic<> ml_p(nc);
60 ChVectorDynamic<> mdir(nc);
61 ChVectorDynamic<> mb(nc);
62 ChVectorDynamic<> mb_tmp(nc);
63 ChVectorDynamic<> ms(nc);
64 ChVectorDynamic<> my(nc);
65 ChVectorDynamic<> mD(nc);
66 ChVectorDynamic<> mDg(nc);
67
68 // Update auxiliary data in all constraints before starting,
69 // that is: g_i=[Cq_i]*[invM_i]*[Cq_i]' and [Eq_i]=[invM_i]*[Cq_i]'
70 for (unsigned int ic = 0; ic < mconstraints.size(); ic++)
71 mconstraints[ic]->Update_auxiliary();
72
73 // Average all g_i for the triplet of contact constraints n,u,v.
74 // Can be used for the fixed point phase and/or by preconditioner.
75 int j_friction_comp = 0;
76 double gi_values[3];
77 for (unsigned int ic = 0; ic < mconstraints.size(); ic++) {
78 if (mconstraints[ic]->GetMode() == CONSTRAINT_FRIC) {
79 gi_values[j_friction_comp] = mconstraints[ic]->Get_g_i();
80 j_friction_comp++;
81 if (j_friction_comp == 3) {
82 double average_g_i = (gi_values[0] + gi_values[1] + gi_values[2]) / 3.0;
83 mconstraints[ic - 2]->Set_g_i(average_g_i);
84 mconstraints[ic - 1]->Set_g_i(average_g_i);
85 mconstraints[ic - 0]->Set_g_i(average_g_i);
86 j_friction_comp = 0;
87 }
88 }
89 }
90 // The vector with the diagonal of the N matrix
91 mD.setZero();
92 int d_i = 0;
93 for (unsigned int ic = 0; ic < mconstraints.size(); ic++)
94 if (mconstraints[ic]->IsActive()) {
95 mD(d_i, 0) = mconstraints[ic]->Get_g_i();
96 ++d_i;
97 }
98
99 // ***TO DO*** move the following thirty lines in a short function ChSystemDescriptor::ShurBvectorCompute() ?
100
101 // Compute the b_shur vector in the Shur complement equation N*l = b_shur
102 // with
103 // N_shur = D'* (M^-1) * D
104 // b_shur = - c + D'*(M^-1)*k = b_i + D'*(M^-1)*k
105 // but flipping the sign of lambdas, b_shur = - b_i - D'*(M^-1)*k
106 // Do this in three steps:
107
108 // Put (M^-1)*k in q sparse vector of each variable..
109 for (unsigned int iv = 0; iv < mvariables.size(); iv++)
110 if (mvariables[iv]->IsActive())
111 mvariables[iv]->Compute_invMb_v(mvariables[iv]->Get_qb(), mvariables[iv]->Get_fb()); // q = [M]'*fb
112
113 // ...and now do b_shur = - D'*q = - D'*(M^-1)*k ..
114 mb.setZero();
115 int s_i = 0;
116 for (unsigned int ic = 0; ic < mconstraints.size(); ic++)
117 if (mconstraints[ic]->IsActive()) {
118 mb(s_i, 0) = -mconstraints[ic]->Compute_Cq_q();
119 ++s_i;
120 }
121
122 // ..and finally do b_shur = b_shur - c
123 sysd.BuildBiVector(mb_tmp); // b_i = -c = phi/h
124 mb -= mb_tmp;
125
126 // Optimization: backup the q sparse data computed above,
127 // because (M^-1)*k will be needed at the end when computing primals.
128 ChVectorDynamic<> mq;
129 sysd.FromVariablesToVector(mq, true);
130
131 // Initialize lambdas
132 if (m_warm_start)
133 sysd.FromConstraintsToVector(ml);
134 else
135 ml.setZero();
136
137 // Initial projection of ml ***TO DO***?
138 sysd.ConstraintsProject(ml);
139
140 // Fallback solution
141 lastgoodres = 1e30;
142 ml_candidate = ml;
143
144 // g = gradient of 0.5*l'*N*l-l'*b
145 // g = N*l-b
146 sysd.ShurComplementProduct(mg, ml); // 1) g = N * l
147 mg -= mb; // 2) g = N * l - b_shur
148
149 mg_p = mg;
150
151 //
152 // THE LOOP
153 //
154
155 double mf_p = 0;
156 double mf = 1e29;
157 std::vector<double> f_hist;
158
159 for (int iter = 0; iter < m_max_iterations; iter++) {
160 // Dg = Di*g;
161 mDg = mg;
162 if (m_use_precond)
163 mDg = mDg.array() / mD.array();
164
165 // dir = [P(l - alpha*Dg) - l]
166 mdir = ml - alpha * mDg; // dir = l - alpha*Dg
167 sysd.ConstraintsProject(mdir); // dir = P(l - alpha*Dg)
168 mdir -= ml; // dir = P(l - alpha*Dg) - l
169
170 // dTg = dir'*g;
171 double dTg = mdir.dot(mg);
172
173 // BB dir backward!? fallback to nonpreconditioned dir
174 if (dTg > 1e-8) {
175 // dir = [P(l - alpha*g) - l]
176 mdir = ml - alpha * mg; // dir = l - alpha*g
177 sysd.ConstraintsProject(mdir); // dir = P(l - alpha*g) ...
178 mdir -= ml; // dir = P(l - alpha*g) - l
179 // dTg = d'*g;
180 dTg = mdir.dot(mg);
181 }
182
183 double lambda = 1;
184
185 int n_backtracks = 0;
186 bool armijo_repeat = true;
187
188 while (armijo_repeat) {
189 // l_p = l + lambda*dir;
190 ml_p = ml + lambda * mdir;
191
192 // m_tmp = Nl_p = N*l_p;
193 sysd.ShurComplementProduct(mb_tmp, ml_p);
194
195 // g_p = N * l_p - b = Nl_p - b
196 mg_p = mb_tmp - mb;
197
198 // f_p = 0.5*l_p'*N*l_p - l_p'*b = l_p'*(0.5*Nl_p - b);
199 mf_p = ml_p.dot(0.5 * mb_tmp - mb);
200
201 f_hist.push_back(mf_p);
202
203 double max_compare = 10e29;
204 for (int h = 1; h <= ChMin(iter, this->n_armijo); h++) {
205 double compare = f_hist[iter - h] + gamma * lambda * dTg;
206 if (compare > max_compare)
207 max_compare = compare;
208 }
209
210 if (mf_p > max_compare) {
211 armijo_repeat = true;
212 if (iter > 0)
213 mf = f_hist[iter - 1];
214 double lambdanew = -lambda * lambda * dTg / (2 * (mf_p - mf - lambda * dTg));
215 lambda = ChMax(sigma_min * lambda, ChMin(sigma_max * lambda, lambdanew));
216 if (verbose)
217 GetLog() << " Repeat Armijo, new lambda=" << lambda << "\n";
218 } else {
219 armijo_repeat = false;
220 }
221
222 n_backtracks = n_backtracks + 1;
223 if (n_backtracks > this->max_armijo_backtrace)
224 armijo_repeat = false;
225 }
226
227 ms = ml_p - ml; // s = l_p - l;
228 my = mg_p - mg; // y = g_p - g;
229 ml = ml_p; // l = l_p;
230 mg = mg_p; // g = g_p;
231
232 if (((do_BB1e2) && (iter % 2 == 0)) || do_BB1) {
233 if (m_use_precond)
234 mb_tmp = ms.array() * mD.array();
235 else
236 mb_tmp = ms;
237 double sDs = ms.dot(mb_tmp);
238 double sy = ms.dot(my);
239 if (sy <= 0) {
240 alpha = neg_BB1_fallback;
241 } else {
242 double alph = sDs / sy; // (s,Ds)/(s,y) BB1
243 alpha = ChMin(a_max, ChMax(a_min, alph));
244 }
245 }
246
247 /*
248 // this is a modified rayleight quotient - looks like it works anyway...
249 if (((do_BB1e2) && (iter%2 ==0)) || do_BB1)
250 {
251 double ss = ms.MatrDot(ms,ms);
252 mb_tmp = my;
253 if (m_use_precond)
254 mb_tmp.MatrDivScale(mD);
255 double sDy = ms.MatrDot(ms, mb_tmp);
256 if (sDy <= 0)
257 {
258 alpha = neg_BB1_fallback;
259 }
260 else
261 {
262 double alph = ss / sDy; // (s,s)/(s,Di*y) BB1 (modified version)
263 alpha = ChMin (a_max, ChMax(a_min, alph));
264 }
265 }
266 */
267
268 if (((do_BB1e2) && (iter % 2 != 0)) || do_BB2) {
269 double sy = ms.dot(my);
270 if (m_use_precond)
271 mb_tmp = my.array() / mD.array();
272 else
273 mb_tmp = my;
274 double yDy = my.dot(mb_tmp);
275 if (sy <= 0) {
276 alpha = neg_BB2_fallback;
277 } else {
278 double alph = sy / yDy; // (s,y)/(y,Di*y) BB2
279 alpha = ChMin(a_max, ChMax(a_min, alph));
280 }
281 }
282
283 // Project the gradient (for rollback strategy)
284 // g_proj = (l-project_orthogonal(l - gdiff*g, fric))/gdiff;
285 mb_tmp = ml - gdiff * mg;
286 sysd.ConstraintsProject(mb_tmp); // mb_tmp = ProjectionOperator(l - gdiff * g)
287 mb_tmp = (ml - mb_tmp) / gdiff; // mb_tmp = [l - ProjectionOperator(l - gdiff * g)] / gdiff
288 double g_proj_norm = mb_tmp.norm(); // infinity norm is faster..
289
290 // Rollback solution: the last best candidate ('l' with lowest projected gradient)
291 // in fact the method is not monotone and it is quite 'noisy', if we do not
292 // do this, a prematurely truncated iteration might give a crazy result.
293 if (g_proj_norm < lastgoodres) {
294 lastgoodres = g_proj_norm;
295 ml_candidate = ml;
296 }
297
298 // METRICS - convergence, plots, etc
299
300 double maxdeltalambda = ms.lpNorm<Eigen::Infinity>();
301 double maxd = lastgoodres;
302
303 // For recording into correction/residuals/violation history, if debugging
304 if (this->record_violation_history)
305 AtIterationEnd(maxd, maxdeltalambda, iter);
306
307 if (verbose)
308 GetLog() << " iter=" << iter << " f=" << mf_p << " |d|=" << maxd << " |s|=" << maxdeltalambda << "\n";
309
310 m_iterations++;
311
312 // Terminate the loop if violation in constraints has been successfully limited.
313 // ***TO DO*** a reliable termination criterion..
314 /*
315 if (maxd < m_tolerance)
316 {
317 GetLog() <<"BB premature proj.gradient break at i=" << iter << "\n";
318 break;
319 }
320 */
321 }
322
323 // Fallback to best found solution (might be useful because of nonmonotonicity)
324 ml = ml_candidate;
325
326 // Resulting DUAL variables:
327 // store ml temporary vector into ChConstraint 'l_i' multipliers
328 sysd.FromVectorToConstraints(ml);
329
330 // Resulting PRIMAL variables:
331 // compute the primal variables as v = (M^-1)(k + D*l)
332
333 // v = (M^-1)*k ... (by rewinding to the backup vector computed ad the beginning)
334 sysd.FromVectorToVariables(mq);
335
336 // ... + (M^-1)*D*l (this increment and also stores 'qb' in the ChVariable items)
337 for (unsigned int ic = 0; ic < mconstraints.size(); ic++) {
338 if (mconstraints[ic]->IsActive())
339 mconstraints[ic]->Increment_q(mconstraints[ic]->Get_l_i());
340 }
341
342 if (verbose)
343 GetLog() << "-----\n";
344
345 return lastgoodres;
346 }
347
348 //////////////////////////////////////////////////////////////////////////////
349 //////////////////////////////////////////////////////////////////////////////
350
ArchiveOUT(ChArchiveOut & marchive)351 void ChSolverBB::ArchiveOUT(ChArchiveOut& marchive) {
352 // version number
353 marchive.VersionWrite<ChSolverBB>();
354 // serialize parent class
355 ChIterativeSolverVI::ArchiveOUT(marchive);
356 // serialize all member data:
357 marchive << CHNVP(n_armijo);
358 marchive << CHNVP(max_armijo_backtrace);
359 marchive << CHNVP(m_use_precond);
360 }
361
ArchiveIN(ChArchiveIn & marchive)362 void ChSolverBB::ArchiveIN(ChArchiveIn& marchive) {
363 // version number
364 /*int version =*/ marchive.VersionRead<ChSolverBB>();
365 // deserialize parent class
366 ChIterativeSolverVI::ArchiveIN(marchive);
367 // stream in all member data:
368 marchive >> CHNVP(n_armijo);
369 marchive >> CHNVP(max_armijo_backtrace);
370 marchive >> CHNVP(m_use_precond);
371 }
372
373 } // end namespace chrono
374