SQP.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) 2007-2019 PCOpt/NTUA
9 Copyright (C) 2013-2019 FOSS GP
10 Copyright (C) 2019-2020 OpenCFD Ltd.
11-------------------------------------------------------------------------------
12License
13 This file is part of OpenFOAM.
14
15 OpenFOAM is free software: you can redistribute it and/or modify it
16 under the terms of the GNU General Public License as published by
17 the Free Software Foundation, either version 3 of the License, or
18 (at your option) any later version.
19
20 OpenFOAM is distributed in the hope that it will be useful, but WITHOUT
21 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
22 FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
23 for more details.
24
25 You should have received a copy of the GNU General Public License
26 along with OpenFOAM. If not, see <http://www.gnu.org/licenses/>.
27
28\*---------------------------------------------------------------------------*/
29
30#include "SQP.H"
31#include "IOmanip.H"
33
34// * * * * * * * * * * * * * * Static Data Members * * * * * * * * * * * * * //
35
36namespace Foam
37{
40 (
42 SQP,
44 );
46 (
48 SQP,
50 );
51}
52
53
54// * * * * * * * * * * * * * Private Member Functions * * * * * * * * * * * //
55
56void Foam::SQP::allocateMatrices()
57{
58 // Set active design variables, if necessary
60 {
62 }
63
64 // Set previous Hessian to be a diagonal matrix
65 SquareMatrix<scalar> temp(activeDesignVars_.size(), I);
66
67 // Allocate correct size and content to Hessian matrices
68 // Has a max. capability of approximately 34000 variables.
69 HessianOld_ = temp;
70 Hessian_ = temp;
71
72 // Set size of Lagrange multipliers
74 lamdas_ = Zero;
75
76 // Set corerction size
79}
80
81
82void Foam::SQP::updateHessian()
83{
84 // Vectors needed to construct the (inverse) Hessian matrix
85 scalarField y(activeDesignVars_.size(), Zero);
86 scalarField s(activeDesignVars_.size(), Zero);
87 scalarField LagrangianDerivativesOld = objectiveDerivativesOld_;
88 forAll(constraintDerivatives_, cI)
89 {
90 LagrangianDerivatives_ -= lamdas_[cI] * constraintDerivatives_[cI];
91 LagrangianDerivativesOld -= lamdas_[cI] * constraintDerivativesOld_[cI];
92 }
93 y.map(LagrangianDerivatives_ - LagrangianDerivativesOld, activeDesignVars_);
94 s.map(correctionOld_, activeDesignVars_);
95
96 scalar ys = globalSum(s*y);
97 if (counter_ == 1 && scaleFirstHessian_)
98 {
99 if (ys > scalar(0))
100 {
101 scalar scaleFactor = ys/globalSum(y*y);
102 Info<< "Scaling Hessian with factor " << scaleFactor << endl;
103 forAll(activeDesignVars_, varI)
104 {
105 HessianOld_[varI][varI] /= scaleFactor;
106 }
107 }
108 else
109 {
111 << " y*s is negative. Skipping the scaling of the first Hessian"
112 << endl;
113 }
114 }
115 scalar sBs = globalSum(leftMult(s, HessianOld_)*s);
116
117 // Check curvature condition
118 scalar theta(1);
119 if (ys < dumpingThreshold_*sBs)
120 {
122 << " y*s is below threshold. Using damped form" << endl;
123 theta = (1 - dumpingThreshold_)*sBs/(sBs - ys);
124 }
125 scalarField r(theta*y + (scalar(1) - theta)*rightMult(HessianOld_, s));
127 << "Unmodified Hessian curvature index " << ys << endl;
129 << "Modified Hessian curvature index " << globalSum(r*s) << endl;
130
131 // Update the Hessian
132 Hessian_ =
133 HessianOld_
134 - outerProd(rightMult(HessianOld_, s), leftMult(s/sBs, HessianOld_))
135 + outerProd(r, r/globalSum(s*r));
136}
137
138
139void Foam::SQP::computeLagrangeMultipliersAndCorrect()
140{
141 SquareMatrix<scalar> HessianInv = inv(Hessian_); //also denoted below as W
142 if (debug > 1)
143 {
144 Info<< "Hessian " << Hessian_ << endl;
145 Info<< "HessianInv " << HessianInv << endl;
146 label n = Hessian_.n();
147 SquareMatrix<scalar> test(n, Zero);
148 for (label k = 0; k < n; k++)
149 {
150 for (label l = 0; l < n; l++)
151 {
152 scalar elem(Zero);
153 for (label i = 0; i < n; i++)
154 {
155 elem += Hessian_[k][i] * HessianInv[i][l];
156 }
157 test[k][l]=elem;
158 }
159 }
160 Info<< "Validation " << test << endl;
161 }
162
163 // Compute new Lagrange multipliers
164 label nc = constraintDerivatives_.size();
165 scalarField activeDerivs(activeDesignVars_.size(), Zero);
166
167 // activeDerivs.map(objectiveDerivatives_, activeDesignVars_);
168 activeDerivs.map(LagrangianDerivatives_, activeDesignVars_);
169 scalarField WgradL = rightMult(HessianInv, activeDerivs);
170
171 scalarField lamdaRHS(nc, Zero);
172 forAll(lamdaRHS, cI)
173 {
174 scalarField activeConsDerivs(activeDesignVars_.size(), Zero);
175 activeConsDerivs.map(constraintDerivatives_[cI], activeDesignVars_);
176 lamdaRHS[cI] = globalSum(activeConsDerivs * WgradL) - cValues_[cI];
177 if (debug > 1)
178 {
179 Info<< "lamdaRHS total|deriv part|constraint part "
180 << lamdaRHS[cI] << " " << globalSum(activeConsDerivs * WgradL)
181 << " " << cValues_[cI] << endl;
182 }
183 }
184
185 // lhs for the lamda system
186 SquareMatrix<scalar> AWA(nc, Zero);
187 PtrList<scalarField> WA(nc);
188 for (label j = 0; j < nc; j++)
189 {
190 scalarField gradcJ(activeDesignVars_.size(), Zero);
191 gradcJ.map(constraintDerivatives_[j], activeDesignVars_);
192 WA.set(j, new scalarField(rightMult(HessianInv, gradcJ)));
193 for (label i = 0; i < nc; i++)
194 {
195 scalarField gradcI(activeDesignVars_.size(), Zero);
196 gradcI.map(constraintDerivatives_[i], activeDesignVars_);
197 AWA[i][j] = globalSum(gradcI * WA[j]);
198 }
199 }
200 SquareMatrix<scalar> invAWA = inv(AWA);
201 scalarField deltaLamda = rightMult(invAWA, lamdaRHS);
202 if (debug > 1)
203 {
204 Info<< "AWA " << AWA << endl;
205 Info<< "AWAInv " << invAWA << endl;
206 Info<< "lamda update " << deltaLamda << endl;
207 }
208 lamdas_ += deltaLamda;
209
210 // Compute design variables correction
211 scalarField activeCorrection(-WgradL);
212 forAll(WA, cI)
213 {
214 activeCorrection += WA[cI]*deltaLamda[cI];
215 }
216 activeCorrection *= etaHessian_;
217 // Transfer correction to the global list
218 correction_ = Zero;
219 forAll(activeDesignVars_, varI)
220 {
221 correction_[activeDesignVars_[varI]] = activeCorrection[varI];
222 }
223 if (counter_ == 0)
224 {
225 correction_ *= eta_;
226 }
227}
228
229
230void Foam::SQP::storeOldFields()
231{
232 objectiveDerivativesOld_ = objectiveDerivatives_;
233 if (constraintDerivativesOld_.empty())
234 {
235 constraintDerivativesOld_.setSize(constraintDerivatives_.size());
236 }
237 forAll(constraintDerivativesOld_, cI)
238 {
239 constraintDerivativesOld_[cI] = constraintDerivatives_[cI];
240 }
241 correctionOld_ = correction_;
242 HessianOld_ = Hessian_;
243}
244
245
246void Foam::SQP::readFromDict()
247{
248 if (optMethodIODict_.headerOk())
249 {
250 optMethodIODict_.readEntry("Hessian", Hessian_);
251 optMethodIODict_.readEntry("HessianOld", HessianOld_);
252 optMethodIODict_.readEntry
253 (
254 "objectiveDerivativesOld",
255 objectiveDerivativesOld_
256 );
257 optMethodIODict_.readEntry
258 (
259 "constraintDerivativesOld",
260 constraintDerivativesOld_
261 );
262 optMethodIODict_.readEntry("correctionOld", correctionOld_);
263 optMethodIODict_.readEntry("lamdas", lamdas_);
264 optMethodIODict_.readEntry("counter", counter_);
265 optMethodIODict_.readEntry("eta", eta_);
266
267 correction_ = scalarField(correctionOld_.size(), Zero);
268
269 if (activeDesignVars_.empty())
270 {
271 activeDesignVars_ = identity(correction_.size());
272 }
273 }
274}
275
276
277// * * * * * * * * * * * * * * * * Constructors * * * * * * * * * * * * * * //
278
280:
282
283 etaHessian_
284 (
285 coeffsDict().getOrDefault<scalar>("etaHessian", 1)
286 ),
287 activeDesignVars_(0),
288 scaleFirstHessian_
289 (
290 coeffsDict().getOrDefault<bool>("scaleFirstHessian", false)
291 ),
292 dumpingThreshold_
293 (
294 coeffsDict().getOrDefault<scalar>("dumpingThreshold", 0.2)
295 ),
296 LagrangianDerivatives_(0),
297 Hessian_(), // construct null matrix since we dont know the dimension yet
298 HessianOld_(),
299 objectiveDerivativesOld_(0),
300 constraintDerivativesOld_(0),
301 correctionOld_(0),
302 lamdas_(0),
303 counter_(0),
304 objFunctionFolder_
305 (
306 mesh_.time().globalPath()/"optimisation"/"objective"/
307 mesh_.time().timeName()
308 ),
309 meritFunctionFile_(nullptr),
310 mu_(Zero),
311 delta_
312 (
313 coeffsDict().getOrDefault<scalar>("delta", 0.1)
314 )
315{
316 if
317 (
318 !coeffsDict().readIfPresent("activeDesignVariables", activeDesignVars_)
319 )
320 {
321 // If not, all available design variables will be used. Number is not
322 // know at the moment
323 Info<< "\t Did not find explicit definition of active design "
324 << "variables. Treating all available ones as active " << endl;
325 }
326
327 // Create folder to merit function
328 if (Pstream::master())
329 {
331 }
332
333 // Read old hessian, correction and derivatives, if present
334 readFromDict();
335}
336
337
338// * * * * * * * * * * * * * * * Member Functions * * * * * * * * * * * * * //
339
341{
342 // Allocate correct sizes in first update
343 if (counter_ == 0)
344 {
345 allocateMatrices();
346 }
347
348 // The first iteration uses a unitary Hessian. No need to update
349 LagrangianDerivatives_ = objectiveDerivatives_;
350 if (counter_ != 0)
351 {
352 updateHessian();
353 }
354
355 // Update lamdas and desing vars
356 computeLagrangeMultipliersAndCorrect();
357
358 // Store fields for the next iteration and write them to file
359 storeOldFields();
360
361 counter_++;
362}
363
364
366{
367 // If condition is not met, update mu value
368 if (mu_ < max(mag(lamdas_)) + delta_)
369 {
370 mu_ = max(mag(lamdas_)) + 2*delta_;
371 if (debug > 1)
372 {
373 Info<< "Updated mu value to " << mu_ << endl;
374 }
375 }
376 scalar L = objectiveValue_ + mu_*sum(mag(cValues_));
377
378 return L;
379}
380
381
383{
384 scalar deriv =
385 globalSum(objectiveDerivatives_*correction_)
386 - mu_*sum(mag(cValues_));
387
388 return deriv;
389}
390
391
393{
395 correctionOld_ = oldCorrection;
396}
397
398
400{
401 // Write updateMethod dictionary
402 optMethodIODict_.add<SquareMatrix<scalar>>("Hessian", Hessian_, true);
403 optMethodIODict_.add<SquareMatrix<scalar>>("HessianOld", HessianOld_, true);
404 optMethodIODict_.
405 add<scalarField>
406 (
407 "objectiveDerivativesOld", objectiveDerivativesOld_, true
408 );
409 optMethodIODict_.
410 add<List<scalarField>>
411 (
412 "constraintDerivativesOld", constraintDerivativesOld_, true
413 );
414 optMethodIODict_.add<scalarField>("correctionOld", correctionOld_, true);
415 optMethodIODict_.add<scalarField>("lamdas", lamdas_, true);
416 optMethodIODict_.add<label>("counter", counter_, true);
417
419
420 // Write merit function
421 scalar constraintPart = sum(mag(cValues_));
422 scalar merit = objectiveValue_ + mu_*constraintPart;
423 if (Pstream::master())
424 {
425 unsigned int width = IOstream::defaultPrecision() + 6;
426 unsigned int constraintsSize = lamdas_.size();
427 constraintsSize = constraintsSize*(width + 1) + 2;
428
429 // Open file and write header
430 if (!meritFunctionFile_)
431 {
432 meritFunctionFile_.reset
433 (
434 new OFstream(objFunctionFolder_/word("meritFunction"))
435 );
436
437 meritFunctionFile_()
438 << setw(1) << "#" << " "
439 << setw(width) << "merit" << " "
440 << setw(width) << "J" << " "
441 << setw(constraintsSize) << "lamdas" << " "
442 << setw(constraintsSize) << "constraints" << " "
443 << setw(width) << "mu" << " "
444 << setw(width) << "constraintContr" << endl;
445
446 }
447
448 meritFunctionFile_()
449 << setw(1) << mesh_.time().value() -1 << " "
450 << setw(width) << merit << " "
451 << setw(width) << objectiveValue_ << " "
452 << setw(1) << "(";
453
454 forAll(lamdas_, cI)
455 {
456 meritFunctionFile_()
457 << setw(width) << lamdas_[cI] << setw(1) << " ";
458 }
459 meritFunctionFile_() << setw(3) << ")(";
460 forAll(cValues_, cI)
461 {
462 meritFunctionFile_()
463 << setw(width) << cValues_[cI] << setw(1) << " ";
464 }
465 meritFunctionFile_() << setw(2) << ") ";
466 meritFunctionFile_() << setw(width) << mu_ << " ";
467 meritFunctionFile_() << setw(width) << constraintPart << endl;
468 }
469}
470
471
472// ************************************************************************* //
Istream and Ostream manipulators taking arguments.
scalar y
label k
label n
Macros for easy insertion into run-time selection tables.
#define addToRunTimeSelectionTable(baseType, thisType, argNames)
Add to construction table with typeName as the key.
static unsigned int defaultPrecision() noexcept
Return the default precision.
Definition: IOstream.H:342
void setSize(const label n)
Alias for resize()
Definition: List.H:218
Output to file stream, using an OSstream.
Definition: OFstream.H:57
The quasi-Newton SQP formula for constrained optimisation.
Definition: SQP.H:57
void computeCorrection()
Compute design variables correction.
Definition: SQP.C:340
virtual scalar meritFunctionDirectionalDerivative()
Definition: SQP.C:382
scalarField lamdas_
Lagrange multipliers.
Definition: SQP.H:94
SquareMatrix< scalar > HessianOld_
The previous Hessian inverse.
Definition: SQP.H:82
labelList activeDesignVars_
Map to active design variables.
Definition: SQP.H:66
virtual scalar computeMeritFunction()
Definition: SQP.C:365
SquareMatrix< scalar > Hessian_
Definition: SQP.H:79
virtual void write()
Write useful quantities to files.
Definition: SQP.C:399
virtual void updateOldCorrection(const scalarField &oldCorrection)
Definition: SQP.C:392
fileName objFunctionFolder_
Name of the objective folder.
Definition: SQP.H:100
A templated (N x N) square matrix of objects of <Type>, containing N*N elements, derived from Matrix.
Definition: SquareMatrix.H:66
bool empty() const noexcept
True if the UList is empty (ie, size() is zero)
Definition: UListI.H:427
void size(const label n)
Older name for setAddressableSize.
Definition: UList.H:114
Abstract base class for optimisation methods supporting constraints. Does not add functionality to up...
A list of keyword definitions, which are a keyword followed by a number of values (eg,...
Definition: dictionary.H:126
Mesh data needed to do the Finite Volume discretisation.
Definition: fvMesh.H:91
splitCell * master() const
Definition: splitCell.H:113
Abstract base class for optimisation methods.
Definition: updateMethod.H:55
scalarField objectiveDerivatives_
Derivatives of the objective functions.
Definition: updateMethod.H:68
scalarField correction_
Design variables correction.
Definition: updateMethod.H:80
dictionary coeffsDict()
Return optional dictionary with parameters specific to each method.
Definition: updateMethod.C:254
PtrList< scalarField > constraintDerivatives_
Derivatives of the constraints.
Definition: updateMethod.H:71
virtual void write()
Write useful quantities to files.
Definition: updateMethod.C:398
virtual void updateOldCorrection(const scalarField &oldCorrection)
Definition: updateMethod.C:390
A class for handling words, derived from Foam::string.
Definition: word.H:68
#define defineTypeNameAndDebug(Type, DebugSwitch)
Define the typeName and debug information.
Definition: className.H:121
bool
Definition: EEqn.H:20
dynamicFvMesh & mesh
gmvFile<< "tracers "<< particles.size()<< nl;for(const passiveParticle &p :particles){ gmvFile<< p.position().x()<< " ";}gmvFile<< nl;for(const passiveParticle &p :particles){ gmvFile<< p.position().y()<< " ";}gmvFile<< nl;for(const passiveParticle &p :particles){ gmvFile<< p.position().z()<< " ";}gmvFile<< nl;forAll(lagrangianScalarNames, i){ word name=lagrangianScalarNames[i];IOField< scalar > s(IOobject(name, runTime.timeName(), cloud::prefix, mesh, IOobject::MUST_READ, IOobject::NO_WRITE))
word timeName
Definition: getTimeIndex.H:3
#define DebugInfo
Report an information message using Foam::Info.
#define WarningInFunction
Report a warning using Foam::Warning.
Namespace for OpenFOAM.
label max(const labelHashSet &set, label maxValue=labelMin)
Find the max value in labelHashSet, optionally limited by second argument.
Definition: hashSets.C:47
labelList identity(const label len, label start=0)
Return an identity map of the given length with (map[i] == i)
Definition: labelList.C:38
bool mkDir(const fileName &pathName, mode_t mode=0777)
Make a directory and return an error if it could not be created.
Definition: MSwindows.C:515
dimensioned< Type > sum(const DimensionedField< Type, GeoMesh > &df)
messageStream Info
Information stream (stdout output on master, null elsewhere)
static const Identity< scalar > I
Definition: Identity.H:94
Field< scalar > scalarField
Specialisation of Field<T> for scalar.
Omanip< int > setw(const int i)
Definition: IOmanip.H:199
Ostream & endl(Ostream &os)
Add newline and flush stream.
Definition: Ostream.H:372
dimensioned< typename typeOfMag< Type >::type > mag(const dimensioned< Type > &dt)
static constexpr const zero Zero
Global zero (0)
Definition: zero.H:131
dimensionedSphericalTensor inv(const dimensionedSphericalTensor &dt)
dictionary dict
#define forAll(list, i)
Loop across all elements in list.
Definition: stdFoam.H:333
propsDict readIfPresent("fields", acceptFields)
const vector L(dict.get< vector >("L"))