nullSpace.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) 2023 PCOpt/NTUA
9  Copyright (C) 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 "nullSpace.H"
31 
32 // * * * * * * * * * * * * * * Static Data Members * * * * * * * * * * * * * //
33 
34 namespace Foam
35 {
36  defineTypeNameAndDebug(nullSpace, 1);
38  (
39  updateMethod,
40  nullSpace,
41  dictionary
42  );
44  (
45  constrainedOptimisationMethod,
46  nullSpace,
48  );
49 }
50 
51 
52 // * * * * * * * * * * * * Protected Member Functions * * * * * * * * * * * //
53 
55 {
56  eps_ = 1;
58 
59  const label m = iTildaEps_[0].size();
60  mu_ = scalarField(m, 1);
61  dualMu_ = scalarField(m, 1);
64 
65  const label nl = iTildaEps_[1].size();
66  l_ = scalarField(nl, 1);
67  dualL_ = scalarField(nl, 1);
70 
71  const label nu = iTildaEps_[2].size();
72  u_ = scalarField(nu, 1);
73  dualU_ = scalarField(nu, 1);
76 }
77 
78 
80 {
81  deltaMu_ = Zero;
82  deltaDualMu_ = Zero;
83  deltaL_ = Zero;
84  deltaDualL_ = Zero;
85  deltaU_ = Zero;
86  deltaDualU_ = Zero;
87 }
88 
89 
91 {
92  label nConstraints =
93  globalSum
94  (
95  iTildaEps_[0].size()
96  + iTildaEps_[1].size()
97  + iTildaEps_[2].size()
98  );
99  if (nConstraints && solveDualProblem_)
100  {
101  scalar resMax(gMax(mag(computeResiduals())));
102  label iter(0);
103  do
104  {
105  DebugInfo
106  << "Dual problem Newton iter " << iter << nl << endl;
107 
108  // Decrease eps
109  if (resMax < 0.9*eps_)
110  {
111  eps_ *= 0.1;
112  }
113 
114  // Computes Newton direction for the subproblem
115  computeNewtonDirection();
116 
117  // Perform line search and return max residual of the solution
118  // satisfying the bound constraints and the residual reduction.
119  // Upates solution.
120  resMax = lineSearch();
121  DebugInfo
122  << "max residual = " << resMax << ", "
123  << "eps = " << eps_ << nl << endl;
124 
125  mesh_.time().printExecutionTime(Info);
126  } while
127  (
128  iter++ < maxNewtonIters_
129  && (eps_ > dualTolerance_ || resMax > 0.9*eps_)
130  );
131 
132  Info<< "Solved the dual Newton problem in " << iter << " iterations "
133  << nl << endl;
134  Info<< "fluid related Lagrange mults " << mu_ << endl;
135  }
136 }
137 
138 
140 {
141  const labelList& iFlow = iTildaEps_[0];
142  const labelList& iLower = iTildaEps_[1];
143  const labelList& iUpper = iTildaEps_[2];
144  const label m = iFlow.size();
145  const label nl = iLower.size();
146  const label nu = iUpper.size();
147  tmp<scalarField> tres(tmp<scalarField>::New(2*(m + nl + nu), Zero));
148  scalarField& res = tres.ref();
149 
150  label iRes = 0;
151 
152  // dLdMu
153  scalarField dLdx(objectiveDerivatives_, activeDesignVars_);
154  forAll(iFlow, i)
155  {
156  dLdx +=
157  mu_[i]
158  *scalarField(constraintDerivatives_[iFlow[i]], activeDesignVars_);
159  }
160  forAll(iLower, i)
161  {
162  dLdx[iLower[i]] -= l_[i];
163  }
164  forAll(iUpper, i)
165  {
166  dLdx[iUpper[i]] += u_[i];
167  }
168 
169  forAll(iFlow, i)
170  {
171  label ic = iFlow[i];
172  scalar gradPart
173  (
174  2*globalSum
175  (
176  dLdx*scalarField(constraintDerivatives_[ic], activeDesignVars_)
177  )
178  );
179  res[iRes++] = gradPart - dualMu_[i];
180  }
181 
182  // mu complementarity slackness
183  forAll(iFlow, i)
184  {
185  res[iRes++] = mu_[i]*dualMu_[i] - eps_;
186  }
187 
188  // dKdl
189  forAll(iLower, i)
190  {
191  res[iRes++] = - dualL_[i] - 2*dLdx[iLower[i]];
192  }
193 
194  // dKdu
195  forAll(iUpper, i)
196  {
197  res[iRes++] = - dualU_[i] + 2*dLdx[iUpper[i]];
198  }
199 
200  // l complementarity slackness
201  forAll(iLower, i)
202  {
203  res[iRes++] = l_[i]*dualL_[i] - eps_;
204  }
205 
206  // u complementarity slackness
207  forAll(iUpper, i)
208  {
209  res[iRes++] = u_[i]*dualU_[i] - eps_;
210  }
211 
212  return tres;
213 }
214 
215 
217 {
218  // Zero the updates computed in the previous optimisation cycle
219  zeroUpdates();
220 
221  const scalarField res(computeResiduals());
222 
223  const labelList& iFlow = iTildaEps_[0];
224  const labelList& iLower = iTildaEps_[1];
225  const labelList& iUpper = iTildaEps_[2];
226  const label m = iFlow.size();
227  const label nl = iLower.size();
228  const label nu = iUpper.size();
229 
230  // Update of the flow-related primal and dual Lagrange multipliers
231  // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
232  scalarField diagKsi(nl, Zero);
233  scalarField rhsKsi(nl, Zero);
234  scalarField diagEta(nu, Zero);
235  scalarField rhsEta(nu, Zero);
236  if (nl)
237  {
238  diagKsi = scalar(1)/(2 + dualL_/l_);
239  rhsKsi =
240  -(
241  SubField<scalar>(res, nl, 2*m)
242  + SubField<scalar>(res, nl, 2*m + nl + nu)/l_
243  )*diagKsi;
244  }
245  if (nu)
246  {
247  diagEta = scalar(1)/(2 + dualU_/u_);
248  rhsEta =
249  -(
250  SubField<scalar>(res, nu, 2*m + nl)
251  + SubField<scalar>(res, nu, 2*m + 2*nl + nu)/u_
252  )*diagEta;
253  }
254 
255  scalarSquareMatrix lhs(m, m, Zero);
256  scalarField rhs(m, Zero);
257  for (label i = 0; i < m; ++i)
258  {
259  const label ic = iFlow[i];
260  scalarField dci(constraintDerivatives_[ic], activeDesignVars_);
261  lhs[i][i] += 2*globalSum(dci*dci) + dualMu_[i]/mu_[i];
262  rhs[i] -= res[i] + res[m + i]/mu_[i];
263 
264  scalarField dciKsi(scalarField(dci, iLower));
265  scalarField dciEta(scalarField(dci, iUpper));
266  lhs[i][i] -=
267  4*globalSum(dciKsi*dciKsi*diagKsi)
268  + 4*globalSum(dciEta*dciEta*diagEta);
269  rhs[i] += 2*globalSum(dciKsi*rhsKsi) - 2*globalSum(dciEta*rhsEta);
270  for (label j = i + 1; j < m; ++j)
271  {
272  const label jc = iFlow[j];
273  scalarField dcj(constraintDerivatives_[jc], activeDesignVars_);
274  scalar ij = 2*globalSum(dci*dcj);
275  lhs[i][j] += ij;
276  lhs[j][i] += ij;
277 
278  scalarField dciKsi(scalarField(dci, iLower));
279  scalarField dcjKsi(scalarField(dcj, iLower));
280  scalarField dciEta(scalarField(dci, iUpper));
281  scalarField dcjEta(scalarField(dcj, iUpper));
282  scalar ksiContr = 4*globalSum(dciKsi*dcjKsi*diagKsi);
283  scalar etaContr = 4*globalSum(dciEta*dcjEta*diagEta);
284  lhs[i][j] -= ksiContr + etaContr;
285  lhs[j][i] -= ksiContr + etaContr;
286  }
287  }
288 
289  // Update flow related Lagrange multipliers
290  if (m)
291  {
292  solve(deltaMu_, lhs, rhs);
293  deltaDualMu_ = -(deltaMu_*dualMu_ + SubField<scalar>(res, m, m))/mu_;
294  }
295 
296  // Compute the rest of the corrections using backwards substitution
297  deltaL_ = Zero;
298  deltaU_ = Zero;
299  forAll(iFlow, i)
300  {
301  const label ic = iFlow[i];
302  scalarField dci(constraintDerivatives_[ic], activeDesignVars_);
303  deltaL_ += scalarField(dci, iLower)*deltaMu_[i];
304  deltaU_ -= scalarField(dci, iUpper)*deltaMu_[i];
305  }
306  deltaL_ *= 2*diagKsi;
307  deltaL_ += rhsKsi;
308 
309  deltaU_ *= 2*diagEta;
310  deltaU_ += rhsEta;
311 
312  if (nl)
313  {
314  deltaDualL_ =
315  - (dualL_*deltaL_ + SubField<scalar>(res, nl, 2*m + nl + nu))
316  /l_;
317  }
318 
319  if (nu)
320  {
321  deltaDualU_ =
322  - (dualU_*deltaU_ + SubField<scalar>(res, nu, 2*m + 2*nl + nu))
323  /u_;
324  }
325 }
326 
327 
328 Foam::scalar Foam::nullSpace::lineSearch()
329 {
330  scalar step(1.);
331 
332  // Perform bound checks and adjust step accordingly
333  forAll(iTildaEps_[0], i)
334  {
335  adjustStep(step, mu_[i], deltaMu_[i]);
336  adjustStep(step, dualMu_[i], deltaDualMu_[i]);
337  }
338 
339  forAll(iTildaEps_[1], i)
340  {
341  adjustStep(step, l_[i], deltaL_[i]);
342  adjustStep(step, dualL_[i], deltaDualL_[i]);
343  }
344 
345  forAll(iTildaEps_[2], i)
346  {
347  adjustStep(step, u_[i], deltaU_[i]);
348  adjustStep(step, dualU_[i], deltaDualU_[i]);
349  }
350 
351  // Each processor might have computed a different step, if design variables
352  // are distributed. Get the global minimum
353  if (globalSum_)
354  {
355  reduce(step, minOp<scalar>());
356  }
357 
358  if (debug > 1)
359  {
360  Info<< "Step before line search is " << step << endl;
361  }
362 
363  // Old residual
364  scalar normResOld = sqrt(globalSum(magSqr(computeResiduals())));
365  scalar maxRes(GREAT);
366 
367  for (label i = 0; i < maxLineSearchIters_ ; ++i)
368  {
369  // Update the solution with given step
370  updateSolution(step);
371 
372  // Compute new residuals and their max value
373  scalarField resNew(computeResiduals());
374  scalar normResNew = sqrt(globalSum(magSqr(resNew)));
375  maxRes = gMax(mag(resNew));
376 
377  if (normResNew < normResOld)
378  {
379  DebugInfo
380  << "Initial residual = " << normResOld << ", "
381  << "Final residual = " << normResNew << ", "
382  << "No of LineSearch Iterations = " << i + 1
383  << endl;
384  break;
385  }
386  else
387  {
388  // Return solution to previous and reduce step
389  updateSolution(-step);
390  step *= 0.5;
391 
392  // If line search could find an appropriate step, increase eps
393  if (i == maxLineSearchIters_ - 1)
394  {
395  eps_ *= 1.5;
396  Info<< "Line search could not find a step that reduced"
397  << " residuals while satisfying the constraints" << nl
398  << "Increasing eps to " << eps_ << endl;
399  }
400  }
401  }
402 
403  if (debug > 1)
404  {
405  Info<< "Step after line search is " << step << nl << endl;
406  }
407 
408  return maxRes;
409 }
410 
411 
413 (
414  scalar& step,
415  const scalar value,
416  const scalar update
417 )
418 {
419  if (0.99*value + step*update < scalar(0))
420  {
421  step = -0.99*value/update;
422  }
423 }
424 
425 
426 void Foam::nullSpace::updateSolution(const scalar step)
427 {
428  mu_ += step*deltaMu_;
429  dualMu_ += step*deltaDualMu_;
430 
431  l_ += step*deltaL_;
432  dualL_ += step*deltaDualL_;
433 
434  u_ += step*deltaU_;
435  dualU_ += step*deltaDualU_;
436 }
437 
438 
440 {
441  updateViolatedIndices(0, cValues_);
442 
443  if (includeBoundConstraints_)
444  {
445  scalarField lowerBounds
446  (designVars_->lowerBoundsRef() - designVars_(), activeDesignVars_);
447  updateViolatedIndices(1, lowerBounds);
448 
449  scalarField upperBounds
450  (designVars_() - designVars_->upperBoundsRef(), activeDesignVars_);
451  updateViolatedIndices(2, upperBounds);
452  }
453 
454  statistics(iTilda_, "violated");
455  statistics(iTildaEps_, "violated-up-to-eps");
456 }
457 
458 
460 {
461  if (solveDualProblem_)
462  {
463  updateCorrectionIndices(0, mu_, dualMu_);
464  updateCorrectionIndices(1, l_, dualL_);
465  updateCorrectionIndices(2, u_, dualU_);
466 
467  statistics(iHat_, "non-tangent,violated");
468  statistics(iRangeSpace_, "to-be-reduced");
469  }
470  else
471  {
472  iHat_ = iTildaEps_;
473  iRangeSpace_ = iTildaEps_;
474  }
475 }
476 
477 
479 (
480  const label i,
481  const scalarField& constraints
482 )
483 {
484  // Set violated constraints
485  labelList& subset = iTilda_[i];
486  subset.setSize(constraints.size(), -1);
487  label iViolated = Zero;
488  forAll(constraints, i)
489  {
490  if (constraints[i] >= 0)
491  {
492  subset[iViolated++] = i;
493  }
494  }
495  subset.setSize(iViolated);
496 
497  // Append violated constraints up to epsConstr_
498  DynamicList<label> iTildaEps(subset);
499  forAll(constraints, i)
500  {
501  if (constraints[i] >= -epsConstr_ && constraints[i] < 0)
502  {
503  iTildaEps.push_back(i);
504  }
505  }
506  iTildaEps_[i].transfer(iTildaEps);
507 }
508 
509 
511 (
512  const label i,
513  const scalarField& LagrangeMults,
514  const scalarField& dual
515 )
516 {
517  // Subset with non-zero Lagrange multipliers
518  const labelList& firstAddr = iTildaEps_[i];
519  labelList& subset = iHat_[i];
520  subset.setSize(LagrangeMults.size(), -1);
521  label iViolated(Zero);
522 
523  // Loop over all indices included in iTilda
524  forAll(iTilda_[i], j)
525  {
526  // The criterion of adding a contraint index to the subset
527  // is based on whether the Lagrange multiplier is larger than
528  // its dual (since their product should, theorerically be zero). This
529  // avoids comparisons with arbitrary small numbers
530  if (LagrangeMults[j] > dual[j])
531  {
532  subset[iViolated++] = firstAddr[j];
533  }
534  }
535  // Loop over the remaining indices included in iTildaEps_ and append
536  // elements with a positive Lagrange multiplier to iRangeSpace too
537  DynamicList<label> iRangeSpace(iTilda_[i]);
538  for (label j = iTilda_[i].size(); j < iTildaEps_[i].size(); ++j)
539  {
540  if (LagrangeMults[j] > dual[j])
541  {
542  subset[iViolated++] = firstAddr[j];
543  iRangeSpace.push_back(firstAddr[j]);
544  }
545  }
546  subset.setSize(iViolated);
547  iRangeSpace_[i].transfer(iRangeSpace);
548 }
549 
550 
552 {
553  // Compute the null space part of the update
554  const scalarField activeDerivs(objectiveDerivatives_, activeDesignVars_);
555  scalarField rJ(Av(activeDerivs, iHat_));
556  scalarField lamdaJ(constraintRelatedUpdate(rJ, iHat_));
557  scalarField ksiJ(activeDerivs - ATv(lamdaJ, iHat_));
558 
559  // Compute the range space part of the update
560  scalarField g(activeConstraints(iRangeSpace_));
561  scalarField lamdaC(constraintRelatedUpdate(g, iRangeSpace_));
562  scalarField ksiC(ATv(lamdaC, iRangeSpace_));
563 
564  if (debug)
565  {
566  scalar magKsiJ = sqrt(globalSum(sqr(ksiJ))) ;
567  scalarField ksiJUnit(ksiJ/(magKsiJ + SMALL));
568  for (const label i : iHat_[0])
569  {
570  scalarField cDerivs
571  (constraintDerivatives_[i], activeDesignVars_);
572  cDerivs /= sqrt(globalSum(sqr(cDerivs))) + SMALL;
573  Info<< "\tNull space update projected to the " << i
574  << "-th flow constraint gradient "
575  << globalSum(ksiJUnit*cDerivs)
576  << endl;
577  }
578 
579  scalar sumLowConsts(Zero);
580  scalar sumUppConsts(Zero);
581  for (const label il : iHat_[1])
582  {
583  sumLowConsts += ksiJUnit[il];
584  }
585  for (const label iu : iHat_[2])
586  {
587  sumUppConsts += ksiJUnit[iu];
588  }
589  if (globalSum_)
590  {
591  reduce(sumLowConsts, sumOp<scalar>());
592  reduce(sumUppConsts, sumOp<scalar>());
593  }
594  Info<< "\tSum of projections to the lower bound constraints "
595  << sumLowConsts << endl;
596  Info<< "\tSum of projections to the upper bound constraints "
597  << sumUppConsts << endl;
598  }
599 
600  scalar maxKsiJ = gMax(mag(ksiJ));
601  if (adaptiveStep_ && maxKsiJ > VSMALL)
602  {
603  if (counter_ < lastAcceleratedCycle_)
604  {
605  aJ_ = maxDVChange_()/eta_/maxKsiJ;
606  }
607  else if (strictMaxDVChange_)
608  {
609  aJ_ = min(aJ_, maxDVChange_()/eta_/maxKsiJ);
610  }
611 
612  /*
613  // Can become unstable close to the optimum
614  aJ_ =
615  targetObjectiveReduction_*objectiveValue_
616  /(eta_*mag(globalSum(activeDerivs*ksiJ)));
617  */
618  DebugInfo
619  << "aJ set to " << aJ_ << endl;
620  }
621 
622  scalar maxKsiC = gMax(mag(ksiC));
623  if (adaptiveStep_ && maxKsiC > VSMALL)
624  {
625  aC_ =
626  min
627  (
628  targetConstraintReduction_/eta_,
629  maxDVChange_()/eta_/maxKsiC
630  );
631  }
632 
633  if (debug)
634  {
635  Info<< "Mag of ksiJ and ksiC "
636  << Foam::sqrt(globalSum(ksiJ*ksiJ)) << " "
637  << Foam::sqrt(globalSum(ksiC*ksiC))
638  << endl;
639 
640  Info<< "Inner product of ksiJ and ksiC " << globalSum(ksiC*ksiJ)
641  << endl;
642  Info<< "max eta*aJ*ksiJ " << gMax(mag(eta_*aJ_*ksiJ)) << endl;
643  Info<< "max eta*aC*ksiC " << gMax(mag(eta_*aC_*ksiC)) << endl;
644  }
645 
646  correction_.rmap(-eta_*(aJ_*ksiJ + aC_*ksiC), activeDesignVars_);
647 }
648 
649 
651 (
652  const scalarField& v,
653  const labelListList& subsets
654 )
655 {
656  const labelList& iFlow = subsets[0];
657  const labelList& iLower = subsets[1];
658  const labelList& iUpper = subsets[2];
659  label m = iFlow.size();
660  label nl = iLower.size();
661  label nu = iUpper.size();
662  if (v.size() != activeDesignVars_.size())
663  {
665  << "Input vector size not equal to the active design variables"
666  << exit(FatalError);
667  }
668  auto tmvp = tmp<scalarField>::New(m + nl + nu, Zero);
669  scalarField& mvp = tmvp.ref();
670 
671  // Flow-related constraints
672  forAll(iFlow, ic)
673  {
674  scalarField di(constraintDerivatives_[iFlow[ic]], activeDesignVars_);
675  mvp[ic] += globalSum(di*v);
676  }
677 
678  // Lower bounds constraints
679  forAll(iLower, il)
680  {
681  mvp[m + il] = -v[iLower[il]];
682  }
683 
684  // Flow-upper bounds constraints interaction
685  forAll(iUpper, iu)
686  {
687  mvp[m + nl + iu] = v[iUpper[iu]];
688  }
689 
690  return tmvp;
691 }
692 
693 
695 (
696  const scalarField& v,
697  const labelListList& subsets
698 )
699 {
700  const labelList& iFlow = subsets[0];
701  const labelList& iLower = subsets[1];
702  const labelList& iUpper = subsets[2];
703  label m = iFlow.size();
704  label nl = iLower.size();
705  label nu = iUpper.size();
706  if (v.size() != m + nl + nu)
707  {
709  << "Input vector size not equal to the active constraints"
710  << exit(FatalError);
711  }
712  auto tmvp = tmp<scalarField>::New(activeDesignVars_.size(), Zero);
713  scalarField& mvp = tmvp.ref();
714 
715  // Flow-related constraints
716  forAll(iFlow, ic)
717  {
718  scalarField di(constraintDerivatives_[iFlow[ic]], activeDesignVars_);
719  mvp += di*v[ic];
720  }
721 
722  // Lower bounds constraints
723  forAll(iLower, il)
724  {
725  mvp[iLower[il]] -= v[m + il];
726  }
727 
728  // Upper bounds constraints
729  forAll(iUpper, iu)
730  {
731  mvp[iUpper[iu]] += v[m + nl + iu];
732  }
733 
734  return tmvp;
735 }
736 
737 
739 (
740  const labelListList& subsets
741 )
742 {
743  const labelList& iFlow = subsets[0];
744  const labelList& iLower = subsets[1];
745  const labelList& iUpper = subsets[2];
746  label m = iFlow.size();
747  label nl = iLower.size();
748  label nu = iUpper.size();
749  auto tcons = tmp<scalarField>::New(m + nl + nu, Zero);
750  scalarField& cons = tcons.ref();
751 
752  label ic = 0;
753  for (const label i : iFlow)
754  {
755  cons[ic++] = cValues_[i];
756  }
757 
758  const autoPtr<scalarField>& lowerBounds = designVars_->lowerBounds();
759  for (const label i : iLower)
760  {
761  const label iActive = activeDesignVars_[i];
762  cons[ic++] = bcMult_*(lowerBounds()[iActive] - designVars_()[iActive]);
763  }
764 
765  const autoPtr<scalarField>& upperBounds = designVars_->upperBounds();
766  for (const label i : iUpper)
767  {
768  const label iActive = activeDesignVars_[i];
769  cons[ic++] = bcMult_*(designVars_()[iActive] - upperBounds()[iActive]);
770  }
771 
772  return tcons;
773 }
774 
775 
777 (
778  const scalarField& rhs,
779  const labelListList& subsets
780 )
781 {
782  const labelList& iFlow = subsets[0];
783  const labelList& iLower = subsets[1];
784  const labelList& iUpper = subsets[2];
785  label m = iFlow.size();
786  label nl = iLower.size();
787  label nu = iUpper.size();
788  if (rhs.size() != m + nl + nu)
789  {
791  << "rhs should have the dimensions of the active constraints"
792  << exit(FatalError);
793  }
794  auto tres = tmp<scalarField>::New(rhs.size(), Zero);
795  scalarField& res = tres.ref();
796 
797  scalarSquareMatrix lhs(m, m, Zero);
798  scalarField r(m, Zero);
799 
800  forAll(iFlow, i)
801  {
802  const label ic = iFlow[i];
803  scalarField dci(constraintDerivatives_[ic], activeDesignVars_);
804 
805  // Diagonal part of lhs
806  lhs[i][i] += globalSum(dci*dci) ;
807  scalar lowerContr(Zero);
808  scalar upperContr(Zero);
809  for (const label il : iLower)
810  {
811  lowerContr -= dci[il]*dci[il];
812  }
813  for (const label iu : iUpper)
814  {
815  upperContr -= dci[iu]*dci[iu];
816  }
817  if (globalSum_)
818  {
819  reduce(lowerContr, sumOp<scalar>());
820  reduce(upperContr, sumOp<scalar>());
821  }
822  lhs[i][i] += lowerContr + upperContr;
823 
824  // Off diagonal part of lhs
825  for (label j = i + 1; j < m; ++j)
826  {
827  const label jc = iFlow[j];
828  scalarField dcj(constraintDerivatives_[jc], activeDesignVars_);
829  scalar ij = globalSum(dci*dcj);
830  lhs[i][j] += ij;
831  lhs[j][i] += ij;
832 
833  lowerContr = Zero;
834  for (const label il : iLower)
835  {
836  lowerContr -= dci[il]*dcj[il];
837  }
838  upperContr = Zero;
839  for (const label iu : iUpper)
840  {
841  upperContr -= dci[iu]*dcj[iu];
842  }
843 
844  if (globalSum_)
845  {
846  reduce(lowerContr, sumOp<scalar>());
847  reduce(upperContr, sumOp<scalar>());
848  }
849  lhs[i][j] += lowerContr + upperContr;
850  lhs[j][i] += lowerContr + upperContr;
851  }
852 
853  // rhs
854  r[i] = rhs[i];
855  lowerContr = Zero;
856  forAll(iLower, il)
857  {
858  lowerContr += dci[iLower[il]]*rhs[m + il];
859  }
860  upperContr = Zero;
861  forAll(iUpper, iu)
862  {
863  upperContr -= dci[iUpper[iu]]*rhs[m + nl + iu];
864  }
865 
866  if (globalSum_)
867  {
868  reduce(lowerContr, sumOp<scalar>());
869  reduce(upperContr, sumOp<scalar>());
870  }
871  r[i] += lowerContr + upperContr;
872  }
873 
874  // Compute solution
875  SubField<scalar>(res, nl, m) = SubField<scalar>(rhs, nl, m);
876  SubField<scalar>(res, nu, nl + m) = SubField<scalar>(rhs, nu, nl + m);
877  if (m)
878  {
879  scalarField solF(m, Zero);
880  solve(solF, lhs, r);
881  SubField<scalar>(res, m, 0) = solF;
882 
883  forAll(iFlow, i)
884  {
885  scalarField dci(constraintDerivatives_[iFlow[i]], activeDesignVars_);
886  forAll(iLower, il)
887  {
888  res[m + il] += dci[iLower[il]]*solF[i];
889  }
890  forAll(iUpper, iu)
891  {
892  res[m + nl + iu] -= dci[iUpper[iu]]*solF[i];
893  }
894  }
895  }
896  return tres;
897 }
898 
899 
901 (
902  const labelListList& subset,
903  const word& description
904 )
905 {
906  DebugInfo
907  << "Number of flow constraints (" << description << ") "
908  << subset[0].size() << nl;
909  if (includeBoundConstraints_)
910  {
911  DebugInfo
912  << "Number of lower bounds constraints (" << description << ") "
913  << globalSum(subset[1].size())
914  << nl;
915  DebugInfo
916  << "Number of upper bounds constraints (" << description << ") "
917  << globalSum(subset[2].size())
918  << nl;
919  }
921 }
922 
923 
924 // * * * * * * * * * * * * * * * * Constructors * * * * * * * * * * * * * * //
925 
926 Foam::nullSpace::nullSpace
927 (
928  const fvMesh& mesh,
929  const dictionary& dict,
930  autoPtr<designVariables>& designVars,
931  const label nConstraints,
932  const word& type
933 )
934 :
935  constrainedOptimisationMethod(mesh, dict, designVars, nConstraints, type),
936  updateMethod(mesh, dict, designVars, nConstraints, type),
937  includeBoundConstraints_
938  (
939  designVars->upperBounds() && designVars->lowerBounds()
940  ),
941  mu_(nConstraints_, Zero),
942  l_(),
943  u_(),
944  solveDualProblem_
945  (coeffsDict(type).getOrDefault<bool>("solveDualProblem", true)),
946  dualMu_(nConstraints_, Zero),
947  dualL_(),
948  dualU_(),
949  iTilda_(3),
950  iTildaEps_(3),
951  iHat_(3),
952  iRangeSpace_(3),
953  deltaMu_(nConstraints_, Zero),
954  deltaL_(),
955  deltaU_(),
956  deltaDualMu_(nConstraints_, Zero),
957  deltaDualL_(),
958  deltaDualU_(),
959  eps_(1),
960  maxNewtonIters_(coeffsDict(type).getOrDefault<label>("maxIters", 6000)),
961  maxLineSearchIters_
962  (
963  coeffsDict(type).getOrDefault<label>("maxLineSearchIters", 10)
964  ),
965  maxCGIters_
966  (coeffsDict(type).getOrDefault<label>("maxCGIters", maxNewtonIters_)),
967  dualTolerance_
968  (coeffsDict(type).getOrDefault<scalar>("dualTolerance", 1.e-12)),
969  epsConstr_
970  (
971  coeffsDict(type).
972  getOrDefault<scalar>("violatedConstraintsThreshold", 1.e-3)
973  ),
974  epsPerturb_(coeffsDict(type). getOrDefault<scalar>("perturbation", 1.e-2)),
975  aJ_(coeffsDict(type).getOrDefault<scalar>("aJ", 1)),
976  aC_(coeffsDict(type).getOrDefault<scalar>("aC", 1)),
977  adaptiveStep_(coeffsDict(type).getOrDefault<bool>("adaptiveStep", true)),
978  lastAcceleratedCycle_
979  (coeffsDict(type).getOrDefault<label>("lastAcceleratedCycle", 20)),
980  maxDVChange_(nullptr),
981  strictMaxDVChange_
982  (coeffsDict(type).getOrDefault<bool>("strictMaxDVChange", false)),
983  targetConstraintReduction_
984  (
985  coeffsDict(type).
986  getOrDefault<scalar>("targetConstraintReduction", 0.5)
987  ),
988  bcMult_(coeffsDict(type).getOrDefault<scalar>("boundConstraintsMult", 0.5))
989 {
990  if (coeffsDict(type).found("maxDVChange"))
991  {
993  (new scalar(coeffsDict(type).get<scalar>("maxDVChange")));
994  }
995  else if (designVars_().isMaxInitChangeSet())
996  {
997  maxDVChange_.reset(new scalar(designVars_().getMaxInitChange()()));
998  }
999  else if (adaptiveStep_)
1000  {
1002  << "Either maxInitChange or maxDVChange has to be setup when using "
1003  << "an adaptive step"
1004  << exit(FatalError);
1005  }
1006 
1007  // Sanity checks for the bounds of the design variables.
1008  // If all design variables start from either their lower or upper bound
1009  // and there is at least one active flow-related constraint, the systems
1010  // providing the update of the desgin varaibles become singular.
1011  // Issue a warning and change the initial values of the design variables
1012  // by a small ammount, to kick start the optimisation
1013  if (designVars_->lowerBounds() && designVars_->upperBounds())
1014  {
1015  const scalarField& lowerBounds = designVars_->lowerBoundsRef();
1016  const scalarField& upperBounds = designVars_->upperBoundsRef();
1017  scalarField& designVars = designVars_();
1018  boolList isOnLowerBound(designVars.size(), false);
1019  boolList isOnUpperBound(designVars.size(), false);
1020  bool existsNonBoundVar = false;
1021  for (const label activeVarI : designVars_->activeDesignVariables())
1022  {
1023  isOnLowerBound[activeVarI] =
1024  mag(designVars[activeVarI] - lowerBounds[activeVarI]) < SMALL;
1025  isOnUpperBound[activeVarI] =
1026  mag(designVars[activeVarI] - upperBounds[activeVarI]) < SMALL;
1027  existsNonBoundVar =
1028  existsNonBoundVar
1029  || (!isOnLowerBound[activeVarI] && !isOnUpperBound[activeVarI]);
1030  }
1031  if (globalSum_)
1032  {
1033  reduce(existsNonBoundVar, orOp<bool>());
1034  }
1035  if (!existsNonBoundVar)
1036  {
1038  << "All design variables lay on their bound values." << nl
1039  << tab << "This will lead to singular matrices in nullSpace"
1040  << nl
1041  << tab << "Perturbing the design variables by "
1042  << epsPerturb_ << "*(upperBound - lowerBounds)"
1043  << nl
1044  << endl;
1045  scalarField update(designVars.size(), Zero);
1046  for (const label activeVarI : designVars_->activeDesignVariables())
1047  {
1048  if (isOnLowerBound[activeVarI])
1049  {
1050  update[activeVarI] =
1051  epsPerturb_
1052  *(upperBounds[activeVarI] - lowerBounds[activeVarI]);
1053  }
1054  if (isOnUpperBound[activeVarI])
1055  {
1056  update[activeVarI] =
1057  - epsPerturb_
1058  *(upperBounds[activeVarI] - lowerBounds[activeVarI]);
1059  }
1060  }
1061  designVars_->update(update);
1062  }
1063  }
1064 
1065  // Read-in aJ in restarts
1067 }
1068 
1069 
1070 // * * * * * * * * * * * * * * * Member Functions * * * * * * * * * * * * * //
1071 
1073 {
1074  // Update sizes of fields related to the active or saturated
1075  // constraints and initialise values
1076  initialise();
1077 
1078  // Solve the dual problem using a Newton optimiser
1079  solveDualProblem();
1080 
1081  // Update subsets related to the null space and range space updates
1082  updateNullAndRangeSpaceSubsets();
1083 
1084  // Compute the update of the design variables
1086 
1087  // Increase counter
1088  ++counter_;
1089 }
1090 
1091 
1093 {
1094  scalar LagrangePart = objectiveValue_;
1095  const autoPtr<scalarField>& lBounds = designVars_->lowerBounds();
1096  const autoPtr<scalarField>& uBounds = designVars_->upperBounds();
1097  forAll(iTildaEps_[0], i)
1098  {
1099  LagrangePart += mu_[i]*cValues_[iTildaEps_[0][i]];
1100  }
1101  scalar lowerPart = Zero;
1102  forAll(iTildaEps_[1], i)
1103  {
1104  const label iActive = activeDesignVars_[iTildaEps_[1][i]];
1105  lowerPart += l_[i]*(lBounds()[iActive] - designVars_()[iActive]);
1106  }
1107  scalar upperPart = Zero;
1108  forAll(iTildaEps_[2], i)
1109  {
1110  const label iActive = activeDesignVars_[iTildaEps_[2][i]];
1111  upperPart += u_[i]*(designVars_()[iActive] - uBounds()[iActive]);
1112  }
1113  if (globalSum_)
1114  {
1115  reduce(lowerPart, sumOp<scalar>());
1116  reduce(upperPart, sumOp<scalar>());
1117  }
1118  LagrangePart += lowerPart + upperPart;
1119  LagrangePart *= aJ_;
1120 
1121  scalarField g(activeConstraints(iTildaEps_));
1122  scalarField invAAT_g(constraintRelatedUpdate(g, iTildaEps_));
1123 
1124  scalar constraintPart = Zero;
1125  const label gSize = g.size();
1126  const label flowSize = iTildaEps_[0].size();
1127  forAll(iTildaEps_[0], i)
1128  {
1129  constraintPart += g[i]*invAAT_g[i];
1130  }
1131 
1132  scalarField a = SubField<scalar>(g, gSize - flowSize, flowSize);
1133  scalarField b = SubField<scalar>(invAAT_g, gSize - flowSize, flowSize);
1134  constraintPart += globalSum(a*b);
1135  constraintPart *= 0.5*aC_;
1136 
1137  return LagrangePart + constraintPart;
1138 }
1139 
1140 
1142 {
1143  scalarField LagrangeMults(mu_.size() + l_.size() + u_.size(), Zero);
1144  SubField<scalar>(LagrangeMults, mu_.size(), 0) = mu_;
1145  SubField<scalar>(LagrangeMults, l_.size(), mu_.size()) = l_;
1146  SubField<scalar>(LagrangeMults, u_.size(), mu_.size() + l_.size()) = u_;
1147  scalarField deriv(objectiveDerivatives_, activeDesignVars_);
1148  deriv += ATv(LagrangeMults, iTildaEps_);
1149  deriv *= aJ_;
1150 
1151  scalarField g(activeConstraints(iTildaEps_));
1152  scalarField lamdaC(constraintRelatedUpdate(g, iTildaEps_));
1153  scalarField ksiC(ATv(lamdaC, iTildaEps_));
1154 
1155  deriv += aC_*ksiC;
1156 
1157  scalarField derivAll(designVars_().size(), Zero);
1158  derivAll.rmap(deriv, activeDesignVars_);
1159 
1160  return globalSum(sqr(derivAll*correction_));
1161 }
1162 
1163 
1165 {
1166  os.writeEntry("aJ", aJ_);
1167  return updateMethod::writeData(os);
1168 }
1169 
1170 
1171 // ************************************************************************* //
scalarField deltaL_
Definition: nullSpace.H:151
void updateViolatedConstraintsSubsets()
Update the constraint subsets.
Definition: nullSpace.C:432
autoPtr< designVariables > & designVars_
Design variables.
Definition: updateMethod.H:65
scalar lineSearch()
Perform line search and return max residual corresponding to the updated solution.
Definition: nullSpace.C:321
tmp< fvMatrix< Type > > correction(const fvMatrix< Type > &)
Return the correction form of the given matrix by subtracting the matrix multiplied by the current fi...
tmp< scalarField > activeConstraints(const labelListList &subsets)
Collect all constraint values corresponding to the provided indices.
Definition: nullSpace.C:732
dictionary dict
void size(const label n)
Older name for setAddressableSize.
Definition: UList.H:116
tmp< scalarField > constraintRelatedUpdate(const scalarField &rhs, const labelListList &subsets)
Computes the parts of ksiJ and ksiC that require a system solution.
Definition: nullSpace.C:770
scalarField deltaDualMu_
Definition: nullSpace.H:153
void correction()
Compute the update of the design variables as a combination of null space and range space parts...
Definition: nullSpace.C:544
void initialise()
Update sizes of fields related to the constraints.
Definition: nullSpace.C:47
Abstract base class for optimisation methods supporting constraints. Does not add functionality to up...
errorManipArg< error, int > exit(error &err, const int errNo=1)
Definition: errorManip.H:125
dimensioned< typename typeOfMag< Type >::type > mag(const dimensioned< Type > &dt)
error FatalError
Error stream (stdout output on all processes), with additional &#39;FOAM FATAL ERROR&#39; header text and sta...
A list of keyword definitions, which are a keyword followed by a number of values (eg...
Definition: dictionary.H:129
#define FatalErrorInFunction
Report an error message using Foam::FatalError.
Definition: error.H:598
void computeNewtonDirection()
Compute direction for the Newton problem.
Definition: nullSpace.C:209
autoPtr< scalar > maxDVChange_
Max change of the design variables, pertaining to the objective reduction.
Definition: nullSpace.H:232
scalar eps_
Infinitesimal quantity.
Definition: nullSpace.H:163
dimensionedSymmTensor sqr(const dimensionedVector &dv)
constexpr char nl
The newline &#39;\n&#39; character (0x0a)
Definition: Ostream.H:50
Abstract base class for optimisation methods.
Definition: updateMethod.H:50
scalarField deltaDualL_
Definition: nullSpace.H:154
dimensionedScalar sqrt(const dimensionedScalar &ds)
Ostream & endl(Ostream &os)
Add newline and flush stream.
Definition: Ostream.H:531
constexpr char tab
The tab &#39;\t&#39; character(0x09)
Definition: Ostream.H:49
Ostream & writeEntry(const keyType &key, const T &value)
Write a keyword/value entry.
Definition: Ostream.H:321
void updateNullAndRangeSpaceSubsets()
Update the constraint subsets.
Definition: nullSpace.C:452
scalarField mu_
Lagrange multipliers for flow-reated constraints.
Definition: nullSpace.H:76
List< labelList > labelListList
List of labelList.
Definition: labelList.H:38
scalarField deltaMu_
Definition: nullSpace.H:150
Macros for easy insertion into run-time selection tables.
bool found(const word &keyword, enum keyType::option matchOpt=keyType::REGEX) const
Find an entry (const access) with the given keyword.
Definition: dictionaryI.H:104
tmp< scalarField > Av(const scalarField &v, const labelListList &subsets)
A & v.
Definition: nullSpace.C:644
#define forAll(list, i)
Loop across all elements in list.
Definition: stdFoam.H:421
scalarField l_
Lagrange multipliers for the lower bound constraints.
Definition: nullSpace.H:81
void reset(T *p=nullptr) noexcept
Delete managed object and set to new given pointer.
Definition: autoPtrI.H:37
scalarField u_
Lagrange multipliers for the upper bound constraints.
Definition: nullSpace.H:86
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
const dimensionedScalar e
Elementary charge.
Definition: createFields.H:11
dynamicFvMesh & mesh
const dimensionedScalar b
Wien displacement law constant: default SI units: [m.K].
Definition: createFields.H:27
virtual void computeCorrection()
Compute design variables correction.
Definition: nullSpace.C:1065
A class for handling words, derived from Foam::string.
Definition: word.H:63
Field< scalar > scalarField
Specialisation of Field<T> for scalar.
void zeroUpdates()
Zero the updates computed in the previous optimisation cycle.
Definition: nullSpace.C:72
void updateSolution(const scalar step)
Update the current solution using the known direction and the given step length.
Definition: nullSpace.C:419
scalar epsPerturb_
Value to perturb the design variables by, if all of them lay on their bounds at the beginning of the ...
Definition: nullSpace.H:202
static tmp< T > New(Args &&... args)
Construct tmp with forwarding arguments.
Definition: tmp.H:206
void updateCorrectionIndices(const label i, const scalarField &LagrangeMults, const scalarField &dual)
Update constraint indices related to the null and range space part of the correction.
Definition: nullSpace.C:504
label min(const labelHashSet &set, label minValue=labelMax)
Find the min value in labelHashSet, optionally limited by second argument.
Definition: hashSets.C:26
SolverPerformance< Type > solve(faMatrix< Type > &, const dictionary &solverControls)
Solve returning the solution statistics given convergence tolerance.
#define DebugInfo
Report an information message using Foam::Info.
An Ostream is an abstract base class for all output systems (streams, files, token lists...
Definition: Ostream.H:56
const uniformDimensionedVectorField & g
int debug
Static debugging option.
bool readIfPresent(const word &keyword, T &val, enum keyType::option matchOpt=keyType::REGEX) const
Find an entry if present, and assign to T val. FatalIOError if it is found and the number of tokens i...
Type gMax(const FieldField< Field, Type > &f)
OBJstream os(runTime.globalPath()/outputName)
mesh update()
defineTypeNameAndDebug(combustionModel, 0)
dictionary coeffsDict(const word &type) const
Return optional dictionary with parameters specific to each method.
Definition: updateMethod.C:306
virtual scalar meritFunctionDirectionalDerivative()
Directional derivative of the merit function, in the direction of the correction. ...
Definition: nullSpace.C:1134
scalar aJ_
Multiplier of the null space update.
Definition: nullSpace.H:207
Abstract base class for line search methods.
Definition: lineSearch.H:53
List< T > subset(const BoolListType &select, const UList< T > &input, const bool invert=false)
Extract elements of the input list when select is true.
tmp< scalarField > ATv(const scalarField &v, const labelListList &subsets)
A.T & v.
Definition: nullSpace.C:688
bool globalSum_
Whether to use gSum or sum in the inner products.
Definition: updateMethod.H:144
#define WarningInFunction
Report a warning using Foam::Warning.
scalarField deltaU_
Definition: nullSpace.H:152
scalarField deltaDualU_
Definition: nullSpace.H:155
scalarField dualU_
Lagrange multipliers of the dual problem for the upper bound constraints.
Definition: nullSpace.H:112
tmp< scalarField > computeResiduals()
Compute and return residuals based on the current solution.
Definition: nullSpace.C:132
void solveDualProblem()
Solve the dual problem for computing the Lagrange multiplier using a Newton solver.
Definition: nullSpace.C:83
Mesh data needed to do the Finite Volume discretisation.
Definition: fvMesh.H:78
bool adaptiveStep_
Change aJ and aC adaptively.
Definition: nullSpace.H:217
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.
messageStream Info
Information stream (stdout output on master, null elsewhere)
virtual bool writeData(Ostream &os) const
Write useful quantities to files.
Definition: nullSpace.C:1157
scalarField dualMu_
Lagrange multipliers of the dual problem for flow-related constraints.
Definition: nullSpace.H:100
void updateViolatedIndices(const label i, const scalarField &constraints)
Update violated constraints indices (iTilda and iTildaEps)
Definition: nullSpace.C:472
void adjustStep(scalar &step, const scalar value, const scalar update)
Adjust step to satisfy cireteria.
Definition: nullSpace.C:406
Pointer management similar to std::unique_ptr, with some additional methods and type checking...
Definition: HashPtrTable.H:48
List< label > labelList
A List of labels.
Definition: List.H:62
A class for managing temporary objects.
Definition: HashPtrTable.H:50
virtual scalar computeMeritFunction()
Compute the merit function for line search.
Definition: nullSpace.C:1085
SquareMatrix< scalar > scalarSquareMatrix
List< bool > boolList
A List of bools.
Definition: List.H:60
volScalarField & nu
virtual bool writeData(Ostream &os) const
Write continuation data under uniform.
Definition: updateMethod.C:466
dimensioned< typename typeOfMag< Type >::type > magSqr(const dimensioned< Type > &dt)
Namespace for OpenFOAM.
scalarField dualL_
Lagrange multipliers of the dual problem for the lower bound constraints.
Definition: nullSpace.H:106
addToRunTimeSelectionTable(functionObject, pointHistory, dictionary)
static constexpr const zero Zero
Global zero (0)
Definition: zero.H:127
void statistics(const labelListList &subset, const word &description)
Print statistics on the number of flow- and bound-related constraints included in the subset...
Definition: nullSpace.C:894
labelListList iTildaEps_
List of saturated or violated constraints (up to epsConstr_)
Definition: nullSpace.H:130