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