ISQP.C
Go to the documentation of this file.
1 /*---------------------------------------------------------------------------*\
2  ========= |
3  \\ / F ield | OpenFOAM: The Open Source CFD Toolbox
4  \\ / O peration |
5  \\ / A nd | www.openfoam.com
6  \\/ M anipulation |
7 -------------------------------------------------------------------------------
8  Copyright (C) 2020-2023 PCOpt/NTUA
9  Copyright (C) 2020-2023 FOSS GP
10 -------------------------------------------------------------------------------
11 License
12  This file is part of OpenFOAM.
13 
14  OpenFOAM is free software: you can redistribute it and/or modify it
15  under the terms of the GNU General Public License as published by
16  the Free Software Foundation, either version 3 of the License, or
17  (at your option) any later version.
18 
19  OpenFOAM is distributed in the hope that it will be useful, but WITHOUT
20  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
21  FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
22  for more details.
23 
24  You should have received a copy of the GNU General Public License
25  along with OpenFOAM. If not, see <http://www.gnu.org/licenses/>.
26 
27 \*---------------------------------------------------------------------------*/
28 
29 #include "ISQP.H"
30 #include "IOmanip.H"
32 
33 // * * * * * * * * * * * * * * Static Data Members * * * * * * * * * * * * * //
34 
35 namespace Foam
36 {
37  defineTypeNameAndDebug(ISQP, 2);
39  (
40  updateMethod,
41  ISQP,
42  dictionary
43  );
45  (
46  constrainedOptimisationMethod,
47  ISQP,
48  dictionary
49  );
50 }
51 
52 
55 ({
56  { preconditioners::diag, "diag" },
57  { preconditioners::invHessian, "invHessian" },
58  { preconditioners::ShermanMorrison, "ShermanMorrison" }
59 });
60 
61 
62 // * * * * * * * * * * * * * Private Member Functions * * * * * * * * * * * //
63 
65 {
66  const label n = activeDesignVars_.size();
67  if (n != deltaP_.size())
68  {
69  // Correction fields
70  p_.setSize(n, Zero);
72 
73  // Lagrange multipliers and slack variables for bound constraints
75  {
76  lTilda_().setSize(n, Zero);
77  ls_().setSize(n, Zero);
78  uTilda_().setSize(n, Zero);
79  us_().setSize(n, Zero);
80 
81  deltaLTilda_().setSize(n, Zero);
82  deltaLs_().setSize(n, Zero);
83  deltaUTilda_().setSize(n, Zero);
84  deltaUs_().setSize(n, Zero);
85  }
86 
87  // Fields used to compute the Hessian
88  for (label i = 0; i < nPrevSteps_; ++i)
89  {
90  y_[i].setSize(n, Zero);
91  s_[i].setSize(n, Zero);
92  }
93  }
94 }
95 
96 
98 {
99  if (includeBoundConstraints_)
100  {
101  // Number of constraints
102  const label n(activeDesignVars_.size());
103 
104  if (!lTilda_)
105  {
106  lTilda_.reset(autoPtr<scalarField>::New(n, Zero));
107  }
108  ls_.reset(autoPtr<scalarField>::New(n, Zero));
109  if (!uTilda_)
110  {
111  uTilda_.reset(autoPtr<scalarField>::New(n, Zero));
112  }
113  us_.reset(autoPtr<scalarField>::New(n, Zero));
114 
115  deltaLTilda_.reset(autoPtr<scalarField>::New(n, Zero));
116  deltaLs_.reset(autoPtr<scalarField>::New(n, Zero));
117  deltaUTilda_.reset(autoPtr<scalarField>::New(n, Zero));
118  deltaUs_.reset(autoPtr<scalarField>::New(n, Zero));
119  }
120 }
121 
122 
124 {
125  // Number of constraints
126  const label m(nConstraints_);
127  // Allocate the extra variables ensuring the feasibility
128  if (includeExtraVars_)
129  {
130  extraVars_.reset(autoPtr<scalarField>::New(m, 1));
131  z_.reset(autoPtr<scalarField>::New(m, max(1, 0.5*c_)));
132 
133  deltaExtraVars_.reset(autoPtr<scalarField>::New(m, Zero));
134  deltaZ_.reset(autoPtr<scalarField>::New(m, Zero));
135  }
136 
137  doAllocateLagrangeMultipliers_ = false;
138 }
139 
140 
142 {
143  // Compute the Lagranian and old Lagrangian derivatives
144  scalarField LagrangianDerivativesOld(derivativesOld_);
145  forAll(constraintDerivatives_, cI)
146  {
147  LagrangianDerivatives_ += lamdas_[cI]*constraintDerivatives_[cI];
148  LagrangianDerivativesOld += lamdas_[cI]*constraintDerivativesOld_[cI];
149  }
150 
151  if (includeBoundConstraints_)
152  {
153  forAll(activeDesignVars_, aI)
154  {
155  const label varI(activeDesignVars_[aI]);
156  const scalar contr(uTilda_()[aI] - lTilda_()[aI]);
157  LagrangianDerivatives_[varI] += contr;
158  LagrangianDerivativesOld[varI] += contr;
159  }
160  }
161 
162  // Update vectors associated to the inverse Hessian matrix
163  updateVectors(LagrangianDerivatives_, LagrangianDerivativesOld);
164 }
165 
166 
168 {
169  const scalarField x(designVars_().getVars(), activeDesignVars_);
170 
171  // Quantities related to design variables
172  p_ = Zero;
173  if (includeBoundConstraints_)
174  {
175  lTilda_() = scalar(1);
176  uTilda_() = scalar(1);
177  ls_() = scalar(1);
178  us_() = scalar(1);
179  }
180 
181  // Quantities related to constraints
182  lamdas_ = scalar(1);
183  gs_ = scalar(1);
184 
185  if (includeExtraVars_)
186  {
187  extraVars_() = scalar(1);
188  z_() = max(1, 0.5*c_);
189  }
190 
191  // Reset eps
192  eps_ = 1;
193 }
194 
195 
197 {
198  deltaP_ = Zero;
199  deltaLamda_ = Zero;
200  deltaGs_ = Zero;
201 
202  if (includeBoundConstraints_)
203  {
204  deltaLTilda_() = Zero;
205  deltaLs_() = Zero;
206  deltaUTilda_() = Zero;
207  deltaUs_() = Zero;
208  }
209 
210  if (includeExtraVars_)
211  {
212  deltaExtraVars_() = Zero;
213  deltaZ_() = Zero;
214  }
215 }
216 
217 
219 {
220  addProfiling(ISQP, "ISQP::solveDeltaPEqn");
221  // Explicit part of the right hand side of the deltaX equation
222  scalarField FDx(-resFL());
223  if (includeBoundConstraints_)
224  {
225  FDx +=
226  (uTilda_()*resFus() + resFuTilda())/us_()
227  - (lTilda_()*resFls() + resFlTilda())/ls_();
228  }
229  scalarField AMult(resFlamda()/lamdas_ - resFGs());
230  scalarField mult(gs_/lamdas_);
231  if (includeExtraVars_)
232  {
233  mult += extraVars_()/z_();
234  AMult -= (extraVars_()*resFExtraVars() + resFz())/z_();
235  }
236  AMult /= mult;
237  forAll(FDx, aI)
238  {
239  const label varI(activeDesignVars_[aI]);
240  forAll(constraintDerivatives_, cI)
241  {
242  FDx[aI] += constraintDerivatives_[cI][varI]*AMult[cI];
243  }
244  }
245  CGforDeltaP(FDx);
246 }
247 
248 
250 (
251  const scalarField& FDx
252 )
253 {
254  tmp<scalarField> trhs(tmp<scalarField>::New(-FDx));
255  scalarField& rhs = trhs.ref();
256 
257  // Compute (Gs)^(-1)*Λ*A*Dp
258  scalarField GsLADp(cValues_.size(), Zero);
259  forAll(constraintDerivatives_, cI)
260  {
261  const scalarField& cDerivsI = constraintDerivatives_[cI];
262  GsLADp[cI] +=
263  globalSum(scalarField(cDerivsI, activeDesignVars_)*deltaP_);
264  }
265  GsLADp *= lamdas_/gs_;
266 
267  // Multiply with A^T
268  forAll(rhs, aI)
269  {
270  const label varI(activeDesignVars_[aI]);
271  forAll(constraintDerivatives_, cI)
272  {
273  rhs[aI] += constraintDerivatives_[cI][varI]*GsLADp[cI];
274  }
275  }
276 
277  // Contributions from bounds
278  if (includeBoundConstraints_)
279  {
280  rhs += (lTilda_()/ls_() + uTilda_()/us_())*deltaP_;
281  }
282 
283  rhs = -invHessianVectorProduct(rhs);
284 
285  rhs = 0.95*deltaP_ + 0.05*rhs;
286  return trhs;
287 }
288 
289 
290 void Foam::ISQP::CGforDeltaP(const scalarField& FDx)
291 {
292  addProfiling(ISQP, "ISQP::CGforDeltaP");
293  autoPtr<scalarField> precon(nullptr);
294  scalarField r(FDx - matrixVectorProduct(deltaP_));
295  scalarField z(preconVectorProduct(r, precon));
296  scalarField p(z);
297  scalar res(sqrt(globalSum(r*r)));
298  scalar resInit(res);
299  scalar rz(globalSum(r*z));
300  scalar rzOld(rz);
301  label iter(0);
302  do
303  {
304  scalarField Ap(matrixVectorProduct(p));
305  scalar a = rz/globalSum(p*Ap);
306  deltaP_ += a*p;
307  r -= a*Ap;
308  res = sqrt(globalSum(r*r));
309  z = preconVectorProduct(r, precon);
310  rz = globalSum(r*z);
311  scalar beta = rz/rzOld;
312  p = z + beta*p;
313  rzOld = rz;
314  } while (iter++ < maxDxIters_ && res > relTol_*resInit);
315  DebugInfo
316  << "CG, Solving for deltaP, Initial Residual " << resInit
317  << ", Final Residual " << res
318  << ", No Iterations " << iter << endl;
319 }
320 
321 
323 (
324  const scalarField& vector
325 )
326 {
327  addProfiling(ISQP, "ISQP::MatrixVectorProduct");
328  tmp<scalarField> tAp(HessianVectorProduct(vector));
329  scalarField& Ap = tAp.ref();
330  scalarField GsLAv(cValues_.size(), Zero);
331  forAll(constraintDerivatives_, cI)
332  {
333  const scalarField& cDerivsI = constraintDerivatives_[cI];
334  GsLAv[cI] =
335  globalSum(scalarField(cDerivsI, activeDesignVars_)*vector);
336  }
337  scalarField mult(gs_/lamdas_);
338  if (includeExtraVars_)
339  {
340  mult += extraVars_()/z_();
341  }
342  GsLAv /= mult;
343 
344  // Multiply with A^T
345  forAll(Ap, aI)
346  {
347  const label varI(activeDesignVars_[aI]);
348  forAll(constraintDerivatives_, cI)
349  {
350  Ap[aI] += constraintDerivatives_[cI][varI]*GsLAv[cI];
351  }
352  }
353 
354  // Contributions from bounds
355  if (includeBoundConstraints_)
356  {
357  Ap += (lTilda_()/ls_() + uTilda_()/us_())*vector;
358  }
360 
361  return tAp;
362 }
363 
364 
366 (
367  const scalarField& vector,
368  autoPtr<scalarField>& precon
369 )
370 {
371  addProfiling(ISQP, "ISQP::preconVectorProduct");
372  if (preconType_ == preconditioners::diag)
373  {
374  if (!precon)
375  {
376  precon.reset(diagPreconditioner().ptr());
377  }
378  return precon()*vector;
379  }
380  else if (preconType_ == preconditioners::invHessian)
381  {
382  return invHessianVectorProduct(vector);
383  }
384  else if (preconType_ == preconditioners::ShermanMorrison)
385  {
386  return ShermanMorrisonPrecon(vector);
387  }
388  return nullptr;
389 }
390 
391 
393 {
394  addProfiling(ISQP, "ISQP::deltaPDiagPreconditioner");
395  tmp<scalarField> tpreconditioner(HessianDiag());
396  scalarField& preconditioner = tpreconditioner.ref();
397 
398  // Part related to the constraints
399  forAll(constraintDerivatives_, cI)
400  {
401  scalarField cDerivs(constraintDerivatives_[cI], activeDesignVars_);
402  scalar mult(gs_[cI]/lamdas_[cI]);
403  if (includeExtraVars_)
404  {
405  mult += extraVars_()[cI]/z_()[cI];
406  }
407  preconditioner += sqr(cDerivs)/mult;
408  }
409 
410  if (includeBoundConstraints_)
411  {
412  preconditioner += lTilda_()/ls_() + uTilda_()/us_();
413  }
414 
415  preconditioner = 1./preconditioner;
416 
417  return tpreconditioner;
418 }
419 
420 
422 (
423  const scalarField& vector
424 )
425 {
426  // Recursively update the inv(LHS)*vector since the LHS consists of the
427  // L-BFGS-based Hessian, computed with rank-2 updates, and the part related
428  // to flow constraints, computed as rank-1 updates. In the inversion
429  // process, the diagonal matrix related to bound constraints is treated as
430  // the initial matrix of the L-BFGS update.
431 
432  // Constribution from bound constraints, treated as the seed of the
433  // L-BFGS inverse
434  tmp<scalarField> tdiag(nullptr);
435  if (includeBoundConstraints_)
436  {
437  tdiag.reset(lTilda_()/ls_() + uTilda_()/us_());
438  }
439 
440  // List of vectors to be used in the rank-1 updates related to the flow
441  // constraitns
442  PtrList<scalarField> r1Updates(cValues_.size());
443 
444  forAll(constraintDerivatives_, cI)
445  {
446  const scalarField& cDerivsI = constraintDerivatives_[cI];
447  r1Updates.set(cI, new scalarField(cDerivsI, activeDesignVars_));
448  }
449  // Multipliers of the rank-1 updates
450  scalarField mult(gs_/lamdas_);
451  if (includeExtraVars_)
452  {
453  mult += extraVars_()/z_();
454  }
456  return
457  ShermanMorrisonRank1Update(r1Updates, vector, tdiag, mult, mult.size());
458 }
459 
460 
462 (
463  const PtrList<scalarField>& r1Updates,
464  const scalarField& p,
465  const tmp<scalarField>& diag,
466  const scalarField& mult,
467  label n
468 )
469 {
470  auto tAp(tmp<scalarField>::New(activeDesignVars_.size(), Zero));
471  scalarField& Ap = tAp.ref();
472 
473  if (n == 0)
474  {
475  Ap = invHessianVectorProduct(p, counter_, diag);
476  return tAp;
477  }
478 
479  do
480  {
481  --n;
482  Ap = ShermanMorrisonRank1Update(r1Updates, p, diag, mult, n);
483  tmp<scalarField> tAv =
484  ShermanMorrisonRank1Update(r1Updates, r1Updates[n], diag, mult, n);
485  scalarField& Av = tAv.ref();
486  scalar yHs = globalSum(r1Updates[n]*Av)/mult[n];
487  Ap -= Av*globalSum(r1Updates[n]*Ap)/(1 + yHs)/mult[n];
488  } while (n > 0);
489  return tAp;
490 }
491 
492 
494 {
495  // Zero the updates computed in the previous optimisation cycle
496  //zeroUpdates();
497 
498  // Solve equation for deltaP_. The expensive part of the step. Everything
499  // else can be computed based on this
500  addProfiling(ISQP, "ISQP::computeNewtonDirection");
501  solveDeltaPEqn();
502 
503  // deltaLamda
504  forAll(constraintDerivatives_, cI)
505  {
506  const scalarField& cDerivsI = constraintDerivatives_[cI];
507  deltaLamda_[cI] =
508  globalSum(scalarField(cDerivsI, activeDesignVars_)*deltaP_);
509  }
510  scalarField mult(gs_/lamdas_);
511  if (includeExtraVars_)
512  {
513  mult += extraVars_()/z_();
514  deltaLamda_ += (resFz() + extraVars_()*resFExtraVars())/z_();
515  }
516  deltaLamda_ += resFGs() - resFlamda()/lamdas_;
517  deltaLamda_ /= mult;
518 
519  // deltaGs
520  deltaGs_ = -(gs_*deltaLamda_ + resFlamda())/lamdas_;
521 
522  if (includeBoundConstraints_)
523  {
524  // deltaLs
525  deltaLs_() = deltaP_ + resFls();
526 
527  // deltaUs
528  deltaUs_() = -deltaP_ + resFus();
529 
530  // deltaLTilda
531  deltaLTilda_() = -(lTilda_()*deltaLs_() + resFlTilda())/ls_();
532 
533  // deltaUTilda
534  deltaUTilda_() = -(uTilda_()*deltaUs_() + resFuTilda())/us_();
535  }
536 
537  if (includeExtraVars_)
538  {
539  deltaZ_() = -deltaLamda_ + resFExtraVars();
540  deltaExtraVars_() = - (extraVars_()*deltaZ_() + resFz())/z_();
541  }
542 }
543 
544 
545 Foam::scalar Foam::ISQP::lineSearch()
546 {
547  const label n(p_.size());
548  const label m(cValues_.size());
549  scalar step(1.);
550 
551  if (includeBoundConstraints_)
552  {
553  for (label i = 0; i < n; ++i)
554  {
555  adjustStep(step, ls_()[i], deltaLs_()[i]);
556  adjustStep(step, us_()[i], deltaUs_()[i]);
557  adjustStep(step, lTilda_()[i], deltaLTilda_()[i]);
558  adjustStep(step, uTilda_()[i], deltaUTilda_()[i]);
559  }
560  }
561 
562  // Perform bound checks and adjust step accordingly
563  for (label i = 0; i < m; ++i)
564  {
565  adjustStep(step, lamdas_[i], deltaLamda_[i]);
566  adjustStep(step, gs_[i], deltaGs_[i]);
567  if (includeExtraVars_)
568  {
569  adjustStep(step, extraVars_()[i], deltaExtraVars_()[i]);
570  adjustStep(step, z_()[i], deltaZ_()[i]);
571  }
572  }
573 
574  // Each processor might have computed a different step, if design variables
575  // are distributed. Get the global minimum
576  if (globalSum_)
577  {
578  reduce(step, minOp<scalar>());
579  }
580 
581  step = min(1, step);
582 
583  if (debug > 1)
584  {
585  Info<< "Step before line search is " << step << endl;
586  }
587 
588  // Old residual
589  scalar normResOld = sqrt(globalSum(magSqr(computeResiduals())));
590  scalar maxRes(GREAT);
591 
592  for (label i = 0; i < maxLineSearchIters_ ; ++i)
593  {
594  // Update the solution with given step
595  updateSolution(step);
596 
597  // Compute new residuals and their max value
598  scalarField resNew(computeResiduals());
599  scalar normResNew = sqrt(globalSum(magSqr(resNew)));
600  maxRes = gMax(mag(resNew));
601 
602  if (normResNew < normResOld)
603  {
604  DebugInfo
605  << "Initial residual = " << normResOld << ", "
606  << "Final residual = " << normResNew << ", "
607  << "No of LineSearch Iterations = " << i + 1
608  << endl;
609  break;
610  }
611  else
612  {
613  // Return solution to previous and reduce step
614  if (i != maxLineSearchIters_ - 1)
615  {
616  updateSolution(-step);
617  step *= 0.5;
618  }
619  else
620  {
621  Info<< tab << "Line search did not converge. "
622  << "Procceding with the last compute step" << endl;
623  }
624  }
625  }
626 
627  if (debug > 1)
628  {
629  Info<< "Step after line search is " << step << nl << endl;
630  }
631 
632  return maxRes;
633 }
634 
635 
637 (
638  scalar& step,
639  const scalar value,
640  const scalar update
641 )
642 {
643  if (0.99*value + step*update < scalar(0))
644  {
645  step = -0.99*value/update;
646  }
647 }
648 
649 
650 void Foam::ISQP::updateSolution(const scalar step)
651 {
652  p_ += step*deltaP_;
653  lamdas_ += step*deltaLamda_;
654  gs_ += step*deltaGs_;
655  if (includeBoundConstraints_)
656  {
657  lTilda_() += step*deltaLTilda_();
658  ls_() += step*deltaLs_();
659  uTilda_() += step*deltaUTilda_();
660  us_() += step*deltaUs_();
661  }
662  if (includeExtraVars_)
663  {
664  extraVars_() += step*deltaExtraVars_();
665  z_() += step*deltaZ_();
666  }
667 }
668 
669 
671 {
672  const label n(activeDesignVars_.size());
673  const label m(cValues_.size());
674  label size(includeBoundConstraints_ ? 5*n + 2*m : n + 2*m);
675  if (includeExtraVars_)
676  {
677  size += 2*m;
678  }
679  tmp<scalarField> tres(tmp<scalarField>::New(size, Zero));
680  scalarField& res = tres.ref();
681 
682  label iRes(0);
683 
684  // Gradient of the Lagrangian
685  res.rmap(resFL()(), identity(n));
686  iRes = n;
687 
688  // Inequality constraints slacks
689  res.rmap(resFGs()(), identity(m, iRes));
690  iRes += m;
691 
692  // Inequality constraints complementarity slackness
693  res.rmap(resFlamda()(), identity(m, iRes));
694  iRes += m;
695 
696  if (includeBoundConstraints_)
697  {
698  // Lower bounds slacks
699  res.rmap(resFls()(), identity(n, iRes));
700  iRes += n;
701 
702  // Upper bounds slacks
703  res.rmap(resFus()(), identity(n, iRes));
704  iRes += n;
705 
706  // Lower bounds complementarity slackness
707  res.rmap(resFlTilda()(), identity(n, iRes));
708  iRes += n;
709 
710  // Upper bounds complementarity slackness
711  res.rmap(resFuTilda()(), identity(n, iRes));
712  iRes += n;
713  }
714 
715  if (includeExtraVars_)
716  {
717  // Lagragian derivative wrt the extra variables
718  res.rmap(resFExtraVars()(), identity(m, iRes));
719  iRes += m;
720 
721  // Lagrange multipliers for the extra variables positiveness
722  res.rmap(resFz(), identity(m, iRes));
723  iRes += m;
724  }
725 
726  return tres;
727 }
728 
729 
731 {
732  tmp<scalarField> tgradL
733  (tmp<scalarField>::New(objectiveDerivatives_, activeDesignVars_));
734  scalarField& gradL = tgradL.ref();
735 
736  scalarField Hp(HessianVectorProduct(p_));
737  //scalarField Hp = SR1HessianVectorProduct(p_);
738  gradL += Hp;
739 
740  if (debug > 2)
741  {
742  scalarField H1Hp(invHessianVectorProduct(Hp));
743  Info << "Diff H1Hp - p " << gSum(mag(H1Hp - p_)) << endl;
744  }
745 
746  forAll(constraintDerivatives_, cI)
747  {
748  gradL +=
749  lamdas_[cI]
750  *scalarField(constraintDerivatives_[cI], activeDesignVars_);
751  }
752 
753  if (includeBoundConstraints_)
754  {
755  gradL += uTilda_() - lTilda_();
756  }
757 
758  return tgradL;
759 }
760 
761 
763 {
764  tmp<scalarField> tinvHFL
765  (tmp<scalarField>::New(objectiveDerivatives_, activeDesignVars_));
766  scalarField& invHFL = tinvHFL.ref();
767 
768  forAll(constraintDerivatives_, cI)
769  {
770  invHFL +=
771  lamdas_[cI]
772  *scalarField(constraintDerivatives_[cI], activeDesignVars_);
773  }
774 
775  if (includeBoundConstraints_)
776  {
777  invHFL += uTilda_() - lTilda_();
778  }
779 
780  invHFL = invHessianVectorProduct(invHFL);
781  invHFL += p_;
782 
783  return tinvHFL;
784 }
785 
786 
788 {
789  tmp<scalarField> tFGs
790  (
791  tmp<scalarField>::New(gs_ + cValues_ - max((1 - cRed_)*cValues_, Zero))
792  );
793  scalarField& FGs = tFGs.ref();
794 
795  forAll(constraintDerivatives_, cI)
796  {
797  FGs[cI] +=
798  globalSum
799  (
800  scalarField(constraintDerivatives_[cI], activeDesignVars_)*p_
801  );
802  }
803 
804  if (includeExtraVars_)
805  {
806  FGs -= extraVars_();
807  }
808 
809  return tFGs;
810 }
811 
814 {
815  return (lamdas_*gs_ - eps_);
816 }
817 
818 
820 {
821  if (includeBoundConstraints_)
822  {
823  const scalarField x(designVars_().getVars(), activeDesignVars_);
824  const scalarField xMin
825  (designVars_().lowerBounds()(), activeDesignVars_);
827  return (x + p_ - xMin - ls_());
828  }
829  return nullptr;
830 }
831 
832 
834 {
835  if (includeBoundConstraints_)
836  {
837  const scalarField x(designVars_().getVars(), activeDesignVars_);
838  const scalarField xMax
839  (designVars_().upperBounds()(), activeDesignVars_);
841  return (xMax - x - p_ - us_());
842  }
843  return nullptr;
844 }
845 
846 
848 {
849  if (includeBoundConstraints_)
850  {
851  return (lTilda_()*ls_() - eps_);
852  }
853  return nullptr;
854 }
855 
856 
858 {
859  if (includeBoundConstraints_)
860  {
861  return (uTilda_()*us_() - eps_);
862  }
863  return nullptr;
864 }
865 
866 
868 {
869  if (includeExtraVars_)
870  {
871  return (c_ - lamdas_ - z_());
872  }
873  return nullptr;
874 }
875 
876 
878 {
879  if (includeExtraVars_)
880  {
881  return (z_()*extraVars_() - eps_);
882  }
883  return nullptr;
884 }
885 
886 
888 {
889  addProfiling(ISQP, "ISQP::solveSubproblem");
890  zeroUpdates();
891  if (includeBoundConstraints_ || !cValues_.empty())
892  {
893  scalar resMax(gMax(mag(computeResiduals())));
894  label iter(0);
895  do
896  {
897  DebugInfo
898  << "Newton iter " << iter << nl << endl;
899 
900  // Decrease eps
901  if (resMax < 0.9*eps_)
902  {
903  eps_ *= 0.1;
904  }
905 
906  // Computes Newton direction for the subproblem
907  computeNewtonDirection();
908 
909  // Perform line search and return max residual of the solution
910  // satisfying the bound constraints and the residual reduction.
911  // Upates solution.
912  resMax = lineSearch();
913  DebugInfo
914  << "max residual = " << resMax << ", "
915  << "eps = " << eps_ << nl << endl;
916  } while
917  (
918  iter++ < maxNewtonIters_ && (eps_ > epsMin_ || resMax > 0.9*eps_)
919  );
920  Info<< "Finished solving the QP subproblem" << nl << endl;
921  if (iter == maxNewtonIters_)
922  {
924  << "Iterative solution of the QP problem did not converge"
925  << endl;
926  }
927  if (debug > 2)
928  {
929  scalarField vars(designVars_().getVars(), activeDesignVars_);
930  scalarField newVars(vars + p_);
931  Info<< "Min of updated vars " << gMin(newVars) << endl;
932  Info<< "Max of updated vars " << gMax(newVars) << endl;
933 
934  Info<< "Min of lamda " << gMin(lamdas_) << endl;
935  Info<< "Max of gs " << gMax(gs_) << endl;
936  Info<< "Max of lamda*gs " << gMax(lamdas_*gs_) << endl;
937 
938  if (includeBoundConstraints_)
939  {
940  Info<< "Min of lTilda " << gMin(lTilda_()) << endl;
941  Info<< "Min of uTilda " << gMin(uTilda_()) << endl;
942  Info<< "Min of ls " << gMin(ls_()) << endl;
943  Info<< "Min of us " << gMin(us_()) << endl;
944  Info<< "Max of lTilda*ls " << gMax(lTilda_()*ls_()) << endl;
945  Info<< "Max of uTilda*us " << gMax(uTilda_()*us_()) << endl;
946  }
947  if (includeExtraVars_)
948  {
949  Info<< "Min of extraVars " << gMin(extraVars_()) << endl;
950  Info<< "Max of extraVars*z " << gMax(extraVars_()*z_()) << endl;
951  }
952  }
953  }
954  else
955  {
956  computeNewtonDirection();
957  lineSearch();
958  }
959 
960  // Pass update to correction field
961  correction_.rmap(p_, activeDesignVars_);
962  if (!counter_)
963  {
964  correction_ *= eta_;
965  }
966  else
967  {
968  correction_ *= etaHessian_;
969  }
970 }
971 
972 
974 {
975  derivativesOld_ = objectiveDerivatives_;
976  if (constraintDerivativesOld_.empty())
977  {
978  constraintDerivativesOld_.setSize(constraintDerivatives_.size());
979  }
980  forAll(constraintDerivativesOld_, cI)
981  {
982  constraintDerivativesOld_[cI] = constraintDerivatives_[cI];
983  }
984  correctionOld_ = correction_;
985 }
986 
987 
988 Foam::scalar Foam::ISQP::meritFunctionConstraintPart() const
989 {
990  // Assumes that all constraints are known by all processors
991  // What about constraints directly imposed on distributed design variables?
992  // These should be met in each iteration of the algorithm, so,
993  // most probably, there is no problem
994  return sum(pos(cValues_)*cValues_);
995 }
996 
997 
998 // * * * * * * * * * * * * * * * * Constructors * * * * * * * * * * * * * * //
999 
1000 Foam::ISQP::ISQP
1001 (
1002  const fvMesh& mesh,
1003  const dictionary& dict,
1004  autoPtr<designVariables>& designVars,
1005  const label nConstraints,
1006  const word& type
1007 )
1008 :
1009  LBFGS(mesh, dict, designVars, nConstraints, type),
1010  SQPBase(mesh, dict, designVars, *this, type),
1011 
1012  doAllocateLagrangeMultipliers_(true),
1013  includeBoundConstraints_
1014  (
1015  designVars->upperBounds() && designVars->lowerBounds()
1016  ),
1017  includeExtraVars_
1018  (
1019  coeffsDict(type).getOrDefault<bool>("includeExtraVars", false)
1020  ),
1021  p_(activeDesignVars_.size(), Zero),
1022  gs_(nConstraints_, 1),
1023  lTilda_
1024  (
1025  includeBoundConstraints_ && found("lTilda") ?
1026  new scalarField("lTilda", *this, activeDesignVars_.size()) :
1027  nullptr
1028  ),
1029  ls_(nullptr),
1030  uTilda_
1031  (
1032  includeBoundConstraints_ && found("uTilda") ?
1033  new scalarField("uTilda", *this, activeDesignVars_.size()) :
1034  nullptr
1035  ),
1036  us_(nullptr),
1037  extraVars_(nullptr),
1038  z_(nullptr),
1039  c_(coeffsDict(type).getOrDefault<scalar>("c", 100)),
1040  deltaP_(activeDesignVars_.size(), Zero),
1041  deltaLamda_(nConstraints_, Zero),
1042  deltaGs_(nConstraints_, Zero),
1043  deltaLTilda_(nullptr),
1044  deltaLs_(nullptr),
1045  deltaUTilda_(nullptr),
1046  deltaUs_(nullptr),
1047  deltaExtraVars_(nullptr),
1048  deltaZ_(nullptr),
1049  eps_(1),
1050  epsMin_(coeffsDict(type).getOrDefault<scalar>("epsMin", 1.e-07)),
1051  maxNewtonIters_(coeffsDict(type).getOrDefault<label>("maxIters", 1000)),
1052  maxLineSearchIters_
1053  (
1054  coeffsDict(type).getOrDefault<label>("maxLineSearchIters", 10)
1055  ),
1056  maxDxIters_(coeffsDict(type).getOrDefault<label>("maxDpIters", 1000)),
1057  relTol_(coeffsDict(type).getOrDefault<scalar>("relTol", 0.01)),
1058  preconType_
1059  (
1060  preconditionerNames.getOrDefault
1061  (
1062  "preconditioner", coeffsDict(type), preconditioners::diag
1063  )
1064  ),
1065  cRed_
1066  (coeffsDict(type).getOrDefault<scalar>("targetConstraintReduction", 1)),
1067  meritFunctionFile_(nullptr)
1068 {
1069  Info<< "Preconditioner type of the SQP subproblem is ::"
1071  << endl;
1072  // Always apply damping of s in ISQP
1073  useYDamping_ = true;
1074  useSDamping_ = false;
1075 
1076  // Allocate multipliers and slack variables for the bound constraints
1079 }
1080 
1081 
1082 // * * * * * * * * * * * * * * * Member Functions * * * * * * * * * * * * * //
1083 
1085 {
1086  // Update sizes of fields related to the active design variables
1087  updateSizes();
1088 
1089  // The first iteration uses a unitary Hessian. No need to update
1090  LagrangianDerivatives_ = objectiveDerivatives_;
1091  if (counter_)
1092  {
1093  updateYS();
1094  }
1095 
1096  // Initiaze variables
1097  initialize();
1098 
1099  // Solve subproblem using a Newton optimiser
1100  solveSubproblem();
1101 
1102  // Store fields for the next iteration and write them to file
1103  storeOldFields();
1104 
1105  // Increase counter
1106  ++counter_;
1107 }
1108 
1109 
1110 Foam::scalar Foam::ISQP::computeMeritFunction()
1111 {
1112  mu_ = max(pos(cValues_)*lamdas_) + delta_;
1113  scalar L = objectiveValue_ + mu_*sum(pos(cValues_)*cValues_);
1114 
1115  return L;
1116 }
1117 
1118 
1120 {
1121  scalar deriv =
1122  globalSum(objectiveDerivatives_*correction_)
1123  - mu_*sum(pos(cValues_)*cValues_);
1124 
1125  return deriv;
1126 }
1127 
1128 
1130 {
1131  updateMethod::updateOldCorrection(oldCorrection);
1132  correctionOld_ = oldCorrection;
1133 }
1134 
1135 
1136 bool Foam::ISQP::writeData(Ostream& os) const
1137 {
1138  if (includeBoundConstraints_)
1139  {
1140  uTilda_().writeEntry("uTilda", os);
1141  lTilda_().writeEntry("lTilda", os);
1142  }
1143 
1145 }
1146 
1147 
1149 {
1150  return SQPBase::writeMeritFunction(*this);
1151 }
1152 
1153 
1154 // ************************************************************************* //
tmp< scalarField > preconVectorProduct(const scalarField &vector, autoPtr< scalarField > &precon)
Preconditioner-vector product for CG.
Definition: ISQP.C:359
tmp< scalarField > resFuTilda()
Residual of the complementarity slackness for the upper bound constraints.
Definition: ISQP.C:850
tmp< scalarField > diagPreconditioner()
Diagonal preconditioner of the deltaP eqn.
Definition: ISQP.C:385
dictionary dict
void size(const label n)
Older name for setAddressableSize.
Definition: UList.H:116
virtual void updateOldCorrection(const scalarField &oldCorrection)
Update old correction. Useful for quasi-Newton methods coupled with line search.
Definition: ISQP.C:1122
dimensioned< Type > sum(const DimensionedField< Type, GeoMesh > &f1)
scalarField p_
The set of design variables being updated during the subproblem.
Definition: ISQP.H:115
scalarField deltaP_
Definition: ISQP.H:164
dimensioned< typename typeOfMag< Type >::type > mag(const dimensioned< Type > &dt)
A list of keyword definitions, which are a keyword followed by a number of values (eg...
Definition: dictionary.H:129
const vector L(dict.get< vector >("L"))
Type gMin(const FieldField< Field, Type > &f)
void updateYS()
Update the vectors accosiated with the Hessian matrix.
Definition: ISQP.C:134
void initialize()
Allocate fields related to constraints.
Definition: ISQP.C:160
T & ref() const
Return non-const reference to the contents of a non-null managed pointer.
Definition: tmpI.H:235
label max(const labelHashSet &set, label maxValue=labelMin)
Find the max value in labelHashSet, optionally limited by second argument.
Definition: hashSets.C:40
dimensionedSymmTensor sqr(const dimensionedVector &dv)
constexpr char nl
The newline &#39;\n&#39; character (0x0a)
Definition: Ostream.H:50
tmp< scalarField > ShermanMorrisonRank1Update(const PtrList< scalarField > &r1Updates, const scalarField &p, const tmp< scalarField > &diag, const scalarField &mult, label n)
Recursive (naive) implementation of the rank-1 update.
Definition: ISQP.C:455
virtual scalar computeMeritFunction()
Compute merit function. Could be different than the objective in the presence of constraints.
Definition: ISQP.C:1103
autoPtr< scalarField > deltaUTilda_
Definition: ISQP.H:169
dimensionedScalar sqrt(const dimensionedScalar &ds)
Ostream & endl(Ostream &os)
Add newline and flush stream.
Definition: Ostream.H:531
virtual bool addToFile(Ostream &os) const
Write continuation info.
Definition: SQPBase.C:102
tmp< scalarField > resFlTilda()
Residual of the complementarity slackness for the lower bound constraints.
Definition: ISQP.C:840
autoPtr< scalarField > uTilda_
Lagrange multipliers for the upper bound constraints.
Definition: ISQP.H:137
bool includeBoundConstraints_
Are bound constraints included?
Definition: ISQP.H:98
autoPtr< scalarField > lTilda_
Lagrange multipliers for the lower bound constraints.
Definition: ISQP.H:127
virtual bool writeData(Ostream &os) const
Write useful quantities to files.
Definition: ISQP.C:1129
virtual scalar meritFunctionDirectionalDerivative()
Derivative of the merit function. Could be different than the objective derivative in the presence of...
Definition: ISQP.C:1112
label preconType_
Which preconditioner to use for the solution of the subproblem.
Definition: ISQP.H:212
tmp< scalarField > computeRHSForDeltaX(const scalarField &FDx)
Compute the RHS for the deltaX equation.
Definition: ISQP.C:243
constexpr char tab
The tab &#39;\t&#39; character(0x09)
Definition: Ostream.H:49
void computeCorrection()
Compute design variables correction.
Definition: ISQP.C:1077
autoPtr< scalarField > deltaLs_
Definition: ISQP.H:168
const scalar xMin
Definition: createFields.H:34
Macros for easy insertion into run-time selection tables.
void adjustStep(scalar &step, const scalar value, const scalar update)
Adjust step to satisfy cireteria.
Definition: ISQP.C:630
virtual scalar meritFunctionConstraintPart() const
Get the part the merit function that depends on the constraints.
Definition: ISQP.C:981
void CGforDeltaP(const scalarField &FDx)
CG algorithm for the solution of the deltaP eqn.
Definition: ISQP.C:283
#define forAll(list, i)
Loop across all elements in list.
Definition: stdFoam.H:421
void reset(T *p=nullptr) noexcept
Delete managed object and set to new given pointer.
Definition: autoPtrI.H:37
tmp< scalarField > resFExtraVars()
Residual of the Lagrangian derivative wrt the extra variables.
Definition: ISQP.C:860
tmp< scalarField > resFL()
Residual of the gradient of the Lagrangian.
Definition: ISQP.C:723
void updateSizes()
Update sizes of fields related to the active design variables.
Definition: ISQP.C:57
tmp< scalarField > resFlamda()
Residual of the complementarity slackness for the inequality constraints.
Definition: ISQP.C:806
dimensionedScalar pos(const dimensionedScalar &ds)
fileName::Type type(const fileName &name, const bool followLink=true)
Return the file type: DIRECTORY or FILE, normally following symbolic links.
Definition: POSIX.C:799
void diag(pointPatchField< vector > &, const pointPatchField< tensor > &)
const labelList & activeDesignVars_
Map to active design variables.
Definition: updateMethod.H:75
autoPtr< scalarField > deltaLTilda_
Definition: ISQP.H:167
#define addProfiling(Name, Descr)
Define profiling trigger with specified name and description string.
virtual void updateOldCorrection(const scalarField &oldCorrection)
Update old correction. Useful for quasi-newton methods coupled with line search.
Definition: updateMethod.C:458
const dimensionedScalar e
Elementary charge.
Definition: createFields.H:11
void setSize(const label n)
Alias for resize()
Definition: List.H:316
dynamicFvMesh & mesh
void solveDeltaPEqn()
Solve the equation for deltaX, which is the expensive part of the Newtopn step.
Definition: ISQP.C:211
Type gSum(const FieldField< Field, Type > &f)
labelList identity(const label len, label start=0)
Return an identity map of the given length with (map[i] == i), works like std::iota() but returning a...
Definition: labelLists.C:44
void computeNewtonDirection()
Compute direction for the Newton problem.
Definition: ISQP.C:486
A class for handling words, derived from Foam::string.
Definition: word.H:63
Field< scalar > scalarField
Specialisation of Field<T> for scalar.
PtrList< scalarField > s_
The previous correction. Holds nPrevSteps_ fields.
Definition: LBFGS.H:86
tmp< scalarField > resFls()
Residual of the lower bounds slackness.
Definition: ISQP.C:812
scalar lineSearch()
Perform line search and return max residual corresponding to the updated solution.
Definition: ISQP.C:538
tmp< scalarField > computeResiduals()
Compute and return residuals based on the current solution.
Definition: ISQP.C:663
static tmp< T > New(Args &&... args)
Construct tmp with forwarding arguments.
Definition: tmp.H:206
label nPrevSteps_
Number of old corrections and grad differences kept.
Definition: LBFGS.H:76
bool useSDamping_
Use damping for s to ensure positive-definitiveness.
Definition: LBFGS.H:91
Vector< scalar > vector
Definition: vector.H:57
Base class for Sequantial Quadratic Programming (SQP) methods.
Definition: SQPBase.H:50
label min(const labelHashSet &set, label minValue=labelMax)
Find the min value in labelHashSet, optionally limited by second argument.
Definition: hashSets.C:26
const T * set(const label i) const
Return const pointer to element (can be nullptr), or nullptr for out-of-range access (ie...
Definition: PtrList.H:159
virtual bool writeData(Ostream &os) const
Write useful quantities to files.
Definition: LBFGS.C:643
void allocateBoundMultipliers()
Allocate multipliers for the bound constraints.
Definition: ISQP.C:90
#define DebugInfo
Report an information message using Foam::Info.
tmp< scalarField > resFz()
Residual of the complementarity slackness for the extra variables.
Definition: ISQP.C:870
An Ostream is an abstract base class for all output systems (streams, files, token lists...
Definition: Ostream.H:56
const List< word > & names() const noexcept
The list of enum names, in construction order. Same as toc()
Definition: EnumI.H:39
Istream and Ostream manipulators taking arguments.
A Vector of values with scalar precision, where scalar is float/double depending on the compilation f...
int debug
Static debugging option.
Type gMax(const FieldField< Field, Type > &f)
tmp< scalarField > resFGs()
Residual of the inequality constraint slackness.
Definition: ISQP.C:780
OBJstream os(runTime.globalPath()/outputName)
tmp< scalarField > ShermanMorrisonPrecon(const scalarField &vector)
Use the Sherman-Morrison formula to compute the matrix-free product of the approximate inverse of the...
Definition: ISQP.C:415
mesh update()
void solveSubproblem()
Solve subproblem using a Newton optimiser.
Definition: ISQP.C:880
defineTypeNameAndDebug(combustionModel, 0)
bool useYDamping_
Use damping for s to ensure positive-definitiveness.
Definition: LBFGS.H:96
An L-BFGS-based SQP algorithm for computing the update of the design variables in the presence of ine...
Definition: ISQP.H:65
void updateSolution(const scalar step)
Update the current solution using the known direction and the given step length.
Definition: ISQP.C:643
virtual bool writeMeritFunction(const updateMethod &UpdateMethod)
Write info about the merit function.
Definition: SQPBase.C:115
static const Enum< preconditioners > preconditionerNames
Names of preconditioners for the subproblem.
Definition: ISQP.H:83
virtual bool writeAuxiliaryData()
Write merit function information.
Definition: ISQP.C:1141
const scalar xMax
Definition: createFields.H:35
autoPtr< scalarField > ls_
Slack variables the lower bound constraints.
Definition: ISQP.H:132
#define WarningInFunction
Report a warning using Foam::Warning.
Enum is a wrapper around a list of names/values that represent particular enumeration (or int) values...
Definition: error.H:64
A list of pointers to objects of type <T>, with allocation/deallocation management of the pointers...
Definition: List.H:55
Mesh data needed to do the Finite Volume discretisation.
Definition: fvMesh.H:78
PtrList< scalarField > y_
The previous differences of derivatives. Holds nPrevSteps_ fields.
Definition: LBFGS.H:81
tmp< scalarField > matrixVectorProduct(const scalarField &vector)
Procudt of the LHS of the deltaP eqn with a vector.
Definition: ISQP.C:316
void reduce(const List< UPstream::commsStruct > &comms, T &value, const BinaryOp &bop, const int tag, const label comm)
Reduce inplace (cf. MPI Allreduce) using specified communication schedule.
The quasi-Newton Limited-memory BFGS formula. Keeps nPrevSteps_ of the y and s vectors and approximat...
Definition: LBFGS.H:50
messageStream Info
Information stream (stdout output on master, null elsewhere)
dimensionedScalar beta("beta", dimless/dimTemperature, laminarTransport)
label n
autoPtr< scalarField > us_
Slack variables the upper bound constraints.
Definition: ISQP.H:142
Pointer management similar to std::unique_ptr, with some additional methods and type checking...
Definition: HashPtrTable.H:48
tmp< scalarField > resFus()
Residual of the upper bounds slackness.
Definition: ISQP.C:826
volScalarField & p
A class for managing temporary objects.
Definition: HashPtrTable.H:50
tmp< scalarField > invHFL()
Product of the inverse Hessian with the residual of the gradient of the Lagrangian.
Definition: ISQP.C:755
static autoPtr< T > New(Args &&... args)
Construct autoPtr with forwarding arguments.
Definition: autoPtr.H:178
void storeOldFields()
Store old fields needed for the next iter.
Definition: ISQP.C:966
autoPtr< scalarField > deltaUs_
Definition: ISQP.H:170
void allocateLagrangeMultipliers()
Allocate Lagrange multipliers for the inequality constraints.
Definition: ISQP.C:116
void zeroUpdates()
Zero the updates computed in the previous optimisation cycle.
Definition: ISQP.C:189
bool found
void reset(tmp< T > &&other) noexcept
Clear existing and transfer ownership.
Definition: tmpI.H:338
dimensioned< typename typeOfMag< Type >::type > magSqr(const dimensioned< Type > &dt)
Namespace for OpenFOAM.
addToRunTimeSelectionTable(functionObject, pointHistory, dictionary)
static constexpr const zero Zero
Global zero (0)
Definition: zero.H:127