From f33075ad67aab8b100fb7efd29f7ab38a9b1918a Mon Sep 17 00:00:00 2001 From: Ping He Date: Thu, 20 Jul 2023 16:07:38 -0500 Subject: [PATCH] Added the deactivate calls for all AD funcs and updated tests. --- src/adjoint/DASolver/DASolver.C | 954 ++++++++++++------ src/adjoint/DASolver/DASolver.H | 22 +- ...SimpleFoamkOmegaFieldInversionOmegaRef.txt | 2 +- ...DASimpleFoamkOmegaSSTFieldInversionRef.txt | 2 +- tests/refs/DAFoam_Test_MphysAeroFieldRef.txt | 29 +- tests/refs/DAFoam_Test_MphysAeroOptRef.txt | 4 +- .../DAFoam_Test_MphysAeroPropCoupledRef.txt | 148 +-- tests/refs/DAFoam_Test_MphysAeroPropRef.txt | 176 ++-- tests/runTests_MphysAeroField.py | 27 +- tests/runTests_MphysAeroOpt.py | 4 +- 10 files changed, 864 insertions(+), 504 deletions(-) diff --git a/src/adjoint/DASolver/DASolver.C b/src/adjoint/DASolver/DASolver.C index c121b4d1..2c4807a0 100644 --- a/src/adjoint/DASolver/DASolver.C +++ b/src/adjoint/DASolver/DASolver.C @@ -475,13 +475,22 @@ void DASolver::calcCouplingFaceCoordsAD( product[i] = volCoordsArray[i].getGradient(); } - delete[] volCoordsArray; - delete[] surfCoordsArray; - // clean up AD this->globalADTape_.clearAdjoints(); this->globalADTape_.reset(); + // ********************************************************************************************** + // clean up OF vars's AD seeds by deactivating the inputs and call the forward func one more time + // ********************************************************************************************** + for (label i = 0; i < daIndexPtr_->nLocalXv; i++) + { + this->globalADTape_.deactivateValue(volCoordsArray[i]); + } + this->calcCouplingFaceCoords(volCoordsArray, surfCoordsArray); + + delete[] volCoordsArray; + delete[] surfCoordsArray; + #endif } @@ -1005,26 +1014,21 @@ void DASolver::getThermalAD( << abort(FatalError); } - // ******************************* NOTE! ****************************** - // This step is tricky. We need to clean up the AD seeds for all variables before the next AD - // This is usually done by theses two calls: updateOFField(wVec) and updateOFMesh(xvVec) - // The above two calls will assign zeros to the gradient() part of all scalar variables because - // the wVec and xvVec's gradient() part is zero (they are double instead of scalar). - // HOWEVER, the above two calls will NOT clean the seeds for field variables's boundary values - // This will not cause problems for most of the functions because their boundary values always - // have zero seeds. However, the getThermal function actually propagates non-zeros seeds for boundary - // values, so here we need to manually clean up the BC's gradient part. NOT doing this will - // mess up the AD computation for the following function. - // The way we clean it is that we set a zero-seed thermal var and call getThermal to propagate - // the zero seeds to all intermediate vars. - // ******************************* NOTE! ****************************** + // clean up AD + this->globalADTape_.clearAdjoints(); + this->globalADTape_.reset(); + + // ********************************************************************************************** + // clean up OF vars's AD seeds by deactivating the inputs and call the forward func one more time + // ********************************************************************************************** + for (label i = 0; i < daIndexPtr_->nLocalXv; i++) { - volCoordsArray[i].setGradient(0.0); + this->globalADTape_.deactivateValue(volCoordsArray[i]); } for (label i = 0; i < daIndexPtr_->nLocalAdjointStates; i++) { - statesArray[i].setGradient(0.0); + this->globalADTape_.deactivateValue(statesArray[i]); } this->getThermal(volCoordsArray, statesArray, thermalArray); @@ -1032,10 +1036,6 @@ void DASolver::getThermalAD( delete[] statesArray; delete[] thermalArray; - // clean up AD - this->globalADTape_.clearAdjoints(); - this->globalADTape_.reset(); - #endif } @@ -1869,10 +1869,7 @@ void DASolver::calcdForceProfiledXvWAD( FatalErrorIn("calcdFvSourcedInputsTPsiAD") << "inputMode not valid" << abort(FatalError); } - daResidualPtr_->correctBoundaryConditions(); - daResidualPtr_->updateIntermediateVariables(); - daModelPtr_->correctBoundaryConditions(); - daModelPtr_->updateIntermediateVariables(); + this->updateStateBoundaryConditions(); // Step 3 this->calcForceProfileInternal(propName, aForce, tForce, rDist, integralForce); @@ -1972,6 +1969,29 @@ void DASolver::calcdForceProfiledXvWAD( this->globalADTape_.clearAdjoints(); this->globalADTape_.reset(); + // ********************************************************************************************** + // clean up OF vars's AD seeds by deactivating the inputs and call the forward func one more time + // ********************************************************************************************** + + if (inputMode == "mesh") + { + forAll(meshPoints, i) + { + for (label j = 0; j < 3; j++) + { + this->globalADTape_.deactivateValue(meshPoints[i][j]); + } + } + meshPtr_->movePoints(meshPoints); + meshPtr_->moving(false); + } + else if (inputMode == "state") + { + this->deactivateStateVariableInput4AD(); + } + this->updateStateBoundaryConditions(); + this->calcForceProfileInternal(propName, aForce, tForce, rDist, integralForce); + #endif } @@ -2640,6 +2660,48 @@ void DASolver::calcdFvSourcedInputsTPsiAD( this->globalADTape_.clearAdjoints(); this->globalADTape_.reset(); + + // ********************************************************************************************** + // clean up OF vars's AD seeds by deactivating the inputs and call the forward func one more time + // ********************************************************************************************** + + if (mode == "aForce") + { + for (label i = 0; i < nPoints; i++) + { + this->globalADTape_.deactivateValue(aForceField[i]); + } + } + else if (mode == "tForce") + { + for (label i = 0; i < nPoints; i++) + { + this->globalADTape_.deactivateValue(tForceField[i]); + } + } + else if (mode == "rDist") + { + for (label i = 0; i < nPoints; i++) + { + this->globalADTape_.deactivateValue(rDistField[i]); + } + } + else if (mode == "targetForce") + { + for (label i = 0; i < 2; i++) + { + this->globalADTape_.deactivateValue(targetForceList[i]); + } + } + else if (mode == "center") + { + for (label i = 0; i < 3; i++) + { + this->globalADTape_.deactivateValue(centerVector[i]); + } + } + + this->calcFvSourceInternal(propName, aForceField, tForceField, rDistField, targetForceList, centerVector, fvSourceVField); #endif } @@ -3537,6 +3599,159 @@ void DASolver::calcdRdAOA( } } +void DASolver::setBCToOFVars( + const dictionary& dvSubDict, + const scalar& BC) +{ + /* + Description: + Assign the boundary condition (BC) value to OF variables + + Input: + + dvSubDict: the design variable subDict that contains the inform for setting BCs + + BC: the BC value + */ + + // get info from dvSubDict. This needs to be defined in the pyDAFoam + // name of the variable for changing the boundary condition + word varName = dvSubDict.getWord("variable"); + // name of the boundary patch + wordList patches; + dvSubDict.readEntry("patches", patches); + // the component of a vector variable, ignore when it is a scalar + label comp = dvSubDict.getLabel("comp"); + + // set the BC + forAll(patches, idxI) + { + word patchName = patches[idxI]; + label patchI = meshPtr_->boundaryMesh().findPatchID(patchName); + if (meshPtr_->thisDb().foundObject(varName)) + { + volVectorField& state(const_cast( + meshPtr_->thisDb().lookupObject(varName))); + // for decomposed domain, don't set BC if the patch is empty + if (meshPtr_->boundaryMesh()[patchI].size() > 0) + { + if (state.boundaryFieldRef()[patchI].type() == "fixedValue") + { + forAll(state.boundaryFieldRef()[patchI], faceI) + { + state.boundaryFieldRef()[patchI][faceI][comp] = BC; + } + } + else if (state.boundaryFieldRef()[patchI].type() == "inletOutlet" + || state.boundaryFieldRef()[patchI].type() == "outletInlet") + { + mixedFvPatchField& inletOutletPatch = + refCast>(state.boundaryFieldRef()[patchI]); + vector val = inletOutletPatch.refValue()[0]; + val[comp] = BC; + inletOutletPatch.refValue() = val; + } + } + } + else if (meshPtr_->thisDb().foundObject(varName)) + { + volScalarField& state(const_cast( + meshPtr_->thisDb().lookupObject(varName))); + // for decomposed domain, don't set BC if the patch is empty + if (meshPtr_->boundaryMesh()[patchI].size() > 0) + { + if (state.boundaryFieldRef()[patchI].type() == "fixedValue") + { + forAll(state.boundaryFieldRef()[patchI], faceI) + { + state.boundaryFieldRef()[patchI][faceI] = BC; + } + } + else if (state.boundaryFieldRef()[patchI].type() == "inletOutlet" + || state.boundaryFieldRef()[patchI].type() == "outletInlet") + { + mixedFvPatchField& inletOutletPatch = + refCast>(state.boundaryFieldRef()[patchI]); + inletOutletPatch.refValue() = BC; + } + } + } + } +} + +void DASolver::getBCFromOFVars( + const dictionary& dvSubDict, + scalar& BC) +{ + /* + Description: + Get the boundary condition (BC) value from OF variables + + Input: + + dvSubDict: the design variable subDict that contains the inform for setting BCs + + Output: + BC: the BC value + */ + + // get info from dvSubDict. This needs to be defined in the pyDAFoam + // name of the variable for changing the boundary condition + word varName = dvSubDict.getWord("variable"); + // name of the boundary patch + wordList patches; + dvSubDict.readEntry("patches", patches); + // the component of a vector variable, ignore when it is a scalar + label comp = dvSubDict.getLabel("comp"); + + // Now get the BC value + forAll(patches, idxI) + { + word patchName = patches[idxI]; + label patchI = meshPtr_->boundaryMesh().findPatchID(patchName); + if (meshPtr_->thisDb().foundObject(varName)) + { + volVectorField& state(const_cast( + meshPtr_->thisDb().lookupObject(varName))); + // for decomposed domain, don't set BC if the patch is empty + if (meshPtr_->boundaryMesh()[patchI].size() > 0) + { + if (state.boundaryFieldRef()[patchI].type() == "fixedValue") + { + BC = state.boundaryFieldRef()[patchI][0][comp]; + } + else if (state.boundaryFieldRef()[patchI].type() == "inletOutlet" + || state.boundaryFieldRef()[patchI].type() == "outletInlet") + { + mixedFvPatchField& inletOutletPatch = + refCast>(state.boundaryFieldRef()[patchI]); + BC = inletOutletPatch.refValue()[0][comp]; + } + } + } + else if (meshPtr_->thisDb().foundObject(varName)) + { + volScalarField& state(const_cast( + meshPtr_->thisDb().lookupObject(varName))); + // for decomposed domain, don't set BC if the patch is empty + if (meshPtr_->boundaryMesh()[patchI].size() > 0) + { + if (state.boundaryFieldRef()[patchI].type() == "fixedValue") + { + BC = state.boundaryFieldRef()[patchI][0]; + } + else if (state.boundaryFieldRef()[patchI].type() == "inletOutlet" + || state.boundaryFieldRef()[patchI].type() == "outletInlet") + { + mixedFvPatchField& inletOutletPatch = + refCast>(state.boundaryFieldRef()[patchI]); + BC = inletOutletPatch.refValue()[0]; + } + } + } + } +} + void DASolver::calcdRdBCTPsiAD( const Vec xvVec, const Vec wVec, @@ -3615,61 +3830,8 @@ void DASolver::calcdRdBCTPsiAD( else { - // get info from dvSubDict. This needs to be defined in the pyDAFoam - // name of the variable for changing the boundary condition - word varName = dvSubDict.getWord("variable"); - // name of the boundary patch - wordList patches; - dvSubDict.readEntry("patches", patches); - // the component of a vector variable, ignore when it is a scalar - label comp = dvSubDict.getLabel("comp"); - - // Now get the BC value - forAll(patches, idxI) - { - word patchName = patches[idxI]; - label patchI = meshPtr_->boundaryMesh().findPatchID(patchName); - if (meshPtr_->thisDb().foundObject(varName)) - { - volVectorField& state(const_cast( - meshPtr_->thisDb().lookupObject(varName))); - // for decomposed domain, don't set BC if the patch is empty - if (meshPtr_->boundaryMesh()[patchI].size() > 0) - { - if (state.boundaryFieldRef()[patchI].type() == "fixedValue") - { - BC = state.boundaryFieldRef()[patchI][0][comp]; - } - else if (state.boundaryFieldRef()[patchI].type() == "inletOutlet" - || state.boundaryFieldRef()[patchI].type() == "outletInlet") - { - mixedFvPatchField& inletOutletPatch = - refCast>(state.boundaryFieldRef()[patchI]); - BC = inletOutletPatch.refValue()[0][comp]; - } - } - } - else if (meshPtr_->thisDb().foundObject(varName)) - { - volScalarField& state(const_cast( - meshPtr_->thisDb().lookupObject(varName))); - // for decomposed domain, don't set BC if the patch is empty - if (meshPtr_->boundaryMesh()[patchI].size() > 0) - { - if (state.boundaryFieldRef()[patchI].type() == "fixedValue") - { - BC = state.boundaryFieldRef()[patchI][0]; - } - else if (state.boundaryFieldRef()[patchI].type() == "inletOutlet" - || state.boundaryFieldRef()[patchI].type() == "outletInlet") - { - mixedFvPatchField& inletOutletPatch = - refCast>(state.boundaryFieldRef()[patchI]); - BC = inletOutletPatch.refValue()[0]; - } - } - } - } + // get the BC value from the OF vars + this->getBCFromOFVars(dvSubDict, BC); // need to reduce the BC value across all processors, this is because some of // the processors might not own the prescribed patches so their BC value will be still -1e16, but // when calling the following reduce function, they will get the correct BC from other processors @@ -3680,59 +3842,7 @@ void DASolver::calcdRdBCTPsiAD( // register BC as the input this->globalADTape_.registerInput(BC); // ******* now set BC ****** - forAll(patches, idxI) - { - word patchName = patches[idxI]; - label patchI = meshPtr_->boundaryMesh().findPatchID(patchName); - if (meshPtr_->thisDb().foundObject(varName)) - { - volVectorField& state(const_cast( - meshPtr_->thisDb().lookupObject(varName))); - // for decomposed domain, don't set BC if the patch is empty - if (meshPtr_->boundaryMesh()[patchI].size() > 0) - { - if (state.boundaryFieldRef()[patchI].type() == "fixedValue") - { - forAll(state.boundaryFieldRef()[patchI], faceI) - { - state.boundaryFieldRef()[patchI][faceI][comp] = BC; - } - } - else if (state.boundaryFieldRef()[patchI].type() == "inletOutlet" - || state.boundaryFieldRef()[patchI].type() == "outletInlet") - { - mixedFvPatchField& inletOutletPatch = - refCast>(state.boundaryFieldRef()[patchI]); - vector val = inletOutletPatch.refValue()[0]; - val[comp] = BC; - inletOutletPatch.refValue() = val; - } - } - } - else if (meshPtr_->thisDb().foundObject(varName)) - { - volScalarField& state(const_cast( - meshPtr_->thisDb().lookupObject(varName))); - // for decomposed domain, don't set BC if the patch is empty - if (meshPtr_->boundaryMesh()[patchI].size() > 0) - { - if (state.boundaryFieldRef()[patchI].type() == "fixedValue") - { - forAll(state.boundaryFieldRef()[patchI], faceI) - { - state.boundaryFieldRef()[patchI][faceI] = BC; - } - } - else if (state.boundaryFieldRef()[patchI].type() == "inletOutlet" - || state.boundaryFieldRef()[patchI].type() == "outletInlet") - { - mixedFvPatchField& inletOutletPatch = - refCast>(state.boundaryFieldRef()[patchI]); - inletOutletPatch.refValue() = BC; - } - } - } - } + this->setBCToOFVars(dvSubDict, BC); } // ******* now set BC done****** // compute residuals @@ -3755,6 +3865,34 @@ void DASolver::calcdRdBCTPsiAD( this->globalADTape_.clearAdjoints(); this->globalADTape_.reset(); + // ********************************************************************************************** + // clean up OF vars's AD seeds by deactivating the inputs and call the forward func one more time + // ********************************************************************************************** + + this->globalADTape_.deactivateValue(BC); + if (designVarName == "MRF") + { + const IOMRFZoneListDF& MRF = meshPtr_->thisDb().lookupObject("MRFProperties"); + scalar& omega = const_cast(MRF.getOmegaRef()); + omega = BC; + } + else if (designVarName == "fvSource") + { + volVectorField& fvSource = const_cast( + meshPtr_->thisDb().lookupObject("fvSource")); + label comp = dvSubDict.getLabel("comp"); + forAll(fvSource, cellI) + { + fvSource[cellI][comp] = BC; + } + } + else + { + this->setBCToOFVars(dvSubDict, BC); + } + this->updateStateBoundaryConditions(); + this->calcResiduals(); + wordList writeJacobians; daOptionPtr_->getAllOptions().readEntry("writeJacobians", writeJacobians); if (writeJacobians.found("dRdBCTPsi") || writeJacobians.found("all")) @@ -3826,61 +3964,8 @@ void DASolver::calcdFdBCAD( } else { - // get info from dvSubDict. This needs to be defined in the pyDAFoam - // name of the variable for changing the boundary condition - word varName = dvSubDict.getWord("variable"); - // name of the boundary patch - wordList patches; - dvSubDict.readEntry("patches", patches); - // the compoent of a vector variable, ignore when it is a scalar - label comp = dvSubDict.getLabel("comp"); - - // Now get the BC value - forAll(patches, idxI) - { - word patchName = patches[idxI]; - label patchI = meshPtr_->boundaryMesh().findPatchID(patchName); - if (meshPtr_->thisDb().foundObject(varName)) - { - volVectorField& state(const_cast( - meshPtr_->thisDb().lookupObject(varName))); - // for decomposed domain, don't set BC if the patch is empty - if (meshPtr_->boundaryMesh()[patchI].size() > 0) - { - if (state.boundaryFieldRef()[patchI].type() == "fixedValue") - { - BC = state.boundaryFieldRef()[patchI][0][comp]; - } - else if (state.boundaryFieldRef()[patchI].type() == "inletOutlet" - || state.boundaryFieldRef()[patchI].type() == "outletInlet") - { - mixedFvPatchField& inletOutletPatch = - refCast>(state.boundaryFieldRef()[patchI]); - BC = inletOutletPatch.refValue()[0][comp]; - } - } - } - else if (meshPtr_->thisDb().foundObject(varName)) - { - volScalarField& state(const_cast( - meshPtr_->thisDb().lookupObject(varName))); - // for decomposed domain, don't set BC if the patch is empty - if (meshPtr_->boundaryMesh()[patchI].size() > 0) - { - if (state.boundaryFieldRef()[patchI].type() == "fixedValue") - { - BC = state.boundaryFieldRef()[patchI][0]; - } - else if (state.boundaryFieldRef()[patchI].type() == "inletOutlet" - || state.boundaryFieldRef()[patchI].type() == "outletInlet") - { - mixedFvPatchField& inletOutletPatch = - refCast>(state.boundaryFieldRef()[patchI]); - BC = inletOutletPatch.refValue()[0]; - } - } - } - } + // get the BC value from the OF vars + this->getBCFromOFVars(dvSubDict, BC); // need to reduce the BC value across all processors, this is because some of // the processors might not own the prescribed patches so their BC value will be still -1e16, but // when calling the following reduce function, they will get the correct BC from other processors @@ -3936,67 +4021,8 @@ void DASolver::calcdFdBCAD( } else { - // get info from dvSubDict. This needs to be defined in the pyDAFoam - // name of the variable for changing the boundary condition - word varName = dvSubDict.getWord("variable"); - // name of the boundary patch - wordList patches; - dvSubDict.readEntry("patches", patches); - // the compoent of a vector variable, ignore when it is a scalar - label comp = dvSubDict.getLabel("comp"); - forAll(patches, idxI) - { - word patchName = patches[idxI]; - label patchI = meshPtr_->boundaryMesh().findPatchID(patchName); - if (meshPtr_->thisDb().foundObject(varName)) - { - volVectorField& state(const_cast( - meshPtr_->thisDb().lookupObject(varName))); - // for decomposed domain, don't set BC if the patch is empty - if (meshPtr_->boundaryMesh()[patchI].size() > 0) - { - if (state.boundaryFieldRef()[patchI].type() == "fixedValue") - { - forAll(state.boundaryFieldRef()[patchI], faceI) - { - state.boundaryFieldRef()[patchI][faceI][comp] = BC; - } - } - else if (state.boundaryFieldRef()[patchI].type() == "inletOutlet" - || state.boundaryFieldRef()[patchI].type() == "outletInlet") - { - mixedFvPatchField& inletOutletPatch = - refCast>(state.boundaryFieldRef()[patchI]); - vector val = inletOutletPatch.refValue()[0]; - val[comp] = BC; - inletOutletPatch.refValue() = val; - } - } - } - else if (meshPtr_->thisDb().foundObject(varName)) - { - volScalarField& state(const_cast( - meshPtr_->thisDb().lookupObject(varName))); - // for decomposed domain, don't set BC if the patch is empty - if (meshPtr_->boundaryMesh()[patchI].size() > 0) - { - if (state.boundaryFieldRef()[patchI].type() == "fixedValue") - { - forAll(state.boundaryFieldRef()[patchI], faceI) - { - state.boundaryFieldRef()[patchI][faceI] = BC; - } - } - else if (state.boundaryFieldRef()[patchI].type() == "inletOutlet" - || state.boundaryFieldRef()[patchI].type() == "outletInlet") - { - mixedFvPatchField& inletOutletPatch = - refCast>(state.boundaryFieldRef()[patchI]); - inletOutletPatch.refValue() = BC; - } - } - } - } + // ******* now set BC ****** + this->setBCToOFVars(dvSubDict, BC); } // update all intermediate variables and boundary conditions this->updateStateBoundaryConditions(); @@ -4025,22 +4051,52 @@ void DASolver::calcdFdBCAD( VecAssemblyBegin(dFdBCPart); VecAssemblyEnd(dFdBCPart); - // need to clear adjoint and tape after the computation is done! - this->globalADTape_.clearAdjoints(); - this->globalADTape_.reset(); - // we need to add dFdBCPart to dFdBC because we want to sum // all dFdBCPart for all parts of this objFuncName. VecAXPY(dFdBC, 1.0, dFdBCPart); - if (daOptionPtr_->getOption