From fcf75af35840779e440c49caaa56394ad224f0c9 Mon Sep 17 00:00:00 2001 From: acrovato <a.crovato@uliege.be> Date: Sun, 12 Sep 2021 22:54:14 +0200 Subject: [PATCH] Update adjoint (correction + refactoring) --- dart/interfaces/mphys.py | 28 +- dart/src/wAdjoint.cpp | 611 +++++++++++++++++++++------------------ dart/src/wAdjoint.h | 41 ++- dart/tests/adjoint.py | 10 +- dart/tests/adjoint3.py | 24 +- 5 files changed, 385 insertions(+), 329 deletions(-) diff --git a/dart/interfaces/mphys.py b/dart/interfaces/mphys.py index 64591d5..53081bc 100644 --- a/dart/interfaces/mphys.py +++ b/dart/interfaces/mphys.py @@ -136,7 +136,7 @@ class DartMorpher(om.ImplicitComponent): def linearize(self, inputs, outputs, partials): """Compute the mesh jacobian matrix """ - self.adj.omLinearizeMesh() + self.adj.linearizeMesh() def apply_linear(self, inputs, outputs, d_inputs, d_outputs, d_residuals, mode): """Compute the partials derivatives and perform the matrix-vector product @@ -144,7 +144,7 @@ class DartMorpher(om.ImplicitComponent): if mode == 'rev': if 'xv' in d_residuals: if 'xv' in d_outputs: - d_outputs['xv'] += self.adj.omComputeJacVecRmeshMesh(d_residuals['xv']) # dX = dRx_dX^T * dRx + d_outputs['xv'] += self.adj.computeJacVecMesh(d_residuals['xv']) # dX = dRx_dX^T * dRx if 'x_aero' in d_inputs: for i in range(len(self.bnd.nodes)): for j in range(self.dim): @@ -156,7 +156,7 @@ class DartMorpher(om.ImplicitComponent): """Solve for the partials gradients of the mesh residuals """ if mode == 'rev': - d_residuals['xv'] = self.adj.omSolveMesh(d_outputs['xv']) # dRx = dRx_dX^-T * dX + d_residuals['xv'] = self.adj.solveMesh(d_outputs['xv']) # dRx = dRx_dX^-T * dX elif mode == 'fwd': raise NotImplementedError('DartMorpher - forward mode not implemented!\n') @@ -234,9 +234,9 @@ class DartSolver(om.ImplicitComponent): raise NotImplementedError('DartSolver - cannot compute the residuals!\n') def linearize(self, inputs, outputs, partials): - """Compute the flow jacobian matrix + """Compute the flow jacobian matrix (and the other partials of the flow residuals) """ - self.adj.omLinearizeFlow() + self.adj.linearizeFlow() def apply_linear(self, inputs, outputs, d_inputs, d_outputs, d_residuals, mode): """Compute the partials derivatives and perform the matrix-vector product @@ -244,11 +244,11 @@ class DartSolver(om.ImplicitComponent): if mode == 'rev': if 'phi' in d_residuals: if 'phi' in d_outputs: - d_outputs['phi'] += self.adj.omComputeJacVecResidualFlow(d_residuals['phi']) # dPhi = dRphi_dPhi^T * dRphi + d_outputs['phi'] += self.adj.computeJacVecFlow(d_residuals['phi']) # dU = dRu_dU^T * dRu if 'aoa' in d_inputs: - d_inputs['aoa'] += self.adj.omComputeJacVecResidualAoa(d_residuals['phi']) # dAoa = dRphi_dAoa^T * dRphi + d_inputs['aoa'] += self.adj.computeJacVecFlowAoa(d_residuals['phi']) # dA = dRu_dA^T * dRu if 'xv' in d_inputs: - d_inputs['xv'] += self.adj.omComputeJacVecResidualMesh(d_residuals['phi']) # dX = dRphi_dX^T * dRphi + d_inputs['xv'] += self.adj.computeJacVecFlowMesh(d_residuals['phi']) # dX = dRu_dX^T * dRu elif mode == 'fwd': raise NotImplementedError('DartSolver - forward mode not implemented!\n') @@ -256,7 +256,7 @@ class DartSolver(om.ImplicitComponent): """Solve for the partials gradients of the flow residuals """ if mode == 'rev': - d_residuals['phi'] = self.adj.omSolveFlow(d_outputs['phi']) # dRphi = dRphi_dPhi^-T * dPhi + d_residuals['phi'] = self.adj.solveFlow(d_outputs['phi']) # dRu = dRu_dU^-T * dU elif mode == 'fwd': raise NotImplementedError('DartSolver - forward mode not implemented!\n') @@ -304,9 +304,9 @@ class DartLoads(om.ExplicitComponent): if mode == 'rev': if 'f_aero' in d_outputs: if 'xv' in d_inputs: - d_inputs['xv'] += self.qinf * np.asarray(self.adj.omComputeJacVecLoadsMesh(d_outputs['f_aero'], self.bnd)) # dX = dL_dX^T * dL + d_inputs['xv'] += self.qinf * np.asarray(self.adj.computeJacVecLoadsMesh(d_outputs['f_aero'], self.bnd)) # dX = dL_dX^T * dL if 'phi' in d_inputs: - d_inputs['phi'] += self.qinf * np.asarray(self.adj.omComputeJacVecLoadsFlow(d_outputs['f_aero'], self.bnd)) # dPhi = dL_dPhi^T * dL + d_inputs['phi'] += self.qinf * np.asarray(self.adj.computeJacVecLoadsFlow(d_outputs['f_aero'], self.bnd)) # dU = dL_dU^T * dL elif mode == 'fwd': raise NotImplementedError('DartLoads - forward mode not implemented!\n') @@ -368,15 +368,15 @@ class DartCoefficients(om.ExplicitComponent): """Compute the partials derivatives """ # Gradients wrt to angle of attack - dCfAoa = self.adj.omComputeGradientCoefficientsAoa() + dCfAoa = self.adj.computeGradientCoefficientsAoa() partials['cl', 'aoa'] = dCfAoa[0] partials['cd', 'aoa'] = dCfAoa[1] # Gradients wrt to mesh - dCfMsh = self.adj.omComputeGradientCoefficientsMesh() + dCfMsh = self.adj.computeGradientCoefficientsMesh() partials['cl', 'xv'] = dCfMsh[0] partials['cd', 'xv'] = dCfMsh[1] # Gradients wrt to flow variables - dCfPhi = self.adj.omComputeGradientCoefficientsFlow() + dCfPhi = self.adj.computeGradientCoefficientsFlow() partials['cl', 'phi'] = dCfPhi[0] partials['cd', 'phi'] = dCfPhi[1] diff --git a/dart/src/wAdjoint.cpp b/dart/src/wAdjoint.cpp index 50e6bdf..103dce0 100644 --- a/dart/src/wAdjoint.cpp +++ b/dart/src/wAdjoint.cpp @@ -48,12 +48,8 @@ using namespace tbox; using namespace dart; -#define ANSI_COLOR_YELLOW "\x1b[1;33m" -#define ANSI_COLOR_RESET "\x1b[0m" - /** * @brief Initialize the adjoint solver - * @authors Adrien Crovato */ Adjoint::Adjoint(std::shared_ptr<Newton> _sol, std::shared_ptr<tbox::MshDeform> _morph, std::shared_ptr<tbox::LinearSolver> _linsol) : sol(_sol), morph(_morph), linsol(_linsol) { @@ -71,9 +67,6 @@ Adjoint::Adjoint(std::shared_ptr<Newton> _sol, std::shared_ptr<tbox::MshDeform> dCdMsh.resize(sol->pbl->msh->nodes.size(), Eigen::Vector3d::Zero()); dClAoa = 0; dCdAoa = 0; - - // Setup rows (unknown index) - this->mapRows(); } /** @@ -92,186 +85,329 @@ void Adjoint::run() << "Number of threads: " << nthreads << "\n" << std::endl; - // Solve adjoint flow equation + // Compute partial gradients of flow residuals and solve flow adjoint tms["0-AdjFlo"].start(); - this->solve(); + this->linearizeFlow(); // dRu/dU, dRu/dA, dRu/dX + std::vector<std::vector<double>> dCf_U = this->computeGradientCoefficientsFlow(); // {dCl, dCd}/dU + lamClFlo = this->solveFlow(dCf_U[0]); // lambdaClU = dRu/dU^-T * dCl/dU + lamCdFlo = this->solveFlow(dCf_U[1]); // lambdaCdU = dRu/dU^-T * dCd/dU tms["0-AdjFlo"].stop(); - // Compute total gradient of loads with respect to angle of attack + // Compute gradients with respect to AoA tms["1-GradAoA"].start(); - this->computeGradientAoa(); + // partials + std::vector<double> dCf_A = this->computeGradientCoefficientsAoa(); // {dCl, dCd}/dA + // totals + dClAoa = dCf_A[0] - this->computeJacVecFlowAoa(lamClFlo); // dCl/dA - dRu/dA^T * lambdaClU + dCdAoa = dCf_A[1] - this->computeJacVecFlowAoa(lamCdFlo); // dCd/dA - dRu/dA^T * lambdaCdU tms["1-GradAoA"].stop(); - // Compute total gradient of loads with respect to mesh nodes (including deformation) + // Compute gradients with respect to mesh coordinates tms["2-GradMsh"].start(); - this->computeGradientMesh(); + // compute mesh Jacobian + this->linearizeMesh(); // dRx/dX + // partials + std::vector<std::vector<double>> dCf_X = this->computeGradientCoefficientsMesh(); // {dCl, dCd}/dX + std::vector<double> dCl_XU = this->computeJacVecFlowMesh(lamClFlo); + std::vector<double> dCd_XU = this->computeJacVecFlowMesh(lamCdFlo); + for (size_t i = 0; i < sol->pbl->nDim * sol->pbl->msh->nodes.size(); ++i) + { + dCf_X[0][i] -= dCl_XU[i]; // dCl/dX - dRu/dX^T * lambdaClU + dCf_X[1][i] -= dCd_XU[i]; // dCd/dX - dRu/dX^T * lambdaCdU + } + // compute mesh adjoint (totals) + std::vector<double> lamClMsh = this->solveMesh(dCf_X[0]); // lambdaClX = dRx/dX^T * (dCl/dX - dRu/dX^T * lambdaClU) + std::vector<double> lamCdMsh = this->solveMesh(dCf_X[1]); // lambdaCdX = dRx/dX^T * (dCd/dX - dRu/dX^T * lambdaCdU) + // store solution + for (auto n : sol->pbl->msh->nodes) + { + for (int m = 0; m < sol->pbl->nDim; m++) + { + dClMsh[n->row](m) = lamClMsh[sol->pbl->nDim * (n->row) + m]; + dCdMsh[n->row](m) = lamCdMsh[sol->pbl->nDim * (n->row) + m]; + } + } tms["2-GradMsh"].stop(); + // Display + // flow adjoint + std::cout << std::setw(15) << std::left << "Flow adjoint" + << std::setw(15) << std::right << "Res[lambdaCl]" + << std::setw(15) << std::right << "Res[lambdaCd]" << std::endl; + std::cout << std::fixed << std::setprecision(5); + std::cout << std::setw(15) << std::left << "" + << std::setw(15) << std::right << log10((dRu_U * Eigen::Map<Eigen::VectorXd>(lamClFlo.data(), lamClFlo.size()) - Eigen::Map<Eigen::VectorXd>(dCf_U[0].data(), dCf_U[0].size())).norm()) + << std::setw(15) << std::right << log10((dRu_U * Eigen::Map<Eigen::VectorXd>(lamCdFlo.data(), lamCdFlo.size()) - Eigen::Map<Eigen::VectorXd>(dCf_U[1].data(), dCf_U[1].size())).norm()) << std::endl; + // aoa gradients + std::cout << std::setw(15) << std::left << "AoA gradients" + << std::setw(15) << std::right << "dCl_dAoA" + << std::setw(15) << std::right << "dCd_dAoA" << std::endl; + std::cout << std::fixed << std::setprecision(5); + std::cout << std::setw(15) << std::left << "" + << std::setw(15) << std::right << dClAoa + << std::setw(15) << std::right << dCdAoa << std::endl; + // mesh adjoint + std::cout << std::setw(15) << std::left << "Mesh adjoint" + << std::setw(15) << std::right << "Res[lambdaCl]" + << std::setw(15) << std::right << "Res[lambdaCd]" << std::endl; + std::cout << std::fixed << std::setprecision(5); + std::cout << std::setw(15) << std::left << "" + << std::setw(15) << std::right << log10((dRx_X * Eigen::Map<Eigen::VectorXd>(lamClMsh.data(), lamClMsh.size()) - Eigen::Map<Eigen::VectorXd>(dCf_X[0].data(), dCf_X[0].size())).norm()) + << std::setw(15) << std::right << log10((dRx_X * Eigen::Map<Eigen::VectorXd>(lamCdMsh.data(), lamCdMsh.size()) - Eigen::Map<Eigen::VectorXd>(dCf_X[1].data(), dCf_X[1].size())).norm()) << std::endl; + // mesh gradients + std::cout << std::setw(15) << std::left << "Mesh gradients" + << std::setw(15) << std::right << "Norm[dCl_dX]" + << std::setw(15) << std::right << "Norm[dCd_dX]" << std::endl; + std::cout << std::fixed << std::setprecision(5); + std::cout << std::setw(15) << std::left << "" + << std::setw(15) << std::right << Eigen::Map<Eigen::VectorXd>(lamClMsh.data(), lamClMsh.size()).norm() + << std::setw(15) << std::right << Eigen::Map<Eigen::VectorXd>(lamCdMsh.data(), lamCdMsh.size()).norm() << std::endl; + // Display timers - if (verbose > 0) + if (verbose > 1) std::cout << "Adjoint solver CPU" << std::endl << tms; std::cout << std::endl; } /** - * @brief Solve the adjoint steady transonic Full Potential Equation + * @brief Compute the mesh Jacobian + * + * J = dRx/dX */ -void Adjoint::solve() +void Adjoint::linearizeMesh() { - // Build partial gradients of residual wrt to potential - tms["00-AdjFloJac"].start(); - Eigen::SparseMatrix<double, Eigen::RowMajor> dR(sol->pbl->msh->nodes.size(), sol->pbl->msh->nodes.size()); - Eigen::SparseMatrix<double, Eigen::RowMajor> dR_(sol->pbl->msh->nodes.size(), sol->pbl->msh->nodes.size()); - sol->buildJac(dR_); - dR = dR_.transpose(); - tms["00-AdjFloJac"].stop(); - tms["01-AdjFloRes"].start(); - // Build partial gradients of loads wrt potential - Eigen::VectorXd dCl(sol->pbl->msh->nodes.size()), dCd(sol->pbl->msh->nodes.size()); - this->buildGradientLoadsFlow(dCl, dCd); - tms["01-AdjFloRes"].stop(); - - // Solve linear adjoint equations: dR {lambdaCl, lambdaCd} = {dCl, dCd} - tms["02-AdjFloSol"].start(); - Eigen::Map<Eigen::VectorXd> lamCl_(lamClFlo.data(), lamClFlo.size()), lamCd_(lamCdFlo.data(), lamCdFlo.size()); - linsol->compute(dR, Eigen::Map<Eigen::VectorXd>(dCl.data(), dCl.size()), lamCl_); - linsol->compute(dR, Eigen::Map<Eigen::VectorXd>(dCd.data(), dCd.size()), lamCd_); - tms["02-AdjFloSol"].stop(); + dRx_X = Eigen::SparseMatrix<double, Eigen::RowMajor>(sol->pbl->nDim * sol->pbl->msh->nodes.size(), sol->pbl->nDim * sol->pbl->msh->nodes.size()); + morph->build(dRx_X); // J = dRx/dX +} - // Display - std::cout << std::setw(15) << std::left << "Flow adjoint" - << std::setw(15) << std::right << "Res[lambdaCl]" - << std::setw(15) << std::right << "Res[lambdaCd]" << std::endl; - std::cout << std::fixed << std::setprecision(5); - std::cout << std::setw(15) << std::left << "" - << std::setw(15) << std::right << log10((dR * lamCl_ - dCl).norm()) - << std::setw(15) << std::right << log10((dR * lamCd_ - dCd).norm()) << std::endl; +/** + * @brief Solve for the mesh residuals given a gradient (wrt to mesh coordinates) + * + * solve for dRx: dRx_dX^T * dRx = dX + */ +std::vector<double> Adjoint::solveMesh(std::vector<double> const &dX) +{ + // Create maps + Eigen::VectorXd dR(sol->pbl->nDim * sol->pbl->msh->nodes.size()); + Eigen::Map<Eigen::VectorXd> dR_(dR.data(), dR.size()); + Eigen::VectorXd dX_ = Eigen::Map<const Eigen::VectorXd>(dX.data(), dX.size()); + // Solve + linsol->compute(dRx_X.transpose(), Eigen::Map<Eigen::VectorXd>(dX_.data(), dX_.size()), dR_); + return std::vector<double>(dR.data(), dR.data() + dR.size()); } /** - * @brief Compute total gradients of the loads with respect to angle of attack - * @todo one gradient for each surface node + * @brief Compute the matrix-vector product of the mesh Jacobian with the mesh residual + * + * dX = dRx/dX^T * dRx + * @todo not tested */ -void Adjoint::computeGradientAoa() +std::vector<double> Adjoint::computeJacVecMesh(std::vector<double> const &dR) { - // Compute partial gradients with respect to AoA - tms["10-GradAoARes"].start(); - Eigen::VectorXd dR = Eigen::VectorXd::Zero(sol->pbl->msh->nodes.size()); - this->buildGradientResidualAoa(dR); - tms["10-GradAoARes"].stop(); - tms["11-GradAoALoad"].start(); - double dL = 0, dD = 0; - this->buildGradientLoadsAoa(dL, dD); - tms["11-GradAoALoad"].stop(); - // Compute total gradients - dClAoa = (dL - Eigen::Map<Eigen::VectorXd>(lamClFlo.data(), lamClFlo.size()).transpose() * dR); - dCdAoa = (dD - Eigen::Map<Eigen::VectorXd>(lamCdFlo.data(), lamCdFlo.size()).transpose() * dR); + Eigen::VectorXd JdR = dRx_X.transpose() * Eigen::Map<const Eigen::VectorXd>(dR.data(), dR.size()); + return std::vector<double>(JdR.data(), JdR.data() + JdR.size()); +} - // Display - std::cout << std::setw(15) << std::left << "AoA gradients" - << std::setw(15) << std::right << "dCl_dAoA" - << std::setw(15) << std::right << "dCd_dAoA" << std::endl; - std::cout << std::fixed << std::setprecision(5); - std::cout << std::setw(15) << std::left << "" - << std::setw(15) << std::right << dClAoa - << std::setw(15) << std::right << dCdAoa << std::endl; +/** + * @brief Compute the partial gradients of the flow residuals + * + * dRu_U = dRu / dU + * dRu_A = dRu / dA + * dRu_X = dRu / dX + */ +void Adjoint::linearizeFlow() +{ + // Partials wrt flow (Jacobian) + dRu_U = Eigen::SparseMatrix<double, Eigen::RowMajor>(sol->pbl->msh->nodes.size(), sol->pbl->msh->nodes.size()); + sol->buildJac(dRu_U); + // Partials wrt AoA + dRu_A = Eigen::VectorXd::Zero(sol->pbl->msh->nodes.size()); + this->buildGradientResidualAoa(dRu_A); + // Partials wrt mesh + dRu_X = Eigen::SparseMatrix<double, Eigen::RowMajor>(sol->pbl->msh->nodes.size(), sol->pbl->nDim * sol->pbl->msh->nodes.size()); + this->buildGradientResidualMesh(dRu_X); } /** - * @brief Compute total gradients of the loads with respect to mesh nodes + * @brief Solve for the flow residuals given a gradient (wrt to flow variables) + * + * solve for dRu: dRu/dU^T * dRu = dU */ -void Adjoint::computeGradientMesh() +std::vector<double> Adjoint::solveFlow(std::vector<double> const &dU) { - // Build partial gradients of flow residual wrt mesh - tms["20-GradMshRes"].start(); - Eigen::SparseMatrix<double, Eigen::RowMajor> dR(sol->pbl->msh->nodes.size(), sol->pbl->nDim * sol->pbl->msh->nodes.size()); - this->buildGradientResidualMesh(dR); - tms["20-GradMshRes"].stop(); - // Build partial gradients of loads wrt mesh - tms["21-GradMshLoad"].start(); - Eigen::RowVectorXd dCl = Eigen::RowVectorXd::Zero(sol->pbl->nDim * sol->pbl->msh->nodes.size()), dCd = Eigen::RowVectorXd::Zero(sol->pbl->nDim * sol->pbl->msh->nodes.size()); - this->buildGradientLoadsMesh(dCl, dCd); - tms["21-GradMshLoad"].stop(); - - // Build partial gradients of mesh deformation residual wrt mesh - tms["22-GradMshAdj"].start(); - Eigen::SparseMatrix<double, Eigen::RowMajor> Kmesh(sol->pbl->nDim * sol->pbl->msh->nodes.size(), sol->pbl->nDim * sol->pbl->msh->nodes.size()); - Eigen::SparseMatrix<double, Eigen::RowMajor> Kmesh_(sol->pbl->nDim * sol->pbl->msh->nodes.size(), sol->pbl->nDim * sol->pbl->msh->nodes.size()); - morph->build(Kmesh_); - Kmesh = Kmesh_.transpose(); - // Build RHS of mesh deformation adjoint equations: {dCl, dCd} = {dCl, dCd}_x - {lambdaCl, lambdaCd}_flow * dR_x - Eigen::VectorXd dClXyz = (dCl - Eigen::Map<Eigen::VectorXd>(lamClFlo.data(), lamClFlo.size()).transpose() * dR).transpose(); - Eigen::VectorXd dCdXyz = (dCd - Eigen::Map<Eigen::VectorXd>(lamCdFlo.data(), lamCdFlo.size()).transpose() * dR).transpose(); - tms["22-GradMshAdj"].stop(); - // Solve mesh deformation adjoint equations: K {lambdaCl, lambdaCd}_mesh = {dCl, dCd} - tms["23-GradMshSol"].start(); - Eigen::VectorXd lamClMsh = Eigen::VectorXd::Zero(sol->pbl->nDim * sol->pbl->msh->nodes.size()); - Eigen::VectorXd lamCdMsh = Eigen::VectorXd::Zero(sol->pbl->nDim * sol->pbl->msh->nodes.size()); - Eigen::Map<Eigen::VectorXd> lamClMsh_(lamClMsh.data(), lamClMsh.size()), lamCdMsh_(lamCdMsh.data(), lamCdMsh.size()); - linsol->compute(Kmesh, Eigen::Map<Eigen::VectorXd>(dClXyz.data(), dClXyz.size()), lamClMsh_); - linsol->compute(Kmesh, Eigen::Map<Eigen::VectorXd>(dCdXyz.data(), dCdXyz.size()), lamCdMsh_); - tms["23-GradMshSol"].stop(); - // Store solution - for (auto n : sol->pbl->msh->nodes) + // Create maps + Eigen::VectorXd dR(sol->pbl->msh->nodes.size()); + Eigen::Map<Eigen::VectorXd> dR_(dR.data(), dR.size()); + Eigen::VectorXd dU_ = Eigen::Map<const Eigen::VectorXd>(dU.data(), dU.size()); + // Solve + linsol->compute(dRu_U.transpose(), Eigen::Map<Eigen::VectorXd>(dU_.data(), dU_.size()), dR_); + return std::vector<double>(dR.data(), dR.data() + dR.size()); +} + +/** + * @brief Compute the matrix-vector product of the flow Jacobian with the flow residuals + * + * dU = dRu/dU^T * dRu + * @todo not tested + */ +std::vector<double> Adjoint::computeJacVecFlow(std::vector<double> const &dR) +{ + Eigen::VectorXd JdR = dRu_U.transpose() * Eigen::Map<const Eigen::VectorXd>(dR.data(), dR.size()); + return std::vector<double>(JdR.data(), JdR.data() + JdR.size()); +} + +/** + * @brief Compute the matrix-vector product of the gradients of the flow residuals (wrt angle of attack) with the flow residuals + * + * dA = dRu/dA^T * dRu + */ +double Adjoint::computeJacVecFlowAoa(std::vector<double> const &dR) +{ + return dRu_A.transpose() * Eigen::Map<const Eigen::VectorXd>(dR.data(), dR.size()); +} + +/** + * @brief Compute the matrix-vector product of the gradients of the flow residuals (wrt mesh coordinates) with the flow residuals + * + * dX = dRu/dX^T * dRu + */ +std::vector<double> Adjoint::computeJacVecFlowMesh(std::vector<double> const &dR) +{ + Eigen::VectorXd dX = dRu_X.transpose() * Eigen::Map<const Eigen::VectorXd>(dR.data(), dR.size()); + return std::vector<double>(dX.data(), dX.data() + dX.size()); +} + +/** + * @brief Compute the matrix-vector product of the gradients of the loads (wrt flow variables) with the loads, on a given boundary + * + * dU = dL/dU^T * dU + * @todo only for FSI, not tested by run() + * @todo linearize outside (check om) + */ +std::vector<double> Adjoint::computeJacVecLoadsFlow(std::vector<double> const &dL, Boundary const &bnd) +{ + // Build gradients + Eigen::SparseMatrix<double, Eigen::RowMajor> dL_U = Eigen::SparseMatrix<double, Eigen::RowMajor>(3 * bnd.nodes.size(), sol->pbl->msh->nodes.size()); + this->buildGradientLoadsFlow(dL_U, bnd); + // Compute product + Eigen::VectorXd dU = dL_U.transpose() * Eigen::Map<const Eigen::VectorXd>(dL.data(), dL.size()); + return std::vector<double>(dU.data(), dU.data() + dU.size()); +} + +/** + * @brief Compute the matrix-vector product of the gradients of the loads (wrt the mesh coordinates) with the loads, on a given boundary + * + * dX = dL/dX^T * dL + * @todo only for FSI, not tested by run() + * @todo linearize outside (check om) + */ +std::vector<double> Adjoint::computeJacVecLoadsMesh(std::vector<double> const &dL, Boundary const &bnd) +{ + // Build gradients + Eigen::SparseMatrix<double, Eigen::RowMajor> dL_X = Eigen::SparseMatrix<double, Eigen::RowMajor>(3 * bnd.nodes.size(), sol->pbl->nDim * sol->pbl->msh->nodes.size()); + this->buildGradientLoadsMesh(dL_X, bnd); + // Compute product + Eigen::VectorXd dX = dL_X.transpose() * Eigen::Map<const Eigen::VectorXd>(dL.data(), dL.size()); // dMsh = dLoads_dMsh^T * dLoads + return std::vector<double>(dX.data(), dX.data() + dX.size()); +} + +/** + * @brief Compute the gradients of the load coefficients wrt flow variables + * + * dCl/dU, dCd/dU + */ +std::vector<std::vector<double>> Adjoint::computeGradientCoefficientsFlow() +{ + // Compute gradients of lift and drag + Eigen::RowVectorXd dCl = Eigen::RowVectorXd::Zero(sol->pbl->msh->nodes.size()), dCd = Eigen::RowVectorXd::Zero(sol->pbl->msh->nodes.size()); + for (auto bnd : sol->pbl->bnds) { - for (int m = 0; m < sol->pbl->nDim; m++) + // gradients of loads + Eigen::SparseMatrix<double, Eigen::RowMajor> dL = Eigen::SparseMatrix<double, Eigen::RowMajor>(3 * bnd->nodes.size(), sol->pbl->msh->nodes.size()); + this->buildGradientLoadsFlow(dL, *bnd); + // project to lift and drag directions + Eigen::RowVector3d dirL = sol->pbl->dirL.eval().transpose(), dirD = sol->pbl->dirD.eval().transpose(); + for (size_t i = 0; i < bnd->nodes.size(); ++i) { - dClMsh[n->row](m) = lamClMsh(sol->pbl->nDim * (n->row) + m); - dCdMsh[n->row](m) = lamCdMsh(sol->pbl->nDim * (n->row) + m); + dCl += dirL * dL.middleRows(i * 3, 3); + dCd += dirD * dL.middleRows(i * 3, 3); } } + return std::vector<std::vector<double>>{std::vector<double>(dCl.data(), dCl.data() + dCl.size()), std::vector<double>(dCd.data(), dCd.data() + dCd.size())}; +} - // Display - std::cout << std::setw(15) << std::left << "Mesh adjoint" - << std::setw(15) << std::right << "Res[lambdaCl]" - << std::setw(15) << std::right << "Res[lambdaCd]" << std::endl; - std::cout << std::fixed << std::setprecision(5); - std::cout << std::setw(15) << std::left << "" - << std::setw(15) << std::right << log10((Kmesh * lamClMsh - dClXyz).norm()) - << std::setw(15) << std::right << log10((Kmesh * lamCdMsh - dCdXyz).norm()) << std::endl; - std::cout << std::setw(15) << std::left << "Mesh gradients" - << std::setw(15) << std::right << "Norm[dCl_dX]" - << std::setw(15) << std::right << "Norm[dCd_dX]" << std::endl; - std::cout << std::setw(15) << std::left << "" - << std::setw(15) << std::right << lamClMsh.norm() - << std::setw(15) << std::right << lamCdMsh.norm() << std::endl; +/** + * @brief Compute the gradients of the load coefficients wrt angle of attack + * + * dCl/dA, dCd/dA + */ +std::vector<double> Adjoint::computeGradientCoefficientsAoa() +{ + double dCl, dCd; + this->buildGradientLoadsAoa(dCl, dCd); + return std::vector<double>{dCl, dCd}; +} + +/** + * @brief Compute the gradients of the load coefficients wrt mesh coordinates + * + * dCl/dX, dCd/dX + */ +std::vector<std::vector<double>> Adjoint::computeGradientCoefficientsMesh() +{ + // Compute the gradients of the lift and drag + Eigen::RowVectorXd dCl = Eigen::RowVectorXd::Zero(sol->pbl->nDim * sol->pbl->msh->nodes.size()), dCd = Eigen::RowVectorXd::Zero(sol->pbl->nDim * sol->pbl->msh->nodes.size()); + for (auto bnd : sol->pbl->bnds) + { + // gradients of loads + Eigen::SparseMatrix<double, Eigen::RowMajor> dL = Eigen::SparseMatrix<double, Eigen::RowMajor>(3 * bnd->nodes.size(), sol->pbl->nDim * sol->pbl->msh->nodes.size()); + this->buildGradientLoadsMesh(dL, *bnd); + // project to lift and drag directions + Eigen::RowVector3d dirL = sol->pbl->dirL.eval().transpose(), dirD = sol->pbl->dirD.eval().transpose(); + for (size_t i = 0; i < bnd->nodes.size(); ++i) + { + dCl += dirL * dL.middleRows(i * 3, 3); + dCd += dirD * dL.middleRows(i * 3, 3); + } + } + return std::vector<std::vector<double>>{std::vector<double>(dCl.data(), dCl.data() + dCl.size()), std::vector<double>(dCd.data(), dCd.data() + dCd.size())}; } /** - * @brief Build the gradients of the lift and drag with respect to the flow variables - * @todo one gradient for each surface node + * @brief Build the gradients of the loads with respect to the flow variables one a given boundary */ -void Adjoint::buildGradientLoadsFlow(Eigen::VectorXd &dL, Eigen::VectorXd &dD) +void Adjoint::buildGradientLoadsFlow(Eigen::SparseMatrix<double, Eigen::RowMajor> &dL, Boundary const &bnd) { // Multithread + tbb::global_control control(tbb::global_control::max_allowed_parallelism, nthreads); tbb::spin_mutex mutex; - // Reset and compute gradient of lift and drag - dL.setZero(); - dD.setZero(); - for (auto sur : sol->pbl->bnds) - { - tbb::parallel_for_each(sur->groups[0]->tag->elems.begin(), sur->groups[0]->tag->elems.end(), [&](Element *e) { - // Associated volume element - Element *eV = sur->svMap.at(e); - // Build - Eigen::MatrixXd Ae = LoadFunctional::buildGradientFlow(*e, *eV, sol->phi, *sol->pbl->medium->cP); - // Assembly - tbb::spin_mutex::scoped_lock lock(mutex); - for (size_t i = 0; i < e->nodes.size(); ++i) + // Build gradients + std::deque<Eigen::Triplet<double>> T; + tbb::parallel_for_each(bnd.groups[0]->tag->elems.begin(), bnd.groups[0]->tag->elems.end(), [&](Element *e) { + // Associated volume element + Element *eV = bnd.svMap.at(e); + // Build + Eigen::MatrixXd Ae = LoadFunctional::buildGradientFlow(*e, *eV, sol->phi, *sol->pbl->medium->cP); + // Assembly + tbb::spin_mutex::scoped_lock lock(mutex); + for (size_t i = 0; i < e->nodes.size(); ++i) + { + int rowi = bnd.nMap.at(e->nodes[i]); + for (int m = 0; m < 3; ++m) { - Eigen::RowVectorXd AeL = sol->pbl->dirL.eval().transpose() * Ae.block(i * 3, 0, 3, eV->nodes.size()); - Eigen::RowVectorXd AeD = sol->pbl->dirD.eval().transpose() * Ae.block(i * 3, 0, 3, eV->nodes.size()); for (size_t j = 0; j < eV->nodes.size(); ++j) { - Node *nodj = eV->nodes[j]; - dL(nodj->row) += AeL(j); - dD(nodj->row) += AeD(j); + int rowj = eV->nodes[j]->row; + T.push_back(Eigen::Triplet<double>(3 * rowi + m, rowj, Ae(3 * i + m, j))); } } - }); - } + } + }); + dL.setFromTriplets(T.begin(), T.end()); + dL.prune(0.); + dL.makeCompressed(); } /** @@ -280,6 +416,7 @@ void Adjoint::buildGradientLoadsFlow(Eigen::VectorXd &dL, Eigen::VectorXd &dD) void Adjoint::buildGradientResidualAoa(Eigen::VectorXd &dR) { // Multithread + tbb::global_control control(tbb::global_control::max_allowed_parallelism, nthreads); tbb::spin_mutex mutex; // Reset gradient @@ -305,9 +442,8 @@ void Adjoint::buildGradientResidualAoa(Eigen::VectorXd &dR) /** * @brief Compute gradients of the lift and drag with respect to angle of attack - * @todo one gradient for each surface node */ -void Adjoint::buildGradientLoadsAoa(double &dL, double &dD) +void Adjoint::buildGradientLoadsAoa(double &dCl, double &dCd) { // Compute integrated aerodynamic load coefficients (normalized by freestream dynamic pressure and reference area) Eigen::Vector3d Cf(0, 0, 0); @@ -315,16 +451,17 @@ void Adjoint::buildGradientLoadsAoa(double &dL, double &dD) for (size_t i = 0; i < bnd->nodes.size(); ++i) Cf += bnd->nLoads[i]; // Rotate to gradient of flow direction - dD = Cf.dot(sol->pbl->dirD.evalGrad()); - dL = Cf.dot(sol->pbl->dirL.evalGrad()); + dCl = Cf.dot(sol->pbl->dirL.evalGrad()); + dCd = Cf.dot(sol->pbl->dirD.evalGrad()); } /** - * @brief Compute gradients of the residual with respect to mesh nodes + * @brief Compute gradients of the residual with respect to mesh coordinates */ void Adjoint::buildGradientResidualMesh(Eigen::SparseMatrix<double, Eigen::RowMajor> &dR) { // Multithread + tbb::global_control control(tbb::global_control::max_allowed_parallelism, nthreads); tbb::spin_mutex mutex; // List of triplets @@ -348,14 +485,9 @@ void Adjoint::buildGradientResidualMesh(Eigen::SparseMatrix<double, Eigen::RowMa size_t rowi = e->nodes[i]->row; for (size_t j = 0; j < e->nodes.size(); ++j) { + int rowj = e->nodes[j]->row; for (int n = 0; n < sol->pbl->nDim; ++n) - { - for (size_t jj = 0; jj < arows[e->nodes[j]->row].size(); ++jj) - { - int rowj = sol->pbl->nDim * (arows[e->nodes[j]->row][jj]) + n; - T.push_back(Eigen::Triplet<double>(sol->rows[rowi], rowj, Ae1(i, sol->pbl->nDim * j + n))); - } - } + T.push_back(Eigen::Triplet<double>(sol->rows[rowi], sol->pbl->nDim * rowj + n, Ae1(i, sol->pbl->nDim * j + n))); } } // Assembly (supersonic) @@ -367,14 +499,9 @@ void Adjoint::buildGradientResidualMesh(Eigen::SparseMatrix<double, Eigen::RowMa size_t rowi = e->nodes[i]->row; for (size_t j = 0; j < eU->nodes.size(); ++j) { + int rowj = eU->nodes[j]->row; for (int n = 0; n < sol->pbl->nDim; ++n) - { - for (size_t jj = 0; jj < arows[eU->nodes[j]->row].size(); ++jj) - { - int rowj = sol->pbl->nDim * (arows[eU->nodes[j]->row][jj]) + n; - T.push_back(Eigen::Triplet<double>(sol->rows[rowi], rowj, Ae2(i, sol->pbl->nDim * j + n))); - } - } + T.push_back(Eigen::Triplet<double>(sol->rows[rowi], sol->pbl->nDim * rowj + n, Ae2(i, sol->pbl->nDim * j + n))); } } } @@ -395,36 +522,21 @@ void Adjoint::buildGradientResidualMesh(Eigen::SparseMatrix<double, Eigen::RowMa int rowi = we->wkN[i].second->row; for (size_t j = 0; j < we->nColUp; ++j) { + int rowj = we->volUpE->nodes[j]->row; for (int n = 0; n < sol->pbl->nDim; ++n) - { - for (size_t jj = 0; jj < arows[we->volUpE->nodes[j]->row].size(); ++jj) - { - int rowj = sol->pbl->nDim * (arows[we->volUpE->nodes[j]->row][jj]) + n; - T.push_back(Eigen::Triplet<double>(rowi, rowj, Aeu(i, sol->pbl->nDim * j + n))); - } - } + T.push_back(Eigen::Triplet<double>(rowi, sol->pbl->nDim * rowj + n, Aeu(i, sol->pbl->nDim * j + n))); } for (size_t j = 0; j < we->nColLw; ++j) { + int rowj = we->volLwE->nodes[j]->row; for (int n = 0; n < sol->pbl->nDim; ++n) - { - for (size_t jj = 0; jj < arows[we->volLwE->nodes[j]->row].size(); ++jj) - { - int rowj = sol->pbl->nDim * (arows[we->volLwE->nodes[j]->row][jj]) + n; - T.push_back(Eigen::Triplet<double>(rowi, rowj, -Ael(i, sol->pbl->nDim * j + n))); - } - } + T.push_back(Eigen::Triplet<double>(rowi, sol->pbl->nDim * rowj + n, -Ael(i, sol->pbl->nDim * j + n))); } for (size_t j = 0; j < we->surUpE->nodes.size(); ++j) { + int rowj = we->surUpE->nodes[j]->row; for (int n = 0; n < sol->pbl->nDim; ++n) - { - for (size_t jj = 0; jj < arows[we->surUpE->nodes[j]->row].size(); ++jj) - { - int rowj = sol->pbl->nDim * (arows[we->surUpE->nodes[j]->row][jj]) + n; - T.push_back(Eigen::Triplet<double>(rowi, rowj, Aes(i, sol->pbl->nDim * j + n))); - } - } + T.push_back(Eigen::Triplet<double>(rowi, sol->pbl->nDim * rowj + n, Aes(i, sol->pbl->nDim * j + n))); } } }); @@ -443,25 +555,15 @@ void Adjoint::buildGradientResidualMesh(Eigen::SparseMatrix<double, Eigen::RowMa int rowi = ke->teN[i].second->row; for (size_t j = 0; j < ke->nCol; ++j) { + int rowj = ke->volE->nodes[j]->row; for (int n = 0; n < sol->pbl->nDim; ++n) - { - for (size_t jj = 0; jj < arows[ke->volE->nodes[j]->row].size(); ++jj) - { - int rowj = sol->pbl->nDim * (arows[ke->volE->nodes[j]->row][jj]) + n; - T.push_back(Eigen::Triplet<double>(rowi, rowj, Ae(i, sol->pbl->nDim * j + n))); - } - } + T.push_back(Eigen::Triplet<double>(rowi, sol->pbl->nDim * rowj + n, Ae(i, sol->pbl->nDim * j + n))); } for (size_t j = 0; j < ke->surE->nodes.size(); ++j) { + int rowj = ke->surE->nodes[j]->row; for (int n = 0; n < sol->pbl->nDim; ++n) - { - for (size_t jj = 0; jj < arows[ke->surE->nodes[j]->row].size(); ++jj) - { - int rowj = sol->pbl->nDim * (arows[ke->surE->nodes[j]->row][jj]) + n; - T.push_back(Eigen::Triplet<double>(rowi, rowj, Aes(i, sol->pbl->nDim * j + n))); - } - } + T.push_back(Eigen::Triplet<double>(rowi, sol->pbl->nDim * rowj + n, Aes(i, sol->pbl->nDim * j + n))); } } }); @@ -473,110 +575,49 @@ void Adjoint::buildGradientResidualMesh(Eigen::SparseMatrix<double, Eigen::RowMa } /** - * @brief Compute gradients of the lift and drag with respect to mesh nodes - * @todo one gradient for each surface node + * @brief Compute gradients of the loads with respect to mesh coordinates, on a given boundary */ -void Adjoint::buildGradientLoadsMesh(Eigen::RowVectorXd &dL, Eigen::RowVectorXd &dD) +void Adjoint::buildGradientLoadsMesh(Eigen::SparseMatrix<double, Eigen::RowMajor> &dL, Boundary const &bnd) { // Multithread + tbb::global_control control(tbb::global_control::max_allowed_parallelism, nthreads); tbb::spin_mutex mutex; - // Reset and compute gradient of lift and drag - dL.setZero(); - dD.setZero(); - for (auto bnd : sol->pbl->bnds) - { - tbb::parallel_for_each(bnd->groups[0]->tag->elems.begin(), bnd->groups[0]->tag->elems.end(), [&](Element *e) { - // Volume element - Element *eV = bnd->svMap.at(e); - // Build - Eigen::MatrixXd Aes, Aev; - std::tie(Aes, Aev) = LoadFunctional::buildGradientMesh(*e, *eV, sol->phi, *sol->pbl->medium->cP, sol->pbl->nDim); - // Assembly - tbb::spin_mutex::scoped_lock lock(mutex); - for (size_t i = 0; i < e->nodes.size(); ++i) + // Build gradients + std::deque<Eigen::Triplet<double>> T; + tbb::parallel_for_each(bnd.groups[0]->tag->elems.begin(), bnd.groups[0]->tag->elems.end(), [&](Element *e) { + // Volume element + Element *eV = bnd.svMap.at(e); + // Build + Eigen::MatrixXd Aes, Aev; + std::tie(Aes, Aev) = LoadFunctional::buildGradientMesh(*e, *eV, sol->phi, *sol->pbl->medium->cP, sol->pbl->nDim); + // Assembly + tbb::spin_mutex::scoped_lock lock(mutex); + for (size_t i = 0; i < e->nodes.size(); ++i) + { + int rowi = bnd.nMap.at(e->nodes[i]); + for (int m = 0; m < 3; ++m) { - // rotate to flow direction - Eigen::RowVectorXd AesL = sol->pbl->dirL.eval().transpose() * Aes.block(i * 3, 0, 3, sol->pbl->nDim * e->nodes.size()); - Eigen::RowVectorXd AesD = sol->pbl->dirD.eval().transpose() * Aes.block(i * 3, 0, 3, sol->pbl->nDim * e->nodes.size()); - Eigen::RowVectorXd AevL = sol->pbl->dirL.eval().transpose() * Aev.block(i * 3, 0, 3, sol->pbl->nDim * eV->nodes.size()); - Eigen::RowVectorXd AevD = sol->pbl->dirD.eval().transpose() * Aev.block(i * 3, 0, 3, sol->pbl->nDim * eV->nodes.size()); - for (int m = 0; m < sol->pbl->nDim; ++m) + // surface + for (size_t j = 0; j < e->nodes.size(); ++j) { - // surface - for (size_t j = 0; j < e->nodes.size(); ++j) - { - size_t rowj = e->nodes[j]->row; - for (size_t jj = 0; jj < arows[rowj].size(); ++jj) - { - dL.col((arows[rowj][jj]) * sol->pbl->nDim + m) += AesL.col(j * sol->pbl->nDim + m); - dD.col((arows[rowj][jj]) * sol->pbl->nDim + m) += AesD.col(j * sol->pbl->nDim + m); - } - } - // volume - for (size_t j = 0; j < eV->nodes.size(); ++j) - { - size_t rowj = eV->nodes[j]->row; - for (size_t jj = 0; jj < arows[rowj].size(); ++jj) - { - dL.col((arows[rowj][jj]) * sol->pbl->nDim + m) += AevL.col(j * sol->pbl->nDim + m); - dD.col((arows[rowj][jj]) * sol->pbl->nDim + m) += AevD.col(j * sol->pbl->nDim + m); - } - } + int rowj = e->nodes[j]->row; + for (int n = 0; n < sol->pbl->nDim; ++n) + T.push_back(Eigen::Triplet<double>(3 * rowi + m, sol->pbl->nDim * rowj + n, Aes(3 * i + m, sol->pbl->nDim * j + n))); } - } - }); - } -} - -/** - * @brief Create the rows vector for the adjoint mesh - * @todo duplicated from wake - */ -void Adjoint::mapRows() -{ - // Collect each row associated to each node - arows.resize(sol->pbl->msh->nodes.size()); - for (size_t i = 0; i < sol->pbl->msh->nodes.size(); ++i) - arows[i].push_back(sol->pbl->msh->nodes[i]->row); - - // Add duplicated row on the wake - for (auto wake : sol->pbl->wBCs) - { - // Collect nodes on the wake - std::vector<Node *> nodesUp, nodesLw; - for (auto e : wake->groups[0]->tag->elems) - for (auto n : e->nodes) - nodesUp.push_back(n); - for (auto e : wake->groups[1]->tag->elems) - for (auto n : e->nodes) - nodesLw.push_back(n); - std::sort(nodesUp.begin(), nodesUp.end()); - auto it = std::unique(nodesUp.begin(), nodesUp.end()); - nodesUp.resize(std::distance(nodesUp.begin(), it)); - std::sort(nodesLw.begin(), nodesLw.end()); - it = std::unique(nodesLw.begin(), nodesLw.end()); - nodesLw.resize(std::distance(nodesLw.begin(), it)); - // Create map between upper and lower sides, but only retain different nodes - std::map<Node *, Node *> nodMap; - for (auto nUp : nodesUp) - { - for (auto nLw : nodesLw) - { - if ((nUp->pos - nLw->pos).norm() <= 1e-14 && nUp != nLw) + // volume + for (size_t j = 0; j < eV->nodes.size(); ++j) { - nodMap[nUp] = nLw; - break; + size_t rowj = eV->nodes[j]->row; + for (int n = 0; n < sol->pbl->nDim; ++n) + T.push_back(Eigen::Triplet<double>(3 * rowi + m, sol->pbl->nDim * rowj + n, Aev(3 * i + m, sol->pbl->nDim * j + n))); } } } - // Create rows - for (auto p : nodMap) - { - arows[p.first->row].push_back(p.second->row); - arows[p.second->row].push_back(p.first->row); - } - } + }); + dL.setFromTriplets(T.begin(), T.end()); + dL.prune(0.); + dL.makeCompressed(); } /** diff --git a/dart/src/wAdjoint.h b/dart/src/wAdjoint.h index ecac9d7..a378133 100644 --- a/dart/src/wAdjoint.h +++ b/dart/src/wAdjoint.h @@ -32,6 +32,7 @@ namespace dart /** * @brief Adjoint solver class * @authors Adrien Crovato + * @todo add interface for Eigen to avoid using Eigen::Map */ class DART_API Adjoint : public fwk::wSharedObject { @@ -51,8 +52,11 @@ public: double dCdAoa; ///< drag gradient with respect to angle of attack private: - fwk::Timers tms; ///< internal timers - std::vector<std::vector<int>> arows; ///< rows (unknown index) with duplicated dof on wake + Eigen::SparseMatrix<double, Eigen::RowMajor> dRx_X; ///< mesh Jacobian + Eigen::SparseMatrix<double, Eigen::RowMajor> dRu_U; ///< flow Jacobian + Eigen::VectorXd dRu_A; ///< partial gradients of flow residual wrt aoa + Eigen::SparseMatrix<double, Eigen::RowMajor> dRu_X; ///< partial gradients of flow residual wrt mesh + fwk::Timers tms; ///< internal timers public: Adjoint(std::shared_ptr<Newton> _sol, std::shared_ptr<tbox::MshDeform> _morph, std::shared_ptr<tbox::LinearSolver> _linsol); @@ -61,26 +65,37 @@ public: void run(); void save(tbox::MshExport *mshWriter, int n = 0); + // Partial gradients of the mesh residuals + void linearizeMesh(); + std::vector<double> solveMesh(std::vector<double> const &dMesh); + std::vector<double> computeJacVecMesh(std::vector<double> const &dR); + // Partial gradients of the flow residuals + void linearizeFlow(); + std::vector<double> solveFlow(std::vector<double> const &dFlow); + std::vector<double> computeJacVecFlow(std::vector<double> const &dR); + double computeJacVecFlowAoa(std::vector<double> const &dR); + std::vector<double> computeJacVecFlowMesh(std::vector<double> const &dR); + // Partial gradients of the loads + std::vector<double> computeJacVecLoadsFlow(std::vector<double> const &dLoads, Boundary const &bnd); + std::vector<double> computeJacVecLoadsMesh(std::vector<double> const &dLoads, Boundary const &bnd); + // Partial gradients of the coefficients + std::vector<std::vector<double>> computeGradientCoefficientsFlow(); + std::vector<double> computeGradientCoefficientsAoa(); + std::vector<std::vector<double>> computeGradientCoefficientsMesh(); + #ifndef SWIG virtual void write(std::ostream &out) const override; #endif private: - // Adjoint solution and total gradients - void solve(); - void computeGradientAoa(); - void computeGradientMesh(); // Partial gradients wrt with respect to flow variables - void buildGradientLoadsFlow(Eigen::VectorXd &dL, Eigen::VectorXd &dD); + void buildGradientLoadsFlow(Eigen::SparseMatrix<double, Eigen::RowMajor> &dL, Boundary const &bnd); // Partial gradients with respect to angle of attack void buildGradientResidualAoa(Eigen::VectorXd &dR); - void buildGradientLoadsAoa(double &dL, double &dD); - // Partial gradients with respect to mesh + void buildGradientLoadsAoa(double &dCl, double &dCd); + // Partial gradients with respect to mesh coordinates void buildGradientResidualMesh(Eigen::SparseMatrix<double, Eigen::RowMajor> &dR); - void buildGradientLoadsMesh(Eigen::RowVectorXd &dL, Eigen::RowVectorXd &dD); - - // @todo - void mapRows(); + void buildGradientLoadsMesh(Eigen::SparseMatrix<double, Eigen::RowMajor> &dL, Boundary const &bnd); }; } // namespace dart diff --git a/dart/tests/adjoint.py b/dart/tests/adjoint.py index 254fcf7..7151ad4 100644 --- a/dart/tests/adjoint.py +++ b/dart/tests/adjoint.py @@ -88,7 +88,7 @@ def main(): dClAoA = 0 dCdAoA = 0 for n in bnd.nodes: - dx = drot.dot(np.array([n.pos[0] - 1, n.pos[1]])) + dx = drot.dot(np.array([n.pos[0] - 0.25, n.pos[1]])) dClAoA += np.array([adjoint.dClMsh[n.row][0], adjoint.dClMsh[n.row][1]]).dot(dx) dCdAoA += np.array([adjoint.dCdMsh[n.row][0], adjoint.dCdMsh[n.row][1]]).dot(dx) @@ -114,15 +114,15 @@ def main(): if M_inf == 0. and alpha == 0*np.pi/180: tests.add(CTest('dCl_dAoA', adjoint.dClAoa, 6.9, 1e-2)) tests.add(CTest('dCd_dAoA', adjoint.dCdAoa, 0.0, 1e-3)) - tests.add(CTest('dCl_dMsh', dClX, 61.302, 1e-3)) - tests.add(CTest('dCd_dMsh', dCdX, 0.0749, 1e-3)) + tests.add(CTest('dCl_dMsh', dClX, 55.439, 1e-3)) + tests.add(CTest('dCd_dMsh', dCdX, 0.482, 1e-3)) tests.add(CTest('dCl_dAoA (msh)', dClAoA, 6.9, 1e-2)) tests.add(CTest('dCd_dAoA (msh)', dCdAoA, 0.0, 1e-3)) elif M_inf == 0.7 and alpha == 2*np.pi/180: tests.add(CTest('dCl_dAoA', adjoint.dClAoa, 11.8, 1e-2)) # FD 11.835 (step = 1e-5) tests.add(CTest('dCd_dAoA', adjoint.dCdAoa, 0.127, 1e-2)) # 0.12692 (step = 1e-5) - tests.add(CTest('dCl_dMsh', dClX, 99.89, 1e-3)) - tests.add(CTest('dCd_dMsh', dCdX, 0.747, 1e-3)) + tests.add(CTest('dCl_dMsh', dClX, 82.67, 1e-3)) + tests.add(CTest('dCd_dMsh', dCdX, 0.908, 1e-3)) tests.add(CTest('dCl_dAoA (msh)', dClAoA, 11.8, 1e-2)) tests.add(CTest('dCd_dAoA (msh)', dCdAoA, 0.127, 1e-2)) else: diff --git a/dart/tests/adjoint3.py b/dart/tests/adjoint3.py index 2aa430e..2ccb806 100644 --- a/dart/tests/adjoint3.py +++ b/dart/tests/adjoint3.py @@ -89,7 +89,7 @@ def main(): dClAoA = 0 dCdAoA = 0 for n in bnd.nodes: - dx = drot.dot(np.array([n.pos[0] - 1, n.pos[1], n.pos[2]])) + dx = drot.dot(np.array([n.pos[0] - 0.25, n.pos[1], n.pos[2]])) dClAoA += np.array([adjoint.dClMsh[n.row][0], adjoint.dClMsh[n.row][1], adjoint.dClMsh[n.row][2]]).dot(dx) dCdAoA += np.array([adjoint.dCdMsh[n.row][0], adjoint.dCdMsh[n.row][1], adjoint.dCdMsh[n.row][2]]).dot(dx) @@ -110,17 +110,17 @@ def main(): if M_inf == 0.3 and alpha == 3*np.pi/180 and span == 1: tests.add(CTest('dCl/dAoA', adjoint.dClAoa, 2.5, 5e-2)) # FD 2.526 (step 1e-5) tests.add(CTest('dCd/dAoA', adjoint.dCdAoa, 0.146, 5e-2)) # FD 0.1460 (step 1e-5) - tests.add(CTest('dCl_dMsh', dClX, 3.778, 5e-2)) - tests.add(CTest('dCd_dMsh', dCdX, 0.192, 5e-2)) - tests.add(CTest('dCl/dAoA (msh)', dClAoA, 2.7, 5e-2)) - tests.add(CTest('dCd/dAoA (msh)', dCdAoA, 0.151, 5e-2)) - elif M_inf == 0.7 and alpha == 5*np.pi/180 and span == 2: # mesh size must be 0.01 - tests.add(CTest('dCl/dAoA', adjoint.dClAoa, 5.0, 1e-2)) - tests.add(CTest('dCd/dAoA', adjoint.dCdAoa, 0.427, 1e-2)) - tests.add(CTest('dCl_dMsh', dClX, 4.773, 1e-3)) - tests.add(CTest('dCd_dMsh', dCdX, 0.327, 1e-3)) - tests.add(CTest('dCl/dAoA (msh)', dClAoA, 5.2, 1e-2)) - tests.add(CTest('dCd/dAoA (msh)', dCdAoA, 0.441, 1e-2)) + tests.add(CTest('dCl_dMsh', dClX, 2.016, 5e-2)) + tests.add(CTest('dCd_dMsh', dCdX, 0.133, 5e-2)) + tests.add(CTest('dCl/dAoA (msh)', dClAoA, 2.5, 5e-2)) + tests.add(CTest('dCd/dAoA (msh)', dCdAoA, 0.146, 5e-2)) + elif M_inf == 0.7 and alpha == 5*np.pi/180 and span == 2: + tests.add(CTest('dCl/dAoA', adjoint.dClAoa, 4.9, 5e-2)) + tests.add(CTest('dCd/dAoA', adjoint.dCdAoa, 0.454, 5e-2)) + tests.add(CTest('dCl_dMsh', dClX, 2.571, 5e-2)) + tests.add(CTest('dCd_dMsh', dCdX, 0.204, 5e-2)) + tests.add(CTest('dCl/dAoA (msh)', dClAoA, 4.9, 5e-2)) + tests.add(CTest('dCd/dAoA (msh)', dCdAoA, 0.454, 5e-2)) else: raise Exception('Test not defined for this flow') tests.run() -- GitLab